Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| a7316d35cc | |||
| 62a7fea0be | |||
| 88e4dceb99 | |||
| e8db767d4a | |||
| 12c635e5ca | |||
| 7f95da7b7c | |||
| 5892a92546 | |||
| 7aa44f20c1 | |||
| 9df2bd5a8e | |||
| 508b11d6ac | |||
| 5f2a8409f2 | |||
| df627ca374 | |||
| a905cdbddc | |||
| 6995c7c1b7 | |||
| 7f43cb87bd | |||
| 5ff9888c11 | |||
| ad545c7802 | |||
| 057c5c3e28 | |||
| 4116dba33d | |||
| 46cc3b75aa | |||
| 2285822ae6 | |||
| e518d28723 | |||
| 3b453b15bc | |||
| 73edc2c7aa | |||
| a0c21471b9 | |||
| 610946f0c4 | |||
| 53d4f07286 | |||
| bc9a77a58f | |||
| e0205f749a | |||
| 0d259b56d3 | |||
| 366c485453 | |||
| 8f86917597 | |||
| f9524a6854 | |||
| 9a27fa81a4 | |||
| c55f1a5803 | |||
| e4d277c8db | |||
| 6cd662bf7d | |||
| 56081f75b3 | |||
| c1cd1ac565 | |||
| 768a296175 | |||
| 6a1a8c6919 | |||
| 3d02af8ade |
@@ -13,7 +13,7 @@ jobs:
|
||||
macOS:
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
XCODE_VERSION: ${{ '12.4' }}
|
||||
XCODE_VERSION: ${{ '13.1' }}
|
||||
steps:
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
@@ -23,3 +23,5 @@ jobs:
|
||||
run: rake build[release]
|
||||
- name: Test
|
||||
run: rake test
|
||||
- name: E2ETests
|
||||
run: rake e2e_only
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
name: Add macOS binaries to release
|
||||
runs-on: macOS-latest
|
||||
env:
|
||||
XCODE_VERSION: ${{ '12.4' }}
|
||||
XCODE_VERSION: ${{ '13.1' }}
|
||||
steps:
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
@@ -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/
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version:5.1
|
||||
// swift-tools-version:5.3
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package
|
||||
|
||||
import PackageDescription
|
||||
@@ -56,7 +56,8 @@ let package = Package(
|
||||
),
|
||||
.testTarget(
|
||||
name: "XCRemoteCacheTests",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
dependencies: ["XCRemoteCache"],
|
||||
resources: [.copy("TestData")]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<p align="center">
|
||||
<img src="docs/img/logo.png" width="75%">
|
||||
<img src="docs/img/logo.png#gh-light-mode-only" width="75%">
|
||||
<img src="docs/img/logo-dark.png#gh-dark-mode-only" width="75%">
|
||||
</p>
|
||||
|
||||
_XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artifacts generated on a remote machine, served from a simple REST server._
|
||||
@@ -37,10 +38,14 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
|
||||
* [Amazon S3 and Google Cloud Storage](#amazon-s3-and-google-cloud-storage)
|
||||
- [CocoaPods plugin](#cocoapods-plugin)
|
||||
- [Requirements](#requirements)
|
||||
- [Apple silicon support](#apple-silicon-support)
|
||||
* [Artifacts per architecture (Recommended)](#artifacts-per-architecture-recommended)
|
||||
* [Fat artifacts](#fat-artifacts)
|
||||
- [Limitations](#limitations)
|
||||
- [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)
|
||||
@@ -190,6 +195,7 @@ Configure Xcode targets that **should use** XCRemoteCache:
|
||||
* `SWIFT_EXEC` - location of `xcprepare` (e.g. `xcremotecache/xcswiftc`)
|
||||
* `LIBTOOL` - location of `xclibtool` (e.g. `xcremotecache/xclibtool`)
|
||||
* `LD` - location of `xcld` (e.g. `xcremotecache/xcld`)
|
||||
* `XCRC_PLATFORM_PREFERRED_ARCH` - `$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)`
|
||||
|
||||
<details>
|
||||
<summary>Screenshot</summary>
|
||||
@@ -209,8 +215,8 @@ Configure Xcode targets that **should use** XCRemoteCache:
|
||||
* command: `"$SCRIPT_INPUT_FILE_0"`
|
||||
* input files: location of `xcpostbuild` command (e.g. `xcremotecache/xcpostbuild`)
|
||||
* output files:
|
||||
* `$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH).swiftmodule.md5`
|
||||
* `$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5`
|
||||
* `$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH).swiftmodule.md5`
|
||||
* `$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5`
|
||||
* discovery dependency file: `$(TARGET_TEMP_DIR)/postbuild.d`
|
||||
|
||||
<details>
|
||||
@@ -299,11 +305,13 @@ _Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`,
|
||||
| `product_files_extensions_with_content_override ` | List of all extensions that should carry over source fingerprints. Extensions of all product files that contain non-deterministic content (absolute paths, timestamp, etc) should be included. | `["swiftmodule"]` | ⬜️ |
|
||||
| `thinning_enabled ` | If true, support for thin projects is enabled | `false` | ⬜️ |
|
||||
| `thinning_target_module_name ` | Module name of a target that works as a helper for thinned targets | `"ThinningRemoteCacheModule"` | ⬜️ |
|
||||
| `prettify_meta_files` | A Boolean value that opts-in pretty JSON formatting for meta files | `false` | ⬜️ |
|
||||
| `aws_secret_key` | Secret key for AWS V4 Signature Authorization. If this is set to a non-empty String - an Authentication Header will be added based on this and the other `aws_*` parameters.| `""` | ⬜️ |
|
||||
| `aws_access_key` | Access key for AWS V4 Signature Authorization. | `""` | ⬜️ |
|
||||
| `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` | ⬜️ |
|
||||
|
||||
## Backend cache server
|
||||
|
||||
@@ -359,6 +367,31 @@ Retention Policy: Buckets usually have a retention policy option which ensures o
|
||||
|
||||
Head over to our [cocoapods-plugin](cocoapods-plugin/README.md) docs to see how to integrate XCRemoteCache in your CocoaPods project.
|
||||
|
||||
## Apple silicon support
|
||||
|
||||
### Artifacts per architecture (Recommended)
|
||||
|
||||
_If all of your machines (both producer and all consumers have the same architecture, either Intel or Apple Silicon), you don't have to do anything._
|
||||
|
||||
XCRemoteCache supports building artifacts for Apple silicon consumers. Is it recommended to build separately for `x86_64` and `arm64` architectures to have single-architecture artifacts that do not require downloading irrelevant binaries. Here are required steps if you want to support both Intel and Apple silicon consumers.
|
||||
|
||||
* Building for a simulator on a producer: run a first build for `x86_64`, clean a build and build again for `arm64`, e.g.:
|
||||
```
|
||||
xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO build ...
|
||||
xcodebuild clean
|
||||
xcodebuild ARCHS=arm64 ONLY_ACTIVE_ARCH=NO build ...
|
||||
```
|
||||
|
||||
### Fat artifacts
|
||||
|
||||
If you prefer to generate far artifacts (with both Intel and Apple silicon binaries), you can disable "Build Archive Architecture Only" on a producer side, e.g.
|
||||
|
||||
```
|
||||
xcodebuild ONLY_ACTIVE_ARCH=NO build ...
|
||||
```
|
||||
|
||||
Note: This setup is not recommended and may not be supported in future XCRemoteCache releases.
|
||||
|
||||
## Requirements
|
||||
|
||||
* The repo under `git` version control
|
||||
@@ -389,6 +422,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,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ enum MachOType: String, Codable {
|
||||
enum PostbuildContextError: Error {
|
||||
/// URL address is not a valid URL
|
||||
case invalidAddress(String)
|
||||
/// ARCHS env does not contain any architecture to build
|
||||
case missingArchitecture
|
||||
}
|
||||
|
||||
public struct PostbuildContext {
|
||||
@@ -64,6 +66,9 @@ public struct PostbuildContext {
|
||||
var machOType: MachOType
|
||||
var wasDsymGenerated: Bool
|
||||
var dSYMPath: URL
|
||||
// building architecture. Used to find all dependencies from *.d files
|
||||
// Warning: if two architectures are built (e.g. for disabled "Build Archive
|
||||
// Architecture Only"), a first architecture one is picked
|
||||
let arch: String
|
||||
let builtProductsDir: URL
|
||||
/// Location to the product bundle. Can be nil for libraries
|
||||
@@ -74,6 +79,8 @@ public struct PostbuildContext {
|
||||
/// 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 {
|
||||
@@ -82,8 +89,13 @@ extension PostbuildContext {
|
||||
let targetNameValue: String = try env.readEnv(key: "TARGET_NAME")
|
||||
targetName = targetNameValue
|
||||
targetTempDir = try env.readEnv(key: "TARGET_TEMP_DIR")
|
||||
arch = try env.readEnv(key: "PLATFORM_PREFERRED_ARCH")
|
||||
compilationTempDir = try env.readEnv(key: "OBJECT_FILE_DIR_normal").appendingPathComponent(arch)
|
||||
let archs: [String] = try env.readEnv(key: "ARCHS").split(separator: " ").map(String.init)
|
||||
guard let firstArch = archs.first, !firstArch.isEmpty else {
|
||||
throw PostbuildContextError.missingArchitecture
|
||||
}
|
||||
arch = firstArch
|
||||
let variant: String = try env.readEnv(key: "CURRENT_VARIANT")
|
||||
compilationTempDir = try env.readEnv(key: "OBJECT_FILE_DIR_\(variant)").appendingPathComponent(arch)
|
||||
configuration = try env.readEnv(key: "CONFIGURATION")
|
||||
platform = try env.readEnv(key: "PLATFORM_NAME")
|
||||
xcodeBuildNumber = try env.readEnv(key: "XCODE_PRODUCT_BUILD_VERSION")
|
||||
@@ -117,5 +129,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,9 +66,10 @@ 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 StringDependenciesRemapper.buildFromEnvs(
|
||||
keys: DependenciesMapping.rewrittenEnvs,
|
||||
envs: env
|
||||
let envsRemapper = try StringDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs,
|
||||
envs: env,
|
||||
customMappings: config.outOfBandMappings
|
||||
)
|
||||
let envFingerprint = try EnvironmentFingerprintGenerator(
|
||||
configuration: config,
|
||||
@@ -85,7 +86,7 @@ public class XCPostbuild {
|
||||
algorithm: MD5Algorithm()
|
||||
)
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
|
||||
let metaWriter = JsonMetaWriter(fileWriter: fileManager)
|
||||
let metaWriter = JsonMetaWriter(fileWriter: fileManager, pretty: config.prettifyMetaFiles)
|
||||
let artifactCreator = BuildArtifactCreator(
|
||||
buildDir: context.productsDir,
|
||||
tempDir: context.targetTempDir,
|
||||
@@ -144,6 +145,18 @@ public class XCPostbuild {
|
||||
fileDependeciesReaderFactory: fileReaderFactory,
|
||||
dirScanner: fileManager
|
||||
)
|
||||
// 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 = try OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
let pathRemapper = DependenciesRemapperComposite([overlayRemapper, envsRemapper])
|
||||
let dependencyProcessor = DependencyProcessorImpl(
|
||||
xcode: context.xcodeDir,
|
||||
product: context.productsDir,
|
||||
|
||||
@@ -43,6 +43,8 @@ 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 +66,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,10 +115,22 @@ public class XCPrebuild {
|
||||
)
|
||||
let client: NetworkClient = config.disableHttpCache ? networkClient : cacheNetworkClient
|
||||
let remoteNetworkClient = RemoteNetworkClientImpl(client, urlBuilder)
|
||||
let pathRemapper = try StringDependenciesRemapper.buildFromEnvs(
|
||||
keys: DependenciesMapping.rewrittenEnvs,
|
||||
envs: env
|
||||
let envsRemapper = try StringDependenciesRemapperFactory().build(
|
||||
orderKeys: DependenciesMapping.rewrittenEnvs,
|
||||
envs: env,
|
||||
customMappings: config.outOfBandMappings
|
||||
)
|
||||
// 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 = try OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
let pathRemapper = DependenciesRemapperComposite([overlayRemapper, envsRemapper])
|
||||
let filesFingerprintGenerator = FingerprintAccumulatorImpl(
|
||||
algorithm: MD5Algorithm(),
|
||||
fileManager: fileManager
|
||||
|
||||
@@ -72,7 +72,7 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
)
|
||||
infoLog("ClangWrapperBuilder compiles file at \(compilationFile).")
|
||||
// -O3: optimize for faster execution
|
||||
let args = [clangCommand, "-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)")
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
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)"
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +100,11 @@ struct XcodeProjIntegrate: Integrate {
|
||||
outputPaths: [
|
||||
"""
|
||||
$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/\
|
||||
$(PLATFORM_PREFERRED_ARCH).swiftmodule.md5
|
||||
$(XCRC_PLATFORM_PREFERRED_ARCH).swiftmodule.md5
|
||||
""",
|
||||
"""
|
||||
$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/\
|
||||
$(PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)\
|
||||
$(XCRC_PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)\
|
||||
$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5
|
||||
""",
|
||||
],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -112,6 +112,8 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
var thinningEnabled: Bool = false
|
||||
/// Module name of a target that works as a helper for thinned targets
|
||||
var thinningTargetModuleName: String = "ThinningRemoteCacheModule"
|
||||
/// Opt-in pretty json formatting for meta files
|
||||
var prettifyMetaFiles: Bool = false
|
||||
/// Secret key for AWS V4 Signature, if this is set the Authentication Header will be added
|
||||
var AWSSecretKey: String = ""
|
||||
/// Access key for AWS V4 Signature
|
||||
@@ -120,6 +122,15 @@ 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.
|
||||
/// 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.
|
||||
var outOfBandMappings: [String: String] = [:]
|
||||
/// If true, SSL certificate validation is disabled
|
||||
var disableCertificateVerification: Bool = false
|
||||
}
|
||||
|
||||
extension XCRemoteCacheConfig {
|
||||
@@ -165,10 +176,13 @@ extension XCRemoteCacheConfig {
|
||||
scheme.productFilesExtensionsWithContentOverride ?? productFilesExtensionsWithContentOverride
|
||||
merge.thinningEnabled = scheme.thinningEnabled ?? thinningEnabled
|
||||
merge.thinningTargetModuleName = scheme.thinningTargetModuleName ?? thinningTargetModuleName
|
||||
merge.prettifyMetaFiles = scheme.prettifyMetaFiles ?? prettifyMetaFiles
|
||||
merge.AWSAccessKey = scheme.AWSAccessKey ?? AWSAccessKey
|
||||
merge.AWSSecretKey = scheme.AWSSecretKey ?? AWSSecretKey
|
||||
merge.AWSRegion = scheme.AWSRegion ?? AWSRegion
|
||||
merge.AWSService = scheme.AWSService ?? AWSService
|
||||
merge.outOfBandMappings = scheme.outOfBandMappings ?? outOfBandMappings
|
||||
merge.disableCertificateVerification = scheme.disableCertificateVerification ?? disableCertificateVerification
|
||||
return merge
|
||||
}
|
||||
|
||||
@@ -223,10 +237,13 @@ struct ConfigFileScheme: Decodable {
|
||||
let productFilesExtensionsWithContentOverride: [String]?
|
||||
let thinningEnabled: Bool?
|
||||
let thinningTargetModuleName: String?
|
||||
let prettifyMetaFiles: Bool?
|
||||
let AWSSecretKey: String?
|
||||
let AWSAccessKey: String?
|
||||
let AWSRegion: String?
|
||||
let AWSService: String?
|
||||
let outOfBandMappings: [String: String]?
|
||||
let disableCertificateVerification: Bool?
|
||||
|
||||
// Yams library doesn't support encoding strategy, see https://github.com/jpsim/Yams/issues/84
|
||||
enum CodingKeys: String, CodingKey {
|
||||
@@ -264,10 +281,13 @@ struct ConfigFileScheme: Decodable {
|
||||
case productFilesExtensionsWithContentOverride = "product_files_extensions_with_content_override"
|
||||
case thinningEnabled = "thinning_enabled"
|
||||
case thinningTargetModuleName = "thinning_target_module_name"
|
||||
case prettifyMetaFiles = "prettify_meta_files"
|
||||
case AWSSecretKey = "aws_secret_key"
|
||||
case AWSAccessKey = "aws_access_key"
|
||||
case AWSRegion = "aws_region"
|
||||
case AWSService = "aws_service"
|
||||
case outOfBandMappings = "out_of_band_mappings"
|
||||
case disableCertificateVerification = "disable_certificate_verification"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class DependenciesRemapperComposite: DependenciesRemapper {
|
||||
}
|
||||
|
||||
func replace(genericPaths: [String]) -> [String] {
|
||||
remappers.reduce(genericPaths) { prev, mapper in
|
||||
remappers.reversed().reduce(genericPaths) { prev, mapper in
|
||||
mapper.replace(genericPaths: prev)
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ final class StringDependenciesRemapper: DependenciesRemapper {
|
||||
|
||||
func replace(genericPaths: [String]) -> [String] {
|
||||
return genericPaths.map { path in
|
||||
let localPath = mappings.reduce(path) { prevPath, mapping in
|
||||
let localPath = mappings.reversed().reduce(path) { prevPath, mapping in
|
||||
prevPath.replacingOccurrences(of: mapping.generic, with: mapping.local)
|
||||
}
|
||||
return localPath
|
||||
@@ -77,14 +77,3 @@ final class StringDependenciesRemapper: DependenciesRemapper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension StringDependenciesRemapper {
|
||||
static func buildFromEnvs(keys: [String], envs: [String: String]) throws -> Self {
|
||||
let mappings: [Mapping] = try keys.map { key in
|
||||
let localValue: String = try envs.readEnv(key: key)
|
||||
return Mapping(generic: "$(\(key))", local: localValue)
|
||||
}
|
||||
return Self(mappings: mappings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,55 @@
|
||||
// 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 var mappings: [OverlayMapping]
|
||||
|
||||
init(overlayReader: OverlayReader) throws {
|
||||
mappings = try overlayReader.provideMappings()
|
||||
}
|
||||
|
||||
private func mapPath(
|
||||
_ path: String,
|
||||
source: KeyPath<OverlayMapping,URL>,
|
||||
destination: KeyPath<OverlayMapping,URL>
|
||||
) -> String {
|
||||
guard let mapping = mappings.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]) -> [String] {
|
||||
Set(genericPaths.map {
|
||||
mapPath($0, source: \.virtual, destination: \.local)
|
||||
}).sorted()
|
||||
}
|
||||
|
||||
func replace(localPaths: [String]) -> [String] {
|
||||
Set(localPaths.map {
|
||||
mapPath($0, source: \.local, destination: \.virtual)
|
||||
}).sorted()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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:
|
||||
printWarning("overlay mapping file \(json) doesn't exist. Skipping overlay for the best-effort mode.")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 Foundation
|
||||
|
||||
enum StringDependenciesRemapperFactoryError: Error {
|
||||
/// Remapping keys are duplicated and can lead to undetermined results
|
||||
case mappingKeyDuplication
|
||||
}
|
||||
|
||||
class StringDependenciesRemapperFactory {
|
||||
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 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)
|
||||
}
|
||||
return StringDependenciesRemapper(mappings: mappings)
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,10 @@
|
||||
|
||||
/// Generates a fingerprint string of the environment (compilation context)
|
||||
class EnvironmentFingerprintGenerator {
|
||||
/// Default ENV variables constituing the environment fingerprint
|
||||
/// Default ENV variables constituting the environment fingerprint
|
||||
private static let defaultEnvFingerprintKeys = [
|
||||
"GCC_PREPROCESSOR_DEFINITIONS",
|
||||
"CLANG_PROFILE_DATA_DIRECTORY",
|
||||
"CLANG_COVERAGE_MAPPING",
|
||||
"TARGET_NAME",
|
||||
"CONFIGURATION",
|
||||
"PLATFORM_NAME",
|
||||
@@ -31,6 +31,7 @@ class EnvironmentFingerprintGenerator {
|
||||
"DYLIB_COMPATIBILITY_VERSION",
|
||||
"DYLIB_CURRENT_VERSION",
|
||||
"PRODUCT_MODULE_NAME",
|
||||
"ARCHS"
|
||||
]
|
||||
private let version: String
|
||||
private let customFingerprintEnvs: [String]
|
||||
|
||||
@@ -24,11 +24,16 @@ protocol MetaWriter {
|
||||
}
|
||||
|
||||
class JsonMetaWriter: MetaWriter {
|
||||
private let metaEncoder = JSONEncoder()
|
||||
private let metaEncoder: JSONEncoder
|
||||
private let fileWriter: FileWriter
|
||||
|
||||
init(fileWriter: FileWriter) {
|
||||
init(fileWriter: FileWriter, pretty: Bool) {
|
||||
self.fileWriter = fileWriter
|
||||
let encoder = JSONEncoder()
|
||||
if pretty {
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
}
|
||||
self.metaEncoder = encoder
|
||||
}
|
||||
|
||||
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta {
|
||||
|
||||
@@ -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,15 @@ class DefaultURLSessionFactory: URLSessionFactory {
|
||||
let configuration = URLSessionConfiguration.default
|
||||
configuration.httpAdditionalHeaders = config.requestCustomHeaders
|
||||
configuration.timeoutIntervalForRequest = config.timeoutResponseDataChunksInterval
|
||||
return URLSession(configuration: configuration)
|
||||
switch config.disableCertificateVerification {
|
||||
case true:
|
||||
return URLSession(
|
||||
configuration: configuration,
|
||||
delegate: IgnoringCertificatesTrustManager(),
|
||||
delegateQueue: nil
|
||||
)
|
||||
case false:
|
||||
return URLSession(configuration: configuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
@@ -63,7 +66,7 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
moduleName: "Target",
|
||||
modulesFolderPath: "",
|
||||
dSYMPath: dSYM,
|
||||
metaWriter: JsonMetaWriter(fileWriter: fileManager),
|
||||
metaWriter: JsonMetaWriter(fileWriter: fileManager, pretty: false),
|
||||
fileManager: fileManager
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -37,7 +37,7 @@ class ZipArtifactCreatorTests: FileXCTestCase {
|
||||
workingDir = try prepareTempDir().appendingPathComponent("creator")
|
||||
creator = ZipArtifactCreator(
|
||||
workingDir: workingDir,
|
||||
metaWriter: JsonMetaWriter(fileWriter: fileManager),
|
||||
metaWriter: JsonMetaWriter(fileWriter: fileManager, pretty: false),
|
||||
fileManager: fileManager
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
private static let SampleEnvs = [
|
||||
"TARGET_NAME": "TARGET_NAME",
|
||||
"TARGET_TEMP_DIR": "TARGET_TEMP_DIR",
|
||||
"PLATFORM_PREFERRED_ARCH": "PLATFORM_PREFERRED_ARCH",
|
||||
"OBJECT_FILE_DIR_normal": "OBJECT_FILE_DIR_normal" ,
|
||||
"ARCHS": "x86_64",
|
||||
"OBJECT_FILE_DIR_normal": "/OBJECT_FILE_DIR_normal" ,
|
||||
"CONFIGURATION": "CONFIGURATION",
|
||||
"PLATFORM_NAME": "PLATFORM_NAME",
|
||||
"XCODE_PRODUCT_BUILD_VERSION": "XCODE_PRODUCT_BUILD_VERSION",
|
||||
@@ -42,6 +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"
|
||||
]
|
||||
|
||||
override func setUpWithError() throws {
|
||||
@@ -87,4 +88,45 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
|
||||
XCTAssertEqual(context.action, .index)
|
||||
}
|
||||
|
||||
func testReadsSingleArch() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["ARCHS"] = "x86_64"
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertEqual(context.arch, "x86_64")
|
||||
}
|
||||
|
||||
func testReadsFirstArch() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["ARCHS"] = "x86_64 arm64"
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertEqual(context.arch, "x86_64")
|
||||
}
|
||||
|
||||
func testFailsForEmptyArch() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["ARCHS"] = ""
|
||||
|
||||
XCTAssertThrowsError(try PostbuildContext(config, env: envs))
|
||||
}
|
||||
|
||||
func testFailsForMissingArch() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["ARCHS"] = nil
|
||||
|
||||
XCTAssertThrowsError(try PostbuildContext(config, env: envs))
|
||||
}
|
||||
|
||||
func testFindTempDirForCustomVariant() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["ARCHS"] = "x86_64"
|
||||
envs["CURRENT_VARIANT"] = "custom"
|
||||
envs["OBJECT_FILE_DIR_custom"] = "/OBJECT_FILE_DIR_custom"
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertEqual(context.compilationTempDir, "/OBJECT_FILE_DIR_custom/x86_64")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ class PostbuildTests: FileXCTestCase {
|
||||
derivedSourcesDir: "",
|
||||
thinnedTargets: [],
|
||||
action: .build,
|
||||
modeMarkerPath: ""
|
||||
modeMarkerPath: "",
|
||||
overlayHeadersPath: ""
|
||||
)
|
||||
private var network = RemoteNetworkClientImpl(
|
||||
NetworkClientFake(fileManager: .default),
|
||||
@@ -85,7 +86,7 @@ class PostbuildTests: FileXCTestCase {
|
||||
)
|
||||
private var modeController = CacheModeControllerFake()
|
||||
private var metaReader = JsonMetaReader(fileAccessor: FileManager.default)
|
||||
private var metaWriter = JsonMetaWriter(fileWriter: FileManager.default)
|
||||
private var metaWriter = JsonMetaWriter(fileWriter: FileManager.default, pretty: false)
|
||||
private static let SampleMeta = MainArtifactSampleMeta.defaults
|
||||
private var sampleMetaFile: URL!
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class TemplateBasedCCWrapperBuilderTests: FileXCTestCase {
|
||||
private static let history = "history.compile"
|
||||
private static let prebuild = "prebuild.d"
|
||||
private static let commitSha = "321"
|
||||
private static let timeout = 5.0
|
||||
private static let timeout = 10.0
|
||||
|
||||
static let xccc: URL = {
|
||||
let fileManager = FileManager.default
|
||||
|
||||
@@ -78,4 +78,41 @@ class DependenciesRemapperCompositeTests: XCTestCase {
|
||||
|
||||
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")])
|
||||
])
|
||||
let localPaths = ["/root/specific/file"]
|
||||
|
||||
let genericPaths = 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")])
|
||||
])
|
||||
let genericPaths = ["$(SPECIFIC)/file"]
|
||||
|
||||
let localPaths = 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")])
|
||||
])
|
||||
let localPaths = ["/root/specific/file"]
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: localPaths)
|
||||
let remappedLocalPaths = 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,66 @@ 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,65 @@
|
||||
// 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 reader = try OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
|
||||
let dependencies = reader.replace(localPaths: ["/Intermediate/Some/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/file.h"])
|
||||
}
|
||||
|
||||
func testMappingFromGenericToLocal() throws {
|
||||
let reader = try OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
|
||||
let dependencies = reader.replace(genericPaths: ["/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
|
||||
}
|
||||
|
||||
func testGenericDependenciesAreMerged() throws {
|
||||
|
||||
let reader = try OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
|
||||
let dependencies = reader.replace(localPaths: ["/Intermediate/Some/file.h", "/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/file.h"])
|
||||
}
|
||||
|
||||
func testLocalDependenciesAreMerged() throws {
|
||||
let reader = try OverlayDependenciesRemapper(
|
||||
overlayReader: overlayReader
|
||||
)
|
||||
|
||||
let dependencies = reader.replace(genericPaths: ["/Intermediate/Some/file.h", "/file.h"])
|
||||
XCTAssertEqual(dependencies, ["/Intermediate/Some/file.h"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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: XCTestCase {
|
||||
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, [])
|
||||
}
|
||||
|
||||
private func pathForTestData(name: String) throws -> URL {
|
||||
return try XCTUnwrap(Bundle.module.url(forResource: name, withExtension: "json", subdirectory: JsonOverlayReaderTests.resourcesSubdirectory))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 StringDependenciesRemapperFactoryTests: XCTestCase {
|
||||
private var factory: StringDependenciesRemapperFactory!
|
||||
|
||||
override func setUp() {
|
||||
factory = StringDependenciesRemapperFactory()
|
||||
}
|
||||
|
||||
func testMappingsFromEnvMaps() throws {
|
||||
let remapper = try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["SRC_ROOT": "/tmp/root"],
|
||||
customMappings: [:]
|
||||
)
|
||||
|
||||
let localPaths = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testInvalidMappingsFromEnvFails() throws {
|
||||
XCTAssertThrowsError(
|
||||
try factory.build(
|
||||
orderKeys: ["SRC_ROOT"],
|
||||
envs: ["NO_SRC_ROOT": ""],
|
||||
customMappings: [:]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func testBuildingRemapperWithMergedCustomMappings() throws {
|
||||
let remapper = try factory.build(
|
||||
orderKeys: ["PWD"],
|
||||
envs: ["PWD": "/some"],
|
||||
customMappings: ["TMP": "/tmp"]
|
||||
)
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: ["/some/repoFile.swift", "/tmp/externalFile.swift"])
|
||||
XCTAssertEqual(genericPaths, ["$(PWD)/repoFile.swift", "$(TMP)/externalFile.swift"])
|
||||
}
|
||||
|
||||
func testFailsBuildingRemapperWithConflictedMappings() throws {
|
||||
XCTAssertThrowsError(
|
||||
try factory.build(
|
||||
orderKeys: ["PWD"],
|
||||
envs: ["PWD": "/some"],
|
||||
customMappings: ["PWD": "/other"]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -70,17 +70,29 @@ class StringDependenciesRemapperTests: XCTestCase {
|
||||
XCTAssertEqual(genericPaths, ["$(SRC_ROOT)/some.swift", "$(PWD)/extra.swift"])
|
||||
}
|
||||
|
||||
func testMappingsFromEnvMaps() throws {
|
||||
remapper = try StringDependenciesRemapper.buildFromEnvs(keys: ["SRC_ROOT"], envs: ["SRC_ROOT": "/tmp/root"])
|
||||
func testMappingsLocalPathsIsDoneInOrder() {
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = [
|
||||
.init(generic: "$(TMP)", local: "/tmp"),
|
||||
.init(generic: "$(ROOT)", local: "$(TMP)/root"),
|
||||
]
|
||||
remapper = StringDependenciesRemapper(mappings: mappings)
|
||||
|
||||
let localPaths = remapper.replace(genericPaths: ["$(SRC_ROOT)/some.swift"])
|
||||
|
||||
let genericPaths = remapper.replace(localPaths: ["/tmp/root/some.swift"])
|
||||
|
||||
XCTAssertEqual(genericPaths, ["$(ROOT)/some.swift"])
|
||||
}
|
||||
|
||||
func testMappingsGenericPathsIsDoneInReversedOrder() {
|
||||
let mappings: [StringDependenciesRemapper.Mapping] = [
|
||||
.init(generic: "$(TMP)", local: "/tmp"),
|
||||
.init(generic: "$(ROOT)", local: "$(TMP)/root"),
|
||||
]
|
||||
remapper = StringDependenciesRemapper(mappings: mappings)
|
||||
|
||||
|
||||
let localPaths = remapper.replace(genericPaths: ["$(ROOT)/some.swift"])
|
||||
|
||||
XCTAssertEqual(localPaths, ["/tmp/root/some.swift"])
|
||||
}
|
||||
|
||||
func testInvalidMappingsFromEnvFAils() throws {
|
||||
XCTAssertThrowsError(
|
||||
try StringDependenciesRemapper.buildFromEnvs(keys: ["SRC_ROOT"], envs: ["NO_SRC_ROOT": ""])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
|
||||
private static let defaultENV = [
|
||||
"GCC_PREPROCESSOR_DEFINITIONS": "GCC",
|
||||
"CLANG_PROFILE_DATA_DIRECTORY": "CLANG",
|
||||
"CLANG_COVERAGE_MAPPING": "YES",
|
||||
"TARGET_NAME": "TARGET",
|
||||
"CONFIGURATION": "CONG",
|
||||
"PLATFORM_NAME": "PLAT",
|
||||
@@ -33,6 +33,7 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
"DYLIB_COMPATIBILITY_VERSION": "2",
|
||||
"DYLIB_CURRENT_VERSION": "3",
|
||||
"PRODUCT_MODULE_NAME": "4",
|
||||
"ARCHS": "AR"
|
||||
]
|
||||
/// Corresponds to EnvironmentFingerprintGenerator.version
|
||||
private static let currentVersion = "5"
|
||||
@@ -55,7 +56,7 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
func testConsidersDefaultEnvs() throws {
|
||||
let fingerprint = try fingerprintGenerator.generateFingerprint()
|
||||
|
||||
XCTAssertEqual(fingerprint, "GCC,CLANG,TARGET,CONG,PLAT,XC,1,2,3,4,\(Self.currentVersion)")
|
||||
XCTAssertEqual(fingerprint, "GCC,YES,TARGET,CONG,PLAT,XC,1,2,3,4,AR,\(Self.currentVersion)")
|
||||
}
|
||||
|
||||
func testFingerprintIncludesVersionAsLastComponent() throws {
|
||||
@@ -73,7 +74,7 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
|
||||
let fingerprint = try fingerprintGenerator.generateFingerprint()
|
||||
|
||||
XCTAssertEqual(fingerprint, ",,,,,,,,,,\(Self.currentVersion)")
|
||||
XCTAssertEqual(fingerprint, ",,,,,,,,,,,\(Self.currentVersion)")
|
||||
}
|
||||
|
||||
func testConsidersCustomEnvs() throws {
|
||||
@@ -89,6 +90,6 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
|
||||
|
||||
let fingerprint = try fingerprintGenerator.generateFingerprint()
|
||||
|
||||
XCTAssertEqual(fingerprint, "GCC,CLANG,TARGET,CONG,PLAT,XC,1,2,3,4,CUSTOM_VALUE,\(Self.currentVersion)")
|
||||
XCTAssertEqual(fingerprint, "GCC,YES,TARGET,CONG,PLAT,XC,1,2,3,4,AR,CUSTOM_VALUE,\(Self.currentVersion)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class JsonMetaWriterTests: XCTestCase {
|
||||
|
||||
func testWritesToFileWithFilekeyFilename() throws {
|
||||
let fileAccessor = FileAccessorFake(mode: .normal)
|
||||
let writer = JsonMetaWriter(fileWriter: fileAccessor)
|
||||
let writer = JsonMetaWriter(fileWriter: fileAccessor, pretty: false)
|
||||
let workingDir: URL = "/"
|
||||
let meta = MainArtifactSampleMeta.defaults
|
||||
|
||||
@@ -35,7 +35,20 @@ class JsonMetaWriterTests: XCTestCase {
|
||||
|
||||
func testWritesMetaInValidFormat() throws {
|
||||
let fileAccessor = FileAccessorFake(mode: .normal)
|
||||
let writer = JsonMetaWriter(fileWriter: fileAccessor)
|
||||
let writer = JsonMetaWriter(fileWriter: fileAccessor, pretty: false)
|
||||
let reader = JsonMetaReader(fileAccessor: fileAccessor)
|
||||
let workingDir: URL = "/"
|
||||
let meta = MainArtifactSampleMeta.defaults
|
||||
|
||||
let url = try writer.write(MainArtifactSampleMeta.defaults, locationDir: workingDir)
|
||||
|
||||
let readMeta = try reader.read(localFile: url)
|
||||
XCTAssertEqual(readMeta, meta)
|
||||
}
|
||||
|
||||
func testWritesPrettyMetaInValidFormat() throws {
|
||||
let fileAccessor = FileAccessorFake(mode: .normal)
|
||||
let writer = JsonMetaWriter(fileWriter: fileAccessor, pretty: true)
|
||||
let reader = JsonMetaReader(fileAccessor: fileAccessor)
|
||||
let workingDir: URL = "/"
|
||||
let meta = MainArtifactSampleMeta.defaults
|
||||
|
||||
+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,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
|
||||
@testable import XCRemoteCache
|
||||
|
||||
class OverlayReaderFake: OverlayReader {
|
||||
private let mappings: [OverlayMapping]
|
||||
init(mappings: [OverlayMapping]) {
|
||||
self.mappings = mappings
|
||||
}
|
||||
|
||||
func provideMappings() throws -> [OverlayMapping] {
|
||||
return mappings
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ An object that is passed to the `xcremotecache` can contain all properties suppo
|
||||
| `modify_lldb_init` | Controls if the pod integration should modify `~/.lldbinit` | `true` | ⬜️ |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ require 'cocoapods/resolver'
|
||||
require 'open-uri'
|
||||
require 'yaml'
|
||||
require 'json'
|
||||
require 'pathname'
|
||||
|
||||
|
||||
module CocoapodsXCRemoteCacheModifier
|
||||
@@ -28,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',
|
||||
@@ -36,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
|
||||
@@ -58,6 +62,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
'xccc_file' => "#{BIN_DIR}/xccc",
|
||||
'remote_commit_file' => "#{BIN_DIR}/arc.rc",
|
||||
'exclude_targets' => [],
|
||||
'prettify_meta_files' => false,
|
||||
'disable_certificate_verification' => false
|
||||
}
|
||||
@@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
|
||||
end
|
||||
@@ -76,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')
|
||||
@@ -97,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)
|
||||
@@ -108,12 +114,15 @@ 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"]
|
||||
config.build_settings['LD'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcld"]
|
||||
|
||||
config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = FAKE_SRCROOT
|
||||
config.build_settings['XCRC_PLATFORM_PREFERRED_ARCH'] = ["$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)"]
|
||||
debug_prefix_map_replacement = '$(SRCROOT' + ':dir:standardizepath' * repo_distance + ')'
|
||||
add_cflags!(config.build_settings, '-fdebug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)")
|
||||
add_swiftflags!(config.build_settings, '-debug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)")
|
||||
@@ -137,7 +146,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
|
||||
# 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'
|
||||
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)
|
||||
@@ -156,13 +165,13 @@ module CocoapodsXCRemoteCacheModifier
|
||||
postbuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
|
||||
postbuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcpostbuild"]
|
||||
postbuild_script.output_paths = [
|
||||
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH).swiftmodule.md5",
|
||||
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5"
|
||||
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH).swiftmodule.md5",
|
||||
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5"
|
||||
]
|
||||
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")
|
||||
@@ -187,8 +196,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
|
||||
@@ -250,14 +260,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
|
||||
|
||||
@@ -272,12 +282,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
|
||||
@@ -334,7 +359,6 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
validate_configuration()
|
||||
|
||||
mode = @@configuration['mode']
|
||||
xccc_location = @@configuration['xccc_file']
|
||||
remote_commit_file = @@configuration['remote_commit_file']
|
||||
@@ -353,7 +377,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
download_xcrc_if_needed(xcrc_location_absolute)
|
||||
|
||||
# Save .rcinfo
|
||||
save_rcinfo(generate_rcinfo(), user_proj_directory)
|
||||
root_rcinfo = generate_rcinfo()
|
||||
save_rcinfo(root_rcinfo, user_proj_directory)
|
||||
|
||||
# Create directory for xccc & arc.rc location
|
||||
Dir.mkdir(BIN_DIR) unless File.exist?(BIN_DIR)
|
||||
@@ -368,7 +393,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)
|
||||
@@ -378,8 +405,30 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
|
||||
pods_proj_directory = installer_context.sandbox_root
|
||||
|
||||
# Manual .rcinfo generation (in YAML format)
|
||||
save_rcinfo({'extra_configuration_file' => "#{user_proj_directory}/.rcinfo"}, pods_proj_directory)
|
||||
# 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
|
||||
pods_path = Pathname.new(pods_proj_directory)
|
||||
root_path = Pathname.new(user_proj_directory)
|
||||
root_path_to_pods = root_path.relative_path_from(pods_path)
|
||||
|
||||
pods_rcinfo = root_rcinfo.merge({
|
||||
'remote_commit_file' => "#{root_path_to_pods}/#{remote_commit_file}",
|
||||
'xccc_file' => "#{root_path_to_pods}/#{xccc_location}"
|
||||
})
|
||||
save_rcinfo(pods_rcinfo, pods_proj_directory)
|
||||
|
||||
installer_context.pods_project.save()
|
||||
end
|
||||
@@ -389,12 +438,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
|
||||
@@ -419,7 +468,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.3"
|
||||
VERSION = "0.0.6"
|
||||
end
|
||||
|
||||
+14
-1
@@ -25,7 +25,18 @@ The generated Xcode project contains schemes for each output application (like `
|
||||
|
||||
#### 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
|
||||
|
||||
@@ -71,3 +82,5 @@ To enable thinning target on the consumer side:
|
||||
* Prefer using fakes instead of spies or mocks. Place testing doubles in [Tests/XCRemoteCacheTests/TestDoubles](../Tests/XCRemoteCacheTests/TestDoubles) so other tests can reuse them
|
||||
|
||||
* If you test a scenario that accesses a file on a disk, consider using the `DiskUsageSizeProviderTests` class that isolates a working directory and eliminates potential file leaks between testcases
|
||||
|
||||
* For dependency injection arguments, avoid passing default values (e.g. `init(dep: SomeDependency = SomeDependency())` and require passing explicit values (e.g. `init(dep: SomeDependency))`. It will be clear from a call site which dependencies is used and suggests adding a testcase when a new dependency is added.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 56 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,41 @@
|
||||
// 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,44 @@
|
||||
// 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,30 @@
|
||||
// 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