Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c052ed8ed6 | |||
| d4f9486b92 | |||
| 8241914543 | |||
| b5ff16484f | |||
| 2b9dde9aec | |||
| 136e7a99ff | |||
| c626d51f97 | |||
| e8ddc9297d | |||
| 3b614c6172 | |||
| 87a214104e | |||
| 599e1e229d | |||
| 423da7cc4a | |||
| 4b082e9dd2 | |||
| 36803d6b5d | |||
| 5809bc963c | |||
| cb6626cfbc | |||
| 3e18711e09 | |||
| 90d784cc8d | |||
| f839b4064b | |||
| e71837b8b2 | |||
| b45792646b | |||
| 1966562eef | |||
| 883b207c5b | |||
| ca137d0ce4 | |||
| b9a633c86f | |||
| 55a87eb4e9 | |||
| e6b56024b9 | |||
| 8c34a31110 | |||
| f7c32d6e80 | |||
| b3a16ae5d0 | |||
| d013fe4c81 | |||
| 4aefee078e | |||
| 9363e68d51 | |||
| 4af8156da5 | |||
| f086feb005 | |||
| d98d4cd0a3 | |||
| a38d445ae9 | |||
| 0436f6ae27 | |||
| be78959437 | |||
| cccae0d9f6 | |||
| e7d1e905cf | |||
| 20d53a1c71 | |||
| f4ba03d581 | |||
| 0e48d39818 | |||
| 3b30939f99 | |||
| e263cb6e25 | |||
| b805bf4a99 | |||
| fd7b68a344 | |||
| a63488a043 | |||
| 48bcdd8ce9 | |||
| 41dd1cae03 | |||
| 1e86cac3ec | |||
| 22faa5dbdb | |||
| 522900748d | |||
| 4998cc4f87 | |||
| 9221f9d2b5 | |||
| 1127257ad5 | |||
| 599e5fe561 | |||
| 478649a1d7 | |||
| 1c5aa569dd | |||
| c6b31d3086 | |||
| f0a4d361b1 | |||
| 0778639ae2 | |||
| c44b793a19 | |||
| 2b383f046e | |||
| cf0b27d03c | |||
| b2d47760cf | |||
| 714c9ef35b | |||
| b872a8d7f9 | |||
| 56b6722dbe | |||
| 92875bcdee | |||
| 46a99bbe32 | |||
| 408698f1e8 | |||
| e54ce770e7 | |||
| 49be11184e | |||
| d8850b555a | |||
| 93a60b2b40 | |||
| cde81d852f | |||
| 97328144fd | |||
| 3f8333c07b | |||
| 2160645f2c | |||
| d9c2213f50 | |||
| 327d282e23 | |||
| 06781763aa | |||
| 60c7a586c7 | |||
| 2ac3da9035 | |||
| 4dbc5c9b19 | |||
| 6d10ac8c7d | |||
| 8b8c2d627c | |||
| b1026b16da | |||
| e1cc629c55 | |||
| f75c77efa2 | |||
| 3f8ec5b453 | |||
| 94b57475e8 | |||
| a30d56ac16 | |||
| 81077edfa4 | |||
| da3a5d59fa | |||
| 94490532f7 | |||
| 326cac7668 | |||
| 44a09befa0 | |||
| cdddc5bf19 | |||
| 93b8dcd0c3 | |||
| da0d1c20d2 | |||
| 8155e042b5 | |||
| f3832c31bd | |||
| 6a7e6d1135 | |||
| da59c2a211 | |||
| eaba7f3c67 | |||
| bab3326175 | |||
| dc0a82058b | |||
| c5c2732cc9 | |||
| 46debcfa8a | |||
| 3f6d3af5a5 | |||
| c42cb7ac55 | |||
| c65eccc5ee |
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: ⚠️ Bug Report
|
||||
about: Something isn't working as expected
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||
-->
|
||||
|
||||
**My integration setup**
|
||||
|
||||
[ ] CocoaPods cocoapods-xcremotecache plugin
|
||||
[ ] Automatic integration using `xcprepare integrate ...`
|
||||
[ ] Manual integration
|
||||
[ ] Carthage
|
||||
|
||||
**Expected/desired behavior**
|
||||
<!-- Describe what the desired behavior would be. -->
|
||||
|
||||
**Minimal reproduction of the problem with instructions**
|
||||
<!-- Please provide the *STEPS TO REPRODUCE*. -->
|
||||
|
||||
**Producer Logs**
|
||||
<!-- Capture logs from 10 minutes: `log show --predicate 'sender BEGINSWITH "xc"' --style compact --info --debug -last 10m` -->
|
||||
|
||||
<details>
|
||||
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
|
||||
</details>
|
||||
|
||||
**Consumer Logs**
|
||||
<!-- Capture logs from 10 minutes: `log show --predicate 'sender BEGINSWITH "xc"' --style compact --info --debug -last 10m` -->
|
||||
|
||||
<details>
|
||||
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
|
||||
</details>
|
||||
|
||||
**Pods/Carthage file**
|
||||
<!-- Delete if you don't use CocoaPods or Carthage -->
|
||||
|
||||
<details>
|
||||
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
|
||||
</details>
|
||||
|
||||
**Environment**
|
||||
|
||||
* **XCRemoteCache:** X.Y.Z
|
||||
* **cocoapods-xcremotecache:** X.Y.Z <!-- check with `gem list cocoapods-xcremotecache` >
|
||||
* **HTTP cache server:** ... <!-- e.g. demo docker, nginx, AWS etc. >
|
||||
* **Xcode:** X.Y.Z
|
||||
|
||||
**Post build stats**
|
||||
<!--
|
||||
To capture build statistics:
|
||||
* call `xcprepare stats --reset` (or `XCRC/xcprepare stats --reset` for CocoaPods)
|
||||
* Build a project in Xcode
|
||||
* `xcprepare stats` (or `XCRC/xcprepare stats` for CocoaPods)
|
||||
-->
|
||||
|
||||
<details>
|
||||
<pre> [REPLACE THIS WITH YOUR INFORMATION] </pre>
|
||||
</details>
|
||||
|
||||
**Others**
|
||||
<!-- Anything else relevant? Operating system version, , ... -->
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: 📕 Documentation Issue
|
||||
about: Suggestion for a change in a documentation
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||
-->
|
||||
|
||||
**A suggestion**
|
||||
<!-- Describe how could the documentation be improved. -->
|
||||
|
||||
**Which file, section, line**
|
||||
<!-- Provide a section it relates (if exist). -->
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: 🙏 Future Request
|
||||
about: Suggestion for an improvement, either behaviour or implementation
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||
-->
|
||||
|
||||
|
||||
**Expected/desired behavior**
|
||||
<!-- Describe what the desired behavior would be. -->
|
||||
|
||||
**Relevant integration setup**
|
||||
|
||||
[ ] CocoaPods cocoapods-xcremotecache plugin
|
||||
[ ] Automatic integration using `xcprepare integrate ...`
|
||||
[ ] Manual integration
|
||||
[ ] Carthage
|
||||
@@ -9,6 +9,8 @@ jobs:
|
||||
- uses: actions/checkout@v1
|
||||
- name: SwiftLint
|
||||
uses: norio-nomura/action-swiftlint@3.1.0
|
||||
with:
|
||||
args: --strict
|
||||
|
||||
macOS:
|
||||
runs-on: macOS-latest
|
||||
@@ -23,3 +25,5 @@ jobs:
|
||||
run: rake build[release]
|
||||
- name: Test
|
||||
run: rake test
|
||||
- name: E2ETests
|
||||
run: rake e2e_only
|
||||
|
||||
@@ -44,3 +44,29 @@ jobs:
|
||||
file_glob: true
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
cocoapods:
|
||||
name: Publish CocoaPods plugin
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: cocoapods-plugin
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.0'
|
||||
- run: bundle install
|
||||
- name: Publish to RubyGems
|
||||
run: |
|
||||
mkdir -p $HOME/.gem
|
||||
touch $HOME/.gem/credentials
|
||||
chmod 0600 $HOME/.gem/credentials
|
||||
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
||||
gem build *.gemspec
|
||||
CURRENT_VERSION=$(gem list cocoapods-xcremotecache --remote -q | sed 's/[^0-9\.]//g')
|
||||
[ -f cocoapods-xcremotecache-$CURRENT_VERSION.gem ] && echo "Version $CURRENT_VERSION already exists" || gem push *.gem
|
||||
env:
|
||||
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
||||
|
||||
+5
-1
@@ -1,9 +1,13 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
*.xcodeproj/
|
||||
*.xcworkspace/
|
||||
DerivedData
|
||||
/.swiftpm/
|
||||
releases
|
||||
tmp/
|
||||
.idea/
|
||||
xcuserdata
|
||||
*.gem
|
||||
Pods/
|
||||
|
||||
+21
-2
@@ -5,7 +5,6 @@ disabled_rules:
|
||||
- superfluous_disable_command # Disabled since we disable some rules pre-emptively to avoid issues in the future
|
||||
- todo # Temporarily disabled. We have too many right now hiding real issues :(
|
||||
- nesting # Does not make sense anymore since Swift 4 uses nested `CodingKeys` enums for example
|
||||
- trailing_dot_in_comments # Triggers warnings for generated file headers
|
||||
|
||||
opt_in_rules:
|
||||
- anyobject_protocol
|
||||
@@ -64,6 +63,7 @@ excluded:
|
||||
- docs/
|
||||
- fastlane/
|
||||
- DerivedData/
|
||||
- e2eTests/XCRemoteCacheSample/Pods
|
||||
|
||||
attributes:
|
||||
always_on_same_line:
|
||||
@@ -88,6 +88,25 @@ file_header:
|
||||
\/\/ Created by .*? on .*\.
|
||||
\/\/ Copyright © \d{4} .*\. All rights reserved\.
|
||||
\/\/
|
||||
required_pattern: |
|
||||
\/\/ Copyright \(c\) \d{4} Spotify AB\.
|
||||
\/\/
|
||||
\/\/ Licensed to the Apache Software Foundation \(ASF\) under one
|
||||
\/\/ or more contributor license agreements\. See the NOTICE file
|
||||
\/\/ distributed with this work for additional information
|
||||
\/\/ regarding copyright ownership\. The ASF licenses this file
|
||||
\/\/ to you under the Apache License, Version 2.0 \(the
|
||||
\/\/ "License"\); you may not use this file except in compliance
|
||||
\/\/ with the License\. You may obtain a copy of the License at
|
||||
\/\/
|
||||
\/\/ http:\/\/www.apache.org\/licenses\/LICENSE-2\.0
|
||||
\/\/
|
||||
\/\/ Unless required by applicable law or agreed to in writing,
|
||||
\/\/ software distributed under the License is distributed on an
|
||||
\/\/ \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
\/\/ KIND, either express or implied\. See the License for the
|
||||
\/\/ specific language governing permissions and limitations
|
||||
\/\/ under the License\.
|
||||
force_cast: warning
|
||||
force_try: warning
|
||||
implicit_getter: warning
|
||||
@@ -124,6 +143,6 @@ custom_rules:
|
||||
severity: warning
|
||||
trailing_dot_in_comments:
|
||||
name: "Trailing dot in comments"
|
||||
regex: '^[ ]*///?[^\n]*\.\n'
|
||||
regex: '^(?!\/\/\ Copyright\ \(c\)\ \d{4}\ Spotify AB\.|\/\/\ under\ the\ License\.)[ ]*///?[^\n]*\.\n'
|
||||
message: "There shouldn't be trailing dot in comments"
|
||||
severity: warning
|
||||
|
||||
+10
-10
@@ -3,16 +3,16 @@
|
||||
"pins": [
|
||||
{
|
||||
"package": "AEXML",
|
||||
"repositoryURL": "https://github.com/tadija/AEXML",
|
||||
"repositoryURL": "https://github.com/tadija/AEXML.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "8623e73b193386909566a9ca20203e33a09af142",
|
||||
"version": "4.5.0"
|
||||
"revision": "38f7d00b23ecd891e1ee656fa6aeebd6ba04ecc3",
|
||||
"version": "4.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "PathKit",
|
||||
"repositoryURL": "https://github.com/kylef/PathKit",
|
||||
"repositoryURL": "https://github.com/kylef/PathKit.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "73f8e9dca9b7a3078cb79128217dc8f2e585a511",
|
||||
@@ -42,8 +42,8 @@
|
||||
"repositoryURL": "https://github.com/tuist/XcodeProj.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "0b18c3e7a10c241323397a80cb445051f4494971",
|
||||
"version": "8.0.0"
|
||||
"revision": "c75c3acc25460195cfd203a04dde165395bf00e0",
|
||||
"version": "8.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -51,8 +51,8 @@
|
||||
"repositoryURL": "https://github.com/jpsim/Yams.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "53741ba55ecca5c7149d8c9f810913ec80845c69",
|
||||
"version": "3.0.0"
|
||||
"revision": "00c403debcd0a007b854bb35e598466207a2d58c",
|
||||
"version": "5.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -60,8 +60,8 @@
|
||||
"repositoryURL": "https://github.com/marmelroy/Zip.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "80b1c3005ee25b4c7ce46c4029ac3347e8d5e37e",
|
||||
"version": "2.0.0"
|
||||
"revision": "67fa55813b9e7b3b9acee9c0ae501def28746d76",
|
||||
"version": "2.1.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+7
-5
@@ -1,4 +1,5 @@
|
||||
// swift-tools-version:5.1
|
||||
// swift-tools-version:5.3
|
||||
// swiftlint:disable:previous file_header
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package
|
||||
|
||||
import PackageDescription
|
||||
@@ -12,10 +13,10 @@ let package = Package(
|
||||
.executable(name: "xcprebuild", targets: ["xcprebuild"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.0.0"),
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "3.0.0"),
|
||||
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.2"),
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.0.0"),
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.7.1"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
@@ -56,7 +57,8 @@ let package = Package(
|
||||
),
|
||||
.testTarget(
|
||||
name: "XCRemoteCacheTests",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
dependencies: ["XCRemoteCache"],
|
||||
resources: [.copy("TestData")]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
|
||||
- [FAQ](#faq)
|
||||
- [Development](#development)
|
||||
- [Release](#release)
|
||||
* [Releasing CocoaPods plugin](#releasing-cocoapods-plugin)
|
||||
* [Building release package](#building-release-package)
|
||||
- [Contributing](#contributing)
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
@@ -310,7 +311,9 @@ _Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`,
|
||||
| `aws_region` | Region for AWS V4 Signature Authorization. E.g. `eu`. | `""` | ⬜️ |
|
||||
| `aws_service` | Service for AWS V4 Signature Authorization. E.g. `storage`. | `""` | ⬜️ |
|
||||
| `out_of_band_mappings` | A dictionary of files path remapping that should be applied to make it absolute path agnostic on a list of dependencies. Useful if a project refers files out of repo root, either compilation files or precompiled dependencies. Keys represent generic replacement and values are substrings that should be replaced. Example: for mapping `["COOL_LIBRARY": "/CoolLibrary"]` `/CoolLibrary/main.swift`will be represented as `$(COOL_LIBRARY)/main.swift`). Warning: remapping order is not-deterministic so avoid remappings with multiple matchings. | `[:]` | ⬜️ |
|
||||
|
||||
| `disable_certificate_verification` | A Boolean value that opts-in SSL certificate validation is disabled | `false` | ⬜️ |
|
||||
| `disable_vfs_overlay` | A feature flag to disable virtual file system overlay support (temporary) | `false` | ⬜️ |
|
||||
| `custom_rewrite_envs` | A list of extra ENVs that should be used as placeholders in the dependency list. ENV rewrite process is optimistic - does nothing if an ENV is not defined in the pre/postbuild process. | `[]` | ⬜️ |
|
||||
|
||||
## Backend cache server
|
||||
|
||||
@@ -401,12 +404,14 @@ Note: This setup is not recommended and may not be supported in future XCRemoteC
|
||||
* Recommended: multi-targets Xcode project
|
||||
* Recommended: do not use fast-forward PR strategy (use merge or squash instead)
|
||||
* Recommended: avoid `DWARF with dSYM File` "Debug Information Format" build setting. Use `DWARF` instead
|
||||
* Recommended: avoid having a symbolic link in the source root (e.g. placing a project in `/tmp`)
|
||||
|
||||
## Limitations
|
||||
|
||||
* Swift Package Manager (SPM) dependencies are not supported. _Because SPM does not allow customizing Build Settings, XCRemoteCache cannot specify `clang` and `swiftc` wrappers that control if the local compilation should be skipped (cache hit) or not (cache miss)_
|
||||
* Filenames with `_vers.c` suffix are reserved and cannot be used as a source file
|
||||
* All compilation files should be referenced via the git repo root. Referencing `/AbsolutePath/someOther.swift` or `../../someOther.swift` that resolve to the location outside of the git repo root is prohibited.
|
||||
* Using "Precompiled prefix headers" for Objective-C targets is not yet supported. [PR is welcome]
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -421,6 +426,12 @@ Follow the [Development](docs/Development.md) guide. It has all the information
|
||||
To release a version, in [Releases](https://github.com/spotify/XCRemoteCache/releases) draft a new release with `v0.3.0{-rc0}` tag format.
|
||||
Packages with binaries will be automatically uploaded to the GitHub [Releases](https://github.com/spotify/XCRemoteCache/releases) page.
|
||||
|
||||
### Releasing CocoaPods plugin
|
||||
|
||||
Bump a gem version defined in [gem_version.rb](cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb) and create a new release described above.
|
||||
|
||||
A plugin is automatically uploaded to [RubyGems](https://rubygems.org/gems/cocoapods-xcremotecache) if a given version doesn't exist yet.
|
||||
|
||||
### Building release package
|
||||
|
||||
To build a release zip package for a single platform (e.g. `x86_64-apple-macosx`, `arm64-apple-macosx`), call:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# encoding: utf-8
|
||||
require_relative 'tasks/e2e'
|
||||
|
||||
################################
|
||||
# Rake configuration
|
||||
@@ -77,6 +78,12 @@ task :test do
|
||||
spm_test()
|
||||
end
|
||||
|
||||
desc 'build and run E2E tests'
|
||||
task :e2e => [:build, :e2e_only]
|
||||
|
||||
desc 'run E2E tests without building the XCRemoteCache binary'
|
||||
task :e2e_only => ['e2e:run']
|
||||
|
||||
################################
|
||||
# Helper functions
|
||||
################################
|
||||
|
||||
@@ -51,8 +51,6 @@ protocol ArtifactSwiftProductsBuilder {
|
||||
/// # {workingDir}/xccache/produced/include/#{moduleName} (if `moduleName` is defined)
|
||||
class ArtifactSwiftProductsBuilderImpl: ArtifactSwiftProductsBuilder {
|
||||
|
||||
/// List of all required swiftmodule related extensions that should be copied to the artifact
|
||||
private static let swiftmoduleExtensionsToInclude = ["swiftmodule", "swiftdoc", "swiftsourceinfo"]
|
||||
private let workingDir: URL
|
||||
private let moduleName: String?
|
||||
private let fileManager: FileManager
|
||||
|
||||
@@ -30,6 +30,7 @@ enum SwiftmoduleFileExtension: String {
|
||||
case swiftmodule
|
||||
case swiftdoc
|
||||
case swiftsourceinfo
|
||||
case swiftinterface
|
||||
}
|
||||
|
||||
extension SwiftmoduleFileExtension {
|
||||
@@ -38,5 +39,6 @@ extension SwiftmoduleFileExtension {
|
||||
.swiftmodule: .required,
|
||||
.swiftdoc: .required,
|
||||
.swiftsourceinfo: .optional,
|
||||
.swiftinterface: .optional,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class ThinningCreatorPlugin: ArtifactCreatorPlugin {
|
||||
|
||||
/// Default Initializer
|
||||
/// - Parameter targetTempDir: Location of current target-specific temp dir (TARGET_TEMP_DIR)
|
||||
/// - Parameter modeMarkerPath: path of maker file that informs if a given target can reuse remote artifacts.
|
||||
/// - Parameter modeMarkerPath: path of maker file that informs if a given target can reuse remote artifacts
|
||||
/// - Parameter dirScanner: scanner to access disk and read files and directories hierarchy
|
||||
init(targetTempDir: URL, modeMarkerPath: String, dirScanner: DirScanner) {
|
||||
self.targetTempDir = targetTempDir
|
||||
@@ -94,7 +94,7 @@ class ThinningCreatorPlugin: ArtifactCreatorPlugin {
|
||||
// ProducerFast mode:
|
||||
// If a target reused already existing artifact, it still has `$(TARGET_TEMP_DIR)/rc.enabled` marker file
|
||||
// and the reused zip is placed in:
|
||||
// `$(TARGET_TEMP_DIR)/xccache/{{fileKey}}.zip` location.
|
||||
// `$(TARGET_TEMP_DIR)/xccache/{{fileKey}}.zip` location
|
||||
|
||||
let targetEnabledMarker = tempDir.appendingPathComponent(modeMarkerPath)
|
||||
let targetReusedArtifactRootDir = tempDir.appendingPathComponent("xccache")
|
||||
|
||||
@@ -25,7 +25,7 @@ class ThinningConsumerPlugin {
|
||||
|
||||
deinit {
|
||||
// initialised but never run plugin suggests that standard target fallbacks to the local development
|
||||
// and DerivedData still misses build artifacts.
|
||||
// and DerivedData still misses build artifacts
|
||||
guard wasRun else {
|
||||
let errorMessage = """
|
||||
\(type(of: self)) plugin has never been run, thinning cannot be supported. Verify you \
|
||||
|
||||
@@ -153,7 +153,7 @@ class Postbuild {
|
||||
// Replace all local paths to the generic ones (e.g. $SRCROOT)
|
||||
let remappers = [remapper] + creatorPlugins.compactMap(\.customPathsRemapper)
|
||||
let remapper = DependenciesRemapperComposite(remappers)
|
||||
let abstractFingerprintFiles = remapper.replace(localPaths: dependencies.map(\.path))
|
||||
let abstractFingerprintFiles = try remapper.replace(localPaths: dependencies.map(\.path))
|
||||
// TODO: use `inputs` read by dependenciesReader
|
||||
var meta = MainArtifactMeta(
|
||||
dependencies: abstractFingerprintFiles,
|
||||
|
||||
@@ -76,12 +76,16 @@ public struct PostbuildContext {
|
||||
let derivedSourcesDir: URL
|
||||
/// List of all targets to downloaded from the thinning aggregation target
|
||||
var thinnedTargets: [String]
|
||||
/// Action type: build, indexbuild etc.
|
||||
/// Action type: build, indexbuild etc
|
||||
var action: BuildActionType
|
||||
let modeMarkerPath: String
|
||||
/// location of the json file that define virtual files system overlay
|
||||
/// (mappings of the virtual location file -> local file path)
|
||||
let overlayHeadersPath: URL
|
||||
}
|
||||
|
||||
extension PostbuildContext {
|
||||
// swiftlint:disable:next function_body_length
|
||||
init(_ config: XCRemoteCacheConfig, env: [String: String]) throws {
|
||||
mode = config.mode
|
||||
let targetNameValue: String = try env.readEnv(key: "TARGET_NAME")
|
||||
@@ -117,7 +121,7 @@ extension PostbuildContext {
|
||||
dSYMPath = try env.readEnv(key: "DWARF_DSYM_FOLDER_PATH")
|
||||
.appendingPathComponent(env.readEnv(key: "DWARF_DSYM_FILE_NAME"))
|
||||
builtProductsDir = try env.readEnv(key: "BUILT_PRODUCTS_DIR")
|
||||
if let contentsFolderPath = env.readEnv(key: "CONTENTS_FOLDER_PATH") {
|
||||
if let contentsFolderPath: String = env.readEnv(key: "CONTENTS_FOLDER_PATH") {
|
||||
bundleDir = productsDir.appendingPathComponent(contentsFolderPath)
|
||||
} else {
|
||||
bundleDir = nil
|
||||
@@ -127,5 +131,7 @@ extension PostbuildContext {
|
||||
thinnedTargets = thinFocusedTargetsString.split(separator: ",").map(String.init)
|
||||
action = (try? BuildActionType(rawValue: env.readEnv(key: "ACTION"))) ?? .unknown
|
||||
modeMarkerPath = config.modeMarkerPath
|
||||
/// Note: The file has yaml extension, even it is in the json format
|
||||
overlayHeadersPath = targetTempDir.appendingPathComponent("all-product-headers.yaml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
/// Checks current mode from a configuration and based on that:
|
||||
/// * triggers build completion
|
||||
/// * triggers uploading artifacts to the server for a 'producer' mode
|
||||
@@ -35,6 +36,7 @@ public class XCPostbuild {
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
context = try PostbuildContext(config, env: env)
|
||||
updateProcessTag(context.targetName)
|
||||
let counterFactory: FileStatsCoordinator.CountersFactory = { file, count in
|
||||
ExclusiveFileCounter(ExclusiveFile(file, mode: .override), countersCount: count)
|
||||
}
|
||||
@@ -66,8 +68,8 @@ public class XCPostbuild {
|
||||
// Initialize dependencies
|
||||
let primaryGitBranch = GitBranch(repoLocation: config.primaryRepo, branch: config.primaryBranch)
|
||||
let gitClient = GitClientImpl(repoRoot: config.repoRoot, primary: primaryGitBranch, shell: shellGetStdout)
|
||||
let pathRemapper = try StringDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs,
|
||||
let envsRemapper = try PathDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs + config.customRewriteEnvs,
|
||||
envs: env,
|
||||
customMappings: config.outOfBandMappings
|
||||
)
|
||||
@@ -145,6 +147,24 @@ public class XCPostbuild {
|
||||
fileDependeciesReaderFactory: fileReaderFactory,
|
||||
dirScanner: fileManager
|
||||
)
|
||||
var remappers: [DependenciesRemapper] = []
|
||||
if !config.disableVFSOverlay {
|
||||
// As the PostbuildContext assumes file location and filename (`all-product-headers.yaml`)
|
||||
// do not fail in case of a missing headers overlay file. In the future, all overlay files could be
|
||||
// captured from the swiftc invocation similarly is stored in the `history.compile`
|
||||
// for the consumer mode
|
||||
let overlayReader = JsonOverlayReader(
|
||||
context.overlayHeadersPath,
|
||||
mode: .bestEffort,
|
||||
fileReader: fileManager
|
||||
)
|
||||
let overlayRemapper = OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
remappers.append(overlayRemapper)
|
||||
}
|
||||
remappers.append(envsRemapper)
|
||||
let pathRemapper = DependenciesRemapperComposite(remappers)
|
||||
let dependencyProcessor = DependencyProcessorImpl(
|
||||
xcode: context.xcodeDir,
|
||||
product: context.productsDir,
|
||||
@@ -301,3 +321,4 @@ public class XCPostbuild {
|
||||
}
|
||||
}
|
||||
}
|
||||
// swiftlint:enable type_body_length
|
||||
|
||||
@@ -63,7 +63,9 @@ class Prebuild {
|
||||
do {
|
||||
let metaData = try networkClient.fetch(.meta(commit: commit))
|
||||
let meta = try metaReader.read(data: metaData)
|
||||
let localDependencies = remapper.replace(genericPaths: meta.dependencies).map(URL.init(fileURLWithPath:))
|
||||
let localDependencies = try remapper.replace(
|
||||
genericPaths: meta.dependencies
|
||||
).map(URL.init(fileURLWithPath:))
|
||||
let localFingerprint = try generateFingerprint(for: localDependencies)
|
||||
if localFingerprint.raw != meta.rawFingerprint {
|
||||
if context.forceCached {
|
||||
|
||||
@@ -43,6 +43,9 @@ public struct PrebuildContext {
|
||||
let targetName: String
|
||||
/// List of all targets to downloaded from the thinning aggregation target
|
||||
var thinnedTargets: [String]?
|
||||
/// location of the json file that define virtual files system overlay
|
||||
/// (mappings of the virtual location file -> local file path)
|
||||
let overlayHeadersPath: URL
|
||||
}
|
||||
|
||||
extension PrebuildContext {
|
||||
@@ -64,5 +67,7 @@ extension PrebuildContext {
|
||||
self.targetName = targetName
|
||||
let thinFocusedTargetsString: String? = env.readEnv(key: "SPT_XCREMOTE_CACHE_THINNED_TARGETS")
|
||||
thinnedTargets = thinFocusedTargetsString?.split(separator: ",").map(String.init)
|
||||
/// Note: The file has yaml extension, even it is in the json format
|
||||
overlayHeadersPath = targetTempDir.appendingPathComponent("all-product-headers.yaml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ public class XCPrebuild {
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileManager: fileManager).readConfiguration()
|
||||
context = try PrebuildContext(config, env: env)
|
||||
updateProcessTag(context.targetName)
|
||||
} catch {
|
||||
// Fatal error:
|
||||
exit(1, "FATAL: Prebuild initialization failed with error: \(error)")
|
||||
@@ -115,11 +116,27 @@ public class XCPrebuild {
|
||||
)
|
||||
let client: NetworkClient = config.disableHttpCache ? networkClient : cacheNetworkClient
|
||||
let remoteNetworkClient = RemoteNetworkClientImpl(client, urlBuilder)
|
||||
let pathRemapper = try StringDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs,
|
||||
let envsRemapper = try PathDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs + config.customRewriteEnvs,
|
||||
envs: env,
|
||||
customMappings: config.outOfBandMappings
|
||||
)
|
||||
var remappers: [DependenciesRemapper] = []
|
||||
if !config.disableVFSOverlay {
|
||||
// As PrebuildContext assumes file location and its filename (`all-product-headers.yaml`)
|
||||
// do not fail in case of a missing headers overlay file
|
||||
let overlayReader = JsonOverlayReader(
|
||||
context.overlayHeadersPath,
|
||||
mode: .bestEffort,
|
||||
fileReader: fileManager
|
||||
)
|
||||
let overlayRemapper = OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
remappers.append(overlayRemapper)
|
||||
}
|
||||
remappers.append(envsRemapper)
|
||||
let pathRemapper = DependenciesRemapperComposite(remappers)
|
||||
let filesFingerprintGenerator = FingerprintAccumulatorImpl(
|
||||
algorithm: MD5Algorithm(),
|
||||
fileManager: fileManager
|
||||
|
||||
@@ -72,7 +72,17 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
)
|
||||
infoLog("ClangWrapperBuilder compiles file at \(compilationFile).")
|
||||
// -O3: optimize for faster execution
|
||||
let args = [clangCommand, "-arch", "arm64", "-arch", "x86_64", "-O3", compilationFile.path, "-o", destination.path]
|
||||
let args = [
|
||||
clangCommand,
|
||||
"-arch",
|
||||
"arm64",
|
||||
"-arch",
|
||||
"x86_64",
|
||||
"-O3",
|
||||
compilationFile.path,
|
||||
"-o",
|
||||
destination.path,
|
||||
]
|
||||
let compilationOutput = try shell("xcrun", args, URL(fileURLWithPath: "").path, nil)
|
||||
infoLog("Clang compilation output: \(compilationOutput)")
|
||||
}
|
||||
@@ -426,7 +436,9 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
isSuffixed(argv[i],".cc") ||
|
||||
isSuffixed(argv[i],".cpp") ||
|
||||
isSuffixed(argv[i],".c++") ||
|
||||
isSuffixed(argv[i],".cxx")
|
||||
isSuffixed(argv[i],".cxx") ||
|
||||
isSuffixed(argv[i],".S") ||
|
||||
isSuffixed(argv[i],".s")
|
||||
) {
|
||||
// a full list of extensions is taken from https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
// support for .m,.mm,.c,.cc,.cpp,.c++,.cxx input files
|
||||
|
||||
+8
-3
@@ -33,10 +33,12 @@ protocol BuildSettingsIntegrateAppender {
|
||||
class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
private let mode: Mode
|
||||
private let repoRoot: URL
|
||||
private let fakeSrcRoot: URL
|
||||
|
||||
init(mode: Mode, repoRoot: URL) {
|
||||
init(mode: Mode, repoRoot: URL, fakeSrcRoot: URL) {
|
||||
self.mode = mode
|
||||
self.repoRoot = repoRoot
|
||||
self.fakeSrcRoot = fakeSrcRoot
|
||||
}
|
||||
|
||||
func appendToBuildSettings(buildSettings: BuildSettings, wrappers: XCRCBinariesPaths) -> BuildSettings {
|
||||
@@ -61,8 +63,11 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
result["OTHER_SWIFT_FLAGS"] = swiftFlags.settingValue
|
||||
result["OTHER_CFLAGS"] = clangFlags.settingValue
|
||||
|
||||
result["XCRC_FAKE_SRCROOT"] = "/\(String(repeating: "x", count: 10))"
|
||||
result["XCRC_PLATFORM_PREFERRED_ARCH"] = "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)"
|
||||
result["XCRC_FAKE_SRCROOT"] = "\(fakeSrcRoot.path)"
|
||||
result["XCRC_PLATFORM_PREFERRED_ARCH"] =
|
||||
"""
|
||||
$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)
|
||||
"""
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ struct IncludeExcludeOracle: IncludeOracle {
|
||||
|
||||
|
||||
func shouldInclude(identifier: OracleIdentifierType) -> Bool {
|
||||
// exclude array has precedence.
|
||||
// exclude array has precedence
|
||||
if excludes.contains(identifier) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ public class XCIntegrate {
|
||||
)
|
||||
let buildSettingsAppender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: context.mode,
|
||||
repoRoot: context.repoRoot
|
||||
repoRoot: context.repoRoot,
|
||||
fakeSrcRoot: context.fakeSrcRoot
|
||||
)
|
||||
let lldbPatcher: LLDBInitPatcher
|
||||
switch lldbMode {
|
||||
|
||||
@@ -227,11 +227,11 @@ struct XcodeProjIntegrate: Integrate {
|
||||
let previousRCPhases = target.buildPhases.filter(isRCPhase)
|
||||
target.buildPhases.removeAll(where: previousRCPhases.contains)
|
||||
|
||||
if target.buildPhases.map(\.buildPhase).contains(.sources) {
|
||||
if let sourceIndex = target.buildPhases.map(\.buildPhase).firstIndex(of: .sources) {
|
||||
// add (pre|post)build phases only when a target has some compilation steps
|
||||
// otherwise they make no sense (nothing to store in an artifact)
|
||||
pbxproj.add(object: prebuildPhase)
|
||||
target.buildPhases.insert(prebuildPhase, at: 0)
|
||||
target.buildPhases.insert(prebuildPhase, at: sourceIndex)
|
||||
pbxproj.add(object: postbuildPhase)
|
||||
target.buildPhases.append(postbuildPhase)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
import Yams
|
||||
|
||||
/// Print current configuration to the console
|
||||
public class XCConfig {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
import Yams
|
||||
|
||||
/// Switch between Online/Offline modes
|
||||
public enum XCPrepareMode {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
import Yams
|
||||
|
||||
/// Manages XCRemoteCache statistics: rests, print to the standard output etc
|
||||
public class XCStats {
|
||||
|
||||
@@ -110,8 +110,8 @@ class SwiftcOrchestrator {
|
||||
try invocationStorage.store(args: invocationArgs)
|
||||
}
|
||||
} catch {
|
||||
// The critical section is protected by a lock. Some other process already called compilation history.
|
||||
// We only need to call our current step then.
|
||||
// The critical section is protected by a lock. Some other process already called compilation history
|
||||
// We only need to call our current step then
|
||||
fallbackToDefault(command: swiftcCommand)
|
||||
}
|
||||
case .consumer:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import Foundation
|
||||
|
||||
enum DiskSwiftcProductsGeneratorError: Error {
|
||||
/// When a generator was asked to generate unknown swiftmodule extension file.
|
||||
/// When a generator was asked to generate unknown swiftmodule extension file
|
||||
/// Probably a programmer error: asking to generate excessive extensions, not listed in
|
||||
/// `SwiftmoduleFileExtension.SwiftmoduleExtensions`
|
||||
case unknownSwiftmoduleFile
|
||||
|
||||
@@ -95,10 +95,10 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
/// Disable cache for http requests to fecth metadata and download artifacts
|
||||
var disableHttpCache: Bool = false
|
||||
/// Path, relative to $TARGET_TEMP_DIR which gathers all compilation commands that should be e
|
||||
/// xecuted if a target switches to local compilation.
|
||||
/// xecuted if a target switches to local compilation
|
||||
/// Example: A new `.swift` file invalidates remote arXcodeProjIntegrate.swifttifact and triggers local compilation
|
||||
/// When that happens, all previously skipped clang build steps
|
||||
/// need to be eventually called locally - this file lists all these commands.
|
||||
/// need to be eventually called locally - this file lists all these commands
|
||||
var compilationHistoryFile: String = "history.compile"
|
||||
/// Timeout for remote response data interval (in seconds). If an interval between data chunks is
|
||||
/// longer than a timeout, a request fails
|
||||
@@ -122,13 +122,20 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
var AWSRegion: String = ""
|
||||
/// Service for AWS V4 Signature (e.g. `storage`)
|
||||
var AWSService: String = ""
|
||||
/// A dictionary of files path remapping that should be applied to make it absolute path agnostic on a list of dependencies.
|
||||
/// Useful if a project refers files out of repo root, either compilation files or precompiled dependencies.
|
||||
/// Keys represent generic replacement and values are substrings that should be replaced.
|
||||
/// A dictionary of files path remapping that should be applied to make it absolute path agnostic on a list of
|
||||
/// dependencies. Useful if a project refers files out of repo root, either compilation files or precompiled
|
||||
/// dependencies. Keys represent generic replacement and values are substrings that should be replaced
|
||||
/// Example: for mapping `["COOL_LIBRARY": "/CoolLibrary"]`
|
||||
/// `/CoolLibrary/main.swift`will be represented as `$(COOL_LIBRARY)/main.swift`).
|
||||
/// Warning: remapping order is not-deterministic so avoid remappings with multiple matchings.
|
||||
/// `/CoolLibrary/main.swift`will be represented as `$(COOL_LIBRARY)/main.swift`)
|
||||
/// Warning: remapping order is not-deterministic so avoid remappings with multiple matchings
|
||||
var outOfBandMappings: [String: String] = [:]
|
||||
/// If true, SSL certificate validation is disabled
|
||||
var disableCertificateVerification: Bool = false
|
||||
/// A feature flag to disable virtual file system overlay support (temporary)
|
||||
var disableVFSOverlay: Bool = false
|
||||
/// A list of extra ENVs that should be used as placeholders in the dependency list
|
||||
/// ENV rewrite process is optimistic - does nothing if an ENV is not defined in the pre/postbuild process
|
||||
var customRewriteEnvs: [String] = []
|
||||
}
|
||||
|
||||
extension XCRemoteCacheConfig {
|
||||
@@ -180,6 +187,9 @@ extension XCRemoteCacheConfig {
|
||||
merge.AWSRegion = scheme.AWSRegion ?? AWSRegion
|
||||
merge.AWSService = scheme.AWSService ?? AWSService
|
||||
merge.outOfBandMappings = scheme.outOfBandMappings ?? outOfBandMappings
|
||||
merge.disableCertificateVerification = scheme.disableCertificateVerification ?? disableCertificateVerification
|
||||
merge.disableVFSOverlay = scheme.disableVFSOverlay ?? disableVFSOverlay
|
||||
merge.customRewriteEnvs = scheme.customRewriteEnvs ?? customRewriteEnvs
|
||||
return merge
|
||||
}
|
||||
|
||||
@@ -240,6 +250,9 @@ struct ConfigFileScheme: Decodable {
|
||||
let AWSRegion: String?
|
||||
let AWSService: String?
|
||||
let outOfBandMappings: [String: String]?
|
||||
let disableCertificateVerification: Bool?
|
||||
let disableVFSOverlay: Bool?
|
||||
let customRewriteEnvs: [String]?
|
||||
|
||||
// Yams library doesn't support encoding strategy, see https://github.com/jpsim/Yams/issues/84
|
||||
enum CodingKeys: String, CodingKey {
|
||||
@@ -283,6 +296,9 @@ struct ConfigFileScheme: Decodable {
|
||||
case AWSRegion = "aws_region"
|
||||
case AWSService = "aws_service"
|
||||
case outOfBandMappings = "out_of_band_mappings"
|
||||
case disableCertificateVerification = "disable_certificate_verification"
|
||||
case disableVFSOverlay = "disable_vfs_overlay"
|
||||
case customRewriteEnvs = "custom_rewrite_envs"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ public class FileDependenciesReader: DependenciesReader {
|
||||
return Set(splitDependencyFileList(value))
|
||||
case let s where s.hasSuffix(".o") || s.hasSuffix(".bc"):
|
||||
// 'swiftc' output formatting
|
||||
// take dependencies from any .o or .bc file.
|
||||
// take dependencies from any .o or .bc file
|
||||
// Note: For WMO, all .{o|bc} files have the same dependencies
|
||||
return Set(splitDependencyFileList(value))
|
||||
default:
|
||||
|
||||
@@ -22,9 +22,9 @@ import Foundation
|
||||
/// Replaces paths formats between generic (placeholders-based) and local
|
||||
protocol DependenciesRemapper {
|
||||
/// Replaces all generic paths (with placeholders) to a local paths
|
||||
func replace(genericPaths: [String]) -> [String]
|
||||
func replace(genericPaths: [String]) throws -> [String]
|
||||
/// Replaces all local paths to the generic dependencies paths
|
||||
func replace(localPaths: [String]) -> [String]
|
||||
func replace(localPaths: [String]) throws -> [String]
|
||||
}
|
||||
|
||||
class DependenciesRemapperComposite: DependenciesRemapper {
|
||||
@@ -34,15 +34,15 @@ class DependenciesRemapperComposite: DependenciesRemapper {
|
||||
self.remappers = remappers
|
||||
}
|
||||
|
||||
func replace(genericPaths: [String]) -> [String] {
|
||||
remappers.reversed().reduce(genericPaths) { prev, mapper in
|
||||
mapper.replace(genericPaths: prev)
|
||||
func replace(genericPaths: [String]) throws -> [String] {
|
||||
try remappers.reversed().reduce(genericPaths) { prev, mapper in
|
||||
try mapper.replace(genericPaths: prev)
|
||||
}
|
||||
}
|
||||
|
||||
func replace(localPaths: [String]) -> [String] {
|
||||
remappers.reduce(localPaths) { prev, mapper in
|
||||
mapper.replace(localPaths: prev)
|
||||
func replace(localPaths: [String]) throws -> [String] {
|
||||
try remappers.reduce(localPaths) { prev, mapper in
|
||||
try mapper.replace(localPaths: prev)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ final class StringDependenciesRemapper: DependenciesRemapper {
|
||||
self.mappings = mappings
|
||||
}
|
||||
|
||||
func replace(genericPaths: [String]) -> [String] {
|
||||
func replace(genericPaths: [String]) throws -> [String] {
|
||||
return genericPaths.map { path in
|
||||
let localPath = mappings.reversed().reduce(path) { prevPath, mapping in
|
||||
prevPath.replacingOccurrences(of: mapping.generic, with: mapping.local)
|
||||
@@ -68,7 +68,7 @@ final class StringDependenciesRemapper: DependenciesRemapper {
|
||||
}
|
||||
}
|
||||
|
||||
func replace(localPaths: [String]) -> [String] {
|
||||
func replace(localPaths: [String]) throws -> [String] {
|
||||
return localPaths.map { path in
|
||||
let result = mappings.reduce(path) { prevPath, mapping in
|
||||
prevPath.replacingOccurrences(of: mapping.local, with: mapping.generic)
|
||||
|
||||
@@ -58,11 +58,11 @@ class DependencyProcessorImpl: DependencyProcessor {
|
||||
private let bundlePath: String?
|
||||
|
||||
init(xcode: URL, product: URL, source: URL, intermediate: URL, bundle: URL?) {
|
||||
xcodePath = xcode.path
|
||||
productPath = product.path
|
||||
sourcePath = source.path
|
||||
intermediatePath = intermediate.path
|
||||
bundlePath = bundle?.path
|
||||
xcodePath = xcode.path.dirPath()
|
||||
productPath = product.path.dirPath()
|
||||
sourcePath = source.path.dirPath()
|
||||
intermediatePath = intermediate.path.dirPath()
|
||||
bundlePath = bundle?.path.dirPath()
|
||||
}
|
||||
|
||||
func process(_ files: [URL]) -> [Dependency] {
|
||||
@@ -72,7 +72,7 @@ class DependencyProcessorImpl: DependencyProcessor {
|
||||
|
||||
private func classify(_ files: [URL]) -> [Dependency] {
|
||||
return files.map { file -> Dependency in
|
||||
let filePath = file.path
|
||||
let filePath = file.resolvingSymlinksInPath().path
|
||||
if filePath.hasPrefix(xcodePath) {
|
||||
return Dependency(url: file, type: .xcode)
|
||||
} else if filePath.hasPrefix(intermediatePath) {
|
||||
@@ -111,3 +111,9 @@ class DependencyProcessorImpl: DependencyProcessor {
|
||||
return !irrelevantDependenciesType.contains(dependency.type)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension String {
|
||||
func dirPath() -> String {
|
||||
hasSuffix("/") ? self : appending("/")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// File paths remapper according the virtual file system mappings
|
||||
/// - Warning: this class is not thread safe
|
||||
class OverlayDependenciesRemapper: DependenciesRemapper {
|
||||
private let overlayReader: OverlayReader
|
||||
private var mappings: [OverlayMapping]?
|
||||
|
||||
init(overlayReader: OverlayReader) {
|
||||
self.overlayReader = overlayReader
|
||||
}
|
||||
|
||||
/// Lazily Reads mappings from a file
|
||||
/// - Warning: this function is not thread safe
|
||||
private func getMappings() throws -> [OverlayMapping] {
|
||||
guard let mappings = mappings else {
|
||||
let mappings = try overlayReader.provideMappings()
|
||||
self.mappings = mappings
|
||||
return mappings
|
||||
}
|
||||
return mappings
|
||||
}
|
||||
|
||||
private func mapPath(
|
||||
_ path: String,
|
||||
source: KeyPath<OverlayMapping, URL>,
|
||||
destination: KeyPath<OverlayMapping, URL>
|
||||
) throws -> String {
|
||||
guard let mapping = try getMappings().first(where: { $0[keyPath: source].path == path }) else {
|
||||
// TODO: support partial mappings, where a directory path can be replaced with some other directory
|
||||
// no direct mapping found
|
||||
return path
|
||||
}
|
||||
return mapping[keyPath: destination].path
|
||||
}
|
||||
|
||||
func replace(genericPaths: [String]) throws -> [String] {
|
||||
try genericPaths.map {
|
||||
try mapPath($0, source: \.virtual, destination: \.local)
|
||||
}
|
||||
}
|
||||
|
||||
func replace(localPaths: [String]) throws -> [String] {
|
||||
try localPaths.map {
|
||||
try mapPath($0, source: \.local, destination: \.virtual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Maps overlay's virtual URL with an actual (local) location
|
||||
struct OverlayMapping: Hashable {
|
||||
let virtual: URL
|
||||
let local: URL
|
||||
}
|
||||
|
||||
enum JsonOverlayReaderError: Error {
|
||||
/// The source file is missing
|
||||
case missingSourceFile(URL)
|
||||
/// The file exists but its content is invalid
|
||||
case invalidSourceContent(URL)
|
||||
/// the overlay format is not supported - either contains a nested directory or a single file
|
||||
case unsupportedFormat
|
||||
}
|
||||
/// Provides virtual file system overlay mappings
|
||||
protocol OverlayReader {
|
||||
func provideMappings() throws -> [OverlayMapping]
|
||||
}
|
||||
|
||||
class JsonOverlayReader: OverlayReader {
|
||||
|
||||
enum Mode {
|
||||
/// Interrupts the operation if the representation file is missing
|
||||
case strict
|
||||
/// Assume empty overlay mapping if the file doesn't exist
|
||||
case bestEffort
|
||||
}
|
||||
|
||||
private struct Overlay: Decodable {
|
||||
enum OverlayType: String, Decodable {
|
||||
case file
|
||||
case directory
|
||||
}
|
||||
|
||||
struct Content: Decodable {
|
||||
let externalContents: String
|
||||
let name: String
|
||||
let type: OverlayType
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case externalContents = "external-contents"
|
||||
case name
|
||||
case type
|
||||
}
|
||||
}
|
||||
|
||||
struct RootContent: Decodable {
|
||||
let contents: [Content]
|
||||
let name: String
|
||||
let type: OverlayType
|
||||
}
|
||||
let roots: [RootContent]
|
||||
}
|
||||
|
||||
private lazy var jsonDecoder = JSONDecoder()
|
||||
private let json: URL
|
||||
private let mode: Mode
|
||||
private let fileReader: FileReader
|
||||
|
||||
|
||||
init(_ json: URL, mode: Mode, fileReader: FileReader) {
|
||||
self.json = json
|
||||
self.mode = mode
|
||||
self.fileReader = fileReader
|
||||
}
|
||||
|
||||
func provideMappings() throws -> [OverlayMapping] {
|
||||
guard let jsonContent = try fileReader.contents(atPath: json.path) else {
|
||||
switch mode {
|
||||
case .strict:
|
||||
throw JsonOverlayReaderError.missingSourceFile(json)
|
||||
case .bestEffort:
|
||||
debugLog("overlay mapping file \(json) doesn't exist. Skipping overlay for the best-effort mode.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
do {
|
||||
let overlay: Overlay = try jsonDecoder.decode(Overlay.self, from: jsonContent)
|
||||
let mappings: [OverlayMapping] = try overlay.roots.reduce([]) { prev, root in
|
||||
switch root.type {
|
||||
case .directory:
|
||||
// iterate all contents
|
||||
let dir = URL(fileURLWithPath: root.name)
|
||||
let mappings: [OverlayMapping] = try root.contents.map { content in
|
||||
switch content.type {
|
||||
case .file:
|
||||
let virtual = dir.appendingPathComponent(content.name)
|
||||
let local = URL(fileURLWithPath: content.externalContents)
|
||||
return .init(virtual: virtual, local: local)
|
||||
case .directory:
|
||||
throw JsonOverlayReaderError.unsupportedFormat
|
||||
}
|
||||
|
||||
}
|
||||
return prev + mappings
|
||||
case .file:
|
||||
throw JsonOverlayReaderError.unsupportedFormat
|
||||
}
|
||||
}
|
||||
return mappings
|
||||
} catch {
|
||||
switch mode {
|
||||
case .strict:
|
||||
throw error
|
||||
case .bestEffort:
|
||||
errorLog("Overlay reader has failed with an error \(error). Best-effort mode - skipping an overlay.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+12
-8
@@ -19,24 +19,28 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum StringDependenciesRemapperFactoryError: Error {
|
||||
enum PathDependenciesRemapperFactoryError: Error {
|
||||
/// Remapping keys are duplicated and can lead to undetermined results
|
||||
case mappingKeyDuplication
|
||||
}
|
||||
|
||||
class StringDependenciesRemapperFactory {
|
||||
class PathDependenciesRemapperFactory {
|
||||
func build(
|
||||
orderKeys: [String],
|
||||
envs: [String: String],
|
||||
customMappings: [String: String]
|
||||
) throws -> StringDependenciesRemapper {
|
||||
let mappingMap = try envs.merging(customMappings) { envValue, outOfBandMapping in
|
||||
throw StringDependenciesRemapperFactoryError.mappingKeyDuplication
|
||||
let mappingMap = try envs.merging(customMappings) { _, _ in
|
||||
throw PathDependenciesRemapperFactoryError.mappingKeyDuplication
|
||||
}
|
||||
let mappingOrderKeys = orderKeys + customMappings.keys
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = try mappingOrderKeys.map { key in
|
||||
let localValue: String = try mappingMap.readEnv(key: key)
|
||||
return StringDependenciesRemapper.Mapping(generic: "$(\(key))", local: localValue)
|
||||
let mappingOrderKeys = orderKeys + customMappings.keys
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = mappingOrderKeys.compactMap { key in
|
||||
guard let localURL: URL = mappingMap.readEnv(key: key) else {
|
||||
debugLog("\(key) ENV to map a dependency is not defined")
|
||||
return nil
|
||||
}
|
||||
infoLog("Found url to remapp: \(localURL). Remapping: \(localURL.standardized.path)")
|
||||
return StringDependenciesRemapper.Mapping(generic: "$(\(key))", local: localURL.standardized.path)
|
||||
}
|
||||
return StringDependenciesRemapper(mappings: mappings)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class TargetDependenciesReader: DependenciesReader {
|
||||
let allURLs = try dirScanner.items(at: directory)
|
||||
let mergedDependencies = try allURLs.reduce(Set<String>()) { (prev: Set<String>, file) in
|
||||
// include only these .d files that either have corresponding .o file (incremental) or end
|
||||
// with '-master' (whole-module).
|
||||
// with '-master' (whole-module)
|
||||
// Otherwise .d is probably just a leftover from previous builds
|
||||
let correspondingOutputURL = file.deletingPathExtension().appendingPathExtension("o")
|
||||
let isDependencyFile = file.pathExtension == "d"
|
||||
|
||||
@@ -31,7 +31,7 @@ class EnvironmentFingerprintGenerator {
|
||||
"DYLIB_COMPATIBILITY_VERSION",
|
||||
"DYLIB_CURRENT_VERSION",
|
||||
"PRODUCT_MODULE_NAME",
|
||||
"ARCHS"
|
||||
"ARCHS",
|
||||
]
|
||||
private let version: String
|
||||
private let customFingerprintEnvs: [String]
|
||||
|
||||
@@ -22,34 +22,36 @@ import Foundation
|
||||
import os.log
|
||||
|
||||
|
||||
private var processTag: String = ""
|
||||
|
||||
public func exit(_ exitCode: Int32, _ message: String) -> Never {
|
||||
os_log("%{public}@", log: OSLog.default, type: .error, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .error, processTag, message)
|
||||
printError(errorMessage: message)
|
||||
exit(exitCode)
|
||||
}
|
||||
|
||||
func defaultLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .default, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .default, processTag, message)
|
||||
}
|
||||
|
||||
func errorLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .error, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .error, processTag, message)
|
||||
}
|
||||
|
||||
func infoLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .info, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .info, processTag, message)
|
||||
}
|
||||
|
||||
func debugLog(_ message: String) {
|
||||
os_log("%{public}@", log: OSLog.default, type: .debug, message)
|
||||
os_log("%{public}@%{public}@", log: OSLog.default, type: .debug, processTag, message)
|
||||
}
|
||||
|
||||
func printError(errorMessage: String) {
|
||||
fputs("error: \(errorMessage)\n", stderr)
|
||||
fputs("error: \(processTag)\(errorMessage)\n", stderr)
|
||||
}
|
||||
|
||||
func printWarning(_ message: String) {
|
||||
print("warning: \(message)")
|
||||
print("warning: \(processTag)\(message)")
|
||||
}
|
||||
|
||||
/// Prints a message to the user. It shows in Xcode (if applies) or console output
|
||||
@@ -57,3 +59,7 @@ func printWarning(_ message: String) {
|
||||
func printToUser(_ message: String) {
|
||||
print("[RC] \(message)")
|
||||
}
|
||||
|
||||
func updateProcessTag(_ tag: String) {
|
||||
processTag = "(\(tag)) "
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
import Foundation
|
||||
|
||||
protocol MetaWriter {
|
||||
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta
|
||||
func write<T>(_ meta: T, locationDir: URL) throws -> URL where T: Meta
|
||||
}
|
||||
|
||||
class JsonMetaWriter: MetaWriter {
|
||||
@@ -36,7 +36,7 @@ class JsonMetaWriter: MetaWriter {
|
||||
self.metaEncoder = encoder
|
||||
}
|
||||
|
||||
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta {
|
||||
func write<T>(_ meta: T, locationDir: URL) throws -> URL where T: Meta {
|
||||
let metaURL = locationDir.appendingPathComponent(meta.fileKey).appendingPathExtension("json")
|
||||
let metaData = try metaEncoder.encode(meta)
|
||||
try fileWriter.write(toPath: metaURL.path, contents: metaData)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
|
||||
final class IgnoringCertificatesTrustManager: NSObject, URLSessionDelegate {
|
||||
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
|
||||
guard let serverTrust = challenge.protectionSpace.serverTrust else {
|
||||
completionHandler(.performDefaultHandling, nil)
|
||||
return
|
||||
}
|
||||
|
||||
let urlCredential = URLCredential(trust: serverTrust)
|
||||
completionHandler(.useCredential, urlCredential)
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,17 @@ class DefaultURLSessionFactory: URLSessionFactory {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.httpAdditionalHeaders = config.requestCustomHeaders
|
||||
configuration.timeoutIntervalForRequest = config.timeoutResponseDataChunksInterval
|
||||
return URLSession(configuration: configuration)
|
||||
configuration.urlCache?.memoryCapacity = 0
|
||||
configuration.urlCache?.diskCapacity = 0
|
||||
switch config.disableCertificateVerification {
|
||||
case true:
|
||||
return URLSession(
|
||||
configuration: configuration,
|
||||
delegate: IgnoringCertificatesTrustManager(),
|
||||
delegateQueue: nil
|
||||
)
|
||||
case false:
|
||||
return URLSession(configuration: configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,8 @@ class ExclusiveFile: ExclusiveFileAccessor {
|
||||
guard flock(fd, LOCK_EX) == 0 else {
|
||||
throw FileAccessorError.lockingFailure
|
||||
}
|
||||
// While having a lock, make sure the file still exists.
|
||||
// It might delete it while we were waiting for a lock.
|
||||
// While having a lock, make sure the file still exists
|
||||
// It might delete it while we were waiting for a lock
|
||||
guard access(fileURL.path, F_OK) == 0 else {
|
||||
throw FileAccessorError.lockingFailure
|
||||
}
|
||||
|
||||
@@ -24,8 +24,15 @@ enum EnvironmentError: Error {
|
||||
}
|
||||
|
||||
extension Dictionary where Key == String, Value == String {
|
||||
func readEnv(key: String) throws -> URL {
|
||||
func readEnv(key: String) -> URL? {
|
||||
guard let value = self[key].map(URL.init(fileURLWithPath:)) else {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func readEnv(key: String) throws -> URL {
|
||||
guard let value: URL = readEnv(key: key) else {
|
||||
throw EnvironmentError.missingEnv(key)
|
||||
}
|
||||
return value
|
||||
|
||||
@@ -27,6 +27,7 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
private var swiftmoduleFile: URL!
|
||||
private var swiftmoduleDocFile: URL!
|
||||
private var swiftmoduleSourceInfoFile: URL!
|
||||
private var swiftmoduleInterfaceFile: URL!
|
||||
private var workingDir: URL!
|
||||
private var builder: ArtifactSwiftProductsBuilderImpl!
|
||||
|
||||
@@ -37,6 +38,7 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
swiftmoduleFile = moduleDir.appendingPathComponent("MyModule.swiftmodule")
|
||||
swiftmoduleDocFile = moduleDir.appendingPathComponent("MyModule.swiftdoc")
|
||||
swiftmoduleSourceInfoFile = moduleDir.appendingPathComponent("MyModule.swiftsourceinfo")
|
||||
swiftmoduleInterfaceFile = moduleDir.appendingPathComponent("MyModule.swiftinterface")
|
||||
workingDir = rootDir.appendingPathComponent("working")
|
||||
builder = ArtifactSwiftProductsBuilderImpl(
|
||||
workingDir: workingDir,
|
||||
@@ -69,7 +71,7 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testIncludesAllSwiftmoduleFiles() throws {
|
||||
func testIncludesAllBasicSwiftmoduleFiles() throws {
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleDocFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleSourceInfoFile)
|
||||
@@ -91,6 +93,32 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftSourceInfoFile.path))
|
||||
}
|
||||
|
||||
func testIncludesAllEvolutionEnabledSwiftmoduleFiles() throws {
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleDocFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleSourceInfoFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleInterfaceFile)
|
||||
let builderSwiftmoduleDir =
|
||||
builder
|
||||
.buildingArtifactSwiftModulesLocation()
|
||||
.appendingPathComponent("arm64")
|
||||
let expectedBuildedSwiftmoduleFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftmodule")
|
||||
let expectedBuildedSwiftmoduledocFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftdoc")
|
||||
let expectedBuildedSwiftSourceInfoFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftsourceinfo")
|
||||
let expectedBuildedSwiftInterfaceFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftinterface")
|
||||
|
||||
try builder.includeModuleDefinitionsToTheArtifact(arch: "arm64", moduleURL: swiftmoduleFile)
|
||||
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftmoduleFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftmoduledocFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftSourceInfoFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftInterfaceFile.path))
|
||||
}
|
||||
|
||||
func testFailsIncludingWhenMissingRequiredSwiftmoduleFiles() throws {
|
||||
XCTAssertThrowsError(
|
||||
try builder.includeModuleDefinitionsToTheArtifact(
|
||||
|
||||
@@ -31,6 +31,7 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
private var swiftmoduleURL: URL!
|
||||
private var swiftdocURL: URL!
|
||||
private var swiftSourceInfoURL: URL!
|
||||
private var swiftInterfaceURL: URL!
|
||||
private var executablePath: String!
|
||||
private var executableURL: URL!
|
||||
private var creator: BuildArtifactCreator!
|
||||
@@ -50,6 +51,8 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
.appendingPathComponent("Target.swiftdoc")
|
||||
swiftSourceInfoURL = workDirectory.appendingPathComponent("Objects-normal")
|
||||
.appendingPathComponent("Target.swiftsourceinfo")
|
||||
swiftInterfaceURL = workDirectory.appendingPathComponent("Objects-normal")
|
||||
.appendingPathComponent("Target.swiftinterface")
|
||||
executablePath = "libTarget.a"
|
||||
executableURL = buildDir.appendingPathComponent(executablePath)
|
||||
dSYM = executableURL.deletingPathExtension().appendingPathExtension(".dSYM")
|
||||
@@ -114,6 +117,28 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
])
|
||||
}
|
||||
|
||||
func testPackagesEvolutionEnabledSwiftmoduleFiles() throws {
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleURL)
|
||||
try fileManager.spt_createEmptyFile(swiftdocURL)
|
||||
try fileManager.spt_createEmptyFile(swiftSourceInfoURL)
|
||||
try fileManager.spt_createEmptyFile(swiftInterfaceURL)
|
||||
|
||||
try creator.includeModuleDefinitionsToTheArtifact(arch: "arch", moduleURL: swiftmoduleURL)
|
||||
let artifact = try creator.createArtifact(artifactKey: "key", meta: sampleMeta)
|
||||
|
||||
let unzippedURL = workDirectory.appendingPathComponent(UUID().uuidString)
|
||||
try Zip.unzipFile(artifact.package, destination: unzippedURL, overwrite: true, password: nil, progress: nil)
|
||||
let allFiles = try fileManager.spt_allFilesRecusively(unzippedURL)
|
||||
XCTAssertEqual(Set(allFiles), [
|
||||
unzippedURL.appendingPathComponent("libTarget.a"),
|
||||
unzippedURL.appendingPathComponent("fileKey.json"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftmodule"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftdoc"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftsourceinfo"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftinterface"),
|
||||
])
|
||||
}
|
||||
|
||||
func testFailsPackageWhenSwiftmoduleRelatedFilesAreMissing() throws {
|
||||
// Creating only `Target.swiftmodule`, without `.swiftdoc`
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleURL)
|
||||
|
||||
@@ -131,7 +131,7 @@ class ThinningCreatorPluginTests: FileXCTestCase {
|
||||
|
||||
XCTAssertEqual(extraKeys, [
|
||||
"thinning_Generated": "000",
|
||||
"thinning_Reused": "999"
|
||||
"thinning_Reused": "999",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
"DWARF_DSYM_FILE_NAME": "DWARF_DSYM_FILE_NAME",
|
||||
"BUILT_PRODUCTS_DIR": "BUILT_PRODUCTS_DIR",
|
||||
"DERIVED_SOURCES_DIR": "DERIVED_SOURCES_DIR",
|
||||
"CURRENT_VARIANT": "normal"
|
||||
"CURRENT_VARIANT": "normal",
|
||||
]
|
||||
|
||||
override func setUpWithError() throws {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
// swiftlint:disable file_length
|
||||
// swiftlint:disable:next type_body_length
|
||||
class PostbuildTests: FileXCTestCase {
|
||||
private var postbuildContext = PostbuildContext(
|
||||
@@ -51,7 +52,8 @@ class PostbuildTests: FileXCTestCase {
|
||||
derivedSourcesDir: "",
|
||||
thinnedTargets: [],
|
||||
action: .build,
|
||||
modeMarkerPath: ""
|
||||
modeMarkerPath: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
private var network = RemoteNetworkClientImpl(
|
||||
NetworkClientFake(fileManager: .default),
|
||||
@@ -638,4 +640,3 @@ class PostbuildTests: FileXCTestCase {
|
||||
XCTAssertEqual(downloadedMeta, expectedMeta)
|
||||
}
|
||||
}
|
||||
// swiftlint:disable:next file_length
|
||||
|
||||
@@ -62,7 +62,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
forceCached: false,
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: ""
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
contextCached = PrebuildContext(
|
||||
targetTempDir: sampleURL,
|
||||
@@ -74,7 +75,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
forceCached: true,
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: ""
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
organizer = ArtifactOrganizerFake(artifactRoot: artifactsRoot, unzippedExtension: "unzip")
|
||||
globalCacheSwitcher = InMemoryGlobalCacheSwitcher()
|
||||
@@ -238,7 +240,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
forceCached: false,
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: ""
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
|
||||
let prebuild = Prebuild(
|
||||
@@ -268,7 +271,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
forceCached: false,
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: ""
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
metaContent = try generateMeta(fingerprint: generator.generate(), filekey: "1")
|
||||
let downloadedArtifactPackage = artifactsRoot.appendingPathComponent("1")
|
||||
@@ -330,7 +334,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
forceCached: false,
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: false,
|
||||
targetName: ""
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
try globalCacheSwitcher.enable(sha: "1")
|
||||
let prebuild = Prebuild(
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
|
||||
private let rootURL: URL = "/root"
|
||||
private let binariesDir: URL = "/binaries"
|
||||
private var buildSettings: BuildSettings!
|
||||
private var binaries: XCRCBinariesPaths!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
buildSettings = BuildSettings()
|
||||
binaries = XCRCBinariesPaths(
|
||||
prepare: binariesDir.appendingPathComponent("xcprepare"),
|
||||
cc: binariesDir.appendingPathComponent("xccc"),
|
||||
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
|
||||
libtool: binariesDir.appendingPathComponent("xclibtool"),
|
||||
ld: binariesDir.appendingPathComponent("xcld"),
|
||||
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
|
||||
postbuild: binariesDir.appendingPathComponent("xcpostbuild")
|
||||
)
|
||||
}
|
||||
|
||||
func testProducerSettingFakeSrcRoot() throws {
|
||||
let mode: Mode = .producer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxP"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(mode: mode, repoRoot: rootURL, fakeSrcRoot: fakeRootURL)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let resultURL = try XCTUnwrap(result["XCRC_FAKE_SRCROOT"] as? String)
|
||||
|
||||
XCTAssertEqual(resultURL, fakeRootURL.path)
|
||||
}
|
||||
|
||||
func testConsumerSettingFakeSrcRoot() throws {
|
||||
let mode: Mode = .consumer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxC"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(mode: mode, repoRoot: rootURL, fakeSrcRoot: fakeRootURL)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let resultURL: String = try XCTUnwrap(result["XCRC_FAKE_SRCROOT"] as? String)
|
||||
|
||||
XCTAssertEqual(resultURL, fakeRootURL.path)
|
||||
}
|
||||
}
|
||||
@@ -280,6 +280,9 @@ class SwiftcTests: FileXCTestCase {
|
||||
let artifactSwiftSourceInfo = URL(
|
||||
fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftsourceinfo"
|
||||
)
|
||||
let artifactSwiftInterfaceInfo = URL(
|
||||
fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftinterface"
|
||||
)
|
||||
|
||||
artifactOrganizer = ArtifactOrganizerFake(artifactRoot: artifactRoot)
|
||||
let swiftc = Swiftc(
|
||||
@@ -303,12 +306,14 @@ class SwiftcTests: FileXCTestCase {
|
||||
let swiftModuleURL = swiftModuleFiles.0[.swiftmodule]
|
||||
let swiftDocURL = swiftModuleFiles.0[.swiftdoc]
|
||||
let swiftSourceInfoURL = swiftModuleFiles.0[.swiftsourceinfo]
|
||||
let swiftInterfaceURL = swiftModuleFiles.0[.swiftinterface]
|
||||
let swiftHeaderURL = swiftModuleFiles.1
|
||||
|
||||
XCTAssertEqual(swiftModuleURL, artifactSwiftmodule)
|
||||
XCTAssertEqual(swiftDocURL, artifactSwiftdoc)
|
||||
XCTAssertEqual(swiftSourceInfoURL, artifactSwiftSourceInfo)
|
||||
XCTAssertEqual(swiftHeaderURL, artifactObjCHeader)
|
||||
XCTAssertEqual(swiftInterfaceURL, artifactSwiftInterfaceInfo)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,89 +29,119 @@ class DependenciesRemapperCompositeTests: XCTestCase {
|
||||
StringDependenciesRemapper.Mapping(generic: "$(PWD)", local: "/pwd"),
|
||||
]
|
||||
|
||||
func testNoRemappersIsTransparent() {
|
||||
func testNoRemappersIsTransparent() throws {
|
||||
let remapper = DependenciesRemapperComposite([])
|
||||
|
||||
let genericPath = remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
let genericPath = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
|
||||
XCTAssertEqual(genericPath, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testOneRemapperReplacesLocalPaths() {
|
||||
func testOneRemapperReplacesLocalPaths() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: mappings1),
|
||||
])
|
||||
|
||||
let genericPath = remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
let genericPath = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
|
||||
XCTAssertEqual(genericPath, ["$(SRC_ROOT)/some.swift"])
|
||||
}
|
||||
|
||||
func testOneRemapperReplacesGenericPaths() {
|
||||
func testOneRemapperReplacesGenericPaths() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: mappings1),
|
||||
])
|
||||
|
||||
let localPath = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
let localPath = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
|
||||
XCTAssertEqual(localPath, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testTwoRemappersReplacesLocalPaths() {
|
||||
func testTwoRemappersReplacesLocalPaths() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: mappings1),
|
||||
StringDependenciesRemapper(mappings: mappings2),
|
||||
])
|
||||
|
||||
let genericPath = remapper.replace(localPaths: ["/tmp/root/some.swift", "/pwd/other.swift"])
|
||||
let genericPath = try remapper.replace(localPaths: ["/tmp/root/some.swift", "/pwd/other.swift"])
|
||||
|
||||
XCTAssertEqual(genericPath, ["$(SRC_ROOT)/some.swift", "$(PWD)/other.swift"])
|
||||
}
|
||||
|
||||
func testOneRemappersReplacesGenericPaths() {
|
||||
func testOneRemappersReplacesGenericPaths() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: mappings1),
|
||||
StringDependenciesRemapper(mappings: mappings2),
|
||||
])
|
||||
|
||||
let localPath = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift", "$(PWD)/other.swift"])
|
||||
let localPath = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift", "$(PWD)/other.swift"])
|
||||
|
||||
XCTAssertEqual(localPath, ["/tmp/root/some.swift", "/pwd/other.swift"])
|
||||
}
|
||||
|
||||
func testRemapsMultipleMatchingMappers() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(ROOT)", local: "/root")]),
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(SPECIFIC)", local: "$(ROOT)/specific")])
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(ROOT)",
|
||||
local: "/root"
|
||||
),
|
||||
]),
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(SPECIFIC)",
|
||||
local: "$(ROOT)/specific"
|
||||
),
|
||||
]),
|
||||
])
|
||||
let localPaths = ["/root/specific/file"]
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: localPaths)
|
||||
let genericPaths = try remapper.replace(localPaths: localPaths)
|
||||
|
||||
XCTAssertEqual(genericPaths, ["$(SPECIFIC)/file"])
|
||||
}
|
||||
|
||||
func testRemapsBackToLocalWithRevertedRemappersOrder() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(ROOT)", local: "/root")]),
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(SPECIFIC)", local: "$(ROOT)/specific")])
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(ROOT)",
|
||||
local: "/root"
|
||||
),
|
||||
]),
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(SPECIFIC)",
|
||||
local: "$(ROOT)/specific"
|
||||
),
|
||||
]),
|
||||
])
|
||||
let genericPaths = ["$(SPECIFIC)/file"]
|
||||
|
||||
let localPaths = remapper.replace(genericPaths: genericPaths)
|
||||
let localPaths = try remapper.replace(genericPaths: genericPaths)
|
||||
|
||||
XCTAssertEqual(localPaths, ["/root/specific/file"])
|
||||
}
|
||||
|
||||
func testRemappingTwoMappingsBackAndForthIsIdentical() throws {
|
||||
let remapper = DependenciesRemapperComposite([
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(ROOT)", local: "/root")]),
|
||||
StringDependenciesRemapper(mappings: [StringDependenciesRemapper.Mapping(generic: "$(SPECIFIC)", local: "$(ROOT)/specific")])
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(ROOT)",
|
||||
local: "/root"
|
||||
),
|
||||
]),
|
||||
StringDependenciesRemapper(mappings: [
|
||||
StringDependenciesRemapper.Mapping(
|
||||
generic: "$(SPECIFIC)",
|
||||
local: "$(ROOT)/specific"
|
||||
),
|
||||
]),
|
||||
])
|
||||
let localPaths = ["/root/specific/file"]
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: localPaths)
|
||||
let remappedLocalPaths = remapper.replace(genericPaths: genericPaths)
|
||||
let genericPaths = try remapper.replace(localPaths: localPaths)
|
||||
let remappedLocalPaths = try remapper.replace(genericPaths: genericPaths)
|
||||
|
||||
XCTAssertEqual(localPaths, remappedLocalPaths)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class DependencyProcessorImplTests: XCTestCase {
|
||||
class DependencyProcessorImplTests: FileXCTestCase {
|
||||
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
@@ -70,6 +70,14 @@ class DependencyProcessorImplTests: XCTestCase {
|
||||
XCTAssertEqual(dependencies, [])
|
||||
}
|
||||
|
||||
func testDoesNotFilterOutOtherProductModulemap() throws {
|
||||
let dependencies = processor.process([
|
||||
"/ProductOther/some.modulemap",
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [.init(url: "/ProductOther/some.modulemap", type: .unknown)])
|
||||
}
|
||||
|
||||
func testDoesNotFilterOutNonProductModulemap() throws {
|
||||
let dependencies = processor.process([
|
||||
"/Source/some.modulemap",
|
||||
@@ -109,4 +117,76 @@ class DependencyProcessorImplTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(dependencies, [.init(url: "/xxx/some", type: .unknown)])
|
||||
}
|
||||
|
||||
func testFiltersOutIntermediateBySymlink() throws {
|
||||
let sampleDir = try prepareTempDir()
|
||||
|
||||
let intermediateDirReal = sampleDir.appendingPathComponent("Intermediate")
|
||||
let symlink = sampleDir.appendingPathComponent("symlink")
|
||||
let someFilename = "some"
|
||||
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/Product",
|
||||
source: "/Source",
|
||||
intermediate: intermediateDirReal,
|
||||
bundle: "/Bundle"
|
||||
)
|
||||
|
||||
let intermediateFileSymlink = createSymlink(
|
||||
filename: someFilename,
|
||||
sourceDir: symlink,
|
||||
destinationDir: intermediateDirReal
|
||||
)
|
||||
|
||||
let dependencies = processor.process([
|
||||
intermediateFileSymlink,
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [])
|
||||
}
|
||||
|
||||
func testDoesNotFilterOutSourceBySymlink() throws {
|
||||
let sampleDir = try prepareTempDir()
|
||||
|
||||
let sourceDirReal = sampleDir.appendingPathComponent("Source")
|
||||
let symlink = sampleDir.appendingPathComponent("symlink")
|
||||
let someFilename = "some"
|
||||
|
||||
let processor = DependencyProcessorImpl(
|
||||
xcode: "/Xcode",
|
||||
product: "/Product",
|
||||
source: sourceDirReal,
|
||||
intermediate: "/Intermediate",
|
||||
bundle: "/Bundle"
|
||||
)
|
||||
|
||||
let sourceFileSymlink = createSymlink(
|
||||
filename: someFilename,
|
||||
sourceDir: symlink,
|
||||
destinationDir: sourceDirReal
|
||||
)
|
||||
|
||||
let dependencies = processor.process([
|
||||
sourceFileSymlink,
|
||||
])
|
||||
|
||||
XCTAssertEqual(dependencies, [.init(url: sourceFileSymlink, type: .source)])
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Symlink from sourceDir to destinationDir and creates empty file inside it
|
||||
* return URL with symbolic link from sourceDir to destinationDir
|
||||
*/
|
||||
fileprivate func createSymlink(filename: String, sourceDir: URL, destinationDir: URL) -> URL {
|
||||
let fileMng = FileManager.default
|
||||
|
||||
XCTAssertNoThrow(try fileMng.spt_forceSymbolicLink(
|
||||
at: sourceDir,
|
||||
withDestinationURL: destinationDir
|
||||
))
|
||||
XCTAssertNoThrow(try fileMng.spt_createEmptyFile(destinationDir.appendingPathComponent(filename)))
|
||||
|
||||
return sourceDir.appendingPathComponent(filename)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class OverlayDependenciesRemapperTests: XCTestCase {
|
||||
private let overlayReader = OverlayReaderFake(
|
||||
mappings: [.init(virtual: "/file.h", local: "/Intermediate/Some/file.h")]
|
||||
)
|
||||
|
||||
func testMappingFromLocalToGeneric() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
let dependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/file.h"])
|
||||
}
|
||||
|
||||
func testMappingFromGenericToLocal() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
|
||||
let dependencies = try remapper.replace(genericPaths: ["/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
|
||||
}
|
||||
|
||||
func testGenericDependenciesAreNotMerged() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
|
||||
let dependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h", "/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/file.h", "/file.h"])
|
||||
}
|
||||
|
||||
func testLocalDependenciesAreNotMerged() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
|
||||
let dependencies = try remapper.replace(genericPaths: ["/Intermediate/Some/file.h", "/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h", "/Intermediate/Some/file.h"])
|
||||
}
|
||||
|
||||
func testMappingsAreReadOnce() throws {
|
||||
let remapper = OverlayDependenciesRemapper(overlayReader: overlayReader)
|
||||
|
||||
let firstDependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h"])
|
||||
// Update mappings in-fly to verify the previous value is cached in a remapper
|
||||
overlayReader.mappings = []
|
||||
let secondDependencies = try remapper.replace(localPaths: ["/Intermediate/Some/file.h"])
|
||||
|
||||
XCTAssertEqual(firstDependencies, ["/file.h"])
|
||||
XCTAssertEqual(secondDependencies, ["/file.h"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class JsonOverlayReaderTests: FileXCTestCase {
|
||||
private static let resourcesSubdirectory = "TestData/Dependencies/JsonOverlayReaderTests"
|
||||
|
||||
func testParsingWithSuccess() throws {
|
||||
let file = try pathForTestData(name: "overlayReaderDefault")
|
||||
let reader = JsonOverlayReader(file, mode: .strict, fileReader: FileManager.default)
|
||||
let mappings = try reader.provideMappings()
|
||||
|
||||
let expectedMappings = [
|
||||
OverlayMapping(
|
||||
virtual: "/DerivedDataProducts/Target1.framework/Headers/Target1.h",
|
||||
local: "/Path/Target1/Target1.h"
|
||||
),
|
||||
OverlayMapping(
|
||||
virtual: "/DerivedDataProducts/Target2.framework/Modules/module.modulemap",
|
||||
local: "/DerivedDataIntermediate/Target2.build/module.modulemap"
|
||||
),
|
||||
]
|
||||
XCTAssertEqual(Set(mappings), Set(expectedMappings))
|
||||
}
|
||||
|
||||
func testFailsWithMissingFileForStrictMode() throws {
|
||||
let file: URL = "nonExiting"
|
||||
let reader = JsonOverlayReader(file, mode: .strict, fileReader: FileManager.default)
|
||||
|
||||
XCTAssertThrowsError(try reader.provideMappings())
|
||||
}
|
||||
|
||||
func testReturnsEmpptyMappingForMissingFileForBestEffortMode() throws {
|
||||
let file: URL = "nonExiting"
|
||||
let reader = JsonOverlayReader(file, mode: .bestEffort, fileReader: FileManager.default)
|
||||
|
||||
let mappings = try reader.provideMappings()
|
||||
|
||||
XCTAssertEqual(mappings, [])
|
||||
}
|
||||
|
||||
func testParsingEmptyOverlay() throws {
|
||||
let file = try pathForTestData(name: "overlayReaderEmpty")
|
||||
let reader = JsonOverlayReader(file, mode: .strict, fileReader: FileManager.default)
|
||||
let mappings = try reader.provideMappings()
|
||||
|
||||
XCTAssertEqual(mappings, [])
|
||||
}
|
||||
|
||||
func testInvalidJsonDoesntThrowForBestEffortMode() throws {
|
||||
let workingDir = try prepareTempDir()
|
||||
let file = workingDir.appendingPathExtension("overlay.json")
|
||||
try fileManager.spt_createEmptyFile(file)
|
||||
let reader = JsonOverlayReader(file, mode: .bestEffort, fileReader: fileManager)
|
||||
let mappings = try reader.provideMappings()
|
||||
|
||||
XCTAssertEqual(mappings, [])
|
||||
}
|
||||
|
||||
func testInvalidJsonThrowsForStrictMode() throws {
|
||||
let workingDir = try prepareTempDir()
|
||||
let file = workingDir.appendingPathExtension("overlay.json")
|
||||
try fileManager.spt_createEmptyFile(file)
|
||||
let reader = JsonOverlayReader(file, mode: .strict, fileReader: fileManager)
|
||||
|
||||
XCTAssertThrowsError(try reader.provideMappings())
|
||||
}
|
||||
|
||||
private func pathForTestData(name: String) throws -> URL {
|
||||
return try XCTUnwrap(Bundle.module.url(
|
||||
forResource: name,
|
||||
withExtension: "json",
|
||||
subdirectory: JsonOverlayReaderTests.resourcesSubdirectory
|
||||
))
|
||||
}
|
||||
}
|
||||
+30
-7
@@ -20,11 +20,12 @@
|
||||
@testable import XCRemoteCache
|
||||
import XCTest
|
||||
|
||||
class StringDependenciesRemapperFactoryTests: XCTestCase {
|
||||
private var factory: StringDependenciesRemapperFactory!
|
||||
class PathDependenciesRemapperFactoryTests: XCTestCase {
|
||||
private var factory: PathDependenciesRemapperFactory!
|
||||
|
||||
override func setUp() {
|
||||
factory = StringDependenciesRemapperFactory()
|
||||
super.setUp()
|
||||
factory = PathDependenciesRemapperFactory()
|
||||
}
|
||||
|
||||
func testMappingsFromEnvMaps() throws {
|
||||
@@ -34,12 +35,34 @@ class StringDependenciesRemapperFactoryTests: XCTestCase {
|
||||
customMappings: [:]
|
||||
)
|
||||
|
||||
let localPaths = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
let localPaths = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testInvalidMappingsFromEnvFails() throws {
|
||||
XCTAssertThrowsError(
|
||||
func testMappingsGenericWhenMappingHasParentDir() throws {
|
||||
let remapper = try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["SRC_ROOT": "/tmp/root/extra/.."],
|
||||
customMappings: [:]
|
||||
)
|
||||
|
||||
let localPaths = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testMappingsLocalWhenMappingHasParentDir() throws {
|
||||
let remapper = try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["SRC_ROOT": "/tmp/root/excessive/.."],
|
||||
customMappings: [:]
|
||||
)
|
||||
|
||||
let localPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
XCTAssertEqual(localPaths, ["$(SRC_ROOT)/some.swift"])
|
||||
}
|
||||
|
||||
func testMissingEnvIsSkipped() throws {
|
||||
XCTAssertNoThrow(
|
||||
try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["NO_SRC_ROOT": ""],
|
||||
@@ -55,7 +78,7 @@ class StringDependenciesRemapperFactoryTests: XCTestCase {
|
||||
customMappings: ["TMP": "/tmp"]
|
||||
)
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: ["/some/repoFile.swift", "/tmp/externalFile.swift"])
|
||||
let genericPaths = try remapper.replace(localPaths: ["/some/repoFile.swift", "/tmp/externalFile.swift"])
|
||||
XCTAssertEqual(genericPaths, ["$(PWD)/repoFile.swift", "$(TMP)/externalFile.swift"])
|
||||
}
|
||||
|
||||
@@ -30,34 +30,34 @@ class StringDependenciesRemapperTests: XCTestCase {
|
||||
remapper = StringDependenciesRemapper(mappings: mappings)
|
||||
}
|
||||
|
||||
func testMappingSingleGenericPathReplacesWithLocalPath() {
|
||||
let localPaths = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
func testMappingSingleGenericPathReplacesWithLocalPath() throws {
|
||||
let localPaths = try remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testRewritingSingleLocalPathReplacesWithGenericPath() {
|
||||
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
func testRewritingSingleLocalPathReplacesWithGenericPath() throws {
|
||||
let genericPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
|
||||
XCTAssertEqual(genericPaths, ["$(SRC_ROOT)/some.swift"])
|
||||
}
|
||||
|
||||
func testRewritingLocalToGenericAndLocalIsIdentical() {
|
||||
func testRewritingLocalToGenericAndLocalIsIdentical() throws {
|
||||
let inputLocalPaths = ["/tmp/root/some.swift"]
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: inputLocalPaths)
|
||||
let localPaths = remapper.replace(genericPaths: genericPaths)
|
||||
let genericPaths = try remapper.replace(localPaths: inputLocalPaths)
|
||||
let localPaths = try remapper.replace(genericPaths: genericPaths)
|
||||
|
||||
XCTAssertEqual(localPaths, inputLocalPaths)
|
||||
}
|
||||
|
||||
func testRewritingUnrelatedDirReturnsInputPath() {
|
||||
let genericPaths = remapper.replace(localPaths: ["/other/some.swift"])
|
||||
func testRewritingUnrelatedDirReturnsInputPath() throws {
|
||||
let genericPaths = try remapper.replace(localPaths: ["/other/some.swift"])
|
||||
|
||||
XCTAssertEqual(genericPaths, ["/other/some.swift"])
|
||||
}
|
||||
|
||||
func testMultipleMatchesTakeTheFirstMapping() {
|
||||
func testMultipleMatchesTakeTheFirstMapping() throws {
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = [
|
||||
.init(generic: "$(SRC_ROOT)", local: "/tmp/root"),
|
||||
.init(generic: "$(PWD)", local: "/tmp"),
|
||||
@@ -65,12 +65,12 @@ class StringDependenciesRemapperTests: XCTestCase {
|
||||
remapper = StringDependenciesRemapper(mappings: mappings)
|
||||
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift", "/tmp/extra.swift"])
|
||||
let genericPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift", "/tmp/extra.swift"])
|
||||
|
||||
XCTAssertEqual(genericPaths, ["$(SRC_ROOT)/some.swift", "$(PWD)/extra.swift"])
|
||||
}
|
||||
|
||||
func testMappingsLocalPathsIsDoneInOrder() {
|
||||
func testMappingsLocalPathsIsDoneInOrder() throws {
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = [
|
||||
.init(generic: "$(TMP)", local: "/tmp"),
|
||||
.init(generic: "$(ROOT)", local: "$(TMP)/root"),
|
||||
@@ -78,12 +78,12 @@ class StringDependenciesRemapperTests: XCTestCase {
|
||||
remapper = StringDependenciesRemapper(mappings: mappings)
|
||||
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
let genericPaths = try remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
|
||||
XCTAssertEqual(genericPaths, ["$(ROOT)/some.swift"])
|
||||
}
|
||||
|
||||
func testMappingsGenericPathsIsDoneInReversedOrder() {
|
||||
func testMappingsGenericPathsIsDoneInReversedOrder() throws {
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = [
|
||||
.init(generic: "$(TMP)", local: "/tmp"),
|
||||
.init(generic: "$(ROOT)", local: "$(TMP)/root"),
|
||||
@@ -91,7 +91,7 @@ class StringDependenciesRemapperTests: XCTestCase {
|
||||
remapper = StringDependenciesRemapper(mappings: mappings)
|
||||
|
||||
|
||||
let localPaths = remapper.replace(genericPaths: ["$(ROOT)/some.swift"])
|
||||
let localPaths = try remapper.replace(genericPaths: ["$(ROOT)/some.swift"])
|
||||
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
"DYLIB_COMPATIBILITY_VERSION": "2",
|
||||
"DYLIB_CURRENT_VERSION": "3",
|
||||
"PRODUCT_MODULE_NAME": "4",
|
||||
"ARCHS": "AR"
|
||||
"ARCHS": "AR",
|
||||
]
|
||||
/// Corresponds to EnvironmentFingerprintGenerator.version
|
||||
private static let currentVersion = "5"
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"case-sensitive": "false",
|
||||
"roots":
|
||||
[
|
||||
{
|
||||
"contents":
|
||||
[
|
||||
{
|
||||
"external-contents": "/Path/Target1/Target1.h",
|
||||
"name": "Target1.h",
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"name": "/DerivedDataProducts/Target1.framework/Headers",
|
||||
"type": "directory",
|
||||
},
|
||||
{
|
||||
"contents":
|
||||
[
|
||||
{
|
||||
"external-contents": "/DerivedDataIntermediate/Target2.build/module.modulemap",
|
||||
"name": "module.modulemap",
|
||||
"type": "file",
|
||||
},
|
||||
],
|
||||
"name": "/DerivedDataProducts/Target2.framework/Modules",
|
||||
"type": "directory",
|
||||
},
|
||||
],
|
||||
"version": 0,
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"case-sensitive":"false","roots":[],"version":0}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
@testable import XCRemoteCache
|
||||
|
||||
class OverlayReaderFake: OverlayReader {
|
||||
var mappings: [OverlayMapping]
|
||||
|
||||
init(mappings: [OverlayMapping]) {
|
||||
self.mappings = mappings
|
||||
}
|
||||
|
||||
func provideMappings() throws -> [OverlayMapping] {
|
||||
return mappings
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ An object that is passed to the `xcremotecache` can contain all properties suppo
|
||||
| `xccc_file` | The path where should be placed the `xccc` binary (in the pod installation phase) | `{podfile_dir}/.rc/xccc` | ⬜️ |
|
||||
| `remote_commit_file` | The path of the file with the remote commit sha (in the pod installation phase) | `{podfile_dir}/.rc/arc.rc`| ⬜️ |
|
||||
| `prettify_meta_files` | A Boolean value that opts-in pretty JSON formatting for meta files | `false` | ⬜️ |
|
||||
| `disable_certificate_verification` | A Boolean value that opts-in SSL certificate validation is disabled | `false` | ⬜️ |
|
||||
|
||||
## Uninstalling
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
LLDB_INIT_PATH = "#{ENV['HOME']}/.lldbinit"
|
||||
FAT_ARCHIVE_NAME_INFIX = 'arm64-x86_64'
|
||||
|
||||
# List of plugins' user properties that should be copied to .rcinfo
|
||||
CUSTOM_CONFIGURATION_KEYS = [
|
||||
'enabled',
|
||||
'xcrc_location',
|
||||
@@ -37,7 +38,9 @@ module CocoapodsXCRemoteCacheModifier
|
||||
'final_target',
|
||||
'check_build_configuration',
|
||||
'check_platform',
|
||||
'modify_lldb_init'
|
||||
'modify_lldb_init',
|
||||
'prettify_meta_files',
|
||||
'disable_certificate_verification'
|
||||
]
|
||||
|
||||
class XCRemoteCache
|
||||
@@ -59,7 +62,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
'xccc_file' => "#{BIN_DIR}/xccc",
|
||||
'remote_commit_file' => "#{BIN_DIR}/arc.rc",
|
||||
'exclude_targets' => [],
|
||||
'prettify_meta_files' => false
|
||||
'prettify_meta_files' => false,
|
||||
'disable_certificate_verification' => false
|
||||
}
|
||||
@@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
|
||||
end
|
||||
@@ -78,8 +82,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
mode = @@configuration['mode']
|
||||
unless mode == 'consumer' || mode == 'producer'
|
||||
throw "Incorrect 'mode' value. Allowed values are ['consumer', 'producer'], but you provided '#{mode}'. A typo?"
|
||||
unless mode == 'consumer' || mode == 'producer' || mode == 'producer-fast'
|
||||
throw "Incorrect 'mode' value. Allowed values are ['consumer', 'producer', 'producer-fast'], but you provided '#{mode}'. A typo?"
|
||||
end
|
||||
|
||||
unless mode == 'consumer' || @@configuration.key?('final_target')
|
||||
@@ -99,7 +103,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# @param repo_distance [Integer] distance from the git repo root to the target's $SRCROOT
|
||||
# @param xc_location [String] path to the dir with all XCRemoteCache binaries, relative to the repo root
|
||||
# @param xc_cc_path [String] path to the XCRemoteCache clang wrapper, relative to the repo root
|
||||
# @param mode [String] mode name ('consumer', 'producer' etc.)
|
||||
# @param mode [String] mode name ('consumer', 'producer', 'producer-fast' etc.)
|
||||
# @param exclude_build_configurations [String[]] list of targets that should have disabled remote cache
|
||||
# @param final_target [String] name of target that should trigger marking
|
||||
def self.enable_xcremotecache(target, repo_distance, xc_location, xc_cc_path, mode, exclude_build_configurations, final_target)
|
||||
@@ -110,6 +114,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
next if exclude_build_configurations.include?(config.name)
|
||||
if mode == 'consumer'
|
||||
config.build_settings['CC'] = ["$SRCROOT/#{parent_dir(xc_cc_path, repo_distance)}"]
|
||||
elsif mode == 'producer' || mode == 'producer-fast'
|
||||
config.build_settings.delete('CC') if config.build_settings.key?('CC')
|
||||
end
|
||||
config.build_settings['SWIFT_EXEC'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcswiftc"]
|
||||
config.build_settings['LIBTOOL'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xclibtool"]
|
||||
@@ -129,7 +135,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
phase.name != nil && phase.name.start_with?("[XCRC] Prebuild")
|
||||
end
|
||||
end
|
||||
prebuild_script = existing_prebuild_script || target.new_shell_script_build_phase("[XCRC] Prebuild")
|
||||
|
||||
prebuild_script = existing_prebuild_script || target.new_shell_script_build_phase("[XCRC] Prebuild #{target.name}")
|
||||
prebuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
|
||||
prebuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprebuild"]
|
||||
prebuild_script.output_paths = [
|
||||
@@ -138,9 +145,10 @@ module CocoapodsXCRemoteCacheModifier
|
||||
]
|
||||
prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d"
|
||||
|
||||
# Move prebuild (last element) to the first position (to make it real 'prebuild')
|
||||
target.build_phases.rotate!(-1) if existing_prebuild_script.nil?
|
||||
elsif mode == 'producer'
|
||||
# Move prebuild (last element) to the position before compile sources phase (to make it real 'prebuild')
|
||||
compile_phase_index = target.build_phases.index(target.source_build_phase)
|
||||
target.build_phases.insert(compile_phase_index, target.build_phases.delete(prebuild_script))
|
||||
elsif mode == 'producer' || mode == 'producer-fast'
|
||||
# Delete existing prebuild build phase (to support switching between modes)
|
||||
target.build_phases.delete_if do |phase|
|
||||
if phase.respond_to?(:name)
|
||||
@@ -155,7 +163,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
phase.name != nil && phase.name.start_with?("[XCRC] Postbuild")
|
||||
end
|
||||
end
|
||||
postbuild_script = existing_postbuild_script || target.new_shell_script_build_phase("[XCRC] Postbuild")
|
||||
postbuild_script = existing_postbuild_script || target.new_shell_script_build_phase("[XCRC] Postbuild #{target.name}")
|
||||
postbuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
|
||||
postbuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcpostbuild"]
|
||||
postbuild_script.output_paths = [
|
||||
@@ -165,7 +173,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d"
|
||||
|
||||
# Mark a sha as ready for a given platform and configuration when building the final_target
|
||||
if mode == 'producer' && target.name == final_target
|
||||
if (mode == 'producer' || mode == 'producer-fast') && target.name == final_target
|
||||
existing_mark_script = target.build_phases.detect do |phase|
|
||||
if phase.respond_to?(:name)
|
||||
phase.name != nil && phase.name.start_with?("[XCRC] Mark")
|
||||
@@ -190,8 +198,9 @@ module CocoapodsXCRemoteCacheModifier
|
||||
config.build_settings.delete('SWIFT_EXEC') if config.build_settings.key?('SWIFT_EXEC')
|
||||
config.build_settings.delete('LIBTOOL') if config.build_settings.key?('LIBTOOL')
|
||||
config.build_settings.delete('LD') if config.build_settings.key?('LD')
|
||||
# Add Fake src root for ObjC & Swift
|
||||
# Remove Fake src root for ObjC & Swift
|
||||
config.build_settings.delete('XCREMOTE_CACHE_FAKE_SRCROOT')
|
||||
config.build_settings.delete('XCRC_PLATFORM_PREFERRED_ARCH')
|
||||
remove_cflags!(config.build_settings, '-fdebug-prefix-map')
|
||||
remove_swiftflags!(config.build_settings, '-debug-prefix-map')
|
||||
end
|
||||
@@ -253,14 +262,14 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
def self.add_cflags!(options, key, value)
|
||||
return if options.fetch('OTHER_CFLAGS',[]).include?(' ' + value)
|
||||
options['OTHER_CFLAGS'] = remove_cflags!(options, key) << " #{key}=#{value}"
|
||||
return if options.fetch('OTHER_CFLAGS',[]).include?(value)
|
||||
options['OTHER_CFLAGS'] = remove_cflags!(options, key) << "#{key}=#{value}"
|
||||
end
|
||||
|
||||
def self.remove_cflags!(options, key)
|
||||
cflags_arr = options.fetch('OTHER_CFLAGS', ['$(inherited)'])
|
||||
cflags_arr = [cflags_arr] if cflags_arr.kind_of? String
|
||||
options['OTHER_CFLAGS'] = cflags_arr.delete_if {|flag| flag.start_with?(" #{key}=") }
|
||||
options['OTHER_CFLAGS'] = cflags_arr.delete_if {|flag| flag.include?("#{key}=") }
|
||||
options['OTHER_CFLAGS']
|
||||
end
|
||||
|
||||
@@ -275,12 +284,27 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
# Uninstall the XCRemoteCache
|
||||
def self.disable_xcremotecache(user_project)
|
||||
def self.disable_xcremotecache(user_project, pods_project = nil)
|
||||
user_project.targets.each do |target|
|
||||
disable_xcremotecache_for_target(target)
|
||||
end
|
||||
user_project.save()
|
||||
|
||||
unless pods_project.nil?
|
||||
pods_project.native_targets.each do |target|
|
||||
disable_xcremotecache_for_target(target)
|
||||
end
|
||||
pods_proj_directory = pods_project.project_dir
|
||||
pods_project.root_object.project_references.each do |subproj_ref|
|
||||
generated_project = Xcodeproj::Project.open("#{pods_proj_directory}/#{subproj_ref[:project_ref].path}")
|
||||
generated_project.native_targets.each do |target|
|
||||
disable_xcremotecache_for_target(target)
|
||||
end
|
||||
generated_project.save()
|
||||
end
|
||||
pods_project.save()
|
||||
end
|
||||
|
||||
# Remove .lldbinit rewrite
|
||||
save_lldbinit_rewrite(nil) unless !@@configuration['modify_lldb_init']
|
||||
end
|
||||
@@ -337,7 +361,6 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
validate_configuration()
|
||||
|
||||
mode = @@configuration['mode']
|
||||
xccc_location = @@configuration['xccc_file']
|
||||
remote_commit_file = @@configuration['remote_commit_file']
|
||||
@@ -372,7 +395,9 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# Always integrate XCRemoteCache to all Pods, in case it will be needed later
|
||||
unless installer_context.pods_project.nil?
|
||||
# Attach XCRemoteCache to Pods targets
|
||||
installer_context.pods_project.targets.each do |target|
|
||||
# Enable only for native targets which can have compilation steps
|
||||
installer_context.pods_project.native_targets.each do |target|
|
||||
next if target.source_build_phase.files_references.empty?
|
||||
next if target.name.start_with?("Pods-")
|
||||
next if target.name.end_with?("Tests")
|
||||
next if exclude_targets.include?(target.name)
|
||||
@@ -382,6 +407,18 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
|
||||
pods_proj_directory = installer_context.sandbox_root
|
||||
|
||||
# Attach XCRemoteCache to Generated Pods projects
|
||||
installer_context.pods_project.root_object.project_references.each do |subproj_ref|
|
||||
generated_project = Xcodeproj::Project.open("#{pods_proj_directory}/#{subproj_ref[:project_ref].path}")
|
||||
generated_project.native_targets.each do |target|
|
||||
next if target.source_build_phase.files_references.empty?
|
||||
next if target.name.end_with?("Tests")
|
||||
next if exclude_targets.include?(target.name)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target)
|
||||
end
|
||||
generated_project.save()
|
||||
end
|
||||
|
||||
# Manual Pods/.rcinfo generation
|
||||
|
||||
# all paths in .rcinfo are relative to the root so paths used in Pods.xcodeproj need to be aligned
|
||||
@@ -403,12 +440,12 @@ module CocoapodsXCRemoteCacheModifier
|
||||
prepare_result = YAML.load`#{xcrc_location_absolute}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}`
|
||||
unless prepare_result['result'] || mode != 'consumer'
|
||||
# Uninstall the XCRemoteCache for the consumer mode
|
||||
disable_xcremotecache(user_project)
|
||||
disable_xcremotecache(user_project, installer_context.pods_project)
|
||||
Pod::UI.puts "[XCRC] XCRemoteCache disabled - no artifacts available"
|
||||
next
|
||||
end
|
||||
rescue => error
|
||||
disable_xcremotecache(user_project)
|
||||
disable_xcremotecache(user_project, installer_context.pods_project)
|
||||
Pod::UI.puts "[XCRC] XCRemoteCache failed with an error: #{error}."
|
||||
next
|
||||
end
|
||||
@@ -433,7 +470,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
rescue Exception => e
|
||||
Pod::UI.puts "[XCRC] XCRemoteCache disabled with error: #{e}"
|
||||
puts e.full_message(highlight: true, order: :top)
|
||||
disable_xcremotecache(user_project)
|
||||
disable_xcremotecache(user_project, installer_context.pods_project)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
# limitations under the License.
|
||||
|
||||
module CocoapodsXcremotecache
|
||||
VERSION = "0.0.4"
|
||||
VERSION = "0.0.7"
|
||||
end
|
||||
|
||||
+40
-1
@@ -23,9 +23,48 @@ If you prefer to edit in Xcode, run `swift package generate-xcodeproj`. Do **not
|
||||
|
||||
The generated Xcode project contains schemes for each output application (like `xcswiftc`, `xcprebuild` etc.) so to build a single app, just select the appropriate scheme and build (⌘+B). If you want to build all applications at once, select `Aggregator` scheme that automatically builds all apps. `Aggregator` target in `Package.swift` is defined only for development convenience, it shouldn't be ever used as a dependency.
|
||||
|
||||
#### Debugging the app in a real project
|
||||
|
||||
Debugging XCRemoteCache in a real project is simple:
|
||||
* Open XCRemoteCache's `Package.swift` in Xcode and select a scheme that corresponds to the process you want to debug, e.g. `xcpostbuild`
|
||||
|
||||

|
||||
|
||||
* Build the app with Product->Build (or ⌘+B)
|
||||
* Open the scheme's settings (⌘+⇧+<) and for the "Run" action, select "Wait for the executable to be launched"
|
||||
|
||||

|
||||
|
||||
* Find the produced binary in your DerivedData. The default location would be `~/Library/Developer/Xcode/DerivedData/XCRemoteCache-{hash}/Build/Products/Debug/xcpostbuild`
|
||||
* In the project you want to debug, pick the target you want to inspect and expand the build phase that calls the process
|
||||
* Replace the Input Files path so it points the locally built binary placed in DerivedData
|
||||
|
||||

|
||||
|
||||
* Now, you can run the **XCRemoteCache** project (not the project you want to debug). Because Xcode will not initiate a new process, it will just wait until someone else triggers it.
|
||||
|
||||

|
||||
|
||||
* Finally, build the project to want to debug and expect your XCRemoteCache breakpoints to hit
|
||||
|
||||

|
||||
|
||||
> Tip: If your breakpoints don't hit, try adding a dummy `sleep(1)` in the process entry point (e.g. at the top of `XCPostbuild.main` function)
|
||||
|
||||
#### Running tests in Xcode
|
||||
|
||||
All unit tests are placed in `XCRemoteCacheTests`. To run them from Xcode, just pick any application scheme and run tests (⌘+U).
|
||||
All unit tests are placed in `XCRemoteCacheTests`. To run them from Xcode, just pick any application scheme and run tests (⌘+U).
|
||||
|
||||
#### Running E2E tests
|
||||
|
||||
E2E tests build a CocoaPods plugin, locally build both `producer` and `consumer` modes and verify 100% hit rate. All `Podfile` templates are placed in [e2eTests/tests](../e2eTests/tests). As a backend server, nginx server with a sample [nginx.conf](../e2eTests/nginx/nginx.conf) configuration is used. The sample server exposes local location `/tmp/cache` under http://localhost:8080.
|
||||
|
||||
To run tests locally, install `nginx` (e.g. `brew install nginx`) and call:
|
||||
|
||||
```bash
|
||||
rake 'build[release]'
|
||||
rake e2e_only
|
||||
```
|
||||
|
||||
## Project organization
|
||||
|
||||
|
||||
+15
@@ -50,3 +50,18 @@ log show --predicate 'sender == "xcprepare"' --style compact --info --debug -las
|
||||
log show --predicate 'sender BEGINSWITH "xc"' --style compact --info --debug -last 10m
|
||||
```
|
||||
</details>
|
||||
|
||||
### Troubleshooting cache misses
|
||||
|
||||
Here is a non-exhaustive list of steps that may help with troubleshooting poor cache hit rate.
|
||||
|
||||
1. ***Producer&Consumer:*** Review XCRemoteCache [Requirements](../#Requirements) and [Limitations](../#limitations)
|
||||
1. ***Producer&Consumer:*** Make sure a producer build uses the same architecture(s) as a consumer. You can inspect `ARCHS` Build Setting in Xcode's Script Phase output logs. Navigate to the report navigator (⌘+9) and expand XCRemoteCache's `prebuild` step output using the "collapsed menu icon" (aka hamburger menu)
|
||||
1. ***Producer:*** Verify that all Xcode targets have a Build Phase called `postbuild`
|
||||
1. ***Producer:*** If you are using optional XCRemoteCache auto-marking feature (`--final-producer-target` or `final_target`) verify an extra Build Phase called `mark` is added to the specified target
|
||||
1. ***Producer:*** After a full build, review logs according to [docs](#how-can-i-find-xcremotecache-logs)
|
||||
1. ***Consumer:*** Verify that all Xcode targets have extra XCRemoteCache Build Phase called `prebuild` and `postbuild`
|
||||
1. ***Consumer:*** After a full build, review according to [docs](#how-can-i-find-xcremotecache-logs). Find a ***first:*** target that reports a cache miss with a message like `Prebuild step failed with error: ...`. If a target reports faces a cache miss, it may have a knock-on effect where a lot of its consumers (dependant targets) need to be built locally too
|
||||
1. ***Consumer:*** ***After a full build, review all meta files placed in `~/Library/Caches/XCRemoteCache/{your_host_path}/meta/*.json` and make sure no absolute paths are used in its `dependencies`. All paths should start a placeholder, like `$(SRCROOT)` or `$(BUILD_DIR)`***
|
||||
1. ***Consumer:*** If you are integrating XCRemoteCache and rebuild artifacts for the same sha, previously downloaded artifacts placed in a local cache may still be used on a consumer side. You can either manually delete your local cache at `~/Library/Caches/XCRemoteCache/` before any consumer build or disable a local cache with `artifact_maximum_age: 0` property in `.rcinfo`
|
||||
1. ***Consumer:*** To find an actual cache hit, before building in Xcode reset statistics with `xcprepare stats --reset` and once it is done, call `xcprepare stats` to find a cache hit rate
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 370 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 216 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,363 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
3695D9CC27A3218C007F3792 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3695D9CB27A3218C007F3792 /* AppDelegate.swift */; };
|
||||
3695D9CE27A3218C007F3792 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3695D9CD27A3218C007F3792 /* SceneDelegate.swift */; };
|
||||
3695D9D027A3218C007F3792 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3695D9CF27A3218C007F3792 /* ViewController.swift */; };
|
||||
3695D9D327A3218C007F3792 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3695D9D127A3218C007F3792 /* Main.storyboard */; };
|
||||
3695D9D527A3218D007F3792 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3695D9D427A3218D007F3792 /* Assets.xcassets */; };
|
||||
3695D9D827A3218D007F3792 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3695D9D627A3218D007F3792 /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3695D9C827A3218C007F3792 /* XCRemoteCacheSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XCRemoteCacheSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3695D9CB27A3218C007F3792 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
3695D9CD27A3218C007F3792 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
3695D9CF27A3218C007F3792 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
3695D9D227A3218C007F3792 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
3695D9D427A3218D007F3792 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
3695D9D727A3218D007F3792 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
3695D9D927A3218D007F3792 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
3695D9C527A3218C007F3792 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
3695D9BF27A3218C007F3792 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3695D9CA27A3218C007F3792 /* XCRemoteCacheSample */,
|
||||
3695D9C927A3218C007F3792 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3695D9C927A3218C007F3792 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3695D9C827A3218C007F3792 /* XCRemoteCacheSample.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3695D9CA27A3218C007F3792 /* XCRemoteCacheSample */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3695D9CB27A3218C007F3792 /* AppDelegate.swift */,
|
||||
3695D9CD27A3218C007F3792 /* SceneDelegate.swift */,
|
||||
3695D9CF27A3218C007F3792 /* ViewController.swift */,
|
||||
3695D9D127A3218C007F3792 /* Main.storyboard */,
|
||||
3695D9D427A3218D007F3792 /* Assets.xcassets */,
|
||||
3695D9D627A3218D007F3792 /* LaunchScreen.storyboard */,
|
||||
3695D9D927A3218D007F3792 /* Info.plist */,
|
||||
);
|
||||
path = XCRemoteCacheSample;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
3695D9C727A3218C007F3792 /* XCRemoteCacheSample */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 3695D9DC27A3218D007F3792 /* Build configuration list for PBXNativeTarget "XCRemoteCacheSample" */;
|
||||
buildPhases = (
|
||||
3695D9C427A3218C007F3792 /* Sources */,
|
||||
3695D9C527A3218C007F3792 /* Frameworks */,
|
||||
3695D9C627A3218C007F3792 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = XCRemoteCacheSample;
|
||||
productName = XCRemoteCacheSample;
|
||||
productReference = 3695D9C827A3218C007F3792 /* XCRemoteCacheSample.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
3695D9C027A3218C007F3792 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1320;
|
||||
TargetAttributes = {
|
||||
3695D9C727A3218C007F3792 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 3695D9C327A3218C007F3792 /* Build configuration list for PBXProject "XCRemoteCacheSample" */;
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 3695D9BF27A3218C007F3792;
|
||||
productRefGroup = 3695D9C927A3218C007F3792 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
3695D9C727A3218C007F3792 /* XCRemoteCacheSample */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
3695D9C627A3218C007F3792 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3695D9D827A3218D007F3792 /* LaunchScreen.storyboard in Resources */,
|
||||
3695D9D527A3218D007F3792 /* Assets.xcassets in Resources */,
|
||||
3695D9D327A3218C007F3792 /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
3695D9C427A3218C007F3792 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3695D9D027A3218C007F3792 /* ViewController.swift in Sources */,
|
||||
3695D9CC27A3218C007F3792 /* AppDelegate.swift in Sources */,
|
||||
3695D9CE27A3218C007F3792 /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
3695D9D127A3218C007F3792 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
3695D9D227A3218C007F3792 /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3695D9D627A3218D007F3792 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
3695D9D727A3218D007F3792 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
3695D9DA27A3218D007F3792 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3695D9DB27A3218D007F3792 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
3695D9DD27A3218D007F3792 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = XCRemoteCacheSample/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.XCRemoteCacheSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
3695D9DE27A3218D007F3792 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = XCRemoteCacheSample/Info.plist;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UIMainStoryboardFile = Main;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.XCRemoteCacheSample;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
3695D9C327A3218C007F3792 /* Build configuration list for PBXProject "XCRemoteCacheSample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3695D9DA27A3218D007F3792 /* Debug */,
|
||||
3695D9DB27A3218D007F3792 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
3695D9DC27A3218D007F3792 /* Build configuration list for PBXNativeTarget "XCRemoteCacheSample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
3695D9DD27A3218D007F3792 /* Debug */,
|
||||
3695D9DE27A3218D007F3792 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 3695D9C027A3218C007F3792 /* Project object */;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>Default Configuration</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2021 Spotify AB.
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import UIKit
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# Nginx configuration used for E2E tests cache server
|
||||
# Listens HTTP on port 8080
|
||||
|
||||
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
default_type application/octet-stream;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /cache/ {
|
||||
# The path to the directory where nginx should store the cache contents.
|
||||
root /tmp/cache;
|
||||
# Allow PUT
|
||||
dav_methods PUT;
|
||||
create_full_put_path on;
|
||||
# The maximum size of a single file.
|
||||
client_max_body_size 1G;
|
||||
allow all;
|
||||
}
|
||||
sendfile on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
plugin 'cocoapods-xcremotecache'
|
||||
|
||||
target 'XCRemoteCacheSample' do
|
||||
use_frameworks!
|
||||
|
||||
pod 'Firebase/Analytics'
|
||||
pod 'ReactiveSwift'
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
plugin 'cocoapods-xcremotecache'
|
||||
|
||||
install! 'cocoapods', :generate_multiple_pod_projects => true
|
||||
|
||||
|
||||
target 'XCRemoteCacheSample' do
|
||||
use_frameworks!
|
||||
|
||||
pod 'Firebase/Analytics'
|
||||
pod 'ReactiveSwift'
|
||||
end
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
require 'json'
|
||||
|
||||
desc 'Support for E2E tests: building XCRemoteCache-enabled xcodeproj using xcodebuild'
|
||||
namespace :e2e do
|
||||
COCOAPODS_DIR = 'cocoapods-plugin'
|
||||
COCOAPODS_GEMSPEC_FILENAME = "cocoapods-xcremotecache.gemspec"
|
||||
E2E_COCOAPODS_SAMPLE_DIR = 'e2eTests/XCRemoteCacheSample'
|
||||
GIT_REMOTE_NAME = 'self'
|
||||
# Location of the remote address that points to itself
|
||||
GIT_REMOTE_ADDRESS = '.'
|
||||
GIT_BRANCH = 'e2e-test-branch'
|
||||
LOG_NAME = "xcodebuild.log"
|
||||
DERIVED_DATA_PATH = './DerivedData'
|
||||
NGINX_ROOT_DIR = '/tmp/cache'
|
||||
XCRC_BINARIES = 'XCRC'
|
||||
SHARED_COCOAPODS_CONFIG = {
|
||||
'cache_addresses' => ['http://localhost:8080/cache/pods'],
|
||||
'primary_repo' => GIT_REMOTE_ADDRESS,
|
||||
'primary_branch' => GIT_BRANCH,
|
||||
'mode' => 'consumer',
|
||||
'final_target' => 'XCRemoteCacheSample',
|
||||
'artifact_maximum_age' => 0
|
||||
}
|
||||
|
||||
Stats = Struct.new(:hits, :misses, :hit_rate)
|
||||
|
||||
# run E2E tests
|
||||
task :run => [:run_cocoapods]
|
||||
|
||||
# run E2E tests for CocoaPods-powered projects
|
||||
task :run_cocoapods do
|
||||
install_cocoapods_plugin
|
||||
start_nginx
|
||||
configure_git
|
||||
|
||||
# Run scenarios for all Podfile scenarios
|
||||
for podfile_path in Dir.glob('e2eTests/**/*.Podfile')
|
||||
run_cocoapods_scenario(podfile_path)
|
||||
end
|
||||
# Revert all side effects
|
||||
clean
|
||||
end
|
||||
|
||||
# Build and install a plugin
|
||||
def self.install_cocoapods_plugin
|
||||
Dir.chdir(COCOAPODS_DIR) do
|
||||
gemfile_path = "cocoapods-xcremotecache.gem"
|
||||
system("gem build #{COCOAPODS_GEMSPEC_FILENAME} -o #{gemfile_path}")
|
||||
system("gem install #{gemfile_path}")
|
||||
end
|
||||
end
|
||||
|
||||
def self.start_nginx
|
||||
# Start nginx server
|
||||
system('nginx -c $PWD/e2eTests/nginx/nginx.conf')
|
||||
puts('starting nginx')
|
||||
# Call cleanup on exit
|
||||
at_exit { puts('resetting ngingx'); system('nginx -s stop') }
|
||||
end
|
||||
|
||||
# Create a new branch out of a current commit and
|
||||
# add remote that points to itself
|
||||
def self.configure_git
|
||||
system("git checkout -B #{GIT_BRANCH}")
|
||||
system("git remote add #{GIT_REMOTE_NAME} #{GIT_REMOTE_ADDRESS} && git fetch -q #{GIT_REMOTE_NAME}")
|
||||
# Revert new remote on exit
|
||||
at_exit { system("git remote remove #{GIT_REMOTE_NAME}")}
|
||||
end
|
||||
|
||||
def self.pre_producer_setup
|
||||
clean_git
|
||||
clean_server
|
||||
# Link prebuilt binaries to the Project
|
||||
system("ln -s $(pwd)/releases #{E2E_COCOAPODS_SAMPLE_DIR}/#{XCRC_BINARIES}")
|
||||
end
|
||||
|
||||
def self.pre_consumer_setup
|
||||
clean_git
|
||||
# Link prebuilt binaries to the Project
|
||||
system("ln -s $(pwd)/releases #{E2E_COCOAPODS_SAMPLE_DIR}/#{XCRC_BINARIES}")
|
||||
end
|
||||
|
||||
def self.clean_server
|
||||
system("rm -rf #{NGINX_ROOT_DIR}")
|
||||
end
|
||||
|
||||
# Revert any local changes in the test project
|
||||
def self.clean_git
|
||||
system("git clean -xdf #{E2E_COCOAPODS_SAMPLE_DIR}")
|
||||
end
|
||||
|
||||
# Cleans all extra locations that a test creates
|
||||
def self.clean
|
||||
clean_git
|
||||
clean_server
|
||||
end
|
||||
|
||||
# xcremotecache configuration to add to Podfile
|
||||
def self.cocoapods_configuration_string(extra_configs = {})
|
||||
configuration_lines = ['xcremotecache({']
|
||||
all_properties = SHARED_COCOAPODS_CONFIG.merge(extra_configs)
|
||||
config_lines = all_properties.map {|key, value| " \"#{key}\" => #{value.inspect},"}
|
||||
configuration_lines.push(*config_lines)
|
||||
configuration_lines << '})'
|
||||
configuration_lines.join("\n")
|
||||
end
|
||||
|
||||
def self.dump_podfile(config, source)
|
||||
# Create producer Podfile
|
||||
File.open("#{E2E_COCOAPODS_SAMPLE_DIR}/Podfile", 'w') do |f|
|
||||
# Copy podfile template
|
||||
File.foreach(source) { |line| f.puts line }
|
||||
f.write(config)
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_project(extra_args = {})
|
||||
system('pod install')
|
||||
xcodebuild_args = {
|
||||
'workspace' => 'XCRemoteCacheSample.xcworkspace',
|
||||
'scheme' => 'XCRemoteCacheSample',
|
||||
'configuration' => 'Debug',
|
||||
'sdk' => 'iphonesimulator',
|
||||
'destination' => 'generic/platform=iOS Simulator',
|
||||
'derivedDataPath' => DERIVED_DATA_PATH,
|
||||
}.merge(extra_args)
|
||||
xcodebuild_vars = {
|
||||
'EXCLUDED_ARCHS' => 'arm64 i386'
|
||||
}
|
||||
args = ['xcodebuild']
|
||||
args.push(*xcodebuild_args.map {|k,v| "-#{k} '#{v}'"})
|
||||
args.push(*xcodebuild_vars.map {|k,v| "#{k}='#{v}'"})
|
||||
args.push('clean build')
|
||||
args.push("> #{LOG_NAME}")
|
||||
puts 'Building a project with xcodebuild...'
|
||||
system(args.join(' '))
|
||||
unless $?.success?
|
||||
system("tail #{LOG_NAME}")
|
||||
raise "xcodebuild failed."
|
||||
end
|
||||
end
|
||||
|
||||
def self.read_stats
|
||||
stats_json_string = JSON.parse(`#{XCRC_BINARIES}/xcprepare stats --format json`)
|
||||
misses = stats_json_string.fetch('miss_count', 0)
|
||||
hits = stats_json_string.fetch('hit_count', 0)
|
||||
all_targets = misses + hits
|
||||
raise "Failure: No XCRemoteCache targets invoked" if all_targets == 0
|
||||
hit_rate = hits * 100 / all_targets
|
||||
Stats.new(hits, misses, hit_rate)
|
||||
end
|
||||
|
||||
# validate 100% hit rate
|
||||
def self.valide_hit_rate
|
||||
status = read_stats()
|
||||
raise "Failure: Hit rate is only #{status.hit_rate}% (#{status.hits}/#{status.all_targets})" if status.misses > 0
|
||||
all_targets = status.misses + status.hits
|
||||
puts("Hit rate: #{status.hit_rate}% (#{status.hits}/#{all_targets})")
|
||||
end
|
||||
|
||||
def self.run_cocoapods_scenario(template_path)
|
||||
producer_configuration = cocoapods_configuration_string({'mode' => 'producer'})
|
||||
consumer_configuration = cocoapods_configuration_string()
|
||||
|
||||
puts("****** Scenario: #{template_path}")
|
||||
|
||||
# Run producer build
|
||||
pre_producer_setup
|
||||
dump_podfile(producer_configuration, template_path)
|
||||
puts('Building producer ...')
|
||||
Dir.chdir(E2E_COCOAPODS_SAMPLE_DIR) do
|
||||
build_project
|
||||
# reset XCRemoteCache stats
|
||||
system("#{XCRC_BINARIES}/xcprepare stats --reset --format json")
|
||||
end
|
||||
|
||||
# Run consumer build
|
||||
pre_consumer_setup
|
||||
dump_podfile(consumer_configuration, template_path)
|
||||
puts('Building consumer ...')
|
||||
Dir.chdir(E2E_COCOAPODS_SAMPLE_DIR) do
|
||||
build_project({'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
|
||||
valide_hit_rate
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user