Compare commits

..

37 Commits

Author SHA1 Message Date
Vadim Smal 25ff5a790b Merge pull request #157 from CognitiveDisson/addUploadRetryDelay
Add a delay between network request retries
2022-07-15 18:38:20 +01:00
Vadim Smal f446f6061d Fix config read 2022-07-15 18:01:22 +01:00
Vadim Smal b46d1e2ca1 Fixed retryDelay decoding 2022-07-14 18:06:35 +01:00
Vadim Smal 88d0666da9 Fix swiftlint violations 2022-07-14 17:44:31 +01:00
Vadim Smal 08bf5c21bf Update documentation 2022-07-14 17:38:48 +01:00
Vadim Smal be97a6a247 Fix swiftlint violations 2022-07-14 17:37:46 +01:00
Vadim Smal dff71f8070 Add a delay between upload retries 2022-07-14 16:59:40 +01:00
Vadim Smal cda5fc1188 Add a delay between upload retries 2022-07-11 17:21:51 +01:00
Bartosz Polaczyk 15586755d2 Merge pull request #152 from polac24/xcode14-fallback
Xcode 14.0 support
2022-06-20 09:29:41 +02:00
Bartosz Polaczyk e978eb182c Add limitation 2022-06-18 17:51:54 +02:00
Bartosz Polaczyk 7e98cebdd9 Merge remote-tracking branch 'upstream/master' into xcode14-fallback 2022-06-18 17:48:18 +02:00
Bartosz Polaczyk fbc2982aa3 [Xcode 14.0] Disable Swift integration driver 2022-06-18 17:43:33 +02:00
Bartosz Polaczyk cbcc028cad Merge pull request #151 from ainopara/fix/denpendency-writer-white-space
Escape white space in denpendency writer
2022-06-14 15:25:57 +02:00
ainopara 2acf97eca1 Fix lint issue 2022-06-14 13:37:33 +08:00
ainopara 40803cf747 Add unit test 2022-06-13 14:45:15 +08:00
ainopara 97496ed7b8 Escape white space in denpendency writer 2022-06-13 01:35:15 +08:00
Bartosz Polaczyk 45222f8e33 Merge pull request #150 from jarbogast-salesforce/cplusplus-support
Support C++ Linker
2022-06-11 22:26:58 +02:00
Jonathan Arbogast 91b5b5e590 Support C++ Linker 2022-06-11 15:07:30 -04:00
Bartosz Polaczyk f2a7880c24 Merge pull request #148 from polac24/20220606-integrate-postbuild
Place postbuild build phase right after compilation
2022-06-07 07:29:14 -04:00
Bartosz Polaczyk 00cb8cc23d Merge pull request #147 from polac24/20220606-bridging-headers-enum-test
Add E2E test for exposing public enums from ObjC to Swift
2022-06-07 07:28:56 -04:00
Bartosz Polaczyk a3cd6bea07 Add postbuild right after compilation for CocoaPods 2022-06-06 17:53:11 -04:00
Bartosz Polaczyk 86c762b070 Add postbuild right after compilation for integrate 2022-06-06 17:49:44 -04:00
Bartosz Polaczyk bd21156695 Add E2E test for exposing public enums from ObjC to Swift 2022-06-06 17:34:14 -04:00
Bartosz Polaczyk 3a82ad91b2 Merge pull request #144 from devMEremenko/fix-caching-in-env-fingerprint-generator
Fixed caching in `generatedFingerprint` in `EnvironmentFingerprintGenerator`
2022-06-02 23:03:38 +02:00
Maxim Eremenko 5a5bf35c4a Clean up 2022-06-02 22:46:10 +03:00
Maxim Eremenko 1f766ad4a4 Fixed SwiftLint in EnvironmentFingerprintGeneratorTests 2022-06-02 22:34:23 +03:00
Maxim Eremenko 3c3cd84d81 Fixed swiftlint issues 2022-06-02 22:23:42 +03:00
Maxim Eremenko 7728733aef Cached generatedFingerprint in EnvironmentFingerprintGenerator 2022-06-02 21:06:41 +03:00
Bartosz Polaczyk 50580bf9fd Merge pull request #139 from samuelsainz/feature/postbuild-dependencies-reader-perf-improvement
Improved DependenciesReader performance (~10x faster)
2022-05-24 17:40:21 +02:00
Samuel Sainz ceaff318d6 Fixed SwiftLint issues and CR improvements 2022-05-24 11:09:18 -03:00
Samuel Sainz 4cc932a592 Improved Dependencies Reader performance to be 10x faster 2022-05-23 15:31:41 -03:00
Bartosz Polaczyk 75bef0baf6 Merge pull request #136 from polac24/20220517-fix-vendored-frameworks
Support Vendored frameworks in the CocoaPods development pod
2022-05-19 09:03:04 +02:00
Bartosz Polaczyk 03671e71b7 Support Vendored frameworks in the CocoaPods plugin 2022-05-17 19:46:15 +02:00
Bartosz Polaczyk 91505a59d2 Merge pull request #135 from polac24/fix-hybrid-incremental
Fix hybrid incremental performance
2022-05-16 09:43:46 +02:00
Bartosz Polaczyk 59c1d999b1 Always clean history when prebuilding 2022-05-15 20:04:27 +02:00
Bartosz Polaczyk ba58c1c21e Do not add marker dependency 2022-05-15 19:59:42 +02:00
Bartosz Polaczyk 96ce18bc31 Retrigger clang compilations when a target is switching between cache miss<->hit 2022-05-13 12:07:12 +02:00
49 changed files with 2435 additions and 94 deletions
+1
View File
@@ -64,6 +64,7 @@ excluded:
- fastlane/
- DerivedData/
- e2eTests/XCRemoteCacheSample/Pods
- e2eTests/StandaloneSampleApp
attributes:
always_on_same_line:
+5 -1
View File
@@ -51,10 +51,14 @@ let package = Package(
name: "xcld",
dependencies: ["XCRemoteCache"]
),
.target(
name: "xcldplusplus",
dependencies: ["XCRemoteCache"]
),
.target(
// Wrapper target that builds all binaries but does nothing in runtime
name: "Aggregator",
dependencies: ["xcprebuild", "xcswiftc", "xclibtool", "xcpostbuild", "xcprepare", "xcld"]
dependencies: ["xcprebuild", "xcswiftc", "xclibtool", "xcpostbuild", "xcprepare", "xcld", "xcldplusplus"]
),
.testTarget(
name: "XCRemoteCacheTests",
+5 -1
View File
@@ -196,7 +196,9 @@ Configure Xcode targets that **should use** XCRemoteCache:
* `SWIFT_EXEC` - location of `xcprepare` (e.g. `xcremotecache/xcswiftc`)
* `LIBTOOL` - location of `xclibtool` (e.g. `xcremotecache/xclibtool`)
* `LD` - location of `xcld` (e.g. `xcremotecache/xcld`)
* `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)`
* `SWIFT_USE_INTEGRATED_DRIVER` - `NO` (required in Xcode 14.0+)
<details>
<summary>Screenshot</summary>
@@ -265,7 +267,7 @@ $ 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`, `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` wrappers become no-op, so it is recommended to not add them for the `producer` mode._
## A full list of configuration parameters:
@@ -296,6 +298,7 @@ _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` | ⬜️ |
| `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 | `[]` | ⬜️ |
@@ -416,6 +419,7 @@ Note: This setup is not recommended and may not be supported in future XCRemoteC
* Swift Package Manager (SPM) dependencies are not supported. _Because SPM does not allow customizing Build Settings, XCRemoteCache cannot specify `clang` and `swiftc` wrappers that control if the local compilation should be skipped (cache hit) or not (cache miss)_
* Filenames with `_vers.c` suffix are reserved and cannot be used as a source file
* All compilation files should be referenced via the git repo root. Referencing `/AbsolutePath/someOther.swift` or `../../someOther.swift` that resolve to the location outside of the git repo root is prohibited.
* The new Swift driver (introduced by default in Xcode 14.0) is not supported and has to be disabled when using XCRemoteCache
## FAQ
+1 -1
View File
@@ -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']
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus']
PROJECT_NAME = 'XCRemoteCache'
SWIFTLINT_ENABLED = true
@@ -123,6 +123,7 @@ public class XCPostbuild {
let networkClient = NetworkClientImpl(
session: sessionFactory.build(),
retries: config.uploadRetries,
retryDelay: config.retryDelay,
fileManager: fileManager,
awsV4Signature: awsV4Signature
)
@@ -78,6 +78,10 @@ public class XCPrebuild {
exit(0)
}
let compilationHistoryOrganizer = CompilationHistoryFileOrganizer(
context.compilationHistoryFile,
fileManager: fileManager
)
do {
let envFingerprint = try EnvironmentFingerprintGenerator(
configuration: config,
@@ -105,6 +109,7 @@ public class XCPrebuild {
let networkClient = NetworkClientImpl(
session: sessionFactory.build(),
retries: config.downloadRetries,
retryDelay: config.retryDelay,
fileManager: fileManager,
awsV4Signature: awsV4Signature
)
@@ -148,10 +153,6 @@ public class XCPrebuild {
algorithm: MD5Algorithm()
)
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
let compilationHistoryOrganizer = CompilationHistoryFileOrganizer(
context.compilationHistoryFile,
fileManager: fileManager
)
let metaReader = JsonMetaReader(fileAccessor: fileManager)
var consumerPlugins: [ArtifactConsumerPrebuildPlugin] = []
@@ -189,7 +190,6 @@ public class XCPrebuild {
case .compatible(localDependencies: let dependencies):
// TODO: pass `allowedInputFiles` observed in the build time
try modeController.enable(allowedInputFiles: dependencies, dependencies: dependencies)
compilationHistoryOrganizer.reset()
}
} catch {
disableRemoteCache(
@@ -197,6 +197,7 @@ public class XCPrebuild {
errorMessage: "Prebuild step failed with error: \(error)"
)
}
compilationHistoryOrganizer.reset()
}
private func disableRemoteCache(modeController: PhaseCacheModeController, errorMessage: String?) {
@@ -44,6 +44,7 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
func appendToBuildSettings(buildSettings: BuildSettings, wrappers: XCRCBinariesPaths) -> BuildSettings {
var result = buildSettings
result["SWIFT_EXEC"] = wrappers.swiftc.path
result["SWIFT_USE_INTEGRATED_DRIVER"] = "NO"
// When generating artifacts, no need to shell-out all compilation commands to our wrappers
if case .consumer = mode {
result["CC"] = wrappers.cc.path
@@ -53,6 +53,7 @@ extension IntegrateContext {
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
libtool: binariesDir.appendingPathComponent("xclibtool"),
ld: binariesDir.appendingPathComponent("xcld"),
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
postbuild: binariesDir.appendingPathComponent("xcpostbuild")
)
@@ -26,6 +26,7 @@ struct XCRCBinariesPaths {
let swiftc: URL
let libtool: URL
let ld: URL
let ldplusplus: URL
let prebuild: URL
let postbuild: URL
}
@@ -230,10 +230,12 @@ struct XcodeProjIntegrate: Integrate {
if let sourceIndex = target.buildPhases.map(\.buildPhase).firstIndex(of: .sources) {
// add (pre|post)build phases only when a target has some compilation steps
// otherwise they make no sense (nothing to store in an artifact)
// add postbuild right after compilation as custom build steps may depend on files generated by
// the xcpostbuild (e.g. to copy -Swift.h.md5)
pbxproj.add(object: postbuildPhase)
target.buildPhases.insert(postbuildPhase, at: sourceIndex + 1)
pbxproj.add(object: prebuildPhase)
target.buildPhases.insert(prebuildPhase, at: sourceIndex)
pbxproj.add(object: postbuildPhase)
target.buildPhases.append(postbuildPhase)
}
}
@@ -87,6 +87,7 @@ public class XCPrepare {
let networkClient = NetworkClientImpl(
session: sessionFactory.build(),
retries: config.downloadRetries,
retryDelay: config.retryDelay,
fileManager: fileManager,
awsV4Signature: awsV4Signature
)
@@ -69,6 +69,7 @@ public class XCPrepareMark {
let networkClient = NetworkClientImpl(
session: sessionFactory.build(),
retries: config.uploadRetries,
retryDelay: config.retryDelay,
fileManager: fileManager,
awsV4Signature: awsV4Signature
)
@@ -83,6 +83,8 @@ 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
/// 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
@@ -175,6 +177,7 @@ extension XCRemoteCacheConfig {
merge.statsDir = scheme.statsDir ?? statsDir
merge.downloadRetries = scheme.downloadRetries ?? downloadRetries
merge.uploadRetries = scheme.uploadRetries ?? uploadRetries
merge.retryDelay = scheme.retryDelay ?? retryDelay
merge.requestCustomHeaders = scheme.requestCustomHeaders ?? requestCustomHeaders
merge.thinTargetMockFilename = scheme.thinTargetMockFilename ?? thinTargetMockFilename
merge.focusedTargets = scheme.focusedTargets ?? focusedTargets
@@ -243,6 +246,7 @@ struct ConfigFileScheme: Decodable {
let statsDir: String?
let downloadRetries: Int?
let uploadRetries: Int?
let retryDelay: Double?
let requestCustomHeaders: [String: String]?
let thinTargetMockFilename: String?
let focusedTargets: [String]?
@@ -291,6 +295,7 @@ struct ConfigFileScheme: Decodable {
case statsDir = "stats_dir"
case downloadRetries = "download_retries"
case uploadRetries = "upload_retries"
case retryDelay = "retry_delay"
case requestCustomHeaders = "request_custom_headers"
case thinTargetMockFilename = "thin_target_mock_filename"
case focusedTargets = "focused_targets"
@@ -357,6 +362,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
}
}
@@ -51,34 +51,22 @@ public class FileDependenciesReader: DependenciesReader {
public func findDependencies() throws -> [String] {
let yaml = try readRaw()
struct ParseState {
var buffer: String = ""
var prevChar: Character?
var result: [String] = []
func with(buffer: String? = nil, prevChar: Character? = nil, result: [String]? = nil) -> ParseState {
var new = self
new.buffer = buffer ?? new.buffer
new.prevChar = prevChar ?? new.prevChar
new.result = result ?? new.result
return new
}
}
let dependencies = yaml.reduce(Set<String>()) { prev, arg1 -> Set<String> in
let (key, value) = arg1
switch key {
case "dependencies":
// 'clang' output formatting
return Set(splitDependencyFileList(value))
return Set(parseDependencyFileList(value))
case let s where s.hasSuffix(".o") || s.hasSuffix(".bc"):
// 'swiftc' output formatting
// take dependencies from any .o or .bc file
// Note: For WMO, all .{o|bc} files have the same dependencies
return Set(splitDependencyFileList(value))
return Set(parseDependencyFileList(value))
default:
return prev
}
}
return Array(dependencies)
}
@@ -92,56 +80,92 @@ public class FileDependenciesReader: DependenciesReader {
return yaml.mapValues { $0.components(separatedBy: .whitespaces) }
}
private func readRaw() throws -> [String: String] {
func readRaw() throws -> [String: String] {
let fileData = try getFileData()
let fileString = try getFileStringFromData(fileData: fileData)
let yaml = try getYaml(fileString: fileString)
return yaml
}
func getFileData() throws -> Data {
guard let fileData = fileManager.contents(atPath: file.path) else {
throw DependenciesReaderError.readingError
}
return fileData
}
func getFileStringFromData(fileData: Data) throws -> String {
guard let fileString = String(data: fileData, encoding: .utf8) else {
throw DependenciesReaderError.invalidFile
}
// .d matches the .yaml format
return fileString
}
func getYaml(fileString: String) throws -> [String: String] {
guard let yaml = try Yams.load(yaml: fileString) as? [String: String] else {
throw DependenciesReaderError.invalidFile
}
return yaml
}
/// Splits space or new line separated files into a set of files
/// Parses the String to get the list of files
/// It iterates over the String using its UTF8View since it is more performant (String type operates
/// in a higher abstraction level and supports features that have a negative impact in the performance)
/// It supports escaping whitespace charaters, prefixed with "\\"
/// - Parameter string: string of whitespace charaters separated file paths
/// - Returns: Array of all file paths
private func splitDependencyFileList(_ string: String) -> [String] {
struct ParseState {
var buffer: String = ""
var prevChar: Character?
var result: [String] = []
func with(buffer: String? = nil, prevChar: Character? = nil, result: [String]? = nil) -> ParseState {
var new = self
new.buffer = buffer ?? new.buffer
new.prevChar = prevChar ?? new.prevChar
new.result = result ?? new.result
return new
}
func parseDependencyFileList(_ string: String) -> [String] {
var result: [String] = []
var prevChar: UTF8.CodeUnit?
// These index are used to move over the UTF8View of the string
// The goal is to optimize the memory used, since UTF8View uses
// the same memory as the original String without copying it
var startIndex = string.utf8.startIndex
var endIndex = startIndex
// This buffer is only used to save the part of the path that has been already parsed when finding a backslash
var buffer: String = ""
for c in string.utf8 {
switch c {
case UTF8.CodeUnit(ascii: "\n") where prevChar == UTF8.CodeUnit(ascii: "\\"):
startIndex = string.utf8.index(after: startIndex)
endIndex = startIndex
case UTF8.CodeUnit(ascii: " ") where startIndex == endIndex && buffer.isEmpty:
startIndex = string.utf8.index(after: startIndex)
endIndex = startIndex
case UTF8.CodeUnit(ascii: " ") where prevChar != UTF8.CodeUnit(ascii: "\\"):
// If a space is found and it is not escaped, then that's the end of the file path
buffer += String(Substring(string.utf8[startIndex ..< endIndex]))
result.append(buffer)
buffer = ""
prevChar = nil
startIndex = string.utf8.index(after: endIndex)
endIndex = startIndex
case UTF8.CodeUnit(ascii: "\\"):
// If a backslash is found it is not included in the file path
// The current parsed range of the UTF8View is saved in the buffer as a String
buffer += String(Substring(string.utf8[startIndex ..< endIndex]))
// The backslash is assigned as the previous char
prevChar = c
// The indexes are moved to the next char so we continue parsing the String
startIndex = string.utf8.index(after: endIndex)
endIndex = startIndex
default:
// As long as it is possible the indexes are used to track the range of the string that
// will be included in the file path (until it ends or until a backslash is found)
endIndex = string.utf8.index(after: endIndex)
// The char is assigned as the previous char
prevChar = c
}
}
let parseResult = string.reduce(ParseState()) { total, char in
switch char {
case "\n" where total.prevChar == "\\":
return total
case " " where total.buffer.isEmpty:
return total
case " " where total.prevChar == "\\":
return total.with(buffer: "\(total.buffer) ")
case " ":
return total.with(buffer: "", prevChar: nil, result: total.result + [total.buffer])
case "\\":
return total.with(prevChar: "\\")
default:
return total.with(buffer: "\(total.buffer)\(char)", prevChar: char, result: total.result)
}
if startIndex != endIndex {
buffer += String(Substring(string.utf8[startIndex ..< endIndex]))
result.append(buffer)
}
if !parseResult.buffer.isEmpty {
return parseResult.result + [parseResult.buffer]
}
return parseResult.result
return result
}
}
@@ -50,7 +50,7 @@ public class FileDependenciesWriter: DependenciesWriter {
var content = ""
for (file, deps) in dependencies {
content.append(file + ": ")
content.append(deps.joined(separator: " "))
content.append(deps.map { $0.replacingOccurrences(of: " ", with: "\\ ") }.joined(separator: " "))
content.append("\n")
}
try content.write(to: file, atomically: true, encoding: .utf8)
@@ -52,7 +52,9 @@ class EnvironmentFingerprintGenerator {
}
try fill(envKeys: Self.defaultEnvFingerprintKeys + customFingerprintEnvs)
try generator.append(version)
return try generator.generate()
let result = try generator.generate()
generatedFingerprint = result
return result
}
/// Creates a fingerprint of the environemtn, by hashing all ENVs specified in keys
@@ -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
}
@@ -173,7 +175,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 +192,13 @@ 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 extension NetworkClientError {
+1 -1
View File
@@ -21,7 +21,7 @@ import Foundation
import XCRemoteCache
/// Wrapper for a `LD` program that copies the dynamic executable from a cached-downloaded location
/// Fallbacks to a standard `clang` when the Ramote cache is not applicable (e.g. modified sources)
/// Fallbacks to a standard `clang` when the Remote cache is not applicable (e.g. modified sources)
public class XCLDMain {
public func main() {
let args = ProcessInfo().arguments
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import Foundation
import XCRemoteCache
/// Wrapper for a `LDPLUSPLUS` program that copies the dynamic executable from a cached-downloaded location
/// Fallbacks to a standard `clang++` when the Remote cache is not applicable (e.g. modified sources)
public class XCLDPlusPlusMain {
public func main() {
let args = ProcessInfo().arguments
var output: 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":
// Skip following `-Xlinker` argument. Sample call:
// `clang -dynamiclib ... -Xlinker -dependency_info -Xlinker /path/Target_dependency_info.dat`
dependencyInfo = args[i + 2]
i += 2
default:
break
}
i += 1
}
guard let outputInput = output, let filelistInput = filelist, let dependencyInfoInput = dependencyInfo else {
let ldCommand = "clang++"
print("Fallbacking to compilation using \(ldCommand).")
let args = ProcessInfo().arguments
let paramList = [ldCommand] + args.dropFirst()
let cargs = paramList.map { strdup($0) } + [nil]
execvp(ldCommand, cargs)
/// C-function `execv` returns only when the command fails
exit(1)
}
// TODO: consider using `clang_command` from .rcinfo
/// concrete clang path should be taken from the current toolchain
let fallbackCommand = "clang++"
XCCreateBinary(
output: outputInput,
filelist: filelistInput,
dependencyInfo: dependencyInfoInput,
fallbackCommand: fallbackCommand,
stepDescription: "xcldplusplus"
).run()
}
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import XCRemoteCache
XCLDPlusPlusMain().main()
@@ -35,6 +35,7 @@ class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
libtool: binariesDir.appendingPathComponent("xclibtool"),
ld: binariesDir.appendingPathComponent("xcld"),
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
postbuild: binariesDir.appendingPathComponent("xcpostbuild")
)
@@ -0,0 +1,139 @@
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
@testable import XCRemoteCache
import XCTest
class DependenciesReaderPerformanceTest: XCTestCase {
private static let resourcesSubdirectory = "TestData/Dependencies/DependenciesReaderPerformanceTest"
private func pathForTestData(name: String) throws -> URL {
return try XCTUnwrap(Bundle.module.url(
forResource: name,
withExtension: "d",
subdirectory: DependenciesReaderPerformanceTest.resourcesSubdirectory
))
}
func testFindDependenciesPerformance() throws {
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
self.measure { // 0.005
do {
_ = try reader.findDependencies()
} catch {
print("Error reading dependencies")
}
}
}
func testReadRawFilePerformance() throws {
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
self.measure { // 0.002
do {
_ = try reader.readRaw()
} catch {
print("Error reading dependencies")
}
}
}
func testGetFileDataPerformance() throws {
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
self.measure { // 0.00008
do {
_ = try reader.getFileData()
} catch {
print("Error reading dependencies")
}
}
}
func testGetFileStringPerformance() throws {
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
let fileData = try reader.getFileData()
self.measure { // 0.00002
do {
_ = try reader.getFileStringFromData(fileData: fileData)
} catch {
print("Error reading dependencies")
}
}
}
func testGetYamlPerformance() throws { // 0.222
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
let fileData = try reader.getFileData()
let fileString = try reader.getFileStringFromData(fileData: fileData)
self.measure { // 0.0022
do {
_ = try reader.getYaml(fileString: fileString)
} catch {
print("Error reading dependencies")
}
}
}
func testParseDependencyFileListUsingUTF8View() throws {
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
let fileData = try reader.getFileData()
let fileString = try reader.getFileStringFromData(fileData: fileData)
let yaml = try reader.getYaml(fileString: fileString)
guard let dependencies = yaml["dependencies"] else {
XCTAssertTrue(false)
return
}
self.measure { // 0.004
let deps = reader.parseDependencyFileList(dependencies)
XCTAssertTrue(deps.count == 1000)
}
}
func testDeprecatedParseDependenciesFilesListOfAnObjectUsingUTF8View() throws {
let file = try pathForTestData(name: "dependencies")
let reader = FileDependenciesReader(file, accessor: FileManager.default)
let fileData = try reader.getFileData()
let fileString = try reader.getFileStringFromData(fileData: fileData)
let yaml = try reader.getYaml(fileString: fileString)
guard let dependencies = yaml["/This/Is/A/Path/To/Some/Object/objectfile.o"] else {
XCTAssertTrue(false)
return
}
self.measure { // 0.00048
let deps = reader.parseDependencyFileList(dependencies)
XCTAssertTrue(deps.count == 100)
}
}
}
@@ -0,0 +1,47 @@
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
@testable import XCRemoteCache
import XCTest
class FileDependenciesWriterTests: XCTestCase {
private func generateTempFileURL(name: String = #function) throws -> URL {
let directory = NSTemporaryDirectory()
return try NSURL.fileURL(withPathComponents: [directory, name]).unwrap()
}
func testWriteDependencyWithSpace() throws {
let url = try generateTempFileURL()
let writer = FileDependenciesWriter(url, accessor: FileManager.default)
try writer.writeGeneric(dependencies: [
"/SomePath/Pods/Target Support Files/lottie-ios/lottie-ios-dummy.m",
])
let expectedContent = """
dependencies: /SomePath/Pods/Target\\ Support\\ Files/lottie-ios/lottie-ios-dummy.m
"""
let content = String(data: try Data(contentsOf: url), encoding: .utf8)
XCTAssertEqual(content, expectedContent)
}
}
@@ -39,13 +39,17 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
private static let currentVersion = "5"
private var config: XCRemoteCacheConfig!
private var generator: FingerprintAccumulator!
private var generator: FingerprintAccumulator! {
return generatorFake
}
private var generatorFake: FingerprintAccumulatorFake!
private var fingerprintGenerator: EnvironmentFingerprintGenerator!
override func setUp() {
super.setUp()
config = XCRemoteCacheConfig(sourceRoot: "")
generator = FingerprintAccumulatorFake()
generatorFake = FingerprintAccumulatorFake()
fingerprintGenerator = EnvironmentFingerprintGenerator(
configuration: config,
env: Self.defaultENV,
@@ -92,4 +96,11 @@ class EnvironmentFingerprintGeneratorTests: XCTestCase {
XCTAssertEqual(fingerprint, "GCC,YES,TARGET,CONG,PLAT,XC,1,2,3,4,AR,CUSTOM_VALUE,\(Self.currentVersion)")
}
func testFingerprintIsGeneratedOnce() throws {
let fingerprint1 = try fingerprintGenerator.generateFingerprint()
let fingerprint2 = try fingerprintGenerator.generateFingerprint()
XCTAssertEqual(fingerprint1, fingerprint2)
XCTAssertEqual(generatorFake.generateCallsCount, 1)
}
}
@@ -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,7 +165,13 @@ class NetworkClientImplTests: XCTestCase {
}
func testUploadSuccessDoesntRetry() throws {
client = NetworkClientImpl(session: session, retries: 0, fileManager: fileManager, awsV4Signature: nil)
client = NetworkClientImpl(
session: session,
retries: 0,
retryDelay: 0,
fileManager: fileManager,
awsV4Signature: nil
)
responses[url] = .success(successResponse, Data())
_ = try waitForResponse { client.upload(fileURL, as: url, completion: $0) }
@@ -208,7 +226,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) }
File diff suppressed because one or more lines are too long
@@ -34,5 +34,11 @@ class FingerprintAccumulatorFake: FingerprintAccumulator {
appendedStrings.append("FILE{\(file.path)}")
}
func generate() throws -> RawFingerprint { return appendedStrings.joined(separator: ",") }
private(set) var generateCallsCount = 0
func generate() throws -> RawFingerprint {
defer {
generateCallsCount += 1
}
return appendedStrings.joined(separator: ",")
}
}
-4
View File
@@ -63,7 +63,3 @@ To fully uninstall the plugin, call:
```bash
gem uninstall cocoapods-xcremotecache
```
## Limitations
* When `generate_multiple_pod_projects` mode is enabled, only first-party targets are cached by XCRemoteCache (all dependencies are compiled locally).
@@ -27,8 +27,9 @@ module CocoapodsXCRemoteCacheModifier
LLDB_INIT_COMMENT="#RemoteCacheCustomSourceMap"
LLDB_INIT_PATH = "#{ENV['HOME']}/.lldbinit"
FAT_ARCHIVE_NAME_INFIX = 'arm64-x86_64'
XCRC_COOCAPODS_ROOT_KEY = 'XCRC_COOCAPODS_ROOT'
# List of plugins' user properties that should be copied to .rcinfo
# List of plugins' user properties that should not be copied to .rcinfo
CUSTOM_CONFIGURATION_KEYS = [
'enabled',
'xcrc_location',
@@ -38,9 +39,7 @@ module CocoapodsXCRemoteCacheModifier
'check_build_configuration',
'check_platform',
'modify_lldb_init',
'prettify_meta_files',
'fake_src_root',
'disable_certificate_verification'
]
class XCRemoteCache
@@ -64,9 +63,14 @@ module CocoapodsXCRemoteCacheModifier
'exclude_targets' => [],
'prettify_meta_files' => false,
'fake_src_root' => "/#{'x' * 10 }",
'disable_certificate_verification' => false
'disable_certificate_verification' => false,
'custom_rewrite_envs' => []
}
@@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
# Always include XCRC_COOCAPODS_ROOT_KEY in custom_rewrite_envs
unless @@configuration['custom_rewrite_envs'].include?(XCRC_COOCAPODS_ROOT_KEY)
@@configuration['custom_rewrite_envs'] << XCRC_COOCAPODS_ROOT_KEY
end
end
def self.validate_configuration()
@@ -109,6 +113,8 @@ module CocoapodsXCRemoteCacheModifier
# @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)
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)
target.build_configurations.each do |config|
# apply only for relevant Configurations
@@ -121,9 +127,12 @@ module CocoapodsXCRemoteCacheModifier
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']
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}"]
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)")
@@ -174,6 +183,11 @@ module CocoapodsXCRemoteCacheModifier
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(XCRC_PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5"
]
postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d"
# Move postbuild (last element) to the position after compile sources phase (to make it real 'postbuild')
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
# Mark a sha as ready for a given platform and configuration when building the final_target
if (mode == 'producer' || mode == 'producer-fast') && target.name == final_target
@@ -201,9 +215,12 @@ module CocoapodsXCRemoteCacheModifier
config.build_settings.delete('SWIFT_EXEC') if config.build_settings.key?('SWIFT_EXEC')
config.build_settings.delete('LIBTOOL') if config.build_settings.key?('LIBTOOL')
config.build_settings.delete('LD') if config.build_settings.key?('LD')
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')
# Remove Fake src root for ObjC & Swift
config.build_settings.delete('XCREMOTE_CACHE_FAKE_SRCROOT')
config.build_settings.delete('XCRC_PLATFORM_PREFERRED_ARCH')
config.build_settings.delete(XCRC_COOCAPODS_ROOT_KEY)
remove_cflags!(config.build_settings, '-fdebug-prefix-map')
remove_swiftflags!(config.build_settings, '-debug-prefix-map')
end
@@ -223,7 +240,7 @@ module CocoapodsXCRemoteCacheModifier
end
def self.download_xcrc_if_needed(local_location)
required_binaries = ['xcld', 'xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc']
binaries_exist = required_binaries.reduce(true) do |exists, filename|
file_path = File.join(local_location, filename)
exists = exists && File.exist?(file_path)
@@ -13,5 +13,5 @@
# limitations under the License.
module CocoapodsXcremotecache
VERSION = "0.0.11"
VERSION = "0.0.13"
end
+8
View File
@@ -0,0 +1,8 @@
---
cache_addresses:
- 'http://localhost:8080/cache/pods'
primary_repo: '.'
primary_branch: 'e2e-test-branch'
mode: 'consumer'
final_target': XCRemoteCacheSample'
artifact_maximum_age: 0 # do not use local cache in ~/Library/Caches/XCRemoteCache
@@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "SomeObjC.h"
@@ -0,0 +1,6 @@
import Foundation
@objc
public class SomeClass: NSObject {
@objc public var someEnum: SomeEnum = .default
}
@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, SomeEnum) {
SomeEnumDefault
};
NS_ASSUME_NONNULL_END
@@ -0,0 +1,544 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
36201A102843B3C3002FF70F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A0F2843B3C3002FF70F /* AppDelegate.swift */; };
36201A122843B3C3002FF70F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A112843B3C3002FF70F /* SceneDelegate.swift */; };
36201A142843B3C3002FF70F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A132843B3C3002FF70F /* ViewController.swift */; };
36201A172843B3C3002FF70F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36201A152843B3C3002FF70F /* Main.storyboard */; };
36201A192843B3C7002FF70F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 36201A182843B3C7002FF70F /* Assets.xcassets */; };
36201A1C2843B3C7002FF70F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36201A1A2843B3C7002FF70F /* LaunchScreen.storyboard */; };
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 */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
36201A332843B431002FF70F /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 36201A042843B3C3002FF70F /* Project object */;
proxyType = 1;
remoteGlobalIDString = 36201A262843B3D3002FF70F;
remoteInfo = MixedTarget;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
36201A252843B3D3002FF70F /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
36201A0C2843B3C3002FF70F /* StandaloneApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StandaloneApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
36201A0F2843B3C3002FF70F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
36201A112843B3C3002FF70F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
36201A132843B3C3002FF70F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
36201A162843B3C3002FF70F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
36201A182843B3C7002FF70F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
36201A1B2843B3C7002FF70F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
36201A1D2843B3C7002FF70F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
36201A272843B3D3002FF70F /* libMixedTarget.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMixedTarget.a; sourceTree = BUILT_PRODUCTS_DIR; };
36201A292843B3D3002FF70F /* MixedTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixedTarget.swift; sourceTree = "<group>"; };
36201A2F2843B413002FF70F /* MixedTarget-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MixedTarget-Bridging-Header.h"; sourceTree = "<group>"; };
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>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
36201A092843B3C3002FF70F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
36201A362843B435002FF70F /* libMixedTarget.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
36201A242843B3D3002FF70F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
36201A032843B3C3002FF70F = {
isa = PBXGroup;
children = (
36201A0E2843B3C3002FF70F /* StandaloneApp */,
36201A282843B3D3002FF70F /* MixedTarget */,
36201A0D2843B3C3002FF70F /* Products */,
36201A352843B435002FF70F /* Frameworks */,
);
sourceTree = "<group>";
};
36201A0D2843B3C3002FF70F /* Products */ = {
isa = PBXGroup;
children = (
36201A0C2843B3C3002FF70F /* StandaloneApp.app */,
36201A272843B3D3002FF70F /* libMixedTarget.a */,
);
name = Products;
sourceTree = "<group>";
};
36201A0E2843B3C3002FF70F /* StandaloneApp */ = {
isa = PBXGroup;
children = (
36201A0F2843B3C3002FF70F /* AppDelegate.swift */,
36201A112843B3C3002FF70F /* SceneDelegate.swift */,
36201A132843B3C3002FF70F /* ViewController.swift */,
36201A152843B3C3002FF70F /* Main.storyboard */,
36201A182843B3C7002FF70F /* Assets.xcassets */,
36201A1A2843B3C7002FF70F /* LaunchScreen.storyboard */,
36201A1D2843B3C7002FF70F /* Info.plist */,
36201A372843BDDC002FF70F /* StandaloneObjc.h */,
36201A382843BDDC002FF70F /* StandaloneObjc.m */,
);
path = StandaloneApp;
sourceTree = "<group>";
};
36201A282843B3D3002FF70F /* MixedTarget */ = {
isa = PBXGroup;
children = (
36201A292843B3D3002FF70F /* MixedTarget.swift */,
36201A302843B414002FF70F /* SomeObjC.h */,
36201A2F2843B413002FF70F /* MixedTarget-Bridging-Header.h */,
);
path = MixedTarget;
sourceTree = "<group>";
};
36201A352843B435002FF70F /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
36201A0B2843B3C3002FF70F /* StandaloneApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 36201A202843B3C7002FF70F /* Build configuration list for PBXNativeTarget "StandaloneApp" */;
buildPhases = (
36201A082843B3C3002FF70F /* Sources */,
36201A092843B3C3002FF70F /* Frameworks */,
36201A0A2843B3C3002FF70F /* Resources */,
);
buildRules = (
);
dependencies = (
36201A342843B431002FF70F /* PBXTargetDependency */,
);
name = StandaloneApp;
productName = StandaloneApp;
productReference = 36201A0C2843B3C3002FF70F /* StandaloneApp.app */;
productType = "com.apple.product-type.application";
};
36201A262843B3D3002FF70F /* MixedTarget */ = {
isa = PBXNativeTarget;
buildConfigurationList = 36201A2B2843B3D3002FF70F /* Build configuration list for PBXNativeTarget "MixedTarget" */;
buildPhases = (
36201A232843B3D3002FF70F /* Sources */,
36201A242843B3D3002FF70F /* Frameworks */,
36201A252843B3D3002FF70F /* CopyFiles */,
36201A3A2843BE0E002FF70F /* Copy Swift Objective-C Interface Header */,
);
buildRules = (
);
dependencies = (
);
name = MixedTarget;
productName = MixedTarget;
productReference = 36201A272843B3D3002FF70F /* libMixedTarget.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
36201A042843B3C3002FF70F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
TargetAttributes = {
36201A0B2843B3C3002FF70F = {
CreatedOnToolsVersion = 13.2.1;
LastSwiftMigration = 1320;
};
36201A262843B3D3002FF70F = {
CreatedOnToolsVersion = 13.2.1;
LastSwiftMigration = 1320;
};
};
};
buildConfigurationList = 36201A072843B3C3002FF70F /* Build configuration list for PBXProject "StandaloneApp" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 36201A032843B3C3002FF70F;
productRefGroup = 36201A0D2843B3C3002FF70F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
36201A0B2843B3C3002FF70F /* StandaloneApp */,
36201A262843B3D3002FF70F /* MixedTarget */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
36201A0A2843B3C3002FF70F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
36201A1C2843B3C7002FF70F /* LaunchScreen.storyboard in Resources */,
36201A192843B3C7002FF70F /* Assets.xcassets in Resources */,
36201A172843B3C3002FF70F /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
36201A3A2843BE0E002FF70F /* Copy Swift Objective-C Interface Header */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)",
"$(DERIVED_SOURCES_DIR)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME).md5",
);
name = "Copy Swift Objective-C Interface Header";
outputFileListPaths = (
);
outputPaths = (
"$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME)",
"$(BUILT_PRODUCTS_DIR)/include/$(PRODUCT_MODULE_NAME)/$(SWIFT_OBJC_INTERFACE_HEADER_NAME).md5",
);
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";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
36201A082843B3C3002FF70F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
36201A142843B3C3002FF70F /* ViewController.swift in Sources */,
36201A102843B3C3002FF70F /* AppDelegate.swift in Sources */,
36201A392843BDDC002FF70F /* StandaloneObjc.m in Sources */,
36201A122843B3C3002FF70F /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
36201A232843B3D3002FF70F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
36201A2A2843B3D3002FF70F /* MixedTarget.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
36201A342843B431002FF70F /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 36201A262843B3D3002FF70F /* MixedTarget */;
targetProxy = 36201A332843B431002FF70F /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
36201A152843B3C3002FF70F /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
36201A162843B3C3002FF70F /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
36201A1A2843B3C7002FF70F /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
36201A1B2843B3C7002FF70F /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
36201A1E2843B3C7002FF70F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
36201A1F2843B3C7002FF70F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
36201A212843B3C7002FF70F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/include/";
INFOPLIST_FILE = StandaloneApp/Info.plist;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.StandaloneApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
36201A222843B3C7002FF70F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/include/";
INFOPLIST_FILE = StandaloneApp/Info.plist;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.StandaloneApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
36201A2C2843B3D3002FF70F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MixedTarget/MixedTarget-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
36201A2D2843B3D3002FF70F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MixedTarget/MixedTarget-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
36201A072843B3C3002FF70F /* Build configuration list for PBXProject "StandaloneApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
36201A1E2843B3C7002FF70F /* Debug */,
36201A1F2843B3C7002FF70F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
36201A202843B3C7002FF70F /* Build configuration list for PBXNativeTarget "StandaloneApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
36201A212843B3C7002FF70F /* Debug */,
36201A222843B3C7002FF70F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
36201A2B2843B3D3002FF70F /* Build configuration list for PBXNativeTarget "MixedTarget" */ = {
isa = XCConfigurationList;
buildConfigurations = (
36201A2C2843B3D3002FF70F /* Debug */,
36201A2D2843B3D3002FF70F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 36201A042843B3C3002FF70F /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,29 @@
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,45 @@
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
@@ -0,0 +1,9 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface StandaloneObjc : NSObject
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,6 @@
#import "StandaloneObjc.h"
#import "MixedTarget/MixedTarget-Swift.h"
@implementation StandaloneObjc
@end
@@ -0,0 +1,13 @@
import UIKit
import MixedTarget
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
+66 -9
View File
@@ -5,6 +5,7 @@ namespace :e2e do
COCOAPODS_DIR = 'cocoapods-plugin'
COCOAPODS_GEMSPEC_FILENAME = "cocoapods-xcremotecache.gemspec"
E2E_COCOAPODS_SAMPLE_DIR = 'e2eTests/XCRemoteCacheSample'
E2E_STANDALONE_SAMPLE_DIR = 'e2eTests/StandaloneSampleApp'
GIT_REMOTE_NAME = 'self'
# Location of the remote address that points to itself
GIT_REMOTE_ADDRESS = '.'
@@ -25,6 +26,7 @@ namespace :e2e do
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]
# run E2E tests for CocoaPods-powered projects
@@ -41,6 +43,51 @@ namespace :e2e do
clean
end
# run E2E tests for standalone (non-CocoaPods) projects
task :run_standalone do
clean_server
start_nginx
configure_git
# Prepare binaries for the standalone mode
prepare_for_standalone(E2E_STANDALONE_SAMPLE_DIR)
puts 'Building standalone producer...'
####### Producer #########
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", 'StandaloneApp')
system("#{XCRC_BINARIES}/xcprepare stats --reset --format json")
end
puts 'Building standalone consumer...'
####### Consumer #########
# new dir to emulate different srcroot
consumer_srcroot = "#{E2E_STANDALONE_SAMPLE_DIR}_consumer"
system("mv #{E2E_STANDALONE_SAMPLE_DIR} #{consumer_srcroot}")
at_exit { puts("reverting #{E2E_STANDALONE_SAMPLE_DIR}"); system("mv #{consumer_srcroot} #{E2E_STANDALONE_SAMPLE_DIR}") }
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
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"})
end
# Revert all side effects
clean
end
# Build and install a plugin
def self.install_cocoapods_plugin
Dir.chdir(COCOAPODS_DIR) do
@@ -114,16 +161,16 @@ namespace :e2e do
end
end
def self.build_project(extra_args = {})
system('pod install')
def self.build_project(workspace, project, scheme, extra_args = {})
xcodebuild_args = {
'workspace' => 'XCRemoteCacheSample.xcworkspace',
'scheme' => 'XCRemoteCacheSample',
'workspace' => workspace,
'project' => project,
'scheme' => scheme,
'configuration' => 'Debug',
'sdk' => 'iphonesimulator',
'destination' => 'generic/platform=iOS Simulator',
'derivedDataPath' => DERIVED_DATA_PATH,
}.merge(extra_args)
}.merge(extra_args).compact
xcodebuild_vars = {
'EXCLUDED_ARCHS' => 'arm64 i386'
}
@@ -131,7 +178,7 @@ namespace :e2e do
args.push(*xcodebuild_args.map {|k,v| "-#{k} '#{v}'"})
args.push(*xcodebuild_vars.map {|k,v| "#{k}='#{v}'"})
args.push('clean build')
args.push("> #{LOG_NAME}")
args.push("| tee #{LOG_NAME}")
puts 'Building a project with xcodebuild...'
system(args.join(' '))
unless $?.success?
@@ -140,6 +187,11 @@ namespace :e2e do
end
end
def self.build_project_cocoapods(extra_args = {})
system('pod install')
build_project('XCRemoteCacheSample.xcworkspace', nil, 'XCRemoteCacheSample', extra_args)
end
def self.read_stats
stats_json_string = JSON.parse(`#{XCRC_BINARIES}/xcprepare stats --format json`)
misses = stats_json_string.fetch('miss_count', 0)
@@ -153,8 +205,8 @@ namespace :e2e do
# validate 100% hit rate
def self.valide_hit_rate
status = read_stats()
raise "Failure: Hit rate is only #{status.hit_rate}% (#{status.hits}/#{status.all_targets})" if status.misses > 0
all_targets = status.misses + status.hits
raise "Failure: Hit rate is only #{status.hit_rate}% (#{all_targets})" if status.misses > 0
puts("Hit rate: #{status.hit_rate}% (#{status.hits}/#{all_targets})")
end
@@ -169,7 +221,7 @@ namespace :e2e do
dump_podfile(producer_configuration, template_path)
puts('Building producer ...')
Dir.chdir(E2E_COCOAPODS_SAMPLE_DIR) do
build_project
build_project_cocoapods
# reset XCRemoteCache stats
system("#{XCRC_BINARIES}/xcprepare stats --reset --format json")
end
@@ -179,8 +231,13 @@ namespace :e2e do
dump_podfile(consumer_configuration, template_path)
puts('Building consumer ...')
Dir.chdir(E2E_COCOAPODS_SAMPLE_DIR) do
build_project({'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
build_project_cocoapods({'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"})
valide_hit_rate
end
end
def self.prepare_for_standalone(dir)
clean_git
system("ln -s $(pwd)/releases #{dir}/#{XCRC_BINARIES}")
end
end