-
-
Notifications
You must be signed in to change notification settings - Fork 508
/
XCFrameworkBuilder.swift
174 lines (150 loc) · 7.97 KB
/
XCFrameworkBuilder.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import Basic
import Foundation
import TuistCore
import TuistSupport
enum XCFrameworkBuilderError: FatalError {
case nonFrameworkTarget(String)
/// Error type.
var type: ErrorType {
switch self {
case .nonFrameworkTarget: return .abort
}
}
/// Error description.
var description: String {
switch self {
case let .nonFrameworkTarget(name):
return "Can't generate an .xcframework from the target '\(name)' because it's not a framework target"
}
}
}
protocol XCFrameworkBuilding {
/// It builds an xcframework for the given target.
/// The target must have framework as product.
///
/// - Parameters:
/// - workspacePath: Path to the generated .xcworkspace that contains the given target.
/// - target: Target whose .xcframework will be generated.
/// - Returns: Path to the compiled .xcframework.
func build(workspacePath: AbsolutePath, target: Target) throws -> AbsolutePath
/// It builds an xcframework for the given target.
/// The target must have framework as product.
///
/// - Parameters:
/// - projectPath: Path to the generated .xcodeproj that contains the given target.
/// - target: Target whose .xcframework will be generated.
/// - Returns: Path to the compiled .xcframework.
func build(projectPath: AbsolutePath, target: Target) throws -> AbsolutePath
}
final class XCFrameworkBuilder: XCFrameworkBuilding {
// MARK: - Attributes
/// When true the builder outputs the output from xcodebuild.
private let printOutput: Bool
// MARK: - Init
/// Initializes the builder.
/// - Parameter printOutput: When true the builder outputs the output from xcodebuild.
init(printOutput: Bool = true) {
self.printOutput = printOutput
}
// MARK: - XCFrameworkBuilding
func build(workspacePath: AbsolutePath, target: Target) throws -> AbsolutePath {
try build(arguments: ["-workspace", workspacePath.pathString], target: target)
}
func build(projectPath: AbsolutePath, target: Target) throws -> AbsolutePath {
try build(arguments: ["-project", projectPath.pathString], target: target)
}
// MARK: - Fileprivate
fileprivate func build(arguments: [String], target: Target) throws -> AbsolutePath {
if target.product != .framework {
throw XCFrameworkBuilderError.nonFrameworkTarget(target.name)
}
// Create temporary directories
let outputDirectory = try TemporaryDirectory(removeTreeOnDeinit: false)
let derivedDataPath = try TemporaryDirectory(removeTreeOnDeinit: true)
Printer.shared.print(section: "Building .xcframework for \(target.productName)")
// Build for the device
let deviceArchivePath = derivedDataPath.path.appending(component: "device.xcarchive")
var deviceArguments = xcodebuildCommand(scheme: target.name,
destination: deviceDestination(platform: target.platform),
sdk: target.platform.xcodeDeviceSDK,
derivedDataPath: derivedDataPath.path)
deviceArguments.append(contentsOf: ["-archivePath", deviceArchivePath.pathString])
deviceArguments.append(contentsOf: arguments)
Printer.shared.print(subsection: "Building \(target.productName) for device")
try runCommand(deviceArguments)
// Build for the simulator
var simulatorArchivePath: AbsolutePath?
if target.platform.hasSimulators {
simulatorArchivePath = derivedDataPath.path.appending(component: "simulator.xcarchive")
var simulatorArguments = xcodebuildCommand(scheme: target.name,
destination: target.platform.xcodeSimulatorDestination!,
sdk: target.platform.xcodeSimulatorSDK!,
derivedDataPath: derivedDataPath.path)
simulatorArguments.append(contentsOf: ["-archivePath", simulatorArchivePath!.pathString])
simulatorArguments.append(contentsOf: arguments)
Printer.shared.print(subsection: "Building \(target.productName) for simulator")
try runCommand(simulatorArguments)
}
// Build the xcframework
Printer.shared.print(subsection: "Exporting xcframework for \(target.productName)")
let xcframeworkPath = outputDirectory.path.appending(component: "\(target.productName).xcframework")
let xcframeworkArguments = xcodebuildXcframeworkCommand(deviceArchivePath: deviceArchivePath,
simulatorArchivePath: simulatorArchivePath,
productName: target.productName,
xcframeworkPath: xcframeworkPath)
try runCommand(xcframeworkArguments)
return xcframeworkPath
}
/// Runs the given command.
/// - Parameter arguments: Command arguments.
fileprivate func runCommand(_ arguments: [String]) throws {
if printOutput {
try System.shared.runAndPrint(arguments)
} else {
try System.shared.run(arguments)
}
}
/// Returns the arguments that should be passed to xcodebuild to compile for a device on the given platform.
/// - Parameter platform: Platform we are compiling for.
fileprivate func deviceDestination(platform: Platform) -> String {
switch platform {
case .macOS: return "osx"
default: return "generic/platform=\(platform.caseValue)"
}
}
/// Returns the xcodebuild command to generate the .xcframework from the device
/// and the simulator frameworks.
///
/// - Parameters:
/// - deviceArchivePath: Path to the archive that contains the framework for the device.
/// - simulatorArchivePath: Path to the archive that contains the framework for the simulator.
/// - productName: Name of the product.
/// - xcframeworkPath: Path where the .xcframework should be exported to (e.g. /path/to/MyFeature.xcframework).
fileprivate func xcodebuildXcframeworkCommand(deviceArchivePath: AbsolutePath,
simulatorArchivePath: AbsolutePath?,
productName: String,
xcframeworkPath: AbsolutePath) -> [String] {
var command = ["xcrun", "xcodebuild", "-create-xcframework"]
command.append(contentsOf: ["-framework", deviceArchivePath.appending(RelativePath("Products/Library/Frameworks/\(productName).framework")).pathString])
if let simulatorArchivePath = simulatorArchivePath {
command.append(contentsOf: ["-framework", simulatorArchivePath.appending(RelativePath("Products/Library/Frameworks/\(productName).framework")).pathString])
}
command.append(contentsOf: ["-output", xcframeworkPath.pathString])
return command
}
/// It returns the xcodebuild command to archive the .framework.
/// - Parameters:
/// - scheme: Name of the scheme that archives the framework.
/// - destination: Compilation destination.
/// - sdk: Compilation SDK.
/// - derivedDataPath: Derived data directory.
fileprivate func xcodebuildCommand(scheme: String, destination: String, sdk: String, derivedDataPath: AbsolutePath) -> [String] {
var command = ["xcrun", "xcodebuild", "clean", "archive"]
command.append(contentsOf: ["-scheme", scheme.spm_shellEscaped()])
command.append(contentsOf: ["-sdk", sdk])
command.append(contentsOf: ["-destination='\(destination)'"])
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
command.append(contentsOf: ["SKIP_INSTALL=NO", "BUILD_LIBRARY_FOR_DISTRIBUTION=YES"])
return command
}
}