Sources/XCRemoteCache/Commands/Plugins/Thinning/ThinningConsumerPostbuildPlugin.swift (96 lines of code) (raw):

// Copyright (c) 2021 Spotify AB. // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import Foundation enum ThinningConsumerPostbuildPluginError: Error { /// The aggregation target meta misses a filekey for targets case missingArtifactKey(targetNames: [String]) /// The unzipped artifact is malformed. Is misses a binary file in a root directory case missingBinaryForArtifact(artifact: URL) /// Postbuild of some target(s) failed (potentially the unzipped artifacts is broken) case failed(underlyingErrors: [Error]) } /// Plugin that performs "postbuild" action for all thinned targets - moves binaries, swift products, decorates with /// fingerprint overrides etc class ThinningConsumerPostbuildPlugin: ThinningConsumerPlugin, ArtifactConsumerPostbuildPlugin { private let targetTempDirsRoot: URL private let builtProductsDir: URL private let productModuleName: String private let arch: String private let thinnedTargets: [String] private let artifactOrganizerFactory: ThinningConsumerArtifactsOrganizerFactory private let swiftProductOrganizerFactory: ThinningConsumerSwiftProductsOrganizerFactory private let diskCopier: DiskCopier private let artifactInspector: ArtifactInspector private let swiftProductsArchitecturesRecognizer: SwiftProductsArchitecturesRecognizer private let worker: Worker init( targetTempDir: URL, builtProductsDir: URL, productModuleName: String, arch: String, thinnedTargets: [String], artifactOrganizerFactory: ThinningConsumerArtifactsOrganizerFactory, swiftProductOrganizerFactory: ThinningConsumerSwiftProductsOrganizerFactory, artifactInspector: ArtifactInspector, swiftProductsArchitecturesRecognizer: SwiftProductsArchitecturesRecognizer, diskCopier: DiskCopier, worker: Worker ) { targetTempDirsRoot = targetTempDir.deletingLastPathComponent() self.builtProductsDir = builtProductsDir self.productModuleName = productModuleName self.arch = arch self.thinnedTargets = thinnedTargets self.artifactOrganizerFactory = artifactOrganizerFactory self.swiftProductOrganizerFactory = swiftProductOrganizerFactory self.artifactInspector = artifactInspector self.swiftProductsArchitecturesRecognizer = swiftProductsArchitecturesRecognizer self.diskCopier = diskCopier self.worker = worker } /// Performs the core part of the postbuild phase for a single thinned target /// - Parameters: /// - targetName: Name of the target /// - productArchs: all architectures that should swift products /// should be generated in DerivedData's 'Products' dir /// - fileKey: fileKey that describes the artifact private func performPostbuildFor(targetName: String, productArchs archs: [String], fileKey: String) throws { // move all downloaded in prebuild phase // headers+binaries+swiftmodule(s) to the corresponding `targetName` directory let targetTempDir = targetTempDirsRoot.appendingPathComponent("\(targetName).build") let artifactOrganizer = artifactOrganizerFactory.build(targetTempDir: targetTempDir) let artifactLocation = artifactOrganizer.getActiveArtifactLocation() // Move cached binary artifacts to the product dir let binaryProducts = try artifactInspector.findBinaryProducts(fromArtifact: artifactLocation) guard !binaryProducts.isEmpty else { throw ThinningConsumerPostbuildPluginError.missingBinaryForArtifact(artifact: artifactLocation) } try binaryProducts.compactMap { $0 }.forEach { product in try diskCopier.copy(file: product, directory: builtProductsDir) } // Move Swift module definitions guard let moduleName = try artifactInspector.recognizeModuleName(fromArtifact: artifactLocation, arch: arch) else { /// Skip targets without swiftmodules (e.g. ObjC targets) return } // Swiftmodules in an artifact are cached from the "swiftc" step. Xcode along moving the swiftmodule files // to the builtProductsDir, duplicates the swiftmodule definition for extra archs // (e.g. "x86_64" -> ["x86_64, "x86_64-apple-ios-simulator"]) for arch in archs { let productsOrganizer = swiftProductOrganizerFactory.build( architecture: arch, targetName: targetName, moduleName: moduleName, artifactLocation: artifactLocation ) /// fileKey is equivalent of the fingerprint try productsOrganizer.syncProducts(fingerprint: fileKey) } } func run(meta: MainArtifactMeta) throws { onRun() // iterate all thinned targetName temp dirs and perform postbuild action let allCachedTargetFileKeys = ThinningPlugin.extractAllProductArtifacts(meta: meta) let thinnedTargetFileKeys = allCachedTargetFileKeys.filter { targetName, _ in thinnedTargets.contains(targetName) } // Ensure all thinned targets keys are available in a meta // (This is a second safety-net for. The same validation is done in the prebuild phase) let missedThinnedTargets = Set(thinnedTargets).subtracting(Set(thinnedTargetFileKeys.keys)) guard missedThinnedTargets.isEmpty else { let targetNames = Array(missedThinnedTargets) let rawError = ThinningConsumerPostbuildPluginError.missingArtifactKey(targetNames: targetNames) // Thin project requires all artifacts to be available locally - has to fail immediately throw PluginError.unrecoverableError(rawError) } let archs = try swiftProductsArchitecturesRecognizer.recognizeArchitectures( builtProductsDir: builtProductsDir, moduleName: productModuleName ) for (targetName, fileKey) in thinnedTargetFileKeys { worker.appendAction { try self.performPostbuildFor(targetName: targetName, productArchs: archs, fileKey: fileKey) } } if case .errors(let errors) = worker.waitForResult() { let rawError = ThinningConsumerPostbuildPluginError.failed(underlyingErrors: errors) // Thin project requires all artifacts to be available locally - has to fail immediately throw PluginError.unrecoverableError(rawError) } } }