Skip to content

Commit 99a6e97

Browse files
dschaefer2Doug Schaefer
and
Doug Schaefer
authored
Add support for a prebuilt swift-syntax library for macros (#8142)
Following the pattern used for the early Traits work, added a workspace prebuilts manager to co-ordinate detecting when macros are using swift-syntax, downloading the zip file for the resolved swift-syntax version, and extracting it into the scratch directory. We add the necessary information about that to the workspace state. We then morph the Modules that use it to add build settings to find the necessary modules and libraries. --------- Co-authored-by: Doug Schaefer <[email protected]>
1 parent b28cdfb commit 99a6e97

25 files changed

+2418
-26
lines changed

Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,15 @@ let package = Package(
655655
name: "swift-package-registry",
656656
dependencies: ["Commands", "PackageRegistryCommand"]
657657
),
658+
.executableTarget(
659+
/** Utility to produce the artifacts for prebuilts */
660+
name: "swift-build-prebuilts",
661+
dependencies: [
662+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
663+
"Basics",
664+
"Workspace",
665+
]
666+
),
658667

659668
// MARK: Support for Swift macros, should eventually move to a plugin-based solution
660669

Sources/Basics/Archiver/ZipArchiver.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
import Dispatch
1414
import struct TSCBasic.FileSystemError
1515

16+
#if os(Windows)
17+
import WinSDK
18+
#endif
19+
1620
/// An `Archiver` that handles ZIP archives using the command-line `zip` and `unzip` tools.
1721
public struct ZipArchiver: Archiver, Cancellable {
1822
public var supportedExtensions: Set<String> { ["zip"] }
@@ -23,6 +27,11 @@ public struct ZipArchiver: Archiver, Cancellable {
2327
/// Helper for cancelling in-flight requests
2428
private let cancellator: Cancellator
2529

30+
/// Absolute path to the Windows tar in the system folder
31+
#if os(Windows)
32+
private let windowsTar: String
33+
#endif
34+
2635
/// Creates a `ZipArchiver`.
2736
///
2837
/// - Parameters:
@@ -31,6 +40,19 @@ public struct ZipArchiver: Archiver, Cancellable {
3140
public init(fileSystem: FileSystem, cancellator: Cancellator? = .none) {
3241
self.fileSystem = fileSystem
3342
self.cancellator = cancellator ?? Cancellator(observabilityScope: .none)
43+
44+
#if os(Windows)
45+
var tarPath: PWSTR?
46+
defer { CoTaskMemFree(tarPath) }
47+
let hr = withUnsafePointer(to: FOLDERID_System) { id in
48+
SHGetKnownFolderPath(id, DWORD(KF_FLAG_DEFAULT.rawValue), nil, &tarPath)
49+
}
50+
if hr == S_OK, let tarPath {
51+
windowsTar = String(decodingCString: tarPath, as: UTF16.self) + "\\tar.exe"
52+
} else {
53+
windowsTar = "tar.exe"
54+
}
55+
#endif
3456
}
3557

3658
public func extract(
@@ -48,7 +70,9 @@ public struct ZipArchiver: Archiver, Cancellable {
4870
}
4971

5072
#if os(Windows)
51-
let process = AsyncProcess(arguments: ["tar.exe", "xf", archivePath.pathString, "-C", destinationPath.pathString])
73+
// FileManager lost the ability to detect tar.exe as executable.
74+
// It's part of system32 anyway so use the absolute path.
75+
let process = AsyncProcess(arguments: [windowsTar, "xf", archivePath.pathString, "-C", destinationPath.pathString])
5276
#else
5377
let process = AsyncProcess(arguments: ["unzip", archivePath.pathString, "-d", destinationPath.pathString])
5478
#endif
@@ -82,7 +106,7 @@ public struct ZipArchiver: Archiver, Cancellable {
82106
#if os(Windows)
83107
let process = AsyncProcess(
84108
// FIXME: are these the right arguments?
85-
arguments: ["tar.exe", "-a", "-c", "-f", destinationPath.pathString, directory.basename],
109+
arguments: [windowsTar, "-a", "-c", "-f", destinationPath.pathString, directory.basename],
86110
workingDirectory: directory.parentDirectory
87111
)
88112
#else
@@ -121,7 +145,7 @@ public struct ZipArchiver: Archiver, Cancellable {
121145
}
122146

123147
#if os(Windows)
124-
let process = AsyncProcess(arguments: ["tar.exe", "tf", path.pathString])
148+
let process = AsyncProcess(arguments: [windowsTar, "tf", path.pathString])
125149
#else
126150
let process = AsyncProcess(arguments: ["unzip", "-t", path.pathString])
127151
#endif

Sources/Basics/Collections/IdentifiableSet.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,9 @@ extension IdentifiableSet: Hashable {
122122
}
123123
}
124124
}
125+
126+
extension IdentifiableSet: ExpressibleByArrayLiteral {
127+
public init(arrayLiteral elements: Element...) {
128+
self.init(elements)
129+
}
130+
}

Sources/Commands/CommandWorkspaceDelegate.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,42 @@ final class CommandWorkspaceDelegate: WorkspaceDelegate {
182182
self.progressHandler(step, total, "Downloading \(artifacts)")
183183
}
184184

185+
/// The workspace has started downloading a binary artifact.
186+
func willDownloadPrebuilt(from url: String, fromCache: Bool) {
187+
if fromCache {
188+
self.outputHandler("Fetching package prebuilt \(url) from cache", false)
189+
} else {
190+
self.outputHandler("Downloading package prebuilt \(url)", false)
191+
}
192+
}
193+
194+
/// The workspace has finished downloading a binary artifact.
195+
func didDownloadPrebuilt(
196+
from url: String,
197+
result: Result<(path: AbsolutePath, fromCache: Bool), Error>,
198+
duration: DispatchTimeInterval
199+
) {
200+
guard case .success(let fetchDetails) = result, !self.observabilityScope.errorsReported else {
201+
return
202+
}
203+
204+
if fetchDetails.fromCache {
205+
self.outputHandler("Fetched \(url) from cache (\(duration.descriptionInSeconds))", false)
206+
} else {
207+
self.outputHandler("Downloaded \(url) (\(duration.descriptionInSeconds))", false)
208+
}
209+
}
210+
211+
/// The workspace is downloading a binary artifact.
212+
func downloadingPrebuilt(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) {
213+
214+
}
215+
216+
/// The workspace finished downloading all binary artifacts.
217+
func didDownloadAllPrebuilts() {
218+
219+
}
220+
185221
// registry signature handlers
186222

187223
func onUnsignedRegistryPackage(registryURL: URL, package: PackageModel.PackageIdentity, version: TSCUtility.Version, completion: (Bool) -> Void) {

Sources/CoreCommands/Options.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ public struct CachingOptions: ParsableArguments {
186186
self.init(rawValue: argument)
187187
}
188188
}
189+
190+
/// Whether to use macro prebuilts or not
191+
@Flag(name: .customLong("experimental-prebuilts"),
192+
inversion: .prefixedEnableDisable,
193+
help: "Whether to use prebuilt swift-syntax libraries for macros")
194+
public var usePrebuilts: Bool = false
189195
}
190196

191197
public struct LoggingOptions: ParsableArguments {

Sources/CoreCommands/SwiftCommandState.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public typealias WorkspaceDelegateProvider = (
8080
_ progressHandler: @escaping (Int64, Int64, String?) -> Void,
8181
_ inputHandler: @escaping (String, (String?) -> Void) -> Void
8282
) -> WorkspaceDelegate
83+
8384
public typealias WorkspaceLoaderProvider = (_ fileSystem: FileSystem, _ observabilityScope: ObservabilityScope)
8485
-> WorkspaceLoader
8586

@@ -470,7 +471,8 @@ public final class SwiftCommandState {
470471
// TODO: should supportsAvailability be a flag as well?
471472
.init(url: $0, supportsAvailability: true)
472473
},
473-
manifestImportRestrictions: .none
474+
manifestImportRestrictions: .none,
475+
usePrebuilts: options.caching.usePrebuilts
474476
),
475477
cancellator: self.cancellator,
476478
initializationWarningHandler: { self.observabilityScope.emit(warning: $0) },

Sources/PackageGraph/ModulesGraph+Loading.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extension ModulesGraph {
2929
requiredDependencies: [PackageReference] = [],
3030
unsafeAllowedPackages: Set<PackageReference> = [],
3131
binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]],
32+
prebuilts: [PackageIdentity: [String: PrebuiltLibrary]],
3233
shouldCreateMultipleTestProducts: Bool = false,
3334
createREPLProduct: Bool = false,
3435
customPlatformsRegistry: PlatformRegistry? = .none,
@@ -47,6 +48,7 @@ extension ModulesGraph {
4748
requiredDependencies: requiredDependencies,
4849
unsafeAllowedPackages: unsafeAllowedPackages,
4950
binaryArtifacts: binaryArtifacts,
51+
prebuilts: prebuilts,
5052
shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts,
5153
createREPLProduct: createREPLProduct,
5254
traitConfiguration: nil,
@@ -69,6 +71,7 @@ extension ModulesGraph {
6971
requiredDependencies: [PackageReference] = [],
7072
unsafeAllowedPackages: Set<PackageReference> = [],
7173
binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]],
74+
prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], // Product name to library mapping
7275
shouldCreateMultipleTestProducts: Bool = false,
7376
createREPLProduct: Bool = false,
7477
traitConfiguration: TraitConfiguration? = nil,
@@ -212,7 +215,8 @@ extension ModulesGraph {
212215
productFilter: node.productFilter,
213216
path: packagePath,
214217
additionalFileRules: additionalFileRules,
215-
binaryArtifacts: binaryArtifacts[node.identity] ?? [:],
218+
binaryArtifacts: binaryArtifacts[node.identity] ?? [:],
219+
prebuilts: prebuilts,
216220
shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts,
217221
testEntryPointPath: testEntryPointPath,
218222
createREPLProduct: manifest.packageKind.isRoot ? createREPLProduct : false,
@@ -246,6 +250,7 @@ extension ModulesGraph {
246250
manifestToPackage: manifestToPackage,
247251
rootManifests: root.manifests,
248252
unsafeAllowedPackages: unsafeAllowedPackages,
253+
prebuilts: prebuilts,
249254
platformRegistry: customPlatformsRegistry ?? .default,
250255
platformVersionProvider: platformVersionProvider,
251256
fileSystem: fileSystem,
@@ -377,6 +382,7 @@ private func createResolvedPackages(
377382
// FIXME: This shouldn't be needed once <rdar://problem/33693433> is fixed.
378383
rootManifests: [PackageIdentity: Manifest],
379384
unsafeAllowedPackages: Set<PackageReference>,
385+
prebuilts: [PackageIdentity: [String: PrebuiltLibrary]],
380386
platformRegistry: PlatformRegistry,
381387
platformVersionProvider: PlatformVersionProvider,
382388
fileSystem: FileSystem,
@@ -665,6 +671,11 @@ private func createResolvedPackages(
665671
}
666672
}
667673

674+
if let package = productRef.package, prebuilts[.plain(package)]?[productRef.name] != nil {
675+
// using a prebuilt instead.
676+
continue
677+
}
678+
668679
// Find the product in this package's dependency products.
669680
// Look it up by ID if module aliasing is used, otherwise by name.
670681
let product = lookupByProductIDs ? productDependencyMap[productRef.identity] : productDependencyMap[productRef.name]

Sources/PackageGraph/ModulesGraph.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ public func loadModulesGraph(
419419
fileSystem: FileSystem,
420420
manifests: [Manifest],
421421
binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]] = [:],
422+
prebuilts: [PackageIdentity: [String: PrebuiltLibrary]] = [:],
422423
explicitProduct: String? = .none,
423424
shouldCreateMultipleTestProducts: Bool = false,
424425
createREPLProduct: Bool = false,
@@ -451,6 +452,7 @@ public func loadModulesGraph(
451452
.swiftpmFileTypes,
452453
externalManifests: externalManifests,
453454
binaryArtifacts: binaryArtifacts,
455+
prebuilts: prebuilts,
454456
shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts,
455457
createREPLProduct: createREPLProduct,
456458
customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets,

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,32 @@ public struct BinaryArtifact {
268268
}
269269
}
270270

271+
/// A structure representing a prebuilt library to be used instead of a source dependency
272+
public struct PrebuiltLibrary {
273+
/// The package reference.
274+
public let packageRef: PackageReference
275+
276+
/// The name of the binary target the artifact corresponds to.
277+
public let libraryName: String
278+
279+
/// The path to the extracted prebuilt artifacts
280+
public let path: AbsolutePath
281+
282+
/// The products in the library
283+
public let products: [String]
284+
285+
/// The C modules that need their includes directory added to the include path
286+
public let cModules: [String]
287+
288+
public init(packageRef: PackageReference, libraryName: String, path: AbsolutePath, products: [String], cModules: [String]) {
289+
self.packageRef = packageRef
290+
self.libraryName = libraryName
291+
self.path = path
292+
self.products = products
293+
self.cModules = cModules
294+
}
295+
}
296+
271297
/// Helper for constructing a package following the convention system.
272298
///
273299
/// The 'builder' here refers to the builder pattern and not any build system
@@ -295,6 +321,9 @@ public final class PackageBuilder {
295321
/// Information concerning the different downloaded or local (archived) binary target artifacts.
296322
private let binaryArtifacts: [String: BinaryArtifact]
297323

324+
/// Prebuilts that may referenced from this package's targets
325+
private let prebuilts: [PackageIdentity: [Product.ID: PrebuiltLibrary]]
326+
298327
/// Create multiple test products.
299328
///
300329
/// If set to true, one test product will be created for each test target.
@@ -351,6 +380,7 @@ public final class PackageBuilder {
351380
path: AbsolutePath,
352381
additionalFileRules: [FileRuleDescription],
353382
binaryArtifacts: [String: BinaryArtifact],
383+
prebuilts: [PackageIdentity: [String: PrebuiltLibrary]],
354384
shouldCreateMultipleTestProducts: Bool = false,
355385
testEntryPointPath: AbsolutePath? = nil,
356386
warnAboutImplicitExecutableTargets: Bool = true,
@@ -365,6 +395,7 @@ public final class PackageBuilder {
365395
self.packagePath = path
366396
self.additionalFileRules = additionalFileRules
367397
self.binaryArtifacts = binaryArtifacts
398+
self.prebuilts = prebuilts
368399
self.shouldCreateMultipleTestProducts = shouldCreateMultipleTestProducts
369400
self.testEntryPointPath = testEntryPointPath
370401
self.createREPLProduct = createREPLProduct
@@ -1211,6 +1242,33 @@ public final class PackageBuilder {
12111242
table.add(assignment, for: .SWIFT_ACTIVE_COMPILATION_CONDITIONS)
12121243
}
12131244

1245+
// Add in flags for prebuilts
1246+
let prebuiltLibraries: [String: PrebuiltLibrary] = target.dependencies.reduce(into: .init()) {
1247+
guard case let .product(name: name, package: package, moduleAliases: _, condition: _) = $1,
1248+
let package = package,
1249+
let prebuilt = prebuilts[.plain(package)]?[name]
1250+
else {
1251+
return
1252+
}
1253+
1254+
$0[prebuilt.libraryName] = prebuilt
1255+
}
1256+
1257+
for prebuilt in prebuiltLibraries.values {
1258+
let lib = prebuilt.path.appending(components: ["lib", "lib\(prebuilt.libraryName).a"]).pathString
1259+
var ldFlagsAssignment = BuildSettings.Assignment()
1260+
ldFlagsAssignment.values = [lib]
1261+
table.add(ldFlagsAssignment, for: .OTHER_LDFLAGS)
1262+
1263+
var includeDirs: [AbsolutePath] = [prebuilt.path.appending(component: "Modules")]
1264+
for cModule in prebuilt.cModules {
1265+
includeDirs.append(prebuilt.path.appending(components: "include", cModule))
1266+
}
1267+
var includeAssignment = BuildSettings.Assignment()
1268+
includeAssignment.values = includeDirs.map({ "-I\($0.pathString)" })
1269+
table.add(includeAssignment, for: .OTHER_SWIFT_FLAGS)
1270+
}
1271+
12141272
return table
12151273
}
12161274

Sources/PackageModel/Product.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212

1313
import Basics
1414

15-
public class Product {
15+
public class Product: Identifiable {
1616
/// The name of the product.
1717
public let name: String
1818

19+
public var id: String { name }
20+
1921
/// Fully qualified name for this product: package ID + name of this product
2022
public let identity: String
2123

Sources/Workspace/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_library(Workspace
1313
LoadableResult.swift
1414
ManagedArtifact.swift
1515
ManagedDependency.swift
16+
ManagedPrebuilt.swift
1617
PackageContainer/FileSystemPackageContainer.swift
1718
PackageContainer/RegistryPackageContainer.swift
1819
PackageContainer/SourceControlPackageContainer.swift
@@ -27,6 +28,7 @@ add_library(Workspace
2728
Workspace+Editing.swift
2829
Workspace+Manifests.swift
2930
Workspace+PackageContainer.swift
31+
Workspace+Prebuilts.swift
3032
Workspace+Registry.swift
3133
Workspace+ResolvedPackages.swift
3234
Workspace+Signing.swift

0 commit comments

Comments
 (0)