Compare commits

...

41 Commits

Author SHA1 Message Date
Bartosz Polaczyk bf90d518f4 Merge pull request #27 from polac24/20211201-debug-map-align
[Pods] Align Debub-prefix-map for Pods project
2021-12-05 20:35:03 +01:00
Bartosz Polaczyk 634afb3f3f Merge pull request #25 from polac24/20211122-producer-fast
Add producer-fast mode
2021-12-05 20:34:48 +01:00
Bartosz Polaczyk c2b80c0112 Do not return optional array in findTargetPackageZip 2021-12-02 22:28:07 +01:00
Bartosz Polaczyk d0604e9042 Fix producer full typo 2021-12-02 22:24:49 +01:00
Bartosz Polaczyk 297c1a90cb Merge remote-tracking branch 'upstream/master' into 20211201-debug-map-align 2021-12-02 22:22:14 +01:00
Bartosz Polaczyk 34cb54b675 Fix typo 2021-12-02 22:19:44 +01:00
Bartosz Polaczyk 14b2b3aceb [CocoapodsPlugin] Support first-party caching for incremental installation 2021-12-02 22:19:43 +01:00
Bartosz Polaczyk cbed913c63 Merge pull request #19 from polac24/20211121-incremental-installation
[CocoaPodsPlugin] Support first-party caching for incremental install
2021-12-02 10:55:47 +01:00
Bartosz Polaczyk 63448ff0a0 Fix typo 2021-12-02 08:58:19 +01:00
Bartosz Polaczyk 64bccaed16 Merge branch 'master' into 20211121-incremental-installation 2021-12-02 08:56:26 +01:00
Bartosz Polaczyk 80a7abb4d5 Update RDoc array definition 2021-12-02 07:26:36 +01:00
Bartosz Polaczyk c50ee6f798 Align Debub-prefix-map for Pods project 2021-12-01 19:47:44 +01:00
Bartosz Polaczyk 6e4bf25d1c Merge pull request #20 from polac24/20211121-update-spm-limitation
Provide a reason why SPM dependencies cannot be supported
2021-11-30 17:43:27 +01:00
Bartosz Polaczyk 71af03f227 Fix user message for producer-fast 2021-11-25 22:14:10 +01:00
Bartosz Polaczyk 0ebe6f5ceb Reuse existing meta sha 2021-11-25 22:11:04 +01:00
Bartosz Polaczyk 29cba26c5d Add tests 2021-11-25 21:38:55 +01:00
Bartosz Polaczyk 08b6115187 Merge remote-tracking branch 'upstream/master' into 20211122-producer-fast 2021-11-25 21:11:27 +01:00
Bartosz Polaczyk bbbb0a5b0f Add unit tests for Postbuild 2021-11-25 21:10:18 +01:00
Bartosz Polaczyk 758764ad95 Refactor to MetaWriter 2021-11-25 20:58:14 +01:00
Bartosz Polaczyk f332593076 Upload meta on the reused artifact scenario 2021-11-24 22:58:09 +01:00
Bartosz Polaczyk d0b2bc0f71 Prereview cleanup 2021-11-24 21:49:30 +01:00
Bartosz Polaczyk e2f68c8f4e Add unit tests for thinning creator plugin 2021-11-24 21:41:34 +01:00
Bartosz Polaczyk dbff760716 Merge pull request #24 from woodencoder/woodencooder/fix_swiftlint_warnings
Fix SwiftLint warnings
2021-11-24 19:47:32 +01:00
Bartosz Polaczyk bb05b02bd8 Merge pull request #22 from mihai8804858/reuse-existing-build-phases
Reuse existing build phases in CocoaPods plugin
2021-11-24 19:40:02 +01:00
Vladislav Klimenko 5c568a1338 Fix SwiftLint warnings 2021-11-24 21:07:40 +03:00
Mihai Seremet 86273017b4 Fix debugging for Pods project 2021-11-24 12:23:46 +02:00
Mihai Seremet 4f1f73132e Support switching between models in plugin 2021-11-23 00:28:08 +02:00
Bartosz Polaczyk ec1ef567cb Add producer-fast mode 2021-11-22 22:18:59 +01:00
Bartosz Polaczyk 1c94e51059 Add producerFast mode 2021-11-22 21:59:19 +01:00
Mihai Seremet ba41e40bb0 Reuse existing build phases in CocoaPods plugin 2021-11-22 19:04:51 +02:00
Bartosz Polaczyk a0c88d9059 Provide a reason why SPM dependencies cannot be supported 2021-11-21 19:41:21 +01:00
Bartosz Polaczyk 15173b9575 [CocoapodsPlugin] Support first-party caching for incremental installation 2021-11-21 14:17:34 +01:00
Bartosz Polaczyk fa82f920ad Merge pull request #17 from tejassharma96/patch-1
Update how and why link in readme
2021-11-17 22:19:28 +01:00
Tejas Sharma 78031a3135 Update how and why link in readme 2021-11-17 12:57:51 -08:00
Bartosz Polaczyk 2156de1706 Merge pull request #14 from polac24/support-readme
Add support session to Readme
2021-11-17 10:45:21 +01:00
Bartosz Polaczyk ce2ef3ea69 Merge pull request #13 from polac24/cocoapods-002
Release CocoaPods plugin 0.0.2
2021-11-17 10:45:02 +01:00
Bartosz Polaczyk 3219ac24aa Merge pull request #15 from eliperkins/patch-1
Fix typo in README
2021-11-17 08:21:23 +01:00
Eli Perkins d9ef32f24f Fix typo in README 2021-11-16 12:23:19 -05:00
Bartosz Polaczyk 3aaf483263 Add a section about slack support 2021-11-16 17:32:47 +01:00
Bartosz Polaczyk cc691b8c67 Add an option to install via RubyGems 2021-11-16 17:10:45 +01:00
Bartosz Polaczyk 4c95ff915f Release CocoaPods plugin 0.0.2 2021-11-16 17:04:18 +01:00
51 changed files with 706 additions and 164 deletions
+2 -1
View File
@@ -5,4 +5,5 @@
DerivedData
/.swiftpm/
releases
tmp/
tmp/
.idea/
+1
View File
@@ -5,6 +5,7 @@ disabled_rules:
- superfluous_disable_command # Disabled since we disable some rules pre-emptively to avoid issues in the future
- todo # Temporarily disabled. We have too many right now hiding real issues :(
- nesting # Does not make sense anymore since Swift 4 uses nested `CodingKeys` enums for example
- trailing_dot_in_comments # Triggers warnings for generated file headers
opt_in_rules:
- anyobject_protocol
+11 -4
View File
@@ -6,8 +6,9 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
[![Build Status](https://github.com/spotify/XCRemoteCache/workflows/CI/badge.svg)](https://github.com/spotify/XCRemoteCache/workflows/CI/badge.svg)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Slack](https://slackin.spotify.com/badge.svg)](https://slackin.spotify.com)
- [How and Why?](#how-and-why-)
- [How and Why?](#how-and-why)
* [Accurate target input files](#accurate-target-input-files)
+ [New file added to the target](#new-file-added-to-the-target)
* [Debug symbols](#debug-symbols)
@@ -50,7 +51,7 @@ _XCRemoteCache is a remote cache tool for Xcode projects. It reuses target artif
The caching mechanism is based on remote artifacts that should be generated and uploaded to the cache server for each commit on a `master` branch, preferably as a part of CI/CD step. Xcode products are not portable between different Xcode versions, each XCRemoteCache artifact is linked with a specific Xcode build number that generated it. To support multiple Xcode versions, artifacts generation should happen for each Xcode version.
The artifact resue flow is as follows: XCRemoteCache performs a target precheck (aka prebuild) and if a fingerprint for local sources matches the one computed on a generation side, several compilation steps wrappers (e.g. `xcswiftc`, `xccc`, `xclibtool`) mock corresponding compilation step(s) and linking (or archiving) moves the cached build artifact to the expected location.
The artifact reuse flow is as follows: XCRemoteCache performs a target precheck (aka prebuild) and if a fingerprint for local sources matches the one computed on a generation side, several compilation steps wrappers (e.g. `xcswiftc`, `xccc`, `xclibtool`) mock corresponding compilation step(s) and linking (or archiving) moves the cached build artifact to the expected location.
> Multiple commits that have the same target sources reuse artifact package on a remote server.
@@ -134,7 +135,7 @@ xcremotecache/xcprepare integrate --input <yourProject.xcodeproj> --mode consume
| Argument | Description | Default | Required |
| ------------- | ------------- | ------------- | ------------- |
| `--input` | .xcodeproj location | N/A | ✅ |
| `--mode` | mode. Supported values: `consumer`, `producer` | N/A | ✅ |
| `--mode` | mode. Supported values: `consumer`, `producer`, `producer-fast`(experimental) | N/A | ✅ |
| `--targets-include` | comma-separated list of targets to integrate XCRemoteCache. | `""` | ⬜️ |
| `--targets-exclude` | comma-separated list of targets to not integrate XCRemoteCache. Takes priority over --targets-include. | `""` | ⬜️ |
| `--configurations-include` | comma-separated list of configurations to integrate XCRemoteCache. | `""` | ⬜️ |
@@ -371,7 +372,7 @@ Head over to our [cocoapods-plugin](cocoapods-plugin/README.md) docs to see how
## Limitations
* Swift Package Manager (SPM) projects are not supported
* 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.
@@ -398,6 +399,12 @@ rake 'build[release, x86_64-apple-macosx]'
The zip package will be generated at `releases/XCRemoteCache.zip`.
## Support
Create a [new issue](https://github.com/spotify/XCRemoteCache/issues/new) with as many details as possible.
Reach us at the `#xcremotecache` channel in [Slack](https://slackin.spotify.com/).
## Contributing
We feel that a welcoming community is important and we ask that you follow Spotify's
+1 -1
View File
@@ -28,7 +28,7 @@ task :lint => [:prepare] do
puts 'Run linting'
system("swiftformat --lint --config .swiftformat --cache ignore .") or abort "swiftformat failure" if SWIFTFORMAT_ENABLED
system("swiftlint lint --config .swiftlint.yml") or abort "swiftlint failure" if SWIFTLINT_ENABLED
system("swiftlint lint --config .swiftlint.yml --strict") or abort "swiftlint failure" if SWIFTLINT_ENABLED
end
task :autocorrect => [:prepare] do
@@ -41,6 +41,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
private let moduleName: String?
private let modulesFolderPath: String
private let dSYMPath: URL
private let metaWriter: MetaWriter
private let fileManager: FileManager
init(
@@ -50,6 +51,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
moduleName: String?,
modulesFolderPath: String,
dSYMPath: URL,
metaWriter: MetaWriter,
fileManager: FileManager
) {
self.buildDir = buildDir
@@ -59,6 +61,7 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
self.moduleName = moduleName
self.fileManager = fileManager
self.dSYMPath = dSYMPath
self.metaWriter = metaWriter
super.init(workingDir: tempDir, moduleName: moduleName, fileManager: fileManager)
}
@@ -72,7 +75,11 @@ class BuildArtifactCreator: ArtifactSwiftProductsBuilderImpl, ArtifactCreator {
let dynamicLibraryArtifacts = try prepareDynamicLibraryArtifacts()
zipPaths.append(contentsOf: dynamicLibraryArtifacts)
let creator = ZipArtifactCreator(workingDir: zipWorkingDir, fileManager: fileManager)
let creator = ZipArtifactCreator(
workingDir: zipWorkingDir,
metaWriter: metaWriter,
fileManager: fileManager
)
return try creator.createArtifact(zipContent: zipPaths, artifactKey: artifactKey, meta: meta)
}
@@ -23,11 +23,12 @@ import Zip
class ZipArtifactCreator {
/// Location where zip file should be generated
private let workingDir: URL
private let metaWriter: MetaWriter
private let fileManager: FileManager
private let metaEncoder = JSONEncoder()
init(workingDir: URL, fileManager: FileManager) {
init(workingDir: URL, metaWriter: MetaWriter, fileManager: FileManager) {
self.workingDir = workingDir
self.metaWriter = metaWriter
self.fileManager = fileManager
}
@@ -35,18 +36,10 @@ class ZipArtifactCreator {
let zipURL = workingDir.appendingPathComponent("\(artifactKey).zip")
try fileManager.createDirectory(at: workingDir, withIntermediateDirectories: true, attributes: nil)
// Include meta json to the artifact
let metaURL = try dumpMeta(meta)
let metaURL = try metaWriter.write(meta, locationDir: workingDir)
let zipPaths = zipContent + [metaURL]
try Zip.zipFiles(paths: zipPaths, zipFilePath: zipURL, password: nil, progress: nil)
return Artifact(id: artifactKey, package: zipURL, meta: metaURL)
}
// Save meta to a local file
private func dumpMeta<T: Meta>(_ meta: T) throws -> URL {
let metaURL = workingDir.appendingPathComponent(meta.fileKey).appendingPathExtension("json")
let metaData = try metaEncoder.encode(meta)
try fileManager.spt_writeToFile(atPath: metaURL.path, contents: metaData)
return metaURL
}
}
@@ -33,13 +33,16 @@ enum ThinningCreatorPluginError: Error {
/// Warning! This plugin assumes that producer's DerivedData are always cleaned before a build
class ThinningCreatorPlugin: ArtifactCreatorPlugin {
private let targetTempDir: URL
private let modeMarkerPath: String
private let dirScanner: DirScanner
/// Default Initializer
/// - Parameter targetTempDir: Location of current target-specific temp dir (TARGET_TEMP_DIR)
/// - Parameter modeMarkerPath: path of maker file that informs if a given target can reuse remote artifacts.
/// - Parameter dirScanner: scanner to access disk and read files and directories hierarchy
init(targetTempDir: URL, dirScanner: DirScanner) {
init(targetTempDir: URL, modeMarkerPath: String, dirScanner: DirScanner) {
self.targetTempDir = targetTempDir
self.modeMarkerPath = modeMarkerPath
self.dirScanner = dirScanner
}
@@ -57,31 +60,19 @@ class ThinningCreatorPlugin: ArtifactCreatorPlugin {
let fileKey: String
}
let uploadedTargetArtifacts = try allURLs.compactMap { tempDir -> TargetTuple? in
// All targets that uploaded their artifacts, have it placed in the
// `$(TARGET_TEMP_DIR)/xccache/produced/{{fileKey}}.zip` location. Find all targets that have such a file
let targetGeneratedArtifactRootDir = tempDir
.appendingPathComponent("xccache")
.appendingPathComponent("produced")
guard try dirScanner.itemType(atPath: targetGeneratedArtifactRootDir.path) == ItemType.dir else {
// given target didn't generate any artifacts (e.g. it is never cached with XCRemoteCache)
return nil
}
let allFilesProduced = try dirScanner.items(at: targetGeneratedArtifactRootDir)
let allArtifacts = allFilesProduced.filter { $0.pathExtension == "zip" }
guard !allArtifacts.isEmpty else {
let potentialArtifacts = try findTargetPackageZip(tempDir: tempDir)
guard !potentialArtifacts.isEmpty else {
// there is no generated *.zip file, so given target didn't create an artifact - it could be
// just a helper target (like the target we integrate this plugin with)
return nil
}
// Find {{fileKey}} based on the .zip file basename
guard allArtifacts.count == 1 else {
guard potentialArtifacts.count == 1 else {
throw ThinningCreatorPluginError.noSingleTargetArtifactsGenerated(
rootDir: targetGeneratedArtifactRootDir
rootDir: tempDir
)
}
let fileKey = allArtifacts[0].deletingPathExtension().lastPathComponent
let fileKey = potentialArtifacts[0].deletingPathExtension().lastPathComponent
// Taking target name from tempDir, which has a structures "*.build"
let targetName = tempDir.deletingPathExtension().lastPathComponent
return TargetTuple(targetName: targetName, fileKey: fileKey)
@@ -96,6 +87,41 @@ class ThinningCreatorPlugin: ArtifactCreatorPlugin {
return Dictionary(uniqueKeysWithValues: extraKeysTuples)
}
private func findTargetPackageZip(tempDir: URL) throws -> [URL] {
// Producer mode:
// All targets that uploaded their artifacts, have it placed in the
// `$(TARGET_TEMP_DIR)/xccache/produced/{{fileKey}}.zip` location. Find all targets that have such a file
// ProducerFast mode:
// If a target reused already existing artifact, it still has `$(TARGET_TEMP_DIR)/rc.enabled` marker file
// and the reused zip is placed in:
// `$(TARGET_TEMP_DIR)/xccache/{{fileKey}}.zip` location.
let targetEnabledMarker = tempDir.appendingPathComponent(modeMarkerPath)
let targetReusedArtifactRootDir = tempDir.appendingPathComponent("xccache")
let targetGeneratedArtifactRootDir = tempDir
.appendingPathComponent("xccache")
.appendingPathComponent("produced")
let pathToDirWithZipArtifacts: URL
// try the `.producerFast` scenario first (the artifact was not locally
// generated but just reused from the remote cache)
if try dirScanner.itemType(atPath: targetEnabledMarker.path) == ItemType.file {
pathToDirWithZipArtifacts = targetReusedArtifactRootDir
} else {
// cover a case when a target was build locally and an artifact
// has just been created (locally)
guard try dirScanner.itemType(atPath: targetGeneratedArtifactRootDir.path) == ItemType.dir else {
// given target didn't generate any artifacts (e.g. it is never cached with XCRemoteCache)
return []
}
pathToDirWithZipArtifacts = targetGeneratedArtifactRootDir
}
let allFilesProduced = try dirScanner.items(at: pathToDirWithZipArtifacts)
let allArtifacts = allFilesProduced.filter { $0.pathExtension == "zip" }
return allArtifacts
}
func artifactToUpload(main: MainArtifactMeta) throws -> [Artifact] {
return []
}
@@ -40,7 +40,7 @@ class ThinningDiskSwiftcProductsGenerator: SwiftcProductsGenerator {
destinationSwiftmodulePaths = Dictionary(
uniqueKeysWithValues: SwiftmoduleFileExtension.SwiftmoduleExtensions
.map { ext, _ in
switch (ext) {
switch ext {
case .swiftsourceinfo:
let dest = modulePathDir.appendingPathComponent("Project")
.appendingPathComponent(moduleName)
@@ -41,6 +41,7 @@ class Postbuild {
private let dSYMOrganizer: DSYMOrganizer
private let modeController: CacheModeController
private let metaReader: MetaReader
private let metaWriter: MetaWriter
private let creatorPlugins: [ArtifactCreatorPlugin]
private let consumerPlugins: [ArtifactConsumerPostbuildPlugin]
@@ -58,6 +59,7 @@ class Postbuild {
dSYMOrganizer: DSYMOrganizer,
modeController: CacheModeController,
metaReader: MetaReader,
metaWriter: MetaWriter,
creatorPlugins: [ArtifactCreatorPlugin],
consumerPlugins: [ArtifactConsumerPostbuildPlugin]
) {
@@ -74,6 +76,7 @@ class Postbuild {
self.dSYMOrganizer = dSYMOrganizer
self.modeController = modeController
self.metaReader = metaReader
self.metaWriter = metaWriter
self.creatorPlugins = creatorPlugins
self.consumerPlugins = consumerPlugins
}
@@ -125,6 +128,22 @@ class Postbuild {
try generateFingerprintOverrides(contextSpecificFingerprint: fingerprint.contextSpecific)
}
/// Uploads only a meta to the remote server - useful when the file artifact (.zip) already exists on a remote
/// server and only a meta for a current commit sha has to be uploaded
public func performMetaUpload(meta: MainArtifactMeta, for commit: String) throws {
// Reset plugins keys as these are unique to each
var meta = meta
meta.pluginsKeys = [:]
meta = try creatorPlugins.reduce(meta) { prevMeta, plugin in
var meta = prevMeta
// add extra keys from the plugin. A plugin overrides previously defined keys in case of duplication
meta.pluginsKeys = try meta.pluginsKeys.merging(plugin.extraMetaKeys(prevMeta), uniquingKeysWith: { $1 })
return meta
}
let metaPath = try metaWriter.write(meta, locationDir: context.targetTempDir)
try networkClient.uploadSynchronously(metaPath, as: .meta(commit: commit))
}
/// Builds an artifact package and uploads it to the remote server
public func performBuildUpload(for commit: String) throws {
let dependencies = try generateDependencies()
@@ -73,6 +73,7 @@ public struct PostbuildContext {
var thinnedTargets: [String]
/// Action type: build, indexbuild etc.
var action: BuildActionType
let modeMarkerPath: String
}
extension PostbuildContext {
@@ -115,5 +116,6 @@ extension PostbuildContext {
let thinFocusedTargetsString: String = env.readEnv(key: "SPT_XCREMOTE_CACHE_THINNED_TARGETS") ?? ""
thinnedTargets = thinFocusedTargetsString.split(separator: ",").map(String.init)
action = (try? BuildActionType(rawValue: env.readEnv(key: "ACTION"))) ?? .unknown
modeMarkerPath = config.modeMarkerPath
}
}
@@ -85,6 +85,7 @@ public class XCPostbuild {
algorithm: MD5Algorithm()
)
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
let metaWriter = JsonMetaWriter(fileWriter: fileManager)
let artifactCreator = BuildArtifactCreator(
buildDir: context.productsDir,
tempDir: context.targetTempDir,
@@ -92,6 +93,7 @@ public class XCPostbuild {
moduleName: context.moduleName,
modulesFolderPath: context.modulesFolderPath,
dSYMPath: context.dSYMPath,
metaWriter: metaWriter,
fileManager: fileManager
)
let dirAccessor = DirAccessorComposer(
@@ -205,9 +207,10 @@ public class XCPostbuild {
worker: DispatchGroupParallelizationWorker(qos: .userInitiated)
)
consumerPlugins.append(thinningPlugin)
case .producer:
case .producer, .producerFast:
let thinningPlugin = ThinningCreatorPlugin(
targetTempDir: context.targetTempDir,
modeMarkerPath: context.modeMarkerPath,
dirScanner: fileManager
)
creatorPlugins.append(thinningPlugin)
@@ -229,6 +232,7 @@ public class XCPostbuild {
dSYMOrganizer: dSYMOrganizer,
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: creatorPlugins,
consumerPlugins: consumerPlugins
)
@@ -243,11 +247,22 @@ public class XCPostbuild {
try postbuildAction.deleteFingerprintOverrides()
}
// Trigger uploading the artifact
if context.mode == .producer {
switch (context.mode, try modeController.isEnabled(), context.remoteCommit) {
case (.producerFast, true, .available(commit: let commitToReuse)):
// Upload only updated meta. Artifact zip is already on a remote server
let referenceCommit = try config.publishingSha ?? gitClient.getCurrentSha()
let metaData = try remoteNetworkClient.fetch(.meta(commit: commitToReuse))
let meta = try metaReader.read(data: metaData)
try postbuildAction.performMetaUpload(meta: meta, for: referenceCommit)
case (.producer, _, _), (.producerFast, _, _):
// Generate artifacts and upload to the remote server for a reference sha
let referenceCommit = try config.publishingSha ?? gitClient.getCurrentSha()
try postbuildAction.performBuildUpload(for: referenceCommit)
default:
// Consumer does not upload anything
break
}
let executableURL = context.productsDir.appendingPathComponent(context.executablePath)
@@ -261,9 +276,8 @@ public class XCPostbuild {
} else {
try postbuildAction.performBuildCleanup()
try cacheHitLogger.logMiss()
// Producer mode doesn't use cached artifacts so modeController is not enabled. If producer
// reaches this point, there were no issues with publishing
let actionName = context.mode == .producer ? "Published" : "Disabled"
// If producers reach this point, there were no issues with publishing
let actionName = context.mode == .consumer ? "Disabled" : "Published"
printToUser("\(actionName) remote cache for \(context.targetName)")
}
} catch PluginError.unrecoverableError(let error) {
@@ -55,6 +55,7 @@ class Prebuild {
self.artifactConsumerPrebuildPlugins = artifactConsumerPrebuildPlugins
}
// swiftlint:disable:next function_body_length
public func perform() throws -> PrebuildResult {
guard case .available(let commit) = context.remoteCommit else {
return .incompatible
@@ -129,7 +129,10 @@ public class XCPrebuild {
algorithm: MD5Algorithm()
)
let organizer = ZipArtifactOrganizer(targetTempDir: context.targetTempDir, fileManager: fileManager)
let compilationHistoryOrganizer = CompilationHistoryFileOrganizer(context.compilationHistoryFile, fileManager: fileManager)
let compilationHistoryOrganizer = CompilationHistoryFileOrganizer(
context.compilationHistoryFile,
fileManager: fileManager
)
let metaReader = JsonMetaReader(fileAccessor: fileManager)
var consumerPlugins: [ArtifactConsumerPrebuildPlugin] = []
@@ -79,6 +79,7 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
/// Generates source of the cc wrapper
// swiftlint:disable line_length
// swiftlint:disable:next function_body_length
private func buildWrapperSource(clangCommand: String, markerFilename: String, commitSha: String) -> String {
return """
@@ -516,5 +517,5 @@ class TemplateBasedCCWrapperBuilder: CCWrapperBuilder {
#pragma GCC diagnostic pop
}
"""
} // swiftlint:disable:next file_length
}
} // swiftlint:disable:next file_length line_length
} // swiftlint:enable line_length
@@ -56,7 +56,7 @@ class FileLLDBInitPatcher: LLDBInitPatcher {
}
private func findIndices(in collection: [String], value: String) -> [Int] {
collection.enumerated().reduce([]) { (result, line) -> [Int] in
collection.enumerated().reduce([]) { result, line -> [Int] in
if line.element == Self.preambleString {
return result + [line.offset]
}
@@ -75,7 +75,7 @@ class FileLLDBInitPatcher: LLDBInitPatcher {
var contentLines = originalContentLines
let preambleIndices = findIndices(in: contentLines, value: Self.preambleString)
if preambleIndices.count > 0 {
if !preambleIndices.isEmpty {
let firstLLDBCommandIndex = preambleIndices[0] + 1
if firstLLDBCommandIndex >= contentLines.count {
// corrupted file, append the script line at the bottom
@@ -64,6 +64,7 @@ public class XCIntegrate {
self.output = output
}
// swiftlint:disable:next function_body_length
public func main() {
do {
let env = ProcessInfo.processInfo.environment
@@ -114,7 +115,7 @@ public class XCIntegrate {
let integrator = XcodeProjIntegrate(
project: context.projectPath,
mode:context.mode,
mode: context.mode,
binaries: context.binaries,
configurationIncludeOracle: configurationOracle,
targetIncludeOracle: targetOracle,
@@ -114,7 +114,9 @@ struct XcodeProjIntegrate: Integrate {
markPhase = PBXShellScriptBuildPhase(
name: "\(Self.BuildStepPrefix)RemoteCache_mark",
inputPaths: [binaries.prepare.path],
shellScript: "\"$SCRIPT_INPUT_FILE_0\" mark --configuration \"$CONFIGURATION\" --platform \"$PLATFORM_NAME\""
shellScript:
"\"$SCRIPT_INPUT_FILE_0\" mark " +
"--configuration \"$CONFIGURATION\" --platform \"$PLATFORM_NAME\""
)
}
@@ -129,6 +131,7 @@ struct XcodeProjIntegrate: Integrate {
try encodedYAML.write(to: configOverrideLocation, atomically: false, encoding: .utf8)
}
// swiftlint:disable:next function_body_length
func run() throws {
let outputFile = output ?? projectURL
let projectRoot = projectURL.deletingLastPathComponent()
@@ -100,7 +100,7 @@ struct XcodeSettingsCFlags: XcodeSettingsFlags {
case (.some(let existing), _):
var flagsComponents: [String] = existing.split(separator: " ").map(String.init)
// remove (if exists)
let existingFlagIndex = flagsComponents.firstIndex { (component) -> Bool in
let existingFlagIndex = flagsComponents.firstIndex { component -> Bool in
component.hasPrefix("\(Self.prefix)\(key)=")
}
if let index = existingFlagIndex {
@@ -38,6 +38,7 @@ public class XCPrepareMark {
self.commit = commit
}
// swiftlint:disable:next function_body_length
public func main() {
let env = ProcessInfo.processInfo.environment
let fileManager = FileManager.default
@@ -120,9 +120,9 @@ class Swiftc: SwiftcProtocol {
let prebuildDiscoveryURL = context.tempDir.appendingPathComponent(context.prebuildDependenciesPath)
let prebuildDiscoverWriter = dependenciesWriterFactory(prebuildDiscoveryURL, fileManager)
try prebuildDiscoverWriter.write(skipForSha: remoteCommit)
case .consumer, .producer:
// Never skips prebuild phase and fallbacks to the swiftc compilation for:
// 1) Not enabled remote cache or 2) producer
case .consumer, .producer, .producerFast:
// Never skip prebuild phase and fallback to the swiftc compilation for:
// 1) Not enabled remote cache, 2) producer(s)
break
}
return .forceFallback
@@ -24,6 +24,8 @@ public struct SwiftcContext {
case producer
/// Commit sha of the commit to use during remote cache
case consumer(commit: RemoteCommitInfo)
/// Remote artifact exists and can be optimistically used in place of a local compilation
case producerFast
}
let objcHeaderOutput: URL
@@ -74,6 +76,14 @@ public struct SwiftcContext {
mode = .consumer(commit: remoteCommit)
case .producer:
mode = .producer
case .producerFast:
let remoteCommit = RemoteCommitInfo(try? String(contentsOf: remoteCommitLocation).trim())
switch remoteCommit {
case .unavailable:
mode = .producer
case .available:
mode = .producerFast
}
}
invocationHistoryFile = URL(fileURLWithPath: config.compilationHistoryFile, relativeTo: tempDir)
}
@@ -116,6 +116,12 @@ class SwiftcOrchestrator {
}
case .consumer:
fallbackToDefault(command: swiftcCommand)
case .producerFast:
let compileStepResult = try swiftc.mockCompilation()
if case .forceFallback = compileStepResult {
// cannot reuse cached artifact. Build it locally and upload to the server just as for the producer
fallthrough
}
case .producer:
var swiftcArgs = ProcessInfo().arguments
swiftcArgs = try producerFallbackCommandProcessors.reduce(swiftcArgs) { args, processor in
@@ -101,7 +101,10 @@ public class XCSwiftc {
objcHeaderOutput: context.objcHeaderOutput,
diskCopier: HardLinkDiskCopier(fileManager: fileManager)
)
let allInvocationsStorage = ExistingFileStorage(storageFile: context.invocationHistoryFile, command: swiftcCommand)
let allInvocationsStorage = ExistingFileStorage(
storageFile: context.invocationHistoryFile,
command: swiftcCommand
)
// When fallbacking to local compilation do not call historical `swiftc` invocations
// The current fallback invocation already compiles all files in a target
let invocationStorage = FilteredInvocationStorage(
+1
View File
@@ -20,4 +20,5 @@
public enum Mode: String, Codable, CaseIterable {
case consumer
case producer
case producerFast = "producer-fast"
}
@@ -94,10 +94,11 @@ public struct XCRemoteCacheConfig: Encodable {
var focusedTargets: [String] = []
/// Disable cache for http requests to fecth metadata and download artifacts
var disableHttpCache: Bool = false
/// Path, relative to $TARGET_TEMP_DIR which gathers all compilation commands that should be executed if a target
/// switches to local compilation. Example: A new `.swift` file invalidates remote artifact and triggers local compilation
/// When that happens, all previously skipped clang build steps need to be eventually called locally - this file lists
/// all these commands.
/// Path, relative to $TARGET_TEMP_DIR which gathers all compilation commands that should be e
/// xecuted if a target switches to local compilation.
/// Example: A new `.swift` file invalidates remote arXcodeProjIntegrate.swifttifact and triggers local compilation
/// When that happens, all previously skipped clang build steps
/// need to be eventually called locally - this file lists all these commands.
var compilationHistoryFile: String = "history.compile"
/// Timeout for remote response data interval (in seconds). If an interval between data chunks is
/// longer than a timeout, a request fails
@@ -124,6 +125,7 @@ public struct XCRemoteCacheConfig: Encodable {
extension XCRemoteCacheConfig {
/// Merges existing config with the other config and returns a final result
/// `other` scheme overrides existing configuration
// swiftlint:disable:next function_body_length
func merged(with scheme: ConfigFileScheme) -> XCRemoteCacheConfig {
var merge = self
merge.mode = scheme.mode ?? mode
@@ -0,0 +1,40 @@
// 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
protocol MetaWriter {
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta
}
class JsonMetaWriter: MetaWriter {
private let metaEncoder = JSONEncoder()
private let fileWriter: FileWriter
init(fileWriter: FileWriter) {
self.fileWriter = fileWriter
}
func write<T>(_ meta: T, locationDir : URL) throws -> URL where T : Meta {
let metaURL = locationDir.appendingPathComponent(meta.fileKey).appendingPathExtension("json")
let metaData = try metaEncoder.encode(meta)
try fileWriter.write(toPath: metaURL.path, contents: metaData)
return metaURL
}
}
@@ -35,9 +35,22 @@ struct AWSV4Signature {
request.setValue((request.httpBody ?? Data()).sha256(), forHTTPHeaderField: "x-amz-content-sha256")
let canonicalRequest = CanonicalRequest(request: request)
let stringToSign = StringToSign(region: region, service: service, canonicalRequestHash: canonicalRequest.hash, date: date)
let awsV4SigningKey = AWSV4SigningKey(secretAccessKey: secretKey, region: region, service: service, date: date)
let signature = HMAC.calcHMAC(keyArray: awsV4SigningKey.value, value: stringToSign.value).map { String(format: "%02hhx", $0) }.joined()
let stringToSign = StringToSign(
region: region,
service: service,
canonicalRequestHash: canonicalRequest.hash,
date: date
)
let awsV4SigningKey = AWSV4SigningKey(
secretAccessKey: secretKey,
region: region,
service: service,
date: date
)
let signature = HMAC.calcHMAC(
keyArray: awsV4SigningKey.value,
value: stringToSign.value
).map { String(format: "%02hhx", $0) }.joined()
let authValue =
"AWS4-HMAC-SHA256 " +
@@ -52,7 +52,14 @@ struct HMAC {
private static func calcHMAC(keyUnsafeBytes: UnsafeRawBufferPointer, value: String, out: UnsafeMutableRawPointer!) {
value.data(using: .utf8)!.withUnsafeBytes { value in
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), keyUnsafeBytes.baseAddress, Int(keyUnsafeBytes.count), value.baseAddress, Int(value.count), out)
CCHmac(
CCHmacAlgorithm(kCCHmacAlgSHA256),
keyUnsafeBytes.baseAddress,
Int(keyUnsafeBytes.count),
value.baseAddress,
Int(value.count),
out
)
}
}
}
@@ -44,7 +44,7 @@ class RemoteNetworkClientAbstractFactory {
return RemoteNetworkClientImpl(networkClient, downloadURLBuilder)
}
switch mode {
case .producer:
case .producer, .producerFast:
let upstreamBuilders = try upstreamStreamURL.map(urlBuilderFactory)
return ReplicatedRemotesNetworkClient(
networkClient,
@@ -28,8 +28,8 @@ protocol CacheHitLogger {
/// Logs target hit or miss, based on an action of a build
class ActionSpecificCacheHitLogger: CacheHitLogger {
private let statsLogger: StatsLogger
private let hitCounter: XCRemoteCacheStatistics.Counter
private let missCounter: XCRemoteCacheStatistics.Counter
private let hitCounter: XCRemoteCacheStatistics.Counter?
private let missCounter: XCRemoteCacheStatistics.Counter?
init(action: BuildActionType, statsLogger: StatsLogger) {
self.statsLogger = statsLogger
@@ -37,19 +37,24 @@ class ActionSpecificCacheHitLogger: CacheHitLogger {
case .index:
hitCounter = .indexingTargetHitCount
missCounter = .indexingTargetMissCount
case .unknown:
fallthrough
case .build:
hitCounter = .targetCacheHit
missCounter = .targetCacheMiss
case .unknown:
hitCounter = nil
missCounter = nil
}
}
func logHit() throws {
try statsLogger.log(hitCounter)
if let hitCounter = hitCounter {
try statsLogger.log(hitCounter)
}
}
func logMiss() throws {
try statsLogger.log(missCounter)
if let missCounter = missCounter {
try statsLogger.log(missCounter)
}
}
}
@@ -31,6 +31,7 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
private var builder: ArtifactSwiftProductsBuilderImpl!
override func setUpWithError() throws {
try super.setUpWithError()
let rootDir = try prepareTempDir()
moduleDir = rootDir.appendingPathComponent("Products")
swiftmoduleFile = moduleDir.appendingPathComponent("MyModule.swiftmodule")
@@ -47,24 +48,41 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
func testIncludesRequiredSwiftmoduleFiles() throws {
try fileManager.spt_createFile(swiftmoduleFile, content: "swiftmodule")
try fileManager.spt_createFile(swiftmoduleDocFile, content: "swiftdoc")
let builderSwiftmoduleDir = builder.buildingArtifactSwiftModulesLocation().appendingPathComponent("arm64")
let expectedBuildedSwiftmoduleFile = builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftmodule")
let expectedBuildedSwiftmoduledocFile = builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftdoc")
let builderSwiftmoduleDir =
builder
.buildingArtifactSwiftModulesLocation()
.appendingPathComponent("arm64")
let expectedBuildedSwiftmoduleFile =
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftmodule")
let expectedBuildedSwiftmoduledocFile =
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftdoc")
try builder.includeModuleDefinitionsToTheArtifact(arch: "arm64", moduleURL: swiftmoduleFile)
XCTAssertEqual(fileManager.contents(atPath: expectedBuildedSwiftmoduleFile.path), "swiftmodule".data(using: .utf8))
XCTAssertEqual(fileManager.contents(atPath: expectedBuildedSwiftmoduledocFile.path), "swiftdoc".data(using: .utf8))
XCTAssertEqual(
fileManager.contents(atPath: expectedBuildedSwiftmoduleFile.path),
"swiftmodule".data(using: .utf8)
)
XCTAssertEqual(
fileManager.contents(atPath: expectedBuildedSwiftmoduledocFile.path),
"swiftdoc".data(using: .utf8)
)
}
func testIncludesAllSwiftmoduleFiles() throws {
try fileManager.spt_createEmptyFile(swiftmoduleFile)
try fileManager.spt_createEmptyFile(swiftmoduleDocFile)
try fileManager.spt_createEmptyFile(swiftmoduleSourceInfoFile)
let builderSwiftmoduleDir = builder.buildingArtifactSwiftModulesLocation().appendingPathComponent("arm64")
let expectedBuildedSwiftmoduleFile = builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftmodule")
let expectedBuildedSwiftmoduledocFile = builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftdoc")
let expectedBuildedSwiftSourceInfoFile = builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftsourceinfo")
let builderSwiftmoduleDir =
builder
.buildingArtifactSwiftModulesLocation()
.appendingPathComponent("arm64")
let expectedBuildedSwiftmoduleFile =
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftmodule")
let expectedBuildedSwiftmoduledocFile =
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftdoc")
let expectedBuildedSwiftSourceInfoFile =
builderSwiftmoduleDir.appendingPathComponent("MyModule.swiftsourceinfo")
try builder.includeModuleDefinitionsToTheArtifact(arch: "arm64", moduleURL: swiftmoduleFile)
@@ -74,6 +92,11 @@ class ArtifactSwiftProductsBuilderImplTests: FileXCTestCase {
}
func testFailsIncludingWhenMissingRequiredSwiftmoduleFiles() throws {
XCTAssertThrowsError(try builder.includeModuleDefinitionsToTheArtifact(arch: "arm64", moduleURL: swiftmoduleFile))
XCTAssertThrowsError(
try builder.includeModuleDefinitionsToTheArtifact(
arch: "arm64",
moduleURL: swiftmoduleFile
)
)
}
}
@@ -63,6 +63,7 @@ class BuildArtifactCreatorTests: FileXCTestCase {
moduleName: "Target",
modulesFolderPath: "",
dSYMPath: dSYM,
metaWriter: JsonMetaWriter(fileWriter: fileManager),
fileManager: fileManager
)
}
@@ -35,7 +35,11 @@ class ZipArtifactCreatorTests: FileXCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
workingDir = try prepareTempDir().appendingPathComponent("creator")
creator = ZipArtifactCreator(workingDir: workingDir, fileManager: fileManager)
creator = ZipArtifactCreator(
workingDir: workingDir,
metaWriter: JsonMetaWriter(fileWriter: fileManager),
fileManager: fileManager
)
}
func testCreatingArtifactGeneratesValidArtifactId() throws {
@@ -33,7 +33,10 @@ class ThinningCreatorPluginTests: FileXCTestCase {
targetTempDirRoot = workingDir.appendingPathComponent("Root")
currentTargetTempDir = targetTempDirRoot.appendingPathComponent("Current.build")
try fileManager.spt_createEmptyDir(currentTargetTempDir)
plugin = ThinningCreatorPlugin(targetTempDir: currentTargetTempDir, dirScanner: FileManager.default)
plugin = ThinningCreatorPlugin(
targetTempDir: currentTargetTempDir,
modeMarkerPath: "rc_marker.enabled",
dirScanner: FileManager.default)
}
func testReturnsEmptyExtraKeysForNoArtifacts() throws {
@@ -73,4 +76,62 @@ class ThinningCreatorPluginTests: FileXCTestCase {
XCTAssertThrowsError(try plugin.extraMetaKeys(Self.sampleMeta))
}
func testDefinesExtraMetaKeysForTargetsThatReusedArtifact() throws {
let otherTargetTempDir = targetTempDirRoot.appendingPathComponent("Other.build")
let marker = otherTargetTempDir.appendingPathComponent("rc_marker.enabled")
let reusedArtifact = otherTargetTempDir
.appendingPathComponent("xccache")
.appendingPathComponent("123")
.appendingPathExtension("zip")
try fileManager.spt_createEmptyFile(marker)
try fileManager.spt_createEmptyFile(reusedArtifact)
let extraKeys = try plugin.extraMetaKeys(Self.sampleMeta)
XCTAssertEqual(extraKeys, ["thinning_Other": "123"])
}
func testFailsGeneratingExtraMetaKeysForTwoArtifactsInTargetTempDir() throws {
let otherTargetTempDir = targetTempDirRoot.appendingPathComponent("Other.build")
let marker = otherTargetTempDir.appendingPathComponent("rc_marker.enabled")
let reusedArtifact1 = otherTargetTempDir
.appendingPathComponent("xccache")
.appendingPathComponent("001")
.appendingPathExtension("zip")
let reusedArtifact2 = otherTargetTempDir
.appendingPathComponent("xccache")
.appendingPathComponent("002")
.appendingPathExtension("zip")
try fileManager.spt_createEmptyFile(marker)
try fileManager.spt_createEmptyFile(reusedArtifact1)
try fileManager.spt_createEmptyFile(reusedArtifact2)
XCTAssertThrowsError(try plugin.extraMetaKeys(Self.sampleMeta))
}
func testDefinesExtraMetaKeysForGeneratedAndReusedArtifact() throws {
let otherTargetTempDir = targetTempDirRoot.appendingPathComponent("Generated.build")
let generatedArtifact = otherTargetTempDir
.appendingPathComponent("xccache")
.appendingPathComponent("produced")
.appendingPathComponent("000")
.appendingPathExtension("zip")
try fileManager.spt_createEmptyFile(generatedArtifact)
let reusedTargetTempDir = targetTempDirRoot.appendingPathComponent("Reused.build")
let marker = reusedTargetTempDir.appendingPathComponent("rc_marker.enabled")
let reusedArtifact = reusedTargetTempDir
.appendingPathComponent("xccache")
.appendingPathComponent("999")
.appendingPathExtension("zip")
try fileManager.spt_createEmptyFile(marker)
try fileManager.spt_createEmptyFile(reusedArtifact)
let extraKeys = try plugin.extraMetaKeys(Self.sampleMeta)
XCTAssertEqual(extraKeys, [
"thinning_Generated": "000",
"thinning_Reused": "999"
])
}
}
@@ -50,7 +50,8 @@ class PostbuildTests: FileXCTestCase {
bundleDir: nil,
derivedSourcesDir: "",
thinnedTargets: [],
action: .build
action: .build,
modeMarkerPath: ""
)
private var network = RemoteNetworkClientImpl(
NetworkClientFake(fileManager: .default),
@@ -84,6 +85,7 @@ class PostbuildTests: FileXCTestCase {
)
private var modeController = CacheModeControllerFake()
private var metaReader = JsonMetaReader(fileAccessor: FileManager.default)
private var metaWriter = JsonMetaWriter(fileWriter: FileManager.default)
private static let SampleMeta = MainArtifactSampleMeta.defaults
private var sampleMetaFile: URL!
@@ -122,6 +124,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -151,6 +154,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -178,6 +182,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: dsymOrganizer,
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -205,6 +210,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: dsymOrganizer,
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -242,6 +248,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: dsymOrganizer,
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -282,6 +289,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: dsymOrganizer,
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -307,6 +315,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: fakeModeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -354,6 +363,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [plugin],
consumerPlugins: []
)
@@ -385,6 +395,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [plugin],
consumerPlugins: []
)
@@ -417,6 +428,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: [consumerPlugin]
)
@@ -454,6 +466,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: [consumerPlugin]
)
@@ -485,6 +498,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: [consumerPlugin]
)
@@ -518,6 +532,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -550,6 +565,7 @@ class PostbuildTests: FileXCTestCase {
dSYMOrganizer: DSYMOrganizerFake(dSYMFile: nil),
modeController: modeController,
metaReader: metaReader,
metaWriter: metaWriter,
creatorPlugins: [],
consumerPlugins: []
)
@@ -558,5 +574,68 @@ class PostbuildTests: FileXCTestCase {
try postbuild.deleteFingerprintOverrides()
XCTAssertFalse(fileManager.fileExists(atPath: previousFingerprintOverride.path))
} // swiftlint:disable:next file_length
}
func testUploadingMeta() throws {
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.performMetaUpload(meta: Self.SampleMeta, for: "33")
let data = try network.fetch(.meta(commit: "33"))
let downloadedMeta = try metaReader.read(data: data)
XCTAssertEqual(downloadedMeta, Self.SampleMeta)
}
func testUploadingMetaWithNewPluginKeys() throws {
let plugin = MetaAppenderArtifactCreatorPlugin(["New": "Value"])
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: [plugin],
consumerPlugins: []
)
var meta = Self.SampleMeta
meta.pluginsKeys = ["Previous": "Value"]
var expectedMeta = meta
expectedMeta.pluginsKeys = ["New": "Value"]
try postbuild.performMetaUpload(meta: meta, for: "33")
let data = try network.fetch(.meta(commit: "33"))
let downloadedMeta = try metaReader.read(data: data)
XCTAssertEqual(downloadedMeta, expectedMeta)
}
}
// swiftlint:disable:next file_length
@@ -28,6 +28,7 @@ class FileLLDBInitPatcherTests: XCTestCase {
private var patcher: FileLLDBInitPatcher!
override func setUp() {
super.setUp()
accessor = FileAccessorFake(mode: .normal)
patcher = FileLLDBInitPatcher(
file: lldbInitPath,
@@ -62,4 +62,19 @@ class SwiftcContextTests: FileXCTestCase {
XCTAssertEqual(context.mode, .consumer(commit: .unavailable))
}
func testProducerModeWhenFileWithCommitShaExistsIsResolvedToProducerFast() throws {
config.mode = .producerFast
let context = try SwiftcContext(config: config, input: input)
XCTAssertEqual(context.mode, .producerFast)
}
func testProducerModeWhenFileWithCommitShaDoesntExxistIsResolvedToProducer() throws {
config.mode = .producerFast
try fileManager.spt_deleteItem(at: remoteCommitFile)
let context = try SwiftcContext(config: config, input: input)
XCTAssertEqual(context.mode, .producer)
}
}
@@ -21,8 +21,8 @@
import XCTest
class SwiftcFilemapInputEditorTests: FileXCTestCase {
private let sampleInfo = SwiftCompilationInfo(info: SwiftModuleCompilationInfo(
private let sampleInfo = SwiftCompilationInfo(
info: SwiftModuleCompilationInfo(
dependencies: nil,
swiftDependencies: "/"
), files: [])
@@ -65,17 +65,19 @@ class SwiftcFilemapInputEditorTests: FileXCTestCase {
}
}
"""#.data(using: .utf8)!
let expectedInfo = SwiftCompilationInfo(info: SwiftModuleCompilationInfo(
dependencies: "/master.d",
swiftDependencies: "/master.swiftdeps"
), files: [
SwiftFileCompilationInfo(
file: "/file1.swift",
dependencies: "/file1.d",
object: "/file1.o",
swiftDependencies: "/file1.swiftdeps"
let expectedInfo = SwiftCompilationInfo(
info: SwiftModuleCompilationInfo(
dependencies: "/master.d",
swiftDependencies: "/master.swiftdeps"
),
])
files: [
SwiftFileCompilationInfo(
file: "/file1.swift",
dependencies: "/file1.d",
object: "/file1.o",
swiftDependencies: "/file1.swiftdeps"
),
])
try fileManager.spt_writeToFile(atPath: inputFile.path, contents: infoContentData)
let readInfo = try editor.read()
@@ -93,17 +95,18 @@ class SwiftcFilemapInputEditorTests: FileXCTestCase {
}
func testWritingSavesContentWithOptionalParameters() throws {
let extendedInfo = SwiftCompilationInfo(info: SwiftModuleCompilationInfo(
dependencies: "/master.d",
swiftDependencies: "/master.swiftdeps"
), files: [
SwiftFileCompilationInfo(
file: "/file1.swift",
dependencies: "/file1.d",
object: "/file1.o",
swiftDependencies: "/file1.swiftdeps"
),
])
let extendedInfo = SwiftCompilationInfo(
info: SwiftModuleCompilationInfo(
dependencies: "/master.d",
swiftDependencies: "/master.swiftdeps"
), files: [
SwiftFileCompilationInfo(
file: "/file1.swift",
dependencies: "/file1.d",
object: "/file1.o",
swiftDependencies: "/file1.swiftdeps"
),
])
try editor.write(extendedInfo)
@@ -190,4 +190,45 @@ class SwiftcOrchestratorTests: XCTestCase {
XCTAssertNotNil(shellOutSpy.switchedProcess)
}
func testForFailedCompilationMockInProducerFastModeBuildsArtifactObjCHeader() throws {
let swiftc = SwiftcMock(mockingResult: .forceFallback)
let orchestrator = SwiftcOrchestrator(
mode: .producerFast,
swiftc: swiftc,
swiftcCommand: "",
objcHeaderOutput: objcHeaderURL,
moduleOutput: moduleOutputURL,
arch: "archTest",
artifactBuilder: artifactBuilder,
producerFallbackCommandProcessors: [],
invocationStorage: invocationStorage,
shellOut: shellOutSpy
)
try orchestrator.run()
XCTAssertEqual(artifactBuilder.addedObjCHeaders, ["archTest": [objcHeaderURL]])
}
func testSuccessedMockInProducerFastModeDoesntFillObjCHeader() throws {
let swiftc = SwiftcMock(mockingResult: .success)
let orchestrator = SwiftcOrchestrator(
mode: .producerFast,
swiftc: swiftc,
swiftcCommand: "",
objcHeaderOutput: objcHeaderURL,
moduleOutput: moduleOutputURL,
arch: "arch",
artifactBuilder: artifactBuilder,
producerFallbackCommandProcessors: [],
invocationStorage: invocationStorage,
shellOut: shellOutSpy
)
try orchestrator.run()
XCTAssertEqual(artifactBuilder.addedObjCHeaders, [:])
}
}
@@ -277,7 +277,9 @@ class SwiftcTests: FileXCTestCase {
let artifactObjCHeader = URL(fileURLWithPath: "/cachedArtifact/include/archTest/Target-Swift.h")
let artifactSwiftmodule = URL(fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftmodule")
let artifactSwiftdoc = URL(fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftdoc")
let artifactSwiftSourceInfo = URL(fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftsourceinfo")
let artifactSwiftSourceInfo = URL(
fileURLWithPath: "/cachedArtifact/swiftmodule/archTest/Target.swiftsourceinfo"
)
artifactOrganizer = ArtifactOrganizerFake(artifactRoot: artifactRoot)
let swiftc = Swiftc(
@@ -457,5 +459,5 @@ class SwiftcTests: FileXCTestCase {
)
XCTAssertNoThrow(try swiftc.mockCompilation())
}
} // swiftlint:disable:next file_length
}
@@ -0,0 +1,33 @@
// Copyright (c) 2021 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
@testable import XCRemoteCache
import XCTest
import Yams
class ModeTests: XCTestCase {
func testProducerFast() throws {
let yaml = "producer-fast"
let decoder = YAMLDecoder(encoding: .utf8)
let mode: Mode = try decoder.decode(from: yaml)
XCTAssertEqual(mode, .producerFast)
}
}
@@ -27,6 +27,7 @@ class FileFingerprintSyncerTests: FileXCTestCase {
private var swiftmoduleDir: URL!
override func setUpWithError() throws {
try super.setUpWithError()
syncer = FileFingerprintSyncer(
fingerprintOverrideExtension: "md5",
dirAccessor: fileManager,
@@ -27,6 +27,7 @@ class TargetDependenciesReaderTests: XCTestCase {
private var reader: TargetDependenciesReader!
override func setUp() {
super.setUp()
dirAccessor = DirAccessorFake()
/// A Factory that builds a faked dependency reader that returns a single dependency,
/// a basename of the input .d file and the ".swift" extension
@@ -27,6 +27,7 @@ class CopyDiskCopierTests: FileXCTestCase {
private var emptySourceFile: URL!
override func setUpWithError() throws {
try super.setUpWithError()
workingDir = try prepareTempDir()
emptySourceFile = workingDir.appendingPathComponent("source")
try fileManager.spt_writeToFile(atPath: emptySourceFile.path, contents: Data())
@@ -0,0 +1,48 @@
// 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 JsonMetaWriterTests: XCTestCase {
func testWritesToFileWithFilekeyFilename() throws {
let fileAccessor = FileAccessorFake(mode: .normal)
let writer = JsonMetaWriter(fileWriter: fileAccessor)
let workingDir: URL = "/"
let meta = MainArtifactSampleMeta.defaults
let url = try writer.write(MainArtifactSampleMeta.defaults, locationDir: workingDir)
XCTAssertEqual(url, workingDir.appendingPathComponent(meta.fileKey).appendingPathExtension("json"))
}
func testWritesMetaInValidFormat() throws {
let fileAccessor = FileAccessorFake(mode: .normal)
let writer = JsonMetaWriter(fileWriter: fileAccessor)
let reader = JsonMetaReader(fileAccessor: fileAccessor)
let workingDir: URL = "/"
let meta = MainArtifactSampleMeta.defaults
let url = try writer.write(MainArtifactSampleMeta.defaults, locationDir: workingDir)
let readMeta = try reader.read(localFile: url)
XCTAssertEqual(readMeta, meta)
}
}
@@ -26,6 +26,7 @@ class FilteredInvocationStorageTests: XCTestCase {
var storage: FilteredInvocationStorage!
override func setUp() {
super.setUp()
storage = FilteredInvocationStorage(storage: underlyingStorage, retrieveIgnoredCommands: ["to_ignore"])
}
@@ -28,6 +28,7 @@ class InvocationFileStorageTests: FileXCTestCase {
private var storage: ExistingFileStorage!
override func setUpWithError() throws {
try super.setUpWithError()
file = try prepareTempDir().appendingPathComponent("file.history")
try fileManager.spt_createEmptyFile(file)
storage = ExistingFileStorage(storageFile: file, command: command)
@@ -25,6 +25,7 @@ class ActionSpecificCacheHitLoggerTests: FileXCTestCase {
private var coordinator: StatsCoordinator!
override func setUp() {
super.setUp()
coordinator = InMemoryStatsCoordinator()
}
+12
View File
@@ -4,6 +4,14 @@ The CocoaPods plugin that integrates XCRemoteCache with the project.
## Installation
### Using RubyGems
```bash
gem install cocoapods-xcremotecache
```
### From sources
Build & install the plugin
```bash
@@ -52,3 +60,7 @@ 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).
@@ -46,17 +46,17 @@ module CocoapodsXCRemoteCacheModifier
@@configuration = c
end
def self.set_configuration_default_values(user_proj_directory)
def self.set_configuration_default_values
default_values = {
'mode' => 'consumer',
'enabled' => true,
'xcrc_location' => "#{user_proj_directory}/XCRC",
'xcrc_location' => "XCRC",
'exclude_build_configurations' => [],
'check_build_configuration' => 'Debug',
'check_platform' => 'iphonesimulator',
'modify_lldb_init' => true,
'xccc_file' => "#{user_proj_directory}/#{BIN_DIR}/xccc",
'remote_commit_file' => "#{user_proj_directory}/#{BIN_DIR}/arc.rc",
'xccc_file' => "#{BIN_DIR}/xccc",
'remote_commit_file' => "#{BIN_DIR}/arc.rc",
'exclude_targets' => [],
}
@@configuration.merge! default_values.select { |k, v| !@@configuration.key?(k) }
@@ -89,61 +89,95 @@ module CocoapodsXCRemoteCacheModifier
@@configuration.select { |key, value| !CUSTOM_CONFIGURATION_KEYS.include?(key) }
end
def self.enable_xcremotecache(target, user_proj_directory, xc_location, xc_cc_path, mode, exclude_build_configurations, check_build_configuration, check_platform, final_target)
target.build_configurations.each do |config|
# apply only for relevant Configurations
next if exclude_build_configurations.include?(config.name)
if mode == 'consumer'
config.build_settings['CC'] = [xc_cc_path]
end
config.build_settings['SWIFT_EXEC'] = ["#{xc_location}/xcswiftc"]
config.build_settings['LIBTOOL'] = ["#{xc_location}/xclibtool"]
config.build_settings['LD'] = ["#{xc_location}/xcld"]
def self.parent_dir(path, parent_count)
"../" * parent_count + path
end
config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = FAKE_SRCROOT
add_cflags!(config.build_settings, '-fdebug-prefix-map', "#{user_proj_directory}=$(XCREMOTE_CACHE_FAKE_SRCROOT)")
add_swiftflags!(config.build_settings, '-debug-prefix-map', "#{user_proj_directory}=$(XCREMOTE_CACHE_FAKE_SRCROOT)")
end
# @param target [Target] target to apply XCRemoteCache
# @param repo_distance [Integer] distance from the git repo root to the target's $SRCROOT
# @param xc_location [String] path to the dir with all XCRemoteCache binaries, relative to the repo root
# @param xc_cc_path [String] path to the XCRemoteCache clang wrapper, relative to the repo root
# @param mode [String] mode name ('consumer', 'producer' etc.)
# @param 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)
srcroot_relative_xc_location = parent_dir(xc_location, repo_distance)
# User project is not generated from scratch (contrary to `Pods`), delete all previous XCRemoteCache phases
target.build_phases.delete_if {|phase|
# Some phases (e.g. PBXSourcesBuildPhase) don't have strict name check respond_to?
if phase.respond_to?(:name)
phase.name != nil && phase.name.start_with?("[XCRC]")
target.build_configurations.each do |config|
# 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)}"]
end
}
config.build_settings['SWIFT_EXEC'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcswiftc"]
config.build_settings['LIBTOOL'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xclibtool"]
config.build_settings['LD'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcld"]
config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = FAKE_SRCROOT
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)")
end
# Prebuild
if mode == 'consumer'
prebuild_script = target.new_shell_script_build_phase("[XCRC] Prebuild")
existing_prebuild_script = target.build_phases.detect do |phase|
if phase.respond_to?(:name)
phase.name != nil && phase.name.start_with?("[XCRC] Prebuild")
end
end
prebuild_script = existing_prebuild_script || target.new_shell_script_build_phase("[XCRC] Prebuild")
prebuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
prebuild_script.input_paths = ["#{xc_location}/xcprebuild"]
prebuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprebuild"]
prebuild_script.output_paths = [
"$(TARGET_TEMP_DIR)/rc.enabled",
"$(TARGET_TEMP_DIR)/rc.enabled",
"$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)"
]
prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d"
# Move prebuild (last element) to the first position (to make it real 'prebuild')
target.build_phases.rotate!(-1)
target.build_phases.rotate!(-1) if existing_prebuild_script.nil?
elsif mode == 'producer'
# Delete existing prebuild build phase (to support switching between modes)
target.build_phases.delete_if do |phase|
if phase.respond_to?(:name)
phase.name != nil && phase.name.start_with?("[XCRC] Prebuild")
end
end
end
# Postbuild
postbuild_script = target.new_shell_script_build_phase("[XCRC] Postbuild")
existing_postbuild_script = target.build_phases.detect do |phase|
if phase.respond_to?(:name)
phase.name != nil && phase.name.start_with?("[XCRC] Postbuild")
end
end
postbuild_script = existing_postbuild_script || target.new_shell_script_build_phase("[XCRC] Postbuild")
postbuild_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\""
postbuild_script.input_paths = ["#{xc_location}/xcpostbuild"]
postbuild_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcpostbuild"]
postbuild_script.output_paths = [
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH).swiftmodule.md5",
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH).swiftmodule.md5",
"$(TARGET_BUILD_DIR)/$(MODULES_FOLDER_PATH)/$(PRODUCT_MODULE_NAME).swiftmodule/$(PLATFORM_PREFERRED_ARCH)-$(LLVM_TARGET_TRIPLE_VENDOR)-$(SWIFT_PLATFORM_TARGET_PREFIX)$(LLVM_TARGET_TRIPLE_SUFFIX).swiftmodule.md5"
]
postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d"
# Mark a sha as ready for a given platform and configuration when building the final_target
if mode == 'producer' && target.name == final_target
mark_script = target.new_shell_script_build_phase("[XCRC] Mark")
existing_mark_script = target.build_phases.detect do |phase|
if phase.respond_to?(:name)
phase.name != nil && phase.name.start_with?("[XCRC] Mark")
end
end
mark_script = existing_mark_script || target.new_shell_script_build_phase("[XCRC] Mark")
mark_script.shell_script = "\"$SCRIPT_INPUT_FILE_0\" mark --configuration $CONFIGURATION --platform $PLATFORM_NAME"
mark_script.input_paths = ["#{xc_location}/xcprepare"]
mark_script.input_paths = ["$SRCROOT/#{srcroot_relative_xc_location}/xcprepare"]
else
# Delete existing mark build phase (to support switching between modes or changing the final target)
target.build_phases.delete_if do |phase|
if phase.respond_to?(:name)
phase.name != nil && phase.name.start_with?("[XCRC] Mark")
end
end
end
end
@@ -251,6 +285,7 @@ module CocoapodsXCRemoteCacheModifier
# Returns the content (array of lines) of the lldbinit with stripped XCRemoteCache rewrite
def self.clean_lldbinit_content(lldbinit_path)
all_lines = []
return all_lines unless File.exist?(lldbinit_path)
File.open(lldbinit_path) { |file|
while(line = file.gets) != nil
line = line.strip
@@ -290,7 +325,7 @@ module CocoapodsXCRemoteCacheModifier
begin
user_proj_directory = File.dirname(user_project.path)
set_configuration_default_values(user_proj_directory)
set_configuration_default_values
unless @@configuration['enabled']
Pod::UI.puts "[XCRC] XCRemoteCache disabled"
@@ -310,8 +345,12 @@ module CocoapodsXCRemoteCacheModifier
check_build_configuration = @@configuration['check_build_configuration']
check_platform = @@configuration['check_platform']
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)
download_xcrc_if_needed(xcrc_location_absolute)
# Save .rcinfo
save_rcinfo(generate_rcinfo(), user_proj_directory)
@@ -320,12 +359,34 @@ module CocoapodsXCRemoteCacheModifier
Dir.mkdir(BIN_DIR) unless File.exist?(BIN_DIR)
# Remove previous xccc & arc.rc
File.delete(remote_commit_file) if File.exist?(remote_commit_file)
File.delete(xccc_location) if File.exist?(xccc_location)
File.delete(remote_commit_file_absolute) if File.exist?(remote_commit_file_absolute)
File.delete(xccc_location_absolute) if File.exist?(xccc_location_absolute)
# Prepare XCRC
# Pods projects can be generated only once (if incremental_installation is enabled)
# Always integrate XCRemoteCache to all Pods, in case it will be needed later
unless installer_context.pods_project.nil?
# Attach XCRemoteCache to Pods targets
installer_context.pods_project.targets.each do |target|
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)
end
# Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
pods_proj_directory = installer_context.sandbox_root
# Manual .rcinfo generation (in YAML format)
save_rcinfo({'extra_configuration_file' => "#{user_proj_directory}/.rcinfo"}, pods_proj_directory)
installer_context.pods_project.save()
end
# Enabled/disable XCRemoteCache for the main (user) project
begin
prepare_result = YAML.load`#{xcrc_location}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}`
prepare_result = YAML.load`#{xcrc_location_absolute}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}`
unless prepare_result['result'] || mode != 'consumer'
# Uninstall the XCRemoteCache for the consumer mode
disable_xcremotecache(user_project)
@@ -338,26 +399,11 @@ module CocoapodsXCRemoteCacheModifier
next
end
# Attach XCRemoteCache to Pods targets
installer_context.pods_project.targets.each do |target|
next if target.name.start_with?("Pods-")
next if target.name.end_with?("Tests")
next if exclude_targets.include?(target.name)
enable_xcremotecache(target, user_proj_directory, xcrc_location, xccc_location, mode, exclude_build_configurations, check_build_configuration, check_platform, final_target)
end
# Create .rcinfo into `Pods` directory as that .xcodeproj reads configuration from .xcodeproj location
pods_proj_directory = installer_context.sandbox_root
# Manual .rcinfo generation (in YAML format)
save_rcinfo({'extra_configuration_file' => "#{user_proj_directory}/.rcinfo"}, pods_proj_directory)
installer_context.pods_project.save()
# Attach XCRC to the app targets
user_project.targets.each do |target|
next if exclude_targets.include?(target.name)
enable_xcremotecache(target, user_proj_directory, xcrc_location, xccc_location, mode, exclude_build_configurations, check_build_configuration, check_platform, final_target)
enable_xcremotecache(target, 0, xcrc_location, xccc_location, mode, exclude_build_configurations, final_target)
end
# Set Target sourcemap
@@ -13,5 +13,5 @@
# limitations under the License.
module CocoapodsXcremotecache
VERSION = "0.0.1"
VERSION = "0.0.3"
end