Fix issue with MacCatalyst archiving not being included in build cache when caching `XCFramework` (#5108)

* Wip

* Applied code review feedback

* Linted the code

* Refactor using Graph instances in CacheController

* Refactor MacCatalyst branch through bundleArtifactBuilder's build function

* Fixed deploymentTarget branch

---------

Co-authored-by: Daniele Formichelli <df@bendingspoons.com>
This commit is contained in:
YoHan Cho 2023-03-28 05:55:13 +09:00 committed by GitHub
parent 1be1aba5d3
commit 4fdcf9d2b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 147 additions and 1 deletions

View File

@ -16,6 +16,7 @@ public protocol CacheArtifactBuilding {
/// - deviceName: The specific device that will be used when compiling the given target.
/// - into: The directory into which the output artifacts will be copied.
func build(
graph: Graph,
scheme: Scheme,
projectTarget: XcodeBuildTarget,
configuration: String,

View File

@ -26,6 +26,7 @@ public final class CacheBundleBuilder: CacheArtifactBuilding {
}
public func build(
graph _: Graph,
scheme: Scheme,
projectTarget: XcodeBuildTarget,
configuration: String,

View File

@ -46,6 +46,7 @@ public final class CacheFrameworkBuilder: CacheArtifactBuilding {
public var cacheOutputType: CacheOutputType = .framework
public func build(
graph _: Graph,
scheme: Scheme,
projectTarget: XcodeBuildTarget,
configuration: String,

View File

@ -38,6 +38,7 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
public var cacheOutputType: CacheOutputType
public func build(
graph: Graph,
scheme: Scheme,
projectTarget: XcodeBuildTarget,
configuration: String,
@ -45,7 +46,9 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
deviceName _: String?,
into outputDirectory: AbsolutePath
) async throws {
guard let buildTargets = scheme.buildAction?.targets else { return }
let platform = self.platform(scheme: scheme)
let macCatalystSupportedTargets = getMacCatalystTargets(graph: graph, scheme: scheme)
// Create temporary directories
return try await FileHandler.shared.inTemporaryDirectory { temporaryDirectory in
@ -63,6 +66,18 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
)
}
// Build for the macCatalyst
var macCatalystArchivePath: AbsolutePath?
if platform == .iOS, !macCatalystSupportedTargets.isEmpty {
macCatalystArchivePath = temporaryDirectory.appending(component: "macCatalyst.xcarchive")
try await self.macCatalystBuild(
projectTarget: projectTarget,
scheme: scheme.name,
configuration: configuration,
archivePath: macCatalystArchivePath!
)
}
// Build for the device - if required
var deviceArchivePath: AbsolutePath?
if self.cacheOutputType.shouldBuildForDevice {
@ -77,7 +92,10 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
}
try await self.createXCFramework(
buildTargets: buildTargets,
macCatalystSupportedTargets: macCatalystSupportedTargets,
simulatorArchivePath: simulatorArchivePath,
macCatalystArchivePath: macCatalystArchivePath,
deviceArchivePath: deviceArchivePath,
outputDirectory: outputDirectory
)
@ -87,7 +105,10 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
// MARK: - Fileprivate
fileprivate func createXCFramework(
buildTargets: [TargetReference],
macCatalystSupportedTargets: [Target],
simulatorArchivePath: AbsolutePath?,
macCatalystArchivePath: AbsolutePath?,
deviceArchivePath: AbsolutePath?,
outputDirectory: AbsolutePath
) async throws {
@ -106,7 +127,7 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
.glob("*.framework")
.map(\.basenameWithoutExt)
// Build the xcframework
logger.notice("Caching to all cacheable targets as xcframeworks", metadata: .section)
for productName in productNames {
var frameworkpaths = [AbsolutePath]()
if let simulatorArchivePath = simulatorArchivePath {
@ -115,12 +136,29 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
productName: productName
))
}
if !macCatalystSupportedTargets.isEmpty {
zip(buildTargets, macCatalystSupportedTargets)
.filter { $0.0.name == $0.1.name }
.filter { $0.0.name == productName }
.forEach { _ in
if let macCatalystArchivePath = macCatalystArchivePath {
frameworkpaths.append(self.frameworkPath(
fromArchivePath: macCatalystArchivePath,
productName: productName
))
}
}
}
if let deviceArchivePath = deviceArchivePath {
frameworkpaths.append(self.frameworkPath(
fromArchivePath: deviceArchivePath,
productName: productName
))
}
logger.notice("Caching \(productName).xcframework")
let xcframeworkPath = outputDirectory.appending(component: "\(productName).xcframework")
try await self.xcodeBuildController.createXCFramework(
frameworks: frameworkpaths,
@ -135,6 +173,16 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
}
}
fileprivate func getMacCatalystTargets(graph: Graph, scheme: Scheme) -> [Target] {
guard let buildTargets = scheme.buildAction?.targets else { return [] }
let buildTargetsSet = Set(buildTargets.map(\.name))
return graph.targets
.flatMap(\.value)
.filter { buildTargetsSet.contains($0.key) }
.map(\.value)
.filter(\.supportsCatalyst)
}
fileprivate func deviceBuild(
projectTarget: XcodeBuildTarget,
scheme: String,
@ -155,6 +203,25 @@ public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
).printFormattedOutput()
}
fileprivate func macCatalystBuild(
projectTarget: XcodeBuildTarget,
scheme: String,
configuration: String,
archivePath: AbsolutePath
) async throws {
try await xcodeBuildController.archive(
projectTarget,
scheme: scheme,
clean: false,
archivePath: archivePath,
arguments: [
.xcarg("SKIP_INSTALL", "NO"),
.destination("platform=macOS,variant=Mac Catalyst"),
.configuration(configuration),
]
).printFormattedOutput()
}
fileprivate func simulatorBuild(
projectTarget: XcodeBuildTarget,
scheme: String,
@ -205,3 +272,14 @@ extension CacheOutputType {
}
}
}
extension Target {
fileprivate var supportsCatalyst: Bool {
switch self.deploymentTarget {
case let .iOS(_, devices, _) where devices.contains(.mac):
return true
default:
return false
}
}
}

View File

@ -31,6 +31,7 @@ public final class MockCacheArtifactBuilder: CacheArtifactBuilding {
[(scheme: Scheme, projectTarget: XcodeBuildTarget, outputDirectory: AbsolutePath)]()
public var stubbedBuildSchemeProjectError: Error?
public func build(
graph _: Graph,
scheme: Scheme,
projectTarget: XcodeBuildTarget,
configuration _: String,

View File

@ -148,10 +148,12 @@ final class CacheController: CacheControlling {
.filter { !($0.buildAction?.targets ?? []).isEmpty }
try await FileHandler.shared.inTemporaryDirectory { outputDirectory in
for scheme in binariesSchemes {
let outputDirectory = outputDirectory.appending(component: scheme.name)
try FileHandler.shared.createFolder(outputDirectory)
try await self.artifactBuilder.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: cacheProfile.configuration,
@ -165,6 +167,7 @@ final class CacheController: CacheControlling {
let outputDirectory = outputDirectory.appending(component: scheme.name)
try FileHandler.shared.createFolder(outputDirectory)
try await self.bundleArtifactBuilder.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: cacheProfile.configuration,

View File

@ -32,9 +32,11 @@ final class CacheFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "iOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -58,9 +60,11 @@ final class CacheFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "macOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",

View File

@ -35,9 +35,11 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "iOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -62,11 +64,13 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "iOS")
let graph = Graph.test()
subject.cacheOutputType = .xcframework(.device)
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -92,11 +96,13 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "iOS")
let graph = Graph.test()
subject.cacheOutputType = .xcframework(.simulator)
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -115,15 +121,60 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
try FileHandler.shared.delete(xcframeworkPath)
}
func test_build_when_macCatalyst_framework() async throws {
// Given
let temporaryPath = try temporaryPath()
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(
name: "iOS",
buildAction: .test(targets: [TargetReference(projectPath: "/Project", name: "iOS")])
)
let graph = Graph.test(
targets: [
try AbsolutePath(validating: "/test"): [
"iOS": Target.test(
name: "iOS",
deploymentTarget: .iOS("14.0", [.iphone, .ipad, .mac], supportsMacDesignedForIOS: true)
),
],
]
)
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
osVersion: nil,
deviceName: nil,
into: temporaryPath
)
// Then
XCTAssertEqual(FileHandler.shared.glob(temporaryPath, glob: "*.xcframework").count, 1)
let xcframeworkPath = try XCTUnwrap(FileHandler.shared.glob(temporaryPath, glob: "*.xcframework").first)
let infoPlist = try infoPlist(xcframeworkPath: xcframeworkPath)
XCTAssertTrue(infoPlist.availableLibraries.contains(where: { $0.identifier == "ios-arm64_x86_64-maccatalyst" }))
XCTAssertNotNil(infoPlist.availableLibraries.first(where: { $0.supportedArchitectures.contains("arm64") }))
XCTAssertNotNil(infoPlist.availableLibraries.first(where: { $0.supportedArchitectures.contains("x86_64") }))
XCTAssertTrue(infoPlist.availableLibraries.allSatisfy { $0.supportedPlatform == "ios" })
XCTAssertTrue(infoPlist.availableLibraries.contains(where: { $0.supportedPlatformVariant == "maccatalyst" }))
try FileHandler.shared.delete(xcframeworkPath)
}
func test_build_when_macOS_framework() async throws {
// Given
let temporaryPath = try temporaryPath()
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "macOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -149,9 +200,11 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "tvOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -176,9 +229,11 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "watchOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",
@ -204,9 +259,11 @@ final class CacheXCFrameworkBuilderIntegrationTests: TuistTestCase {
let frameworksPath = try temporaryFixture("Frameworks")
let projectPath = frameworksPath.appending(component: "Frameworks.xcodeproj")
let scheme = Scheme.test(name: "Documentation-iOS")
let graph = Graph.test()
// When
try await subject.build(
graph: graph,
scheme: scheme,
projectTarget: XcodeBuildTarget(with: projectPath),
configuration: "Debug",