Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dfb4039404 | |||
| b28613a2ef | |||
| 1535b762bc | |||
| e6816846c3 | |||
| b89d98f411 | |||
| a0d3d1b0b9 | |||
| f432917505 | |||
| 5d297a4fb2 | |||
| b0d5f1660e | |||
| baea2de79a | |||
| d2803f4ad5 | |||
| ab017367b2 | |||
| 30cb648641 | |||
| 3330ca45f8 | |||
| d3193b15a8 | |||
| 7b14f2c9ff | |||
| 4933b454a5 | |||
| 79ffdce295 | |||
| 376e6a17c5 | |||
| a4d1849821 | |||
| 325fb07080 | |||
| a2afe62751 | |||
| bbaa374e12 | |||
| 39a259ff49 | |||
| a2b9cbf332 | |||
| 5710594bc4 | |||
| 7d123792b8 | |||
| 5856dbec77 | |||
| 600310f44b | |||
| aae2b3289c | |||
| 11eabdab3d | |||
| de24e609ef | |||
| 850983cbde | |||
| 0064335cc7 | |||
| 1ddadcb361 | |||
| 2f10c6a3a0 | |||
| c7cd649aab | |||
| 8201f7778b | |||
| 504393c3e3 | |||
| 82334dda04 | |||
| 1072979479 | |||
| d741b3f6df | |||
| a0f20b4da3 | |||
| f325b74796 | |||
| a50eae615c | |||
| 3c8f062e95 | |||
| 1cf685e197 | |||
| d2ba874079 | |||
| b439674378 | |||
| de066f2b1c | |||
| 73d7a13246 | |||
| 75fdd27a5f | |||
| 2fa1f4e927 | |||
| 0a64893489 | |||
| 56850cf2b0 | |||
| cef92d2f0b | |||
| b1507b6e60 | |||
| c76c8a7672 | |||
| 816a9c07c3 | |||
| 1e741bc859 | |||
| 398b9b11e4 | |||
| cb76934ca2 | |||
| aa92805e14 | |||
| d6355074b2 | |||
| 070e671ddb | |||
| 4efdbabf3e | |||
| f33819da60 | |||
| f16e6b06f3 | |||
| 25ff5a790b | |||
| f446f6061d | |||
| 4262620c57 | |||
| b46d1e2ca1 | |||
| 88d0666da9 | |||
| 08bf5c21bf | |||
| be97a6a247 | |||
| dff71f8070 | |||
| 36fc5ae1e4 | |||
| 2d7c881b3b | |||
| aed4a124cd | |||
| cda5fc1188 | |||
| 2a5c6edfce | |||
| 7a1703e70f | |||
| 8222478817 | |||
| 7a1b5267bf | |||
| 2bd50e1c19 | |||
| 6d0fd51c8e | |||
| 65a16d0964 | |||
| a152d9b159 |
@@ -15,7 +15,7 @@ jobs:
|
||||
macOS:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_VERSION: ${{ '13.3.1' }}
|
||||
XCODE_VERSION: ${{ '14.2' }}
|
||||
steps:
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
docs:
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_VERSION: ${{ '13.3.1' }}
|
||||
XCODE_VERSION: ${{ '14.2' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Select Xcode
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
name: Add macOS binaries to release
|
||||
runs-on: macos-12
|
||||
env:
|
||||
XCODE_VERSION: ${{ '13.3.1' }}
|
||||
XCODE_VERSION: ${{ '14.2' }}
|
||||
steps:
|
||||
- name: Select Xcode
|
||||
run: "sudo xcode-select -s /Applications/Xcode_$XCODE_VERSION.app"
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@
|
||||
"repositoryURL": "https://github.com/tuist/XcodeProj.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "c75c3acc25460195cfd203a04dde165395bf00e0",
|
||||
"version": "8.7.1"
|
||||
"revision": "fae27b48bc14ff3fd9b02902e48c4665ce5a0793",
|
||||
"version": "8.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+23
-2
@@ -16,7 +16,7 @@ let package = Package(
|
||||
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.2"),
|
||||
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.7.1"),
|
||||
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.9.0"),
|
||||
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
|
||||
],
|
||||
targets: [
|
||||
@@ -32,8 +32,16 @@ let package = Package(
|
||||
name: "xcswiftc",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
),
|
||||
.target(
|
||||
name: "xclibtoolSupport",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
),
|
||||
.target(
|
||||
name: "xclibtool",
|
||||
dependencies: ["XCRemoteCache", "xclibtoolSupport"]
|
||||
),
|
||||
.target(
|
||||
name: "xclipo",
|
||||
dependencies: ["XCRemoteCache"]
|
||||
),
|
||||
.target(
|
||||
@@ -58,12 +66,25 @@ let package = Package(
|
||||
.target(
|
||||
// Wrapper target that builds all binaries but does nothing in runtime
|
||||
name: "Aggregator",
|
||||
dependencies: ["xcprebuild", "xcswiftc", "xclibtool", "xcpostbuild", "xcprepare", "xcld", "xcldplusplus"]
|
||||
dependencies: [
|
||||
"xcprebuild",
|
||||
"xcswiftc",
|
||||
"xclibtool",
|
||||
"xcpostbuild",
|
||||
"xcprepare",
|
||||
"xcld",
|
||||
"xcldplusplus",
|
||||
"xclipo",
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "XCRemoteCacheTests",
|
||||
dependencies: ["XCRemoteCache"],
|
||||
resources: [.copy("TestData")]
|
||||
),
|
||||
.testTarget(
|
||||
name: "xclibtoolSupportTests",
|
||||
dependencies: ["xclibtoolSupport"]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
|
||||
[](https://github.com/spotify/XCRemoteCache/workflows/CI/badge.svg)
|
||||
[](LICENSE)
|
||||
[](https://slackin.spotify.com)
|
||||
[](https://github.com/spotify/XCRemoteCache/workflows/Docs/badge.svg)
|
||||
[](https://spotify.github.io/XCRemoteCache/documentation/xcremotecache/)
|
||||
|
||||
- [How and Why?](#how-and-why)
|
||||
* [Accurate target input files](#accurate-target-input-files)
|
||||
@@ -152,6 +152,7 @@ xcremotecache/xcprepare integrate --input <yourProject.xcodeproj> --mode consume
|
||||
| `--lldb-init` | LLDBInit mode. Appends to .lldbinit a command required for debugging. Supported values: 'none' (do not append to .lldbinit), 'user' (append to ~/.lldbinit) | `user` | ⬜️ |
|
||||
| `--fake-src-root` | An arbitrary source location shared between producers and consumers. Should be unique for a project. | `/xxxxxxxxxx` | ⬜️ |
|
||||
| `--output` | Save the project with integrated XCRemoteCache to a separate location. | N/A | ⬜️ |
|
||||
| `--sdks-exclude` | comma separated list of sdks to not integrate XCRemoteCache (e.g. "watchos*, watchsimulator*"). (Experimental) | `""` | ⬜️ |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -195,6 +196,7 @@ Configure Xcode targets that **should use** XCRemoteCache:
|
||||
* `CC` - `xccc_file` from your `.rcinfo` configuration (e.g. `xcremotecache/xccc`)
|
||||
* `SWIFT_EXEC` - location of `xcprepare` (e.g. `xcremotecache/xcswiftc`)
|
||||
* `LIBTOOL` - location of `xclibtool` (e.g. `xcremotecache/xclibtool`)
|
||||
* `LIPO` - location of `xclipo` (e.g. `xcremotecache/xclipo`)
|
||||
* `LD` - location of `xcld` (e.g. `xcremotecache/xcld`)
|
||||
* `LDPLUSPLUS` - location of `xcldplusplus` (e.g. `xcremotecache/xcldplusplus`)
|
||||
* `XCRC_PLATFORM_PREFERRED_ARCH` - `$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)`
|
||||
@@ -267,7 +269,42 @@ $ xcremotecache/xcprepare mark --configuration Debug --platform iphonesimulator
|
||||
|
||||
That command creates an empty file on a remote server which informs that for given sha, configuration, platform, Xcode versions etc. all artifacts are available.
|
||||
|
||||
_Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`, `xcldplusplus`, `xclibtool` wrappers become no-op, so it is recommended to not add them for the `producer` mode._
|
||||
_Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`, `xcldplusplus`, `xclibtool`, `xclipo` wrappers become no-op, so it is recommended to not add them for the `producer` mode._
|
||||
|
||||
##### 7. Generalize `-Swift.h` (Optional only if using static library with a bridging header with public `NS_ENUM` exposed from ObjC)
|
||||
|
||||
If a static library target contains a mixed target with a bridging header exposing an enum from ObjC in a public Swift API, your custom script that moves `*-Swift.h` to the shared location, it should also move `*-Swift.h.md5` next to it.
|
||||
|
||||
Example:
|
||||
|
||||
##### Existing script (Before):
|
||||
|
||||
```shell
|
||||
ditto "${SCRIPT_INPUT_FILE_0}" "${SCRIPT_OUTPUT_FILE_0}"
|
||||
```
|
||||
|
||||
where
|
||||
* `SCRIPT_INPUT_FILE_0="$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"`
|
||||
* `SCRIPT_OUTPUT_FILE_0="$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"`
|
||||
|
||||
##### Correct script (After):
|
||||
|
||||
```shell
|
||||
ditto "${SCRIPT_INPUT_FILE_0}" "${SCRIPT_OUTPUT_FILE_0}"
|
||||
[ -f "${SCRIPT_INPUT_FILE_1}" ] && ditto "${SCRIPT_INPUT_FILE_1}" "${SCRIPT_OUTPUT_FILE_1}" || rm "${SCRIPT_OUTPUT_FILE_1}"
|
||||
```
|
||||
|
||||
where
|
||||
* `SCRIPT_INPUT_FILE_0="$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"`
|
||||
* `SCRIPT_INPUT_FILE_1="$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME).md5"`
|
||||
* `SCRIPT_OUTPUT_FILE_0="$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)"`
|
||||
* `SCRIPT_OUTPUT_FILE_1="$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME).md5"`
|
||||
|
||||
Note: This step is not required if at least one of these is true:
|
||||
|
||||
* you build a framework (not a static library)
|
||||
* you don't expose `NS_ENUM` type from ObjC to Swift via a bridging header
|
||||
|
||||
|
||||
## A full list of configuration parameters:
|
||||
|
||||
@@ -298,6 +335,8 @@ _Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`,
|
||||
| `stats_dir` | Directory where all XCRemoteCache statistics (e.g. counters) are stored | `~/.xccache` | ⬜️ |
|
||||
| `download_retries` | Number of retries for download requests | `0` | ⬜️ |
|
||||
| `upload_retries` | Number of retries for upload requests | `3` | ⬜️ |
|
||||
| `retry_delay` | Delay between retries in seconds | `10` | ⬜️ |
|
||||
| `upload_batch_size` | Maximum number of simultaneous requests. 0 means no limits | `0` | ⬜️ |
|
||||
| `request_custom_headers` | Dictionary of extra HTTP headers for all remote server requests | `[]` | ⬜️ |
|
||||
| `thin_target_mock_filename` | Filename (without an extension) of the compilation input file that is used as a fake compilation for the forced-cached target (aka thin target) | `standin` | ⬜️ |
|
||||
| `focused_targets` | A list of all targets that are not thinned. If empty, all targets are meant to be non-thin | `[]` | ⬜️ |
|
||||
|
||||
@@ -10,7 +10,7 @@ DERIVED_DATA_DIR = File.join('.build').freeze
|
||||
RELEASES_ROOT_DIR = File.join('releases').freeze
|
||||
|
||||
EXECUTABLE_NAME = 'XCRemoteCache'
|
||||
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus']
|
||||
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus', 'xclipo']
|
||||
PROJECT_NAME = 'XCRemoteCache'
|
||||
|
||||
SWIFTLINT_ENABLED = true
|
||||
@@ -72,6 +72,22 @@ task :build, [:configuration, :arch, :sdks, :is_archive] do |task, args|
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Build release artifacts'
|
||||
task :prepare_release do
|
||||
system("rm -rf releases && rm -rf tmp")
|
||||
Rake::Task['build'].invoke("release", "x86_64-apple-macosx")
|
||||
system("mkdir -p tmp && unzip releases/XCRemoteCache.zip -d tmp/xcremotecache-x86_64")
|
||||
system("rm -rf releases")
|
||||
Rake::Task['build'].invoke("release", "arm64-apple-macosx")
|
||||
system("rake 'build[release, arm64-apple-macosx]'")
|
||||
system("mkdir -p tmp && unzip releases/XCRemoteCache.zip -d tmp/xcremotecache-arm64")
|
||||
system("rm -rf releases")
|
||||
system("mkdir -p releases && zip -jr releases/XCRemoteCache-macOS-x86_64.zip LICENSE README.md tmp/xcremotecache-x86_64")
|
||||
system("zip -jr releases/XCRemoteCache-macOS-arm64.zip LICENSE README.md tmp/xcremotecache-arm64")
|
||||
system("mkdir -p tmp/xcremotecache && ls tmp/xcremotecache-x86_64 | xargs -I {} lipo -create -output tmp/xcremotecache/{} tmp/xcremotecache-x86_64/{} tmp/xcremotecache-arm64/{}")
|
||||
system("zip -jr releases/XCRemoteCache-macOS-arm64-x86_64.zip LICENSE README.md tmp/xcremotecache")
|
||||
end
|
||||
|
||||
desc 'run tests with SPM'
|
||||
task :test do
|
||||
# Running tests
|
||||
|
||||
@@ -42,6 +42,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
|
||||
private let modulesFolderPath: String
|
||||
private let dSYMPath: URL
|
||||
private let metaWriter: MetaWriter
|
||||
private let artifactProcessor: ArtifactProcessor
|
||||
private let fileManager: FileManager
|
||||
|
||||
init(
|
||||
@@ -52,6 +53,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
|
||||
modulesFolderPath: String,
|
||||
dSYMPath: URL,
|
||||
metaWriter: MetaWriter,
|
||||
artifactProcessor: ArtifactProcessor,
|
||||
fileManager: FileManager
|
||||
) {
|
||||
self.buildDir = buildDir
|
||||
@@ -62,6 +64,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
|
||||
self.fileManager = fileManager
|
||||
self.dSYMPath = dSYMPath
|
||||
self.metaWriter = metaWriter
|
||||
self.artifactProcessor = artifactProcessor
|
||||
super.init(workingDir: tempDir, moduleName: moduleName, fileManager: fileManager)
|
||||
}
|
||||
|
||||
@@ -87,6 +90,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
|
||||
/// - Parameter tempDir: Temp location to organize file hierarchy in the artifact
|
||||
/// - returns: URLs to include into the artifact package
|
||||
fileprivate func prepareSwiftArtifacts(tempDir: URL) throws -> [URL] {
|
||||
try artifactProcessor.process(localArtifact: tempDir)
|
||||
var artifacts: [URL] = []
|
||||
|
||||
// Add optional directory with generated ObjC headers
|
||||
|
||||
@@ -31,7 +31,7 @@ enum ArtifactOrganizerLocationPreparationResult: Equatable {
|
||||
case preparedForArtifact(artifact: URL)
|
||||
}
|
||||
|
||||
/// Prepares .zip artifact for the local operations
|
||||
/// Prepares existing .zip artifact for the local operations
|
||||
protocol ArtifactOrganizer {
|
||||
/// Prepares the location for the artifact unzipping
|
||||
/// - Parameter fileKey: artifact fileKey that corresponds to the zip filename on the remote cache server
|
||||
@@ -48,10 +48,13 @@ protocol ArtifactOrganizer {
|
||||
|
||||
class ZipArtifactOrganizer: ArtifactOrganizer {
|
||||
private let cacheDir: URL
|
||||
// all processors that should "prepare" the unzipped raw artifact
|
||||
private let artifactProcessors: [ArtifactProcessor]
|
||||
private let fileManager: FileManager
|
||||
|
||||
init(targetTempDir: URL, fileManager: FileManager) {
|
||||
init(targetTempDir: URL, artifactProcessors: [ArtifactProcessor], fileManager: FileManager) {
|
||||
cacheDir = targetTempDir.appendingPathComponent("xccache")
|
||||
self.artifactProcessors = artifactProcessors
|
||||
self.fileManager = fileManager
|
||||
}
|
||||
|
||||
@@ -93,6 +96,10 @@ class ZipArtifactOrganizer: ArtifactOrganizer {
|
||||
// when the command was interrupted (internal crash or `kill -9` signal)
|
||||
let tempDestination = destinationURL.appendingPathExtension("tmp")
|
||||
try Zip.unzipFile(artifact, destination: tempDestination, overwrite: true, password: nil)
|
||||
|
||||
try artifactProcessors.forEach { processor in
|
||||
try processor.process(rawArtifact: tempDestination)
|
||||
}
|
||||
try fileManager.moveItem(at: tempDestination, to: destinationURL)
|
||||
return destinationURL
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2022 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
|
||||
|
||||
|
||||
/// Performs a pre/postprocessing on an artifact package
|
||||
/// Could be a place for file reorganization (to support legacy package formats) and/or
|
||||
/// remapp absolute paths in some package files
|
||||
protocol ArtifactProcessor {
|
||||
/// Processes a raw artifact in a directory. Raw artifact is a format of an artifact
|
||||
/// that is stored in a remote cache server (generic)
|
||||
/// - Parameter rawArtifact: directory that contains raw artifact content
|
||||
func process(rawArtifact: URL) throws
|
||||
|
||||
/// Processes a local artifact in a directory
|
||||
/// - Parameter localArtifact: directory that contains local (machine-specific) artifact content
|
||||
func process(localArtifact: URL) throws
|
||||
}
|
||||
|
||||
/// Processes downloaded artifact by replacing generic paths in generated ObjC headers placed in ./include
|
||||
class UnzippedArtifactProcessor: ArtifactProcessor {
|
||||
/// All directories in an artifact that should be processed by path remapping
|
||||
private static let remappingDirs = ["include"]
|
||||
private let fileRemapper: FileDependenciesRemapper
|
||||
private let dirScanner: DirScanner
|
||||
|
||||
init(fileRemapper: FileDependenciesRemapper, dirScanner: DirScanner) {
|
||||
self.fileRemapper = fileRemapper
|
||||
self.dirScanner = dirScanner
|
||||
}
|
||||
|
||||
private func findProcessingEligableFiles(path: String) throws -> [URL] {
|
||||
let remappingURL = URL(fileURLWithPath: path)
|
||||
let allFiles = try dirScanner.recursiveItems(at: remappingURL)
|
||||
return allFiles.filter({ !$0.isHidden })
|
||||
}
|
||||
|
||||
/// Replaces all generic paths in a raw artifact's `include` dir with
|
||||
/// absolute paths, specific for a given machine and configuration
|
||||
/// - Parameter rawArtifact: raw artifact location
|
||||
func process(rawArtifact url: URL) throws {
|
||||
for remappingDir in Self.remappingDirs {
|
||||
let remappingPath = url.appendingPathComponent(remappingDir).path
|
||||
let allFiles = try findProcessingEligableFiles(path: remappingPath)
|
||||
try allFiles.forEach(fileRemapper.remap(fromGeneric:))
|
||||
}
|
||||
}
|
||||
|
||||
func process(localArtifact url: URL) throws {
|
||||
for remappingDir in Self.remappingDirs {
|
||||
let remappingPath = url.appendingPathComponent(remappingDir).path
|
||||
let allFiles = try findProcessingEligableFiles(path: remappingPath)
|
||||
try allFiles.forEach(fileRemapper.remap(fromLocal:))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension URL {
|
||||
// Recognize hidden files starting with a dot
|
||||
var isHidden: Bool {
|
||||
lastPathComponent.hasPrefix(".")
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class ArtifactSwiftProductsBuilderImpl: ArtifactSwiftProductsBuilder {
|
||||
throw ArtifactSwiftProductsBuilderError.populatingNonExistingObjCHeader
|
||||
}
|
||||
try fileManager.createDirectory(at: moduleObjCURL, withIntermediateDirectories: true, attributes: nil)
|
||||
try fileManager.spt_forceLinkItem(at: headerURL, to: headerArtifactURL)
|
||||
try fileManager.spt_forceCopyItem(at: headerURL, to: headerArtifactURL)
|
||||
}
|
||||
|
||||
func includeModuleDefinitionsToTheArtifact(arch: String, moduleURL: URL) throws {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2022 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 FileDependenciesRemapperError: Error {
|
||||
/// Thrown when the file to remap is invalid (e.g. doesn't exist or has unexpected format)
|
||||
case invalidRemappingFile(URL)
|
||||
}
|
||||
|
||||
/// Replaces paths in a file content between generic (placeholder-based)
|
||||
/// and local formats
|
||||
protocol FileDependenciesRemapper {
|
||||
/// Replaces all generic paths (with placeholders) to a local, machine
|
||||
/// specific absolute paths
|
||||
/// - Parameter url: location of a file that should be remapped in-place
|
||||
func remap(fromGeneric url: URL) throws
|
||||
/// Replaces all local, machine specific absolute paths to
|
||||
/// generic ones
|
||||
/// - Parameter url: location of a file that should be remapped in-place
|
||||
func remap(fromLocal url: URL) throws
|
||||
}
|
||||
|
||||
/// Remaps absolute paths in a text files stored on a disk
|
||||
/// Note: That class can be used only for text-based files, not binaries
|
||||
class TextFileDependenciesRemapper: FileDependenciesRemapper {
|
||||
private static let linesSeparator = "\n"
|
||||
private let remapper: DependenciesRemapper
|
||||
private let fileAccessor: FileAccessor
|
||||
|
||||
init(remapper: DependenciesRemapper, fileAccessor: FileAccessor) {
|
||||
self.remapper = remapper
|
||||
self.fileAccessor = fileAccessor
|
||||
}
|
||||
|
||||
private func readFileLines(_ url: URL) throws -> [String] {
|
||||
guard let content = try fileAccessor.contents(atPath: url.path) else {
|
||||
// the file is empty
|
||||
return []
|
||||
}
|
||||
guard let contentString = String(data: content, encoding: .utf8) else {
|
||||
throw FileDependenciesRemapperError.invalidRemappingFile(url)
|
||||
}
|
||||
return contentString.components(separatedBy: .newlines)
|
||||
}
|
||||
|
||||
private func storeFileLines(lines: [String], url: URL) throws {
|
||||
let contentString = lines.joined(separator: "\n")
|
||||
let contentData = contentString.data(using: String.Encoding.utf8)
|
||||
try fileAccessor.write(toPath: url.path, contents: contentData)
|
||||
}
|
||||
|
||||
func remap(fromGeneric url: URL) throws {
|
||||
let contentLines = try readFileLines(url)
|
||||
let remappedContent = try remapper.replace(genericPaths: contentLines)
|
||||
try storeFileLines(lines: remappedContent, url: url)
|
||||
}
|
||||
|
||||
func remap(fromLocal url: URL) throws {
|
||||
let contentLines = try readFileLines(url)
|
||||
let remappedContent = try remapper.replace(localPaths: contentLines)
|
||||
try storeFileLines(lines: remappedContent, url: url)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ enum SwiftmoduleFileExtension: String {
|
||||
case swiftdoc
|
||||
case swiftsourceinfo
|
||||
case swiftinterface
|
||||
case privateSwiftinterface = "private.swiftinterface"
|
||||
case abiJson = "abi.json"
|
||||
}
|
||||
|
||||
extension SwiftmoduleFileExtension {
|
||||
@@ -40,5 +42,7 @@ extension SwiftmoduleFileExtension {
|
||||
.swiftdoc: .required,
|
||||
.swiftsourceinfo: .optional,
|
||||
.swiftinterface: .optional,
|
||||
.privateSwiftinterface: .optional,
|
||||
.abiJson: .optional,
|
||||
]
|
||||
}
|
||||
|
||||
+21
-11
@@ -19,31 +19,42 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum XCLibtoolCreateUniversalBinaryError: Error {
|
||||
enum XCCreateUniversalBinaryError: Error {
|
||||
/// Missing ar libraries that should constitute an universal build
|
||||
case missingInputLibrary
|
||||
}
|
||||
|
||||
/// Wrapper for `libtool` call for creating an universal binary
|
||||
class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
|
||||
/// Wrapper for `libtool`/`lipo` call for creating an universal binary
|
||||
class XCCreateUniversalBinary: XCLibtoolLogic {
|
||||
private let output: URL
|
||||
private let tempDir: URL
|
||||
private let firstInputURL: URL
|
||||
private let toolName: String
|
||||
private let fallbackCommand: String
|
||||
|
||||
init(output: String, inputs: [String]) throws {
|
||||
init(
|
||||
output: String,
|
||||
inputs: [String],
|
||||
toolName: String,
|
||||
fallbackCommand: String
|
||||
) throws {
|
||||
self.output = URL(fileURLWithPath: output)
|
||||
guard let firstInput = inputs.first else {
|
||||
throw XCLibtoolCreateUniversalBinaryError.missingInputLibrary
|
||||
throw XCCreateUniversalBinaryError.missingInputLibrary
|
||||
}
|
||||
let firstInputURL = URL(fileURLWithPath: firstInput)
|
||||
// inputs are place in $TARGET_TEMP_DIR/Objects-normal/$ARCH/Binary/$TARGET_NAME.a
|
||||
// TODO: find better (stable) technique to determine `$TARGET_TEMP_DIR`
|
||||
errorLog("\(firstInputURL.absoluteString)")
|
||||
|
||||
tempDir = firstInputURL
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
self.firstInputURL = firstInputURL
|
||||
self.toolName = toolName
|
||||
self.fallbackCommand = fallbackCommand
|
||||
}
|
||||
|
||||
func run() {
|
||||
@@ -55,7 +66,7 @@ class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
|
||||
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileReader: fileManager)
|
||||
.readConfiguration()
|
||||
} catch {
|
||||
errorLog("Libtool initialization failed with error: \(error). Fallbacking to libtool")
|
||||
errorLog("\(toolName) initialization failed with error: \(error). Fallbacking to \(fallbackCommand)")
|
||||
fallbackToDefault()
|
||||
}
|
||||
|
||||
@@ -74,22 +85,21 @@ class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
|
||||
// that these are already an universal binary
|
||||
try fileManager.spt_forceLinkItem(at: firstInputURL, to: output)
|
||||
} catch {
|
||||
errorLog("Libtool failed with error: \(error). Fallbacking to libtool")
|
||||
errorLog("\(toolName) failed with error: \(error). Fallbacking to \(fallbackCommand)")
|
||||
do {
|
||||
try fileManager.removeItem(at: markerURL)
|
||||
fallbackToDefault()
|
||||
} catch {
|
||||
exit(1, "FATAL: libtool failed with error: \(error)")
|
||||
exit(1, "FATAL: \(fallbackCommand) failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fallbackToDefault() -> Never {
|
||||
let args = ProcessInfo().arguments
|
||||
let command = "libtool"
|
||||
let paramList = [command] + args.dropFirst()
|
||||
let paramList = [fallbackCommand] + args.dropFirst()
|
||||
let cargs = paramList.map { strdup($0) } + [nil]
|
||||
execvp(command, cargs)
|
||||
execvp(fallbackCommand, cargs)
|
||||
|
||||
/// C-function `execv` returns only when the command fails
|
||||
exit(1)
|
||||
@@ -20,7 +20,7 @@
|
||||
import Foundation
|
||||
|
||||
/// Represents a mode that libtool was called
|
||||
public enum XCLibtoolMode {
|
||||
public enum XCLibtoolMode: Equatable {
|
||||
/// Creating a static library (ar format) from a set of .o input files
|
||||
case createLibrary(output: String, filelist: String, dependencyInfo: String)
|
||||
/// Creating a universal library (multiple-architectures) from a set of input .a static libraries
|
||||
@@ -44,7 +44,12 @@ public class XCLibtool {
|
||||
stepDescription: "Libtool"
|
||||
)
|
||||
case .createUniversalBinary(let output, let inputs):
|
||||
logic = try XCLibtoolCreateUniversalBinary(output: output, inputs: inputs)
|
||||
logic = try XCCreateUniversalBinary(
|
||||
output: output,
|
||||
inputs: inputs,
|
||||
toolName: "Libtool",
|
||||
fallbackCommand: "libtool"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2023 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
|
||||
|
||||
/// Wrapper for a `lipo` tool that creates a fat archive
|
||||
public class XCLipo {
|
||||
private let logic: XCLibtoolLogic
|
||||
|
||||
public init(
|
||||
output: String,
|
||||
inputs: [String],
|
||||
fallbackCommand: String,
|
||||
stepDescription: String
|
||||
) throws {
|
||||
errorLog("\(output)")
|
||||
errorLog("\(inputs.joined(separator: ","))")
|
||||
logic = try XCCreateUniversalBinary(
|
||||
output: output,
|
||||
inputs: inputs,
|
||||
toolName: stepDescription,
|
||||
fallbackCommand: fallbackCommand
|
||||
)
|
||||
}
|
||||
|
||||
/// Handles a `-create` action which is responsible to create a fat archive
|
||||
/// If remote cache can reuse artifacts from a remote cache, it just links any of input
|
||||
/// files to the destination (output) location because the binary in XCRC artifact already
|
||||
/// contains a fat library
|
||||
/// If a remote artifact cannot be reused, a fallback to the `lipo` command is performed
|
||||
public func run() {
|
||||
logic.run()
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -27,13 +27,19 @@ protocol ThinningConsumerArtifactsOrganizerFactory {
|
||||
}
|
||||
|
||||
class ThinningConsumerZipArtifactsOrganizerFactory: ThinningConsumerArtifactsOrganizerFactory {
|
||||
private let processors: [ArtifactProcessor]
|
||||
private let fileManager: FileManager
|
||||
|
||||
init(fileManager: FileManager) {
|
||||
init(processors: [ArtifactProcessor], fileManager: FileManager) {
|
||||
self.processors = processors
|
||||
self.fileManager = fileManager
|
||||
}
|
||||
|
||||
func build(targetTempDir: URL) -> ArtifactOrganizer {
|
||||
ZipArtifactOrganizer(targetTempDir: targetTempDir, fileManager: fileManager)
|
||||
ZipArtifactOrganizer(
|
||||
targetTempDir: targetTempDir,
|
||||
artifactProcessors: processors,
|
||||
fileManager: fileManager
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -58,7 +58,7 @@ class ThinningDiskSwiftcProductsGenerator: SwiftcProductsGenerator {
|
||||
func generateFrom(
|
||||
artifactSwiftModuleFiles sourceAtifactSwiftModuleFiles: [SwiftmoduleFileExtension: URL],
|
||||
artifactSwiftModuleObjCFile: URL
|
||||
) throws -> URL {
|
||||
) throws -> SwiftcProductsGeneratorOutput {
|
||||
// Move cached -Swift.h file to the expected location
|
||||
try diskCopier.copy(file: artifactSwiftModuleObjCFile, destination: objcHeaderOutput)
|
||||
for (ext, url) in sourceAtifactSwiftModuleFiles {
|
||||
@@ -79,6 +79,9 @@ class ThinningDiskSwiftcProductsGenerator: SwiftcProductsGenerator {
|
||||
}
|
||||
|
||||
// Build parent dir of the .swiftmodule file that contains a module
|
||||
return modulePathOutput.deletingLastPathComponent()
|
||||
return SwiftcProductsGeneratorOutput(
|
||||
swiftmoduleDir: modulePathOutput.deletingLastPathComponent(),
|
||||
objcHeaderFile: objcHeaderOutput
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -73,11 +73,12 @@ class UnzippedArtifactSwiftProductsOrganizer: SwiftProductsOrganizer {
|
||||
.appendingPathComponent(moduleName)
|
||||
.appendingPathComponent("\(moduleName)-Swift.h")
|
||||
|
||||
let generatedModuleDir = try productsGenerator.generateFrom(
|
||||
let generatedModule = try productsGenerator.generateFrom(
|
||||
artifactSwiftModuleFiles: artifactSwiftmoduleFiles,
|
||||
artifactSwiftModuleObjCFile: artifactSwiftModuleObjCFile
|
||||
)
|
||||
|
||||
try fingerprintSyncer.decorate(sourceDir: generatedModuleDir, fingerprint: fingerprint)
|
||||
try fingerprintSyncer.decorate(sourceDir: generatedModule.swiftmoduleDir, fingerprint: fingerprint)
|
||||
try fingerprintSyncer.decorate(file: generatedModule.objcHeaderFile, fingerprint: fingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,13 +238,32 @@ class Postbuild {
|
||||
let moduleSwiftProductURL = context.productsDir
|
||||
.appendingPathComponent(context.modulesFolderPath)
|
||||
.appendingPathComponent("\(modulename).swiftmodule")
|
||||
let objcHeaderSwiftProductURL = context.derivedSourcesDir
|
||||
.appendingPathComponent("\(modulename)-Swift.h")
|
||||
// This header is obly valid if building a frameworks
|
||||
let objcHeaderSwiftPublicPathURL = context.publicHeadersFolderPath?
|
||||
.appendingPathComponent("\(modulename)-Swift.h")
|
||||
if let fingerprint = contextSpecificFingerprint {
|
||||
try fingerprintSyncer.decorate(
|
||||
sourceDir: moduleSwiftProductURL,
|
||||
fingerprint: fingerprint
|
||||
)
|
||||
try fingerprintSyncer.decorate(
|
||||
file: objcHeaderSwiftProductURL,
|
||||
fingerprint: fingerprint
|
||||
)
|
||||
if let objcPublic = objcHeaderSwiftPublicPathURL {
|
||||
try fingerprintSyncer.decorate(
|
||||
file: objcPublic,
|
||||
fingerprint: fingerprint
|
||||
)
|
||||
}
|
||||
} else {
|
||||
try fingerprintSyncer.delete(sourceDir: moduleSwiftProductURL)
|
||||
try fingerprintSyncer.delete(sourceDir: objcHeaderSwiftProductURL)
|
||||
if let objcPublic = objcHeaderSwiftPublicPathURL {
|
||||
try fingerprintSyncer.delete(file: objcPublic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public struct PostbuildContext {
|
||||
let builtProductsDir: URL
|
||||
/// Location to the product bundle. Can be nil for libraries
|
||||
let bundleDir: URL?
|
||||
let derivedSourcesDir: URL
|
||||
var derivedSourcesDir: URL
|
||||
/// List of all targets to downloaded from the thinning aggregation target
|
||||
var thinnedTargets: [String]
|
||||
/// Action type: build, indexbuild etc
|
||||
@@ -85,6 +85,10 @@ public struct PostbuildContext {
|
||||
let overlayHeadersPath: URL
|
||||
/// Regexes of files that should not be included in the dependency list
|
||||
let irrelevantDependenciesPaths: [String]
|
||||
/// Location of public headers. Not always available (e.g. static libraries)
|
||||
var publicHeadersFolderPath: URL?
|
||||
/// XCRemoteCache is explicitly disabled
|
||||
let disabled: Bool
|
||||
}
|
||||
|
||||
extension PostbuildContext {
|
||||
@@ -138,5 +142,12 @@ extension PostbuildContext {
|
||||
/// Note: The file has yaml extension, even it is in the json format
|
||||
overlayHeadersPath = targetTempDir.appendingPathComponent("all-product-headers.yaml")
|
||||
irrelevantDependenciesPaths = config.irrelevantDependenciesPaths
|
||||
let publicHeadersPathEnv: String? = env.readEnv(key: "PUBLIC_HEADERS_FOLDER_PATH")
|
||||
if let publicHeadersPath = publicHeadersPathEnv, publicHeadersPathEnv != "/usr/local/include" {
|
||||
// '/usr/local/include' is a value of PUBLIC_HEADERS_FOLDER_PATH when no public headers are automatically
|
||||
// generated and it is up to a project configuration to place it in a common location (e.g. static library)
|
||||
publicHeadersFolderPath = builtProductsDir.appendingPathComponent(publicHeadersPath)
|
||||
}
|
||||
disabled = try env.readEnv(key: "XCRC_DISABLED") ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,14 @@ public class XCPostbuild {
|
||||
fingerprintFilesGenerator,
|
||||
algorithm: MD5Algorithm()
|
||||
)
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: context.targetTempDir,
|
||||
// In postbuild we don't preprocess artifacts (no need to replace path placeholders)
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
let metaWriter = JsonMetaWriter(fileWriter: fileManager, pretty: config.prettifyMetaFiles)
|
||||
let fileRemapper = TextFileDependenciesRemapper(remapper: envsRemapper, fileAccessor: fileManager)
|
||||
let artifactCreator = BuildArtifactCreator(
|
||||
buildDir: context.productsDir,
|
||||
tempDir: context.targetTempDir,
|
||||
@@ -97,6 +103,7 @@ public class XCPostbuild {
|
||||
modulesFolderPath: context.modulesFolderPath,
|
||||
dSYMPath: context.dSYMPath,
|
||||
metaWriter: metaWriter,
|
||||
artifactProcessor: UnzippedArtifactProcessor(fileRemapper: fileRemapper, dirScanner: fileManager),
|
||||
fileManager: fileManager
|
||||
)
|
||||
let dirAccessor = DirAccessorComposer(
|
||||
@@ -123,6 +130,7 @@ public class XCPostbuild {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.uploadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
@@ -130,6 +138,7 @@ public class XCPostbuild {
|
||||
mode: context.mode,
|
||||
downloadStreamURL: context.recommendedCacheAddress,
|
||||
upstreamStreamURL: context.cacheAddresses,
|
||||
uploadBatchSize: config.uploadBatchSize,
|
||||
networkClient: networkClient,
|
||||
urlBuilderFactory: {
|
||||
try URLBuilderImpl(
|
||||
@@ -200,7 +209,10 @@ public class XCPostbuild {
|
||||
if context.moduleName == config.thinningTargetModuleName {
|
||||
switch context.mode {
|
||||
case .consumer:
|
||||
// no need to process artifacts in postbuild. Prebuild has already
|
||||
// run a processor on a downloaded artifact
|
||||
let artifactOrganizerFactory = ThinningConsumerZipArtifactsOrganizerFactory(
|
||||
processors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
let swiftProductsLocationProvider =
|
||||
@@ -262,7 +274,12 @@ public class XCPostbuild {
|
||||
)
|
||||
|
||||
// Trigger build completion
|
||||
if try modeController.isEnabled() {
|
||||
if context.disabled {
|
||||
infoLog("XCRC fully disabled for \(context.targetName), \(context.platform), \(context.configuration)")
|
||||
// Cutoff the process is disabled, but generate an "empty" list of dependencies
|
||||
try? modeController.disable()
|
||||
return
|
||||
} else if try modeController.isEnabled() {
|
||||
// Decorate .swiftmodule in the product dir with fingerprint(s) overrides from a cache artifact
|
||||
try postbuildAction.performBuildCompletion()
|
||||
} else if context.mode == .consumer {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import Foundation
|
||||
|
||||
enum PrebuildResult: Equatable {
|
||||
case disabled
|
||||
case incompatible
|
||||
case compatible(localDependencies: [URL])
|
||||
}
|
||||
@@ -57,6 +58,9 @@ class Prebuild {
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
public func perform() throws -> PrebuildResult {
|
||||
guard !context.disabled else {
|
||||
return .disabled
|
||||
}
|
||||
guard case .available(let commit) = context.remoteCommit else {
|
||||
return .incompatible
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ public struct PrebuildContext {
|
||||
/// location of the json file that define virtual files system overlay
|
||||
/// (mappings of the virtual location file -> local file path)
|
||||
let overlayHeadersPath: URL
|
||||
/// XCRemoteCache is explicitly disabled
|
||||
let disabled: Bool
|
||||
}
|
||||
|
||||
extension PrebuildContext {
|
||||
@@ -69,5 +71,6 @@ extension PrebuildContext {
|
||||
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")
|
||||
disabled = try env.readEnv(key: "XCRC_DISABLED") ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ public class XCPrebuild {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.downloadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
@@ -151,13 +152,25 @@ public class XCPrebuild {
|
||||
filesFingerprintGenerator,
|
||||
algorithm: MD5Algorithm()
|
||||
)
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
|
||||
let fileRemapper = TextFileDependenciesRemapper(
|
||||
remapper: envsRemapper,
|
||||
fileAccessor: fileManager
|
||||
)
|
||||
let artifactProcessor = UnzippedArtifactProcessor(fileRemapper: fileRemapper, dirScanner: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: context.targetTempDir,
|
||||
artifactProcessors: [artifactProcessor],
|
||||
fileManager: fileManager
|
||||
)
|
||||
let metaReader = JsonMetaReader(fileAccessor: fileManager)
|
||||
var consumerPlugins: [ArtifactConsumerPrebuildPlugin] = []
|
||||
|
||||
if config.thinningEnabled {
|
||||
if context.moduleName == config.thinningTargetModuleName, let thinnedTarget = context.thinnedTargets {
|
||||
let organizerFactory = ThinningConsumerZipArtifactsOrganizerFactory(fileManager: .default)
|
||||
let organizerFactory = ThinningConsumerZipArtifactsOrganizerFactory(
|
||||
processors: [artifactProcessor],
|
||||
fileManager: fileManager
|
||||
)
|
||||
let aggregationPlugin = ThinningConsumerPrebuildPlugin(
|
||||
targetName: context.targetName,
|
||||
tempDir: context.targetTempDir,
|
||||
@@ -189,6 +202,9 @@ public class XCPrebuild {
|
||||
case .compatible(localDependencies: let dependencies):
|
||||
// TODO: pass `allowedInputFiles` observed in the build time
|
||||
try modeController.enable(allowedInputFiles: dependencies, dependencies: dependencies)
|
||||
case .disabled:
|
||||
infoLog("XCRemoteCache is explicitly disabled")
|
||||
try modeController.disable()
|
||||
}
|
||||
} catch {
|
||||
disableRemoteCache(
|
||||
|
||||
@@ -396,7 +396,8 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
const char *output_arg_name = "-o";
|
||||
const char *serialize_diagnostics_arg_name = "--serialize-diagnostics";
|
||||
const char *language_mode_arg_name = "-x";
|
||||
const char *precompile_header_arg_value = "objective-c-header";
|
||||
const char *precompile_objc_header_arg_value = "objective-c-header";
|
||||
const char *precompile_c_header_arg_value = "c-header";
|
||||
const char *clang_cmd = "\(clangCommand)";
|
||||
const char *markerFile = "\(markerFilename)";
|
||||
const char *compilationHistoryFile = "\(compilationHistoryFilename)";
|
||||
@@ -464,7 +465,7 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
|
||||
clang_args[argc] = NULL;
|
||||
|
||||
// Verify mode. Even a target is cached, pch mode is not supported. Fallback to the local compilation
|
||||
if (language_mode != NULL && strcmp(language_mode, precompile_header_arg_value) == 0) {
|
||||
if (language_mode != NULL && (strcmp(language_mode, precompile_objc_header_arg_value) == 0 || strcmp(language_mode, precompile_c_header_arg_value) == 0)) {
|
||||
return execvp(clang_cmd, (char *const*) clang_args);
|
||||
}
|
||||
|
||||
|
||||
+43
-10
@@ -34,22 +34,33 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
private let mode: Mode
|
||||
private let repoRoot: URL
|
||||
private let fakeSrcRoot: URL
|
||||
private let sdksExclude: [String]
|
||||
|
||||
init(mode: Mode, repoRoot: URL, fakeSrcRoot: URL) {
|
||||
init(mode: Mode, repoRoot: URL, fakeSrcRoot: URL, sdksExclude: [String]) {
|
||||
self.mode = mode
|
||||
self.repoRoot = repoRoot
|
||||
self.fakeSrcRoot = fakeSrcRoot
|
||||
self.sdksExclude = sdksExclude
|
||||
}
|
||||
|
||||
func appendToBuildSettings(buildSettings: BuildSettings, wrappers: XCRCBinariesPaths) -> BuildSettings {
|
||||
var result = buildSettings
|
||||
result["SWIFT_EXEC"] = wrappers.swiftc.path
|
||||
result["SWIFT_USE_INTEGRATED_DRIVER"] = "NO"
|
||||
setBuildSetting(buildSettings: &result, key: "SWIFT_EXEC", value: wrappers.swiftc.path )
|
||||
setBuildSetting(buildSettings: &result, key: "SWIFT_USE_INTEGRATED_DRIVER", value: "NO" )
|
||||
// When generating artifacts, no need to shell-out all compilation commands to our wrappers
|
||||
if case .consumer = mode {
|
||||
result["CC"] = wrappers.cc.path
|
||||
result["LD"] = wrappers.ld.path
|
||||
result["LIBTOOL"] = wrappers.libtool.path
|
||||
setBuildSetting(buildSettings: &result, key: "CC", value: wrappers.cc.path )
|
||||
setBuildSetting(buildSettings: &result, key: "LD", value: wrappers.ld.path )
|
||||
// Setting LIBTOOL to '' breaks SwiftDriver intengration so resetting it to the original value
|
||||
// 'libtool' for all excluded configurations
|
||||
setBuildSetting(
|
||||
buildSettings: &result,
|
||||
key: "LIBTOOL",
|
||||
value: wrappers.libtool.path,
|
||||
excludedValue: "libtool"
|
||||
)
|
||||
setBuildSetting(buildSettings: &result, key: "LIPO", value: wrappers.lipo.path )
|
||||
setBuildSetting(buildSettings: &result, key: "LDPLUSPLUS", value: wrappers.ldplusplus.path )
|
||||
}
|
||||
|
||||
let existingSwiftFlags = result["OTHER_SWIFT_FLAGS"] as? String
|
||||
@@ -61,14 +72,36 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
|
||||
swiftFlags.assignFlag(key: "debug-prefix-map", value: "\(repoRoot.path)=$(XCRC_FAKE_SRCROOT)")
|
||||
clangFlags.assignFlag(key: "debug-prefix-map", value: "\(repoRoot.path)=$(XCRC_FAKE_SRCROOT)")
|
||||
|
||||
result["OTHER_SWIFT_FLAGS"] = swiftFlags.settingValue
|
||||
result["OTHER_CFLAGS"] = clangFlags.settingValue
|
||||
setBuildSetting(buildSettings: &result, key: "OTHER_SWIFT_FLAGS", value: swiftFlags.settingValue )
|
||||
setBuildSetting(buildSettings: &result, key: "OTHER_CFLAGS", value: clangFlags.settingValue )
|
||||
|
||||
result["XCRC_FAKE_SRCROOT"] = "\(fakeSrcRoot.path)"
|
||||
result["XCRC_PLATFORM_PREFERRED_ARCH"] =
|
||||
setBuildSetting(buildSettings: &result, key: "XCRC_FAKE_SRCROOT", value: "\(fakeSrcRoot.path)" )
|
||||
setBuildSetting(buildSettings: &result, key: "XCRC_PLATFORM_PREFERRED_ARCH", value:
|
||||
"""
|
||||
$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)
|
||||
"""
|
||||
)
|
||||
|
||||
explicitlyDisableSDKs(buildSettings: &result)
|
||||
return result
|
||||
}
|
||||
|
||||
private func setBuildSetting(buildSettings: inout BuildSettings, key: String, value: String?, excludedValue: String = "") {
|
||||
buildSettings[key] = value
|
||||
guard value != nil else {
|
||||
// no need to exclude as the value will
|
||||
return
|
||||
}
|
||||
// Erase all overrides for a given sdk so a default toolchain is used
|
||||
for skippedSDK in sdksExclude {
|
||||
buildSettings["\(key)[sdk=\(skippedSDK)]"] = excludedValue
|
||||
}
|
||||
}
|
||||
|
||||
// For all exlcuded SDKs passes XCRC_DISABLED=TRUE, which will cut-off early the pre_build phase
|
||||
private func explicitlyDisableSDKs(buildSettings: inout BuildSettings) {
|
||||
for skippedSDK in sdksExclude {
|
||||
buildSettings["XCRC_DISABLED[sdk=\(skippedSDK)]"] = "YES"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ extension IntegrateContext {
|
||||
cc: binariesDir.appendingPathComponent("xccc"),
|
||||
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
|
||||
libtool: binariesDir.appendingPathComponent("xclibtool"),
|
||||
lipo: binariesDir.appendingPathComponent("xclipo"),
|
||||
ld: binariesDir.appendingPathComponent("xcld"),
|
||||
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
|
||||
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
|
||||
|
||||
@@ -34,6 +34,7 @@ public class XCIntegrate {
|
||||
private let consumerEligiblePlatforms: String
|
||||
private let lldbMode: LLDBInitMode
|
||||
private let fakeSrcRoot: String
|
||||
private let sdksExclude: String
|
||||
private let output: String?
|
||||
|
||||
public init(
|
||||
@@ -48,6 +49,7 @@ public class XCIntegrate {
|
||||
consumerEligiblePlatforms: String,
|
||||
lldbMode: LLDBInitMode,
|
||||
fakeSrcRoot: String,
|
||||
sdksExclude: String,
|
||||
output: String?
|
||||
) {
|
||||
projectPath = input
|
||||
@@ -61,6 +63,7 @@ public class XCIntegrate {
|
||||
self.consumerEligiblePlatforms = consumerEligiblePlatforms
|
||||
self.lldbMode = lldbMode
|
||||
self.fakeSrcRoot = fakeSrcRoot
|
||||
self.sdksExclude = sdksExclude
|
||||
self.output = output
|
||||
}
|
||||
|
||||
@@ -98,7 +101,8 @@ public class XCIntegrate {
|
||||
let buildSettingsAppender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: context.mode,
|
||||
repoRoot: context.repoRoot,
|
||||
fakeSrcRoot: context.fakeSrcRoot
|
||||
fakeSrcRoot: context.fakeSrcRoot,
|
||||
sdksExclude: sdksExclude.integrateArrayArguments
|
||||
)
|
||||
let lldbPatcher: LLDBInitPatcher
|
||||
switch lldbMode {
|
||||
|
||||
@@ -25,6 +25,7 @@ struct XCRCBinariesPaths {
|
||||
let cc: URL
|
||||
let swiftc: URL
|
||||
let libtool: URL
|
||||
let lipo: URL
|
||||
let ld: URL
|
||||
let ldplusplus: URL
|
||||
let prebuild: URL
|
||||
|
||||
@@ -31,10 +31,12 @@ public struct PrepareMarkContext {
|
||||
let recommendedCacheAddress: URL
|
||||
/// All remote servers to mark
|
||||
let cacheAddresses: [URL]
|
||||
/// XCRemoteCache is explicitly disabled
|
||||
let disabled: Bool
|
||||
}
|
||||
|
||||
extension PrepareMarkContext {
|
||||
init(_ config: XCRemoteCacheConfig) throws {
|
||||
init(_ config: XCRemoteCacheConfig, env: [String: String]) throws {
|
||||
let sourceRoot = URL(fileURLWithPath: config.sourceRoot, isDirectory: true)
|
||||
repoRoot = URL(fileURLWithPath: config.repoRoot, relativeTo: sourceRoot)
|
||||
guard let address = URL(string: config.recommendedCacheAddress) else {
|
||||
@@ -43,5 +45,6 @@ extension PrepareMarkContext {
|
||||
}
|
||||
recommendedCacheAddress = address
|
||||
cacheAddresses = try config.cacheAddresses.map(URL.build)
|
||||
disabled = try env.readEnv(key: "XCRC_DISABLED") ?? false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ public class XCPrepare {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.downloadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
|
||||
@@ -47,12 +47,17 @@ public class XCPrepareMark {
|
||||
let xcodeVersion: String
|
||||
do {
|
||||
config = try XCRemoteCacheConfigReader(env: env, fileReader: fileManager).readConfiguration()
|
||||
context = try PrepareMarkContext(config)
|
||||
context = try PrepareMarkContext(config, env: env)
|
||||
xcodeVersion = try xcode ?? XcodeProbeImpl(shell: shellGetStdout).read().buildVersion
|
||||
} catch {
|
||||
exit(1, "FATAL: Prepare initialization failed with error: \(error)")
|
||||
}
|
||||
|
||||
guard !context.disabled else {
|
||||
infoLog("XCRemoteCache explicitly disabled for marking.")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let sessionFactory = DefaultURLSessionFactory(config: config)
|
||||
var awsV4Signature: AWSV4Signature?
|
||||
@@ -69,6 +74,7 @@ public class XCPrepareMark {
|
||||
let networkClient = NetworkClientImpl(
|
||||
session: sessionFactory.build(),
|
||||
retries: config.uploadRetries,
|
||||
retryDelay: config.retryDelay,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: awsV4Signature
|
||||
)
|
||||
@@ -76,6 +82,7 @@ public class XCPrepareMark {
|
||||
mode: .producer,
|
||||
downloadStreamURL: context.recommendedCacheAddress,
|
||||
upstreamStreamURL: context.cacheAddresses,
|
||||
uploadBatchSize: config.uploadBatchSize,
|
||||
networkClient: networkClient
|
||||
) { [configuration, platform] cacheAddress in
|
||||
// Prepare URLs don't include target name or envFingperint, which are valid only for a target level
|
||||
|
||||
@@ -78,7 +78,12 @@ public class XCCreateBinary {
|
||||
}
|
||||
let markerURL = tempDir.appendingPathComponent(config.modeMarkerPath)
|
||||
do {
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: tempDir, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: tempDir,
|
||||
// Creation binary doesn't call artifact preprocessing
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
let dependenciesWriter = FileDatWriter(dependencyInfo, fileManager: fileManager)
|
||||
let markerReader = FileMarkerReader(markerURL, fileManager: fileManager)
|
||||
guard fileManager.fileExists(atPath: markerURL.path) else {
|
||||
|
||||
@@ -55,7 +55,7 @@ class MirroredLinkingSwiftcProductsGenerator: SwiftcProductsGenerator {
|
||||
func generateFrom(
|
||||
artifactSwiftModuleFiles: [SwiftmoduleFileExtension: URL],
|
||||
artifactSwiftModuleObjCFile: URL
|
||||
) throws -> URL {
|
||||
) throws -> SwiftcProductsGeneratorOutput {
|
||||
/// Predict moduleName from the `*.swiftmodule` artifact
|
||||
let foundSwiftmoduleFile = artifactSwiftModuleFiles[.swiftmodule]
|
||||
guard let mainSwiftmoduleFile = foundSwiftmoduleFile else {
|
||||
|
||||
@@ -26,14 +26,19 @@ enum DiskSwiftcProductsGeneratorError: Error {
|
||||
case unknownSwiftmoduleFile
|
||||
}
|
||||
|
||||
struct SwiftcProductsGeneratorOutput {
|
||||
let swiftmoduleDir: URL
|
||||
let objcHeaderFile: URL
|
||||
}
|
||||
|
||||
/// Generates swiftc product to the expected location
|
||||
protocol SwiftcProductsGenerator {
|
||||
/// Generates products from given files
|
||||
/// - Returns: location dir where .swiftmodule files have been placed
|
||||
/// - Returns: location dir where .swiftmodule and ObjC header files have been placed
|
||||
func generateFrom(
|
||||
artifactSwiftModuleFiles: [SwiftmoduleFileExtension: URL],
|
||||
artifactSwiftModuleObjCFile: URL
|
||||
) throws -> URL
|
||||
) throws -> SwiftcProductsGeneratorOutput
|
||||
}
|
||||
|
||||
/// Generator that produces all products in the locations where Xcode expects it, using provided disk copier
|
||||
@@ -64,7 +69,7 @@ class DiskSwiftcProductsGenerator: SwiftcProductsGenerator {
|
||||
func generateFrom(
|
||||
artifactSwiftModuleFiles sourceAtifactSwiftModuleFiles: [SwiftmoduleFileExtension: URL],
|
||||
artifactSwiftModuleObjCFile: URL
|
||||
) throws -> URL {
|
||||
) throws -> SwiftcProductsGeneratorOutput {
|
||||
// Move cached -Swift.h file to the expected location
|
||||
try diskCopier.copy(file: artifactSwiftModuleObjCFile, destination: objcHeaderOutput)
|
||||
for (ext, url) in sourceAtifactSwiftModuleFiles {
|
||||
@@ -85,6 +90,9 @@ class DiskSwiftcProductsGenerator: SwiftcProductsGenerator {
|
||||
}
|
||||
|
||||
// Build parent dir of the .swiftmodule file that contains a module
|
||||
return modulePathOutput.deletingLastPathComponent()
|
||||
return SwiftcProductsGeneratorOutput(
|
||||
swiftmoduleDir: modulePathOutput.deletingLastPathComponent(),
|
||||
objcHeaderFile: objcHeaderOutput
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,12 @@ public class XCSwiftc {
|
||||
|
||||
let inputReader = SwiftcFilemapInputEditor(context.filemap, fileManager: fileManager)
|
||||
let fileListEditor = FileListEditor(context.fileList, fileManager: fileManager)
|
||||
let artifactOrganizer = ZipArtifactOrganizer(targetTempDir: context.tempDir, fileManager: fileManager)
|
||||
let artifactOrganizer = ZipArtifactOrganizer(
|
||||
targetTempDir: context.tempDir,
|
||||
// xcswiftc doesn't call artifact preprocessing
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
// TODO: check for allowedFile comparing a list of all inputfiles, not dependencies from a marker
|
||||
let makerReferencedFilesListScanner = FileListScannerImpl(markerReader, caseSensitive: false)
|
||||
let allowedFilesListScanner = ExceptionsFilteredFileListScanner(
|
||||
|
||||
@@ -83,6 +83,10 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
var downloadRetries: Int = 0
|
||||
/// Number of retries for upload requests
|
||||
var uploadRetries: Int = 3
|
||||
/// Delay between retries in seconds
|
||||
var retryDelay: Double = 10.0
|
||||
/// Maximum number of simultaneous requests. 0 means no limits
|
||||
var uploadBatchSize: Int = 0
|
||||
/// Extra headers appended to all remote HTTP(S) requests
|
||||
var requestCustomHeaders: [String: String] = [:]
|
||||
/// Filename (without an extension) of the compilation input file that is used
|
||||
@@ -107,7 +111,8 @@ public struct XCRemoteCacheConfig: Encodable {
|
||||
var turnOffRemoteCacheOnFirstTimeout: Bool = false
|
||||
/// 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
|
||||
var productFilesExtensionsWithContentOverride = ["swiftmodule"]
|
||||
/// .h files may contain absolute paths if NS_ENUM is used in a public API from Swift code
|
||||
var productFilesExtensionsWithContentOverride = ["swiftmodule", "h"]
|
||||
/// If true, plugins for thinning support should be enabled
|
||||
var thinningEnabled: Bool = false
|
||||
/// Module name of a target that works as a helper for thinned targets
|
||||
@@ -175,6 +180,8 @@ extension XCRemoteCacheConfig {
|
||||
merge.statsDir = scheme.statsDir ?? statsDir
|
||||
merge.downloadRetries = scheme.downloadRetries ?? downloadRetries
|
||||
merge.uploadRetries = scheme.uploadRetries ?? uploadRetries
|
||||
merge.retryDelay = scheme.retryDelay ?? retryDelay
|
||||
merge.uploadBatchSize = scheme.uploadBatchSize ?? uploadBatchSize
|
||||
merge.requestCustomHeaders = scheme.requestCustomHeaders ?? requestCustomHeaders
|
||||
merge.thinTargetMockFilename = scheme.thinTargetMockFilename ?? thinTargetMockFilename
|
||||
merge.focusedTargets = scheme.focusedTargets ?? focusedTargets
|
||||
@@ -243,6 +250,8 @@ struct ConfigFileScheme: Decodable {
|
||||
let statsDir: String?
|
||||
let downloadRetries: Int?
|
||||
let uploadRetries: Int?
|
||||
let retryDelay: Double?
|
||||
let uploadBatchSize: Int?
|
||||
let requestCustomHeaders: [String: String]?
|
||||
let thinTargetMockFilename: String?
|
||||
let focusedTargets: [String]?
|
||||
@@ -291,6 +300,8 @@ struct ConfigFileScheme: Decodable {
|
||||
case statsDir = "stats_dir"
|
||||
case downloadRetries = "download_retries"
|
||||
case uploadRetries = "upload_retries"
|
||||
case retryDelay = "retry_delay"
|
||||
case uploadBatchSize = "upload_batch_size"
|
||||
case requestCustomHeaders = "request_custom_headers"
|
||||
case thinTargetMockFilename = "thin_target_mock_filename"
|
||||
case focusedTargets = "focused_targets"
|
||||
@@ -357,6 +368,7 @@ class XCRemoteCacheConfigReader {
|
||||
extraConfURL = URL(fileURLWithPath: config.extraConfigurationFile, relativeTo: rootURL)
|
||||
} catch {
|
||||
infoLog("Extra config override failed with \(error). Skipping extra configuration")
|
||||
// swiftlint:disable:next unneeded_break_in_switch
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ protocol FingerprintSyncer {
|
||||
func decorate(sourceDir: URL, fingerprint: String) throws
|
||||
/// Deletes fingerprint overrides in the dir (if already created)
|
||||
func delete(sourceDir: URL) throws
|
||||
/// Sets a fingerprint override for a singe file placed
|
||||
func decorate(file: URL, fingerprint: String) throws
|
||||
/// Deletes fingerprint override for a file (if already created)
|
||||
func delete(file: URL) throws
|
||||
}
|
||||
|
||||
class FileFingerprintSyncer: FingerprintSyncer {
|
||||
@@ -78,4 +82,25 @@ class FileFingerprintSyncer: FingerprintSyncer {
|
||||
try dirAccessor.removeItem(atPath: file.path)
|
||||
}
|
||||
}
|
||||
|
||||
func decorate(file: URL, fingerprint: String) throws {
|
||||
guard let fingerprintData = fingerprint.data(using: .utf8) else {
|
||||
throw FingerprintSyncerError.invalidFingerprint
|
||||
}
|
||||
let fingerprintFile = file.appendingPathExtension(fingerprintExtension)
|
||||
try dirAccessor.write(toPath: fingerprintFile.path, contents: fingerprintData)
|
||||
}
|
||||
|
||||
func delete(file: URL) throws {
|
||||
guard case .file = try dirAccessor.itemType(atPath: file.path) else {
|
||||
// no file to decorate (no module was generated)
|
||||
return
|
||||
}
|
||||
let overrideURL = file.appendingPathExtension(fingerprintExtension)
|
||||
guard case .file = try dirAccessor.itemType(atPath: overrideURL.path) else {
|
||||
// no override
|
||||
return
|
||||
}
|
||||
try dirAccessor.removeItem(atPath: overrideURL.path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,13 @@ protocol DirScanner {
|
||||
|
||||
/// Returns all items in a directory (shallow search)
|
||||
/// - Parameter at: url of an existing directory to search
|
||||
/// - Throws: an error if dir doesn't exist or I/O error
|
||||
/// - Throws: an error if a dir doesn't exist or on I/O error
|
||||
func items(at dir: URL) throws -> [URL]
|
||||
|
||||
/// Returns all items in a directory (recursive search)
|
||||
/// - Parameter at: url of an existing directory to search
|
||||
/// - Throws: an error if a dir doesn't exist or on I/O error
|
||||
func recursiveItems(at dir: URL) throws -> [URL]
|
||||
}
|
||||
|
||||
typealias DirAccessor = FileAccessor & DirScanner
|
||||
@@ -54,4 +59,18 @@ extension FileManager: DirScanner {
|
||||
let resolvedDir = dir.resolvingSymlinksInPath()
|
||||
return try contentsOfDirectory(at: resolvedDir, includingPropertiesForKeys: nil, options: [])
|
||||
}
|
||||
|
||||
func recursiveItems(at dir: URL) throws -> [URL] {
|
||||
// Iterating DFS
|
||||
var queue: [URL] = [dir]
|
||||
var results: [URL] = []
|
||||
while let item = queue.popLast() {
|
||||
if try itemType(atPath: item.path) == .dir {
|
||||
try queue.append(contentsOf: items(at: item))
|
||||
} else {
|
||||
results.append(item)
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,4 +52,8 @@ class DirAccessorComposer: DirAccessor {
|
||||
func items(at dir: URL) throws -> [URL] {
|
||||
try dirScanner.items(at: dir)
|
||||
}
|
||||
|
||||
func recursiveItems(at dir: URL) throws -> [URL] {
|
||||
try dirScanner.recursiveItems(at: dir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,15 @@ struct CanonicalRequest {
|
||||
if url.path.isEmpty {
|
||||
path = "/"
|
||||
} else {
|
||||
path = url.path
|
||||
if let escapedPath = url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
|
||||
path = escapedPath
|
||||
} else {
|
||||
path = "/"
|
||||
printWarning("""
|
||||
Escaping the path \(url.path) failed and a placeholder is used instead. \
|
||||
Make sure the path doesn't contain invalid characters.
|
||||
""")
|
||||
}
|
||||
}
|
||||
return
|
||||
"\(httpMethod)\n" +
|
||||
|
||||
@@ -29,12 +29,14 @@ class NetworkClientImpl: NetworkClient {
|
||||
private let session: URLSession
|
||||
private let fileManager: FileManager
|
||||
private let maxRetries: Int
|
||||
private let retryDelay: TimeInterval
|
||||
private let awsV4Signature: AWSV4Signature?
|
||||
|
||||
init(session: URLSession, retries: Int, fileManager: FileManager, awsV4Signature: AWSV4Signature?) {
|
||||
init(session: URLSession, retries: Int, retryDelay: TimeInterval, fileManager: FileManager, awsV4Signature: AWSV4Signature?) {
|
||||
self.session = session
|
||||
self.fileManager = fileManager
|
||||
maxRetries = retries
|
||||
self.maxRetries = retries
|
||||
self.retryDelay = retryDelay
|
||||
self.awsV4Signature = awsV4Signature
|
||||
}
|
||||
|
||||
@@ -75,7 +77,12 @@ class NetworkClientImpl: NetworkClient {
|
||||
func download(_ url: URL, to location: URL, completion: @escaping (Result<Void, NetworkClientError>) -> Void) {
|
||||
var request = URLRequest(url: url)
|
||||
setupAuthenticationSignatureIfPresent(&request)
|
||||
makeDownloadRequest(request, output: location, completion: completion)
|
||||
makeDownloadRequest(
|
||||
request,
|
||||
output: location,
|
||||
retries: maxRetries,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
func upload(_ file: URL, as url: URL, completion: @escaping (Result<Void, NetworkClientError>) -> Void) {
|
||||
@@ -123,7 +130,7 @@ class NetworkClientImpl: NetworkClient {
|
||||
dataTask.resume()
|
||||
}
|
||||
|
||||
private func makeDownloadRequest(_ request: URLRequest, output: URL, completion: @escaping (Result<Void, NetworkClientError>) -> Void) {
|
||||
private func makeDownloadRequest(_ request: URLRequest, output: URL, retries: Int, completion: @escaping (Result<Void, NetworkClientError>) -> Void) {
|
||||
guard fileManager.fileExists(atPath: output.path) == false else {
|
||||
infoLog("Download file found in the destination, skipping download.")
|
||||
completion(.success(()))
|
||||
@@ -133,6 +140,17 @@ class NetworkClientImpl: NetworkClient {
|
||||
let dataTask = session.downloadTask(with: request) { [fileManager] fileURL, _, error in
|
||||
guard let fileURL = fileURL else {
|
||||
let networkError = error.map(NetworkClientError.build) ?? .inconsistentSession
|
||||
if retries > 0 {
|
||||
infoLog("Download request failed with \(networkError). Left retries: \(retries).")
|
||||
self.retryDownload(
|
||||
request,
|
||||
output: output,
|
||||
retries: retries,
|
||||
completion: completion,
|
||||
after: self.retryDelay
|
||||
)
|
||||
return
|
||||
}
|
||||
errorLog("Download request failed: \(networkError)")
|
||||
completion(.failure(networkError))
|
||||
return
|
||||
@@ -173,7 +191,13 @@ class NetworkClientImpl: NetworkClient {
|
||||
if let error = responseError {
|
||||
if retries > 0 {
|
||||
infoLog("Upload request failed with \(error). Left retries: \(retries).")
|
||||
self.makeUploadRequest(request, input: input, retries: retries - 1, completion: completion)
|
||||
self.retryUpload(
|
||||
request,
|
||||
input: input,
|
||||
retries: retries,
|
||||
completion: completion,
|
||||
after: self.retryDelay
|
||||
)
|
||||
return
|
||||
}
|
||||
errorLog("Upload request failed: \(error)")
|
||||
@@ -184,6 +208,30 @@ class NetworkClientImpl: NetworkClient {
|
||||
}
|
||||
dataTask.resume()
|
||||
}
|
||||
|
||||
private func retryUpload(_ request: URLRequest, input: URL, retries: Int, completion: @escaping (Result<Void, NetworkClientError>) -> Void, after: TimeInterval) {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + after) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.makeUploadRequest(
|
||||
request,
|
||||
input: input,
|
||||
retries: retries - 1,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func retryDownload(_ request: URLRequest, output: URL, retries: Int, completion: @escaping (Result<Void, NetworkClientError>) -> Void, after: TimeInterval) {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + after) { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.makeDownloadRequest(
|
||||
request,
|
||||
output: output,
|
||||
retries: retries - 1,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension NetworkClientError {
|
||||
|
||||
@@ -27,11 +27,20 @@ class RemoteNetworkClientAbstractFactory {
|
||||
private let upstreamStreamURL: [URL]
|
||||
private let networkClient: NetworkClient
|
||||
private let urlBuilderFactory: (URL) throws -> URLBuilder
|
||||
private let uploadBatchSize: Int
|
||||
|
||||
init(mode: Mode, downloadStreamURL: URL, upstreamStreamURL: [URL], networkClient: NetworkClient, urlBuilderFactory: @escaping (URL) throws -> URLBuilder) {
|
||||
init(
|
||||
mode: Mode,
|
||||
downloadStreamURL: URL,
|
||||
upstreamStreamURL: [URL],
|
||||
uploadBatchSize: Int,
|
||||
networkClient: NetworkClient,
|
||||
urlBuilderFactory: @escaping (URL) throws -> URLBuilder
|
||||
) {
|
||||
self.mode = mode
|
||||
self.downloadStreamURL = downloadStreamURL
|
||||
self.upstreamStreamURL = upstreamStreamURL
|
||||
self.uploadBatchSize = uploadBatchSize
|
||||
self.networkClient = networkClient
|
||||
self.urlBuilderFactory = urlBuilderFactory
|
||||
}
|
||||
@@ -49,7 +58,8 @@ class RemoteNetworkClientAbstractFactory {
|
||||
return ReplicatedRemotesNetworkClient(
|
||||
networkClient,
|
||||
download: downloadURLBuilder,
|
||||
uploads: upstreamBuilders
|
||||
uploads: upstreamBuilders,
|
||||
uploadBatchSize: uploadBatchSize
|
||||
)
|
||||
case .consumer:
|
||||
return RemoteNetworkClientImpl(networkClient, downloadURLBuilder)
|
||||
|
||||
@@ -23,10 +23,12 @@ import Foundation
|
||||
class ReplicatedRemotesNetworkClient: RemoteNetworkClientImpl {
|
||||
private let networkClient: NetworkClient
|
||||
private let uploadURLBuilders: [URLBuilder]
|
||||
private let uploadBatchSize: Int
|
||||
|
||||
init(_ networkClient: NetworkClient, download: URLBuilder, uploads uploadURLBuilders: [URLBuilder]) {
|
||||
init(_ networkClient: NetworkClient, download: URLBuilder, uploads uploadURLBuilders: [URLBuilder], uploadBatchSize: Int) {
|
||||
self.networkClient = networkClient
|
||||
self.uploadURLBuilders = uploadURLBuilders
|
||||
self.uploadBatchSize = uploadBatchSize
|
||||
super.init(networkClient, download)
|
||||
}
|
||||
|
||||
@@ -39,6 +41,9 @@ class ReplicatedRemotesNetworkClient: RemoteNetworkClientImpl {
|
||||
let group = DispatchGroup()
|
||||
var results: [Result<Void, NetworkClientError>] = Array(repeating: .failure(.noResponse), count: urls.count)
|
||||
urls.enumerated().forEach { index, url in
|
||||
if uploadBatchSize > 0 && index > 0 && index % uploadBatchSize == 0 {
|
||||
group.wait()
|
||||
}
|
||||
group.enter()
|
||||
networkClient.upload(file, as: url) { receivedResult in
|
||||
results[index] = receivedResult
|
||||
@@ -58,6 +63,9 @@ class ReplicatedRemotesNetworkClient: RemoteNetworkClientImpl {
|
||||
let group = DispatchGroup()
|
||||
var results: [Result<Void, NetworkClientError>] = Array(repeating: .failure(.noResponse), count: urls.count)
|
||||
urls.enumerated().forEach { index, url in
|
||||
if uploadBatchSize > 0 && index > 0 && index % uploadBatchSize == 0 {
|
||||
group.wait()
|
||||
}
|
||||
group.enter()
|
||||
networkClient.create(url) { receivedResult in
|
||||
results[index] = receivedResult
|
||||
|
||||
@@ -55,4 +55,11 @@ extension Dictionary where Key == String, Value == String {
|
||||
}
|
||||
return value == "YES"
|
||||
}
|
||||
|
||||
func readEnv(key: String) throws -> Bool? {
|
||||
guard let value = self[key] else {
|
||||
return nil
|
||||
}
|
||||
return value == "YES"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,53 +18,24 @@
|
||||
// under the License.
|
||||
|
||||
import Foundation
|
||||
import xclibtoolSupport
|
||||
import XCRemoteCache
|
||||
|
||||
public enum XCLibtoolMainError: Error {
|
||||
case missingOutput
|
||||
case unsupportedMode
|
||||
}
|
||||
|
||||
/// Wrapper for a `libtool` program that copies the build executable (e.g. .a) from a cached-downloaded location
|
||||
/// Fallbacks to a standard `libtool` when the Ramote cache is not applicable (e.g. modified sources)
|
||||
public class XCLibtoolMain {
|
||||
public init() { }
|
||||
|
||||
public func main() {
|
||||
let args = ProcessInfo().arguments
|
||||
var output: String?
|
||||
// all input arguments library '.a'. Used to create an universal binary
|
||||
var inputLibraries: [String] = []
|
||||
var filelist: String?
|
||||
var dependencyInfo: String?
|
||||
var i = 0
|
||||
while i < args.count {
|
||||
switch args[i] {
|
||||
case "-o":
|
||||
output = args[i + 1]
|
||||
i += 1
|
||||
case "-filelist":
|
||||
filelist = args[i + 1]
|
||||
i += 1
|
||||
case "-dependency_info":
|
||||
dependencyInfo = args[i + 1]
|
||||
i += 1
|
||||
case let input where input.hasSuffix(".a"):
|
||||
inputLibraries.append(input)
|
||||
default:
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
guard let outputInput = output else {
|
||||
exit(1, "Missing 'output' argument. Args: \(args)")
|
||||
}
|
||||
|
||||
let mode: XCLibtoolMode
|
||||
if let filelistInput = filelist, let dependencyInfoInput = dependencyInfo {
|
||||
// libtool is creating a library
|
||||
mode = .createLibrary(output: outputInput, filelist: filelistInput, dependencyInfo: dependencyInfoInput)
|
||||
} else if !inputLibraries.isEmpty {
|
||||
// multiple input libraries suggest creating an universal binary
|
||||
mode = .createUniversalBinary(output: outputInput, inputs: inputLibraries)
|
||||
} else {
|
||||
// unknown mode
|
||||
exit(1, "Unsupported mode. Args: \(args)")
|
||||
}
|
||||
do {
|
||||
let mode = try XCLibtoolHelper.buildMode(args: args)
|
||||
try XCLibtool(mode).run()
|
||||
} catch {
|
||||
exit(1, "Failed with: \(error). Args: \(args)")
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2023 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
|
||||
import XCRemoteCache
|
||||
|
||||
public enum XCLibtoolHelperError: Error {
|
||||
case missingOutput
|
||||
case unsupportedMode
|
||||
}
|
||||
|
||||
public class XCLibtoolHelper {
|
||||
public static func buildMode(args: [String]) throws -> XCLibtoolMode {
|
||||
var output: String?
|
||||
// all input arguments are '*.a' or no path extension. Used to create an universal binary
|
||||
var inputLibraries: [String] = []
|
||||
var filelist: String?
|
||||
var dependencyInfo: String?
|
||||
var i = 0
|
||||
while i < args.count {
|
||||
switch args[i] {
|
||||
case "-o":
|
||||
output = args[i + 1]
|
||||
i += 1
|
||||
case "-filelist":
|
||||
filelist = args[i + 1]
|
||||
i += 1
|
||||
case "-dependency_info":
|
||||
dependencyInfo = args[i + 1]
|
||||
i += 1
|
||||
case let input where ["", "a"].contains(URL(string: args[i])?.pathExtension):
|
||||
// Support for static frameworks (no extension) and static libraries (.a)
|
||||
inputLibraries.append(input)
|
||||
default:
|
||||
break
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
guard let outputInput = output else {
|
||||
throw XCLibtoolHelperError.missingOutput
|
||||
}
|
||||
|
||||
let mode: XCLibtoolMode
|
||||
if let filelistInput = filelist, let dependencyInfoInput = dependencyInfo {
|
||||
// libtool is creating a library
|
||||
mode = .createLibrary(output: outputInput, filelist: filelistInput, dependencyInfo: dependencyInfoInput)
|
||||
} else if !inputLibraries.isEmpty {
|
||||
// multiple input libraries suggest creating an universal binary
|
||||
mode = .createUniversalBinary(output: outputInput, inputs: inputLibraries)
|
||||
} else {
|
||||
// unknown mode
|
||||
throw XCLibtoolHelperError.unsupportedMode
|
||||
}
|
||||
return mode
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2023 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
|
||||
import XCRemoteCache
|
||||
|
||||
/// Wrapper for a `lipo` program that links any of input binaries to the destination paths
|
||||
/// Fallbacks to a standard `lipo` when the Ramote cache is not applicable (e.g. modified sources)
|
||||
public class XCLipoMain {
|
||||
public init() { }
|
||||
|
||||
public func main() {
|
||||
let args = ProcessInfo().arguments
|
||||
var output: String?
|
||||
var create = false
|
||||
var inputs: [String] = []
|
||||
|
||||
var i = 1
|
||||
while i < args.count {
|
||||
switch args[i] {
|
||||
case "-output":
|
||||
output = args[i + 1]
|
||||
i += 1
|
||||
case "-create":
|
||||
create = true
|
||||
default:
|
||||
inputs.append(args[i])
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
let lipoCommand = "lipo"
|
||||
guard let output = output, !inputs.isEmpty, create else {
|
||||
print("Fallbacking to compilation using \(lipoCommand).")
|
||||
|
||||
let args = ProcessInfo().arguments
|
||||
let paramList = [lipoCommand] + args.dropFirst()
|
||||
let cargs = paramList.map { strdup($0) } + [nil]
|
||||
execvp(lipoCommand, cargs)
|
||||
|
||||
/// C-function `execv` returns only when the command fails
|
||||
exit(1)
|
||||
}
|
||||
|
||||
do {
|
||||
try XCLipo(
|
||||
output: output,
|
||||
inputs: inputs,
|
||||
fallbackCommand: lipoCommand,
|
||||
stepDescription: "xclipo"
|
||||
).run()
|
||||
} catch {
|
||||
exit(1, "Failed with: \(error). Args: \(args)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2023 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 XCRemoteCache
|
||||
|
||||
XCLipoMain().main()
|
||||
@@ -229,6 +229,11 @@ struct XCPrepareMain: ParsableCommand {
|
||||
)
|
||||
var fakeSrcRoot: String
|
||||
|
||||
@Option(name: .customLong("sdks-exclude"), default: "", help: """
|
||||
comma separated list of sdks to not integrate XCRemoteCache (e.g. "watchos*, watchsimulator*")
|
||||
""", transform: nonEmptyString)
|
||||
var sdksExclude: String
|
||||
|
||||
|
||||
func run() throws {
|
||||
XCIntegrate(
|
||||
@@ -243,6 +248,7 @@ struct XCPrepareMain: ParsableCommand {
|
||||
consumerEligiblePlatforms: consumerEligiblePlatforms,
|
||||
lldbMode: lldbInit,
|
||||
fakeSrcRoot: fakeSrcRoot,
|
||||
sdksExclude: sdksExclude,
|
||||
output: output
|
||||
).main()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
private var swiftmoduleDocFile: URL!
|
||||
private var swiftmoduleSourceInfoFile: URL!
|
||||
private var swiftmoduleInterfaceFile: URL!
|
||||
private var privateSwiftmoduleInterfaceFile: URL!
|
||||
private var abiJsonFile: URL!
|
||||
private var workingDir: URL!
|
||||
private var builder: ArtifactSwiftProductsBuilderImpl!
|
||||
|
||||
@@ -39,6 +41,9 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
swiftmoduleDocFile = moduleDir.appendingPathComponent("MyModule.swiftdoc")
|
||||
swiftmoduleSourceInfoFile = moduleDir.appendingPathComponent("MyModule.swiftsourceinfo")
|
||||
swiftmoduleInterfaceFile = moduleDir.appendingPathComponent("MyModule.swiftinterface")
|
||||
privateSwiftmoduleInterfaceFile = moduleDir.appendingPathComponent("MyModule.private.swiftinterface")
|
||||
abiJsonFile = moduleDir.appendingPathComponent("MyModule.abi.json")
|
||||
|
||||
workingDir = rootDir.appendingPathComponent("working")
|
||||
builder = ArtifactSwiftProductsBuilderImpl(
|
||||
workingDir: workingDir,
|
||||
@@ -98,6 +103,8 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleDocFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleSourceInfoFile)
|
||||
try fileManager.spt_createEmptyFile(swiftmoduleInterfaceFile)
|
||||
try fileManager.spt_createEmptyFile(privateSwiftmoduleInterfaceFile)
|
||||
try fileManager.spt_createEmptyFile(abiJsonFile)
|
||||
let builderSwiftmoduleDir =
|
||||
builder
|
||||
.buildingArtifactSwiftModulesLocation()
|
||||
@@ -110,6 +117,10 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftsourceinfo")
|
||||
let expectedBuildedSwiftInterfaceFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftinterface")
|
||||
let expectedPrivateSwiftmoduleInterfaceFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.private.swiftinterface")
|
||||
let expectedAbiJsonFile =
|
||||
builderSwiftmoduleDir.appendingPathComponent("MyModule.abi.json")
|
||||
|
||||
try builder.includeModuleDefinitionsToTheArtifact(arch: "arm64", moduleURL: swiftmoduleFile)
|
||||
|
||||
@@ -117,6 +128,8 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftmoduledocFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftSourceInfoFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedBuildedSwiftInterfaceFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedPrivateSwiftmoduleInterfaceFile.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: expectedAbiJsonFile.path))
|
||||
}
|
||||
|
||||
func testFailsIncludingWhenMissingRequiredSwiftmoduleFiles() throws {
|
||||
|
||||
@@ -32,6 +32,8 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
private var swiftdocURL: URL!
|
||||
private var swiftSourceInfoURL: URL!
|
||||
private var swiftInterfaceURL: URL!
|
||||
private var privateSwiftInterfaceURL: URL!
|
||||
private var abiJsonURL: URL!
|
||||
private var executablePath: String!
|
||||
private var executableURL: URL!
|
||||
private var creator: BuildArtifactCreator!
|
||||
@@ -53,11 +55,16 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
.appendingPathComponent("Target.swiftsourceinfo")
|
||||
swiftInterfaceURL = workDirectory.appendingPathComponent("Objects-normal")
|
||||
.appendingPathComponent("Target.swiftinterface")
|
||||
privateSwiftInterfaceURL = workDirectory.appendingPathComponent("Objects-normal")
|
||||
.appendingPathComponent("Target.private.swiftinterface")
|
||||
abiJsonURL = workDirectory.appendingPathComponent("Objects-normal")
|
||||
.appendingPathComponent("Target.abi.json")
|
||||
executablePath = "libTarget.a"
|
||||
executableURL = buildDir.appendingPathComponent(executablePath)
|
||||
dSYM = executableURL.deletingPathExtension().appendingPathExtension(".dSYM")
|
||||
try fileManager.spt_createEmptyFile(executableURL)
|
||||
try fileManager.spt_createEmptyFile(headerURL)
|
||||
let artifactProcessor = NoopArtifactProcessor()
|
||||
|
||||
creator = BuildArtifactCreator(
|
||||
buildDir: buildDir,
|
||||
@@ -67,6 +74,7 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
modulesFolderPath: "",
|
||||
dSYMPath: dSYM,
|
||||
metaWriter: JsonMetaWriter(fileWriter: fileManager, pretty: false),
|
||||
artifactProcessor: artifactProcessor,
|
||||
fileManager: fileManager
|
||||
)
|
||||
}
|
||||
@@ -122,6 +130,8 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
try fileManager.spt_createEmptyFile(swiftdocURL)
|
||||
try fileManager.spt_createEmptyFile(swiftSourceInfoURL)
|
||||
try fileManager.spt_createEmptyFile(swiftInterfaceURL)
|
||||
try fileManager.spt_createEmptyFile(privateSwiftInterfaceURL)
|
||||
try fileManager.spt_createEmptyFile(abiJsonURL)
|
||||
|
||||
try creator.includeModuleDefinitionsToTheArtifact(arch: "arch", moduleURL: swiftmoduleURL)
|
||||
let artifact = try creator.createArtifact(artifactKey: "key", meta: sampleMeta)
|
||||
@@ -136,6 +146,8 @@ class BuildArtifactCreatorTests: FileXCTestCase {
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftdoc"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftsourceinfo"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.swiftinterface"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.private.swiftinterface"),
|
||||
unzippedURL.appendingPathComponent("swiftmodule/arch/Target.abi.json"),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2022 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 TextFileDependenciesRemapperTests: FileXCTestCase {
|
||||
|
||||
let stringsRemapper = StringDependenciesRemapper(
|
||||
mappings: [
|
||||
.init(generic: "$(SRCROOT)", local: "/example"),
|
||||
])
|
||||
let fileAccessor = FileAccessorFake(mode: .strict)
|
||||
var remapper: TextFileDependenciesRemapper!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
remapper = TextFileDependenciesRemapper(
|
||||
remapper: stringsRemapper,
|
||||
fileAccessor: fileAccessor
|
||||
)
|
||||
}
|
||||
|
||||
func testRemapsGenericPlaceholders() throws {
|
||||
try fileAccessor.write(toPath: "/file", contents: "Some $(SRCROOT).")
|
||||
|
||||
try remapper.remap(fromGeneric: "/file")
|
||||
|
||||
try XCTAssertEqual(fileAccessor.contents(atPath: "/file"), "Some /example.")
|
||||
}
|
||||
|
||||
func testRemapsLocalPathsToPlaceholders() throws {
|
||||
try fileAccessor.write(toPath: "/file", contents: "Some /example.")
|
||||
|
||||
try remapper.remap(fromLocal: "/file")
|
||||
|
||||
try XCTAssertEqual(fileAccessor.contents(atPath: "/file"), "Some $(SRCROOT).")
|
||||
}
|
||||
|
||||
func testPersistsEmptyLines() throws {
|
||||
let multilineData = """
|
||||
Line1
|
||||
|
||||
Line 2
|
||||
""".data(using: .utf8)
|
||||
try fileAccessor.write(toPath: "/file", contents: multilineData)
|
||||
|
||||
try remapper.remap(fromGeneric: "/file")
|
||||
|
||||
try XCTAssertEqual(fileAccessor.contents(atPath: "/file"), multilineData)
|
||||
}
|
||||
|
||||
func testPersistsEmptyLineAtTheEnd() throws {
|
||||
// swiftlint:disable trailing_whitespace
|
||||
let multilineData = """
|
||||
Line1
|
||||
|
||||
Line 2
|
||||
|
||||
""".data(using: .utf8)
|
||||
// swiftlint:enable trailing_whitespace
|
||||
try fileAccessor.write(toPath: "/file", contents: multilineData)
|
||||
|
||||
try remapper.remap(fromGeneric: "/file")
|
||||
|
||||
try XCTAssertEqual(fileAccessor.contents(atPath: "/file"), multilineData)
|
||||
}
|
||||
|
||||
func testReplacesMultipletimesInLine() throws {
|
||||
try fileAccessor.write(toPath: "/file", contents: "$(SRCROOT) and $(SRCROOT)")
|
||||
|
||||
try remapper.remap(fromGeneric: "/file")
|
||||
|
||||
try XCTAssertEqual(fileAccessor.contents(atPath: "/file"), "/example and /example")
|
||||
}
|
||||
|
||||
func testReplacesInMultipleLine() throws {
|
||||
try fileAccessor.write(toPath: "/file", contents: "$(SRCROOT)\n$(SRCROOT)")
|
||||
|
||||
try remapper.remap(fromGeneric: "/file")
|
||||
|
||||
try XCTAssertEqual(fileAccessor.contents(atPath: "/file"), "/example\n/example")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2022 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 UnzippedArtifactProcessorTests: FileXCTestCase {
|
||||
|
||||
private let fileAccessor = FileAccessorFake(mode: .strict)
|
||||
private let remapper = StringDependenciesRemapper(mappings: [.init(generic: "$(SRCROOT)", local: "/local")])
|
||||
private var fileRemapper: FileDependenciesRemapper!
|
||||
private var processor: UnzippedArtifactProcessor!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
fileRemapper = TextFileDependenciesRemapper(remapper: remapper, fileAccessor: fileAccessor)
|
||||
processor = UnzippedArtifactProcessor(
|
||||
fileRemapper: fileRemapper,
|
||||
dirScanner: fileAccessor
|
||||
)
|
||||
}
|
||||
|
||||
func testProcessingRawArtifactReplacesPlaceholders() throws {
|
||||
try fileAccessor.write(toPath: "/artifact/include/file", contents: "Some $(SRCROOT)")
|
||||
|
||||
try processor.process(rawArtifact: "/artifact")
|
||||
|
||||
XCTAssertEqual(try fileAccessor.contents(atPath: "/artifact/include/file"), "Some /local")
|
||||
}
|
||||
|
||||
func testProcessingRawArtifactReplacesInNestedInclude() throws {
|
||||
try fileAccessor.write(toPath: "/artifact/include/nested/file", contents: "Some $(SRCROOT)")
|
||||
|
||||
try processor.process(rawArtifact: "/artifact")
|
||||
|
||||
XCTAssertEqual(try fileAccessor.contents(atPath: "/artifact/include/nested/file"), "Some /local")
|
||||
}
|
||||
|
||||
func testProcessingRawArtifactDoesntReplacesInNonIncludeDir() throws {
|
||||
try fileAccessor.write(toPath: "/artifact/some/file", contents: "Some $(SRCROOT)")
|
||||
|
||||
try processor.process(rawArtifact: "/artifact")
|
||||
|
||||
XCTAssertEqual(try fileAccessor.contents(atPath: "/artifact/some/file"), "Some $(SRCROOT)")
|
||||
}
|
||||
|
||||
func testDoesntProcessEmptyFiles() throws {
|
||||
try fileAccessor.write(toPath: "/artifact/include/.hidden", contents: "Some $(SRCROOT)")
|
||||
|
||||
try processor.process(rawArtifact: "/artifact")
|
||||
|
||||
XCTAssertEqual(try fileAccessor.contents(atPath: "/artifact/include/.hidden"), "Some $(SRCROOT)")
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,11 @@ class ZipArtifactOrganizerTests: XCTestCase {
|
||||
|
||||
func testPreparePlacesArtifactInTheActiveLocation() throws {
|
||||
let zipURL = try prepareZipFile(content: "Magic", fileName: "content.txt")
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: workingDirectory, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: workingDirectory,
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
|
||||
let preparedArtifact = try organizer.prepare(artifact: zipURL)
|
||||
|
||||
@@ -64,7 +68,11 @@ class ZipArtifactOrganizerTests: XCTestCase {
|
||||
|
||||
func testPreparingExistingArtifact() throws {
|
||||
let zipURL = try prepareZipFile(content: "Magic", fileName: "content.txt")
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: workingDirectory, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: workingDirectory,
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
|
||||
_ = try organizer.prepare(artifact: zipURL)
|
||||
let preparedArtifact = try organizer.prepare(artifact: zipURL)
|
||||
@@ -75,7 +83,11 @@ class ZipArtifactOrganizerTests: XCTestCase {
|
||||
|
||||
func testPreparePlacesArtifactInTheFileKeyRelatedLocation() throws {
|
||||
let zipURL = try prepareZipFile(content: "Magic", fileName: "content.txt", zipFileName: "abb32_fileKey")
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: workingDirectory, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: workingDirectory,
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
let expectedArtifactLocation = workingDirectory.appendingPathComponent("abb32_fileKey")
|
||||
|
||||
let preparedArtifact = try organizer.prepare(artifact: zipURL)
|
||||
@@ -89,7 +101,11 @@ class ZipArtifactOrganizerTests: XCTestCase {
|
||||
let artifactLocation = workingDirectory.appendingPathComponent("xccache")
|
||||
.appendingPathComponent(sampleFileKey, isDirectory: true)
|
||||
try fileManager.createDirectory(at: artifactLocation, withIntermediateDirectories: true, attributes: nil)
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: workingDirectory, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: workingDirectory,
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
|
||||
let result = try organizer.prepareArtifactLocationFor(fileKey: sampleFileKey)
|
||||
if case .artifactExists(artifactDir: let u) = result {
|
||||
@@ -105,7 +121,11 @@ class ZipArtifactOrganizerTests: XCTestCase {
|
||||
.appendingPathComponent("xccache")
|
||||
.appendingPathComponent(sampleFileKey)
|
||||
.appendingPathExtension("zip")
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: workingDirectory, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: workingDirectory,
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
|
||||
let result = try organizer.prepareArtifactLocationFor(fileKey: sampleFileKey)
|
||||
|
||||
@@ -126,7 +146,11 @@ class ZipArtifactOrganizerTests: XCTestCase {
|
||||
try fileManager.createDirectory(at: activeArtifact, withIntermediateDirectories: true, attributes: nil)
|
||||
try fileManager.spt_forceSymbolicLink(at: activeLink, withDestinationURL: activeArtifact)
|
||||
|
||||
let organizer = ZipArtifactOrganizer(targetTempDir: workingDirectory, fileManager: fileManager)
|
||||
let organizer = ZipArtifactOrganizer(
|
||||
targetTempDir: workingDirectory,
|
||||
artifactProcessors: [],
|
||||
fileManager: fileManager
|
||||
)
|
||||
|
||||
let fileKey = try organizer.getActiveArtifactFilekey()
|
||||
|
||||
|
||||
+2
-1
@@ -67,7 +67,8 @@ class ThinningDiskSwiftcProductsGeneratorTests: FileXCTestCase {
|
||||
artifactSwiftModuleObjCFile: headerFile
|
||||
)
|
||||
|
||||
XCTAssertEqual(generatedModulePath, destinationSwiftModuleDir)
|
||||
XCTAssertEqual(generatedModulePath.swiftmoduleDir, destinationSwiftModuleDir)
|
||||
XCTAssertEqual(generatedModulePath.objcHeaderFile, objCHeader)
|
||||
XCTAssertEqual(fileManager.contents(atPath: expectedSwiftSourceInfoFile.path), "sourceInfo".data(using: .utf8))
|
||||
XCTAssertEqual(fileManager.contents(atPath: objCHeader.path), "header".data(using: .utf8))
|
||||
}
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ class UnzippedArtifactSwiftProductsOrganizerTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
let destination = SwiftcProductsGeneratorOutput(swiftmoduleDir: destination, objcHeaderFile: "")
|
||||
generator = SwiftcProductsGeneratorSpy(generatedDestination: destination)
|
||||
dirAccessor = DirAccessorFake()
|
||||
syncer = FileFingerprintSyncer(
|
||||
|
||||
@@ -44,6 +44,7 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
"BUILT_PRODUCTS_DIR": "BUILT_PRODUCTS_DIR",
|
||||
"DERIVED_SOURCES_DIR": "DERIVED_SOURCES_DIR",
|
||||
"CURRENT_VARIANT": "normal",
|
||||
"PUBLIC_HEADERS_FOLDER_PATH": "/usr/local/include",
|
||||
]
|
||||
|
||||
override func setUpWithError() throws {
|
||||
@@ -130,4 +131,59 @@ class PostbuildContextTests: FileXCTestCase {
|
||||
|
||||
XCTAssertEqual(context.compilationTempDir, "/OBJECT_FILE_DIR_custom/x86_64")
|
||||
}
|
||||
|
||||
func testGenericPublicHeaderDestinationIsSkipped() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["PUBLIC_HEADERS_FOLDER_PATH"] = "/usr/local/include"
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertNil(context.publicHeadersFolderPath)
|
||||
}
|
||||
|
||||
func testPublicHeaderFolderIsRelativeToProductsDir() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["BUILT_PRODUCTS_DIR"] = "/MyBuiltProductsDir"
|
||||
envs["PUBLIC_HEADERS_FOLDER_PATH"] = "MyModule.grameworks/Headers"
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertEqual(context.publicHeadersFolderPath, "/MyBuiltProductsDir/MyModule.grameworks/Headers")
|
||||
}
|
||||
|
||||
func testPublicHeaderFolderPathEnvIsOptional() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs.removeValue(forKey: "PUBLIC_HEADERS_FOLDER_PATH")
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertNil(context.publicHeadersFolderPath)
|
||||
}
|
||||
|
||||
func testDisabledEnvIsFalseByDefault() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs.removeValue(forKey: "XCRC_DISABLED")
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertFalse(context.disabled)
|
||||
}
|
||||
|
||||
func testDisabledIsTrueForYesEnv() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["XCRC_DISABLED"] = "YES"
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertTrue(context.disabled)
|
||||
}
|
||||
|
||||
func testDisabledIsFalseForNonYesEnv() throws {
|
||||
var envs = Self.SampleEnvs
|
||||
envs["XCRC_DISABLED"] = "NO"
|
||||
|
||||
let context = try PostbuildContext(config, env: envs)
|
||||
|
||||
XCTAssertFalse(context.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,9 @@ class PostbuildTests: FileXCTestCase {
|
||||
action: .build,
|
||||
modeMarkerPath: "",
|
||||
overlayHeadersPath: "",
|
||||
irrelevantDependenciesPaths: []
|
||||
irrelevantDependenciesPaths: [],
|
||||
publicHeadersFolderPath: nil,
|
||||
disabled: false
|
||||
)
|
||||
private var network = RemoteNetworkClientImpl(
|
||||
NetworkClientFake(fileManager: .default),
|
||||
@@ -643,4 +645,79 @@ class PostbuildTests: FileXCTestCase {
|
||||
|
||||
XCTAssertEqual(downloadedMeta, expectedMeta)
|
||||
}
|
||||
|
||||
func testDecoratesDerivedSwiftHeaderWithEmptyModulesFolderPath() throws {
|
||||
let dir = try prepareTempDir()
|
||||
let derivedSourcesDir = dir
|
||||
.appendingPathComponent("DerivedSources")
|
||||
let swiftSwiftHeader = derivedSourcesDir
|
||||
.appendingPathComponent("MyModule-Swift.h")
|
||||
let swiftSwiftHeaderOverride = swiftSwiftHeader
|
||||
.appendingPathExtension("md5")
|
||||
|
||||
try fileManager.spt_createEmptyDir(derivedSourcesDir)
|
||||
try fileManager.spt_createEmptyFile(swiftSwiftHeader)
|
||||
postbuildContext.moduleName = "MyModule"
|
||||
postbuildContext.derivedSourcesDir = derivedSourcesDir
|
||||
let postbuild = Postbuild(
|
||||
context: postbuildContext,
|
||||
networkClient: network,
|
||||
remapper: remapper,
|
||||
fingerprintAccumulator: fingerprintGenerator,
|
||||
artifactsOrganizer: organizer,
|
||||
artifactCreator: artifactCreator,
|
||||
fingerprintSyncer: syncer,
|
||||
dependenciesReader: dependenciesReader,
|
||||
dependencyProcessor: processor,
|
||||
fingerprintOverrideManager: overrideManager,
|
||||
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
|
||||
modeController: modeController,
|
||||
metaReader: metaReader,
|
||||
metaWriter: metaWriter,
|
||||
creatorPlugins: [],
|
||||
consumerPlugins: []
|
||||
)
|
||||
|
||||
try postbuild.performBuildCompletion()
|
||||
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: swiftSwiftHeaderOverride.path))
|
||||
}
|
||||
|
||||
func testDecoratesPublicSwiftHeaderWithEmptyModulesFolderPath() throws {
|
||||
let dir = try prepareTempDir()
|
||||
let productsDir = dir
|
||||
.appendingPathComponent("MyModule.framework")
|
||||
.appendingPathComponent("Headers")
|
||||
let swiftSwiftHeader = productsDir
|
||||
.appendingPathComponent("MyModule-Swift.h")
|
||||
let swiftSwiftHeaderOverride = swiftSwiftHeader
|
||||
.appendingPathExtension("md5")
|
||||
|
||||
try fileManager.spt_createEmptyDir(productsDir)
|
||||
try fileManager.spt_createEmptyFile(swiftSwiftHeader)
|
||||
postbuildContext.moduleName = "MyModule"
|
||||
postbuildContext.publicHeadersFolderPath = productsDir
|
||||
let postbuild = Postbuild(
|
||||
context: postbuildContext,
|
||||
networkClient: network,
|
||||
remapper: remapper,
|
||||
fingerprintAccumulator: fingerprintGenerator,
|
||||
artifactsOrganizer: organizer,
|
||||
artifactCreator: artifactCreator,
|
||||
fingerprintSyncer: syncer,
|
||||
dependenciesReader: dependenciesReader,
|
||||
dependencyProcessor: processor,
|
||||
fingerprintOverrideManager: overrideManager,
|
||||
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
|
||||
modeController: modeController,
|
||||
metaReader: metaReader,
|
||||
metaWriter: metaWriter,
|
||||
creatorPlugins: [],
|
||||
consumerPlugins: []
|
||||
)
|
||||
|
||||
try postbuild.performBuildCompletion()
|
||||
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: swiftSwiftHeaderOverride.path))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
overlayHeadersPath: "",
|
||||
disabled: false
|
||||
)
|
||||
contextCached = PrebuildContext(
|
||||
targetTempDir: sampleURL,
|
||||
@@ -76,7 +77,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
overlayHeadersPath: "",
|
||||
disabled: false
|
||||
)
|
||||
organizer = ArtifactOrganizerFake(artifactRoot: artifactsRoot, unzippedExtension: "unzip")
|
||||
globalCacheSwitcher = InMemoryGlobalCacheSwitcher()
|
||||
@@ -241,7 +243,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
overlayHeadersPath: "",
|
||||
disabled: false
|
||||
)
|
||||
|
||||
let prebuild = Prebuild(
|
||||
@@ -272,7 +275,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
overlayHeadersPath: "",
|
||||
disabled: false
|
||||
)
|
||||
metaContent = try generateMeta(fingerprint: generator.generate(), filekey: "1")
|
||||
let downloadedArtifactPackage = artifactsRoot.appendingPathComponent("1")
|
||||
@@ -335,7 +339,8 @@ class PrebuildTests: FileXCTestCase {
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: false,
|
||||
targetName: "",
|
||||
overlayHeadersPath: ""
|
||||
overlayHeadersPath: "",
|
||||
disabled: false
|
||||
)
|
||||
try globalCacheSwitcher.enable(sha: "1")
|
||||
let prebuild = Prebuild(
|
||||
@@ -353,4 +358,34 @@ class PrebuildTests: FileXCTestCase {
|
||||
|
||||
XCTAssertEqual(globalCacheSwitcher.state, .enabled(sha: "1"))
|
||||
}
|
||||
|
||||
func testReturnsDisabledIfXCRCExplicitlyDisabled() throws {
|
||||
contextNonCached = PrebuildContext(
|
||||
targetTempDir: sampleURL,
|
||||
productsDir: sampleURL,
|
||||
moduleName: nil,
|
||||
remoteCommit: .unavailable,
|
||||
remoteCommitLocation: sampleURL,
|
||||
recommendedCacheAddress: sampleURL,
|
||||
forceCached: false,
|
||||
compilationHistoryFile: compilationHistory,
|
||||
turnOffRemoteCacheOnFirstTimeout: true,
|
||||
targetName: "",
|
||||
overlayHeadersPath: "",
|
||||
disabled: true
|
||||
)
|
||||
|
||||
let prebuild = Prebuild(
|
||||
context: contextNonCached,
|
||||
networkClient: remoteNetwork,
|
||||
remapper: remapper,
|
||||
fingerprintAccumulator: generator,
|
||||
artifactsOrganizer: organizer,
|
||||
globalCacheSwitcher: globalCacheSwitcher,
|
||||
metaReader: metaReader,
|
||||
artifactConsumerPrebuildPlugins: []
|
||||
)
|
||||
|
||||
XCTAssertEqual(try prebuild.perform(), .disabled)
|
||||
}
|
||||
}
|
||||
|
||||
+88
-2
@@ -34,6 +34,7 @@ class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
|
||||
cc: binariesDir.appendingPathComponent("xccc"),
|
||||
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
|
||||
libtool: binariesDir.appendingPathComponent("xclibtool"),
|
||||
lipo: binariesDir.appendingPathComponent("lipo"),
|
||||
ld: binariesDir.appendingPathComponent("xcld"),
|
||||
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
|
||||
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
|
||||
@@ -44,7 +45,12 @@ class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
|
||||
func testProducerSettingFakeSrcRoot() throws {
|
||||
let mode: Mode = .producer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxP"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(mode: mode, repoRoot: rootURL, fakeSrcRoot: fakeRootURL)
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: fakeRootURL,
|
||||
sdksExclude: []
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let resultURL = try XCTUnwrap(result["XCRC_FAKE_SRCROOT"] as? String)
|
||||
|
||||
@@ -54,10 +60,90 @@ class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
|
||||
func testConsumerSettingFakeSrcRoot() throws {
|
||||
let mode: Mode = .consumer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxC"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(mode: mode, repoRoot: rootURL, fakeSrcRoot: fakeRootURL)
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: fakeRootURL,
|
||||
sdksExclude: []
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let resultURL: String = try XCTUnwrap(result["XCRC_FAKE_SRCROOT"] as? String)
|
||||
|
||||
XCTAssertEqual(resultURL, fakeRootURL.path)
|
||||
}
|
||||
|
||||
func testConsumerSettingLdPlusPlus() throws {
|
||||
let mode: Mode = .consumer
|
||||
let fakeRootURL: URL = "/xxxxxxxxxxC"
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: fakeRootURL,
|
||||
sdksExclude: []
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let ldPlusPlus: String = try XCTUnwrap(result["LDPLUSPLUS"] as? String)
|
||||
|
||||
XCTAssertEqual(ldPlusPlus, binaries.ldplusplus.path)
|
||||
}
|
||||
|
||||
func testSinglesdksExcludeIsAppended() throws {
|
||||
let mode: Mode = .consumer
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: "/",
|
||||
sdksExclude: ["watchOS*"]
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let ldPlusPlusWatchOS: String = try XCTUnwrap(result["LDPLUSPLUS[sdk=watchOS*]"] as? String)
|
||||
|
||||
XCTAssertEqual(ldPlusPlusWatchOS, "")
|
||||
}
|
||||
|
||||
func testLibtoolIsSetForExcludedSdks() throws {
|
||||
let mode: Mode = .consumer
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: "/",
|
||||
sdksExclude: ["watchOS*"]
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let libtoolWatchOS: String = try XCTUnwrap(result["LIBTOOL[sdk=watchOS*]"] as? String)
|
||||
|
||||
XCTAssertEqual(libtoolWatchOS, "libtool")
|
||||
}
|
||||
|
||||
func testMultiplesdksExcludeAreAppended() throws {
|
||||
let mode: Mode = .consumer
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: "/",
|
||||
sdksExclude: ["watchOS*", "watchsimulator*"]
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let ldPlusPlusWatchOS: String = try XCTUnwrap(result["LDPLUSPLUS[sdk=watchOS*]"] as? String)
|
||||
let ldPlusPlusWatchSimulator: String = try XCTUnwrap(result["LDPLUSPLUS[sdk=watchsimulator*]"] as? String)
|
||||
|
||||
XCTAssertEqual(ldPlusPlusWatchOS, "")
|
||||
XCTAssertEqual(ldPlusPlusWatchSimulator, "")
|
||||
}
|
||||
|
||||
func testAddsDisabledFlagForExcludedSDKs() throws {
|
||||
let mode: Mode = .consumer
|
||||
let appender = XcodeProjBuildSettingsIntegrateAppender(
|
||||
mode: mode,
|
||||
repoRoot: rootURL,
|
||||
fakeSrcRoot: "/",
|
||||
sdksExclude: ["watchOS*", "watchsimulator*"]
|
||||
)
|
||||
let result = appender.appendToBuildSettings(buildSettings: buildSettings, wrappers: binaries)
|
||||
let disabledWatchOS: String = try XCTUnwrap(result["XCRC_DISABLED[sdk=watchOS*]"] as? String)
|
||||
let disabledWatchSimulator: String = try XCTUnwrap(result["XCRC_DISABLED[sdk=watchsimulator*]"] as? String)
|
||||
|
||||
XCTAssertEqual(disabledWatchOS, "YES")
|
||||
XCTAssertEqual(disabledWatchSimulator, "YES")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class PrepareMarkContextTests: XCTestCase {
|
||||
let repoPath = "/AbsolutePath"
|
||||
config.repoRoot = repoPath
|
||||
|
||||
let context = try PrepareMarkContext(config)
|
||||
let context = try PrepareMarkContext(config, env: [:])
|
||||
|
||||
XCTAssertEqual(context.repoRoot.path, repoPath)
|
||||
}
|
||||
@@ -43,8 +43,20 @@ class PrepareMarkContextTests: XCTestCase {
|
||||
let repoPath = "."
|
||||
config.repoRoot = repoPath
|
||||
|
||||
let context = try PrepareMarkContext(config)
|
||||
let context = try PrepareMarkContext(config, env: [:])
|
||||
|
||||
XCTAssertEqual(context.repoRoot.path, "/Root")
|
||||
}
|
||||
|
||||
func testDisabledIsFalseByDefault() throws {
|
||||
let context = try PrepareMarkContext(config, env: [:])
|
||||
|
||||
XCTAssertFalse(context.disabled)
|
||||
}
|
||||
|
||||
func testDisabledIsTrueForYes() throws {
|
||||
let context = try PrepareMarkContext(config, env: ["XCRC_DISABLED": "YES"])
|
||||
|
||||
XCTAssertTrue(context.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@ class SwiftcTests: FileXCTestCase {
|
||||
)
|
||||
context = try SwiftcContext(config: config, input: input)
|
||||
markerWriter = MarkerWriterSpy()
|
||||
productsGenerator = SwiftcProductsGeneratorSpy()
|
||||
productsGenerator = SwiftcProductsGeneratorSpy(
|
||||
generatedDestination: SwiftcProductsGeneratorOutput(swiftmoduleDir: "", objcHeaderFile: "")
|
||||
)
|
||||
let dependenciesWriterSpy = DependenciesWriterSpy()
|
||||
self.dependenciesWriterSpy = dependenciesWriterSpy
|
||||
dependenciesWriterFactory = { [dependenciesWriterSpy] _, _ in dependenciesWriterSpy }
|
||||
@@ -283,6 +285,12 @@ class SwiftcTests: FileXCTestCase {
|
||||
let artifactSwiftInterfaceInfo = URL(
|
||||
fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftinterface"
|
||||
)
|
||||
let artifactPrivateSwiftInterfaceInfo = URL(
|
||||
fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.private.swiftinterface"
|
||||
)
|
||||
let artifactAbiJsonInfo = URL(
|
||||
fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.abi.json"
|
||||
)
|
||||
|
||||
artifactOrganizer = ArtifactOrganizerFake(artifactRoot: artifactRoot)
|
||||
let swiftc = Swiftc(
|
||||
@@ -303,17 +311,14 @@ class SwiftcTests: FileXCTestCase {
|
||||
_ = try swiftc.mockCompilation()
|
||||
|
||||
let swiftModuleFiles = try productsGenerator.generated.first.unwrap()
|
||||
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)
|
||||
XCTAssertEqual(swiftModuleFiles.0[.swiftmodule], artifactSwiftmodule)
|
||||
XCTAssertEqual(swiftModuleFiles.0[.swiftdoc], artifactSwiftdoc)
|
||||
XCTAssertEqual(swiftModuleFiles.0[.swiftsourceinfo], artifactSwiftSourceInfo)
|
||||
XCTAssertEqual(swiftModuleFiles.0[.swiftinterface], artifactSwiftInterfaceInfo)
|
||||
XCTAssertEqual(swiftModuleFiles.0[.privateSwiftinterface], artifactPrivateSwiftInterfaceInfo)
|
||||
XCTAssertEqual(swiftModuleFiles.0[.abiJson], artifactAbiJsonInfo)
|
||||
XCTAssertEqual(swiftModuleFiles.1, artifactObjCHeader)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ class TemplateBasedCCWrapperBuilderTests: FileXCTestCase {
|
||||
XCTAssertNotEqual(newFileOutputData, Data())
|
||||
}
|
||||
|
||||
func testPCHCompilationFallbacks() throws {
|
||||
func testPCHObjCCompilationFallbacks() throws {
|
||||
// Marker is empty to mimic the new file scenario
|
||||
let pchFile = directory.appendingPathComponent("input.pch")
|
||||
createValidPCHFile(pchFile)
|
||||
@@ -344,6 +344,17 @@ class TemplateBasedCCWrapperBuilderTests: FileXCTestCase {
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: outputFile.path))
|
||||
}
|
||||
|
||||
func testPCHCCompilationFallbacks() throws {
|
||||
// Marker is empty to mimic the new file scenario
|
||||
let pchFile = directory.appendingPathComponent("input.pch")
|
||||
createValidPCHFile(pchFile)
|
||||
arguments = ["-x", "c-header", "-MF", dependencyFile.path, "-o", outputFile.path, pchFile.path]
|
||||
|
||||
try shellExec(Self.xccc.path, args: arguments, inDir: directory.path)
|
||||
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: outputFile.path))
|
||||
}
|
||||
|
||||
/// Creates a simple C code in the location
|
||||
private func createValidCFile(_ location: URL) {
|
||||
fileManager.createFile(atPath: location.path, contents: "int main(){}".data(using: .utf8), attributes: nil)
|
||||
|
||||
@@ -32,11 +32,18 @@ class XCRemoteCacheConfigReaderTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testReadsFromExtraConfig() throws {
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: "cache_addresses: [test]")
|
||||
let contents = [
|
||||
"cache_addresses: [test]",
|
||||
"retry_delay: 30",
|
||||
"upload_batch_size: 5",
|
||||
].joined(separator: "\n").data(using: .utf8)
|
||||
try fileReader.write(toPath: "/.rcinfo", contents: contents)
|
||||
|
||||
let config = try reader.readConfiguration()
|
||||
|
||||
XCTAssertEqual(config.cacheAddresses, ["test"])
|
||||
XCTAssertEqual(config.retryDelay, 30)
|
||||
XCTAssertEqual(config.uploadBatchSize, 5)
|
||||
}
|
||||
|
||||
func testOverridesExtraConfigFromExtraFile() throws {
|
||||
|
||||
@@ -74,4 +74,51 @@ class FileFingerprintSyncerTests: FileXCTestCase {
|
||||
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: nonOverrideFile.path))
|
||||
}
|
||||
|
||||
func testDecoratesFile() throws {
|
||||
let header = swiftmoduleDir.appendingPathComponent("Module-Swift.h")
|
||||
let headerOverride = swiftmoduleDir.appendingPathComponent("Module-Swift.h.md5")
|
||||
try fileManager.spt_createEmptyFile(header)
|
||||
|
||||
|
||||
try syncer.decorate(file: header, fingerprint: "1")
|
||||
|
||||
XCTAssertEqual(try String(contentsOf: headerOverride), "1")
|
||||
}
|
||||
|
||||
func testFileDecorateOverridesPreviousOverlay() throws {
|
||||
let header = swiftmoduleDir.appendingPathComponent("Module-Swift.h")
|
||||
let headerOverride = swiftmoduleDir.appendingPathComponent("Module-Swift.h.md5")
|
||||
try fileManager.spt_createEmptyFile(header)
|
||||
try "1".write(to: headerOverride, atomically: true, encoding: .utf8)
|
||||
|
||||
try syncer.decorate(file: header, fingerprint: "2")
|
||||
|
||||
XCTAssertEqual(try String(contentsOf: headerOverride), "2")
|
||||
}
|
||||
|
||||
func testDeletesFileOverride() throws {
|
||||
let header = swiftmoduleDir.appendingPathComponent("Module-Swift.h")
|
||||
let headerOverride = swiftmoduleDir.appendingPathComponent("Module-Swift.h.md5")
|
||||
try fileManager.spt_createEmptyFile(header)
|
||||
try fileManager.spt_createEmptyFile(headerOverride)
|
||||
|
||||
|
||||
try syncer.delete(file: header)
|
||||
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: headerOverride.path))
|
||||
}
|
||||
|
||||
func testDeletesDoesntDeleteWhenFileIsMissing() throws {
|
||||
let nonExistingFile = swiftmoduleDir.appendingPathComponent("Module-Swift.h")
|
||||
|
||||
XCTAssertNoThrow(try syncer.delete(file: nonExistingFile))
|
||||
}
|
||||
|
||||
func testDeletesDoesntDeleteWhenOverrideIsMissing() throws {
|
||||
let header = swiftmoduleDir.appendingPathComponent("Module-Swift.h")
|
||||
try fileManager.spt_createEmptyFile(header)
|
||||
|
||||
XCTAssertNoThrow(try syncer.delete(file: header))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,4 +71,17 @@ class FileManagerDirScannerTests: FileXCTestCase {
|
||||
|
||||
try XCTAssertThrowsError(dirScanner.items(at: dir))
|
||||
}
|
||||
|
||||
func testFindsAllFilesRecursively() throws {
|
||||
let dir = workingDirectory!.appendingPathComponent("dir")
|
||||
let nestedDir = dir.appendingPathComponent("nested")
|
||||
let nestedFile = nestedDir.appendingPathComponent("file")
|
||||
try fileManager.spt_createEmptyFile(nestedFile)
|
||||
|
||||
let allFiles = try dirScanner.recursiveItems(at: dir)
|
||||
|
||||
// returned items may contain symbolic links in a path
|
||||
let allFilesResolve = allFiles.map { $0.resolvingSymlinksInPath() }
|
||||
XCTAssertEqual(allFilesResolve, [nestedFile])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,27 @@ class CanonicalRequestTest: XCTestCase {
|
||||
}
|
||||
|
||||
func testCanonicalRequest() {
|
||||
request.url = URL(
|
||||
string: "https://region.amazonaws.com/bucket/with%20space?param=value&hej=hej&abd=cde&test=-_.~"
|
||||
)!
|
||||
let canonicalRequest = CanonicalRequest(
|
||||
request: request
|
||||
)
|
||||
XCTAssertEqual(
|
||||
canonicalRequest.value,
|
||||
"GET\n" +
|
||||
"/bucket/with%20space\n" +
|
||||
"abd=cde&hej=hej¶m=value&test=-_.~\n" +
|
||||
"a-header2key:A-Header2Value\n" +
|
||||
"b-header3key:B-Header3Value\n" +
|
||||
"c-header4key:C Header 4 Value\n" +
|
||||
"x-header1key:X-Header1Value\n\n" +
|
||||
"a-header2key;b-header3key;c-header4key;x-header1key\n" +
|
||||
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
)
|
||||
}
|
||||
|
||||
func testCanonicalRequestWithEmptySpaceInPath() {
|
||||
let canonicalRequest = CanonicalRequest(
|
||||
request: request
|
||||
)
|
||||
|
||||
@@ -72,7 +72,13 @@ class NetworkClientImplTests: XCTestCase {
|
||||
configuration.protocolClasses = [URLProtocolStub.self]
|
||||
session = URLSession(configuration: configuration)
|
||||
fileManager = FileManager.default
|
||||
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: nil)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 0,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -87,7 +93,7 @@ class NetworkClientImplTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func waitForResponse<R>(_ action: (@escaping Completion<R>) -> Void) throws -> Result<R, NetworkClientError> {
|
||||
func waitForResponse<R>(_ action: (@escaping Completion<R>) -> Void, timeout: TimeInterval = 0.1) throws -> Result<R, NetworkClientError> {
|
||||
let responseExpectation = expectation(description: "RequestResponse")
|
||||
var receivedResponse: Result<R, NetworkClientError>?
|
||||
|
||||
@@ -95,7 +101,7 @@ class NetworkClientImplTests: XCTestCase {
|
||||
receivedResponse = response
|
||||
responseExpectation.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 0.1)
|
||||
waitForExpectations(timeout: timeout)
|
||||
return try receivedResponse.unwrap()
|
||||
}
|
||||
|
||||
@@ -141,9 +147,15 @@ class NetworkClientImplTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testUploadFilureWith400Retries() throws {
|
||||
client = NetworkClientImpl(session: session, retries: 2, fileManager: fileManager, awsV4Signature: nil)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 2,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
responses[url] = .success(failureResponse, Data())
|
||||
_ = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
|
||||
_ = try waitForResponse({ client.upload(fileURL, as: url, completion: $0) }, timeout: 0.5)
|
||||
|
||||
XCTAssertEqual(
|
||||
requests.map(\.url),
|
||||
@@ -153,13 +165,51 @@ class NetworkClientImplTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testUploadSuccessDoesntRetry() throws {
|
||||
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: nil)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 2,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
responses[url] = .success(successResponse, Data())
|
||||
_ = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
|
||||
|
||||
XCTAssertEqual(requests.map(\.url), [url], "Expected 1 request - original only")
|
||||
}
|
||||
|
||||
func testDownloadFilureWith400Retries() throws {
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 2,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
responses[url] = nil
|
||||
_ = try waitForResponse({ client.download(url, to: fileURL, completion: $0) }, timeout: 0.5)
|
||||
|
||||
XCTAssertEqual(
|
||||
requests.map(\.url),
|
||||
Array(repeating: url, count: 3),
|
||||
"Expected 3 requests (original + 2 retries)"
|
||||
)
|
||||
}
|
||||
|
||||
func testDownloadSuccessDoesntRetry() throws {
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 2,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: nil
|
||||
)
|
||||
responses[url] = .success(successResponse, Data())
|
||||
_ = try waitForResponse { client.download(url, to: fileURL, completion: $0) }
|
||||
|
||||
XCTAssertEqual(requests.map(\.url), [url], "Expected 1 request - original only")
|
||||
}
|
||||
|
||||
func testFileExits400CompletesWithFalse() throws {
|
||||
responses[url] = .success(failureResponse, Data())
|
||||
let response = try waitForResponse { client.fileExists(url, completion: $0) }
|
||||
@@ -208,7 +258,13 @@ class NetworkClientImplTests: XCTestCase {
|
||||
service: "iam",
|
||||
date: Date(timeIntervalSince1970: 1_440_938_160)
|
||||
)
|
||||
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: signature)
|
||||
client = NetworkClientImpl(
|
||||
session: session,
|
||||
retries: 0,
|
||||
retryDelay: 0,
|
||||
fileManager: fileManager,
|
||||
awsV4Signature: signature
|
||||
)
|
||||
responses[url] = .success(successResponse, Data())
|
||||
_ = try waitForResponse { client.fetch(url, completion: $0) }
|
||||
|
||||
|
||||
@@ -39,7 +39,12 @@ class ReplicatedRemotesNetworkClientTests: XCTestCase {
|
||||
uploadURLs = try [URL(string: "http://upload1.com").unwrap(), URL(string: "http://upload2.com").unwrap()]
|
||||
download = URLBuilderFake(downloadURL)
|
||||
uploads = uploadURLs.map(URLBuilderFake.init)
|
||||
client = ReplicatedRemotesNetworkClient(networkClient, download: download, uploads: uploads)
|
||||
client = ReplicatedRemotesNetworkClient(
|
||||
networkClient,
|
||||
download: download,
|
||||
uploads: uploads,
|
||||
uploadBatchSize: 1
|
||||
)
|
||||
}
|
||||
|
||||
private func prepareLocalEmptyFile() throws -> URL {
|
||||
@@ -62,6 +67,30 @@ class ReplicatedRemotesNetworkClientTests: XCTestCase {
|
||||
XCTAssertTrue(try networkClient.fileExistsSynchronously(expectedArtifact2))
|
||||
}
|
||||
|
||||
func testUploadsWithLimit() throws {
|
||||
var expectedArtifacts = [URL]()
|
||||
var uploadURLs = [URL]()
|
||||
for index in 0...99 {
|
||||
let expectedArtifact = try URL(string: "http://upload\(index).com/file/id1").unwrap()
|
||||
expectedArtifacts.append(expectedArtifact)
|
||||
let uploadURL = try URL(string: "http://upload\(index).com").unwrap()
|
||||
uploadURLs.append(uploadURL)
|
||||
}
|
||||
uploads = uploadURLs.map(URLBuilderFake.init)
|
||||
client = ReplicatedRemotesNetworkClient(
|
||||
networkClient,
|
||||
download: download,
|
||||
uploads: uploads,
|
||||
uploadBatchSize: 10
|
||||
)
|
||||
|
||||
try client.uploadSynchronously(localSampleFile, as: .artifact(id: "id1"))
|
||||
|
||||
for expectedArtifact in expectedArtifacts {
|
||||
XCTAssertTrue(try networkClient.fileExistsSynchronously(expectedArtifact))
|
||||
}
|
||||
}
|
||||
|
||||
func testCreatesInAllStreams() throws {
|
||||
let expectedMeta1 = try URL(string: "http://upload1.com/meta/commit_id").unwrap()
|
||||
let expectedMeta2 = try URL(string: "http://upload2.com/meta/commit_id").unwrap()
|
||||
@@ -71,4 +100,5 @@ class ReplicatedRemotesNetworkClientTests: XCTestCase {
|
||||
XCTAssertTrue(try networkClient.fileExistsSynchronously(expectedMeta1))
|
||||
XCTAssertTrue(try networkClient.fileExistsSynchronously(expectedMeta2))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ class DirAccessorFake: DirAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
func recursiveItems(at dir: URL) throws -> [URL] {
|
||||
memory.compactMap { url, _ in
|
||||
// comparing paths to ignore dir or url's "isDir" property
|
||||
if url.deletingLastPathComponent().path.starts(with: dir.path) {
|
||||
return url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func contents(atPath path: String) throws -> Data? {
|
||||
memory[URL(fileURLWithPath: path)]
|
||||
}
|
||||
|
||||
@@ -60,3 +60,28 @@ class FileAccessorFake: FileAccessor {
|
||||
return storage[path]?.mdate
|
||||
}
|
||||
}
|
||||
|
||||
extension FileAccessorFake: DirScanner {
|
||||
func itemType(atPath path: String) throws -> ItemType {
|
||||
if storage[path] != nil {
|
||||
return .file
|
||||
}
|
||||
if try !recursiveItems(at: URL(fileURLWithPath: path)).isEmpty {
|
||||
return .dir
|
||||
}
|
||||
return .nonExisting
|
||||
}
|
||||
|
||||
func items(at dir: URL) throws -> [URL] {
|
||||
storage.keys.map(URL.init(fileURLWithPath:)).filter {
|
||||
$0.deletingLastPathComponent() == dir
|
||||
}
|
||||
}
|
||||
|
||||
func recursiveItems(at dir: URL) throws -> [URL] {
|
||||
let paths = storage.keys.filter {
|
||||
$0.hasPrefix(dir.path)
|
||||
}
|
||||
return paths.map(URL.init(fileURLWithPath:))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2022 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
|
||||
|
||||
/// No-operation processor
|
||||
class NoopArtifactProcessor: ArtifactProcessor {
|
||||
func process(rawArtifact url: URL) throws {}
|
||||
func process(localArtifact url: URL) throws {}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class SwiftcProductsGeneratorFake: SwiftcProductsGenerator {
|
||||
func generateFrom(
|
||||
artifactSwiftModuleFiles: [SwiftmoduleFileExtension: URL],
|
||||
artifactSwiftModuleObjCFile: URL
|
||||
) throws -> URL {
|
||||
) throws -> SwiftcProductsGeneratorOutput {
|
||||
let swiftmoduleDestBasename = swiftmoduleDest.deletingPathExtension()
|
||||
for (ext, url) in artifactSwiftModuleFiles {
|
||||
try dirAccessor.write(
|
||||
@@ -51,6 +51,9 @@ class SwiftcProductsGeneratorFake: SwiftcProductsGenerator {
|
||||
toPath: swiftmoduleObjCFile.path,
|
||||
contents: dirAccessor.contents(atPath: artifactSwiftModuleObjCFile.path)
|
||||
)
|
||||
return swiftmoduleDest.deletingLastPathComponent()
|
||||
return SwiftcProductsGeneratorOutput(
|
||||
swiftmoduleDir: swiftmoduleDest.deletingLastPathComponent(),
|
||||
objcHeaderFile: swiftmoduleObjCFile
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,16 +22,16 @@ import Foundation
|
||||
|
||||
class SwiftcProductsGeneratorSpy: SwiftcProductsGenerator {
|
||||
private(set) var generated: [([SwiftmoduleFileExtension: URL], URL)] = []
|
||||
private let generationDestination: URL
|
||||
private let generationDestination: SwiftcProductsGeneratorOutput
|
||||
|
||||
init(generatedDestination: URL = "") {
|
||||
init(generatedDestination: SwiftcProductsGeneratorOutput) {
|
||||
generationDestination = generatedDestination
|
||||
}
|
||||
|
||||
func generateFrom(
|
||||
artifactSwiftModuleFiles: [SwiftmoduleFileExtension: URL],
|
||||
artifactSwiftModuleObjCFile: URL
|
||||
) throws -> URL {
|
||||
) throws -> SwiftcProductsGeneratorOutput {
|
||||
generated.append((
|
||||
artifactSwiftModuleFiles,
|
||||
artifactSwiftModuleObjCFile
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2023 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 xclibtoolSupport
|
||||
import XCTest
|
||||
|
||||
class XCLibtoolHelperTests: XCTestCase {
|
||||
func testStaticFrameworkUniversalBinary() throws {
|
||||
let mode = try XCLibtoolHelper.buildMode(
|
||||
args: ["-o", "/universal/static", "/arch1/static", "arch2/static"]
|
||||
)
|
||||
|
||||
XCTAssertEqual(mode, .createUniversalBinary(
|
||||
output: "/universal/static",
|
||||
inputs: ["/arch1/static", "arch2/static"]
|
||||
))
|
||||
}
|
||||
|
||||
func testStaticLibraryUniversalBinary() throws {
|
||||
let mode = try XCLibtoolHelper.buildMode(
|
||||
args: ["-o", "/universal/static.a", "/arch1/static.a", "arch2/static.a"]
|
||||
)
|
||||
|
||||
XCTAssertEqual(mode, .createUniversalBinary(
|
||||
output: "/universal/static.a",
|
||||
inputs: ["/arch1/static.a", "arch2/static.a"]
|
||||
))
|
||||
}
|
||||
|
||||
func testUnknownExtensionInputThrowsUnsupportedMode() throws {
|
||||
XCTAssertThrowsError(
|
||||
try XCLibtoolHelper.buildMode(args: ["-o", "/universal/static.a", "/arch1/static.unknown"])) { error in
|
||||
switch error {
|
||||
case XCLibtoolHelperError.unsupportedMode: break
|
||||
default:
|
||||
XCTFail("Not expected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testMissingOutputThrowsMissingOutput() throws {
|
||||
XCTAssertThrowsError(try XCLibtoolHelper.buildMode(args: ["/arch1/static"])) { error in
|
||||
switch error {
|
||||
case XCLibtoolHelperError.missingOutput: break
|
||||
default:
|
||||
XCTFail("Not expected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: XCRemoteCache
|
||||
spec:
|
||||
type: library
|
||||
owner: foundation
|
||||
@@ -28,7 +28,7 @@ plugin 'cocoapods-xcremotecache'
|
||||
2. Configure XCRemoteCache at the top of your `Podfile` definition:
|
||||
```ruby
|
||||
xcremotecache({
|
||||
'cache_addresses' => ['http://localhost:8080/cache/pods'],
|
||||
'cache_addresses' => ['http://localhost:8080/cache/pods'],
|
||||
'primary_repo' => 'https://your.primary.repo.git',
|
||||
'mode' => 'consumer'
|
||||
})
|
||||
@@ -48,13 +48,14 @@ An object that is passed to the `xcremotecache` can contain all properties suppo
|
||||
| `exclude_build_configurations` | Comma-separated list of configurations that shouldn't use XCRemoteCache | `[]`| ⬜️ |
|
||||
| `final_target` | A target name that is build at the end of the build chain. Relevant only for a 'producer' mode to mark a given sha as ready to use from cache | `Debug` | ⬜️ |
|
||||
| `check_build_configuration` | A build configuration for which the remote cache availability is performed. Relevant only for a 'consumer' mode | `Debug` | ⬜️ |
|
||||
| `check_platform` | A platform for which the remote cache availability is performed. Relevant only for a 'consumer' mode | `iphonesimulator` | ⬜️
|
||||
| `check_platform` | A platform for which the remote cache availability is performed. Relevant only for a 'consumer' mode | `iphonesimulator` | ⬜️
|
||||
| `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` | ⬜️ |
|
||||
| `fake_src_root` | An arbitrary source location shared between producers and consumers. Should be unique for a project. | `/xxxxxxxxxx` | ⬜️ |
|
||||
| `disable_certificate_verification` | A Boolean value that opts-in SSL certificate validation is disabled | `false` | ⬜️ |
|
||||
| `exclude_sdks_configurations` | array of sdks to not integrate XCRemoteCache (e.g. "watchos*, watchsimulator*") (Experimental) | `[]`| ⬜️ |
|
||||
|
||||
## Uninstalling
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
# Licensed 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.
|
||||
@@ -29,42 +29,44 @@ module CocoapodsXCRemoteCacheModifier
|
||||
FAT_ARCHIVE_NAME_INFIX = 'arm64-x86_64'
|
||||
XCRC_COOCAPODS_ROOT_KEY = 'XCRC_COOCAPODS_ROOT'
|
||||
|
||||
# List of plugins' user properties that should not be copied to .rcinfo
|
||||
# List of plugins' user properties that should not be copied to .rcinfo
|
||||
CUSTOM_CONFIGURATION_KEYS = [
|
||||
'enabled',
|
||||
'enabled',
|
||||
'xcrc_location',
|
||||
'exclude_targets',
|
||||
'exclude_build_configurations',
|
||||
'final_target',
|
||||
'check_build_configuration',
|
||||
'check_platform',
|
||||
'check_build_configuration',
|
||||
'check_platform',
|
||||
'modify_lldb_init',
|
||||
'fake_src_root',
|
||||
'exclude_sdks_configurations'
|
||||
]
|
||||
|
||||
class XCRemoteCache
|
||||
@@configuration = nil
|
||||
|
||||
def self.configure(c)
|
||||
def self.configure(c)
|
||||
@@configuration = c
|
||||
end
|
||||
|
||||
def self.set_configuration_default_values
|
||||
default_values = {
|
||||
'mode' => 'consumer',
|
||||
'enabled' => true,
|
||||
'enabled' => true,
|
||||
'xcrc_location' => "XCRC",
|
||||
'exclude_build_configurations' => [],
|
||||
'check_build_configuration' => 'Debug',
|
||||
'check_platform' => 'iphonesimulator',
|
||||
'modify_lldb_init' => true,
|
||||
'check_platform' => 'iphonesimulator',
|
||||
'modify_lldb_init' => true,
|
||||
'xccc_file' => "#{BIN_DIR}/xccc",
|
||||
'remote_commit_file' => "#{BIN_DIR}/arc.rc",
|
||||
'exclude_targets' => [],
|
||||
'prettify_meta_files' => false,
|
||||
'fake_src_root' => "/#{'x' * 10 }",
|
||||
'disable_certificate_verification' => false,
|
||||
'custom_rewrite_envs' => []
|
||||
'custom_rewrite_envs' => [],
|
||||
'exclude_sdks_configurations' => []
|
||||
}
|
||||
@@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
|
||||
# Always include XCRC_COOCAPODS_ROOT_KEY in custom_rewrite_envs
|
||||
@@ -75,12 +77,12 @@ module CocoapodsXCRemoteCacheModifier
|
||||
|
||||
def self.validate_configuration()
|
||||
required_values = [
|
||||
'cache_addresses',
|
||||
'cache_addresses',
|
||||
'primary_repo',
|
||||
'check_build_configuration',
|
||||
'check_platform'
|
||||
]
|
||||
|
||||
|
||||
missing_configuration_values = required_values.select { |v| !@@configuration.key?(v) }
|
||||
unless missing_configuration_values.empty?
|
||||
throw "XCRemoteCache not fully configured. Make sure all required fields are provided. Missing fields are: #{missing_configuration_values.join(', ')}."
|
||||
@@ -105,13 +107,24 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
# @param target [Target] target to apply XCRemoteCache
|
||||
# @param repo_distance [Integer] distance from the git repo root to the target's $SRCROOT
|
||||
# @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', '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, fake_src_root)
|
||||
# @param exclude_sdks_configurations [String[]] list of sdks that should have disabled remote cache
|
||||
def self.enable_xcremotecache(
|
||||
target,
|
||||
repo_distance,
|
||||
xc_location,
|
||||
xc_cc_path,
|
||||
mode,
|
||||
exclude_build_configurations,
|
||||
final_target,
|
||||
fake_src_root,
|
||||
exclude_sdks_configurations
|
||||
)
|
||||
srcroot_relative_xc_location = parent_dir(xc_location, repo_distance)
|
||||
# location of the entrite CocoaPods project, relative to SRCROOT
|
||||
srcroot_relative_project_location = parent_dir('', repo_distance)
|
||||
@@ -120,22 +133,27 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# apply only for relevant Configurations
|
||||
next if exclude_build_configurations.include?(config.name)
|
||||
if mode == 'consumer'
|
||||
config.build_settings['CC'] = ["$SRCROOT/#{parent_dir(xc_cc_path, repo_distance)}"]
|
||||
reset_build_setting(config.build_settings, 'CC', "$SRCROOT/#{parent_dir(xc_cc_path, repo_distance)}", exclude_sdks_configurations)
|
||||
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['LDPLUSPLUS'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcldplusplus"]
|
||||
config.build_settings['SWIFT_USE_INTEGRATED_DRIVER'] = ['NO']
|
||||
reset_build_setting(config.build_settings, 'SWIFT_EXEC', "$SRCROOT/#{srcroot_relative_xc_location}/xcswiftc", exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, 'LIBTOOL', "$SRCROOT/#{srcroot_relative_xc_location}/xclibtool", exclude_sdks_configurations)
|
||||
# Setting LIBTOOL to '' breaks SwiftDriver intengration so resetting it to the original value 'libtool' for all excluded configurations
|
||||
add_build_setting_for_sdks(config.build_settings, 'LIBTOOL', 'libtool', exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, 'LD', "$SRCROOT/#{srcroot_relative_xc_location}/xcld", exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, 'LDPLUSPLUS', "$SRCROOT/#{srcroot_relative_xc_location}/xcldplusplus", exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, 'LIPO', "$SRCROOT/#{srcroot_relative_xc_location}/xclipo", exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, 'SWIFT_USE_INTEGRATED_DRIVER', 'NO', exclude_sdks_configurations)
|
||||
|
||||
config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = fake_src_root
|
||||
config.build_settings['XCRC_PLATFORM_PREFERRED_ARCH'] = ["$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)"]
|
||||
config.build_settings[XCRC_COOCAPODS_ROOT_KEY] = ["$SRCROOT/#{srcroot_relative_project_location}"]
|
||||
reset_build_setting(config.build_settings, 'XCREMOTE_CACHE_FAKE_SRCROOT', fake_src_root, exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, 'XCRC_PLATFORM_PREFERRED_ARCH', "$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)", exclude_sdks_configurations)
|
||||
reset_build_setting(config.build_settings, XCRC_COOCAPODS_ROOT_KEY, "$SRCROOT/#{srcroot_relative_project_location}", exclude_sdks_configurations)
|
||||
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)")
|
||||
add_cflags!(config.build_settings, '-fdebug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)", exclude_sdks_configurations)
|
||||
add_swiftflags!(config.build_settings, '-debug-prefix-map', "#{debug_prefix_map_replacement}=$(XCREMOTE_CACHE_FAKE_SRCROOT)", exclude_sdks_configurations)
|
||||
delete_build_setting(config.build_settings, 'XCRC_DISABLED')
|
||||
add_build_setting_for_sdks(config.build_settings, 'XCRC_DISABLED', 'YES', exclude_sdks_configurations)
|
||||
end
|
||||
|
||||
# Prebuild
|
||||
@@ -156,7 +174,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d"
|
||||
|
||||
# Move prebuild (last element) to the position before compile sources phase (to make it real 'prebuild')
|
||||
if !existing_prebuild_script
|
||||
if !existing_prebuild_script
|
||||
compile_phase_index = target.build_phases.index(target.source_build_phase)
|
||||
target.build_phases.insert(compile_phase_index, target.build_phases.delete(prebuild_script))
|
||||
end
|
||||
@@ -184,7 +202,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
]
|
||||
postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d"
|
||||
# Move postbuild (last element) to the position after compile sources phase (to make it real 'postbuild')
|
||||
if !existing_postbuild_script
|
||||
if !existing_postbuild_script
|
||||
compile_phase_index = target.build_phases.index(target.source_build_phase)
|
||||
target.build_phases.insert(compile_phase_index + 1, target.build_phases.delete(postbuild_script))
|
||||
end
|
||||
@@ -214,6 +232,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
config.build_settings.delete('CC') if config.build_settings.key?('CC')
|
||||
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('LIPO') if config.build_settings.key?('LIPO')
|
||||
config.build_settings.delete('LD') if config.build_settings.key?('LD')
|
||||
config.build_settings.delete('LDPLUSPLUS') if config.build_settings.key?('LDPLUSPLUS')
|
||||
config.build_settings.delete('SWIFT_USE_INTEGRATED_DRIVER') if config.build_settings.key?('SWIFT_USE_INTEGRATED_DRIVER')
|
||||
@@ -226,9 +245,9 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
# User project is not generated from scratch (contrary to `Pods`), delete all previous XCRemoteCache phases
|
||||
target.build_phases.delete_if {|phase|
|
||||
target.build_phases.delete_if {|phase|
|
||||
# Some phases (e.g. PBXSourcesBuildPhase) don't have strict name check respond_to?
|
||||
if phase.respond_to?(:name)
|
||||
if phase.respond_to?(:name)
|
||||
phase.name != nil && phase.name.start_with?("[XCRC]")
|
||||
end
|
||||
}
|
||||
@@ -240,9 +259,9 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
|
||||
def self.download_xcrc_if_needed(local_location)
|
||||
required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
|
||||
required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xclipo', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
|
||||
binaries_exist = required_binaries.reduce(true) do |exists, filename|
|
||||
file_path = File.join(local_location, filename)
|
||||
file_path = File.join(local_location, filename)
|
||||
exists = exists && File.exist?(file_path)
|
||||
end
|
||||
|
||||
@@ -256,13 +275,13 @@ module CocoapodsXCRemoteCacheModifier
|
||||
|
||||
if !system("unzip #{local_package_location} -d #{local_location}")
|
||||
throw "Unzipping XCRemoteCache failed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.download_latest_xcrc_release(local_package_location)
|
||||
release_url = 'https://api.github.com/repos/spotify/XCRemoteCache/releases/latest'
|
||||
asset_url = nil
|
||||
|
||||
|
||||
URI.open(release_url) do |f|
|
||||
assets_array = JSON.parse(f.read)['assets']
|
||||
# Pick fat archive
|
||||
@@ -270,7 +289,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
asset_url = asset_array['url']
|
||||
end
|
||||
|
||||
if asset_url.nil?
|
||||
if asset_url.nil?
|
||||
throw "Downloading XCRemoteCache failed"
|
||||
end
|
||||
|
||||
@@ -281,9 +300,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
end
|
||||
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}"
|
||||
def self.add_cflags!(options, key, value, exclude_sdks_configurations)
|
||||
reset_build_setting(options, 'OTHER_CFLAGS', remove_cflags!(options, key) << "#{key}=#{value}", exclude_sdks_configurations)
|
||||
end
|
||||
|
||||
def self.remove_cflags!(options, key)
|
||||
@@ -293,9 +311,8 @@ module CocoapodsXCRemoteCacheModifier
|
||||
options['OTHER_CFLAGS']
|
||||
end
|
||||
|
||||
def self.add_swiftflags!(options, key, value)
|
||||
return if options.fetch('OTHER_SWIFT_FLAGS','').include?(value)
|
||||
options['OTHER_SWIFT_FLAGS'] = remove_swiftflags!(options, key) + " #{key} #{value}"
|
||||
def self.add_swiftflags!(options, key, value, exclude_sdks_configurations)
|
||||
reset_build_setting(options, 'OTHER_SWIFT_FLAGS', remove_swiftflags!(options, key) + " #{key} #{value}", exclude_sdks_configurations)
|
||||
end
|
||||
|
||||
def self.remove_swiftflags!(options, key)
|
||||
@@ -303,6 +320,34 @@ module CocoapodsXCRemoteCacheModifier
|
||||
options['OTHER_SWIFT_FLAGS']
|
||||
end
|
||||
|
||||
def self.add_build_setting(build_settings, key, value, exclude_sdks_configurations)
|
||||
build_settings[key] = value
|
||||
for exclude_sdks_configuration in exclude_sdks_configurations
|
||||
build_settings["#{key}[sdk=#{exclude_sdks_configuration}]"] = [""]
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes all previous build settings for a key, and sets a new value to all configurations
|
||||
# but the sdks in exclude_sdks_configurations
|
||||
def self.reset_build_setting(build_settings, key, value, exclude_sdks_configurations)
|
||||
delete_build_setting(build_settings, key)
|
||||
add_build_setting(build_settings, key, value, exclude_sdks_configurations)
|
||||
end
|
||||
|
||||
# Delete all build setting for a key, including settings like "[skd=*,arch=*]"
|
||||
def self.delete_build_setting(build_settings, key)
|
||||
for build_setting_key in build_settings.keys
|
||||
build_settings.delete(build_setting_key) if build_setting_key == key || build_setting_key.start_with?("#{key}[")
|
||||
end
|
||||
end
|
||||
|
||||
# Sets value for a key only for a subset of sdk configurations
|
||||
def self.add_build_setting_for_sdks(build_settings, key, value, sdk_configurations)
|
||||
for sdk_configuration in sdk_configurations
|
||||
build_settings["#{key}[sdk=#{sdk_configuration}]"] = value
|
||||
end
|
||||
end
|
||||
|
||||
# Uninstall the XCRemoteCache
|
||||
def self.disable_xcremotecache(user_project, pods_project = nil)
|
||||
user_project.targets.each do |target|
|
||||
@@ -336,7 +381,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
File.open(lldbinit_path) { |file|
|
||||
while(line = file.gets) != nil
|
||||
line = line.strip
|
||||
if line == LLDB_INIT_COMMENT
|
||||
if line == LLDB_INIT_COMMENT
|
||||
# skip current and next lines
|
||||
file.gets
|
||||
next
|
||||
@@ -351,7 +396,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
def self.add_lldbinit_rewrite(lines_content, user_proj_directory,fake_src_root)
|
||||
all_lines = lines_content.clone
|
||||
all_lines << LLDB_INIT_COMMENT
|
||||
all_lines << "settings set target.source-map #{fake_src_root} #{user_proj_directory}"
|
||||
all_lines << "settings set target.source-map #{fake_src_root} #{user_proj_directory}"
|
||||
all_lines << ""
|
||||
all_lines
|
||||
end
|
||||
@@ -362,15 +407,76 @@ module CocoapodsXCRemoteCacheModifier
|
||||
File.write(LLDB_INIT_PATH, lldbinit_lines.join("\n"), mode: "w")
|
||||
end
|
||||
|
||||
Pod::HooksManager.register('cocoapods-xcremotecache', :post_install) do |installer_context|
|
||||
Pod::HooksManager.register('cocoapods-xcremotecache', :pre_install) do |installer_context|
|
||||
# The main responsibility of that hook is forcing Pods regeneration when XCRemoteCache is enabled for the first time
|
||||
# In the post_install hook, this plugin adds extra build settings and steps to all Pods targets, but only when XCRemoteCache
|
||||
# is enabled and all artifacts are available (i.e. xcprepare returns 0).
|
||||
# If Pods projects/targets are cached from previous `pod install` action that didn't enable XCRemoteCache (e.g. artifacts
|
||||
# are not available in the remote cache), these projects/targets should be invalidated to include XCRemoteCache-related
|
||||
# build steps and build settings.
|
||||
if @@configuration.nil?
|
||||
Pod::UI.puts "[XCRC] Warning! XCRemoteCache not configured. Call xcremotecache({...}) in Podfile to enable XCRemoteCache"
|
||||
next
|
||||
end
|
||||
|
||||
begin
|
||||
# `user_pod_directory`` and `user_proj_directory` in the 'postinstall' should be equal
|
||||
user_pod_directory = File.dirname(installer_context.podfile.defined_in_file)
|
||||
set_configuration_default_values
|
||||
|
||||
unless @@configuration['enabled']
|
||||
# No need to check if enabling remote cache for the first time
|
||||
next
|
||||
end
|
||||
|
||||
validate_configuration()
|
||||
mode = @@configuration['mode']
|
||||
remote_commit_file = @@configuration['remote_commit_file']
|
||||
xcrc_location = @@configuration['xcrc_location']
|
||||
check_build_configuration = @@configuration['check_build_configuration']
|
||||
check_platform = @@configuration['check_platform']
|
||||
|
||||
xcrc_location_absolute = "#{user_pod_directory}/#{xcrc_location}"
|
||||
remote_commit_file_absolute = "#{user_pod_directory}/#{remote_commit_file}"
|
||||
|
||||
# Download XCRC
|
||||
download_xcrc_if_needed(xcrc_location_absolute)
|
||||
|
||||
# Save .rcinfo
|
||||
root_rcinfo = generate_rcinfo()
|
||||
save_rcinfo(root_rcinfo, user_pod_directory)
|
||||
|
||||
# Create directory for xccc & arc.rc location
|
||||
Dir.mkdir(BIN_DIR) unless File.exist?(BIN_DIR)
|
||||
|
||||
# Remove previous xccc & arc.rc
|
||||
was_previously_enabled = File.exist?(remote_commit_file_absolute)
|
||||
File.delete(remote_commit_file_absolute) if File.exist?(remote_commit_file_absolute)
|
||||
|
||||
prepare_result = YAML.load`#{xcrc_location_absolute}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}`
|
||||
if !prepare_result['result'] && mode == 'consumer'
|
||||
# Remote cache is still disabled - no need to force Pods projects/targets regeneration
|
||||
next
|
||||
end
|
||||
|
||||
# Force rebuilding all Pods project, because XCRC build steps and settings need to be added to Pods project/targets
|
||||
# It is relevant only when 'incremental_installation' is enabled, otherwise installed_cache_path does not exist on a disk
|
||||
installed_cache_path = installer_context.sandbox.project_installation_cache_path
|
||||
if !was_previously_enabled && File.exist?(installed_cache_path)
|
||||
Pod::UI.puts "[XCRC] Forces Pods project regenerations because XCRC is enabled for the first time."
|
||||
File.delete(installed_cache_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Pod::HooksManager.register('cocoapods-xcremotecache', :post_install) do |installer_context|
|
||||
if @@configuration.nil?
|
||||
next
|
||||
end
|
||||
|
||||
user_project = installer_context.umbrella_targets[0].user_project
|
||||
|
||||
begin
|
||||
begin
|
||||
user_proj_directory = File.dirname(user_project.path)
|
||||
set_configuration_default_values
|
||||
|
||||
@@ -391,23 +497,16 @@ module CocoapodsXCRemoteCacheModifier
|
||||
check_build_configuration = @@configuration['check_build_configuration']
|
||||
check_platform = @@configuration['check_platform']
|
||||
fake_src_root = @@configuration['fake_src_root']
|
||||
exclude_sdks_configurations = @@configuration['exclude_sdks_configurations'] || []
|
||||
|
||||
xccc_location_absolute = "#{user_proj_directory}/#{xccc_location}"
|
||||
xcrc_location_absolute = "#{user_proj_directory}/#{xcrc_location}"
|
||||
remote_commit_file_absolute = "#{user_proj_directory}/#{remote_commit_file}"
|
||||
|
||||
# Download XCRC
|
||||
download_xcrc_if_needed(xcrc_location_absolute)
|
||||
|
||||
# Save .rcinfo
|
||||
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)
|
||||
|
||||
# Remove previous xccc & arc.rc
|
||||
File.delete(remote_commit_file_absolute) if File.exist?(remote_commit_file_absolute)
|
||||
# Remove previous xccc
|
||||
File.delete(xccc_location_absolute) if File.exist?(xccc_location_absolute)
|
||||
|
||||
# Prepare XCRC
|
||||
@@ -422,7 +521,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
next if target.name.start_with?("Pods-")
|
||||
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,fake_src_root)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations)
|
||||
end
|
||||
|
||||
# Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
|
||||
@@ -435,18 +534,18 @@ module CocoapodsXCRemoteCacheModifier
|
||||
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,fake_src_root)
|
||||
enable_xcremotecache(target, 1, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations)
|
||||
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}"
|
||||
@@ -458,6 +557,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
|
||||
# Enabled/disable XCRemoteCache for the main (user) project
|
||||
begin
|
||||
# TODO: Do not compile xcc again. `xcprepare` compiles it in pre-install anyway
|
||||
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
|
||||
@@ -475,7 +575,7 @@ module CocoapodsXCRemoteCacheModifier
|
||||
# Attach XCRC to the app targets
|
||||
user_project.targets.each do |target|
|
||||
next if exclude_targets.include?(target.name)
|
||||
enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root)
|
||||
enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target,fake_src_root, exclude_sdks_configurations)
|
||||
end
|
||||
|
||||
# Set Target sourcemap
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
# Licensed 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.
|
||||
@@ -13,5 +13,5 @@
|
||||
# limitations under the License.
|
||||
|
||||
module CocoapodsXcremotecache
|
||||
VERSION = "0.0.13"
|
||||
VERSION = "0.0.16"
|
||||
end
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
36201A2A2843B3D3002FF70F /* MixedTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A292843B3D3002FF70F /* MixedTarget.swift */; };
|
||||
36201A362843B435002FF70F /* libMixedTarget.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 36201A272843B3D3002FF70F /* libMixedTarget.a */; };
|
||||
36201A392843BDDC002FF70F /* StandaloneObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 36201A382843BDDC002FF70F /* StandaloneObjc.m */; };
|
||||
4E10D63029BBFD8000A8655C /* WatchExtensionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E10D62F29BBFD8000A8655C /* WatchExtensionExtension.swift */; };
|
||||
4E10D63229BBFD8000A8655C /* WatchExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E10D63129BBFD8000A8655C /* WatchExtension.swift */; };
|
||||
4EE6CF4929B6C1A000AEE1B4 /* StaticFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EE6CF4829B6C1A000AEE1B4 /* StaticFramework.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4EE6CF5329B6C1AF00AEE1B4 /* StaticFrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE6CF5229B6C1AF00AEE1B4 /* StaticFrameworkFile.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -26,6 +30,13 @@
|
||||
remoteGlobalIDString = 36201A262843B3D3002FF70F;
|
||||
remoteInfo = MixedTarget;
|
||||
};
|
||||
4E10D63729BBFD8E00A8655C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 36201A042843B3C3002FF70F /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 4EE6CF4529B6C1A000AEE1B4;
|
||||
remoteInfo = StaticFramework;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -38,6 +49,16 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EE6CF4E29B6C1A000AEE1B4 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -55,6 +76,13 @@
|
||||
36201A302843B414002FF70F /* SomeObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SomeObjC.h; sourceTree = "<group>"; };
|
||||
36201A372843BDDC002FF70F /* StandaloneObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StandaloneObjc.h; sourceTree = "<group>"; };
|
||||
36201A382843BDDC002FF70F /* StandaloneObjc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StandaloneObjc.m; sourceTree = "<group>"; };
|
||||
4E10D62D29BBFD8000A8655C /* WatchExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = WatchExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4E10D62F29BBFD8000A8655C /* WatchExtensionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchExtensionExtension.swift; sourceTree = "<group>"; };
|
||||
4E10D63129BBFD8000A8655C /* WatchExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchExtension.swift; sourceTree = "<group>"; };
|
||||
4E10D63329BBFD8000A8655C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4EE6CF4629B6C1A000AEE1B4 /* StaticFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StaticFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4EE6CF4829B6C1A000AEE1B4 /* StaticFramework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StaticFramework.h; sourceTree = "<group>"; };
|
||||
4EE6CF5229B6C1AF00AEE1B4 /* StaticFrameworkFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticFrameworkFile.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -73,6 +101,20 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4E10D62A29BBFD8000A8655C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EE6CF4329B6C1A000AEE1B4 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -81,6 +123,8 @@
|
||||
children = (
|
||||
36201A0E2843B3C3002FF70F /* StandaloneApp */,
|
||||
36201A282843B3D3002FF70F /* MixedTarget */,
|
||||
4EE6CF4729B6C1A000AEE1B4 /* StaticFramework */,
|
||||
4E10D62E29BBFD8000A8655C /* WatchExtension */,
|
||||
36201A0D2843B3C3002FF70F /* Products */,
|
||||
36201A352843B435002FF70F /* Frameworks */,
|
||||
);
|
||||
@@ -91,6 +135,8 @@
|
||||
children = (
|
||||
36201A0C2843B3C3002FF70F /* StandaloneApp.app */,
|
||||
36201A272843B3D3002FF70F /* libMixedTarget.a */,
|
||||
4EE6CF4629B6C1A000AEE1B4 /* StaticFramework.framework */,
|
||||
4E10D62D29BBFD8000A8655C /* WatchExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -128,8 +174,38 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E10D62E29BBFD8000A8655C /* WatchExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4E10D62F29BBFD8000A8655C /* WatchExtensionExtension.swift */,
|
||||
4E10D63129BBFD8000A8655C /* WatchExtension.swift */,
|
||||
4E10D63329BBFD8000A8655C /* Info.plist */,
|
||||
);
|
||||
path = WatchExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4EE6CF4729B6C1A000AEE1B4 /* StaticFramework */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4EE6CF5229B6C1AF00AEE1B4 /* StaticFrameworkFile.swift */,
|
||||
4EE6CF4829B6C1A000AEE1B4 /* StaticFramework.h */,
|
||||
);
|
||||
path = StaticFramework;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
4EE6CF4129B6C1A000AEE1B4 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4EE6CF4929B6C1A000AEE1B4 /* StaticFramework.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
36201A0B2843B3C3002FF70F /* StandaloneApp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
@@ -138,6 +214,7 @@
|
||||
36201A082843B3C3002FF70F /* Sources */,
|
||||
36201A092843B3C3002FF70F /* Frameworks */,
|
||||
36201A0A2843B3C3002FF70F /* Resources */,
|
||||
4EE6CF4E29B6C1A000AEE1B4 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -167,6 +244,42 @@
|
||||
productReference = 36201A272843B3D3002FF70F /* libMixedTarget.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
4E10D62C29BBFD8000A8655C /* WatchExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4E10D63429BBFD8000A8655C /* Build configuration list for PBXNativeTarget "WatchExtension" */;
|
||||
buildPhases = (
|
||||
4E10D62929BBFD8000A8655C /* Sources */,
|
||||
4E10D62A29BBFD8000A8655C /* Frameworks */,
|
||||
4E10D62B29BBFD8000A8655C /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
4E10D63829BBFD8E00A8655C /* PBXTargetDependency */,
|
||||
);
|
||||
name = WatchExtension;
|
||||
productName = WatchExtension;
|
||||
productReference = 4E10D62D29BBFD8000A8655C /* WatchExtension.appex */;
|
||||
productType = "com.apple.product-type.extensionkit-extension";
|
||||
};
|
||||
4EE6CF4529B6C1A000AEE1B4 /* StaticFramework */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4EE6CF5129B6C1A000AEE1B4 /* Build configuration list for PBXNativeTarget "StaticFramework" */;
|
||||
buildPhases = (
|
||||
4EE6CF4129B6C1A000AEE1B4 /* Headers */,
|
||||
4EE6CF4229B6C1A000AEE1B4 /* Sources */,
|
||||
4EE6CF4329B6C1A000AEE1B4 /* Frameworks */,
|
||||
4EE6CF4429B6C1A000AEE1B4 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = StaticFramework;
|
||||
productName = StaticFramework;
|
||||
productReference = 4EE6CF4629B6C1A000AEE1B4 /* StaticFramework.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -174,7 +287,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastSwiftUpdateCheck = 1420;
|
||||
LastUpgradeCheck = 1320;
|
||||
TargetAttributes = {
|
||||
36201A0B2843B3C3002FF70F = {
|
||||
@@ -185,6 +298,13 @@
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
LastSwiftMigration = 1320;
|
||||
};
|
||||
4E10D62C29BBFD8000A8655C = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
};
|
||||
4EE6CF4529B6C1A000AEE1B4 = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
LastSwiftMigration = 1420;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 36201A072843B3C3002FF70F /* Build configuration list for PBXProject "StandaloneApp" */;
|
||||
@@ -202,6 +322,8 @@
|
||||
targets = (
|
||||
36201A0B2843B3C3002FF70F /* StandaloneApp */,
|
||||
36201A262843B3D3002FF70F /* MixedTarget */,
|
||||
4EE6CF4529B6C1A000AEE1B4 /* StaticFramework */,
|
||||
4E10D62C29BBFD8000A8655C /* WatchExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -217,6 +339,20 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4E10D62B29BBFD8000A8655C /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EE6CF4429B6C1A000AEE1B4 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@@ -240,7 +376,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\nditto \"${SCRIPT_INPUT_FILE_1}\" \"${SCRIPT_OUTPUT_FILE_1}\" || true\n\n";
|
||||
shellScript = "ditto \"${SCRIPT_INPUT_FILE_0}\" \"${SCRIPT_OUTPUT_FILE_0}\"\n[ -f \"${SCRIPT_INPUT_FILE_1}\" ] && ditto \"${SCRIPT_INPUT_FILE_1}\" \"${SCRIPT_OUTPUT_FILE_1}\" || rm \"${SCRIPT_OUTPUT_FILE_1}\"\n\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@@ -264,6 +400,23 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4E10D62929BBFD8000A8655C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4E10D63029BBFD8000A8655C /* WatchExtensionExtension.swift in Sources */,
|
||||
4E10D63229BBFD8000A8655C /* WatchExtension.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EE6CF4229B6C1A000AEE1B4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4EE6CF5329B6C1AF00AEE1B4 /* StaticFrameworkFile.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
@@ -272,6 +425,11 @@
|
||||
target = 36201A262843B3D3002FF70F /* MixedTarget */;
|
||||
targetProxy = 36201A332843B431002FF70F /* PBXContainerItemProxy */;
|
||||
};
|
||||
4E10D63829BBFD8E00A8655C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 4EE6CF4529B6C1A000AEE1B4 /* StaticFramework */;
|
||||
targetProxy = 4E10D63729BBFD8E00A8655C /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
@@ -413,6 +571,7 @@
|
||||
36201A212843B3C7002FF70F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -443,6 +602,7 @@
|
||||
36201A222843B3C7002FF70F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -508,6 +668,135 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4E10D63529BBFD8000A8655C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = WatchExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = WatchExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.WatchExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4E10D63629BBFD8000A8655C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = WatchExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = WatchExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.WatchExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 4;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4EE6CF4F29B6C1A000AEE1B4 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MARKETING_VERSION = 1.0;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.squareup.StaticFramework;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4EE6CF5029B6C1A000AEE1B4 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACH_O_TYPE = staticlib;
|
||||
MARKETING_VERSION = 1.0;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.squareup.StaticFramework;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = watchos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
VERSION_INFO_PREFIX = "";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 4.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -538,6 +827,24 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4E10D63429BBFD8000A8655C /* Build configuration list for PBXNativeTarget "WatchExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4E10D63529BBFD8000A8655C /* Debug */,
|
||||
4E10D63629BBFD8000A8655C /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4EE6CF5129B6C1A000AEE1B4 /* Build configuration list for PBXNativeTarget "StaticFramework" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4EE6CF4F29B6C1A000AEE1B4 /* Debug */,
|
||||
4EE6CF5029B6C1A000AEE1B4 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 36201A042843B3C3002FF70F /* Project object */;
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4E10D61129BBF2FA00A8655C"
|
||||
BuildableName = "SampleWatchApp.app"
|
||||
BlueprintName = "SampleWatchApp"
|
||||
ReferencedContainer = "container:StandaloneApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4E10D61129BBF2FA00A8655C"
|
||||
BuildableName = "SampleWatchApp.app"
|
||||
BlueprintName = "SampleWatchApp"
|
||||
ReferencedContainer = "container:StandaloneApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4E10D61129BBF2FA00A8655C"
|
||||
BuildableName = "SampleWatchApp.app"
|
||||
BlueprintName = "SampleWatchApp"
|
||||
ReferencedContainer = "container:StandaloneApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4E10D61129BBF2FA00A8655C"
|
||||
BuildableName = "SampleWatchApp.app"
|
||||
BlueprintName = "SampleWatchApp"
|
||||
ReferencedContainer = "container:StandaloneApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1420"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4EE6CF4529B6C1A000AEE1B4"
|
||||
BuildableName = "StaticFramework.framework"
|
||||
BlueprintName = "StaticFramework"
|
||||
ReferencedContainer = "container:StandaloneApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4EE6CF4529B6C1A000AEE1B4"
|
||||
BuildableName = "StaticFramework.framework"
|
||||
BlueprintName = "StaticFramework"
|
||||
ReferencedContainer = "container:StandaloneApp.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for StaticFramework.
|
||||
FOUNDATION_EXPORT double StaticFrameworkVersionNumber;
|
||||
|
||||
//! Project version string for StaticFramework.
|
||||
FOUNDATION_EXPORT const unsigned char StaticFrameworkVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <StaticFramework/PublicHeader.h>
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
public class SomeClass {
|
||||
public init() {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?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>EXAppExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>EXExtensionPointIdentifier</key>
|
||||
<string>com.apple.appintents-extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,2 @@
|
||||
import AppIntents
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import AppIntents
|
||||
|
||||
struct WatchExtensionExtension: AppIntentsExtension {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
### How to add a CocoaPods scenario
|
||||
|
||||
In order to run different test suites in cocoapods integration, you can create multiple `.Podfile` files as a "starting point" of the E2E test.
|
||||
|
||||
#### Files organization
|
||||
|
||||
1. File `{SUITE_NAME}.Podfile` contains a base Podfile that should be excercised
|
||||
1. [Optional] File `{SUITE_NAME}.Podfile.config` contains a hash of extra xcremote-cache-plugin parameters that should be added to the `xcremotecache()` configuration in a podfile
|
||||
1. [Optional] File `{SUITE_NAME}.Podfile.expectations` overrides a set of default validation steps that should be checked after consumer's build. Supported steps are: `hits`, `misses` or `hit_rate` (e.g. `100` for 100$). By default, 100% `hit_rate` and 0 `misses` are used - if you want to skip a validation step for a given suite, you can set it to `null` in a dictionary
|
||||
@@ -0,0 +1,8 @@
|
||||
plugin 'cocoapods-xcremotecache'
|
||||
|
||||
target 'XCRemoteCacheSample' do
|
||||
use_frameworks!
|
||||
|
||||
pod 'Firebase/Analytics'
|
||||
pod 'ReactiveSwift'
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"exclude_sdks_configurations": ["iphoneos*", "iphonesimulator*"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"hits": 0,
|
||||
"misses": 0,
|
||||
"hit_rate": null
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
plugin 'cocoapods-xcremotecache'
|
||||
|
||||
target 'XCRemoteCacheSample' do
|
||||
use_frameworks!
|
||||
|
||||
pod 'Firebase/Analytics'
|
||||
pod 'ReactiveSwift'
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"exclude_sdks_configurations": ["watchos*", "watchsimulator*"]
|
||||
}
|
||||
+59
-27
@@ -1,4 +1,5 @@
|
||||
require 'json'
|
||||
require "ostruct"
|
||||
|
||||
desc 'Support for E2E tests: building XCRemoteCache-enabled xcodeproj using xcodebuild'
|
||||
namespace :e2e do
|
||||
@@ -15,19 +16,22 @@ namespace :e2e do
|
||||
NGINX_ROOT_DIR = '/tmp/cache'
|
||||
XCRC_BINARIES = 'XCRC'
|
||||
SHARED_COCOAPODS_CONFIG = {
|
||||
'cache_addresses' => ['http://localhost:8080/cache/pods'],
|
||||
'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
|
||||
}
|
||||
}.freeze
|
||||
DEFAULT_EXPECTATIONS = {
|
||||
'misses' => 0,
|
||||
'hit_rate' => 100
|
||||
}.freeze
|
||||
|
||||
Stats = Struct.new(:hits, :misses, :hit_rate)
|
||||
|
||||
# run E2E tests
|
||||
# TODO: add :run_standalone when support for bridging headers support is ready
|
||||
task :run => [:run_cocoapods]
|
||||
task :run => [:run_cocoapods, :run_standalone]
|
||||
|
||||
# run E2E tests for CocoaPods-powered projects
|
||||
task :run_cocoapods do
|
||||
@@ -56,10 +60,10 @@ namespace :e2e do
|
||||
Dir.chdir(E2E_STANDALONE_SAMPLE_DIR) do
|
||||
clean_git
|
||||
# Run integrate the project
|
||||
p "#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode producer --final-producer-target StandaloneApp"
|
||||
system("pwd")
|
||||
system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode producer --final-producer-target StandaloneApp")
|
||||
# Build the project to fill in the cache
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS')
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp')
|
||||
system("#{XCRC_BINARIES}/xcprepare stats --reset --format json")
|
||||
end
|
||||
@@ -75,13 +79,15 @@ namespace :e2e do
|
||||
prepare_for_standalone(consumer_srcroot)
|
||||
Dir.chdir(consumer_srcroot) do
|
||||
system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode consumer")
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
|
||||
valide_hit_rate
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
|
||||
valide_hit_rate(OpenStruct.new(DEFAULT_EXPECTATIONS))
|
||||
|
||||
puts 'Building standalone consumer with local change...'
|
||||
# Extra: validate local compilation of the Standalone ObjC code
|
||||
system("echo '' >> StandaloneApp/StandaloneObjc.m")
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"})
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"})
|
||||
build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"})
|
||||
end
|
||||
|
||||
# Revert all side effects
|
||||
@@ -105,7 +111,7 @@ namespace :e2e do
|
||||
at_exit { puts('resetting ngingx'); system('nginx -s stop') }
|
||||
end
|
||||
|
||||
# Create a new branch out of a current commit and
|
||||
# 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}")
|
||||
@@ -146,7 +152,7 @@ namespace :e2e do
|
||||
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},"}
|
||||
config_lines = all_properties.map {|key, value| " \"#{key}\" => #{value.inspect},"}
|
||||
configuration_lines.push(*config_lines)
|
||||
configuration_lines << '})'
|
||||
configuration_lines.join("\n")
|
||||
@@ -161,20 +167,20 @@ namespace :e2e do
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_project(workspace, project, scheme, extra_args = {})
|
||||
def self.build_project(workspace, project, scheme, sdk = 'iphone', platform = 'iOS', extra_args = {})
|
||||
xcodebuild_args = {
|
||||
'workspace' => workspace,
|
||||
'project' => project,
|
||||
'scheme' => scheme,
|
||||
'configuration' => 'Debug',
|
||||
'sdk' => 'iphonesimulator',
|
||||
'destination' => 'generic/platform=iOS Simulator',
|
||||
'sdk' => "#{sdk}simulator",
|
||||
'destination' => "generic/platform=#{platform} Simulator",
|
||||
'derivedDataPath' => DERIVED_DATA_PATH,
|
||||
}.merge(extra_args).compact
|
||||
xcodebuild_vars = {
|
||||
'EXCLUDED_ARCHS' => 'arm64 i386'
|
||||
'EXCLUDED_ARCHS' => 'arm64'
|
||||
}
|
||||
args = ['xcodebuild']
|
||||
args = ['set -o pipefail;', 'xcodebuild']
|
||||
args.push(*xcodebuild_args.map {|k,v| "-#{k} '#{v}'"})
|
||||
args.push(*xcodebuild_vars.map {|k,v| "#{k}='#{v}'"})
|
||||
args.push('clean build')
|
||||
@@ -187,35 +193,46 @@ namespace :e2e do
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_project_cocoapods(extra_args = {})
|
||||
def self.build_project_cocoapods(sdk = 'iphone', platform = 'iOS', extra_args = {})
|
||||
system('pod install')
|
||||
build_project('XCRemoteCacheSample.xcworkspace', nil, 'XCRemoteCacheSample', extra_args)
|
||||
build_project('XCRemoteCacheSample.xcworkspace', nil, 'XCRemoteCacheSample', sdk, platform, extra_args)
|
||||
end
|
||||
|
||||
def self.read_stats
|
||||
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
|
||||
hit_rate = all_targets == 0 ? nil : hits * 100 / all_targets
|
||||
Stats.new(hits, misses, hit_rate)
|
||||
end
|
||||
|
||||
# validate 100% hit rate
|
||||
def self.valide_hit_rate
|
||||
def self.valide_hit_rate(expectations)
|
||||
status = read_stats()
|
||||
all_targets = status.misses + status.hits
|
||||
raise "Failure: Hit rate is only #{status.hit_rate}% (#{all_targets})" if status.misses > 0
|
||||
unless expectations.misses.nil?
|
||||
raise "Failure: Unexpected misses: #{status.misses} (#{all_targets}). Expected #{expectations.misses}" if status.misses != expectations.misses
|
||||
end
|
||||
unless expectations.hit_rate.nil?
|
||||
raise "Failure: Hit rate is #{status.hit_rate}% (#{all_targets}). Expected #{expectations.hit_rate}%" if status.hit_rate != expectations.hit_rate
|
||||
end
|
||||
unless expectations.hits.nil?
|
||||
raise "Failure: Hits count is #{status.hit_rate}% (#{all_targets}). Expected #{expectations.hits}" if status.hits != expectations.hits
|
||||
end
|
||||
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()
|
||||
# Optional file, which adds extra cocoapods configs to a template
|
||||
template_config_path = "#{template_path}.config"
|
||||
extra_config = File.exist?(template_config_path) ? JSON.load(File.read(template_config_path)) : {}
|
||||
producer_configuration = cocoapods_configuration_string({'mode' => 'producer'}.merge(extra_config))
|
||||
consumer_configuration = cocoapods_configuration_string(extra_config)
|
||||
expectations = build_expectations(template_path)
|
||||
|
||||
puts("****** Scenario: #{template_path}")
|
||||
|
||||
|
||||
# Run producer build
|
||||
pre_producer_setup
|
||||
dump_podfile(producer_configuration, template_path)
|
||||
@@ -231,8 +248,8 @@ namespace :e2e do
|
||||
dump_podfile(consumer_configuration, template_path)
|
||||
puts('Building consumer ...')
|
||||
Dir.chdir(E2E_COCOAPODS_SAMPLE_DIR) do
|
||||
build_project_cocoapods({'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
|
||||
valide_hit_rate
|
||||
build_project_cocoapods('iphone', 'iOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
|
||||
valide_hit_rate(expectations)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -240,4 +257,19 @@ namespace :e2e do
|
||||
clean_git
|
||||
system("ln -s $(pwd)/releases #{dir}/#{XCRC_BINARIES}")
|
||||
end
|
||||
|
||||
# Returns a hash of all expectations that should be validated for a template
|
||||
# The implementation assumes 100% hitrate and extra expecations can be provided in an optional file
|
||||
# #{template_path}.expectations
|
||||
def self.build_expectations(template_path)
|
||||
expectations = DEFAULT_EXPECTATIONS.dup
|
||||
return expectations if template_path.nil?
|
||||
|
||||
template_config_path = "#{template_path}.expectations"
|
||||
if File.exist?(template_config_path)
|
||||
extra_config = File.exist?(template_config_path) ? JSON.load(File.read(template_config_path)) : {}
|
||||
expectations.merge!(extra_config)
|
||||
end
|
||||
OpenStruct.new(expectations)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user