Skip to content

Commit ac1680d

Browse files
Verify product dependencies w/ target platform, vs. hardcoded macOS (#6963)
Product dependencies are now verified using the `buildEnvironment`'s platform, instead of the hardcoded `macOS` platform. This builds on the work of #6732, now allowing cross-compilation of product dependencies 🎉 ### Motivation: Let's say we're cross-compiling macOS -> iOS simulator, with a host system of `macOS v13`. ```swift let package = Package( name: "SwiftFlashlight", products: [.library(...)], platforms: [.iOS(.v16)], dependencies: [ // internally, for iOS(.v16), macOS(.v14) .package(url: "probably/alamofire", exact: "1.2.3") ], // ... ) ``` As long as we set our triple & SDK, we _should_ be good... (and, with #6828, only the triple needed) ```fish > swift build --triple arm64-apple-ios-simulator \ --sdk "$(xcrun --sdk iphonesimulator --show-sdk-path)" ... error: the library 'SwiftFlashlight' requires macos 10.13, but depends on the product 'probably/alamofire' which requires macos 10.14; [...] ``` Even though `SwiftFlashlight` only supports `iOS(.v16)`, [SupportedPlatform](https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageDescription.md#supportedplatform) "[by] default, [...] assigns a predefined minimum deployment version for each supported platforms unless you configure supported platforms [...]". As a side-effect, `macOS` is inferred as a valid platform. (This is why no "target/platform mismatch" error was thrown.) Previously, product dependencies were verified against `macOS`, rather than the target platform. Since `macOS` is always inferred, this surfaced as a "woah! your dependencies are out of sync". ```swift let swiftFlashlight = Package( platforms: [ .iOS(.v16), .macOS(.v13) // <-- inferred-| ], | ) |-- // since --triple is .iOS, | // shouldn't be comparing let probably/alamofire = Package ( | // macOS. platforms: [ | iOS(.v16), | macOS(.v14)] // <-- inferred-| ] ) ``` Now, the intention of `--triple` is respected 🐱 ### Modifications: - `validateDeploymentVersionOfProductDependency` accepts a `buildEnvironment` containing the target platform ### Result: Product dependencies can now be compiled for `darwin` other than macOS -- i.e, iOS :) ### Future work - Target-level or product-level platform support is still up in the air - This only applies to `triple.isDarwin()` -- perhaps a next step of verifying dependencies against non-darwin? --------- Co-authored-by: Max Desiatov <[email protected]>
1 parent ace3488 commit ac1680d

File tree

3 files changed

+1362
-832
lines changed

3 files changed

+1362
-832
lines changed

Sources/Build/BuildPlan/BuildPlan.swift

Lines changed: 87 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ import var TSCUtility.verbosity
3232

3333
extension String {
3434
var asSwiftStringLiteralConstant: String {
35-
return unicodeScalars.reduce("", { $0 + $1.escaped(asASCII: false) })
35+
unicodeScalars.reduce("") { $0 + $1.escaped(asASCII: false) }
3636
}
3737
}
3838

39-
extension Array where Element == String {
39+
extension [String] {
4040
/// Converts a set of C compiler flags into an equivalent set to be
4141
/// indirected through the Swift compiler instead.
4242
func asSwiftcCCompilerFlags() -> Self {
@@ -142,7 +142,8 @@ extension BuildParameters {
142142
return args
143143
}
144144

145-
/// Computes the linker flags to use in order to rename a module-named main function to 'main' for the target platform, or nil if the linker doesn't support it for the platform.
145+
/// Computes the linker flags to use in order to rename a module-named main function to 'main' for the target
146+
/// platform, or nil if the linker doesn't support it for the platform.
146147
func linkerFlagsForRenamingMainFunction(of target: ResolvedTarget) -> [String]? {
147148
let args: [String]
148149
if self.triple.isApple() {
@@ -209,12 +210,12 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
209210

210211
/// The build targets.
211212
public var targets: AnySequence<TargetBuildDescription> {
212-
return AnySequence(targetMap.values)
213+
AnySequence(self.targetMap.values)
213214
}
214215

215216
/// The products in this plan.
216217
public var buildProducts: AnySequence<SPMBuildCore.ProductBuildDescription> {
217-
return AnySequence(productMap.values.map { $0 as SPMBuildCore.ProductBuildDescription })
218+
AnySequence(self.productMap.values.map { $0 as SPMBuildCore.ProductBuildDescription })
218219
}
219220

220221
/// The results of invoking any build tool plugins used by targets in this build.
@@ -313,11 +314,12 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
313314
observabilityScope: observabilityScope
314315
)
315316
}
316-
let macroProductsByTarget = productMap.keys.filter { $0.type == .macro }.reduce(into: [ResolvedTarget: ResolvedProduct]()) {
317-
if let target = $1.targets.first {
318-
$0[target] = $1
317+
let macroProductsByTarget = productMap.keys.filter { $0.type == .macro }
318+
.reduce(into: [ResolvedTarget: ResolvedProduct]()) {
319+
if let target = $1.targets.first {
320+
$0[target] = $1
321+
}
319322
}
320-
}
321323

322324
// Create build target description for each target which we need to plan.
323325
// Plugin targets are noted, since they need to be compiled, but they do
@@ -348,6 +350,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
348350
try BuildPlan.validateDeploymentVersionOfProductDependency(
349351
product: product,
350352
forTarget: target,
353+
buildEnvironment: buildParameters.buildEnvironment,
351354
observabilityScope: self.observabilityScope
352355
)
353356
}
@@ -372,45 +375,50 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
372375
shouldGenerateTestObservation = false // Only generate the code once.
373376
}
374377

375-
targetMap[target] = try .swift(SwiftTargetBuildDescription(
376-
package: package,
377-
target: target,
378-
toolsVersion: toolsVersion,
379-
additionalFileRules: additionalFileRules,
380-
buildParameters: buildParameters,
381-
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
382-
prebuildCommandResults: prebuildCommandResults[target] ?? [],
383-
requiredMacroProducts: requiredMacroProducts,
384-
shouldGenerateTestObservation: generateTestObservation,
385-
disableSandbox: self.disableSandbox,
386-
fileSystem: fileSystem,
387-
observabilityScope: observabilityScope)
378+
targetMap[target] = try .swift(
379+
SwiftTargetBuildDescription(
380+
package: package,
381+
target: target,
382+
toolsVersion: toolsVersion,
383+
additionalFileRules: additionalFileRules,
384+
buildParameters: buildParameters,
385+
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
386+
prebuildCommandResults: prebuildCommandResults[target] ?? [],
387+
requiredMacroProducts: requiredMacroProducts,
388+
shouldGenerateTestObservation: generateTestObservation,
389+
disableSandbox: self.disableSandbox,
390+
fileSystem: fileSystem,
391+
observabilityScope: observabilityScope
392+
)
388393
)
389394
case is ClangTarget:
390-
targetMap[target] = try .clang(ClangTargetBuildDescription(
391-
target: target,
392-
toolsVersion: toolsVersion,
393-
additionalFileRules: additionalFileRules,
394-
buildParameters: buildParameters,
395-
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
396-
prebuildCommandResults: prebuildCommandResults[target] ?? [],
397-
fileSystem: fileSystem,
398-
observabilityScope: observabilityScope)
395+
targetMap[target] = try .clang(
396+
ClangTargetBuildDescription(
397+
target: target,
398+
toolsVersion: toolsVersion,
399+
additionalFileRules: additionalFileRules,
400+
buildParameters: buildParameters,
401+
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target] ?? [],
402+
prebuildCommandResults: prebuildCommandResults[target] ?? [],
403+
fileSystem: fileSystem,
404+
observabilityScope: observabilityScope
405+
)
399406
)
400407
case is PluginTarget:
401408
guard let package = graph.package(for: target) else {
402409
throw InternalError("package not found for \(target)")
403410
}
404411
try pluginDescriptions.append(PluginDescription(
405412
target: target,
406-
products: package.products.filter{ $0.targets.contains(target) },
413+
products: package.products.filter { $0.targets.contains(target) },
407414
package: package,
408415
toolsVersion: toolsVersion,
409-
fileSystem: fileSystem))
416+
fileSystem: fileSystem
417+
))
410418
case is SystemLibraryTarget, is BinaryTarget:
411-
break
419+
break
412420
default:
413-
throw InternalError("unhandled \(target.underlying)")
421+
throw InternalError("unhandled \(target.underlying)")
414422
}
415423
}
416424

@@ -443,7 +451,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
443451
derivedTestTargets.append(discoveryTargetBuildDescription.target)
444452
}
445453

446-
derivedTestTargetsMap[item.product] = derivedTestTargets
454+
self.derivedTestTargetsMap[item.product] = derivedTestTargets
447455
}
448456
}
449457

@@ -452,18 +460,25 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
452460
self.pluginDescriptions = pluginDescriptions
453461

454462
// Finally plan these targets.
455-
try plan()
463+
try self.plan()
456464
}
457465

458466
static func validateDeploymentVersionOfProductDependency(
459467
product: ResolvedProduct,
460468
forTarget target: ResolvedTarget,
469+
buildEnvironment: BuildEnvironment,
461470
observabilityScope: ObservabilityScope
462471
) throws {
463-
// Supported platforms are defined at the package level.
472+
// Supported platforms are defined at the package (e.g., build environment) level.
464473
// This will need to become a bit complicated once we have target-level or product-level platform support.
465-
let productPlatform = product.getSupportedPlatform(for: .macOS, usingXCTest: product.isLinkingXCTest)
466-
let targetPlatform = target.getSupportedPlatform(for: .macOS, usingXCTest: target.type == .test)
474+
let productPlatform = product.getSupportedPlatform(
475+
for: buildEnvironment.platform,
476+
usingXCTest: product.isLinkingXCTest
477+
)
478+
let targetPlatform = target.getSupportedPlatform(
479+
for: buildEnvironment.platform,
480+
usingXCTest: target.type == .test
481+
)
467482

468483
// Check if the version requirement is satisfied.
469484
//
@@ -481,7 +496,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
481496
/// Plan the targets and products.
482497
private func plan() throws {
483498
// Plan targets.
484-
for buildTarget in targets {
499+
for buildTarget in self.targets {
485500
switch buildTarget {
486501
case .swift(let target):
487502
try self.plan(swiftTarget: target)
@@ -491,8 +506,8 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
491506
}
492507

493508
// Plan products.
494-
for buildProduct in buildProducts {
495-
try plan(buildProduct: buildProduct as! ProductBuildDescription)
509+
for buildProduct in self.buildProducts {
510+
try self.plan(buildProduct: buildProduct as! ProductBuildDescription)
496511
}
497512
// FIXME: We need to find out if any product has a target on which it depends
498513
// both static and dynamically and then issue a suitable diagnostic or auto
@@ -523,7 +538,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
523538
arguments += extraSwiftCFlags
524539

525540
// Add search paths to the directories containing module maps and Swift modules.
526-
for target in targets {
541+
for target in self.targets {
527542
switch target {
528543
case .swift(let targetDescription):
529544
arguments += ["-I", targetDescription.moduleOutputPath.parentDirectory.pathString]
@@ -553,16 +568,16 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
553568
var arguments = ["repl", "-I" + buildPath, "-L" + buildPath]
554569

555570
// Link the special REPL product that contains all of the library targets.
556-
let replProductName = graph.rootPackages[0].identity.description + Product.replProductSuffix
571+
let replProductName = self.graph.rootPackages[0].identity.description + Product.replProductSuffix
557572
arguments.append("-l" + replProductName)
558573

559574
// The graph should have the REPL product.
560-
assert(graph.allProducts.first(where: { $0.name == replProductName }) != nil)
575+
assert(self.graph.allProducts.first(where: { $0.name == replProductName }) != nil)
561576

562577
// Add the search path to the directory containing the modulemap file.
563-
for target in targets {
578+
for target in self.targets {
564579
switch target {
565-
case .swift: break
580+
case .swift: break
566581
case .clang(let targetDescription):
567582
if let includeDir = targetDescription.moduleMap?.parentDirectory {
568583
arguments += ["-I\(includeDir.pathString)"]
@@ -585,16 +600,15 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
585600
// If we already have these flags, we're done.
586601
if let flags = pkgConfigCache[target] {
587602
return flags
588-
}
589-
else {
590-
pkgConfigCache[target] = ([], [])
603+
} else {
604+
self.pkgConfigCache[target] = ([], [])
591605
}
592606
let results = try pkgConfigArgs(
593607
for: target,
594608
pkgConfigDirectories: self.destinationBuildParameters.pkgConfigDirectories,
595609
sdkRootPath: self.destinationBuildParameters.toolchain.sdkRootPath,
596-
fileSystem: fileSystem,
597-
observabilityScope: observabilityScope
610+
fileSystem: self.fileSystem,
611+
observabilityScope: self.observabilityScope
598612
)
599613
var ret: [(cFlags: [String], libs: [String])] = []
600614
for result in results {
@@ -613,21 +627,23 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
613627
}
614628

615629
let result = ([String](cflagsCache), libsCache)
616-
pkgConfigCache[target] = result
630+
self.pkgConfigCache[target] = result
617631
return result
618632
}
619633

620634
/// Extracts the library information from an XCFramework.
621635
func parseXCFramework(for binaryTarget: BinaryTarget, triple: Basics.Triple) throws -> [LibraryInfo] {
622636
try self.externalLibrariesCache.memoize(key: binaryTarget) {
623-
return try binaryTarget.parseXCFrameworks(for: triple, fileSystem: self.fileSystem)
637+
try binaryTarget.parseXCFrameworks(for: triple, fileSystem: self.fileSystem)
624638
}
625639
}
626640
}
627641

628642
extension Basics.Diagnostic {
629643
static var swiftBackDeployError: Self {
630-
.warning("Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from \"More Downloads\" for Apple Developers at https://developer.apple.com/download/more/")
644+
.warning(
645+
"Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from \"More Downloads\" for Apple Developers at https://developer.apple.com/download/more/"
646+
)
631647
}
632648

633649
static func productRequiresHigherPlatformVersion(
@@ -637,15 +653,15 @@ extension Basics.Diagnostic {
637653
productPlatform: SupportedPlatform
638654
) -> Self {
639655
.error("""
640-
the \(target.type.rawValue) '\(target.name)' requires \
641-
\(targetPlatform.platform.name) \(targetPlatform.version.versionString), \
642-
but depends on the product '\(product)' which requires \
643-
\(productPlatform.platform.name) \(productPlatform.version.versionString); \
644-
consider changing the \(target.type.rawValue) '\(target.name)' to require \
645-
\(productPlatform.platform.name) \(productPlatform.version.versionString) or later, \
646-
or the product '\(product)' to require \
647-
\(targetPlatform.platform.name) \(targetPlatform.version.versionString) or earlier.
648-
""")
656+
the \(target.type.rawValue) '\(target.name)' requires \
657+
\(targetPlatform.platform.name) \(targetPlatform.version.versionString), \
658+
but depends on the product '\(product)' which requires \
659+
\(productPlatform.platform.name) \(productPlatform.version.versionString); \
660+
consider changing the \(target.type.rawValue) '\(target.name)' to require \
661+
\(productPlatform.platform.name) \(productPlatform.version.versionString) or later, \
662+
or the product '\(product)' to require \
663+
\(targetPlatform.platform.name) \(targetPlatform.version.versionString) or earlier.
664+
""")
649665
}
650666

651667
static func binaryTargetsNotSupported() -> Self {
@@ -656,7 +672,7 @@ extension Basics.Diagnostic {
656672
extension BuildParameters {
657673
/// Returns a named bundle's path inside the build directory.
658674
func bundlePath(named name: String) -> AbsolutePath {
659-
return buildPath.appending(component: name + self.triple.nsbundleExtension)
675+
buildPath.appending(component: name + self.triple.nsbundleExtension)
660676
}
661677
}
662678

@@ -705,20 +721,20 @@ extension ResolvedPackage {
705721

706722
extension ResolvedProduct {
707723
private var isAutomaticLibrary: Bool {
708-
return self.type == .library(.automatic)
724+
self.type == .library(.automatic)
709725
}
710726

711727
private var isBinaryOnly: Bool {
712728
return self.targets.filter({ !($0.underlying is BinaryTarget) }).isEmpty
713729
}
714730

715731
private var isPlugin: Bool {
716-
return self.type == .plugin
732+
self.type == .plugin
717733
}
718734

719-
// We shouldn't create product descriptions for automatic libraries, plugins or products which consist solely of binary targets, because they don't produce any output.
735+
// We shouldn't create product descriptions for automatic libraries, plugins or products which consist solely of
736+
// binary targets, because they don't produce any output.
720737
fileprivate var shouldCreateProductDescription: Bool {
721-
return !isAutomaticLibrary && !isBinaryOnly && !isPlugin
738+
!self.isAutomaticLibrary && !self.isBinaryOnly && !self.isPlugin
722739
}
723740
}
724-

0 commit comments

Comments
 (0)