@@ -20,42 +20,51 @@ public struct Packer: Sendable {
2020 // swift-tools-version: 6.0
2121 import PackageDescription
2222 let package = Package(
23- name: " \( plan. product) -Builder " ,
23+ name: " \( plan. app . product) -Builder " ,
2424 platforms: [
25- .iOS( " \( plan. deploymentTarget) " ),
25+ .iOS( " \( plan. app . deploymentTarget) " ),
2626 ],
2727 dependencies: [
2828 .package(name: " RootPackage " , path: " ../.. " ),
2929 ],
3030 targets: [
31- .executableTarget(
32- name: " \( plan. product) -App " ,
33- dependencies: [
34- .product(name: " \( plan. product) " , package: " RootPackage " ),
35- ]
36- ),
31+ \(
32+ plan. allProducts. map {
33+ """
34+ .executableTarget(
35+ name: " \( $0. targetName) " ,
36+ dependencies: [
37+ .product(name: " \( $0. product) " , package: " RootPackage " ),
38+ ],
39+ linkerSettings: \( $0. linkerSettings)
40+ )
41+ """
42+ }
43+ . joined ( separator: " , \n " )
44+ )
3745 ]
3846 ) \n
3947 """
4048 try Data ( contents. utf8) . write ( to: packageSwift)
41- let sources = packageDir. appendingPathComponent ( " Sources " )
42- try ? FileManager . default. createDirectory ( at: sources, withIntermediateDirectories: true )
43- try Data ( ) . write ( to: sources. appendingPathComponent ( " stub.c " ) )
49+
50+ for product in plan. allProducts {
51+ let sources : URL = packageDir. appendingPathComponent ( " Sources/ \( product. targetName) " , isDirectory: true )
52+ try FileManager . default. createDirectory ( at: sources, withIntermediateDirectories: true )
53+ try Data ( ) . write ( to: sources. appendingPathComponent ( " stub.c " , isDirectory: false ) )
54+ }
4455
4556 let builder = try await buildSettings. swiftPMInvocation (
4657 forTool: " build " ,
4758 arguments: [
4859 " --package-path " , packageDir. path,
4960 " --scratch-path " , " .build " ,
50- " --product " , " \( plan. product) -App " ,
5161 // resolving can cause SwiftPM to overwrite the root package deps
5262 // with just the deps needed for the builder package (which is to
5363 // say, any "dev dependencies" of the root package may be removed.)
5464 // fortunately we've already resolved the root package by this point
5565 // in order to dump the plan, so we can skip resolution here to skirt
5666 // the issue.
5767 " --disable-automatic-resolution " ,
58- " -Xlinker " , " -rpath " , " -Xlinker " , " @executable_path/Frameworks " ,
5968 ]
6069 )
6170 builder. standardOutput = FileHandle . standardError
@@ -65,15 +74,49 @@ public struct Packer: Sendable {
6574 public func pack( ) async throws -> URL {
6675 try await build ( )
6776
68- let output = try TemporaryDirectory ( name: " \( plan. product) .app " )
77+ let output = try TemporaryDirectory ( name: " \( plan. app. product) .app " )
78+
79+ let outputURL = output. url
6980
7081 let binDir = URL (
7182 fileURLWithPath: " .build/ \( buildSettings. triple) / \( buildSettings. configuration. rawValue) " ,
7283 isDirectory: true
7384 )
7485
75- let outputURL = output. url
86+ try await withThrowingTaskGroup ( of: Void . self) { group in
87+ for product in plan. allProducts {
88+ try pack (
89+ product: product,
90+ binDir: binDir,
91+ outputURL: product. directory ( inApp: outputURL) ,
92+ & group
93+ )
94+ }
7695
96+ while !group. isEmpty {
97+ do {
98+ try await group. next ( )
99+ } catch is CancellationError {
100+ // continue
101+ } catch {
102+ group. cancelAll ( )
103+ throw error
104+ }
105+ }
106+ }
107+
108+ let dest = URL ( fileURLWithPath: " xtool " ) . appendingPathComponent ( outputURL. lastPathComponent)
109+ try ? FileManager . default. removeItem ( at: dest)
110+ try output. persist ( at: dest)
111+ return dest
112+ }
113+
114+ @Sendable private func pack(
115+ product: Plan . Product ,
116+ binDir: URL ,
117+ outputURL: URL ,
118+ _ group: inout ThrowingTaskGroup < Void , Error >
119+ ) throws {
77120 @Sendable func packFileToRoot( srcName: String ) async throws {
78121 let srcURL = URL ( fileURLWithPath: srcName)
79122 let destURL = outputURL. appendingPathComponent ( srcURL. lastPathComponent)
@@ -91,70 +134,89 @@ public struct Packer: Sendable {
91134 try Task . checkCancellation ( )
92135 }
93136
94- try await withThrowingTaskGroup ( of: Void . self) { group in
95- for command in plan. resources {
96- group. addTask {
97- switch command {
98- case . bundle( let package , let target) :
99- try await packFile ( srcName: " \( package ) _ \( target) .bundle " )
100- case . binaryTarget( let name) :
101- let src = URL ( fileURLWithPath: " \( name) .framework/ \( name) " , relativeTo: binDir)
102- let magic = Data ( " !<arch> \n " . utf8)
103- let thinMagic = Data ( " !<thin> \n " . utf8)
104- let bytes = try FileHandle ( forReadingFrom: src) . read ( upToCount: magic. count)
105- // if the magic matches one of these it's a static archive; don't embed it.
106- // https://github.com/apple/llvm-project/blob/e716ff14c46490d2da6b240806c04e2beef01f40/llvm/include/llvm/Object/Archive.h#L33
107- // swiftlint:disable:previous line_length
108- if bytes != magic && bytes != thinMagic {
109- try await packFile ( srcName: " \( name) .framework " , dstName: " Frameworks/ \( name) .framework " , sign: true )
110- }
111- case . library( let name) :
112- try await packFile ( srcName: " lib \( name) .dylib " , dstName: " Frameworks/lib \( name) .dylib " , sign: true )
113- case . root( let source) :
114- try await packFileToRoot ( srcName: source)
137+ // Ensure output directory is available
138+ try ? FileManager . default. createDirectory ( at: outputURL, withIntermediateDirectories: true )
139+
140+ for command in product. resources {
141+ group. addTask {
142+ switch command {
143+ case . bundle( let package , let target) :
144+ try await packFile ( srcName: " \( package ) _ \( target) .bundle " )
145+ case . binaryTarget( let name) :
146+ let src = URL ( fileURLWithPath: " \( name) .framework/ \( name) " , relativeTo: binDir)
147+ let magic = Data ( " !<arch> \n " . utf8)
148+ let thinMagic = Data ( " !<thin> \n " . utf8)
149+ let bytes = try FileHandle ( forReadingFrom: src) . read ( upToCount: magic. count)
150+ // if the magic matches one of these it's a static archive; don't embed it.
151+ // https://github.com/apple/llvm-project/blob/e716ff14c46490d2da6b240806c04e2beef01f40/llvm/include/llvm/Object/Archive.h#L33
152+ // swiftlint:disable:previous line_length
153+ if bytes != magic && bytes != thinMagic {
154+ try await packFile ( srcName: " \( name) .framework " , dstName: " Frameworks/ \( name) .framework " , sign: true )
115155 }
156+ case . library( let name) :
157+ try await packFile ( srcName: " lib \( name) .dylib " , dstName: " Frameworks/lib \( name) .dylib " , sign: true )
158+ case . root( let source) :
159+ try await packFileToRoot ( srcName: source)
116160 }
117161 }
118- if let iconPath = plan. iconPath {
119- group. addTask {
120- try await packFileToRoot ( srcName: iconPath)
121- }
122- }
162+ }
163+ if let iconPath = product. iconPath {
123164 group. addTask {
124- try await packFile ( srcName: " \( plan . product ) -App " , dstName : plan . product )
165+ try await packFileToRoot ( srcName: iconPath )
125166 }
126- group. addTask {
127- var info = plan. infoPlist
128-
129- if let iconPath = plan. iconPath {
130- let iconName = URL ( fileURLWithPath: iconPath) . deletingPathExtension ( ) . lastPathComponent
131- info [ " CFBundleIconFile " ] = iconName
132- }
167+ }
168+ group. addTask {
169+ try await packFile ( srcName: product. targetName, dstName: product. product)
170+ }
171+ group. addTask {
172+ var info = product. infoPlist
133173
134- let infoPath = outputURL. appendingPathComponent ( " Info.plist " )
135- let encodedPlist = try PropertyListSerialization . data (
136- fromPropertyList: info,
137- format: . xml,
138- options: 0
139- )
140- try encodedPlist. write ( to: infoPath)
174+ if product. type == . application {
175+ info [ " UIRequiredDeviceCapabilities " ] = [ " arm64 " ]
176+ info [ " LSRequiresIPhoneOS " ] = true
177+ info [ " CFBundleSupportedPlatforms " ] = [ " iPhoneOS " ]
141178 }
142- while !group. isEmpty {
143- do {
144- try await group. next ( )
145- } catch is CancellationError {
146- // continue
147- } catch {
148- group. cancelAll ( )
149- throw error
150- }
179+
180+ if let iconPath = product. iconPath {
181+ let iconName = URL ( fileURLWithPath: iconPath) . deletingPathExtension ( ) . lastPathComponent
182+ info [ " CFBundleIconFile " ] = iconName
151183 }
184+
185+ let infoPath = outputURL. appendingPathComponent ( " Info.plist " )
186+ let encodedPlist = try PropertyListSerialization . data (
187+ fromPropertyList: info,
188+ format: . xml,
189+ options: 0
190+ )
191+ try encodedPlist. write ( to: infoPath)
152192 }
193+ }
194+ }
153195
154- let dest = URL ( fileURLWithPath: " xtool " )
155- . appendingPathComponent ( output. url. lastPathComponent)
156- try ? FileManager . default. removeItem ( at: dest)
157- try output. persist ( at: dest)
158- return dest
196+ extension Plan . Product {
197+ fileprivate var linkerSettings : String {
198+ switch self . type {
199+ case . application: """
200+ [
201+ .unsafeFlags([
202+ " -Xlinker " , " -rpath " , " -Xlinker " , " @executable_path/Frameworks " ,
203+ ]),
204+ ]
205+ """
206+ case . appExtension: """
207+ [
208+ // Link to Foundation framework which implements the _NSExtensionMain entrypoint
209+ .linkedFramework( " Foundation " ),
210+ .unsafeFlags([
211+ // Set the entry point to Foundation`_NSExtensionMain
212+ " -Xlinker " , " -e " , " -Xlinker " , " _NSExtensionMain " ,
213+ // Include frameworks that the host app may use
214+ " -Xlinker " , " -rpath " , " -Xlinker " , " @executable_path/../../Frameworks " ,
215+ // ...as well as our own
216+ " -Xlinker " , " -rpath " , " -Xlinker " , " @executable_path/Frameworks " ,
217+ ]),
218+ ]
219+ """
220+ }
159221 }
160222}
0 commit comments