From a0fbf35868d3588314f1c9be1215305954565870 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Fri, 4 Apr 2025 13:38:16 -0700 Subject: [PATCH 01/99] Minor Codable conformance improvements in SwiftBuildSupport (#8449) ### Motivation: Some of the `SwiftBuildSupport/PIFBuilder.swift` encoding/decoding code was using unsafe string keys. This replaces with proper typesafe keys. This part of the ground work to support the new PIF builder in `SwiftBuildSupport` (i.e., rdar://147527170). ### Modifications: When conforming to `Codable`, replace string keys with `enum` based keys instead. ### Result: The resulting code is a tad safer and easier to understand :-) Tracked by rdar://148546582. --- Sources/SwiftBuildSupport/PIF.swift | 143 ++++++++++++++++------------ 1 file changed, 82 insertions(+), 61 deletions(-) diff --git a/Sources/SwiftBuildSupport/PIF.swift b/Sources/SwiftBuildSupport/PIF.swift index 8d14a6a4b16..fa295895139 100644 --- a/Sources/SwiftBuildSupport/PIF.swift +++ b/Sources/SwiftBuildSupport/PIF.swift @@ -60,7 +60,25 @@ public enum PIF { } } - public class TypedObject: Codable { + /// Represents a high-level PIF object. + /// + /// For instance, a JSON serialized *workspace* might look like this: + /// ```json + /// { + /// "type" : "workspace", + /// "signature" : "22e9436958aec481799", + /// "contents" : { + /// "guid" : "Workspace:/Users/foo/BarPackage", + /// "name" : "BarPackage", + /// "path" : "/Users/foo/BarPackage", + /// "projects" : [ + /// "70a588f37dcfcddbc1f", + /// "c1d9cb257bd42cafbb8" + /// ] + /// } + /// } + /// ``` + public class HighLevelObject: Codable { class var type: String { fatalError("\(self) missing implementation") } @@ -71,8 +89,9 @@ public enum PIF { type = Swift.type(of: self).type } - private enum CodingKeys: CodingKey { + fileprivate enum CodingKeys: CodingKey { case type + case signature, contents // Used by subclasses. } public func encode(to encoder: Encoder) throws { @@ -86,7 +105,7 @@ public enum PIF { } } - public final class Workspace: TypedObject { + public final class Workspace: HighLevelObject { override class var type: String { "workspace" } public let guid: GUID @@ -113,8 +132,8 @@ public enum PIF { public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: StringKey.self) - var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) + var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) try contents.encode(name, forKey: .name) try contents.encode(path, forKey: .path) @@ -123,7 +142,7 @@ public enum PIF { guard let signature else { throw InternalError("Expected to have workspace signature when encoding for SwiftBuild") } - try container.encode(signature, forKey: "signature") + try superContainer.encode(signature, forKey: .signature) try contents.encode(projects.map({ $0.signature }), forKey: .projects) } else { try contents.encode(projects, forKey: .projects) @@ -131,21 +150,21 @@ public enum PIF { } public required init(from decoder: Decoder) throws { - let superContainer = try decoder.container(keyedBy: StringKey.self) - let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) + let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - let guidString = try container.decode(GUID.self, forKey: .guid) + let guidString = try contents.decode(GUID.self, forKey: .guid) self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - self.name = try container.decode(String.self, forKey: .name) - self.path = try container.decode(AbsolutePath.self, forKey: .path) - self.projects = try container.decode([Project].self, forKey: .projects) + self.name = try contents.decode(String.self, forKey: .name) + self.path = try contents.decode(AbsolutePath.self, forKey: .path) + self.projects = try contents.decode([Project].self, forKey: .projects) try super.init(from: decoder) } } /// A PIF project, consisting of a tree of groups and file references, a list of targets, and some additional /// information. - public final class Project: TypedObject { + public final class Project: HighLevelObject { override class var type: String { "project" } public let guid: GUID @@ -191,8 +210,8 @@ public enum PIF { public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: StringKey.self) - var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) + var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) try contents.encode(name, forKey: .projectName) try contents.encode("true", forKey: .projectIsPackage) @@ -206,7 +225,7 @@ public enum PIF { guard let signature else { throw InternalError("Expected to have project signature when encoding for SwiftBuild") } - try container.encode(signature, forKey: "signature") + try superContainer.encode(signature, forKey: .signature) try contents.encode(targets.map{ $0.signature }, forKey: .targets) } else { try contents.encode(targets, forKey: .targets) @@ -216,19 +235,19 @@ public enum PIF { } public required init(from decoder: Decoder) throws { - let superContainer = try decoder.container(keyedBy: StringKey.self) - let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) + let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - let guidString = try container.decode(GUID.self, forKey: .guid) + let guidString = try contents.decode(GUID.self, forKey: .guid) self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - self.name = try container.decode(String.self, forKey: .projectName) - self.path = try container.decode(AbsolutePath.self, forKey: .path) - self.projectDirectory = try container.decode(AbsolutePath.self, forKey: .projectDirectory) - self.developmentRegion = try container.decode(String.self, forKey: .developmentRegion) - self.buildConfigurations = try container.decode([BuildConfiguration].self, forKey: .buildConfigurations) - - let untypedTargets = try container.decode([UntypedTarget].self, forKey: .targets) - var targetContainer = try container.nestedUnkeyedContainer(forKey: .targets) + self.name = try contents.decode(String.self, forKey: .projectName) + self.path = try contents.decode(AbsolutePath.self, forKey: .path) + self.projectDirectory = try contents.decode(AbsolutePath.self, forKey: .projectDirectory) + self.developmentRegion = try contents.decode(String.self, forKey: .developmentRegion) + self.buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations) + + let untypedTargets = try contents.decode([UntypedTarget].self, forKey: .targets) + var targetContainer = try contents.nestedUnkeyedContainer(forKey: .targets) self.targets = try untypedTargets.map { target in let type = target.contents.type switch type { @@ -241,13 +260,13 @@ public enum PIF { } } - self.groupTree = try container.decode(Group.self, forKey: .groupTree) + self.groupTree = try contents.decode(Group.self, forKey: .groupTree) try super.init(from: decoder) } } /// Abstract base class for all items in the group hierarchy. - public class Reference: TypedObject { + public class Reference: HighLevelObject { /// Determines the base path for a reference's relative path. public enum SourceTree: String, Codable { @@ -387,7 +406,7 @@ public enum PIF { public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let untypedChildren = try container.decode([TypedObject].self, forKey: .children) + let untypedChildren = try container.decode([HighLevelObject].self, forKey: .children) var childrenContainer = try container.nestedUnkeyedContainer(forKey: .children) self.children = try untypedChildren.map { child in @@ -440,7 +459,7 @@ public enum PIF { } } - public class BaseTarget: TypedObject { + public class BaseTarget: HighLevelObject { class override var type: String { "target" } public let guid: GUID public var name: String @@ -500,8 +519,8 @@ public enum PIF { public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: StringKey.self) - var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) + var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) try contents.encode("aggregate", forKey: .type) try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) try contents.encode(name, forKey: .name) @@ -514,22 +533,22 @@ public enum PIF { guard let signature else { throw InternalError("Expected to have \(Swift.type(of: self)) signature when encoding for SwiftBuild") } - try container.encode(signature, forKey: "signature") + try superContainer.encode(signature, forKey: .signature) } } public required init(from decoder: Decoder) throws { - let superContainer = try decoder.container(keyedBy: StringKey.self) - let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) + let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - let guidString = try container.decode(GUID.self, forKey: .guid) + let guidString = try contents.decode(GUID.self, forKey: .guid) let guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - let name = try container.decode(String.self, forKey: .name) - let buildConfigurations = try container.decode([BuildConfiguration].self, forKey: .buildConfigurations) + let name = try contents.decode(String.self, forKey: .name) + let buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations) - let untypedBuildPhases = try container.decode([TypedObject].self, forKey: .buildPhases) - var buildPhasesContainer = try container.nestedUnkeyedContainer(forKey: .buildPhases) + let untypedBuildPhases = try contents.decode([HighLevelObject].self, forKey: .buildPhases) + var buildPhasesContainer = try contents.nestedUnkeyedContainer(forKey: .buildPhases) let buildPhases: [BuildPhase] = try untypedBuildPhases.map { guard let type = $0.type else { @@ -538,8 +557,8 @@ public enum PIF { return try BuildPhase.decode(container: &buildPhasesContainer, type: type) } - let dependencies = try container.decode([TargetDependency].self, forKey: .dependencies) - let impartedBuildProperties = try container.decode(BuildSettings.self, forKey: .impartedBuildProperties) + let dependencies = try contents.decode([TargetDependency].self, forKey: .dependencies) + let impartedBuildProperties = try contents.decode(BuildSettings.self, forKey: .impartedBuildProperties) super.init( guid: guid, @@ -600,8 +619,8 @@ public enum PIF { override public func encode(to encoder: Encoder) throws { try super.encode(to: encoder) - var container = encoder.container(keyedBy: StringKey.self) - var contents = container.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) + var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) try contents.encode(name, forKey: .name) try contents.encode(dependencies, forKey: .dependencies) @@ -611,7 +630,7 @@ public enum PIF { guard let signature else { throw InternalError("Expected to have \(Swift.type(of: self)) signature when encoding for SwiftBuild") } - try container.encode(signature, forKey: "signature") + try superContainer.encode(signature, forKey: .signature) } if productType == .packageProduct { @@ -639,16 +658,16 @@ public enum PIF { } public required init(from decoder: Decoder) throws { - let superContainer = try decoder.container(keyedBy: StringKey.self) - let container = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: "contents") + let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) + let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - let guidString = try container.decode(GUID.self, forKey: .guid) + let guidString = try contents.decode(GUID.self, forKey: .guid) let guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - let name = try container.decode(String.self, forKey: .name) - let buildConfigurations = try container.decode([BuildConfiguration].self, forKey: .buildConfigurations) - let dependencies = try container.decode([TargetDependency].self, forKey: .dependencies) + let name = try contents.decode(String.self, forKey: .name) + let buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations) + let dependencies = try contents.decode([TargetDependency].self, forKey: .dependencies) - let type = try container.decode(String.self, forKey: .type) + let type = try contents.decode(String.self, forKey: .type) let buildPhases: [BuildPhase] let impartedBuildProperties: ImpartedBuildProperties @@ -656,17 +675,17 @@ public enum PIF { if type == "packageProduct" { self.productType = .packageProduct self.productName = "" - let fwkBuildPhase = try container.decodeIfPresent(FrameworksBuildPhase.self, forKey: .frameworksBuildPhase) + let fwkBuildPhase = try contents.decodeIfPresent(FrameworksBuildPhase.self, forKey: .frameworksBuildPhase) buildPhases = fwkBuildPhase.map{ [$0] } ?? [] impartedBuildProperties = ImpartedBuildProperties(settings: BuildSettings()) } else if type == "standard" { - self.productType = try container.decode(ProductType.self, forKey: .productTypeIdentifier) + self.productType = try contents.decode(ProductType.self, forKey: .productTypeIdentifier) - let productReference = try container.decode([String: String].self, forKey: .productReference) + let productReference = try contents.decode([String: String].self, forKey: .productReference) self.productName = productReference["name"]! - let untypedBuildPhases = try container.decodeIfPresent([TypedObject].self, forKey: .buildPhases) ?? [] - var buildPhasesContainer = try container.nestedUnkeyedContainer(forKey: .buildPhases) + let untypedBuildPhases = try contents.decodeIfPresent([HighLevelObject].self, forKey: .buildPhases) ?? [] + var buildPhasesContainer = try contents.nestedUnkeyedContainer(forKey: .buildPhases) buildPhases = try untypedBuildPhases.map { guard let type = $0.type else { @@ -675,7 +694,7 @@ public enum PIF { return try BuildPhase.decode(container: &buildPhasesContainer, type: type) } - impartedBuildProperties = try container.decode(ImpartedBuildProperties.self, forKey: .impartedBuildProperties) + impartedBuildProperties = try contents.decode(ImpartedBuildProperties.self, forKey: .impartedBuildProperties) } else { throw InternalError("Unhandled target type \(type)") } @@ -693,7 +712,7 @@ public enum PIF { } /// Abstract base class for all build phases in a target. - public class BuildPhase: TypedObject { + public class BuildPhase: HighLevelObject { static func decode(container: inout UnkeyedDecodingContainer, type: String) throws -> BuildPhase { switch type { case HeadersBuildPhase.type: @@ -1158,7 +1177,7 @@ public struct SwiftBuildFileType: CaseIterable { } } -struct StringKey: CodingKey, ExpressibleByStringInterpolation { +fileprivate struct StringKey: CodingKey, ExpressibleByStringInterpolation { var stringValue: String var intValue: Int? @@ -1253,6 +1272,8 @@ private struct UntypedTarget: Decodable { let contents: TargetContents } +// MARK: - PIF Signature Support + protocol PIFSignableObject: AnyObject { var signature: String? { get set } } From c8d439a2ccc679477165d6dc992ef4a44b23b77c Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Fri, 4 Apr 2025 20:31:36 -0700 Subject: [PATCH 02/99] Switch to ProjectModel API in the new SwiftBuildSupport PIF builder (#8441) ### Motivation: The goal is to adopt the new `SwiftBuild.ProjectModel` API. This new API provides a *typesafer* and *modern* way of building PIFs programmatically. This continues the work I started in PR #8405, introducing now our new PIF builder for packages. ### Modifications: Replaces all `SwiftBuild.PIF` (aka, `SWBProjectModel.PIF`) API usage, in `PackagePIFBuilder`, with the new `SwiftBuild.ProjectModel` API instead. ### Result: `PackagePIFBuilder` is now modernized... but still not actually used. This will come in my next pull request. Tracked by rdar://147526957. --- Sources/SwiftBuildSupport/PIFBuilder.swift | 6 +- .../PackagePIFBuilder+Helpers.swift | 295 +++++----- .../PackagePIFBuilder+Plugins.swift | 5 +- .../SwiftBuildSupport/PackagePIFBuilder.swift | 226 +++---- .../PackagePIFProjectBuilder+Modules.swift | 444 ++++++++------ .../PackagePIFProjectBuilder+Products.swift | 556 +++++++++++------- .../PackagePIFProjectBuilder.swift | 262 +++++---- 7 files changed, 1028 insertions(+), 766 deletions(-) diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index c658d0bb4b6..e5a4762c926 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -1641,7 +1641,7 @@ private struct PIFBuildSettingAssignment { let platforms: [PIF.BuildSettings.Platform]? } -extension BuildSettings.AssignmentTable { +extension PackageModel.BuildSettings.AssignmentTable { fileprivate var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] { var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] = [:] @@ -1679,7 +1679,7 @@ extension BuildSettings.AssignmentTable { } } -extension BuildSettings.Assignment { +extension PackageModel.BuildSettings.Assignment { fileprivate var configurations: [BuildConfiguration] { if let configurationCondition = conditions.lazy.compactMap(\.configurationCondition).first { [configurationCondition.configuration] @@ -1866,7 +1866,7 @@ extension PIF.BuildSettings { .filter { isSupportedVersion($0) }.map(\.description) } - func computeEffectiveTargetVersion(for assignment: BuildSettings.Assignment) throws -> String { + func computeEffectiveTargetVersion(for assignment: PackageModel.BuildSettings.Assignment) throws -> String { let versions = assignment.values.compactMap { SwiftLanguageVersion(string: $0) } if let effectiveVersion = computeEffectiveSwiftVersions(for: versions).last { return effectiveVersion diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index 0b2f782b4c2..f8819eaf0cf 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -56,7 +56,8 @@ import struct PackageGraph.ResolvedProduct import func PackageLoading.pkgConfigArgs #if canImport(SwiftBuild) -import enum SwiftBuild.PIF + +import enum SwiftBuild.ProjectModel // MARK: - PIF GUID Helpers @@ -76,25 +77,25 @@ extension TargetGUIDSuffix? { } extension PackageModel.Module { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> String { - PIFPackageBuilder.targetGUID(forModuleName: self.name, suffix: suffix) + func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { + PackagePIFBuilder.targetGUID(forModuleName: self.name, suffix: suffix) } } extension PackageGraph.ResolvedModule { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> String { + func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { self.underlying.pifTargetGUID(suffix: suffix) } } extension PackageModel.Product { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> String { - PIFPackageBuilder.targetGUID(forProductName: self.name, suffix: suffix) + func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { + PackagePIFBuilder.targetGUID(forProductName: self.name, suffix: suffix) } } extension PackageGraph.ResolvedProduct { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> String { + func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { self.underlying.pifTargetGUID(suffix: suffix) } @@ -106,12 +107,12 @@ extension PackageGraph.ResolvedProduct { } } -extension PIFPackageBuilder { +extension PackagePIFBuilder { /// Helper function to consistently generate a PIF target identifier string for a module in a package. /// /// This format helps make sure that there is no collision with any other PIF targets, /// and in particular that a PIF target and a PIF product can have the same name (as they often do). - static func targetGUID(forModuleName name: String, suffix: TargetGUIDSuffix? = nil) -> String { + static func targetGUID(forModuleName name: String, suffix: TargetGUIDSuffix? = nil) -> GUID { let suffixDescription = suffix.description(forName: name) return "PACKAGE-TARGET:\(name)\(suffixDescription)" } @@ -120,7 +121,7 @@ extension PIFPackageBuilder { /// /// This format helps make sure that there is no collision with any other PIF targets, /// and in particular that a PIF target and a PIF product can have the same name (as they often do). - static func targetGUID(forProductName name: String, suffix: TargetGUIDSuffix? = nil) -> String { + static func targetGUID(forProductName name: String, suffix: TargetGUIDSuffix? = nil) -> GUID { let suffixDescription = suffix.description(forName: name) return "PACKAGE-PRODUCT:\(name)\(suffixDescription)" } @@ -140,14 +141,14 @@ extension PackageModel.Package { self.manifest.displayName } - var packageBaseBuildSettings: SwiftBuild.PIF.BuildSettings { - var settings = SwiftBuild.PIF.BuildSettings() - settings.SDKROOT = "auto" - settings.SDK_VARIANT = "auto" + var packageBaseBuildSettings: ProjectModel.BuildSettings { + var settings = BuildSettings() + settings[.SDKROOT] = "auto" + settings[.SDK_VARIANT] = "auto" if self.manifest.toolsVersion >= ToolsVersion.v6_0 { if let version = manifest.version, !version.isPrerelease && !version.hasBuildMetadata { - settings.SWIFT_USER_MODULE_VERSION = version.stringRepresentation + settings[.SWIFT_USER_MODULE_VERSION] = version.stringRepresentation } } return settings @@ -204,14 +205,14 @@ extension PackageModel.Platform { } extension Sequence { - func toPlatformFilter(toolsVersion: ToolsVersion) -> Set { - let pifPlatforms = self.flatMap { packageCondition -> [SwiftBuild.PIF.BuildSettings.Platform] in + func toPlatformFilter(toolsVersion: ToolsVersion) -> Set { + let pifPlatforms = self.flatMap { packageCondition -> [ProjectModel.BuildSettings.Platform] in guard let platforms = packageCondition.platformsCondition?.platforms else { return [] } - var pifPlatformsForCondition: [SwiftBuild.PIF.BuildSettings.Platform] = platforms - .map { SwiftBuild.PIF.BuildSettings.Platform(from: $0) } + var pifPlatformsForCondition: [ProjectModel.BuildSettings.Platform] = platforms + .map { ProjectModel.BuildSettings.Platform(from: $0) } // Treat catalyst like macOS for backwards compatibility with older tools versions. if pifPlatformsForCondition.contains(.macOS), toolsVersion < ToolsVersion.v5_5 { @@ -219,7 +220,7 @@ extension Sequence { } return pifPlatformsForCondition } - return pifPlatforms.toPlatformFilter() + return Set(pifPlatforms.flatMap { $0.toPlatformFilter() }) } var splitIntoConcreteConditions: ( @@ -291,7 +292,7 @@ extension PackageGraph.ResolvedPackage { } /// The options declared per platform. - func sdkOptions(delegate: PIFPackageBuilder.BuildDelegate) -> [PackageModel.Platform: [String]] { + func sdkOptions(delegate: PackagePIFBuilder.BuildDelegate) -> [PackageModel.Platform: [String]] { let platformDescriptionsByName: [String: PlatformDescription] = Dictionary( uniqueKeysWithValues: self.manifest.platforms.map { platformDescription in let key = platformDescription.platformName.lowercased() @@ -314,7 +315,7 @@ extension PackageGraph.ResolvedPackage { } extension PackageGraph.ResolvedPackage { - public var packageBaseBuildSettings: SwiftBuild.PIF.BuildSettings { + public var packageBaseBuildSettings: ProjectModel.BuildSettings { self.underlying.packageBaseBuildSettings } } @@ -338,7 +339,7 @@ extension PackageGraph.ResolvedModule { } /// Minimum deployment targets for particular platforms, as declared in the manifest. - func deploymentTargets(using delegate: PIFPackageBuilder.BuildDelegate) -> [PackageModel.Platform: String] { + func deploymentTargets(using delegate: PackagePIFBuilder.BuildDelegate) -> [PackageModel.Platform: String] { let isUsingXCTest = (self.type == .test) let derivedSupportedPlatforms: [SupportedPlatform] = Platform.knownPlatforms.map { self.getSupportedPlatform(for: $0, usingXCTest: isUsingXCTest) @@ -489,8 +490,7 @@ extension PackageGraph.ResolvedModule { func productRepresentingDependencyOfBuildPlugin(in mainModuleProducts: [ResolvedProduct]) -> ResolvedProduct? { mainModuleProducts.only { (mainModuleProduct: ResolvedProduct) -> Bool in // NOTE: We can't use the 'id' here as we need to explicitly ignore the build triple because our build - // triple - // will be '.tools' while the target we want to depend on will have a build triple of '.destination'. + // triple will be '.tools' while the target we want to depend on will have a build triple of '.destination'. // See for more details: // https://github.com/swiftlang/swift-package-manager/commit/b22168ec41061ddfa3438f314a08ac7a776bef7a. return mainModuleProduct.mainModule!.packageIdentity == self.packageIdentity && @@ -500,7 +500,8 @@ extension PackageGraph.ResolvedModule { } struct AllBuildSettings { - typealias BuildSettingsByPlatform = [PackageModel.Platform?: [BuildSettings.Declaration: [String]]] + typealias BuildSettingsByPlatform = + [ProjectModel.BuildSettings.Platform?: [BuildSettings.Declaration: [String]]] /// Target-specific build settings declared in the manifest and that apply to the target itself. var targetSettings: [BuildConfiguration: BuildSettingsByPlatform] = [:] @@ -519,22 +520,22 @@ extension PackageGraph.ResolvedModule { for (declaration, settingsAssigments) in self.underlying.buildSettings.assignments { for settingAssignment in settingsAssigments { - // Create a build setting value; in some cases there isn't a direct mapping to Swift Build build - // settings. - let swbDeclaration: BuildSettings.Declaration + // Create a build setting value; in some cases there + // isn't a direct mapping to Swift Build build settings. + let pifDeclaration: BuildSettings.Declaration let values: [String] switch declaration { case .LINK_FRAMEWORKS: - swbDeclaration = .OTHER_LDFLAGS + pifDeclaration = .OTHER_LDFLAGS values = settingAssignment.values.flatMap { ["-framework", $0] } case .LINK_LIBRARIES: - swbDeclaration = .OTHER_LDFLAGS + pifDeclaration = .OTHER_LDFLAGS values = settingAssignment.values.map { "-l\($0)" } case .HEADER_SEARCH_PATHS: - swbDeclaration = .HEADER_SEARCH_PATHS + pifDeclaration = .HEADER_SEARCH_PATHS values = settingAssignment.values.map { self.sourceDirAbsolutePath.pathString + "/" + $0 } default: - swbDeclaration = declaration + pifDeclaration = ProjectModel.BuildSettings.Declaration(from: declaration) values = settingAssignment.values } @@ -542,24 +543,28 @@ extension PackageGraph.ResolvedModule { let (platforms, configurations, _) = settingAssignment.conditions.splitIntoConcreteConditions for platform in platforms { - if swbDeclaration == .OTHER_LDFLAGS { - var settingsByDeclaration: [BuildSettings.Declaration: [String]] = allSettings - .impartedSettings[platform] ?? [:] - settingsByDeclaration[swbDeclaration, default: []].append(contentsOf: values) + let pifPlatform = platform.map { ProjectModel.BuildSettings.Platform(from: $0) } + + if pifDeclaration == .OTHER_LDFLAGS { + var settingsByDeclaration: [ProjectModel.BuildSettings.Declaration: [String]] + + settingsByDeclaration = allSettings.impartedSettings[pifPlatform] ?? [:] + settingsByDeclaration[pifDeclaration, default: []].append(contentsOf: values) - allSettings.impartedSettings[platform] = settingsByDeclaration + allSettings.impartedSettings[pifPlatform] = settingsByDeclaration } for configuration in configurations { - var settingsByDeclaration: [BuildSettings.Declaration: [String]] = allSettings - .targetSettings[configuration]?[platform] ?? [:] - if swbDeclaration.allowsMultipleValues { - settingsByDeclaration[swbDeclaration, default: []].append(contentsOf: values) + var settingsByDeclaration: [ProjectModel.BuildSettings.Declaration: [String]] + settingsByDeclaration = allSettings.targetSettings[configuration]?[pifPlatform] ?? [:] + + if declaration.allowsMultipleValues { + settingsByDeclaration[pifDeclaration, default: []].append(contentsOf: values) } else { - settingsByDeclaration[swbDeclaration] = values.only.flatMap { [$0] } ?? [] + settingsByDeclaration[pifDeclaration] = values.only.flatMap { [$0] } ?? [] } - allSettings.targetSettings[configuration, default: [:]][platform] = settingsByDeclaration + allSettings.targetSettings[configuration, default: [:]][pifPlatform] = settingsByDeclaration } } } @@ -793,110 +798,128 @@ extension TSCUtility.Version { // MARK: - Swift Build PIF Helpers -/// Helpers for building custom PIF targets by `PIFPackageBuilder` clients. -extension SwiftBuild.PIF.Project { +extension ProjectModel.BuildSettings { + subscript(_ setting: MultipleValueSetting, default defaultValue: [String]) -> [String] { + get { self[setting] ?? defaultValue } + set { self[setting] = newValue } + } +} + +/// Helpers for building custom PIF targets by `PackagePIFBuilder` clients. +extension ProjectModel.Project { @discardableResult - public func addTarget( + public mutating func addTarget( packageProductName: String, - productType: SwiftBuild.PIF.Target.ProductType - ) throws -> SwiftBuild.PIF.Target { - let pifTarget = try self.addTargetThrowing( - id: PIFPackageBuilder.targetGUID(forProductName: packageProductName), - productType: productType, - name: packageProductName, - productName: packageProductName - ) - return pifTarget + productType: ProjectModel.Target.ProductType + ) throws -> WritableKeyPath { + let targetKeyPath = try self.addTarget { _ in + ProjectModel.Target( + id: PackagePIFBuilder.targetGUID(forProductName: packageProductName), + productType: productType, + name: packageProductName, + productName: packageProductName + ) + } + return targetKeyPath } @discardableResult - public func addTarget( + public mutating func addTarget( packageModuleName: String, - productType: SwiftBuild.PIF.Target.ProductType - ) throws -> SwiftBuild.PIF.Target { - let pifTarget = try self.addTargetThrowing( - id: PIFPackageBuilder.targetGUID(forModuleName: packageModuleName), - productType: productType, - name: packageModuleName, - productName: packageModuleName - ) - return pifTarget + productType: ProjectModel.Target.ProductType + ) throws -> WritableKeyPath { + let targetKeyPath = try self.addTarget { _ in + ProjectModel.Target( + id: PackagePIFBuilder.targetGUID(forModuleName: packageModuleName), + productType: productType, + name: packageModuleName, + productName: packageModuleName + ) + } + return targetKeyPath } } -extension SwiftBuild.PIF.BuildSettings { +extension ProjectModel.BuildSettings { /// Internal helper function that appends list of string values to a declaration. /// If a platform is specified, then the values are appended to the `platformSpecificSettings`, /// otherwise they are appended to the platform-neutral settings. /// /// Note that this restricts the settings that can be set by this function to those that can have platform-specific - /// values, - /// i.e. those in `PIF.Declaration`. If a platform is specified, it must be one of the known platforms in - /// `PIF.Platform`. + /// values, i.e. those in `ProjectModel.BuildSettings.Declaration`. If a platform is specified, + /// it must be one of the known platforms in `ProjectModel.BuildSettings.Platform`. mutating func append(values: [String], to setting: Declaration, platform: Platform? = nil) { - // This dichotomy is quite unfortunate but that's currently the underlying model in `PIF.BuildSettings`. + // This dichotomy is quite unfortunate but that's currently the underlying model in ProjectModel.BuildSettings. if let platform { - // FIXME: The force unwraps here are pretty bad, - // but are the same as in the existing code before it was factored into this function. - // We should get rid of the force unwraps. And fix the PIF generation model. - // NOTE: Appending implies the setting is resilient to having ["$(inherited)"] switch setting { - case .FRAMEWORK_SEARCH_PATHS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - case .GCC_PREPROCESSOR_DEFINITIONS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - case .HEADER_SEARCH_PATHS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - case .OTHER_CFLAGS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - case .OTHER_CPLUSPLUSFLAGS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - case .OTHER_LDFLAGS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - case .OTHER_SWIFT_FLAGS: + case .FRAMEWORK_SEARCH_PATHS, + .GCC_PREPROCESSOR_DEFINITIONS, + .HEADER_SEARCH_PATHS, + .OTHER_CFLAGS, + .OTHER_CPLUSPLUSFLAGS, + .OTHER_LDFLAGS, + .OTHER_SWIFT_FLAGS, + .SWIFT_ACTIVE_COMPILATION_CONDITIONS: + // Appending implies the setting is resilient to having ["$(inherited)"] self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) + case .SWIFT_VERSION: - self.platformSpecificSettings[platform]![setting] = values // we are not resilient to $(inherited) - case .SWIFT_ACTIVE_COMPILATION_CONDITIONS: - self.platformSpecificSettings[platform]![setting]!.append(contentsOf: values) - default: - fatalError("Unsupported PIF.Declaration: \(setting)") + self.platformSpecificSettings[platform]![setting] = values // We are not resilient to $(inherited). + + case .ARCHS, .IPHONEOS_DEPLOYMENT_TARGET, .SPECIALIZATION_SDK_OPTIONS: + fatalError("Unexpected BuildSettings.Declaration: \(setting)") } } else { - // FIXME: This is pretty ugly. - // The whole point of this helper function is to hide this ugliness from the rest of the logic. - // We need to fix the PIF generation model. switch setting { - case .FRAMEWORK_SEARCH_PATHS: - self.FRAMEWORK_SEARCH_PATHS = (self.FRAMEWORK_SEARCH_PATHS ?? ["$(inherited)"]) + values - case .GCC_PREPROCESSOR_DEFINITIONS: - self.GCC_PREPROCESSOR_DEFINITIONS = (self.GCC_PREPROCESSOR_DEFINITIONS ?? ["$(inherited)"]) + values - case .HEADER_SEARCH_PATHS: - self.HEADER_SEARCH_PATHS = (self.HEADER_SEARCH_PATHS ?? ["$(inherited)"]) + values - case .OTHER_CFLAGS: - self.OTHER_CFLAGS = (self.OTHER_CFLAGS ?? ["$(inherited)"]) + values - case .OTHER_CPLUSPLUSFLAGS: - self.OTHER_CPLUSPLUSFLAGS = (self.OTHER_CPLUSPLUSFLAGS ?? ["$(inherited)"]) + values - case .OTHER_LDFLAGS: - self.OTHER_LDFLAGS = (self.OTHER_LDFLAGS ?? ["$(inherited)"]) + values - case .OTHER_SWIFT_FLAGS: - self.OTHER_SWIFT_FLAGS = (self.OTHER_SWIFT_FLAGS ?? ["$(inherited)"]) + values + case .FRAMEWORK_SEARCH_PATHS, + .GCC_PREPROCESSOR_DEFINITIONS, + .HEADER_SEARCH_PATHS, + .OTHER_CFLAGS, + .OTHER_CPLUSPLUSFLAGS, + .OTHER_LDFLAGS, + .OTHER_SWIFT_FLAGS, + .SWIFT_ACTIVE_COMPILATION_CONDITIONS: + let multipleSetting = MultipleValueSetting(from: setting)! + self[multipleSetting, default: ["$(inherited)"]].append(contentsOf: values) + case .SWIFT_VERSION: - self.SWIFT_VERSION = values.only.unwrap(orAssert: "Invalid values for 'SWIFT_VERSION': \(values)") - case .SWIFT_ACTIVE_COMPILATION_CONDITIONS: - self - .SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - self - .SWIFT_ACTIVE_COMPILATION_CONDITIONS ?? ["$(inherited)"] - ) + values - default: - fatalError("Unsupported PIF.Declaration: \(setting)") + self[.SWIFT_VERSION] = values.only.unwrap(orAssert: "Invalid values for 'SWIFT_VERSION': \(values)") + + case .ARCHS, .IPHONEOS_DEPLOYMENT_TARGET, .SPECIALIZATION_SDK_OPTIONS: + fatalError("Unexpected BuildSettings.Declaration: \(setting)") } } } } -extension SwiftBuild.PIF.BuildSettings.Platform { +extension ProjectModel.BuildSettings.MultipleValueSetting { + init?(from declaration: ProjectModel.BuildSettings.Declaration) { + switch declaration { + case .GCC_PREPROCESSOR_DEFINITIONS: + self = .GCC_PREPROCESSOR_DEFINITIONS + case .FRAMEWORK_SEARCH_PATHS: + self = .FRAMEWORK_SEARCH_PATHS + case .HEADER_SEARCH_PATHS: + self = .HEADER_SEARCH_PATHS + case .OTHER_CFLAGS: + self = .OTHER_CFLAGS + case .OTHER_CPLUSPLUSFLAGS: + self = .OTHER_CPLUSPLUSFLAGS + case .OTHER_LDFLAGS: + self = .OTHER_LDFLAGS + case .OTHER_SWIFT_FLAGS: + self = .OTHER_SWIFT_FLAGS + case .SPECIALIZATION_SDK_OPTIONS: + self = .SPECIALIZATION_SDK_OPTIONS + case .SWIFT_ACTIVE_COMPILATION_CONDITIONS: + self = .SWIFT_ACTIVE_COMPILATION_CONDITIONS + case .ARCHS, .IPHONEOS_DEPLOYMENT_TARGET, .SWIFT_VERSION: + return nil + } + } +} + +extension ProjectModel.BuildSettings.Platform { init(from platform: PackageModel.Platform) { self = switch platform { case .macOS: .macOS @@ -916,7 +939,7 @@ extension SwiftBuild.PIF.BuildSettings.Platform { } } -extension SwiftBuild.PIF.BuildSettings { +extension ProjectModel.BuildSettings { /// Configure necessary settings for a dynamic library/framework. mutating func configureDynamicSettings( productName: String, @@ -926,41 +949,41 @@ extension SwiftBuild.PIF.BuildSettings { packageName: String?, createDylibForDynamicProducts: Bool, installPath: String, - delegate: PIFPackageBuilder.BuildDelegate + delegate: PackagePIFBuilder.BuildDelegate ) { - self.TARGET_NAME = targetName - self.PRODUCT_NAME = createDylibForDynamicProducts ? productName : executableName - self.PRODUCT_MODULE_NAME = productName - self.PRODUCT_BUNDLE_IDENTIFIER = "\(packageIdentity).\(productName)".spm_mangledToBundleIdentifier() - self.EXECUTABLE_NAME = executableName - self.CLANG_ENABLE_MODULES = "YES" - self.SWIFT_PACKAGE_NAME = packageName ?? nil + self[.TARGET_NAME] = targetName + self[.PRODUCT_NAME] = createDylibForDynamicProducts ? productName : executableName + self[.PRODUCT_MODULE_NAME] = productName + self[.PRODUCT_BUNDLE_IDENTIFIER] = "\(packageIdentity).\(productName)".spm_mangledToBundleIdentifier() + self[.EXECUTABLE_NAME] = executableName + self[.CLANG_ENABLE_MODULES] = "YES" + self[.SWIFT_PACKAGE_NAME] = packageName ?? nil if !createDylibForDynamicProducts { - self.GENERATE_INFOPLIST_FILE = "YES" + self[.GENERATE_INFOPLIST_FILE] = "YES" // If the built framework is named same as one of the target in the package, // it can be picked up automatically during indexing since the build system always adds a -F flag // to the built products dir. // To avoid this problem, we build all package frameworks in a subdirectory. - self.TARGET_BUILD_DIR = "$(TARGET_BUILD_DIR)/PackageFrameworks" + self[.TARGET_BUILD_DIR] = "$(TARGET_BUILD_DIR)/PackageFrameworks" // Set the project and marketing version for the framework because the app store requires these to be // present. // The AppStore requires bumping the project version when ingesting new builds but that's for top-level apps // and not frameworks embedded inside it. - self.MARKETING_VERSION = "1.0" // Version - self.CURRENT_PROJECT_VERSION = "1" // Build + self[.MARKETING_VERSION] = "1.0" // Version + self[.CURRENT_PROJECT_VERSION] = "1" // Build } // Might set install path depending on build delegate. if delegate.shouldSetInstallPathForDynamicLib(productName: productName) { - self.SKIP_INSTALL = "NO" - self.INSTALL_PATH = installPath + self[.SKIP_INSTALL] = "NO" + self[.INSTALL_PATH] = installPath } } } -extension SwiftBuild.PIF.BuildSettings.Declaration { +extension ProjectModel.BuildSettings.Declaration { init(from declaration: PackageModel.BuildSettings.Declaration) { self = switch declaration { // Swift. diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift index 693f15a5a21..3934f30984e 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift @@ -18,9 +18,10 @@ import enum Basics.Sandbox import struct Basics.SourceControlURL #if canImport(SwiftBuild) -import enum SwiftBuild.PIF -extension PIFPackageBuilder { +import enum SwiftBuild.ProjectModel + +extension PackagePIFBuilder { /// Contains all of the information resulting from applying a build tool plugin to a package target thats affect how /// a target is built. /// diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 5549ab693a6..c2582c5c8bd 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -31,10 +31,17 @@ import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage #if canImport(SwiftBuild) -import enum SwiftBuild.PIF + +import enum SwiftBuild.ProjectModel + +typealias GUID = SwiftBuild.ProjectModel.GUID +typealias BuildFile = SwiftBuild.ProjectModel.BuildFile +typealias BuildConfig = SwiftBuild.ProjectModel.BuildConfig +typealias BuildSettings = SwiftBuild.ProjectModel.BuildSettings +typealias FileReference = SwiftBuild.ProjectModel.FileReference /// A builder for generating the PIF object from a package. -public final class PIFPackageBuilder { +public final class PackagePIFBuilder { let modulesGraph: ModulesGraph private let package: ResolvedPackage @@ -42,12 +49,12 @@ public final class PIFPackageBuilder { let packageManifest: PackageModel.Manifest // FIXME: Can't we just use `package.manifest` instead? —— Paulo /// The built PIF project object. - public var pifProject: SwiftBuild.PIF.Project { + public var pifProject: ProjectModel.Project { assert(self._pifProject != nil, "Call build() method to build the PIF first") return self._pifProject! } - private var _pifProject: SwiftBuild.PIF.Project? + private var _pifProject: ProjectModel.Project? /// Scope for logging informational debug messages (intended for developers, not end users). let observabilityScope: ObservabilityScope @@ -86,7 +93,7 @@ public final class PIFPackageBuilder { /// For executables — only executables for now — we check to see if there is a custom package product type /// provider that can provide this information. - func customProductType(forExecutable product: PackageModel.Product) -> SwiftBuild.PIF.Target.ProductType? + func customProductType(forExecutable product: PackageModel.Product) -> ProjectModel.Target.ProductType? /// Returns all *device family* IDs for all SDK variants. func deviceFamilyIDs() -> Set @@ -98,12 +105,12 @@ public final class PIFPackageBuilder { var isPluginExecutionSandboxingDisabled: Bool { get } /// Hook to customize the project-wide build settings. - func configureProjectBuildSettings(_ buildSettings: inout SwiftBuild.PIF.BuildSettings) + func configureProjectBuildSettings(_ buildSettings: inout ProjectModel.BuildSettings) /// Hook to customize source module build settings. func configureSourceModuleBuildSettings( sourceModule: PackageGraph.ResolvedModule, - settings: inout SwiftBuild.PIF.BuildSettings + settings: inout ProjectModel.BuildSettings ) /// Custom install path for the specified product, if any. @@ -119,23 +126,25 @@ public final class PIFPackageBuilder { func customSDKOptions(forPlatform: PackageModel.Platform) -> [String] /// Create additional custom PIF targets after all targets have been built. - func addCustomTargets(pifProject: SwiftBuild.PIF.Project) throws -> [PIFPackageBuilder.ModuleOrProduct] + func addCustomTargets(pifProject: ProjectModel.Project) throws -> [PackagePIFBuilder.ModuleOrProduct] /// Should we suppresses the specific product dependency, updating the provided build settings if necessary? /// The specified product may be in the same package or a different one. func shouldSuppressProductDependency( product: PackageModel.Product, - buildSettings: inout SwiftBuild.PIF.BuildSettings + buildSettings: inout ProjectModel.BuildSettings ) -> Bool /// Should we set the install path for a dynamic library/framework? func shouldSetInstallPathForDynamicLib(productName: String) -> Bool + // FIXME: Let's try to replace `WritableKeyPath><_, Foo>` with `inout Foo` —— Paulo + /// Provides additional configuration and files for the specified library product. func configureLibraryProduct( product: PackageModel.Product, - pifTarget: SwiftBuild.PIF.Target, - additionalFiles: SwiftBuild.PIF.Group + target: WritableKeyPath, + additionalFiles: WritableKeyPath ) /// The design intention behind this is to set a value for `watchOS`, `tvOS`, and `visionOS` @@ -153,7 +162,7 @@ public final class PIFPackageBuilder { } /// Records the results of applying build tool plugins to modules in the package. - let buildToolPluginResultsByTargetName: [String: PIFPackageBuilder.BuildToolPluginInvocationResult] + let buildToolPluginResultsByTargetName: [String: PackagePIFBuilder.BuildToolPluginInvocationResult] /// Whether to create dynamic libraries for dynamic products. /// @@ -182,7 +191,7 @@ public final class PIFPackageBuilder { modulesGraph: ModulesGraph, resolvedPackage: ResolvedPackage, packageManifest: PackageModel.Manifest, - delegate: PIFPackageBuilder.BuildDelegate, + delegate: PackagePIFBuilder.BuildDelegate, buildToolPluginResultsByTargetName: [String: BuildToolPluginInvocationResult], createDylibForDynamicProducts: Bool = false, packageDisplayVersion: String?, @@ -200,12 +209,12 @@ public final class PIFPackageBuilder { /// Build an empty PIF project. public func buildEmptyPIF() { - self._pifProject = PIFPackageBuilder.buildEmptyPIF(package: self.package.underlying) + self._pifProject = PackagePIFBuilder.buildEmptyPIF(package: self.package.underlying) } /// Build an empty PIF project for the specified `Package`. - public class func buildEmptyPIF(package: PackageModel.Package) -> SwiftBuild.PIF.Project { + public class func buildEmptyPIF(package: PackageModel.Package) -> ProjectModel.Project { self.buildEmptyPIF( id: "PACKAGE:\(package.identity)", path: package.manifest.path.pathString, @@ -222,37 +231,46 @@ public final class PIFPackageBuilder { projectDir: String, name: String, developmentRegion: String? = nil - ) -> SwiftBuild.PIF.Project { - let project = SwiftBuild.PIF.Project( - id: id, + ) -> ProjectModel.Project { + var project = ProjectModel.Project( + id: GUID(id), path: path, projectDir: projectDir, name: name, developmentRegion: developmentRegion ) - let settings = SwiftBuild.PIF.BuildSettings() + let settings = ProjectModel.BuildSettings() - project.addBuildConfig(name: "Debug", settings: settings) - project.addBuildConfig(name: "Release", settings: settings) + project.addBuildConfig { id in ProjectModel.BuildConfig(id: id, name: "Debug", settings: settings) } + project.addBuildConfig { id in ProjectModel.BuildConfig(id: id, name: "Release", settings: settings) } return project } public func buildPlaceholderPIF(id: String, path: String, projectDir: String, name: String) -> ModuleOrProduct { - let project = SwiftBuild.PIF.Project( - id: id, + var project = ProjectModel.Project( + id: GUID(id), path: path, projectDir: projectDir, name: name ) - let projectSettings = SwiftBuild.PIF.BuildSettings() - project.addBuildConfig(name: "Debug", settings: projectSettings) - project.addBuildConfig(name: "Release", settings: projectSettings) - let target = project.addAggregateTarget(id: "PACKAGE-PLACEHOLDER:\(id)", name: id) - let targetSettings: SwiftBuild.PIF.BuildSettings = self.package.underlying.packageBaseBuildSettings - target.addBuildConfig(name: "Debug", settings: targetSettings) - target.addBuildConfig(name: "Release", settings: targetSettings) + let projectSettings = ProjectModel.BuildSettings() + + project.addBuildConfig { id in ProjectModel.BuildConfig(id: id, name: "Debug", settings: projectSettings) } + project.addBuildConfig { id in ProjectModel.BuildConfig(id: id, name: "Release", settings: projectSettings) } + + let targetKeyPath = try! project.addAggregateTarget { _ in + ProjectModel.AggregateTarget(id: "PACKAGE-PLACEHOLDER:\(id)", name: id) + } + let targetSettings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings + + project[keyPath: targetKeyPath].common.addBuildConfig { id in + ProjectModel.BuildConfig(id: id, name: "Debug", settings: targetSettings) + } + project[keyPath: targetKeyPath].common.addBuildConfig { id in + ProjectModel.BuildConfig(id: id, name: "Release", settings: targetSettings) + } self._pifProject = project @@ -260,7 +278,7 @@ public final class PIFPackageBuilder { type: .placeholder, name: name, moduleName: name, - pifTarget: target, + pifTarget: .aggregate(project[keyPath: targetKeyPath]), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: [], @@ -281,7 +299,7 @@ public final class PIFPackageBuilder { public var moduleName: String? public var isDynamicLibraryVariant: Bool = false - public var pifTarget: SwiftBuild.PIF.BaseTarget? + public var pifTarget: ProjectModel.BaseTarget? public var indexableFileURLs: [SourceControlURL] public var headerFiles: Set @@ -334,7 +352,7 @@ public final class PIFPackageBuilder { public var description: String { rawValue } - init(from pifProductType: SwiftBuild.PIF.Target.ProductType) { + init(from pifProductType: ProjectModel.Target.ProductType) { self = switch pifProductType { case .application: .application case .staticArchive: .staticArchive @@ -355,12 +373,12 @@ public final class PIFPackageBuilder { /// Build the PIF. @discardableResult public func build() throws -> [ModuleOrProduct] { - self.log(.info, "building PIF for package \(self.package.identity)") + self.log(.info, "Building PIF for package \(self.package.identity)") - var project = PackagePIFProjectBuilder(createForPackage: package, builder: self) - self.addProjectBuildSettings(project: project) + var builder = PackagePIFProjectBuilder(createForPackage: package, builder: self) + self.addProjectBuildSettings(&builder) - self._pifProject = project.pif + self._pifProject = builder.project // // Construct PIF *targets* (for modules, products, and test bundles) based on the contents of the parsed @@ -389,27 +407,27 @@ public final class PIFPackageBuilder { switch product.type { case .library(.static): let libraryType = self.delegate.customLibraryType(product: product.underlying) ?? .static - try project.makeLibraryProduct(product, type: libraryType) + try builder.makeLibraryProduct(product, type: libraryType) case .library(.dynamic): let libraryType = self.delegate.customLibraryType(product: product.underlying) ?? .dynamic - try project.makeLibraryProduct(product, type: libraryType) + try builder.makeLibraryProduct(product, type: libraryType) case .library(.automatic): // Check if this is a system library product. if product.isSystemLibraryProduct { - try project.makeSystemLibraryProduct(product) + try builder.makeSystemLibraryProduct(product) } else { // Otherwise, it is a regular library product. let libraryType = self.delegate.customLibraryType(product: product.underlying) ?? .automatic - try project.makeLibraryProduct(product, type: libraryType) + try builder.makeLibraryProduct(product, type: libraryType) } case .executable, .test: - try project.makeMainModuleProduct(product) + try builder.makeMainModuleProduct(product) case .plugin: - try project.makePluginProduct(product) + try builder.makePluginProduct(product) case .snippet, .macro: break // TODO: Double-check what's going on here as we skip snippet modules too (rdar://147705448) @@ -421,17 +439,17 @@ public final class PIFPackageBuilder { for module in self.package.modules { switch module.type { case .executable: - try project.makeTestableExecutableSourceModule(module) + try builder.makeTestableExecutableSourceModule(module) case .snippet: // Already handled as a product. Note that snippets don't need testable modules. break case .library: - try project.makeLibraryModule(module) + try builder.makeLibraryModule(module) case .systemModule: - try project.makeSystemLibraryModule(module) + try builder.makeSystemLibraryModule(module) case .test: // Skip test module targets. @@ -443,89 +461,89 @@ public final class PIFPackageBuilder { break case .plugin: - try project.makePluginModule(module) + try builder.makePluginModule(module) case .macro: - try project.makeMacroModule(module) + try builder.makeMacroModule(module) } } - let customModulesAndProducts = try delegate.addCustomTargets(pifProject: project.pif) - project.builtModulesAndProducts.append(contentsOf: customModulesAndProducts) + let customModulesAndProducts = try delegate.addCustomTargets(pifProject: builder.project) + builder.builtModulesAndProducts.append(contentsOf: customModulesAndProducts) - return project.builtModulesAndProducts + return builder.builtModulesAndProducts } /// Configure the project-wide build settings. /// First we set those that are in common between the "Debug" and "Release" configurations, and then we set those /// that are different. - private func addProjectBuildSettings(project: PackagePIFProjectBuilder) { - var settings = SwiftBuild.PIF.BuildSettings() - settings.PRODUCT_NAME = "$(TARGET_NAME)" - settings.SUPPORTED_PLATFORMS = ["$(AVAILABLE_PLATFORMS)"] - settings.SKIP_INSTALL = "YES" - settings.MACOSX_DEPLOYMENT_TARGET = project.deploymentTargets[.macOS] ?? nil - settings.IPHONEOS_DEPLOYMENT_TARGET = project.deploymentTargets[.iOS] ?? nil - if let deploymentTarget_macCatalyst = project.deploymentTargets[.macCatalyst] ?? nil { + private func addProjectBuildSettings(_ builder: inout PackagePIFProjectBuilder) { + var settings = ProjectModel.BuildSettings() + settings[.PRODUCT_NAME] = "$(TARGET_NAME)" + settings[.SUPPORTED_PLATFORMS] = ["$(AVAILABLE_PLATFORMS)"] + settings[.SKIP_INSTALL] = "YES" + settings[.MACOSX_DEPLOYMENT_TARGET] = builder.deploymentTargets[.macOS] ?? nil + settings[.IPHONEOS_DEPLOYMENT_TARGET] = builder.deploymentTargets[.iOS] ?? nil + if let deploymentTarget_macCatalyst = builder.deploymentTargets[.macCatalyst] ?? nil { settings .platformSpecificSettings[.macCatalyst]![.IPHONEOS_DEPLOYMENT_TARGET] = [deploymentTarget_macCatalyst] } - settings.TVOS_DEPLOYMENT_TARGET = project.deploymentTargets[.tvOS] ?? nil - settings.WATCHOS_DEPLOYMENT_TARGET = project.deploymentTargets[.watchOS] ?? nil - settings.DRIVERKIT_DEPLOYMENT_TARGET = project.deploymentTargets[.driverKit] ?? nil - settings.XROS_DEPLOYMENT_TARGET = project.deploymentTargets[.visionOS] ?? nil - settings.DYLIB_INSTALL_NAME_BASE = "@rpath" - settings.USE_HEADERMAP = "NO" - settings.OTHER_SWIFT_FLAGS.lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("-DXcode") } + settings[.TVOS_DEPLOYMENT_TARGET] = builder.deploymentTargets[.tvOS] ?? nil + settings[.WATCHOS_DEPLOYMENT_TARGET] = builder.deploymentTargets[.watchOS] ?? nil + settings[.DRIVERKIT_DEPLOYMENT_TARGET] = builder.deploymentTargets[.driverKit] ?? nil + settings[.XROS_DEPLOYMENT_TARGET] = builder.deploymentTargets[.visionOS] ?? nil + settings[.DYLIB_INSTALL_NAME_BASE] = "@rpath" + settings[.USE_HEADERMAP] = "NO" + settings[.OTHER_SWIFT_FLAGS].lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("-DXcode") } // TODO: Might be relevant to make customizable —— Paulo // (If we want to be extra careful with differences to the existing PIF in the SwiftPM.) - settings.OTHER_CFLAGS = ["$(inherited)", "-DXcode"] + settings[.OTHER_CFLAGS] = ["$(inherited)", "-DXcode"] if !self.delegate.isRootPackage { if self.suppressWarningsForPackageDependencies { - settings.SUPPRESS_WARNINGS = "YES" + settings[.SUPPRESS_WARNINGS] = "YES" } if self.skipStaticAnalyzerForPackageDependencies { - settings.SKIP_CLANG_STATIC_ANALYZER = "YES" + settings[.SKIP_CLANG_STATIC_ANALYZER] = "YES" } } - settings.SWIFT_ACTIVE_COMPILATION_CONDITIONS + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS] .lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("SWIFT_PACKAGE") } - settings.GCC_PREPROCESSOR_DEFINITIONS = ["$(inherited)", "SWIFT_PACKAGE"] - settings.CLANG_ENABLE_OBJC_ARC = "YES" - settings.KEEP_PRIVATE_EXTERNS = "NO" + settings[.GCC_PREPROCESSOR_DEFINITIONS] = ["$(inherited)", "SWIFT_PACKAGE"] + settings[.CLANG_ENABLE_OBJC_ARC] = "YES" + settings[.KEEP_PRIVATE_EXTERNS] = "NO" // We currently deliberately do not support Swift ObjC interface headers. - settings.SWIFT_INSTALL_OBJC_HEADER = "NO" - settings.SWIFT_OBJC_INTERFACE_HEADER_NAME = "" - settings.OTHER_LDRFLAGS = [] + settings[.SWIFT_INSTALL_OBJC_HEADER] = "NO" + settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME] = "" + settings[.OTHER_LDRFLAGS] = [] // Packages use the SwiftPM workspace's cache directory as a compiler working directory to maximize module // sharing. - settings.COMPILER_WORKING_DIRECTORY = "$(WORKSPACE_DIR)" + settings[.COMPILER_WORKING_DIRECTORY] = "$(WORKSPACE_DIR)" // Hook to customize the project-wide build settings. self.delegate.configureProjectBuildSettings(&settings) for (platform, platformOptions) in self.package.sdkOptions(delegate: self.delegate) { - let pifPlatform = SwiftBuild.PIF.BuildSettings.Platform(from: platform) + let pifPlatform = ProjectModel.BuildSettings.Platform(from: platform) settings.platformSpecificSettings[pifPlatform]![.SPECIALIZATION_SDK_OPTIONS]! .append(contentsOf: platformOptions) } let deviceFamilyIDs: Set = self.delegate.deviceFamilyIDs() - settings.TARGETED_DEVICE_FAMILY = deviceFamilyIDs.sorted().map { String($0) }.joined(separator: ",") + settings[.TARGETED_DEVICE_FAMILY] = deviceFamilyIDs.sorted().map { String($0) }.joined(separator: ",") // This will add the XCTest related search paths automatically, // including the Swift overlays. - settings.ENABLE_TESTING_SEARCH_PATHS = "YES" + settings[.ENABLE_TESTING_SEARCH_PATHS] = "YES" // Disable signing for all the things since there is no way // to configure signing information in packages right now. - settings.ENTITLEMENTS_REQUIRED = "NO" - settings.CODE_SIGNING_REQUIRED = "NO" - settings.CODE_SIGN_IDENTITY = "" + settings[.ENTITLEMENTS_REQUIRED] = "NO" + settings[.CODE_SIGNING_REQUIRED] = "NO" + settings[.CODE_SIGN_IDENTITY] = "" // If in a workspace that's set to build packages for arm64e, pass that along to Swift Build. if self.delegate.shouldiOSPackagesBuildForARM64e { @@ -534,26 +552,24 @@ public final class PIFPackageBuilder { // Add the build settings that are specific to debug builds, and set those as the "Debug" configuration. var debugSettings = settings - debugSettings.COPY_PHASE_STRIP = "NO" - debugSettings.DEBUG_INFORMATION_FORMAT = "dwarf" - debugSettings.ENABLE_NS_ASSERTIONS = "YES" - debugSettings.GCC_OPTIMIZATION_LEVEL = "0" - debugSettings.ONLY_ACTIVE_ARCH = "YES" - debugSettings.SWIFT_OPTIMIZATION_LEVEL = "-Onone" - debugSettings.ENABLE_TESTABILITY = "YES" - debugSettings - .SWIFT_ACTIVE_COMPILATION_CONDITIONS = (settings.SWIFT_ACTIVE_COMPILATION_CONDITIONS ?? []) + ["DEBUG"] - debugSettings - .GCC_PREPROCESSOR_DEFINITIONS = (settings.GCC_PREPROCESSOR_DEFINITIONS ?? ["$(inherited)"]) + ["DEBUG=1"] - project.pif.addBuildConfig(name: "Debug", settings: debugSettings) + debugSettings[.COPY_PHASE_STRIP] = "NO" + debugSettings[.DEBUG_INFORMATION_FORMAT] = "dwarf" + debugSettings[.ENABLE_NS_ASSERTIONS] = "YES" + debugSettings[.GCC_OPTIMIZATION_LEVEL] = "0" + debugSettings[.ONLY_ACTIVE_ARCH] = "YES" + debugSettings[.SWIFT_OPTIMIZATION_LEVEL] = "-Onone" + debugSettings[.ENABLE_TESTABILITY] = "YES" + debugSettings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS, default: []].append(contentsOf: ["DEBUG"]) + debugSettings[.GCC_PREPROCESSOR_DEFINITIONS, default: ["$(inherited)"]].append(contentsOf: ["DEBUG=1"]) + builder.project.addBuildConfig { id in BuildConfig(id: id, name: "Debug", settings: debugSettings) } // Add the build settings that are specific to release builds, and set those as the "Release" configuration. var releaseSettings = settings - releaseSettings.COPY_PHASE_STRIP = "YES" - releaseSettings.DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym" - releaseSettings.GCC_OPTIMIZATION_LEVEL = "s" - releaseSettings.SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule" - project.pif.addBuildConfig(name: "Release", settings: releaseSettings) + releaseSettings[.COPY_PHASE_STRIP] = "YES" + releaseSettings[.DEBUG_INFORMATION_FORMAT] = "dwarf-with-dsym" + releaseSettings[.GCC_OPTIMIZATION_LEVEL] = "s" + releaseSettings[.SWIFT_OPTIMIZATION_LEVEL] = "-Owholemodule" + builder.project.addBuildConfig { id in BuildConfig(id: id, name: "Release", settings: releaseSettings) } } private enum SourceModuleType { @@ -587,15 +603,15 @@ public final class PIFPackageBuilder { // MARK: - Helpers -extension PIFPackageBuilder.ModuleOrProduct { +extension PackagePIFBuilder.ModuleOrProduct { public init( - type moduleOrProductType: PIFPackageBuilder.ModuleOrProductType, + type moduleOrProductType: PackagePIFBuilder.ModuleOrProductType, name: String, moduleName: String?, - pifTarget: SwiftBuild.PIF.BaseTarget?, + pifTarget: ProjectModel.BaseTarget?, indexableFileURLs: [SourceControlURL] = [], headerFiles: Set = [], - linkedPackageBinaries: [PIFPackageBuilder.LinkedPackageBinary] = [], + linkedPackageBinaries: [PackagePIFBuilder.LinkedPackageBinary] = [], swiftLanguageVersion: String? = nil, declaredPlatforms: [PackageModel.Platform]? = [], deploymentTargets: [PackageModel.Platform: String?]? = [:] @@ -617,7 +633,7 @@ enum PIFBuildingError: Error { case packageExtensionFeatureNotEnabled } -extension PIFPackageBuilder.LinkedPackageBinary { +extension PackagePIFBuilder.LinkedPackageBinary { init?(module: ResolvedModule, package: ResolvedPackage) { let packageName = package.manifest.displayName diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index cfdda27048c..89d7d1f7b16 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -26,7 +26,8 @@ import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage #if canImport(SwiftBuild) -import enum SwiftBuild.PIF + +import enum SwiftBuild.ProjectModel /// Extension to create PIF **modules** for a given package. extension PackagePIFProjectBuilder { @@ -36,15 +37,20 @@ extension PackagePIFProjectBuilder { precondition(pluginModule.type == .plugin) // Create an executable PIF target in order to get specialization. - let pluginPifTarget = try self.pif.addTargetThrowing( - id: pluginModule.pifTargetGUID(), - productType: .executable, - name: pluginModule.name, - productName: pluginModule.name - ) - log(.debug, "created \(type(of: pluginPifTarget)) '\(pluginPifTarget.id)' with name '\(pluginPifTarget.name)'") + let pluginTargetKeyPath = try self.project.addTarget { _ in + ProjectModel.Target( + id: pluginModule.pifTargetGUID(), + productType: .executable, + name: pluginModule.name, + productName: pluginModule.name + ) + } + do { + let pluginTarget = self.project[keyPath: pluginTargetKeyPath] + log(.debug, "Created \(pluginTarget.productType) '\(pluginTarget.id)' with name '\(pluginTarget.name)'") + } - var buildSettings: SwiftBuild.PIF.BuildSettings = self.package.underlying.packageBaseBuildSettings + var buildSettings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings // Add the dependencies. pluginModule.recursivelyTraverseDependencies { dependency in @@ -53,7 +59,7 @@ extension PackagePIFProjectBuilder { // This assertion is temporarily disabled since we may see targets from // _other_ packages, but this should be resolved; see rdar://95467710. /* assert(moduleDependency.packageName == self.package.name) */ - + let dependencyPlatformFilters = packageConditions .toPlatformFilter(toolsVersion: self.package.manifest.toolsVersion) @@ -66,25 +72,26 @@ extension PackagePIFProjectBuilder { .productRepresentingDependencyOfBuildPlugin(in: moduleProducts) if let productDependency { - pluginPifTarget.addDependency( + self.project[keyPath: pluginTargetKeyPath].common.addDependency( on: productDependency.pifTargetGUID(), platformFilters: dependencyPlatformFilters ) - log(.debug, ".. added dependency on product '\(productDependency.pifTargetGUID())'") + log(.debug, indent: 1, "Added dependency on product '\(productDependency.pifTargetGUID())'") } else { log( .debug, - ".. could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID())'" + indent: 1, + "Could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID())'" ) } case .library, .systemModule, .test, .binary, .plugin, .macro: let dependencyGUID = moduleDependency.pifTargetGUID() - pluginPifTarget.addDependency( + self.project[keyPath: pluginTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: dependencyPlatformFilters ) - log(.debug, ".. added dependency on target '\(dependencyGUID)'") + log(.debug, indent: 1, "Added dependency on target '\(dependencyGUID)'") } case .product(let productDependency, let packageConditions): @@ -101,26 +108,30 @@ extension PackagePIFProjectBuilder { let dependencyPlatformFilters = packageConditions .toPlatformFilter(toolsVersion: self.package.manifest.toolsVersion) - pluginPifTarget.addDependency( + self.project[keyPath: pluginTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: dependencyPlatformFilters ) - log(.debug, ".. added dependency on product '\(dependencyGUID)'") + log(.debug, indent: 1, "Added dependency on product '\(dependencyGUID)'") } } } // Any dependencies of plugin targets need to be built for the host. - buildSettings.SUPPORTED_PLATFORMS = ["$(HOST_PLATFORM)"] + buildSettings[.SUPPORTED_PLATFORMS] = ["$(HOST_PLATFORM)"] - pluginPifTarget.addBuildConfig(name: "Debug", settings: buildSettings) - pluginPifTarget.addBuildConfig(name: "Release", settings: buildSettings) + self.project[keyPath: pluginTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Debug", settings: buildSettings) + } + self.project[keyPath: pluginTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Release", settings: buildSettings) + } - let pluginModuleMetadata = PIFPackageBuilder.ModuleOrProduct( + let pluginModuleMetadata = PackagePIFBuilder.ModuleOrProduct( type: .plugin, name: pluginModule.name, moduleName: pluginModule.name, - pifTarget: pluginPifTarget, + pifTarget: .target(self.project[keyPath: pluginTargetKeyPath]), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: [], @@ -168,13 +179,13 @@ extension PackagePIFProjectBuilder { dynamicLibraryVariant.isDynamicLibraryVariant = true self.builtModulesAndProducts.append(dynamicLibraryVariant) - let pifTarget = staticLibrary.pifTarget as? SwiftBuild.PIF.Target - let dynamicPifTarget = dynamicLibraryVariant.pifTarget as? SwiftBuild.PIF.Target - - guard let pifTarget, let dynamicPifTarget else { + guard let pifTarget = staticLibrary.pifTarget, + let pifTargetKeyPath = self.project.findTarget(id: pifTarget.id), + let dynamicPifTarget = dynamicLibraryVariant.pifTarget + else { fatalError("Could not assign dynamic PIF target") } - pifTarget.dynamicTargetVariant = dynamicPifTarget + self.project[keyPath: pifTargetKeyPath].dynamicTargetVariantId = dynamicPifTarget.id } } @@ -220,12 +231,12 @@ extension PackagePIFProjectBuilder { targetSuffix: TargetGUIDSuffix? = nil, addBuildToolPluginCommands: Bool = true, inputResourceBundleName: String? = nil - ) throws -> (PIFPackageBuilder.ModuleOrProduct, resourceBundleName: String?) { + ) throws -> (PackagePIFBuilder.ModuleOrProduct, resourceBundleName: String?) { precondition(sourceModule.isSourceModule) let pifTargetName: String let executableName: String - let productType: SwiftBuild.PIF.Target.ProductType + let productType: ProjectModel.Target.ProductType switch desiredModuleType { case .dynamicLibrary: @@ -261,22 +272,28 @@ extension PackagePIFProjectBuilder { true } - let sourceModulePifTarget = try self.pif.addTargetThrowing( - id: sourceModule.pifTargetGUID(suffix: targetSuffix), - productType: productType, - name: sourceModule.name, - productName: pifTargetName, - approvedByUser: approvedByUser - ) - log( - .debug, - "created \(type(of: sourceModulePifTarget)) '\(sourceModulePifTarget.id)' of type '\(sourceModulePifTarget.productType.asString)' with name '\(sourceModulePifTarget.name)' and product name '\(sourceModulePifTarget.productName)'" - ) + let sourceModuleTargetKeyPath = try self.project.addTarget { _ in + ProjectModel.Target( + id: sourceModule.pifTargetGUID(suffix: targetSuffix), + productType: productType, + name: sourceModule.name, + productName: pifTargetName, + approvedByUser: approvedByUser + ) + } + do { + let sourceModuleTarget = self.project[keyPath: sourceModuleTargetKeyPath] + log( + .debug, + "Created \(sourceModuleTarget.productType) '\(sourceModuleTarget.id)' " + + "with name '\(sourceModuleTarget.name)' and product name '\(sourceModuleTarget.productName)'" + ) + } // Deal with any generated source files or resource files. let (generatedSourceFiles, generatedResourceFiles) = computePluginGeneratedFiles( module: sourceModule, - pifTarget: sourceModulePifTarget, + targetKeyPath: sourceModuleTargetKeyPath, addBuildToolPluginCommands: false ) @@ -287,7 +304,7 @@ extension PackagePIFProjectBuilder { if resourceBundleName == nil && desiredModuleType != .executable && desiredModuleType != .macro { let (result, resourceBundle) = try addResourceBundle( for: sourceModule, - pifTarget: sourceModulePifTarget, + targetKeyPath: sourceModuleTargetKeyPath, generatedResourceFiles: generatedResourceFiles ) if let resourceBundle { self.builtModulesAndProducts.append(resourceBundle) } @@ -307,31 +324,32 @@ extension PackagePIFProjectBuilder { } // Find the PIF target for the resource bundle, if any. Otherwise fall back to the module. - let resourceBundlePifTarget = self - .resourceBundleTarget(forModuleName: sourceModule.name) ?? sourceModulePifTarget + let resourceBundleTargetKeyPath = self.resourceBundleTargetKeyPath( + forModuleName: sourceModule.name + ) ?? sourceModuleTargetKeyPath // Add build tool commands to the resource bundle target. if desiredModuleType != .executable && desiredModuleType != .macro && addBuildToolPluginCommands { addBuildToolCommands( module: sourceModule, - sourceModulePifTarget: sourceModulePifTarget, - resourceBundlePifTarget: resourceBundlePifTarget, + sourceModuleTargetKeyPath: sourceModuleTargetKeyPath, + resourceBundleTargetKeyPath: resourceBundleTargetKeyPath, sourceFilePaths: generatedSourceFiles, resourceFilePaths: generatedResourceFiles ) } // Create a set of build settings that will be imparted to any target that depends on this one. - var impartedSettings = SwiftBuild.PIF.BuildSettings() + var impartedSettings = BuildSettings() // Configure the target-wide build settings. The details depend on the kind of product we're building. - var settings: SwiftBuild.PIF.BuildSettings = self.package.underlying.packageBaseBuildSettings + var settings: BuildSettings = self.package.underlying.packageBaseBuildSettings if shouldGenerateBundleAccessor { - settings.GENERATE_RESOURCE_ACCESSORS = "YES" + settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" } if shouldGenerateEmbedInCodeAccessor { - settings.GENERATE_EMBED_IN_CODE_ACCESSORS = "YES" + settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "YES" } // Generate a module map file, if needed. @@ -341,8 +359,8 @@ extension PackagePIFProjectBuilder { if sourceModule.usesSwift && desiredModuleType != .macro { // Generate ObjC compatibility header for Swift library targets. - settings.SWIFT_OBJC_INTERFACE_HEADER_DIR = generatedModuleMapDir - settings.SWIFT_OBJC_INTERFACE_HEADER_NAME = "\(sourceModule.name)-Swift.h" + settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR] = generatedModuleMapDir + settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME] = "\(sourceModule.name)-Swift.h" moduleMapFileContents = """ module \(sourceModule.c99name) { @@ -353,7 +371,7 @@ extension PackagePIFProjectBuilder { moduleMapFile = "\(generatedModuleMapDir)/\(sourceModule.name).modulemap" // We only need to impart this to C clients. - impartedSettings.OTHER_CFLAGS = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] } else if sourceModule.moduleMapFileRelativePath == nil { // Otherwise, this is a C library module and we generate a modulemap if one is already not provided. if case .umbrellaHeader(let path) = sourceModule.moduleMapType { @@ -376,8 +394,8 @@ extension PackagePIFProjectBuilder { if moduleMapFileContents.hasContent { // Pass the path of the module map up to all direct and indirect clients. moduleMapFile = "\(generatedModuleMapDir)/\(sourceModule.name).modulemap" - impartedSettings.OTHER_CFLAGS = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] - impartedSettings.OTHER_SWIFT_FLAGS = ["-Xcc", "-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] } } @@ -393,28 +411,28 @@ extension PackagePIFProjectBuilder { delegate: pifBuilder.delegate ) } else { - settings.TARGET_NAME = sourceModule.name - settings.PRODUCT_NAME = "$(TARGET_NAME)" - settings.PRODUCT_MODULE_NAME = sourceModule.c99name - settings.PRODUCT_BUNDLE_IDENTIFIER = "\(self.package.identity).\(sourceModule.name)" + settings[.TARGET_NAME] = sourceModule.name + settings[.PRODUCT_NAME] = "$(TARGET_NAME)" + settings[.PRODUCT_MODULE_NAME] = sourceModule.c99name + settings[.PRODUCT_BUNDLE_IDENTIFIER] = "\(self.package.identity).\(sourceModule.name)" .spm_mangledToBundleIdentifier() - settings.EXECUTABLE_NAME = executableName - settings.CLANG_ENABLE_MODULES = "YES" - settings.GENERATE_MASTER_OBJECT_FILE = "NO" - settings.STRIP_INSTALLED_PRODUCT = "NO" + settings[.EXECUTABLE_NAME] = executableName + settings[.CLANG_ENABLE_MODULES] = "YES" + settings[.GENERATE_MASTER_OBJECT_FILE] = "NO" + settings[.STRIP_INSTALLED_PRODUCT] = "NO" // Macros build as executables, so they need slightly different // build settings from other module types which build a "*.o". if desiredModuleType == .macro { - settings.MACH_O_TYPE = "mh_execute" + settings[.MACH_O_TYPE] = "mh_execute" } else { - settings.MACH_O_TYPE = "mh_object" + settings[.MACH_O_TYPE] = "mh_object" // Disable code coverage linker flags since we're producing .o files. // Otherwise, we will run into duplicated symbols when there are more than one targets that produce .o // as their product. - settings.CLANG_COVERAGE_MAPPING_LINKER_ARGS = "NO" + settings[.CLANG_COVERAGE_MAPPING_LINKER_ARGS] = "NO" } - settings.SWIFT_PACKAGE_NAME = sourceModule.packageName + settings[.SWIFT_PACKAGE_NAME] = sourceModule.packageName if desiredModuleType == .executable { // Tell the Swift compiler to produce an alternate entry point rather than the standard `_main` entry @@ -422,16 +440,16 @@ extension PackagePIFProjectBuilder { // so that we can link one or more testable executable modules together into a single test bundle. // This allows the test bundle to treat the executable as if it were any regular library module, // and will have access to all symbols except the main entry point its. - settings.OTHER_SWIFT_FLAGS.lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { + settings[.OTHER_SWIFT_FLAGS].lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append(contentsOf: ["-Xfrontend", "-entry-point-function-name"]) $0.append(contentsOf: ["-Xfrontend", "\(sourceModule.c99name)_main"]) } // We have to give each target a unique name. - settings.TARGET_NAME = sourceModule.name + targetSuffix.description(forName: sourceModule.name) + settings[.TARGET_NAME] = sourceModule.name + targetSuffix.description(forName: sourceModule.name) // Redirect the built executable into a separate directory so it won't conflict with the real one. - settings.TARGET_BUILD_DIR = "$(TARGET_BUILD_DIR)/ExecutableModules" + settings[.TARGET_BUILD_DIR] = "$(TARGET_BUILD_DIR)/ExecutableModules" // Don't install the Swift module of the testable side-built artifact, lest it conflict with the regular // one. @@ -439,45 +457,45 @@ extension PackagePIFProjectBuilder { // different in the Swift module // (the actual runtime artifact is of course very different, and that's why we're building a separate // testable artifact). - settings.SWIFT_INSTALL_MODULE = "NO" + settings[.SWIFT_INSTALL_MODULE] = "NO" } if let aliases = sourceModule.moduleAliases { // Format each entry as "original_name=alias" let list = aliases.map { $0.0 + "=" + $0.1 } - settings.SWIFT_MODULE_ALIASES = list.isEmpty ? nil : list + settings[.SWIFT_MODULE_ALIASES] = list.isEmpty ? nil : list } // We mark in the PIF that we are intentionally not offering a dynamic target here, // so we can emit a diagnostic if it is being requested by Swift Build. if !self.shouldOfferDynamicTarget(sourceModule.name) { - settings.PACKAGE_TARGET_NAME_CONFLICTS_WITH_PRODUCT_NAME = "YES" + settings[.PACKAGE_TARGET_NAME_CONFLICTS_WITH_PRODUCT_NAME] = "YES" } // We are setting this instead of `LD_DYLIB_INSTALL_NAME` because `mh_object` files // don't actually have install names, so we should not pass an install name to the linker. - settings.TAPI_DYLIB_INSTALL_NAME = sourceModule.name + settings[.TAPI_DYLIB_INSTALL_NAME] = sourceModule.name } - settings.PACKAGE_RESOURCE_TARGET_KIND = "regular" - settings.MODULEMAP_FILE_CONTENTS = moduleMapFileContents - settings.MODULEMAP_PATH = moduleMapFile - settings.DEFINES_MODULE = "YES" + settings[.PACKAGE_RESOURCE_TARGET_KIND] = "regular" + settings[.MODULEMAP_FILE_CONTENTS] = moduleMapFileContents + settings[.MODULEMAP_PATH] = moduleMapFile + settings[.DEFINES_MODULE] = "YES" // Settings for text-based API. // Due to rdar://78331694 (Cannot use TAPI for packages in contexts where we need to code-sign (e.g. apps)) // we are only enabling TAPI in `configureSourceModuleBuildSettings`, if desired. - settings.SUPPORTS_TEXT_BASED_API = "NO" + settings[.SUPPORTS_TEXT_BASED_API] = "NO" // If the module includes C headers, we set up the HEADER_SEARCH_PATHS setting appropriately. if let includeDirAbsPath = sourceModule.includeDirAbsolutePath { // Let the target itself find its own headers. - settings.HEADER_SEARCH_PATHS = [includeDirAbsPath.pathString, "$(inherited)"] - log(.debug, ".. added '\(includeDirAbsPath)' to HEADER_SEARCH_PATHS") + settings[.HEADER_SEARCH_PATHS] = [includeDirAbsPath.pathString, "$(inherited)"] + log(.debug, indent: 1, "Added '\(includeDirAbsPath)' to HEADER_SEARCH_PATHS") // Also propagate this search path to all direct and indirect clients. - impartedSettings.HEADER_SEARCH_PATHS = [includeDirAbsPath.pathString, "$(inherited)"] - log(.debug, ".. added '\(includeDirAbsPath)' to imparted HEADER_SEARCH_PATHS") + impartedSettings[.HEADER_SEARCH_PATHS] = [includeDirAbsPath.pathString, "$(inherited)"] + log(.debug, indent: 1, "Added '\(includeDirAbsPath)' to imparted HEADER_SEARCH_PATHS") } // Additional settings for the linker. @@ -494,24 +512,29 @@ extension PackagePIFProjectBuilder { } else { baselineOTHER_LDFLAGS = ["$(inherited)"] } - impartedSettings.OTHER_LDFLAGS = (sourceModule.isCxx ? ["-lc++"] : []) + baselineOTHER_LDFLAGS - impartedSettings.OTHER_LDRFLAGS = [] - log(.debug, ".. added '\(String(describing: impartedSettings.OTHER_LDFLAGS))' to imparted OTHER_LDFLAGS") + impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + baselineOTHER_LDFLAGS + impartedSettings[.OTHER_LDRFLAGS] = [] + log( + .debug, + indent: 1, + "Added '\(String(describing: impartedSettings[.OTHER_LDFLAGS]))' to imparted OTHER_LDFLAGS" + ) // This should be only for dynamic targets, but that isn't possible today. // Improvement is tracked by rdar://77403529 (Only impart `PackageFrameworks` search paths to clients of dynamic // package targets and products). - impartedSettings.FRAMEWORK_SEARCH_PATHS = ["$(BUILT_PRODUCTS_DIR)/PackageFrameworks", "$(inherited)"] + impartedSettings[.FRAMEWORK_SEARCH_PATHS] = ["$(BUILT_PRODUCTS_DIR)/PackageFrameworks", "$(inherited)"] log( .debug, - ".. added '\(String(describing: impartedSettings.FRAMEWORK_SEARCH_PATHS))' to imparted FRAMEWORK_SEARCH_PATHS" + indent: 1, + "Added '\(String(describing: impartedSettings[.FRAMEWORK_SEARCH_PATHS]))' to imparted FRAMEWORK_SEARCH_PATHS" ) // Set the appropriate language versions. - settings.SWIFT_VERSION = sourceModule.packageSwiftLanguageVersion(manifest: packageManifest) - settings.GCC_C_LANGUAGE_STANDARD = sourceModule.cLanguageStandard - settings.CLANG_CXX_LANGUAGE_STANDARD = sourceModule.cxxLanguageStandard - settings.SWIFT_ENABLE_BARE_SLASH_REGEX = "NO" + settings[.SWIFT_VERSION] = sourceModule.packageSwiftLanguageVersion(manifest: packageManifest) + settings[.GCC_C_LANGUAGE_STANDARD] = sourceModule.cLanguageStandard + settings[.CLANG_CXX_LANGUAGE_STANDARD] = sourceModule.cxxLanguageStandard + settings[.SWIFT_ENABLE_BARE_SLASH_REGEX] = "NO" // Create a group for the target's source files. // @@ -520,11 +543,17 @@ extension PackagePIFProjectBuilder { // be a mismatch between the paths that the index service is using for Swift Build queries, // and what paths Swift Build uses in its build description; such a mismatch would result // in the index service failing to get compiler arguments for source files of the target. - let targetSourceFileGroup = self.pif.mainGroup.addGroup( - path: try! resolveSymlinks(sourceModule.sourceDirAbsolutePath).pathString, - pathBase: .absolute - ) - log(.debug, ".. added source file group '\(targetSourceFileGroup.path)'") + let targetSourceFileGroupKeyPath = self.project.mainGroup.addGroup { id in + ProjectModel.Group( + id: id, + path: try! resolveSymlinks(sourceModule.sourceDirAbsolutePath).pathString, + pathBase: .absolute + ) + } + do { + let targetSourceFileGroup = self.project.mainGroup[keyPath: targetSourceFileGroupKeyPath] + log(.debug, indent: 1, "Added source file group '\(targetSourceFileGroup.path)'") + } // Add a source file reference for each of the source files, // and also an indexable-file URL for each one. @@ -532,16 +561,19 @@ extension PackagePIFProjectBuilder { // Symlinks should be resolved externally. var indexableFileURLs: [SourceControlURL] = [] for sourcePath in sourceModule.sourceFileRelativePaths { - sourceModulePifTarget.addSourceFile( - ref: targetSourceFileGroup.addFileReference(path: sourcePath.pathString, pathBase: .groupDir) - ) - log(.debug, ".. .. added source file '\(sourcePath)'") + let sourceFileRef = self.project.mainGroup[keyPath: targetSourceFileGroupKeyPath].addFileReference { id in + FileReference(id: id, path: sourcePath.pathString, pathBase: .groupDir) + } + self.project[keyPath: sourceModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: sourceFileRef) + } indexableFileURLs.append( SourceControlURL(fileURLWithPath: sourceModule.sourceDirAbsolutePath.appending(sourcePath)) ) + log(.debug, indent: 2, "Added source file '\(sourcePath)'") } for resource in sourceModule.resources { - log(.debug, ".. .. added resource file '\(resource.path)'") + log(.debug, indent: 2, "Added resource file '\(resource.path)'") indexableFileURLs.append(SourceControlURL(fileURLWithPath: resource.path)) } @@ -549,24 +581,27 @@ extension PackagePIFProjectBuilder { // Add any additional source files emitted by custom build commands. for path in generatedSourceFiles { - sourceModulePifTarget.addSourceFile( - ref: targetSourceFileGroup.addFileReference(path: path.pathString, pathBase: .absolute) - ) - log(.debug, ".. .. added generated source file '\(path)'") + let sourceFileRef = self.project.mainGroup[keyPath: targetSourceFileGroupKeyPath].addFileReference { id in + FileReference(id: id, path: path.pathString, pathBase: .absolute) + } + self.project[keyPath: sourceModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: sourceFileRef) + } + log(.debug, indent: 2, "Added generated source file '\(path)'") } if let resourceBundle = resourceBundleName { - impartedSettings.EMBED_PACKAGE_RESOURCE_BUNDLE_NAMES = ["$(inherited)", resourceBundle] - settings.PACKAGE_RESOURCE_BUNDLE_NAME = resourceBundle - settings.COREML_CODEGEN_LANGUAGE = sourceModule.usesSwift ? "Swift" : "Objective-C" - settings.COREML_COMPILER_CONTAINER = "swift-package" + impartedSettings[.EMBED_PACKAGE_RESOURCE_BUNDLE_NAMES] = ["$(inherited)", resourceBundle] + settings[.PACKAGE_RESOURCE_BUNDLE_NAME] = resourceBundle + settings[.COREML_CODEGEN_LANGUAGE] = sourceModule.usesSwift ? "Swift" : "Objective-C" + settings[.COREML_COMPILER_CONTAINER] = "swift-package" } if desiredModuleType == .macro { - settings.SWIFT_IMPLEMENTS_MACROS_FOR_MODULE_NAMES = [sourceModule.c99name] + settings[.SWIFT_IMPLEMENTS_MACROS_FOR_MODULE_NAMES] = [sourceModule.c99name] } if sourceModule.type == .macro { - settings.SKIP_BUILDING_DOCUMENTATION = "YES" + settings[.SKIP_BUILDING_DOCUMENTATION] = "YES" } // Handle the target's dependencies (but only link against them if needed). @@ -577,7 +612,7 @@ extension PackagePIFProjectBuilder { // This assertion is temporarily disabled since we may see targets from // _other_ packages, but this should be resolved; see rdar://95467710. /* assert(moduleDependency.packageName == self.package.name) */ - + let dependencyPlatformFilters = packageConditions .toPlatformFilter(toolsVersion: self.package.manifest.toolsVersion) @@ -589,56 +624,66 @@ extension PackagePIFProjectBuilder { if let product = moduleDependency .productRepresentingDependencyOfBuildPlugin(in: moduleMainProducts) { - sourceModulePifTarget.addDependency( + self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( on: product.pifTargetGUID(), platformFilters: dependencyPlatformFilters, linkProduct: false ) - log(.debug, ".. added dependency on product '\(product.pifTargetGUID)'") + log(.debug, indent: 1, "Added dependency on product '\(product.pifTargetGUID())'") } else { log( .debug, - ".. could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID())'" + indent: 1, + "Could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID())'" ) } case .binary: - let binaryReference = self.binaryGroup.addFileReference(path: moduleDependency.path.pathString) + let binaryReference = self.binaryGroup.addFileReference { id in + FileReference(id: id, path: moduleDependency.path.pathString) + } if shouldLinkProduct { - sourceModulePifTarget.addLibrary( - ref: binaryReference, - platformFilters: dependencyPlatformFilters, - codeSignOnCopy: true, - removeHeadersOnCopy: true - ) + self.project[keyPath: sourceModuleTargetKeyPath].addLibrary { id in + BuildFile( + id: id, + fileRef: binaryReference, + platformFilters: dependencyPlatformFilters, + codeSignOnCopy: true, + removeHeadersOnCopy: true + ) + } } else { // If we are producing a single ".o", don't link binaries since they // could be static which would cause them to become part of the ".o". - sourceModulePifTarget.addResourceFile( - ref: binaryReference, - platformFilters: dependencyPlatformFilters - ) + self.project[keyPath: sourceModuleTargetKeyPath].addResourceFile { id in + BuildFile( + id: id, + fileRef: binaryReference, + platformFilters: dependencyPlatformFilters + ) + } } - log(.debug, ".. added use of binary library '\(moduleDependency.path)'") + log(.debug, indent: 1, "Added use of binary library '\(moduleDependency.path)'") case .plugin: let dependencyGUID = moduleDependency.pifTargetGUID() - sourceModulePifTarget.addDependency( + self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: dependencyPlatformFilters, linkProduct: false ) - log(.debug, ".. added use of plugin target '\(dependencyGUID)'") + log(.debug, indent: 1, "Added use of plugin target '\(dependencyGUID)'") case .library, .test, .macro, .systemModule: - sourceModulePifTarget.addDependency( + self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( on: moduleDependency.pifTargetGUID(), platformFilters: dependencyPlatformFilters, linkProduct: shouldLinkProduct ) log( .debug, - ".. added \(shouldLinkProduct ? "linked " : "")dependency on target '\(moduleDependency.pifTargetGUID())'" + indent: 1, + "Added \(shouldLinkProduct ? "linked " : "")dependency on target '\(moduleDependency.pifTargetGUID())'" ) } @@ -656,14 +701,15 @@ extension PackagePIFProjectBuilder { .toPlatformFilter(toolsVersion: self.package.manifest.toolsVersion) let shouldLinkProduct = shouldLinkProduct && productDependency.isLinkable - sourceModulePifTarget.addDependency( + self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( on: productDependency.pifTargetGUID(), platformFilters: dependencyPlatformFilters, linkProduct: shouldLinkProduct ) log( .debug, - ".. added \(shouldLinkProduct ? "linked " : "")dependency on product '\(productDependency.pifTargetGUID)'" + indent: 1, + "Added \(shouldLinkProduct ? "linked " : "")dependency on product '\(productDependency.pifTargetGUID)'" ) } } @@ -683,16 +729,13 @@ extension PackagePIFProjectBuilder { // Apply target-specific build settings defined in the manifest. for (buildConfig, declarationsByPlatform) in allBuildSettings.targetSettings { for (platform, settingsByDeclaration) in declarationsByPlatform { - // A `nil` platform means that the declaration applies to *all* platforms. - let pifPlatform = platform.map { SwiftBuild.PIF.BuildSettings.Platform(from: $0) } - + // Note: A `nil` platform means that the declaration applies to *all* platforms. for (declaration, stringValues) in settingsByDeclaration { - let pifDeclaration = SwiftBuild.PIF.BuildSettings.Declaration(from: declaration) switch buildConfig { case .debug: - debugSettings.append(values: stringValues, to: pifDeclaration, platform: pifPlatform) + debugSettings.append(values: stringValues, to: declaration, platform: platform) case .release: - releaseSettings.append(values: stringValues, to: pifDeclaration, platform: pifPlatform) + releaseSettings.append(values: stringValues, to: declaration, platform: platform) } } } @@ -700,38 +743,41 @@ extension PackagePIFProjectBuilder { // Impart the linker flags. for (platform, settingsByDeclaration) in sourceModule.allBuildSettings.impartedSettings { - // A `nil` platform means that the declaration applies to *all* platforms. - let pifPlatform = platform.map { SwiftBuild.PIF.BuildSettings.Platform(from: $0) } - + // Note: A `nil` platform means that the declaration applies to *all* platforms. for (declaration, stringValues) in settingsByDeclaration { - let pifDeclaration = SwiftBuild.PIF.BuildSettings.Declaration(from: declaration) - impartedSettings.append(values: stringValues, to: pifDeclaration, platform: pifPlatform) + impartedSettings.append(values: stringValues, to: declaration, platform: platform) } } // Set the imparted settings, which are ones that clients (both direct and indirect ones) use. var debugImpartedSettings = impartedSettings - debugImpartedSettings.LD_RUNPATH_SEARCH_PATHS = + debugImpartedSettings[.LD_RUNPATH_SEARCH_PATHS] = ["$(BUILT_PRODUCTS_DIR)/PackageFrameworks"] + - (debugImpartedSettings.LD_RUNPATH_SEARCH_PATHS ?? ["$(inherited)"]) - - sourceModulePifTarget.addBuildConfig( - name: "Debug", - settings: debugSettings, - impartedBuildSettings: debugImpartedSettings - ) - sourceModulePifTarget.addBuildConfig( - name: "Release", - settings: releaseSettings, - impartedBuildSettings: impartedSettings - ) + (debugImpartedSettings[.LD_RUNPATH_SEARCH_PATHS] ?? ["$(inherited)"]) + + self.project[keyPath: sourceModuleTargetKeyPath].common.addBuildConfig { id in + BuildConfig( + id: id, + name: "Debug", + settings: debugSettings, + impartedBuildSettings: debugImpartedSettings + ) + } + self.project[keyPath: sourceModuleTargetKeyPath].common.addBuildConfig { id in + BuildConfig( + id: id, + name: "Release", + settings: releaseSettings, + impartedBuildSettings: impartedSettings + ) + } // Collect linked binaries. - let linkedPackageBinaries: [PIFPackageBuilder.LinkedPackageBinary] = sourceModule.dependencies.compactMap { - PIFPackageBuilder.LinkedPackageBinary(dependency: $0, package: self.package) + let linkedPackageBinaries: [PackagePIFBuilder.LinkedPackageBinary] = sourceModule.dependencies.compactMap { + PackagePIFBuilder.LinkedPackageBinary(dependency: $0, package: self.package) } - let productOrModuleType: PIFPackageBuilder.ModuleOrProductType = if desiredModuleType == .dynamicLibrary { + let productOrModuleType: PackagePIFBuilder.ModuleOrProductType = if desiredModuleType == .dynamicLibrary { pifBuilder.createDylibForDynamicProducts ? .dynamicLibrary : .framework } else if desiredModuleType == .macro { .macro @@ -739,11 +785,11 @@ extension PackagePIFProjectBuilder { .module } - let moduleOrProduct = PIFPackageBuilder.ModuleOrProduct( + let moduleOrProduct = PackagePIFBuilder.ModuleOrProduct( type: productOrModuleType, name: sourceModule.name, moduleName: sourceModule.c99name, - pifTarget: sourceModulePifTarget, + pifTarget: .target(self.project[keyPath: sourceModuleTargetKeyPath]), indexableFileURLs: indexableFileURLs, headerFiles: headerFiles, linkedPackageBinaries: linkedPackageBinaries, @@ -759,52 +805,61 @@ extension PackagePIFProjectBuilder { mutating func makeSystemLibraryModule(_ resolvedSystemLibrary: PackageGraph.ResolvedModule) throws { precondition(resolvedSystemLibrary.type == .systemModule) - let systemLibrary = resolvedSystemLibrary.underlying as! SystemLibraryModule // Create an aggregate PIF target (which doesn't have an actual product). - let systemLibraryPifTarget = self.pif.addAggregateTarget( - id: resolvedSystemLibrary.pifTargetGUID(), - name: resolvedSystemLibrary.name - ) - log( - .debug, - "created \(type(of: systemLibraryPifTarget)) '\(systemLibraryPifTarget.id)' with name '\(systemLibraryPifTarget.name)'" - ) + let systemLibraryTargetKeyPath = try self.project.addAggregateTarget { _ in + ProjectModel.AggregateTarget( + id: resolvedSystemLibrary.pifTargetGUID(), + name: resolvedSystemLibrary.name + ) + } + do { + let systemLibraryTarget = self.project[keyPath: systemLibraryTargetKeyPath] + log( + .debug, + "Created \(type(of: systemLibraryTarget)) '\(systemLibraryTarget.id)' with name '\(systemLibraryTarget.name)'" + ) + } - let settings: SwiftBuild.PIF.BuildSettings = self.package.underlying.packageBaseBuildSettings + let settings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings let pkgConfig = try systemLibrary.pkgConfig( package: self.package, observabilityScope: pifBuilder.observabilityScope ) // Impart the header search path to all direct and indirect clients. - var impartedSettings = SwiftBuild.PIF.BuildSettings() - impartedSettings.OTHER_CFLAGS = ["-fmodule-map-file=\(systemLibrary.modulemapFileAbsolutePath)"] + pkgConfig - .cFlags.prepending("$(inherited)") - impartedSettings.OTHER_LDFLAGS = pkgConfig.libs.prepending("$(inherited)") - impartedSettings.OTHER_LDRFLAGS = [] - impartedSettings.OTHER_SWIFT_FLAGS = ["-Xcc"] + impartedSettings.OTHER_CFLAGS! - log(.debug, ".. added '\(systemLibrary.path.pathString)' to imparted HEADER_SEARCH_PATHS") - - systemLibraryPifTarget.addBuildConfig( - name: "Debug", - settings: settings, - impartedBuildSettings: impartedSettings - ) - systemLibraryPifTarget.addBuildConfig( - name: "Release", - settings: settings, - impartedBuildSettings: impartedSettings - ) - + var impartedSettings = ProjectModel.BuildSettings() + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(systemLibrary.modulemapFileAbsolutePath)"] + + pkgConfig.cFlags.prepending("$(inherited)") + impartedSettings[.OTHER_LDFLAGS] = pkgConfig.libs.prepending("$(inherited)") + impartedSettings[.OTHER_LDRFLAGS] = [] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc"] + impartedSettings[.OTHER_CFLAGS]! + log(.debug, indent: 1, "Added '\(systemLibrary.path.pathString)' to imparted HEADER_SEARCH_PATHS") + + self.project[keyPath: systemLibraryTargetKeyPath].common.addBuildConfig { id in + BuildConfig( + id: id, + name: "Debug", + settings: settings, + impartedBuildSettings: impartedSettings + ) + } + self.project[keyPath: systemLibraryTargetKeyPath].common.addBuildConfig { id in + BuildConfig( + id: id, + name: "Release", + settings: settings, + impartedBuildSettings: impartedSettings + ) + } // FIXME: Should we also impart linkage? - let systemModule = PIFPackageBuilder.ModuleOrProduct( + let systemModule = PackagePIFBuilder.ModuleOrProduct( type: .module, name: resolvedSystemLibrary.name, moduleName: resolvedSystemLibrary.c99name, - pifTarget: systemLibraryPifTarget, + pifTarget: .aggregate(self.project[keyPath: systemLibraryTargetKeyPath]), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: [], @@ -815,4 +870,5 @@ extension PackagePIFProjectBuilder { self.builtModulesAndProducts.append(systemModule) } } + #endif diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index 2d83afd0bc5..e44f2b099ff 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -28,7 +28,8 @@ import struct PackageGraph.ResolvedPackage import struct PackageGraph.ResolvedProduct #if canImport(SwiftBuild) -import enum SwiftBuild.PIF + +import enum SwiftBuild.ProjectModel /// Extension to create PIF **products** for a given package. extension PackagePIFProjectBuilder { @@ -49,15 +50,15 @@ extension PackagePIFProjectBuilder { } // Determine the kind of PIF target *product type* to create for the package product. - let pifProductType: SwiftBuild.PIF.Target.ProductType - let moduleOrProductType: PIFPackageBuilder.ModuleOrProductType - let synthesizedResourceGeneratingPluginInvocationResults: [PIFPackageBuilder.BuildToolPluginInvocationResult] = + let pifProductType: ProjectModel.Target.ProductType + let moduleOrProductType: PackagePIFBuilder.ModuleOrProductType + let synthesizedResourceGeneratingPluginInvocationResults: [PackagePIFBuilder.BuildToolPluginInvocationResult] = [] if product.type == .executable { if let customPIFProductType = pifBuilder.delegate.customProductType(forExecutable: product.underlying) { pifProductType = customPIFProductType - moduleOrProductType = PIFPackageBuilder.ModuleOrProductType(from: customPIFProductType) + moduleOrProductType = PackagePIFBuilder.ModuleOrProductType(from: customPIFProductType) } else { // No custom type provider. Current behavior is to fall back on regular executable. pifProductType = .executable @@ -71,16 +72,22 @@ extension PackagePIFProjectBuilder { } // It's not a library product, so create a regular PIF target of the appropriate product type. - let mainModulePifTarget = try self.pif.addTargetThrowing( - id: product.pifTargetGUID(), - productType: pifProductType, - name: product.name, - productName: product.name - ) - log( - .debug, - "created \(type(of: mainModulePifTarget)) '\(mainModulePifTarget.id)' of type '\(mainModulePifTarget.productType.asString)' with name '\(mainModulePifTarget.name)' and product name '\(mainModulePifTarget.productName)'" - ) + let mainModuleTargetKeyPath = try self.project.addTarget { _ in + ProjectModel.Target( + id: product.pifTargetGUID(), + productType: pifProductType, + name: product.name, + productName: product.name + ) + } + do { + let mainModuleTarget = self.project[keyPath: mainModuleTargetKeyPath] + log( + .debug, + "Created \(mainModuleTarget.productType)) '\(mainModuleTarget.id)' " + + "with name '\(mainModuleTarget.name)' and product name '\(mainModuleTarget.productName)'" + ) + } // We're currently *not* handling other module targets (and SwiftPM should never return them) for // a main-module product but, for diagnostic purposes, we warn about any that we do come across. @@ -92,7 +99,7 @@ extension PackagePIFProjectBuilder { // Deal with any generated source files or resource files. let (generatedSourceFiles, pluginGeneratedResourceFiles) = computePluginGeneratedFiles( module: mainModule, - pifTarget: mainModulePifTarget, + targetKeyPath: mainModuleTargetKeyPath, addBuildToolPluginCommands: pifProductType == .application ) if mainModule.resources.hasContent || pluginGeneratedResourceFiles.hasContent { @@ -101,87 +108,114 @@ extension PackagePIFProjectBuilder { // Configure the target-wide build settings. The details depend on the kind of product we're building, // but are in general the ones that are suitable for end-product artifacts such as executables and test bundles. - var settings: SwiftBuild.PIF.BuildSettings = package.underlying.packageBaseBuildSettings - settings.TARGET_NAME = product.name - settings.PACKAGE_RESOURCE_TARGET_KIND = "regular" - settings.PRODUCT_NAME = "$(TARGET_NAME)" - settings.PRODUCT_MODULE_NAME = product.c99name - settings.PRODUCT_BUNDLE_IDENTIFIER = "\(self.package.identity).\(product.name)" + var settings: ProjectModel.BuildSettings = package.underlying.packageBaseBuildSettings + settings[.TARGET_NAME] = product.name + settings[.PACKAGE_RESOURCE_TARGET_KIND] = "regular" + settings[.PRODUCT_NAME] = "$(TARGET_NAME)" + settings[.PRODUCT_MODULE_NAME] = product.c99name + settings[.PRODUCT_BUNDLE_IDENTIFIER] = "\(self.package.identity).\(product.name)" .spm_mangledToBundleIdentifier() - settings.EXECUTABLE_NAME = product.name - settings.CLANG_ENABLE_MODULES = "YES" - settings.SWIFT_PACKAGE_NAME = mainModule.packageName + settings[.EXECUTABLE_NAME] = product.name + settings[.CLANG_ENABLE_MODULES] = "YES" + settings[.SWIFT_PACKAGE_NAME] = mainModule.packageName if mainModule.type == .test { // FIXME: we shouldn't always include both the deep and shallow bundle paths here, but for that we'll need rdar://31867023 - settings.LD_RUNPATH_SEARCH_PATHS = ["@loader_path/Frameworks", "@loader_path/../Frameworks", "$(inherited)"] - settings.GENERATE_INFOPLIST_FILE = "YES" - settings.SKIP_INSTALL = "NO" - settings.SWIFT_ACTIVE_COMPILATION_CONDITIONS.lazilyInitialize { ["$(inherited)"] } + settings[.LD_RUNPATH_SEARCH_PATHS] = [ + "@loader_path/Frameworks", + "@loader_path/../Frameworks", + "$(inherited)", + ] + settings[.GENERATE_INFOPLIST_FILE] = "YES" + settings[.SKIP_INSTALL] = "NO" + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS].lazilyInitialize { ["$(inherited)"] } } else if mainModule.type == .executable { // Setup install path for executables if it's in root of a pure Swift package. if pifBuilder.delegate.hostsOnlyPackages && pifBuilder.delegate.isRootPackage { - settings.SKIP_INSTALL = "NO" - settings.INSTALL_PATH = "/usr/local/bin" - settings.LD_RUNPATH_SEARCH_PATHS = ["$(inherited)", "@executable_path/../lib"] + settings[.SKIP_INSTALL] = "NO" + settings[.INSTALL_PATH] = "/usr/local/bin" + settings[.LD_RUNPATH_SEARCH_PATHS] = ["$(inherited)", "@executable_path/../lib"] } } let mainTargetDeploymentTargets = mainModule.deploymentTargets(using: pifBuilder.delegate) - settings.MACOSX_DEPLOYMENT_TARGET = mainTargetDeploymentTargets[.macOS] ?? nil - settings.IPHONEOS_DEPLOYMENT_TARGET = mainTargetDeploymentTargets[.iOS] ?? nil + settings[.MACOSX_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.macOS] ?? nil + settings[.IPHONEOS_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.iOS] ?? nil if let deploymentTarget_macCatalyst = mainTargetDeploymentTargets[.macCatalyst] { settings .platformSpecificSettings[.macCatalyst]![.IPHONEOS_DEPLOYMENT_TARGET] = [deploymentTarget_macCatalyst] } - settings.TVOS_DEPLOYMENT_TARGET = mainTargetDeploymentTargets[.tvOS] ?? nil - settings.WATCHOS_DEPLOYMENT_TARGET = mainTargetDeploymentTargets[.watchOS] ?? nil - settings.DRIVERKIT_DEPLOYMENT_TARGET = mainTargetDeploymentTargets[.driverKit] ?? nil - settings.XROS_DEPLOYMENT_TARGET = mainTargetDeploymentTargets[.visionOS] ?? nil + settings[.TVOS_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.tvOS] ?? nil + settings[.WATCHOS_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.watchOS] ?? nil + settings[.DRIVERKIT_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.driverKit] ?? nil + settings[.XROS_DEPLOYMENT_TARGET] = mainTargetDeploymentTargets[.visionOS] ?? nil // If the main module includes C headers, then we need to set up the HEADER_SEARCH_PATHS setting appropriately. if let includeDirAbsolutePath = mainModule.includeDirAbsolutePath { // Let the main module itself find its own headers. - settings.HEADER_SEARCH_PATHS = [includeDirAbsolutePath.pathString, "$(inherited)"] - log(.debug, ".. added '\(includeDirAbsolutePath)' to HEADER_SEARCH_PATHS") + settings[.HEADER_SEARCH_PATHS] = [includeDirAbsolutePath.pathString, "$(inherited)"] + log(.debug, indent: 1, "Added '\(includeDirAbsolutePath)' to HEADER_SEARCH_PATHS") } // Set the appropriate language versions. - settings.SWIFT_VERSION = mainModule.packageSwiftLanguageVersion(manifest: packageManifest) - settings.GCC_C_LANGUAGE_STANDARD = mainModule.cLanguageStandard - settings.CLANG_CXX_LANGUAGE_STANDARD = mainModule.cxxLanguageStandard - settings.SWIFT_ENABLE_BARE_SLASH_REGEX = "NO" + settings[.SWIFT_VERSION] = mainModule.packageSwiftLanguageVersion(manifest: packageManifest) + settings[.GCC_C_LANGUAGE_STANDARD] = mainModule.cLanguageStandard + settings[.CLANG_CXX_LANGUAGE_STANDARD] = mainModule.cxxLanguageStandard + settings[.SWIFT_ENABLE_BARE_SLASH_REGEX] = "NO" // Create a group for the source files of the main module // For now we use an absolute path for it, but we should really make it // container-relative, since it's always inside the package directory. - let mainTargetSourceFileGroup = self.pif.mainGroup.addGroup( - path: mainModule.sourceDirAbsolutePath.pathString, - pathBase: .absolute - ) - log(.debug, ".. added source file group '\(mainTargetSourceFileGroup.path)'") + let mainTargetSourceFileGroupKeyPath = self.project.mainGroup.addGroup { id in + ProjectModel.Group( + id: id, + path: mainModule.sourceDirAbsolutePath.pathString, + pathBase: .absolute + ) + } + do { + let mainTargetSourceFileGroup = self.project.mainGroup[keyPath: mainTargetSourceFileGroupKeyPath] + log(.debug, indent: 1, "Added source file group '\(mainTargetSourceFileGroup.path)'") + } // Add a source file reference for each of the source files, and also an indexable-file URL for each one. // Note that the indexer requires them to have any symbolic links resolved. var indexableFileURLs: [SourceControlURL] = [] for sourcePath in mainModule.sourceFileRelativePaths { - mainModulePifTarget.addSourceFile( - ref: mainTargetSourceFileGroup.addFileReference(path: sourcePath.pathString, pathBase: .groupDir) + let sourceFileRef = self.project.mainGroup[keyPath: mainTargetSourceFileGroupKeyPath] + .addFileReference { id in + FileReference( + id: id, + path: sourcePath.pathString, + pathBase: .groupDir + ) + } + self.project[keyPath: mainModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: sourceFileRef) + } + log(.debug, indent: 2, "Added source file '\(sourcePath)'") + indexableFileURLs.append( + SourceControlURL(fileURLWithPath: mainModule.sourceDirAbsolutePath.appending(sourcePath)) ) - log(.debug, ".. .. added source file '\(sourcePath)'") - indexableFileURLs - .append(SourceControlURL(fileURLWithPath: mainModule.sourceDirAbsolutePath.appending(sourcePath))) } let headerFiles = Set(mainModule.headerFileAbsolutePaths) // Add any additional source files emitted by custom build commands. for path in generatedSourceFiles { - mainModulePifTarget.addSourceFile( - ref: mainTargetSourceFileGroup.addFileReference(path: path.pathString, pathBase: .absolute) - ) - log(.debug, ".. .. added generated source file '\(path)'") + let sourceFileRef = self.project.mainGroup[keyPath: mainTargetSourceFileGroupKeyPath] + .addFileReference { id in + FileReference( + id: id, + path: path.pathString, + pathBase: .absolute + ) + } + self.project[keyPath: mainModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: sourceFileRef) + } + log(.debug, indent: 2, "Added generated source file '\(path)'") } // Add any additional resource files emitted by synthesized build commands @@ -190,7 +224,7 @@ extension PackagePIFProjectBuilder { generatedResourceFiles.append( contentsOf: addBuildToolCommands( from: synthesizedResourceGeneratingPluginInvocationResults, - pifTarget: mainModulePifTarget, + targetKeyPath: mainModuleTargetKeyPath, addBuildToolPluginCommands: pifProductType == .application ) ) @@ -202,76 +236,80 @@ extension PackagePIFProjectBuilder { if pifProductType == .application { let result = processResources( for: mainModule, - sourceModulePifTarget: mainModulePifTarget, + sourceModuleTargetKeyPath: mainModuleTargetKeyPath, // For application products we embed the resources directly into the PIF target. - resourceBundlePifTarget: nil, + resourceBundleTargetKeyPath: nil, generatedResourceFiles: generatedResourceFiles ) if result.shouldGenerateBundleAccessor { - settings.GENERATE_RESOURCE_ACCESSORS = "YES" + settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" } if result.shouldGenerateEmbedInCodeAccessor { - settings.GENERATE_EMBED_IN_CODE_ACCESSORS = "YES" + settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "YES" } - // FIXME: We should also adjust the generated module bundle glue so that `Bundle.module` is a synonym for `Bundle.main` in this case. } else { let (result, resourceBundle) = try addResourceBundle( for: mainModule, - pifTarget: mainModulePifTarget, + targetKeyPath: mainModuleTargetKeyPath, generatedResourceFiles: generatedResourceFiles ) if let resourceBundle { self.builtModulesAndProducts.append(resourceBundle) } if let resourceBundle = result.bundleName { // Associate the resource bundle with the target. - settings.PACKAGE_RESOURCE_BUNDLE_NAME = resourceBundle + settings[.PACKAGE_RESOURCE_BUNDLE_NAME] = resourceBundle if result.shouldGenerateBundleAccessor { - settings.GENERATE_RESOURCE_ACCESSORS = "YES" + settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" } if result.shouldGenerateEmbedInCodeAccessor { - settings.GENERATE_EMBED_IN_CODE_ACCESSORS = "YES" + settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "YES" } // If it's a kind of product that can contain resources, we also add a use of it. - let ref = self.pif.mainGroup - .addFileReference(path: "$(CONFIGURATION_BUILD_DIR)/\(resourceBundle).bundle") + let resourceBundleRef = self.project.mainGroup.addFileReference { id in + FileReference(id: id, path: "$(CONFIGURATION_BUILD_DIR)/\(resourceBundle).bundle") + } if pifProductType == .bundle || pifProductType == .unitTest { - settings.COREML_CODEGEN_LANGUAGE = mainModule.usesSwift ? "Swift" : "Objective-C" - settings.COREML_COMPILER_CONTAINER = "swift-package" + settings[.COREML_CODEGEN_LANGUAGE] = mainModule.usesSwift ? "Swift" : "Objective-C" + settings[.COREML_COMPILER_CONTAINER] = "swift-package" - mainModulePifTarget.addResourceFile(ref: ref) - log(.debug, ".. added use of resource bundle '\(ref.path)'") + self.project[keyPath: mainModuleTargetKeyPath].addResourceFile { id in + BuildFile(id: id, fileRef: resourceBundleRef) + } + log(.debug, indent: 2, "Added use of resource bundle '\(resourceBundleRef.path)'") } else { log( .debug, - ".. ignored resource bundle '\(ref.path)' for main module of type \(type(of: mainModule))" + indent: 2, + "Ignored resource bundle '\(resourceBundleRef.path)' for main module of type \(type(of: mainModule))" ) } // Add build tool commands to the resource bundle target. - let resourceBundlePifTarget = self - .resourceBundleTarget(forModuleName: mainModule.name) ?? mainModulePifTarget + let mainResourceBundleTargetKeyPath = self.resourceBundleTargetKeyPath(forModuleName: mainModule.name) + let resourceBundleTargetKeyPath = mainResourceBundleTargetKeyPath ?? mainModuleTargetKeyPath + addBuildToolCommands( module: mainModule, - sourceModulePifTarget: mainModulePifTarget, - resourceBundlePifTarget: resourceBundlePifTarget, + sourceModuleTargetKeyPath: mainModuleTargetKeyPath, + resourceBundleTargetKeyPath: resourceBundleTargetKeyPath, sourceFilePaths: generatedSourceFiles, resourceFilePaths: generatedResourceFiles ) } else { // Generated resources always trigger the creation of a bundle accessor. - settings.GENERATE_RESOURCE_ACCESSORS = "YES" - settings.GENERATE_EMBED_IN_CODE_ACCESSORS = "NO" + settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" + settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "NO" - // If we did not create a resource bundle target, we still need to add build tool commands for any - // generated files. + // If we did not create a resource bundle target, + // we still need to add build tool commands for any generated files. addBuildToolCommands( module: mainModule, - sourceModulePifTarget: mainModulePifTarget, - resourceBundlePifTarget: mainModulePifTarget, + sourceModuleTargetKeyPath: mainModuleTargetKeyPath, + resourceBundleTargetKeyPath: mainModuleTargetKeyPath, sourceFilePaths: generatedSourceFiles, resourceFilePaths: generatedResourceFiles ) @@ -288,39 +326,44 @@ extension PackagePIFProjectBuilder { switch moduleDependency.type { case .binary: - let binaryReference = self.binaryGroup.addFileReference(path: moduleDependency.path.pathString) - mainModulePifTarget.addLibrary( - ref: binaryReference, - platformFilters: packageConditions - .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), - codeSignOnCopy: true, - removeHeadersOnCopy: true - ) - log(.debug, ".. added use of binary library '\(moduleDependency.path)'") + let binaryFileRef = self.binaryGroup.addFileReference { id in + FileReference(id: id, path: moduleDependency.path.pathString) + } + let toolsVersion = self.package.manifest.toolsVersion + self.project[keyPath: mainModuleTargetKeyPath].addLibrary { id in + BuildFile( + id: id, + fileRef: binaryFileRef, + platformFilters: packageConditions.toPlatformFilter(toolsVersion: toolsVersion), + codeSignOnCopy: true, + removeHeadersOnCopy: true + ) + } + log(.debug, indent: 1, "Added use of binary library '\(moduleDependency.path)'") case .plugin: let dependencyId = moduleDependency.pifTargetGUID() - mainModulePifTarget.addDependency( + self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: dependencyId, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: false ) - log(.debug, ".. added use of plugin target '\(dependencyId)'") + log(.debug, indent: 1, "Added use of plugin target '\(dependencyId)'") case .macro: let dependencyId = moduleDependency.pifTargetGUID() - mainModulePifTarget.addDependency( + self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: dependencyId, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: false ) - log(.debug, ".. added dependency on product '\(dependencyId)'") + log(.debug, indent: 1, "Added dependency on product '\(dependencyId)'") // Link with a testable version of the macro if appropriate. if product.type == .test { - mainModulePifTarget.addDependency( + self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: moduleDependency.pifTargetGUID(suffix: .testable), platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), @@ -328,7 +371,8 @@ extension PackagePIFProjectBuilder { ) log( .debug, - ".. added linked dependency on target '\(moduleDependency.pifTargetGUID(suffix: .testable))'" + indent: 1, + "Added linked dependency on target '\(moduleDependency.pifTargetGUID(suffix: .testable))'" ) // FIXME: Manually propagate product dependencies of macros but the build system should really handle this. @@ -340,7 +384,7 @@ extension PackagePIFProjectBuilder { productDependency, with: packageConditions, isLinkable: isLinkable, - pifTarget: mainModulePifTarget, + targetKeyPath: mainModuleTargetKeyPath, settings: &settings ) case .module: @@ -355,32 +399,32 @@ extension PackagePIFProjectBuilder { let productDependency = modulesGraph.allProducts.only { $0.name == moduleDependency.name } if let productDependency { let productDependencyGUID = productDependency.pifTargetGUID() - mainModulePifTarget.addDependency( + self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: productDependencyGUID, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: false ) - log(.debug, ".. added dependency on product '\(productDependencyGUID)'") + log(.debug, indent: 1, "Added dependency on product '\(productDependencyGUID)'") } // If we're linking against an executable and the tools version is new enough, // we also link against a testable version of the executable. if product.type == .test, self.package.manifest.toolsVersion >= .v5_5 { let moduleDependencyGUID = moduleDependency.pifTargetGUID(suffix: .testable) - mainModulePifTarget.addDependency( + self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: moduleDependencyGUID, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: true ) - log(.debug, ".. added linked dependency on target '\(moduleDependencyGUID)'") + log(.debug, indent: 1, "Added linked dependency on target '\(moduleDependencyGUID)'") } case .library, .systemModule, .test: let shouldLinkProduct = moduleDependency.type != .systemModule let dependencyGUID = moduleDependency.pifTargetGUID() - mainModulePifTarget.addDependency( + self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), @@ -388,7 +432,8 @@ extension PackagePIFProjectBuilder { ) log( .debug, - ".. added \(shouldLinkProduct ? "linked " : "")dependency on target '\(dependencyGUID)'" + indent: 1, + "Added \(shouldLinkProduct ? "linked " : "")dependency on target '\(dependencyGUID)'" ) } @@ -398,7 +443,7 @@ extension PackagePIFProjectBuilder { productDependency, with: packageConditions, isLinkable: isLinkable, - pifTarget: mainModulePifTarget, + targetKeyPath: mainModuleTargetKeyPath, settings: &settings ) } @@ -407,38 +452,40 @@ extension PackagePIFProjectBuilder { // Until this point the build settings for the target have been the same between debug and release // configurations. // The custom manifest settings might cause them to diverge. - var debugSettings: SwiftBuild.PIF.BuildSettings = settings - var releaseSettings: SwiftBuild.PIF.BuildSettings = settings + var debugSettings: ProjectModel.BuildSettings = settings + var releaseSettings: ProjectModel.BuildSettings = settings // Apply target-specific build settings defined in the manifest. for (buildConfig, declarationsByPlatform) in mainModule.allBuildSettings.targetSettings { for (platform, declarations) in declarationsByPlatform { // A `nil` platform means that the declaration applies to *all* platforms. - let pifPlatform = platform.map { SwiftBuild.PIF.BuildSettings.Platform(from: $0) } for (declaration, stringValues) in declarations { - let pifDeclaration = SwiftBuild.PIF.BuildSettings.Declaration(from: declaration) switch buildConfig { case .debug: - debugSettings.append(values: stringValues, to: pifDeclaration, platform: pifPlatform) + debugSettings.append(values: stringValues, to: declaration, platform: platform) case .release: - releaseSettings.append(values: stringValues, to: pifDeclaration, platform: pifPlatform) + releaseSettings.append(values: stringValues, to: declaration, platform: platform) } } } } - mainModulePifTarget.addBuildConfig(name: "Debug", settings: debugSettings) - mainModulePifTarget.addBuildConfig(name: "Release", settings: releaseSettings) + self.project[keyPath: mainModuleTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Debug", settings: debugSettings) + } + self.project[keyPath: mainModuleTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Release", settings: releaseSettings) + } // Collect linked binaries. - let linkedPackageBinaries: [PIFPackageBuilder.LinkedPackageBinary] = mainModule.dependencies.compactMap { - PIFPackageBuilder.LinkedPackageBinary(dependency: $0, package: self.package) + let linkedPackageBinaries: [PackagePIFBuilder.LinkedPackageBinary] = mainModule.dependencies.compactMap { + PackagePIFBuilder.LinkedPackageBinary(dependency: $0, package: self.package) } - let moduleOrProduct = PIFPackageBuilder.ModuleOrProduct( + let moduleOrProduct = PackagePIFBuilder.ModuleOrProduct( type: moduleOrProductType, name: product.name, moduleName: product.c99name, - pifTarget: mainModulePifTarget, + pifTarget: .target(self.project[keyPath: mainModuleTargetKeyPath]), indexableFileURLs: indexableFileURLs, headerFiles: headerFiles, linkedPackageBinaries: linkedPackageBinaries, @@ -449,12 +496,12 @@ extension PackagePIFProjectBuilder { self.builtModulesAndProducts.append(moduleOrProduct) } - private func handleProduct( + private mutating func handleProduct( _ product: PackageGraph.ResolvedProduct, with packageConditions: [PackageModel.PackageCondition], isLinkable: Bool, - pifTarget: SwiftBuild.PIF.Target, - settings: inout SwiftBuild.PIF.BuildSettings + targetKeyPath: WritableKeyPath, + settings: inout ProjectModel.BuildSettings ) { // Do not add a dependency for binary-only executable products since they are not part of the build. if product.isBinaryOnlyExecutableProduct { @@ -463,14 +510,15 @@ extension PackagePIFProjectBuilder { if !pifBuilder.delegate.shouldSuppressProductDependency(product: product.underlying, buildSettings: &settings) { let shouldLinkProduct = isLinkable - pifTarget.addDependency( + self.project[keyPath: targetKeyPath].common.addDependency( on: product.pifTargetGUID(), platformFilters: packageConditions.toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: shouldLinkProduct ) log( .debug, - ".. added \(shouldLinkProduct ? "linked " : "")dependency on product '\(product.pifTargetGUID()))'" + indent: 1, + "Added \(shouldLinkProduct ? "linked " : "")dependency on product '\(product.pifTargetGUID()))'" ) } } @@ -503,14 +551,13 @@ extension PackagePIFProjectBuilder { dynamicLibraryVariant.isDynamicLibraryVariant = true self.builtModulesAndProducts.append(dynamicLibraryVariant) - let pifTarget = library.pifTarget as? SwiftBuild.PIF.Target - let dynamicPifTarget = dynamicLibraryVariant.pifTarget as? SwiftBuild.PIF.Target - - if let pifTarget, let dynamicPifTarget { - pifTarget.dynamicTargetVariant = dynamicPifTarget - } else { - assertionFailure("Could not assign dynamic PIF target") + guard let pifTarget = library.pifTarget, + let pifTargetKeyPath = self.project.findTarget(id: pifTarget.id), + let dynamicPifTarget = dynamicLibraryVariant.pifTarget + else { + fatalError("Could not assign dynamic PIF target") } + self.project[keyPath: pifTargetKeyPath].dynamicTargetVariantId = dynamicPifTarget.id } } @@ -520,19 +567,19 @@ extension PackagePIFProjectBuilder { /// all SwiftPM library products are represented by two PIF targets: /// one of the "native" manifestation that gets linked into the client, /// and another for a dynamic framework specifically for use by the development-time features. - private func buildLibraryProduct( + private mutating func buildLibraryProduct( _ product: PackageGraph.ResolvedProduct, type desiredProductType: ProductType.LibraryType, targetSuffix: TargetGUIDSuffix? = nil, embedResources: Bool - ) throws -> PIFPackageBuilder.ModuleOrProduct { + ) throws -> PackagePIFBuilder.ModuleOrProduct { precondition(product.type.isLibrary) // FIXME: Cleanup this mess with let pifTargetProductName: String let executableName: String - let productType: SwiftBuild.PIF.Target.ProductType + let productType: ProjectModel.Target.ProductType if desiredProductType == .dynamic { if pifBuilder.createDylibForDynamicProducts { @@ -540,8 +587,8 @@ extension PackagePIFProjectBuilder { executableName = pifTargetProductName productType = .dynamicLibrary } else { - // If a product is explicitly declared dynamic, we preserve its name, otherwise we will compute an - // automatic one. + // If a product is explicitly declared dynamic, we preserve its name, + // otherwise we will compute an automatic one. if product.libraryType == .dynamic { if let customExecutableName = pifBuilder.delegate .customExecutableName(product: product.underlying) @@ -551,7 +598,7 @@ extension PackagePIFProjectBuilder { executableName = product.name } } else { - executableName = PIFPackageBuilder.computePackageProductFrameworkName(productName: product.name) + executableName = PackagePIFBuilder.computePackageProductFrameworkName(productName: product.name) } pifTargetProductName = "\(executableName).framework" productType = .framework @@ -563,51 +610,77 @@ extension PackagePIFProjectBuilder { } // Create a special kind of PIF target that just "groups" a set of targets for clients to depend on. - // SwiftBuild will *not* produce a separate artifact for a package product, but will instead consider any - // dependency on - // the package product to be a dependency on the whole set of targets on which the package product depends. - let pifTarget = try self.pif.addTargetThrowing( - id: product.pifTargetGUID(suffix: targetSuffix), - productType: productType, - name: product.name, - productName: pifTargetProductName - ) - log( - .debug, - "created \(type(of: pifTarget)) '\(pifTarget.id)' of type '\(pifTarget.productType.asString)' with name '\(pifTarget.name)' and product name '\(pifTarget.productName)'" - ) + // Swift Build will *not* produce a separate artifact for a package product, but will instead consider any + // dependency on the package product to be a dependency on the whole set of targets + // on which the package product depends. + let librayUmbrellaTargetKeyPath = try self.project.addTarget { _ in + ProjectModel.Target( + id: product.pifTargetGUID(suffix: targetSuffix), + productType: productType, + name: product.name, + productName: pifTargetProductName + ) + } + do { + let librayTarget = self.project[keyPath: librayUmbrellaTargetKeyPath] + log( + .debug, + "Created target '\(librayTarget.id)' of type '\(librayTarget.productType)' with " + + "name '\(librayTarget.name)' and product name '\(librayTarget.productName)'" + ) + } // Add linked dependencies on the *targets* that comprise the product. for module in product.modules { // Binary targets are special in that they are just linked, not built. if let binaryTarget = module.underlying as? BinaryModule { - let binaryReference = self.binaryGroup.addFileReference(path: binaryTarget.artifactPath.pathString) - pifTarget.addLibrary(ref: binaryReference, codeSignOnCopy: true, removeHeadersOnCopy: true) - log(.debug, ".. added use of binary library '\(binaryTarget.artifactPath.pathString)'") + let binaryFileRef = self.binaryGroup.addFileReference { id in + FileReference(id: id, path: binaryTarget.artifactPath.pathString) + } + self.project[keyPath: librayUmbrellaTargetKeyPath].addLibrary { id in + BuildFile(id: id, fileRef: binaryFileRef, codeSignOnCopy: true, removeHeadersOnCopy: true) + } + log(.debug, indent: 1, "Added use of binary library '\(binaryTarget.artifactPath)'") continue } // We add these as linked dependencies; because the product type is `.packageProduct`, // SwiftBuild won't actually link them, but will instead impart linkage to any clients that // link against the package product. - pifTarget.addDependency(on: module.pifTargetGUID(), platformFilters: [], linkProduct: true) - log(.debug, ".. added linked dependency on target '\(module.pifTargetGUID())'") + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( + on: module.pifTargetGUID(), + platformFilters: [], + linkProduct: true + ) + log(.debug, indent: 1, "Added linked dependency on target '\(module.pifTargetGUID())'") } for module in product.modules where module.underlying.isSourceModule && module.resources.hasContent { - // FIXME: Find a way to determine whether a module has generated resources here so that we can embed resources into dynamic targets. - pifTarget.addDependency(on: pifTargetIdForResourceBundle(module.name), platformFilters: []) + // FIXME: Find a way to determine whether a module has generated resources + // here so that we can embed resources into dynamic targets. + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( + on: pifTargetIdForResourceBundle(module.name), + platformFilters: [] + ) - let filreRef = self.pif.mainGroup - .addFileReference(path: "$(CONFIGURATION_BUILD_DIR)/\(package.name)_\(module.name).bundle") + let packageName = self.package.name + let fileRef = self.project.mainGroup.addFileReference { id in + FileReference(id: id, path: "$(CONFIGURATION_BUILD_DIR)/\(packageName)_\(module.name).bundle") + } if embedResources { - pifTarget.addResourceFile(ref: filreRef) - log(.debug, ".. added use of resource bundle '\(filreRef.path)'") + self.project[keyPath: librayUmbrellaTargetKeyPath].addResourceFile { id in + BuildFile(id: id, fileRef: fileRef) + } + log(.debug, indent: 1, "Added use of resource bundle '\(fileRef.path)'") } else { - log(.debug, ".. ignored resource bundle '\(filreRef.path)' because resource embedding is disabled") + log( + .debug, + indent: 1, + "Ignored resource bundle '\(fileRef.path)' because resource embedding is disabled" + ) } } - var settings: SwiftBuild.PIF.BuildSettings = package.underlying.packageBaseBuildSettings + var settings: ProjectModel.BuildSettings = package.underlying.packageBaseBuildSettings // Add other build settings when we're building an actual dylib. if desiredProductType == .dynamic { @@ -621,22 +694,23 @@ extension PackagePIFProjectBuilder { installPath: installPath(for: product.underlying), delegate: pifBuilder.delegate ) - - pifTarget.addSourcesBuildPhase() + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addSourcesBuildPhase { id in + ProjectModel.SourcesBuildPhase(id: id) + } } // Additional configuration and files for this library product. pifBuilder.delegate.configureLibraryProduct( product: product.underlying, - pifTarget: pifTarget, - additionalFiles: self.additionalFilesGroup + target: librayUmbrellaTargetKeyPath, + additionalFiles: additionalFilesGroupKeyPath ) // If the given package is a root package or it is used via a branch/revision, we allow unsafe flags. - let implicitlyAllowAllUnsafeFlags = pifBuilder.delegate.isBranchOrRevisionBased || pifBuilder.delegate - .isUserManaged + let implicitlyAllowAllUnsafeFlags = pifBuilder.delegate.isBranchOrRevisionBased || + pifBuilder.delegate.isUserManaged let recordUsesUnsafeFlags = try !implicitlyAllowAllUnsafeFlags && product.usesUnsafeFlags - settings.USES_SWIFTPM_UNSAFE_FLAGS = recordUsesUnsafeFlags ? "YES" : "NO" + settings[.USES_SWIFTPM_UNSAFE_FLAGS] = recordUsesUnsafeFlags ? "YES" : "NO" // Handle the dependencies of the targets in the product // (and link against them, which in the case of a package product, really just means that clients should link @@ -647,34 +721,39 @@ extension PackagePIFProjectBuilder { // This assertion is temporarily disabled since we may see targets from // _other_ packages, but this should be resolved; see rdar://95467710. /* assert(moduleDependency.packageName == self.package.name) */ - + if moduleDependency.type == .systemModule { - log(.debug, ".. noted use of system module '\(moduleDependency.name)'") + log(.debug, indent: 1, "Noted use of system module '\(moduleDependency.name)'") return } if let binaryTarget = moduleDependency.underlying as? BinaryModule { - let binaryReference = self.binaryGroup.addFileReference(path: binaryTarget.path.pathString) - pifTarget.addLibrary( - ref: binaryReference, - platformFilters: packageConditions - .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), - codeSignOnCopy: true, - removeHeadersOnCopy: true - ) - log(.debug, ".. added use of binary library '\(binaryTarget.path)'") + let binaryFileRef = self.binaryGroup.addFileReference { id in + FileReference(id: id, path: binaryTarget.path.pathString) + } + let toolsVersion = package.manifest.toolsVersion + self.project[keyPath: librayUmbrellaTargetKeyPath].addLibrary { id in + BuildFile( + id: id, + fileRef: binaryFileRef, + platformFilters: packageConditions.toPlatformFilter(toolsVersion: toolsVersion), + codeSignOnCopy: true, + removeHeadersOnCopy: true + ) + } + log(.debug, indent: 1, "Added use of binary library '\(binaryTarget.path)'") return } if moduleDependency.type == .plugin { let dependencyId = moduleDependency.pifTargetGUID() - pifTarget.addDependency( + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( on: dependencyId, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: false ) - log(.debug, ".. added use of plugin target '\(dependencyId)'") + log(.debug, indent: 1, "Added use of plugin target '\(dependencyId)'") return } @@ -689,28 +768,29 @@ extension PackagePIFProjectBuilder { if let product = moduleDependency .productRepresentingDependencyOfBuildPlugin(in: mainModuleProducts) { - pifTarget.addDependency( + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( on: product.pifTargetGUID(), platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: false ) - log(.debug, ".. added dependency on product '\(product.pifTargetGUID())'") + log(.debug, indent: 1, "Added dependency on product '\(product.pifTargetGUID())'") return } else { log( .debug, - ".. could not find a build plugin product to depend on for target '\(product.pifTargetGUID()))'" + indent: 1, + "Could not find a build plugin product to depend on for target '\(product.pifTargetGUID()))'" ) } } - pifTarget.addDependency( + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( on: moduleDependency.pifTargetGUID(), platformFilters: packageConditions.toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: true ) - log(.debug, ".. added linked dependency on target '\(moduleDependency.pifTargetGUID()))'") + log(.debug, indent: 1, "Added linked dependency on target '\(moduleDependency.pifTargetGUID()))'") case .product(let productDependency, let packageConditions): // Do not add a dependency for binary-only executable products since they are not part of the build. @@ -723,7 +803,7 @@ extension PackagePIFProjectBuilder { buildSettings: &settings ) { let shouldLinkProduct = productDependency.isLinkable - pifTarget.addDependency( + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( on: productDependency.pifTargetGUID(), platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), @@ -731,7 +811,8 @@ extension PackagePIFProjectBuilder { ) log( .debug, - ".. added \(shouldLinkProduct ? "linked" : "") dependency on product '\(productDependency.pifTargetGUID()))'" + indent: 1, + "Added \(shouldLinkProduct ? "linked" : "") dependency on product '\(productDependency.pifTargetGUID()))'" ) } } @@ -754,29 +835,33 @@ extension PackagePIFProjectBuilder { let encoder = PropertyListEncoder() encoder.outputFormat = .xml let data = try encoder.encode(signatureData) - settings.PACKAGE_REGISTRY_SIGNATURE = String(data: data, encoding: .utf8) + settings[.PACKAGE_REGISTRY_SIGNATURE] = String(data: data, encoding: .utf8) } - pifTarget.addBuildConfig(name: "Debug", settings: settings) - pifTarget.addBuildConfig(name: "Release", settings: settings) + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Debug", settings: settings) + } + self.project[keyPath: librayUmbrellaTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Release", settings: settings) + } // Collect linked binaries. let linkedPackageBinaries = product.modules.compactMap { - PIFPackageBuilder.LinkedPackageBinary(module: $0, package: self.package) + PackagePIFBuilder.LinkedPackageBinary(module: $0, package: self.package) } - let moduleOrProductType: PIFPackageBuilder.ModuleOrProductType = switch product.libraryType { + let moduleOrProductType: PackagePIFBuilder.ModuleOrProductType = switch product.libraryType { case .dynamic: pifBuilder.createDylibForDynamicProducts ? .dynamicLibrary : .framework default: .staticArchive } - return PIFPackageBuilder.ModuleOrProduct( + return PackagePIFBuilder.ModuleOrProduct( type: moduleOrProductType, name: product.name, moduleName: product.c99name, - pifTarget: pifTarget, + pifTarget: .target(self.project[keyPath: librayUmbrellaTargetKeyPath]), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: linkedPackageBinaries, @@ -791,34 +876,42 @@ extension PackagePIFProjectBuilder { mutating func makeSystemLibraryProduct(_ product: PackageGraph.ResolvedProduct) throws { precondition(product.type == .library(.automatic)) - let pifTarget = try self.pif.addTargetThrowing( - id: product.pifTargetGUID(), - productType: .packageProduct, - name: product.name, - productName: product.name - ) - - log( - .debug, - "created \(type(of: pifTarget)) '\(pifTarget.id)' of type '\(pifTarget.productType.asString)' " + - "with name '\(pifTarget.name)' and product name '\(pifTarget.productName)'" - ) + let systemLibraryTargetKeyPath = try self.project.addTarget { _ in + ProjectModel.Target( + id: product.pifTargetGUID(), + productType: .packageProduct, + name: product.name, + productName: product.name + ) + } + do { + let systemLibraryTarget = self.project[keyPath: systemLibraryTargetKeyPath] + log( + .debug, + "Created target '\(systemLibraryTarget.id)' of type '\(systemLibraryTarget.productType)' " + + "with name '\(systemLibraryTarget.name)' and product name '\(systemLibraryTarget.productName)'" + ) + } let buildSettings = self.package.underlying.packageBaseBuildSettings - pifTarget.addBuildConfig(name: "Debug", settings: buildSettings) - pifTarget.addBuildConfig(name: "Release", settings: buildSettings) + self.project[keyPath: systemLibraryTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Debug", settings: buildSettings) + } + self.project[keyPath: systemLibraryTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Release", settings: buildSettings) + } - pifTarget.addDependency( + self.project[keyPath: systemLibraryTargetKeyPath].common.addDependency( on: product.systemModule!.pifTargetGUID(), platformFilters: [], linkProduct: false ) - let systemLibrary = PIFPackageBuilder.ModuleOrProduct( + let systemLibrary = PackagePIFBuilder.ModuleOrProduct( type: .staticArchive, name: product.name, moduleName: product.c99name, - pifTarget: pifTarget, + pifTarget: .target(self.project[keyPath: systemLibraryTargetKeyPath]), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: [], @@ -834,24 +927,33 @@ extension PackagePIFProjectBuilder { mutating func makePluginProduct(_ pluginProduct: PackageGraph.ResolvedProduct) throws { precondition(pluginProduct.type == .plugin) - let pluginPifTarget = self.pif.addAggregateTarget( - id: pluginProduct.pifTargetGUID(), - name: pluginProduct.name - ) - log(.debug, "created \(type(of: pluginPifTarget)) '\(pluginPifTarget.id)' with name '\(pluginPifTarget.name)'") + let pluginTargetKeyPath = try self.project.addAggregateTarget { _ in + ProjectModel.AggregateTarget( + id: pluginProduct.pifTargetGUID(), + name: pluginProduct.name + ) + } + do { + let pluginTarget = self.project[keyPath: pluginTargetKeyPath] + log(.debug, "Created aggregate target '\(pluginTarget.id)' with name '\(pluginTarget.name)'") + } - let buildSettings: SwiftBuild.PIF.BuildSettings = package.underlying.packageBaseBuildSettings - pluginPifTarget.addBuildConfig(name: "Debug", settings: buildSettings) - pluginPifTarget.addBuildConfig(name: "Release", settings: buildSettings) + let buildSettings: ProjectModel.BuildSettings = package.underlying.packageBaseBuildSettings + self.project[keyPath: pluginTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Debug", settings: buildSettings) + } + self.project[keyPath: pluginTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Release", settings: buildSettings) + } for pluginModule in pluginProduct.pluginModules! { - pluginPifTarget.addDependency( + self.project[keyPath: pluginTargetKeyPath].common.addDependency( on: pluginModule.pifTargetGUID(), platformFilters: [] ) } - let pluginType: PIFPackageBuilder.ModuleOrProductType = { + let pluginType: PackagePIFBuilder.ModuleOrProductType = { if let pluginTarget = pluginProduct.pluginModules!.only { switch pluginTarget.capability { case .buildTool: @@ -867,11 +969,11 @@ extension PackagePIFProjectBuilder { } }() - let pluginProductMetadata = PIFPackageBuilder.ModuleOrProduct( + let pluginProductMetadata = PackagePIFBuilder.ModuleOrProduct( type: pluginType, name: pluginProduct.name, moduleName: pluginProduct.c99name, - pifTarget: pluginPifTarget, + pifTarget: .aggregate(self.project[keyPath: pluginTargetKeyPath]), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: [], diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index e41074799ef..149d97e84ab 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -31,19 +31,31 @@ import struct PackageLoading.FileRuleDescription import struct PackageLoading.TargetSourcesBuilder #if canImport(SwiftBuild) -import enum SwiftBuild.PIF + +import struct SwiftBuild.Pair +import enum SwiftBuild.ProjectModel import struct SwiftBuild.SwiftBuildFileType /// Helper type to create PIF **project** and **targets** for a given package. struct PackagePIFProjectBuilder { - let pifBuilder: PIFPackageBuilder + let pifBuilder: PackagePIFBuilder let package: PackageGraph.ResolvedPackage let packageManifest: PackageModel.Manifest let modulesGraph: PackageGraph.ModulesGraph - let pif: SwiftBuild.PIF.Project - let binaryGroup: SwiftBuild.PIF.Group - let additionalFilesGroup: SwiftBuild.PIF.Group + var project: ProjectModel.Project + + let binaryGroupKeyPath: WritableKeyPath + var binaryGroup: ProjectModel.Group { + get { self.project.mainGroup[keyPath: self.binaryGroupKeyPath] } + set { self.project.mainGroup[keyPath: self.binaryGroupKeyPath] = newValue } + } + + let additionalFilesGroupKeyPath: WritableKeyPath + var additionalFilesGroup: ProjectModel.Group { + get { self.project.mainGroup[keyPath: self.additionalFilesGroupKeyPath] } + set { self.project.mainGroup[keyPath: self.additionalFilesGroupKeyPath] = newValue } + } let declaredPlatforms: [PackageModel.Platform]? let deploymentTargets: [PackageModel.Platform: String?] @@ -55,23 +67,25 @@ struct PackagePIFProjectBuilder { /// bit of information from processing the *products* to processing the *targets*. var mainModuleTargetNamesWithResources: Set = [] - var builtModulesAndProducts: [PIFPackageBuilder.ModuleOrProduct] + var builtModulesAndProducts: [PackagePIFBuilder.ModuleOrProduct] func log( _ severity: Diagnostic.Severity, + indent: Int = 0, _ message: String, sourceFile: StaticString = #fileID, sourceLine: UInt = #line ) { - self.pifBuilder.log(severity, message, sourceFile: sourceFile, sourceLine: sourceLine) + let levelPrefix = String(repeating: " ", count: indent) + self.pifBuilder.log(severity, levelPrefix + message, sourceFile: sourceFile, sourceLine: sourceLine) } - init(createForPackage package: PackageGraph.ResolvedPackage, builder: PIFPackageBuilder) { + init(createForPackage package: PackageGraph.ResolvedPackage, builder: PackagePIFBuilder) { // Create a PIF project using an identifier that's based on the normalized absolute path of the package. // We use the package manifest path as the project path, and the package path as the project's base source // directory. // FIXME: The PIF creation should ideally be done on a background thread. - let pifProject = SwiftBuild.PIF.Project( + var pifProject = ProjectModel.Project( id: "PACKAGE:\(package.identity)", path: package.manifest.path.pathString, projectDir: package.path.pathString, @@ -79,12 +93,22 @@ struct PackagePIFProjectBuilder { developmentRegion: package.manifest.defaultLocalization ) - let additionalFilesGroup = pifProject.mainGroup.addGroup( - path: "/", - pathBase: .absolute, - name: "AdditionalFiles" - ) - let binaryGroup = pifProject.mainGroup.addGroup(path: "/", pathBase: .absolute, name: "Binaries") + let additionalFilesGroupKeyPath = pifProject.mainGroup.addGroup { id in + ProjectModel.Group( + id: id, + path: "/", + pathBase: .absolute, + name: "AdditionalFiles" + ) + } + let binaryGroupKeyPath = pifProject.mainGroup.addGroup { id in + ProjectModel.Group( + id: id, + path: "/", + pathBase: .absolute, + name: "Binaries" + ) + } // Test modules have a higher minimum deployment target by default, // so we favor non-test modules as representative for the package's deployment target. @@ -119,9 +143,9 @@ struct PackagePIFProjectBuilder { self.package = package self.packageManifest = self.pifBuilder.packageManifest self.modulesGraph = self.pifBuilder.modulesGraph - self.pif = pifProject - self.binaryGroup = binaryGroup - self.additionalFilesGroup = additionalFilesGroup + self.project = pifProject + self.binaryGroupKeyPath = binaryGroupKeyPath + self.additionalFilesGroupKeyPath = additionalFilesGroupKeyPath self.declaredPlatforms = declaredPlatforms self.deploymentTargets = deploymentTargets self.dynamicLibraryProductNames = dynamicLibraryProductNames @@ -130,13 +154,13 @@ struct PackagePIFProjectBuilder { // MARK: - Handling Resources - func addResourceBundle( + mutating func addResourceBundle( for module: PackageGraph.ResolvedModule, - pifTarget: SwiftBuild.PIF.Target, + targetKeyPath: WritableKeyPath, generatedResourceFiles: [String] - ) throws -> (PIFPackageBuilder.EmbedResourcesResult, PIFPackageBuilder.ModuleOrProduct?) { + ) throws -> (PackagePIFBuilder.EmbedResourcesResult, PackagePIFBuilder.ModuleOrProduct?) { if module.resources.isEmpty && generatedResourceFiles.isEmpty { - return (PIFPackageBuilder.EmbedResourcesResult( + return (PackagePIFBuilder.EmbedResourcesResult( bundleName: nil, shouldGenerateBundleAccessor: false, shouldGenerateEmbedInCodeAccessor: false @@ -144,53 +168,71 @@ struct PackagePIFProjectBuilder { } let bundleName = self.resourceBundleName(forModuleName: module.name) - let resourcesTarget = try self.pif.addTargetThrowing( - id: self.pifTargetIdForResourceBundle(module.name), - productType: .bundle, - name: bundleName, - productName: bundleName - ) + let resourceBundleGUID = self.pifTargetIdForResourceBundle(module.name) + let resourcesTargetKeyPath = try self.project.addTarget { _ in + ProjectModel.Target( + id: resourceBundleGUID, + productType: .bundle, + name: bundleName, + productName: bundleName + ) + } + var resourcesTarget: ProjectModel.Target { self.project[keyPath: resourcesTargetKeyPath] } - pifTarget.addDependency(on: resourcesTarget.id, platformFilters: [], linkProduct: false) - self.log(.debug, ".. added dependency on resource target '\(resourcesTarget.id)'") + self.project[keyPath: targetKeyPath].common.addDependency( + on: resourcesTarget.id, + platformFilters: [], + linkProduct: false + ) + self.log(.debug, indent: 1, "Added dependency on resource target '\(resourcesTarget.id)'") for pluginModule in module.pluginsAppliedToModule { - resourcesTarget.addDependency(on: pluginModule.pifTargetGUID(), linkProduct: false) + self.project[keyPath: resourcesTargetKeyPath].common.addDependency( + on: pluginModule.pifTargetGUID(), + platformFilters: [], + linkProduct: false + ) } self.log( .debug, - ".. created \(type(of: resourcesTarget)) '\(resourcesTarget.id)' of type '\(resourcesTarget.productType.asString)' with name '\(resourcesTarget.name)' and product name '\(resourcesTarget.productName)'" + indent: 1, + "Created \(type(of: resourcesTarget)) '\(resourcesTarget.id)' of type '\(resourcesTarget.productType)' " + + "with name '\(resourcesTarget.name)' and product name '\(resourcesTarget.productName)'" ) - var settings: SwiftBuild.PIF.BuildSettings = self.package.underlying.packageBaseBuildSettings - settings.TARGET_NAME = bundleName - settings.PRODUCT_NAME = "$(TARGET_NAME)" - settings.PRODUCT_MODULE_NAME = bundleName - settings.PRODUCT_BUNDLE_IDENTIFIER = "\(self.package.identity).\(module.name).resources" + var settings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings + settings[.TARGET_NAME] = bundleName + settings[.PRODUCT_NAME] = "$(TARGET_NAME)" + settings[.PRODUCT_MODULE_NAME] = bundleName + settings[.PRODUCT_BUNDLE_IDENTIFIER] = "\(self.package.identity).\(module.name).resources" .spm_mangledToBundleIdentifier() - settings.EXECUTABLE_NAME = "" - settings.GENERATE_INFOPLIST_FILE = "YES" - settings.PACKAGE_RESOURCE_TARGET_KIND = "resource" + settings[.EXECUTABLE_NAME] = "" + settings[.GENERATE_INFOPLIST_FILE] = "YES" + settings[.PACKAGE_RESOURCE_TARGET_KIND] = "resource" - settings.COREML_COMPILER_CONTAINER = "swift-package" - settings.COREML_CODEGEN_LANGUAGE = "None" + settings[.COREML_COMPILER_CONTAINER] = "swift-package" + settings[.COREML_CODEGEN_LANGUAGE] = "None" - resourcesTarget.addBuildConfig(name: "Debug", settings: settings) - resourcesTarget.addBuildConfig(name: "Release", settings: settings) + self.project[keyPath: resourcesTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Debug", settings: settings) + } + self.project[keyPath: resourcesTargetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: "Release", settings: settings) + } let result = self.processResources( for: module, - sourceModulePifTarget: pifTarget, - resourceBundlePifTarget: resourcesTarget, + sourceModuleTargetKeyPath: targetKeyPath, + resourceBundleTargetKeyPath: resourcesTargetKeyPath, generatedResourceFiles: generatedResourceFiles ) - let resourceBundle = PIFPackageBuilder.ModuleOrProduct( + let resourceBundle = PackagePIFBuilder.ModuleOrProduct( type: .resourceBundle, name: bundleName, moduleName: bundleName, - pifTarget: resourcesTarget, + pifTarget: .target(resourcesTarget), indexableFileURLs: [], headerFiles: [], linkedPackageBinaries: [], @@ -202,28 +244,29 @@ struct PackagePIFProjectBuilder { return (result, resourceBundle) } - func processResources( + mutating func processResources( for module: PackageGraph.ResolvedModule, - sourceModulePifTarget: SwiftBuild.PIF.Target, - resourceBundlePifTarget: SwiftBuild.PIF.Target?, + sourceModuleTargetKeyPath: WritableKeyPath, + resourceBundleTargetKeyPath: WritableKeyPath?, generatedResourceFiles: [String] - ) -> PIFPackageBuilder.EmbedResourcesResult { + ) -> PackagePIFBuilder.EmbedResourcesResult { if module.resources.isEmpty && generatedResourceFiles.isEmpty { - return PIFPackageBuilder.EmbedResourcesResult( + return PackagePIFBuilder.EmbedResourcesResult( bundleName: nil, shouldGenerateBundleAccessor: false, shouldGenerateEmbedInCodeAccessor: false ) } - // If `resourceBundlePifTarget` is nil, we add resources to the `sourceModulePifTarget`. - let pifTargetForResources = resourceBundlePifTarget ?? sourceModulePifTarget - + // If resourceBundleTarget is nil, we add resources to the sourceModuleTarget instead. + let targetForResourcesKeyPath: WritableKeyPath = + resourceBundleTargetKeyPath ?? sourceModuleTargetKeyPath + // Generated resources get a default treatment for rule and localization. let generatedResources = generatedResourceFiles.compactMap { - PIFPackageBuilder.Resource(path: $0, rule: .process(localization: nil)) + PackagePIFBuilder.Resource(path: $0, rule: .process(localization: nil)) } - let resources = module.resources.map { PIFPackageBuilder.Resource($0) } + generatedResources + let resources = module.resources.map { PackagePIFBuilder.Resource($0) } + generatedResources let shouldGenerateBundleAccessor = resources.anySatisfy { $0.rule != .embedInCode } let shouldGenerateEmbedInCodeAccessor = resources.anySatisfy { $0.rule == .embedInCode } @@ -231,9 +274,9 @@ struct PackagePIFProjectBuilder { let resourcePath = resource.path // Add a file reference for the resource. We use an absolute path, as for all the other files, // but we should be able to optimize this later by making it group-relative. - let ref = self.pif.mainGroup.addFileReference( - path: resourcePath, pathBase: .absolute - ) + let ref = self.project.mainGroup.addFileReference { id in + ProjectModel.FileReference(id: id, path: resourcePath, pathBase: .absolute) + } // CoreData files should also be in the actual target because they // can end up generating code during the build. @@ -242,8 +285,10 @@ struct PackagePIFProjectBuilder { .contains { $0.fileTypes.contains(resourcePath.pathExtension) } if isCoreDataFile { - sourceModulePifTarget.addSourceFile(ref: ref) - self.log(.debug, ".. .. added core data resource as source file '\(resourcePath)'") + self.project[keyPath: sourceModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: ref) + } + self.log(.debug, indent: 2, "Added core data resource as source file '\(resourcePath)'") } // Core ML files need to be included in the source module as well, because there is code generation. @@ -251,49 +296,68 @@ struct PackagePIFProjectBuilder { let isCoreMLFile = coreMLFileTypes.contains { $0.fileTypes.contains(resourcePath.pathExtension) } if isCoreMLFile { - sourceModulePifTarget.addSourceFile(ref: ref, generatedCodeVisibility: .public) - self.log(.debug, ".. .. added coreml resource as source file '\(resourcePath)'") + self.project[keyPath: sourceModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: ref, generatedCodeVisibility: .public) + } + self.log(.debug, indent: 2, "Added coreml resource as source file '\(resourcePath)'") } // Metal source code needs to be added to the source build phase. let isMetalFile = SwiftBuild.SwiftBuildFileType.metal.fileTypes.contains(resourcePath.pathExtension) if isMetalFile { - pifTargetForResources.addSourceFile(ref: ref) + self.project[keyPath: targetForResourcesKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: ref) + } } else { // FIXME: Handle additional rules here (e.g. `.copy`). - pifTargetForResources.addResourceFile( - ref: ref, - platformFilters: [], - resourceRule: resource.rule == .embedInCode ? .embedInCode : .process - ) + self.project[keyPath: targetForResourcesKeyPath].addResourceFile { id in + BuildFile( + id: id, + fileRef: ref, + platformFilters: [], + resourceRule: resource.rule == .embedInCode ? .embedInCode : .process + ) + } } // Asset Catalogs need to be included in the sources modules for generated asset symbols. let isAssetCatalog = resourcePath.pathExtension == "xcassets" if isAssetCatalog { - sourceModulePifTarget.addSourceFile(ref: ref) - self.log(.debug, ".. .. added asset catalog as source file '\(resourcePath)'") + self.project[keyPath: sourceModuleTargetKeyPath].addSourceFile { id in + BuildFile(id: id, fileRef: ref) + } + self.log(.debug, indent: 2, "Added asset catalog as source file '\(resourcePath)'") } - self.log(.debug, ".. .. added resource file '\(resourcePath)'") + self.log(.debug, indent: 2, "Added resource file '\(resourcePath)'") } - return PIFPackageBuilder.EmbedResourcesResult( - bundleName: resourceBundlePifTarget?.name, + let resourceBundleTargetName: String? + if let resourceBundleTargetKeyPath { + let resourceBundleTarget = self.project[keyPath: resourceBundleTargetKeyPath] + resourceBundleTargetName = resourceBundleTarget.name + } else { + resourceBundleTargetName = nil + } + + return PackagePIFBuilder.EmbedResourcesResult( + bundleName: resourceBundleTargetName, shouldGenerateBundleAccessor: shouldGenerateBundleAccessor, shouldGenerateEmbedInCodeAccessor: shouldGenerateEmbedInCodeAccessor ) } - func resourceBundleTarget(forModuleName name: String) -> SwiftBuild.PIF.Target? { + func resourceBundleTargetKeyPath( + forModuleName name: String + ) -> WritableKeyPath? { let resourceBundleGUID = self.pifTargetIdForResourceBundle(name) - let target = self.pif.targets.only { $0.id == resourceBundleGUID } as? SwiftBuild.PIF.Target - return target + let targetKeyPath = self.project.findTarget(id: resourceBundleGUID) + return targetKeyPath } - func pifTargetIdForResourceBundle(_ name: String) -> String { - "PACKAGE-RESOURCE:\(name)" + func pifTargetIdForResourceBundle(_ name: String) -> GUID { + GUID("PACKAGE-RESOURCE:\(name)") } func resourceBundleName(forModuleName name: String) -> String { @@ -307,9 +371,9 @@ struct PackagePIFProjectBuilder { /// /// The reason we might not add them is that some targets are derivatives of other targets — in such cases, /// only the primary target adds the build tool commands to the PIF target. - func computePluginGeneratedFiles( + mutating func computePluginGeneratedFiles( module: PackageGraph.ResolvedModule, - pifTarget: SwiftBuild.PIF.Target, + targetKeyPath: WritableKeyPath, addBuildToolPluginCommands: Bool ) -> (sourceFilePaths: [AbsolutePath], resourceFilePaths: [String]) { guard let pluginResult = pifBuilder.buildToolPluginResultsByTargetName[module.name] else { @@ -321,7 +385,7 @@ struct PackagePIFProjectBuilder { // If we've been asked to add build tool commands for the result, we do so now. if addBuildToolPluginCommands { for command in pluginResult.buildCommands { - self.addBuildToolCommand(command, to: pifTarget) + self.addBuildToolCommand(command, to: targetKeyPath) } } @@ -339,10 +403,10 @@ struct PackagePIFProjectBuilder { /// Helper function for adding build tool commands to the right PIF target depending on whether they generate /// sources or resources. - func addBuildToolCommands( + mutating func addBuildToolCommands( module: PackageGraph.ResolvedModule, - sourceModulePifTarget: SwiftBuild.PIF.Target, - resourceBundlePifTarget: SwiftBuild.PIF.Target, + sourceModuleTargetKeyPath: WritableKeyPath, + resourceBundleTargetKeyPath: WritableKeyPath, sourceFilePaths: [AbsolutePath], resourceFilePaths: [String] ) { @@ -354,9 +418,9 @@ struct PackagePIFProjectBuilder { let producesResources = Set(command.outputPaths).intersection(resourceFilePaths).hasContent if producesResources { - self.addBuildToolCommand(command, to: resourceBundlePifTarget) + self.addBuildToolCommand(command, to: resourceBundleTargetKeyPath) } else { - self.addBuildToolCommand(command, to: sourceModulePifTarget) + self.addBuildToolCommand(command, to: sourceModuleTargetKeyPath) } } } @@ -364,9 +428,9 @@ struct PackagePIFProjectBuilder { /// Adds build rules to `pifTarget` for any build tool commands from invocation results. /// Returns the absolute paths of any generated source files that should be added to the sources build phase of the /// PIF target. - func addBuildToolCommands( - from pluginInvocationResults: [PIFPackageBuilder.BuildToolPluginInvocationResult], - pifTarget: SwiftBuild.PIF.Target, + mutating func addBuildToolCommands( + from pluginInvocationResults: [PackagePIFBuilder.BuildToolPluginInvocationResult], + targetKeyPath: WritableKeyPath, addBuildToolPluginCommands: Bool ) -> [String] { var generatedSourceFileAbsPaths: [String] = [] @@ -374,7 +438,7 @@ struct PackagePIFProjectBuilder { // Create build rules for all the commands in the result. if addBuildToolPluginCommands { for command in result.buildCommands { - self.addBuildToolCommand(command, to: pifTarget) + self.addBuildToolCommand(command, to: targetKeyPath) } } // Add the paths of the generated source files, so that they can be added to the Sources build phase. @@ -384,19 +448,19 @@ struct PackagePIFProjectBuilder { } /// Adds a single plugin-created build command to a PIF target. - func addBuildToolCommand( - _ command: PIFPackageBuilder.CustomBuildCommand, - to pifTarget: SwiftBuild.PIF.Target + mutating func addBuildToolCommand( + _ command: PackagePIFBuilder.CustomBuildCommand, + to targetKeyPath: WritableKeyPath ) { var commandLine = [command.executable] + command.arguments if let sandbox = command.sandboxProfile, !pifBuilder.delegate.isPluginExecutionSandboxingDisabled { commandLine = try! sandbox.apply(to: commandLine) } - pifTarget.customTasks.append( - SwiftBuild.PIF.CustomTask( + self.project[keyPath: targetKeyPath].customTasks.append( + ProjectModel.CustomTask( commandLine: commandLine, - environment: command.environment.map { ($0, $1) }.sorted(by: <), + environment: command.environment.map { Pair($0, $1) }.sorted(by: <), workingDirectory: command.workingDir?.pathString, executionDescription: command.displayName ?? "Performing build tool plugin command", inputFilePaths: [command.executable] + command.inputPaths.map(\.pathString), From 60976eecce01ba858db96c10e5a17afff0b51831 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 8 Apr 2025 13:16:16 -0400 Subject: [PATCH 03/99] Tests: Enabled SourceControlTests on Windows (#8453) Some tests in Tests/SourceControlTests are passing on Windows since upgrading the Git Version in the associated docker image, which was completd in https://github.com/swiftlang/swift-docker/pulls/452 Enable the tests that are skipped. Relates to: #8433 rdar://148248105 --- .../GitRepositoryTests.swift | 34 ------------------- .../RepositoryManagerTests.swift | 4 --- 2 files changed, 38 deletions(-) diff --git a/Tests/SourceControlTests/GitRepositoryTests.swift b/Tests/SourceControlTests/GitRepositoryTests.swift index ac905968bc5..daadddc3fd5 100644 --- a/Tests/SourceControlTests/GitRepositoryTests.swift +++ b/Tests/SourceControlTests/GitRepositoryTests.swift @@ -110,10 +110,6 @@ class GitRepositoryTests: XCTestCase { /// Check hash validation. func testGitRepositoryHash() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - let validHash = "0123456789012345678901234567890123456789" XCTAssertNotEqual(GitRepository.Hash(validHash), nil) @@ -190,10 +186,6 @@ class GitRepositoryTests: XCTestCase { } func testSubmoduleRead() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try makeDirectories(testRepoPath) @@ -305,10 +297,6 @@ class GitRepositoryTests: XCTestCase { /// Test the handling of local checkouts. func testCheckouts() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in // Create a test repository. let testRepoPath = path.appending("test-repo") @@ -355,10 +343,6 @@ class GitRepositoryTests: XCTestCase { } func testFetch() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -398,10 +382,6 @@ class GitRepositoryTests: XCTestCase { } func testHasUnpushedCommits() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -438,10 +418,6 @@ class GitRepositoryTests: XCTestCase { } func testSetRemote() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -554,10 +530,6 @@ class GitRepositoryTests: XCTestCase { } func testCheckoutRevision() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -690,8 +662,6 @@ class GitRepositoryTests: XCTestCase { } func testAlternativeObjectStoreValidation() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "test might hang in CI") - try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -762,10 +732,6 @@ class GitRepositoryTests: XCTestCase { } func testMissingDefaultBranch() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - try testWithTemporaryDirectory { path in // Create a repository. let testRepoPath = path.appending("test-repo") diff --git a/Tests/SourceControlTests/RepositoryManagerTests.swift b/Tests/SourceControlTests/RepositoryManagerTests.swift index cf74e43dd25..81b32496df4 100644 --- a/Tests/SourceControlTests/RepositoryManagerTests.swift +++ b/Tests/SourceControlTests/RepositoryManagerTests.swift @@ -19,10 +19,6 @@ import XCTest final class RepositoryManagerTests: XCTestCase { func testBasics() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - Test failed with: 0 [main] sh (9736) C:\\Program Files\\Git\\usr\\bin\\sh.exe: *** fatal error - add_item ("\\??\\C:\\Program Files\\Git", "/", ...) failed, errno 1 - """) - let fs = localFileSystem let observability = ObservabilitySystem.makeForTesting() From d4368fee54e69a11281ba5cc7f161a82f55a013f Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Tue, 8 Apr 2025 18:14:10 -0500 Subject: [PATCH 04/99] Clarify documented behavior of versioned manifest files in usage docs (#8462) This updates and clarifies the [Version-specific Manifest Selection](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Usage.md#version-specific-manifest-selection) section of the usage documentation to clarify the behavior when having either newer or older tools versions. ### Motivation: In general, when using the versioned manifest feature, it's preferable to have the unversioned `Package.swift` represent the newest-supported tools version, and only use version-specific manifest files for older versions. This provides a better workflow because it allows you to more easily drop support for older versions when ready (by avoiding the need to modify the unversioned file), among other benefits. ### Modifications: - "Reverse" the example shown in this documentation by having the unversioned file be newest. - Modernize the version numbers shown in examples. - Fix some non-inclusive language usages. - Add a footnote with some helpful historical context, and giving a recommendation. --- Documentation/Usage.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/Documentation/Usage.md b/Documentation/Usage.md index 29b31495c5e..88e3a92a2b3 100644 --- a/Documentation/Usage.md +++ b/Documentation/Usage.md @@ -497,7 +497,7 @@ This feature is intended for use in the following scenarios: It is *not* expected that the packages would ever use this feature unless absolutely necessary to support existing clients. Specifically, packages *should not* -adopt this syntax for tagging versions supporting the _latest GM_ Swift +adopt this syntax for tagging versions supporting the _latest released_ Swift version. The package manager supports looking for any of the following marked tags, in @@ -511,7 +511,7 @@ order of preference: The package manager will additionally look for a version-specific marked manifest version when loading the particular version of a package, by searching -for a manifest in the form of `Package@swift-3.swift`. The set of markers +for a manifest in the form of `Package@swift-6.swift`. The set of markers looked for is the same as for version-specific tag selection. This feature is intended for use in cases where a package wishes to maintain @@ -521,20 +521,28 @@ changes in the manifest API). It is *not* expected the packages would ever use this feature unless absolutely necessary to support existing clients. Specifically, packages *should not* -adopt this syntax for tagging versions supporting the _latest GM_ Swift +adopt this syntax for tagging versions supporting the _latest released_ Swift version. In case the current Swift version doesn't match any version-specific manifest, the package manager will pick the manifest with the most compatible tools version. For example, if there are three manifests: -`Package.swift` (tools version 3.0) -`Package@swift-4.swift` (tools version 4.0) -`Package@swift-4.2.swift` (tools version 4.2) +- `Package.swift` (tools version 6.0) +- `Package@swift-5.10.swift` (tools version 5.10) +- `Package@swift-5.9.swift` (tools version 5.9) -The package manager will pick `Package.swift` on Swift 3, `Package@swift-4.swift` on -Swift 4, and `Package@swift-4.2.swift` on Swift 4.2 and above because its tools -version will be most compatible with future version of the package manager. +The package manager will pick `Package.swift` on Swift 6 and above, because its +tools version will be most compatible with future version of the package manager. +When using Swift 5.10, it will pick `Package@swift-5.10.swift`. Otherwise, when +using Swift 5.9 it will pick `Package@swift-5.9.swift`, and this is the minimum +tools version this package may be used with. + +A package may have versioned manifest files which specify newer tools versions +than its unversioned `Package.swift` file[^1]. In this scenario, the manifest +corresponding to the newest-compatible tools version will be used. + +[^1]: Support for having a versioned manifest file with a _newer_ tools version was required when the feature was first introduced, because prior versions of the package manager were not aware of the concept and only knew to look for the unversioned `Package.swift`. This is still supported, but there have been many Swift releases since the feature was introduced and it is now considered best practice to have `Package.swift` declare the newest-supported tools version and for versioned manifest files to only specifer older versions. ## Editing a Package From 451196234d81f82a147293105fd4467cdd69b6ac Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 8 Apr 2025 20:59:54 -0400 Subject: [PATCH 05/99] Tests: Enable some WorkspaceTests on Windows (#8463) Enable WorkspaceTests on Windows that, for some reason, previously failed. Relates: #8433 rdar://148248105 --- Tests/WorkspaceTests/WorkspaceTests.swift | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index a6948417aac..bb479d83893 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -171,8 +171,6 @@ final class WorkspaceTests: XCTestCase { } func testInterpreterFlags() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let fs = localFileSystem try testWithTemporaryDirectory { path in @@ -297,8 +295,6 @@ final class WorkspaceTests: XCTestCase { } func testManifestParseError() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let observability = ObservabilitySystem.makeForTesting() try await testWithTemporaryDirectory { path in @@ -15899,8 +15895,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_SignedCorrectly() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let actualMetadata = RegistryReleaseMetadata.createWithSigningEntity( .recognized( type: "adp", @@ -15920,8 +15914,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_SignedIncorrectly() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let actualMetadata = RegistryReleaseMetadata.createWithSigningEntity( .recognized( type: "adp", @@ -15953,8 +15945,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_Unsigned() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let expectedSigningEntity: RegistryReleaseMetadata.SigningEntity = .recognized( type: "adp", commonName: "Jane Doe", @@ -15977,8 +15967,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_NotFound() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let expectedSigningEntity: RegistryReleaseMetadata.SigningEntity = .recognized( type: "adp", commonName: "Jane Doe", @@ -16001,8 +15989,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirroredSignedCorrectly() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let mirrors = try DependencyMirrors() try mirrors.set(mirror: "ecorp.bar", for: "org.bar") @@ -16032,8 +16018,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirrorSignedIncorrectly() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let mirrors = try DependencyMirrors() try mirrors.set(mirror: "ecorp.bar", for: "org.bar") @@ -16071,8 +16055,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirroredUnsigned() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let mirrors = try DependencyMirrors() try mirrors.set(mirror: "ecorp.bar", for: "org.bar") @@ -16098,8 +16080,6 @@ final class WorkspaceTests: XCTestCase { } func testSigningEntityVerification_MirroredToSCM() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let mirrors = try DependencyMirrors() try mirrors.set(mirror: "https://scm.com/org/bar-mirror", for: "org.bar") @@ -16485,8 +16465,6 @@ final class WorkspaceTests: XCTestCase { archiver: Archiver? = .none, fileSystem: FileSystem ) throws -> RegistryClient { - try skipOnWindowsAsTestCurrentlyFails() - let jsonEncoder = JSONEncoder.makeWithDefaults() guard let identity = packageIdentity.registry else { From cc0b1a5d166872c07a4725c8893fb661ea2957b3 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Wed, 9 Apr 2025 06:51:59 -0400 Subject: [PATCH 06/99] Tests: Enable tests on Windows (#8466) Enable additional tests on Windows host platform - Enable a few `AsyncProcessTests` - Enable the lone skipped test in `EnvironmentTests` - Enable the only skipped test in `Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift` - Enable the only skipped test in `Tests/BuildTests/BuildSystemDelegateTests.swift` - Enable the lone skipped test in `Tests/BuildTests/LLBuildManifestBuilderTests.swift` - Replace usages of `fixwin` with `AbsolutePath.pathString` Related to: #8433 rdar://148248105 --- Tests/BasicsTests/AsyncProcessTests.swift | 106 +++++++++++++----- .../Environment/EnvironmentTests.swift | 3 - .../BuildTests/BuildSystemDelegateTests.swift | 9 +- .../LLBuildManifestBuilderTests.swift | 4 +- .../SourceKitLSPAPITests.swift | 20 +--- Tests/WorkspaceTests/PrebuiltsTests.swift | 28 ++--- 6 files changed, 102 insertions(+), 68 deletions(-) diff --git a/Tests/BasicsTests/AsyncProcessTests.swift b/Tests/BasicsTests/AsyncProcessTests.swift index 8d7c36fa3bc..8ffb8bc7f7d 100644 --- a/Tests/BasicsTests/AsyncProcessTests.swift +++ b/Tests/BasicsTests/AsyncProcessTests.swift @@ -23,14 +23,19 @@ import func TSCBasic.withTemporaryFile import func TSCTestSupport.withCustomEnv final class AsyncProcessTests: XCTestCase { + #if os(Windows) + let executableExt = ".exe" + #else + let executableExt = "" + #endif - override func setUp() async throws { - try skipOnWindowsAsTestCurrentlyFails() - } - func testBasics() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "echo.exe")" + """) + do { - let process = AsyncProcess(args: "echo", "hello") + let process = AsyncProcess(args: "echo\(executableExt)", "hello") try process.launch() let result = try process.waitUntilExit() XCTAssertEqual(try result.utf8Output(), "hello\n") @@ -46,27 +51,26 @@ final class AsyncProcessTests: XCTestCase { } } - func testPopen() throws { - #if os(Windows) - let echo = "echo.exe" - #else - let echo = "echo" - #endif + func testPopenBasic() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "echo.exe")" + """) + // Test basic echo. - XCTAssertEqual(try AsyncProcess.popen(arguments: [echo, "hello"]).utf8Output(), "hello\n") + XCTAssertEqual(try AsyncProcess.popen(arguments: ["echo\(executableExt)", "hello"]).utf8Output(), "hello\n") + } + func testPopenWithBufferLargerThanAllocated() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "cat.exe")" + """) // Test buffer larger than that allocated. try withTemporaryFile { file in let count = 10000 let stream = BufferedOutputByteStream() stream.send(Format.asRepeating(string: "a", count: count)) try localFileSystem.writeFileContents(file.path, bytes: stream.bytes) - #if os(Windows) - let cat = "cat.exe" - #else - let cat = "cat" - #endif - let outputCount = try AsyncProcess.popen(args: cat, file.path.pathString).utf8Output().count + let outputCount = try AsyncProcess.popen(args: "cat\(executableExt)", file.path.pathString).utf8Output().count XCTAssert(outputCount == count) } } @@ -119,8 +123,12 @@ final class AsyncProcessTests: XCTestCase { } func testCheckNonZeroExit() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "echo.exe")" + """) + do { - let output = try AsyncProcess.checkNonZeroExit(args: "echo", "hello") + let output = try AsyncProcess.checkNonZeroExit(args: "echo\(executableExt)", "hello") XCTAssertEqual(output, "hello\n") } @@ -134,8 +142,12 @@ final class AsyncProcessTests: XCTestCase { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testCheckNonZeroExitAsync() async throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "echo.exe")" + """) + do { - let output = try await AsyncProcess.checkNonZeroExit(args: "echo", "hello") + let output = try await AsyncProcess.checkNonZeroExit(args: "echo\(executableExt)", "hello") XCTAssertEqual(output, "hello\n") } @@ -148,6 +160,8 @@ final class AsyncProcessTests: XCTestCase { } func testFindExecutable() throws { + try skipOnWindowsAsTestCurrentlyFails(because: "Assertion failure when trying to find ls executable") + try testWithTemporaryDirectory { tmpdir in // This process should always work. XCTAssertTrue(AsyncProcess.findExecutable("ls") != nil) @@ -192,7 +206,11 @@ final class AsyncProcessTests: XCTestCase { } func testThreadSafetyOnWaitUntilExit() throws { - let process = AsyncProcess(args: "echo", "hello") + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "echo.exe")" + """) + + let process = AsyncProcess(args: "echo\(executableExt)", "hello") try process.launch() var result1 = "" @@ -217,7 +235,11 @@ final class AsyncProcessTests: XCTestCase { @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testThreadSafetyOnWaitUntilExitAsync() async throws { - let process = AsyncProcess(args: "echo", "hello") + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "echo.exe")" + """) + + let process = AsyncProcess(args: "echo\(executableExt)", "hello") try process.launch() let t1 = Task { @@ -236,6 +258,10 @@ final class AsyncProcessTests: XCTestCase { } func testStdin() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) + var stdout = [UInt8]() let process = AsyncProcess(scriptName: "in-to-out", outputRedirection: .stream(stdout: { stdoutBytes in stdout += stdoutBytes @@ -253,6 +279,10 @@ final class AsyncProcessTests: XCTestCase { } func testStdoutStdErr() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) + // A simple script to check that stdout and stderr are captured separatly. do { let result = try AsyncProcess.popen(scriptName: "simple-stdout-stderr") @@ -279,6 +309,10 @@ final class AsyncProcessTests: XCTestCase { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testStdoutStdErrAsync() async throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) + // A simple script to check that stdout and stderr are captured separatly. do { let result = try await AsyncProcess.popen(scriptName: "simple-stdout-stderr") @@ -304,6 +338,10 @@ final class AsyncProcessTests: XCTestCase { } func testStdoutStdErrRedirected() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) + // A simple script to check that stdout and stderr are captured in the same location. do { let process = AsyncProcess( @@ -332,6 +370,10 @@ final class AsyncProcessTests: XCTestCase { } func testStdoutStdErrStreaming() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) + var stdout = [UInt8]() var stderr = [UInt8]() let process = AsyncProcess(scriptName: "long-stdout-stderr", outputRedirection: .stream(stdout: { stdoutBytes in @@ -348,6 +390,10 @@ final class AsyncProcessTests: XCTestCase { } func testStdoutStdErrStreamingRedirected() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) + var stdout = [UInt8]() var stderr = [UInt8]() let process = AsyncProcess(scriptName: "long-stdout-stderr", outputRedirection: .stream(stdout: { stdoutBytes in @@ -364,6 +410,10 @@ final class AsyncProcessTests: XCTestCase { } func testWorkingDirectory() throws { + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "missingExecutableProgram(program: "cat.exe")" + """) + guard #available(macOS 10.15, *) else { // Skip this test since it's not supported in this OS. return @@ -385,7 +435,7 @@ final class AsyncProcessTests: XCTestCase { try localFileSystem.writeFileContents(childPath, bytes: ByteString("child")) do { - let process = AsyncProcess(arguments: ["cat", "file"], workingDirectory: tempDirPath) + let process = AsyncProcess(arguments: ["cat\(executableExt)", "file"], workingDirectory: tempDirPath) try process.launch() let result = try process.waitUntilExit() XCTAssertEqual(try result.utf8Output(), "parent") @@ -403,12 +453,15 @@ final class AsyncProcessTests: XCTestCase { func testAsyncStream() async throws { // rdar://133548796 try XCTSkipIfCI() + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) let (stdoutStream, stdoutContinuation) = AsyncProcess.ReadableStream.makeStream() let (stderrStream, stderrContinuation) = AsyncProcess.ReadableStream.makeStream() let process = AsyncProcess( - scriptName: "echo", + scriptName: "echo\(executableExt)", outputRedirection: .stream { stdoutContinuation.yield($0) } stderr: { @@ -460,9 +513,12 @@ final class AsyncProcessTests: XCTestCase { func testAsyncStreamHighLevelAPI() async throws { // rdar://133548796 try XCTSkipIfCI() + try skipOnWindowsAsTestCurrentlyFails(because: """ + threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" + """) let result = try await AsyncProcess.popen( - scriptName: "echo", + scriptName: "echo\(executableExt)", stdout: { stdin, stdout in var counter = 0 stdin.write("Hello \(counter)\n") diff --git a/Tests/BasicsTests/Environment/EnvironmentTests.swift b/Tests/BasicsTests/Environment/EnvironmentTests.swift index f03bdbf8fa3..5b0388470c6 100644 --- a/Tests/BasicsTests/Environment/EnvironmentTests.swift +++ b/Tests/BasicsTests/Environment/EnvironmentTests.swift @@ -15,7 +15,6 @@ import Basics import XCTest -import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails() final class EnvironmentTests: XCTestCase { func test_init() { @@ -103,8 +102,6 @@ final class EnvironmentTests: XCTestCase { /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. func test_current() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "ProcessInfo.processInfo.environment[pathEnvVarName] return nil in the docker container") - #if os(Windows) let pathEnvVarName = "Path" #else diff --git a/Tests/BuildTests/BuildSystemDelegateTests.swift b/Tests/BuildTests/BuildSystemDelegateTests.swift index 90907ed6758..8e07b6a3d1a 100644 --- a/Tests/BuildTests/BuildSystemDelegateTests.swift +++ b/Tests/BuildTests/BuildSystemDelegateTests.swift @@ -30,13 +30,18 @@ final class BuildSystemDelegateTests: XCTestCase { } func testFilterNonFatalCodesignMessages() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try skipOnWindowsAsTestCurrentlyFails(because: "Package fails to build when the test is being executed") try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") // Note: we can re-use the `TestableExe` fixture here since we just need an executable. + #if os(Windows) + let executableExt = ".exe" + #else + let executableExt = "" + #endif try await fixture(name: "Miscellaneous/TestableExe") { fixturePath in _ = try await executeSwiftBuild(fixturePath) - let execPath = fixturePath.appending(components: ".build", "debug", "TestableExe1") + let execPath = fixturePath.appending(components: ".build", "debug", "TestableExe1\(executableExt)") XCTAssertTrue(localFileSystem.exists(execPath), "executable not found at '\(execPath)'") try localFileSystem.removeFileTree(execPath) let (fullLog, _) = try await executeSwiftBuild(fixturePath) diff --git a/Tests/BuildTests/LLBuildManifestBuilderTests.swift b/Tests/BuildTests/LLBuildManifestBuilderTests.swift index 057d4514568..7fa466a4545 100644 --- a/Tests/BuildTests/LLBuildManifestBuilderTests.swift +++ b/Tests/BuildTests/LLBuildManifestBuilderTests.swift @@ -195,8 +195,6 @@ final class LLBuildManifestBuilderTests: XCTestCase { /// Verifies that two modules with the same name but different triples don't share same build manifest keys. func testToolsBuildTriple() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let (graph, fs, scope) = try macrosPackageGraph() let productsTriple = Triple.x86_64MacOS let toolsTriple = Triple.arm64Linux @@ -221,6 +219,6 @@ final class LLBuildManifestBuilderTests: XCTestCase { XCTAssertNotNil(manifest.commands["C.SwiftSyntax-aarch64-unknown-linux-gnu-debug-tool.module"]) // Ensure that Objects.LinkFileList is -tool suffixed. - XCTAssertNotNil(manifest.commands["/path/to/build/aarch64-unknown-linux-gnu/debug/MMIOMacros-tool.product/Objects.LinkFileList"]) + XCTAssertNotNil(manifest.commands[AbsolutePath("/path/to/build/aarch64-unknown-linux-gnu/debug/MMIOMacros-tool.product/Objects.LinkFileList").pathString]) } } diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index 437be5757e2..a920599bdf3 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -24,8 +24,6 @@ import XCTest final class SourceKitLSPAPITests: XCTestCase { func testBasicSwiftPackage() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", "/Pkg/Sources/exe/README.md", @@ -89,7 +87,7 @@ final class SourceKitLSPAPITests: XCTestCase { "-package-name", "pkg", "-emit-dependencies", "-emit-module", - "-emit-module-path", "/path/to/build/\(plan.destinationBuildParameters.triple)/debug/Modules/exe.swiftmodule" + "-emit-module-path", AbsolutePath("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/Modules/exe.swiftmodule").pathString ], resources: [.init(filePath: "/Pkg/Sources/exe/Resources/some_file.txt")], ignoredFiles: [.init(filePath: "/Pkg/Sources/exe/exe.docc")], @@ -104,7 +102,7 @@ final class SourceKitLSPAPITests: XCTestCase { "-package-name", "pkg", "-emit-dependencies", "-emit-module", - "-emit-module-path", "/path/to/build/\(plan.destinationBuildParameters.triple)/debug/Modules/lib.swiftmodule" + "-emit-module-path", AbsolutePath("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/Modules/lib.swiftmodule").pathString ], resources: [.init(filePath: "/Pkg/Sources/lib/Resources/some_file.txt")], ignoredFiles: [.init(filePath: "/Pkg/Sources/lib/lib.docc")], @@ -115,7 +113,7 @@ final class SourceKitLSPAPITests: XCTestCase { for: "plugin", graph: graph, partialArguments: [ - "-I", "/fake/manifestLib/path" + "-I", AbsolutePath("/fake/manifestLib/path").pathString ], isPartOfRootPackage: true, destination: .host @@ -318,7 +316,7 @@ final class SourceKitLSPAPITests: XCTestCase { "-package-name", "pkg", "-emit-dependencies", "-emit-module", - "-emit-module-path", "/path/to/build/\(destinationBuildParameters.triple)/debug/Modules/lib.swiftmodule".fixwin + "-emit-module-path", AbsolutePath("/path/to/build/\(destinationBuildParameters.triple)/debug/Modules/lib.swiftmodule").pathString ], isPartOfRootPackage: true ) @@ -402,13 +400,3 @@ extension SourceKitLSPAPI.BuildDescription { return result } } - -extension String { - var fixwin: String { - #if os(Windows) - return self.replacingOccurrences(of: "/", with: "\\") - #else - return self - #endif - } -} diff --git a/Tests/WorkspaceTests/PrebuiltsTests.swift b/Tests/WorkspaceTests/PrebuiltsTests.swift index 7739dbd75f2..8af88b8464e 100644 --- a/Tests/WorkspaceTests/PrebuiltsTests.swift +++ b/Tests/WorkspaceTests/PrebuiltsTests.swift @@ -105,10 +105,10 @@ final class PrebuiltsTests: XCTestCase { func checkSettings(_ target: Module, usePrebuilt: Bool) throws { if usePrebuilt { let swiftFlags = try XCTUnwrap(target.buildSettings.assignments[.OTHER_SWIFT_FLAGS]).flatMap({ $0.values }) - XCTAssertTrue(swiftFlags.contains("-I/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/Modules".fixwin)) - XCTAssertTrue(swiftFlags.contains("-I/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/include/_SwiftSyntaxCShims".fixwin)) + XCTAssertTrue(swiftFlags.contains("-I\(AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/Modules").pathString)")) + XCTAssertTrue(swiftFlags.contains("-I\(AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/include/_SwiftSyntaxCShims").pathString)")) let ldFlags = try XCTUnwrap(target.buildSettings.assignments[.OTHER_LDFLAGS]).flatMap({ $0.values }) - XCTAssertTrue(ldFlags.contains("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/lib/libMacroSupport.a".fixwin)) + XCTAssertTrue(ldFlags.contains(AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/lib/libMacroSupport.a").pathString)) } else { XCTAssertNil(target.buildSettings.assignments[.OTHER_SWIFT_FLAGS]) XCTAssertNil(target.buildSettings.assignments[.OTHER_LDFLAGS]) @@ -141,8 +141,8 @@ final class PrebuiltsTests: XCTestCase { } let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTAssertEqual(archivePath.pathString, "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip".fixwin) - XCTAssertEqual(destination.pathString, "/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64".fixwin) + XCTAssertEqual(archivePath.pathString, AbsolutePath("/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip").pathString) + XCTAssertEqual(destination.pathString, AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64").pathString) completion(.success(())) }) @@ -202,8 +202,8 @@ final class PrebuiltsTests: XCTestCase { } let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTAssertEqual(archivePath.pathString, "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip".fixwin) - XCTAssertEqual(destination.pathString, "/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64".fixwin) + XCTAssertEqual(archivePath.pathString, AbsolutePath("/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip").pathString) + XCTAssertEqual(destination.pathString, AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64").pathString) completion(.success(())) }) @@ -468,8 +468,8 @@ final class PrebuiltsTests: XCTestCase { } let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTAssertEqual(archivePath.pathString, "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip".fixwin) - XCTAssertEqual(destination.pathString, "/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64".fixwin) + XCTAssertEqual(archivePath.pathString, AbsolutePath("/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip").pathString) + XCTAssertEqual(destination.pathString, AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64").pathString) completion(.success(())) }) @@ -581,13 +581,3 @@ final class PrebuiltsTests: XCTestCase { } } } - -extension String { - var fixwin: String { - #if os(Windows) - return self.replacingOccurrences(of: "/", with: "\\") - #else - return self - #endif - } -} From 21e333620ab27947e77f9455a5116800dd978ee8 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 9 Apr 2025 22:51:47 +0900 Subject: [PATCH 07/99] SwiftSDK: Remove hardcoded WASI sysroot path derivation (#8468) Remove hardcoded WASI sysroot path derivation just for wasi-sysroot embedded in toolchains ### Motivation: The previous implementation assumed there is $TOOLCHAIN_ROOT/share/wasi-sysroot when `--triple wasm32-unknown-wasi` is passed, but this is no longer the case with the [deprecation of the Wasm toolchain installation](https://github.com/swiftwasm/swift/issues/5604) in favor of Swift SDKs. Due to this unnecessary branch, when `--triple wasm32-unknown-wasi` is passed together with `--swift-sdk`, `--swift-sdk` is just ignored: https://github.com/swiftlang/swift-package-manager/issues/8465 ### Modifications: This change removes the hardcoded path derivation for the WASI sysroot from the `SwiftSDK.deriveTargetSwiftSDK` method. ### Result: When `--triple wasm32-unknown-wasi` is passed without `--swift-sdk`, no user visible change and they will keep getting the following: ``` error: emit-module command failed with exit code 1 (use -v to see invocation) :0: warning: libc not found for 'wasm32-unknown-wasi'; C stdlib may be unavailable :0: error: unable to load standard library for target 'wasm32-unknown-wasi' ``` When `--triple wasm32-unknown-wasi` is passed together with `--swift-sdk`, `--triple` is ignored and `--swift-sdk` is respected. --- Sources/PackageModel/SwiftSDKs/SwiftSDK.swift | 11 ----------- Tests/PackageModelTests/SwiftSDKBundleTests.swift | 13 ------------- 2 files changed, 24 deletions(-) diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index 313d454a965..066d29c1147 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -691,17 +691,6 @@ public struct SwiftSDK: Equatable { hostSDK: SwiftSDK, environment: Environment = .current ) -> SwiftSDK? { - if targetTriple.isWASI() { - let wasiSysroot = hostSDK.toolset.rootPaths.first? - .parentDirectory // usr - .appending(components: "share", "wasi-sysroot") - return SwiftSDK( - targetTriple: targetTriple, - toolset: hostSDK.toolset, - pathsConfiguration: .init(sdkRootPath: wasiSysroot) - ) - } - #if os(macOS) if let darwinPlatform = targetTriple.darwinPlatform { // the Darwin SDKs are trivially available on macOS diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index b2618cc9594..0a65b116675 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -450,19 +450,6 @@ final class SwiftSDKBundleTests: XCTestCase { XCTAssertEqual(targetSwiftSDK.toolset.rootPaths, [toolsetRootPath] + hostSwiftSDK.toolset.rootPaths) } - do { - let targetSwiftSDK = try SwiftSDK.deriveTargetSwiftSDK( - hostSwiftSDK: hostSwiftSDK, - hostTriple: hostTriple, - swiftSDKSelector: "wasm32-unknown-wasi", - store: store, - observabilityScope: system.topScope, - fileSystem: fileSystem - ) - // Ensure that triples that have a `defaultSwiftSDK` are handled - XCTAssertEqual(targetSwiftSDK.targetTriple?.triple, "wasm32-unknown-wasi") - } - do { // Check explicit overriding options. let customCompileSDK = AbsolutePath("/path/to/sdk") From e303b898a7791285626493e74745cb8565a3659e Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Wed, 9 Apr 2025 10:58:41 -0400 Subject: [PATCH 08/99] Copy helpers internally (#8467) Until #8223 is fixed copy some helpers from `IntergrationTests/Source/IntegrationTests` to `Test/_InternalTestSupport` so we can re-use the functionality. Related to: #8433 rdar://148248105 --- Sources/_InternalTestSupport/Process.swift | 43 ++++++++++++ .../SkippedTestSupport.swift | 70 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 Sources/_InternalTestSupport/Process.swift create mode 100644 Sources/_InternalTestSupport/SkippedTestSupport.swift diff --git a/Sources/_InternalTestSupport/Process.swift b/Sources/_InternalTestSupport/Process.swift new file mode 100644 index 00000000000..eacfcbc2896 --- /dev/null +++ b/Sources/_InternalTestSupport/Process.swift @@ -0,0 +1,43 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +public import Foundation + +public enum OperatingSystem: Hashable, Sendable { + case macOS + case windows + case linux + case android + case unknown +} + +extension ProcessInfo { + #if os(macOS) + public static let hostOperatingSystem = OperatingSystem.macOS + #elseif os(Linux) + public static let hostOperatingSystem = OperatingSystem.linux + #elseif os(Windows) + public static let hostOperatingSystem = OperatingSystem.windows + #else + public static let hostOperatingSystem = OperatingSystem.unknown + #endif + + #if os(Windows) + public static let EOL = "\r\n" + #else + public static let EOL = "\n" + #endif + + #if os(Windows) + public static let exeSuffix = ".exe" + #else + public static let exeSuffix = "" + #endif +} diff --git a/Sources/_InternalTestSupport/SkippedTestSupport.swift b/Sources/_InternalTestSupport/SkippedTestSupport.swift new file mode 100644 index 00000000000..b73727d1c64 --- /dev/null +++ b/Sources/_InternalTestSupport/SkippedTestSupport.swift @@ -0,0 +1,70 @@ + +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +import class Foundation.FileManager +import class Foundation.ProcessInfo +import Testing + +extension Trait where Self == Testing.ConditionTrait { + /// Skip test if the host operating system does not match the running OS. + public static func requireHostOS(_ os: OperatingSystem, when condition: Bool = true) -> Self { + enabled("This test requires a \(os) host OS.") { + ProcessInfo.hostOperatingSystem == os && condition + } + } + + /// Skip test if the host operating system matches the running OS. + public static func skipHostOS(_ os: OperatingSystem, _ comment: Comment? = nil) -> Self { + disabled(comment ?? "This test cannot run on a \(os) host OS.") { + ProcessInfo.hostOperatingSystem == os + } + } + + /// Skip test unconditionally + public static func skip(_ comment: Comment? = nil) -> Self { + disabled(comment ?? "Unconditional skip, a comment should be added for the reason") { true } + } + + /// Skip test if the environment is self hosted. + public static func skipSwiftCISelfHosted(_ comment: Comment? = nil) -> Self { + disabled(comment ?? "SwiftCI is self hosted") { + ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil + } + } + + /// Skip test if the test environment has a restricted network access, i.e. cannot get to internet. + public static func requireUnrestrictedNetworkAccess(_ comment: Comment? = nil) -> Self { + disabled(comment ?? "CI Environment has restricted network access") { + ProcessInfo.processInfo.environment["SWIFTCI_RESTRICTED_NETWORK_ACCESS"] != nil + } + } + + /// Skip test if built by XCode. + public static func skipIfXcodeBuilt() -> Self { + disabled("Tests built by Xcode") { + #if Xcode + true + #else + false + #endif + } + } + + /// Constructs a condition trait that causes a test to be disabled if the Foundation process spawning implementation + /// is not using `posix_spawn_file_actions_addchdir`. + public static var requireThreadSafeWorkingDirectory: Self { + disabled("Thread-safe process working directory support is unavailable.") { + // Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support + FileManager.default.contents(atPath: "/etc/system-release") + .map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false + } + } +} From cd5d9dd6103be137977f7a4d5c62019bcc02c046 Mon Sep 17 00:00:00 2001 From: nate-chandler <46721658+nate-chandler@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:27:52 -0700 Subject: [PATCH 09/99] Revert "Copy helpers internally" (#8474) Reverts swiftlang/swift-package-manager#8467 --- Sources/_InternalTestSupport/Process.swift | 43 ------------ .../SkippedTestSupport.swift | 70 ------------------- 2 files changed, 113 deletions(-) delete mode 100644 Sources/_InternalTestSupport/Process.swift delete mode 100644 Sources/_InternalTestSupport/SkippedTestSupport.swift diff --git a/Sources/_InternalTestSupport/Process.swift b/Sources/_InternalTestSupport/Process.swift deleted file mode 100644 index eacfcbc2896..00000000000 --- a/Sources/_InternalTestSupport/Process.swift +++ /dev/null @@ -1,43 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See http://swift.org/LICENSE.txt for license information - See http://swift.org/CONTRIBUTORS.txt for Swift project authors - */ - -public import Foundation - -public enum OperatingSystem: Hashable, Sendable { - case macOS - case windows - case linux - case android - case unknown -} - -extension ProcessInfo { - #if os(macOS) - public static let hostOperatingSystem = OperatingSystem.macOS - #elseif os(Linux) - public static let hostOperatingSystem = OperatingSystem.linux - #elseif os(Windows) - public static let hostOperatingSystem = OperatingSystem.windows - #else - public static let hostOperatingSystem = OperatingSystem.unknown - #endif - - #if os(Windows) - public static let EOL = "\r\n" - #else - public static let EOL = "\n" - #endif - - #if os(Windows) - public static let exeSuffix = ".exe" - #else - public static let exeSuffix = "" - #endif -} diff --git a/Sources/_InternalTestSupport/SkippedTestSupport.swift b/Sources/_InternalTestSupport/SkippedTestSupport.swift deleted file mode 100644 index b73727d1c64..00000000000 --- a/Sources/_InternalTestSupport/SkippedTestSupport.swift +++ /dev/null @@ -1,70 +0,0 @@ - -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See http://swift.org/LICENSE.txt for license information - See http://swift.org/CONTRIBUTORS.txt for Swift project authors - */ - -import class Foundation.FileManager -import class Foundation.ProcessInfo -import Testing - -extension Trait where Self == Testing.ConditionTrait { - /// Skip test if the host operating system does not match the running OS. - public static func requireHostOS(_ os: OperatingSystem, when condition: Bool = true) -> Self { - enabled("This test requires a \(os) host OS.") { - ProcessInfo.hostOperatingSystem == os && condition - } - } - - /// Skip test if the host operating system matches the running OS. - public static func skipHostOS(_ os: OperatingSystem, _ comment: Comment? = nil) -> Self { - disabled(comment ?? "This test cannot run on a \(os) host OS.") { - ProcessInfo.hostOperatingSystem == os - } - } - - /// Skip test unconditionally - public static func skip(_ comment: Comment? = nil) -> Self { - disabled(comment ?? "Unconditional skip, a comment should be added for the reason") { true } - } - - /// Skip test if the environment is self hosted. - public static func skipSwiftCISelfHosted(_ comment: Comment? = nil) -> Self { - disabled(comment ?? "SwiftCI is self hosted") { - ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil - } - } - - /// Skip test if the test environment has a restricted network access, i.e. cannot get to internet. - public static func requireUnrestrictedNetworkAccess(_ comment: Comment? = nil) -> Self { - disabled(comment ?? "CI Environment has restricted network access") { - ProcessInfo.processInfo.environment["SWIFTCI_RESTRICTED_NETWORK_ACCESS"] != nil - } - } - - /// Skip test if built by XCode. - public static func skipIfXcodeBuilt() -> Self { - disabled("Tests built by Xcode") { - #if Xcode - true - #else - false - #endif - } - } - - /// Constructs a condition trait that causes a test to be disabled if the Foundation process spawning implementation - /// is not using `posix_spawn_file_actions_addchdir`. - public static var requireThreadSafeWorkingDirectory: Self { - disabled("Thread-safe process working directory support is unavailable.") { - // Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support - FileManager.default.contents(atPath: "/etc/system-release") - .map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false - } - } -} From a0d1600e45a5ce476c0b3da473d9f8f4851c0437 Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:55:00 -0400 Subject: [PATCH 10/99] Fix duplicate modulemap errors with macro and plugin deps (#8472) We were including flags to hook up modulemaps and include files to C library dependencies in macros and plugin tools through to the modules that depend on those. This adds the capability to prune the depth first searches through the dependencies to ensure these are skipped when crossing macro and plugin boundaries. --- Sources/Basics/Graph/GraphAlgorithms.swift | 47 +++++++++++++ .../ModuleBuildDescription.swift | 22 +++++++ .../SwiftModuleBuildDescription.swift | 6 ++ Sources/Build/BuildPlan/BuildPlan+Swift.swift | 3 +- Sources/Build/BuildPlan/BuildPlan.swift | 12 ++-- Tests/BuildTests/BuildPlanTests.swift | 66 +++++++++++++++++++ .../BuildTests/BuildPlanTraversalTests.swift | 2 + 7 files changed, 150 insertions(+), 8 deletions(-) diff --git a/Sources/Basics/Graph/GraphAlgorithms.swift b/Sources/Basics/Graph/GraphAlgorithms.swift index 8ccc6038cc0..deee4941985 100644 --- a/Sources/Basics/Graph/GraphAlgorithms.swift +++ b/Sources/Basics/Graph/GraphAlgorithms.swift @@ -131,3 +131,50 @@ public func depthFirstSearch( } } } + +/// Implements a pre-order depth-first search that traverses the whole graph and +/// doesn't distinguish between unique and duplicate nodes. The visitor can abort +/// a path as needed to prune the tree. +/// The method expects the graph to be acyclic but doesn't check that. +/// +/// - Parameters: +/// - nodes: The list of input nodes to sort. +/// - successors: A closure for fetching the successors of a particular node. +/// - onNext: A callback to indicate the node currently being processed +/// including its parent (if any) and its depth. Returns whether to +/// continue down the current path. +/// +/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges +/// reachable from the input nodes via the relation. +public enum DepthFirstContinue { + case `continue` + case abort +} + +public func depthFirstSearch( + _ nodes: [T], + successors: (T) throws -> [T], + visitNext: (T, _ parent: T?) throws -> DepthFirstContinue +) rethrows { + var stack = OrderedSet>() + + for node in nodes { + precondition(stack.isEmpty) + stack.append(TraversalNode(parent: nil, curr: node)) + + while !stack.isEmpty { + let node = stack.removeLast() + + if try visitNext(node.curr, node.parent) == .continue { + for succ in try successors(node.curr) { + stack.append( + TraversalNode( + parent: node.curr, + curr: succ + ) + ) + } + } + } + } +} diff --git a/Sources/Build/BuildDescription/ModuleBuildDescription.swift b/Sources/Build/BuildDescription/ModuleBuildDescription.swift index 8f52d45f370..06c52486045 100644 --- a/Sources/Build/BuildDescription/ModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ModuleBuildDescription.swift @@ -187,8 +187,30 @@ extension ModuleBuildDescription { var dependencies: [Dependency] = [] plan.traverseDependencies(of: self) { product, _, description in dependencies.append(.product(product, description)) + return .continue } onModule: { module, _, description in dependencies.append(.module(module, description)) + return .continue + } + return dependencies + } + + package func recursiveLinkDependencies(using plan: BuildPlan) -> [Dependency] { + var dependencies: [Dependency] = [] + plan.traverseDependencies(of: self) { product, _, description in + guard product.type != .macro && product.type != .plugin else { + return .abort + } + + dependencies.append(.product(product, description)) + return .continue + } onModule: { module, _, description in + guard module.type != .macro && module.type != .plugin else { + return .abort + } + + dependencies.append(.module(module, description)) + return .continue } return dependencies } diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index fc9dc8c317a..4eac7878727 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -1042,6 +1042,12 @@ extension SwiftModuleBuildDescription { ModuleBuildDescription.swift(self).dependencies(using: plan) } + package func recursiveLinkDependencies( + using plan: BuildPlan + ) -> [ModuleBuildDescription.Dependency] { + ModuleBuildDescription.swift(self).recursiveLinkDependencies(using: plan) + } + package func recursiveDependencies( using plan: BuildPlan ) -> [ModuleBuildDescription.Dependency] { diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index 7d387f8cfd5..f0c0b256cbb 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -19,7 +19,7 @@ import class PackageModel.SystemLibraryModule extension BuildPlan { func plan(swiftTarget: SwiftModuleBuildDescription) throws { // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target - // depends on. + // builds against for case .module(let dependency, let description) in swiftTarget.recursiveDependencies(using: self) { switch dependency.underlying { case let underlyingTarget as ClangModule where underlyingTarget.type == .library: @@ -53,5 +53,4 @@ extension BuildPlan { } } } - } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index bf6ac9bd12f..01ffe8ec665 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -1154,8 +1154,8 @@ extension BuildPlan { package func traverseDependencies( of description: ModuleBuildDescription, - onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void, - onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void + onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> DepthFirstContinue, + onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> DepthFirstContinue ) { var visited = Set() func successors( @@ -1196,16 +1196,16 @@ extension BuildPlan { case .package: [] } - } onNext: { module, _ in + } visitNext: { module, _ in switch module { case .package: - break + return .continue case .product(let product, let destination): - onProduct(product, destination, self.description(for: product, context: destination)) + return onProduct(product, destination, self.description(for: product, context: destination)) case .module(let module, let destination): - onModule(module, destination, self.description(for: module, context: destination)) + return onModule(module, destination, self.description(for: module, context: destination)) } } } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6caaae88baf..423afcd0e04 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -6909,6 +6909,72 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { XCTAssertMatch(contents, .regex(#"args: \[.*"-I","/testpackagedep/SomeArtifact.xcframework/macos/Headers".*,"/testpackage/Sources/CLib/lib.c".*]"#)) XCTAssertMatch(contents, .regex(#"args: \[.*"-module-name","SwiftLib",.*"-I","/testpackagedep/SomeArtifact.xcframework/macos/Headers".*]"#)) } + + func testMacroPluginDependencyLeakage() async throws { + // Make sure the include paths from macro and plugin executables don't leak into dependents + let observability = ObservabilitySystem.makeForTesting() + let fs = InMemoryFileSystem(emptyFiles: [ + "/LeakTest/Sources/CLib/include/Clib.h", + "/LeakTest/Sources/CLib/Clib.c", + "/LeakTest/Sources/MyMacro/MyMacro.swift", + "/LeakTest/Sources/MyPluginTool/MyPluginTool.swift", + "/LeakTest/Plugins/MyPlugin/MyPlugin.swift", + "/LeakTest/Sources/MyLib/MyLib.swift", + "/LeakLib/Sources/CLib2/include/Clib.h", + "/LeakLib/Sources/CLib2/Clib.c", + "/LeakLib/Sources/MyMacro2/MyMacro.swift", + "/LeakLib/Sources/MyPluginTool2/MyPluginTool.swift", + "/LeakLib/Plugins/MyPlugin2/MyPlugin.swift", + "/LeakLib/Sources/MyLib2/MyLib.swift" + ]) + + let graph = try loadModulesGraph(fileSystem: fs, manifests: [ + Manifest.createFileSystemManifest( + displayName: "LeakLib", + path: "/LeakLib", + products: [ + ProductDescription(name: "MyLib2", type: .library(.automatic), targets: ["MyLib2"]), + ], + targets: [ + TargetDescription(name: "CLib2"), + TargetDescription(name: "MyMacro2", dependencies: ["CLib2"], type: .macro), + TargetDescription(name: "MyPluginTool2", dependencies: ["CLib2"], type: .executable), + TargetDescription(name: "MyPlugin2", dependencies: ["MyPluginTool2"], type: .plugin, pluginCapability: .buildTool), + TargetDescription(name: "MyLib2", dependencies: ["CLib2", "MyMacro2"], pluginUsages: [.plugin(name: "MyPlugin2", package: nil)]), + ] + ), + Manifest.createRootManifest( + displayName: "LeakTest", + path: "/LeakTest", + dependencies: [ + .fileSystem(path: "/LeakLib") + ], + targets: [ + TargetDescription(name: "CLib"), + TargetDescription(name: "MyMacro", dependencies: ["CLib"], type: .macro), + TargetDescription(name: "MyPluginTool", dependencies: ["CLib"], type: .executable), + TargetDescription(name: "MyPlugin", dependencies: ["MyPluginTool"], type: .plugin, pluginCapability: .buildTool), + TargetDescription( + name: "MyLib", + dependencies: ["CLib", "MyMacro", .product(name: "MyLib2", package: "LeakLib")], + pluginUsages: [.plugin(name: "MyPlugin", package: nil)] + ), + ] + ) + ], observabilityScope: observability.topScope) + XCTAssertNoDiagnostics(observability.diagnostics) + + let plan = try await mockBuildPlan( + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + let myLib = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyLib" })).swift() + print(myLib.additionalFlags) + XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool/include")}), "flags shouldn't contain tools items") + } } class BuildPlanNativeTests: BuildPlanTestCase { diff --git a/Tests/BuildTests/BuildPlanTraversalTests.swift b/Tests/BuildTests/BuildPlanTraversalTests.swift index 2b898cabd76..ed469887d60 100644 --- a/Tests/BuildTests/BuildPlanTraversalTests.swift +++ b/Tests/BuildTests/BuildPlanTraversalTests.swift @@ -146,8 +146,10 @@ final class BuildPlanTraversalTests: XCTestCase { XCTAssertEqual(product.name, "SwiftSyntax") XCTAssertEqual(destination, .host) XCTAssertNil(description) + return .continue } onModule: { module, destination, description in moduleDependencies.append((module, destination, description)) + return .continue } XCTAssertEqual(moduleDependencies.count, 2) From 1d48e0a8e14d84e54feaabc24fc366f5a694b7fe Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Fri, 11 Apr 2025 13:12:30 -0400 Subject: [PATCH 11/99] Copy helpers internally (#8476) Until #8223 is fixed copy some helpers from `IntergrationTests/Source/IntegrationTests` to `Test/_InternalTestSupport` so we can re-use the functionality. Related to: #8433 rdar://148248105 Test with: https://github.com/swiftlang/swift/pull/80717 --- Package.swift | 6 +- Sources/_InternalTestSupport/Process.swift | 43 ++++++++++++ .../SkippedTestSupport.swift | 70 +++++++++++++++++++ 3 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 Sources/_InternalTestSupport/Process.swift create mode 100644 Sources/_InternalTestSupport/SkippedTestSupport.swift diff --git a/Package.swift b/Package.swift index 216810019bc..15d291a3eba 100644 --- a/Package.swift +++ b/Package.swift @@ -725,7 +725,7 @@ let package = Package( // MARK: Additional Test Dependencies - .target( + .testTarget( /** SwiftPM internal build test suite support library */ name: "_InternalBuildTestSupport", dependencies: [ @@ -734,12 +734,13 @@ let package = Package( "SwiftBuildSupport", "_InternalTestSupport" ], + path: "Sources/_InternalBuildTestSupport", swiftSettings: [ .unsafeFlags(["-static"]), ] ), - .target( + .testTarget( /** SwiftPM internal test suite support library */ name: "_InternalTestSupport", dependencies: [ @@ -754,6 +755,7 @@ let package = Package( .product(name: "OrderedCollections", package: "swift-collections"), "Workspace", ], + path: "./Sources/_InternalTestSupport", swiftSettings: [ .unsafeFlags(["-static"]), ] diff --git a/Sources/_InternalTestSupport/Process.swift b/Sources/_InternalTestSupport/Process.swift new file mode 100644 index 00000000000..eacfcbc2896 --- /dev/null +++ b/Sources/_InternalTestSupport/Process.swift @@ -0,0 +1,43 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +public import Foundation + +public enum OperatingSystem: Hashable, Sendable { + case macOS + case windows + case linux + case android + case unknown +} + +extension ProcessInfo { + #if os(macOS) + public static let hostOperatingSystem = OperatingSystem.macOS + #elseif os(Linux) + public static let hostOperatingSystem = OperatingSystem.linux + #elseif os(Windows) + public static let hostOperatingSystem = OperatingSystem.windows + #else + public static let hostOperatingSystem = OperatingSystem.unknown + #endif + + #if os(Windows) + public static let EOL = "\r\n" + #else + public static let EOL = "\n" + #endif + + #if os(Windows) + public static let exeSuffix = ".exe" + #else + public static let exeSuffix = "" + #endif +} diff --git a/Sources/_InternalTestSupport/SkippedTestSupport.swift b/Sources/_InternalTestSupport/SkippedTestSupport.swift new file mode 100644 index 00000000000..b73727d1c64 --- /dev/null +++ b/Sources/_InternalTestSupport/SkippedTestSupport.swift @@ -0,0 +1,70 @@ + +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +import class Foundation.FileManager +import class Foundation.ProcessInfo +import Testing + +extension Trait where Self == Testing.ConditionTrait { + /// Skip test if the host operating system does not match the running OS. + public static func requireHostOS(_ os: OperatingSystem, when condition: Bool = true) -> Self { + enabled("This test requires a \(os) host OS.") { + ProcessInfo.hostOperatingSystem == os && condition + } + } + + /// Skip test if the host operating system matches the running OS. + public static func skipHostOS(_ os: OperatingSystem, _ comment: Comment? = nil) -> Self { + disabled(comment ?? "This test cannot run on a \(os) host OS.") { + ProcessInfo.hostOperatingSystem == os + } + } + + /// Skip test unconditionally + public static func skip(_ comment: Comment? = nil) -> Self { + disabled(comment ?? "Unconditional skip, a comment should be added for the reason") { true } + } + + /// Skip test if the environment is self hosted. + public static func skipSwiftCISelfHosted(_ comment: Comment? = nil) -> Self { + disabled(comment ?? "SwiftCI is self hosted") { + ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil + } + } + + /// Skip test if the test environment has a restricted network access, i.e. cannot get to internet. + public static func requireUnrestrictedNetworkAccess(_ comment: Comment? = nil) -> Self { + disabled(comment ?? "CI Environment has restricted network access") { + ProcessInfo.processInfo.environment["SWIFTCI_RESTRICTED_NETWORK_ACCESS"] != nil + } + } + + /// Skip test if built by XCode. + public static func skipIfXcodeBuilt() -> Self { + disabled("Tests built by Xcode") { + #if Xcode + true + #else + false + #endif + } + } + + /// Constructs a condition trait that causes a test to be disabled if the Foundation process spawning implementation + /// is not using `posix_spawn_file_actions_addchdir`. + public static var requireThreadSafeWorkingDirectory: Self { + disabled("Thread-safe process working directory support is unavailable.") { + // Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support + FileManager.default.contents(atPath: "/etc/system-release") + .map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false + } + } +} From 6dfb613fab808061d37d5ef3427ead7f6e0070b7 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sat, 12 Apr 2025 02:42:51 +0100 Subject: [PATCH 12/99] Fix `SwiftBuildSupport/README.md` (#8483) Fixed up incorrect wording: 1. Wasm is not an acronym, thus it's not uppercased [per the spec](https://webassembly.github.io/spec/core/intro/introduction.html#wasm). 2. Existing Swift SDKs support only WASI, while WASI-less Wasm modules can be created with Embedded Swift and Swift SDKs are not needed for that. --- Sources/SwiftBuildSupport/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftBuildSupport/README.md b/Sources/SwiftBuildSupport/README.md index 76fd69e1c97..82701fc78b2 100644 --- a/Sources/SwiftBuildSupport/README.md +++ b/Sources/SwiftBuildSupport/README.md @@ -15,7 +15,7 @@ Work is continuing in these areas: * Conditional target dependencies (i.e. dependencies that are conditional on ".when()" specific platforms) * Plugin support * Friendly Error and Warning Descriptions and Fixups -* Cross compiling Swift SDK's (e.g. Static Linux SDK, and WASM) +* Cross compiling Swift SDK's (e.g. Static Linux SDK, and Wasm with WASI) * Improvements to test coverage * Task execution reporting From 57a67b4a34cec4cd0f014ebf5caf650e2fb8eda8 Mon Sep 17 00:00:00 2001 From: 3405691582 Date: Mon, 14 Apr 2025 09:35:03 -0400 Subject: [PATCH 13/99] Fix bootstrapping on OpenBSD (#8451) ### Motivation: Ensure swiftpm bootstraps successfully to fully build an OpenBSD toolchain. ### Modifications: * Add the nobtcfi linker flag to the bootstrap script for OpenBSD. This is unconditional for now since swiftpm uses Concurrency liberally and this triggers #80059, so swiftpm only builds on the configuration where BTCFI is disabled. We can revisit some of that perhaps later. * Ensure SPMSQLite3 builds with the correct link library search path flag in CMakeLists.txt. * Update Package.swift conditionals for SPMSQLite3. ### Result: swiftpm successfully builds and bootstraps on OpenBSD. --- Package.swift | 2 +- Sources/SPMSQLite3/CMakeLists.txt | 3 +++ Utilities/bootstrap | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 15d291a3eba..28d9d2869f5 100644 --- a/Package.swift +++ b/Package.swift @@ -227,7 +227,7 @@ let package = Package( name: "Basics", dependencies: [ "_AsyncFileSystem", - .target(name: "SPMSQLite3", condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS, .visionOS, .macCatalyst, .linux, .custom("freebsd")])), + .target(name: "SPMSQLite3", condition: .when(platforms: [.macOS, .iOS, .tvOS, .watchOS, .visionOS, .macCatalyst, .linux, .openbsd, .custom("freebsd")])), .product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.windows, .android])), .product(name: "DequeModule", package: "swift-collections"), .product(name: "OrderedCollections", package: "swift-collections"), diff --git a/Sources/SPMSQLite3/CMakeLists.txt b/Sources/SPMSQLite3/CMakeLists.txt index fb7fa18a1fe..46777305bd4 100644 --- a/Sources/SPMSQLite3/CMakeLists.txt +++ b/Sources/SPMSQLite3/CMakeLists.txt @@ -11,3 +11,6 @@ target_include_directories(SPMSQLite3 INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(SPMSQLite3 INTERFACE SQLite::SQLite3) +if(CMAKE_SYSTEM_NAME STREQUAL OpenBSD) + target_link_options(SPMSQLite3 INTERFACE "-L/usr/local/lib") +endif() diff --git a/Utilities/bootstrap b/Utilities/bootstrap index e87e21faed6..58920f40b82 100755 --- a/Utilities/bootstrap +++ b/Utilities/bootstrap @@ -926,6 +926,10 @@ def get_swiftpm_flags(args): ]) if '-openbsd' in args.build_target: + # Because of swiftlang/swift#80059, swiftpm only works + # with BTCFI disabled. + if 'aarch64' in args.build_target: + build_flags.extend(["-Xlinker", "-z", "-Xlinker", "nobtcfi"]) build_flags.extend(["-Xlinker", "-z", "-Xlinker", "origin"]) build_flags.extend(["-Xcc", "-I/usr/local/include"]) build_flags.extend(["-Xlinker", "-L/usr/local/lib"]) From 3d063d02076d3848db8b1c5c9e69d07f8a62f1c5 Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:16:02 -0400 Subject: [PATCH 14/99] Link test command test failures on Linux to issue (#8459) The TestCommandTests fail on Linux due to a common linker problem with swift build build system, which is the missing 'main' symbol problem. This is described further detail in #8439. Provide the link in the skip messages and remove the TODO tags since the investigation is complete and the reason for the failures is now known. Update the common test case logic so that it dumps the command execution stdout and stderr in the case of failure to help diagnose these result.xml failures immediately in the logs. --- Tests/CommandsTests/TestCommandTests.swift | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 4e27e48fc68..e4e79a69b61 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -225,8 +225,8 @@ class TestCommandTestCase: CommandsBuildProviderTestCase { let xUnitUnderTest = fixturePath.appending("result\(testRunner.fileSuffix).xml") // WHEN we execute swift-test in parallel while specifying xUnit generation - let extraCommandArgs = enableExperimentalFlag ? ["--experimental-xunit-message-failure"]: [], - _ = try await execute( + let extraCommandArgs = enableExperimentalFlag ? ["--experimental-xunit-message-failure"]: [] + let (stdout, stderr) = try await execute( [ "--parallel", "--verbose", @@ -239,6 +239,12 @@ class TestCommandTestCase: CommandsBuildProviderTestCase { throwIfCommandFails: false ) + if !FileManager.default.fileExists(atPath: xUnitUnderTest.pathString) { + // If the build failed then produce a output dump of what happened during the execution + print("\(stdout)") + print("\(stderr)") + } + // THEN we expect \(xUnitUnderTest) to exists XCTAssertFileExists(xUnitUnderTest) let contents: String = try localFileSystem.readFileContents(xUnitUnderTest) @@ -694,35 +700,35 @@ class TestCommandSwiftBuildTests: TestCommandTestCase { #if !os(macOS) override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("SWBINTTODO: Result XML could not be found. This looks to be a build layout issue. Further investigation is needed.") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("SWBINTTODO: Result XML could not be found. This looks to be a build layout issue. Further investigation is needed.") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("SWBINTTODO: Result XML could not be found. This looks to be a build layout issue. Further investigation is needed.") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("SWBINTTODO: Result XML could not be found. This looks to be a build layout issue. Further investigation is needed.") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestSkip() async throws { - throw XCTSkip("SWBINTTODO: This fails due to a linker error on Linux. Further investigation is needed.") + throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } override func testSwiftTestXMLOutputWhenEmpty() async throws { - throw XCTSkip("SWBINTTODO: This fails due to a linker error on Linux 'undefined reference to main'. Further investigation is needed.") + throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } override func testSwiftTestFilter() async throws { - throw XCTSkip("SWBINTTODO: This fails due to an unknown linker error on Linux. Further investigation is needed.") + throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } override func testSwiftTestParallel() async throws { - throw XCTSkip("SWBINTTODO: This fails due to the test expecting specific test output that appears to be empty on Linux. Further investigation is needed.") + throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } #endif } From 49e69c78cf6c54198b39cb701bfcc8d418dfbc95 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Mon, 14 Apr 2025 09:46:44 -0700 Subject: [PATCH 15/99] Revert "Copy helpers internally" again (#8494) ### Motivation: Pull request #8476 was breaking SwiftPM builds in Xcode (i.e., the _package resolution_ step): ### Modifications: This reverts commit 1d48e0a8e14d84e54feaabc24fc366f5a694b7fe. --- .../Tests/IntegrationTests/BasicTests.swift | 2 +- Package.swift | 6 +- Sources/_InternalTestSupport/Process.swift | 43 ------------ .../SkippedTestSupport.swift | 70 ------------------- .../RegistryClientTests.swift | 4 +- 5 files changed, 5 insertions(+), 120 deletions(-) delete mode 100644 Sources/_InternalTestSupport/Process.swift delete mode 100644 Sources/_InternalTestSupport/SkippedTestSupport.swift diff --git a/IntegrationTests/Tests/IntegrationTests/BasicTests.swift b/IntegrationTests/Tests/IntegrationTests/BasicTests.swift index f02fd1faa69..91d6abd483a 100644 --- a/IntegrationTests/Tests/IntegrationTests/BasicTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/BasicTests.swift @@ -94,7 +94,7 @@ private struct BasicTests { } @Test( - .skipHostOS(.windows, "'try!' expression unexpectedly raised an error: TSCBasic.Process.Error.missingExecutableProgram(program: \"which\")"), + .skipHostOS(.windows, "'try!' expression unexpectedly raised an error: TSCBasic.Process.Error.missingExecutableProgram(program: \"which\")") ) func testSwiftCompiler() throws { try withTemporaryDirectory { tempDir in diff --git a/Package.swift b/Package.swift index 28d9d2869f5..93baca641c2 100644 --- a/Package.swift +++ b/Package.swift @@ -725,7 +725,7 @@ let package = Package( // MARK: Additional Test Dependencies - .testTarget( + .target( /** SwiftPM internal build test suite support library */ name: "_InternalBuildTestSupport", dependencies: [ @@ -734,13 +734,12 @@ let package = Package( "SwiftBuildSupport", "_InternalTestSupport" ], - path: "Sources/_InternalBuildTestSupport", swiftSettings: [ .unsafeFlags(["-static"]), ] ), - .testTarget( + .target( /** SwiftPM internal test suite support library */ name: "_InternalTestSupport", dependencies: [ @@ -755,7 +754,6 @@ let package = Package( .product(name: "OrderedCollections", package: "swift-collections"), "Workspace", ], - path: "./Sources/_InternalTestSupport", swiftSettings: [ .unsafeFlags(["-static"]), ] diff --git a/Sources/_InternalTestSupport/Process.swift b/Sources/_InternalTestSupport/Process.swift deleted file mode 100644 index eacfcbc2896..00000000000 --- a/Sources/_InternalTestSupport/Process.swift +++ /dev/null @@ -1,43 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See http://swift.org/LICENSE.txt for license information - See http://swift.org/CONTRIBUTORS.txt for Swift project authors - */ - -public import Foundation - -public enum OperatingSystem: Hashable, Sendable { - case macOS - case windows - case linux - case android - case unknown -} - -extension ProcessInfo { - #if os(macOS) - public static let hostOperatingSystem = OperatingSystem.macOS - #elseif os(Linux) - public static let hostOperatingSystem = OperatingSystem.linux - #elseif os(Windows) - public static let hostOperatingSystem = OperatingSystem.windows - #else - public static let hostOperatingSystem = OperatingSystem.unknown - #endif - - #if os(Windows) - public static let EOL = "\r\n" - #else - public static let EOL = "\n" - #endif - - #if os(Windows) - public static let exeSuffix = ".exe" - #else - public static let exeSuffix = "" - #endif -} diff --git a/Sources/_InternalTestSupport/SkippedTestSupport.swift b/Sources/_InternalTestSupport/SkippedTestSupport.swift deleted file mode 100644 index b73727d1c64..00000000000 --- a/Sources/_InternalTestSupport/SkippedTestSupport.swift +++ /dev/null @@ -1,70 +0,0 @@ - -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See http://swift.org/LICENSE.txt for license information - See http://swift.org/CONTRIBUTORS.txt for Swift project authors - */ - -import class Foundation.FileManager -import class Foundation.ProcessInfo -import Testing - -extension Trait where Self == Testing.ConditionTrait { - /// Skip test if the host operating system does not match the running OS. - public static func requireHostOS(_ os: OperatingSystem, when condition: Bool = true) -> Self { - enabled("This test requires a \(os) host OS.") { - ProcessInfo.hostOperatingSystem == os && condition - } - } - - /// Skip test if the host operating system matches the running OS. - public static func skipHostOS(_ os: OperatingSystem, _ comment: Comment? = nil) -> Self { - disabled(comment ?? "This test cannot run on a \(os) host OS.") { - ProcessInfo.hostOperatingSystem == os - } - } - - /// Skip test unconditionally - public static func skip(_ comment: Comment? = nil) -> Self { - disabled(comment ?? "Unconditional skip, a comment should be added for the reason") { true } - } - - /// Skip test if the environment is self hosted. - public static func skipSwiftCISelfHosted(_ comment: Comment? = nil) -> Self { - disabled(comment ?? "SwiftCI is self hosted") { - ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil - } - } - - /// Skip test if the test environment has a restricted network access, i.e. cannot get to internet. - public static func requireUnrestrictedNetworkAccess(_ comment: Comment? = nil) -> Self { - disabled(comment ?? "CI Environment has restricted network access") { - ProcessInfo.processInfo.environment["SWIFTCI_RESTRICTED_NETWORK_ACCESS"] != nil - } - } - - /// Skip test if built by XCode. - public static func skipIfXcodeBuilt() -> Self { - disabled("Tests built by Xcode") { - #if Xcode - true - #else - false - #endif - } - } - - /// Constructs a condition trait that causes a test to be disabled if the Foundation process spawning implementation - /// is not using `posix_spawn_file_actions_addchdir`. - public static var requireThreadSafeWorkingDirectory: Self { - disabled("Thread-safe process working directory support is unavailable.") { - // Amazon Linux 2 has glibc 2.26, and glibc 2.29 is needed for posix_spawn_file_actions_addchdir_np support - FileManager.default.contents(atPath: "/etc/system-release") - .map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false - } - } -} diff --git a/Tests/PackageRegistryTests/RegistryClientTests.swift b/Tests/PackageRegistryTests/RegistryClientTests.swift index b8390c1900f..809fdf32b89 100644 --- a/Tests/PackageRegistryTests/RegistryClientTests.swift +++ b/Tests/PackageRegistryTests/RegistryClientTests.swift @@ -803,7 +803,7 @@ final class RegistryClientTests: XCTestCase { let availableManifests = try await registryClient.getAvailableManifests( package: identity, version: version, - observabilityScope: ObservabilitySystem.NOOP, + observabilityScope: ObservabilitySystem.NOOP ) XCTAssertEqual(availableManifests["Package.swift"]?.toolsVersion, .v5_5) @@ -3987,7 +3987,7 @@ extension RegistryClient { package: package.underlying, version: version, fileSystem: InMemoryFileSystem(), - observabilityScope: ObservabilitySystem.NOOP, + observabilityScope: ObservabilitySystem.NOOP ) } From 453e6d38dfdab79cb7ccab27ad974e1f3fe89e00 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 15 Apr 2025 14:22:22 -0400 Subject: [PATCH 16/99] Tests: Split SerializedJSONTests (#8496) Split the SerializedJSONTests into tests that pass and fail on Windows Related: #8433 rdar://148248105 --- .../Serialization/SerializedJSONTests.swift | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift index a7841df439f..b499a4eb1dc 100644 --- a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift +++ b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift @@ -16,8 +16,6 @@ import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails final class SerializedJSONTests: XCTestCase { func testPathInterpolation() throws { - try skipOnWindowsAsTestCurrentlyFails() - var path = try AbsolutePath(validating: #"/test\backslashes"#) var json: SerializedJSON = "\(path)" @@ -28,20 +26,26 @@ final class SerializedJSONTests: XCTestCase { #endif #if os(Windows) - path = try AbsolutePath(validating: #"\\?\C:\Users"#) + path = try AbsolutePath(validating: #"\??\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}\\"#) json = "\(path)" + XCTAssertEqual(json.underlying, #"\\??\\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}"#) + #endif + } + + func testPathInterpolationFailsOnWindows() throws { + try skipOnWindowsAsTestCurrentlyFails(because: "Expectations are not met") + +#if os(Windows) + var path = try AbsolutePath(validating: #"\\?\C:\Users"#) + var json: SerializedJSON = "\(path)" + XCTAssertEqual(json.underlying, #"C:\\Users"#) path = try AbsolutePath(validating: #"\\.\UNC\server\share\"#) json = "\(path)" XCTAssertEqual(json.underlying, #"\\.\\UNC\\server\\share"#) - - path = try AbsolutePath(validating: #"\??\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}\\"#) - json = "\(path)" - - XCTAssertEqual(json.underlying, #"\\??\\Volumes{b79de17a-a1ed-4c58-a353-731b7c4885a6}"#) - #endif +#endif } } From b69b7924f507938bb3cd66421badde422386971c Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 15 Apr 2025 15:43:19 -0400 Subject: [PATCH 17/99] Mark skipped test with a GitHub issue (#8502) --- Tests/BasicsTests/HTTPClientTests.swift | 2 +- Tests/BasicsTests/LegacyHTTPClientTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/BasicsTests/HTTPClientTests.swift b/Tests/BasicsTests/HTTPClientTests.swift index ef313889238..de4434e2190 100644 --- a/Tests/BasicsTests/HTTPClientTests.swift +++ b/Tests/BasicsTests/HTTPClientTests.swift @@ -226,7 +226,7 @@ final class HTTPClientTests: XCTestCase { } func testExponentialBackoff() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") let counter = SendableBox(0) let lastCall = SendableBox() diff --git a/Tests/BasicsTests/LegacyHTTPClientTests.swift b/Tests/BasicsTests/LegacyHTTPClientTests.swift index 38a8300ce83..b4de3965733 100644 --- a/Tests/BasicsTests/LegacyHTTPClientTests.swift +++ b/Tests/BasicsTests/LegacyHTTPClientTests.swift @@ -350,7 +350,7 @@ final class LegacyHTTPClientTests: XCTestCase { } func testExponentialBackoff() throws { - try skipOnWindowsAsTestCurrentlyFails() + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") let count = ThreadSafeBox(0) let lastCall = ThreadSafeBox() From dec1dede6011708d88475136a9a5a6c6b9d9775f Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Tue, 15 Apr 2025 13:21:19 -0700 Subject: [PATCH 18/99] Improve Swift Build error formatting (#8493) ### Motivation: This improves a bit the error/warning output when building with the new `--build-system swiftbuild`. This was the current output: ```shell warning: unknown Enabling the Swift language feature 'MemberImportVisibility' is recommended; set 'SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES' [] error: path("/Users/pmattos/Development/SampleProjects/SamplePackages/PackageAccessCLI/Sources/ExampleApp/main.swift", fileLocation: Optional(SwiftBuild.SwiftBuildMessage.DiagnosticInfo.Location.FileLocation.textual(line: 6, column: Optional(5)))) cannot find 'bar' in scope [] ``` ...and this is the improved one: ```shell warning: Enabling the Swift language feature 'MemberImportVisibility' is recommended; set 'SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES' error: /Users/pmattos/Development/SampleProjects/SamplePackages/PackageAccessCLI/Sources/ExampleApp/main.swift:5:5 cannot find 'foo2' in scope ``` Eventually, we should consider adopting the error output style from the `--build-system native` instead, i.e.: ```shell /Users/pmattos/Development/SampleProjects/SamplePackages/PackageAccessCLI/Sources/ExampleApp/main.swift:5:5: error: cannot find 'foo2' in scope 3 | print("Hello, world!") 4 | 5 | _ = foo2() | `- error: cannot find 'foo2' in scope 6 | _ = bar() 7 | ``` ### Modifications: These formatting changes are strictly local to the new `SwiftBuildSupport` target. ### Result: Slightly better error formatting :-) --- .../Tests/IntegrationTests/SwiftPMTests.swift | 41 ++++++------- .../SwiftBuildSupport/SwiftBuildSystem.swift | 57 ++++++++++++++++--- .../_InternalTestSupport/Observability.swift | 6 +- .../XCBuildSupportTests/PIFBuilderTests.swift | 2 +- 4 files changed, 74 insertions(+), 32 deletions(-) diff --git a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift index 19a6307a9c6..b35d0f6e274 100644 --- a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift @@ -110,29 +110,30 @@ private struct SwiftPMTests { .requireThreadSafeWorkingDirectory, .bug(id: 0, "SWBINTTODO: Linux: /lib/x86_64-linux-gnu/Scrt1.o:function _start: error:"), .bug("https://github.com/swiftlang/swift-package-manager/issues/8380", "lld-link: error: subsystem must be defined"), - .bug(id:0, "SWBINTTODO: MacOS: Could not find or use auto-linked library 'Testing': library 'Testing' not found"), + .bug(id: 0, "SWBINTTODO: MacOS: Could not find or use auto-linked library 'Testing': library 'Testing' not found"), arguments: BuildSystemProvider.allCases ) func packageInitLibrary(_ buildSystemProvider: BuildSystemProvider) throws { - do { - try withTemporaryDirectory { tmpDir in - let packagePath = tmpDir.appending(component: "foo") - try localFileSystem.createDirectory(packagePath) - try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "library") - try withKnownIssue(""" - Linux: /lib/x86_64-linux-gnu/Scrt1.o:function _start: error: undefined reference to 'main' - Windows: lld-link: error: subsystem must be defined - MacOS: Could not find or use auto-linked library 'Testing': library 'Testing' not found - """) { - try sh(swiftBuild, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue, "--vv") - let (stdout, stderr) = try sh( - swiftTest, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue, "--vv" - ) - #expect(!stderr.contains("error:")) - #expect(stdout.contains("Test Suite 'All tests' passed")) - } when: { - buildSystemProvider == .swiftbuild - } + try withTemporaryDirectory { tmpDir in + let packagePath = tmpDir.appending(component: "foo") + try localFileSystem.createDirectory(packagePath) + try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "library") + try withKnownIssue( + """ + Linux: /lib/x86_64-linux-gnu/Scrt1.o:function _start: error: undefined reference to 'main' + Windows: lld-link: error: subsystem must be defined + MacOS: Could not find or use auto-linked library 'Testing': library 'Testing' not found + """, + isIntermittent: true + ) { + try sh(swiftBuild, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue, "--vv") + let (stdout, stderr) = try sh( + swiftTest, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue, "--vv" + ) + #expect(!stderr.contains("error:")) + #expect(stdout.contains("Test Suite 'All tests' passed")) + } when: { + buildSystemProvider == .swiftbuild } } } diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 9127648ce3f..fe774d54173 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -353,15 +353,23 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } progressAnimation.update(step: step, total: 100, text: message) case .diagnostic(let info): - if info.kind == .error { - self.observabilityScope.emit(error: "\(info.location) \(info.message) \(info.fixIts)") - } else if info.kind == .warning { - self.observabilityScope.emit(warning: "\(info.location) \(info.message) \(info.fixIts)") - } else if info.kind == .note { - self.observabilityScope.emit(info: "\(info.location) \(info.message) \(info.fixIts)") - } else if info.kind == .remark { - self.observabilityScope.emit(debug: "\(info.location) \(info.message) \(info.fixIts)") + let fixItsDescription = if info.fixIts.hasContent { + ": " + info.fixIts.map { String(describing: $0) }.joined(separator: ", ") + } else { + "" + } + let message = if let locationDescription = info.location.userDescription { + "\(locationDescription) \(info.message)\(fixItsDescription)" + } else { + "\(info.message)\(fixItsDescription)" + } + let severity: Diagnostic.Severity = switch info.kind { + case .error: .error + case .warning: .warning + case .note: .info + case .remark: .debug } + self.observabilityScope.emit(severity: severity, message: message) case .taskOutput(let info): self.observabilityScope.emit(info: "\(info.data)") case .taskStarted(let info): @@ -509,6 +517,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } } +// MARK: - Helpers + extension String { /// Escape the usual shell related things, such as quoting, but also handle Windows /// back-slashes. @@ -541,3 +551,34 @@ extension Basics.Diagnostic.Severity { self <= .info } } + +#if canImport(SwiftBuild) + +fileprivate extension SwiftBuild.SwiftBuildMessage.DiagnosticInfo.Location { + var userDescription: String? { + switch self { + case .path(let path, let fileLocation): + switch fileLocation { + case .textual(let line, let column): + var description = "\(path):\(line)" + if let column { description += ":\(column)" } + return description + case .object(let identifier): + return "\(path):\(identifier)" + case .none: + return path + } + + case .buildSettings(let names): + return names.joined(separator: ", ") + + case .buildFiles(let buildFiles, let targetGUID): + return "\(targetGUID): " + buildFiles.map { String(describing: $0) }.joined(separator: ", ") + + case .unknown: + return nil + } + } +} + +#endif diff --git a/Sources/_InternalTestSupport/Observability.swift b/Sources/_InternalTestSupport/Observability.swift index 36c260ede79..74a9594f4bd 100644 --- a/Sources/_InternalTestSupport/Observability.swift +++ b/Sources/_InternalTestSupport/Observability.swift @@ -56,15 +56,14 @@ public struct TestingObservability { self.collector.hasWarnings } - final class Collector: ObservabilityHandlerProvider, DiagnosticsHandler, CustomStringConvertible { + fileprivate final class Collector: ObservabilityHandlerProvider, DiagnosticsHandler, CustomStringConvertible { var diagnosticsHandler: DiagnosticsHandler { self } - let diagnostics: ThreadSafeArrayStore private let verbose: Bool + let diagnostics = ThreadSafeArrayStore() init(verbose: Bool) { self.verbose = verbose - self.diagnostics = .init() } // TODO: do something useful with scope @@ -98,6 +97,7 @@ public func XCTAssertNoDiagnostics( ) { let diagnostics = problemsOnly ? diagnostics.filter { $0.severity >= .warning } : diagnostics if diagnostics.isEmpty { return } + let description = diagnostics.map { "- " + $0.description }.joined(separator: "\n") XCTFail("Found unexpected diagnostics: \n\(description)", file: file, line: line) } diff --git a/Tests/XCBuildSupportTests/PIFBuilderTests.swift b/Tests/XCBuildSupportTests/PIFBuilderTests.swift index fc480b34e27..172d59f255b 100644 --- a/Tests/XCBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/XCBuildSupportTests/PIFBuilderTests.swift @@ -2952,7 +2952,7 @@ final class PIFBuilderTests: XCTestCase { "/Foo/Sources/qux/main.swift" ) - let observability = ObservabilitySystem.makeForTesting() + let observability = ObservabilitySystem.makeForTesting(verbose: false) // Don't print expected [error]s. let graph = try loadModulesGraph( fileSystem: fs, manifests: [ From 1befa527a52dd5fd1d4ceb70beb8fbc713f7eeff Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 15 Apr 2025 23:33:11 -0400 Subject: [PATCH 19/99] Docs: Refer to swiftly in CONTRIBUTING.md (#8504) The Swift toolchain installation on swift.org indicate to use swiftly to install the toolchain on macOS and Linux. Update the CONTRIBUTING.md file to refer to swiftly for toolchain management. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b565d970a89..a49877c8e8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,7 +88,7 @@ $> swift --version Apple Swift version 5.3 ``` -Note: Alternatively use tools like [swiftenv](https://github.com/kylef/swiftenv) that help manage toolchains versions. +Note: Alternatively use tools like [swiftly](https://www.swift.org/swiftly/documentation/swiftlydocs/) that help manage toolchains versions. ## Local Development From 84322209c88cd590b7599d058e37cee478b20dc5 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Wed, 16 Apr 2025 08:59:37 -0400 Subject: [PATCH 20/99] Test: Enable ConcurrencyHelpersTests on Windows (#8506) --- Tests/BasicsTests/ConcurrencyHelpersTests.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/BasicsTests/ConcurrencyHelpersTests.swift b/Tests/BasicsTests/ConcurrencyHelpersTests.swift index fb47cfcd3dc..2efa891fded 100644 --- a/Tests/BasicsTests/ConcurrencyHelpersTests.swift +++ b/Tests/BasicsTests/ConcurrencyHelpersTests.swift @@ -14,15 +14,9 @@ import TSCTestSupport import XCTest -import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails - final class ConcurrencyHelpersTest: XCTestCase { let queue = DispatchQueue(label: "ConcurrencyHelpersTest", attributes: .concurrent) - override func setUpWithError() throws { - try skipOnWindowsAsTestCurrentlyFails() - } - func testThreadSafeKeyValueStore() { for _ in 0 ..< 100 { let sync = DispatchGroup() From 6245fa90f5c64ac478ef98399d6199d3ee9586da Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:24:18 -0400 Subject: [PATCH 21/99] Really fix duplicate module maps this time. (#8498) I had mucked with the fix to finish some testing and forgot to put the fix back. And the test didn't properly catch that. Fixing both. --- Sources/Build/BuildPlan/BuildPlan+Swift.swift | 2 +- Tests/BuildTests/BuildPlanTests.swift | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index f0c0b256cbb..51a9ad4679d 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -20,7 +20,7 @@ extension BuildPlan { func plan(swiftTarget: SwiftModuleBuildDescription) throws { // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target // builds against - for case .module(let dependency, let description) in swiftTarget.recursiveDependencies(using: self) { + for case .module(let dependency, let description) in swiftTarget.recursiveLinkDependencies(using: self) { switch dependency.underlying { case let underlyingTarget as ClangModule where underlyingTarget.type == .library: guard case let .clang(target)? = description else { diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 423afcd0e04..6d8741e7ad4 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -6972,8 +6972,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let myLib = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyLib" })).swift() - print(myLib.additionalFlags) - XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool/include")}), "flags shouldn't contain tools items") + XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool")}), "flags shouldn't contain tools items") } } From e842b0ed3feb04eb885b3c7a1fc5fbc86d51cd25 Mon Sep 17 00:00:00 2001 From: Dave Inglis Date: Wed, 16 Apr 2025 15:26:46 -0400 Subject: [PATCH 22/99] Implement --enable-parseable-module-interfaces for swift-build buildsystem (#8421) - this also fixes -enable-library-evolution when used as a unsafeFlags Closes: #8337 ### Modifications: sets SWIFT_EMIT_MODULE_INTERFACE build setting when option is set ### Result: the build option will include the .swftinertface files in the module folder --- .../SwiftBuildSupport/SwiftBuildSystem.swift | 5 + Tests/CommandsTests/BuildCommandTests.swift | 148 ++++++++++-------- 2 files changed, 88 insertions(+), 65 deletions(-) diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index fe774d54173..374a02cf517 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -478,6 +478,11 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { settings["ARCHS"] = architectures.joined(separator: " ") } + // support for --enable-parseable-module-interfaces + if buildParameters.driverParameters.enableParseableModuleInterfaces { + settings["SWIFT_EMIT_MODULE_INTERFACE"] = "YES" + } + // Generate the build parameters. var params = SwiftBuild.SWBBuildParameters() params.configurationName = buildParameters.configuration.swiftbuildName diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index bead37cf431..61db4261f59 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -38,7 +38,7 @@ class BuildCommandTestCases: CommandsBuildProviderTestCase { } @discardableResult - private func execute( + func execute( _ args: [String] = [], environment: Environment? = nil, packagePath: AbsolutePath? = nil @@ -70,7 +70,19 @@ class BuildCommandTestCases: CommandsBuildProviderTestCase { // is what `binContents` is meant to represent. return contents != ["output-file-map.json"] } - let moduleContents = (try? localFileSystem.getDirectoryContents(binPath.appending(component: "Modules"))) ?? [] + var moduleContents: [String] = [] + if buildSystemProvider == .native { + moduleContents = (try? localFileSystem.getDirectoryContents(binPath.appending(component: "Modules"))) ?? [] + } else { + let moduleDirs = (try? localFileSystem.getDirectoryContents(binPath).filter { + $0.hasSuffix(".swiftmodule") + }) ?? [] + for dir: String in moduleDirs { + moduleContents += + (try? localFileSystem.getDirectoryContents(binPath.appending(component: dir)).map { "\(dir)/\($0)" }) ?? [] + } + } + if cleanAfterward { try! await executeSwiftPackage( @@ -103,6 +115,10 @@ class BuildCommandTestCases: CommandsBuildProviderTestCase { XCTAssertMatch(stdout, .contains("USAGE: swift build")) } + func testBinSymlink() async throws { + XCTAssertTrue(false, "Must be implemented at build system test class.") + } + func testSeeAlso() async throws { let stdout = try await execute(["--help"]).stdout XCTAssertMatch(stdout, .contains("SEE ALSO: swift run, swift package, swift test")) @@ -190,48 +206,6 @@ class BuildCommandTestCases: CommandsBuildProviderTestCase { } } - func testBinSymlink() async throws { - try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in - let fullPath = try resolveSymlinks(fixturePath) - let targetPath = try fullPath.appending( - components: ".build", - UserToolchain.default.targetTriple.platformBuildPathComponent - ) - let xcbuildTargetPath = fullPath.appending(components: ".build", "apple") - try await XCTAssertAsyncEqual( - try await self.execute(["--show-bin-path"], packagePath: fullPath).stdout, - "\(targetPath.appending("debug").pathString)\n" - ) - try await XCTAssertAsyncEqual( - try await self.execute(["-c", "release", "--show-bin-path"], packagePath: fullPath).stdout, - "\(targetPath.appending("release").pathString)\n" - ) - - guard buildSystemProvider == .xcode || buildSystemProvider == .swiftbuild else { - // The remainder of this test only applies to XCBuild or Swift Build - return - } - - // Print correct path when building with XCBuild or Swift Build - let xcodeDebugOutput = try await execute( - ["-c", "debug", "--show-bin-path"], - packagePath: fullPath) - .stdout - let xcodeReleaseOutput = try await execute( - ["-c", "release", "--show-bin-path"], - packagePath: fullPath - ).stdout - XCTAssertEqual( - xcodeDebugOutput, - "\(xcbuildTargetPath.appending(components: "Products", "Debug").pathString)\n" - ) - XCTAssertEqual( - xcodeReleaseOutput, - "\(xcbuildTargetPath.appending(components: "Products", "Release").pathString)\n" - ) - } - } - func testSymlink() async throws { try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in let fullPath = try resolveSymlinks(fixturePath) @@ -792,6 +766,25 @@ class BuildCommandNativeTests: BuildCommandTestCases { override func testUsage() async throws { try await super.testUsage() } + + override func testBinSymlink() async throws { + try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in + let fullPath = try resolveSymlinks(fixturePath) + let targetPath = try fullPath.appending( + components: ".build", + UserToolchain.default.targetTriple.platformBuildPathComponent + ) + try await XCTAssertAsyncEqual( + try await self.execute(["--show-bin-path"], packagePath: fullPath).stdout, + "\(targetPath.appending("debug").pathString)\n" + ) + try await XCTAssertAsyncEqual( + try await self.execute(["-c", "release", "--show-bin-path"], packagePath: fullPath) + .stdout, + "\(targetPath.appending("release").pathString)\n" + ) + } + } } #if os(macOS) @@ -806,51 +799,51 @@ class BuildCommandXcodeTests: BuildCommandTestCases { } override func testAutomaticParseableInterfacesWithLibraryEvolution() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testNonReachableProductsAndTargetsFunctional() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testCodeCoverage() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testBuildStartMessage() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testBinSymlink() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testSymlink() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testSwiftGetVersion() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testParseableInterfaces() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testProductAndTarget() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testImportOfMissedDepWarning() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testGetTaskAllowEntitlement() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } override func testBuildCompleteMessage() async throws { - try XCTSkip("Test not implemented for xcode build system.") + throw XCTSkip("Test not implemented for xcode build system.") } } #endif @@ -866,9 +859,31 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { } override func testParseableInterfaces() async throws { - throw XCTSkip("SWBINTTODO: Test failed with swiftbuild engine because the --enable-parseable-module-interfaces flag doesn't yet produce .swiftinterface files. This needs to be investigated") + try await fixture(name: "Miscellaneous/ParseableInterfaces") { fixturePath in + do { + let result = try await build(["--enable-parseable-module-interfaces"], packagePath: fixturePath) + XCTAssertMatch(result.moduleContents, [.regex(#"A\.swiftmodule\/.*\.swiftinterface"#)]) + XCTAssertMatch(result.moduleContents, [.regex(#"B\.swiftmodule\/.*\.swiftmodule"#)]) + } catch SwiftPMError.executionFailure(_, _, let stderr) { + XCTFail(stderr) + } + } } + override func testBinSymlink() async throws { + try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in + let fullPath = try resolveSymlinks(fixturePath) + let targetPath = try fullPath.appending( + components: ".build", + UserToolchain.default.targetTriple.platformBuildPathComponent + ) + let debugPath = try await self.execute(["--show-bin-path"], packagePath: fullPath).stdout + XCTAssertMatch(debugPath, .regex(targetPath.appending(components: "Products", "Debug").pathString + "(\\-linux|\\-Windows)?\\n")) + let releasePath = try await self.execute(["-c", "release", "--show-bin-path"], packagePath: fullPath).stdout + XCTAssertMatch(releasePath, .regex(targetPath.appending(components: "Products", "Release").pathString + "(\\-linux|\\-Windows)?\\n")) + } + } + override func testGetTaskAllowEntitlement() async throws { throw XCTSkip("SWBINTTODO: Test failed because swiftbuild doesn't output precis codesign commands. Once swift run works with swiftbuild the test can be investigated.") } @@ -880,13 +895,20 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { override func testAtMainSupport() async throws { #if !os(macOS) throw XCTSkip("SWBINTTODO: File not found or missing libclang errors on non-macOS platforms. This needs to be investigated") - #endif - + #else try await super.testAtMainSupport() + #endif } override func testAutomaticParseableInterfacesWithLibraryEvolution() async throws { - throw XCTSkip("SWBINTTODO: The test fails because when the unsafe flag for a target is set to '-enable-library-evolution' it is not producing the correct .swiftinterface files. This needs to be investigated") + try await fixture(name: "Miscellaneous/LibraryEvolution") { fixturePath in + do { + let result = try await build([], packagePath: fixturePath) + XCTAssertMatch( + result.moduleContents, [.regex(#"A\.swiftmodule\/.*\.swiftinterface"#)]) + XCTAssertMatch(result.moduleContents, [.regex(#"B\.swiftmodule\/.*\.swiftmodule"#)]) + } + } } override func testImportOfMissedDepWarning() async throws { @@ -901,10 +923,6 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { throw XCTSkip("SWBINTTODO: Test fails because the dummy-swiftc used in the test isn't accepted by swift-build. This needs to be investigated") } - override func testBinSymlink() async throws { - throw XCTSkip("SWBINTTODO: Test fails because of a difference in the build layout. This needs to be updated to the expected path") - } - override func testSymlink() async throws { throw XCTSkip("SWBINTTODO: Test fails because of a difference in the build layout. This needs to be updated to the expected path") } @@ -927,7 +945,7 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { override func testBuildSystemDefaultSettings() async throws { #if os(Linux) - if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { + if FileManager.default.contents(atPath: "/etc/system-release").map( { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ) ?? false { throw XCTSkip("Skipping SwiftBuild testing on Amazon Linux because of platform issues.") } #endif From 861a87105df840ffedf2ae0b2d235310f621a479 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 16 Apr 2025 16:05:01 -0400 Subject: [PATCH 23/99] Add `swift test --attachments-path`. (#8492) This PR adds support for the `--attachments-path` CLI argument on `swift test` as approved in [ST-0009](https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0009-attachments.md). We will maintain support for the older `--experimental-attachments-path` version through Swift 6.2. The implementation of this option is held entirely in Swift Testing at this time, so no actual code is needed to support it, we just need to make sure Swift Argument Parser doesn't complain about it. Resolves rdar://147753584. @plemarquand has integration tests but they're blocked on a newer toolchain; unit testing of this argument exists in the Swift Testing repo. --- Sources/Commands/SwiftTestCommand.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index 0eaf6b8623c..c4c6b45bc62 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -124,6 +124,11 @@ struct TestEventStreamOptions: ParsableArguments { @Option(name: .customLong("experimental-attachments-path"), help: .private) var experimentalAttachmentsPath: AbsolutePath? + + /// Path for writing attachments (Swift Testing only.) + @Option(name: .customLong("attachments-path"), + help: "Path where attachments should be written (Swift Testing only). This path must be an existing directory the current user can write to. If not specified, any attachments created during testing are discarded.") + var attachmentsPath: AbsolutePath? } struct TestCommandOptions: ParsableArguments { From 0b230739a426b1edd5f61f627fce0c78401d0384 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Wed, 16 Apr 2025 17:34:12 -0700 Subject: [PATCH 24/99] Tests: Ensure we get clean test fixtures (#8507) ### Motivation: When running tests ensure the *test fixtures* we copy over to temporary directories don't carry over any previous build information. ### Modifications: When copying packages from `/Fixtures/**` we now ensure we delete any `.build` or `.swiftpm` directories, if any. --- Sources/_InternalTestSupport/misc.swift | 17 +++++++++++++---- Tests/CommandsTests/BuildCommandTests.swift | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Sources/_InternalTestSupport/misc.swift b/Sources/_InternalTestSupport/misc.swift index b8bdf00999b..0cc73af222d 100644 --- a/Sources/_InternalTestSupport/misc.swift +++ b/Sources/_InternalTestSupport/misc.swift @@ -173,13 +173,22 @@ fileprivate func verifyFixtureExists(at fixtureSubpath: RelativePath, file: Stat return fixtureDir } -fileprivate func setup(fixtureDir: AbsolutePath, in tmpDirPath: AbsolutePath, copyName: String, createGitRepo: Bool = true) throws -> AbsolutePath { +fileprivate func setup( + fixtureDir: AbsolutePath, + in tmpDirPath: AbsolutePath, + copyName: String, + createGitRepo: Bool = true +) throws -> AbsolutePath { func copy(from srcDir: AbsolutePath, to dstDir: AbsolutePath) throws { -#if os(Windows) + #if os(Windows) try localFileSystem.copy(from: srcDir, to: dstDir) -#else + #else try systemQuietly("cp", "-R", "-H", srcDir.pathString, dstDir.pathString) -#endif + #endif + + // Ensure we get a clean test fixture. + try localFileSystem.removeFileTree(dstDir.appending(component: ".build")) + try localFileSystem.removeFileTree(dstDir.appending(component: ".swiftpm")) } // The fixture contains either a checkout or just a Git directory. diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 61db4261f59..20cfedce6d8 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -209,8 +209,8 @@ class BuildCommandTestCases: CommandsBuildProviderTestCase { func testSymlink() async throws { try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in let fullPath = try resolveSymlinks(fixturePath) - let targetPath = try fullPath.appending( - components: ".build", + let targetPath = try fullPath.appending(components: + ".build", UserToolchain.default.targetTriple.platformBuildPathComponent ) // Test symlink. From f673bf6225fcd0c24bbfc324648bb1f7f78e201e Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:35:47 -0400 Subject: [PATCH 25/99] Revert "Really fix duplicate module maps this time." (#8517) Reverts swiftlang/swift-package-manager#8498 Broke swift-foundation build. --- Sources/Build/BuildPlan/BuildPlan+Swift.swift | 2 +- Tests/BuildTests/BuildPlanTests.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index 51a9ad4679d..f0c0b256cbb 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -20,7 +20,7 @@ extension BuildPlan { func plan(swiftTarget: SwiftModuleBuildDescription) throws { // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target // builds against - for case .module(let dependency, let description) in swiftTarget.recursiveLinkDependencies(using: self) { + for case .module(let dependency, let description) in swiftTarget.recursiveDependencies(using: self) { switch dependency.underlying { case let underlyingTarget as ClangModule where underlyingTarget.type == .library: guard case let .clang(target)? = description else { diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6d8741e7ad4..423afcd0e04 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -6972,7 +6972,8 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let myLib = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyLib" })).swift() - XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool")}), "flags shouldn't contain tools items") + print(myLib.additionalFlags) + XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool/include")}), "flags shouldn't contain tools items") } } From 393a8f32da9f49203932400be4e4b04eefc7b1e8 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Thu, 17 Apr 2025 01:03:29 -0700 Subject: [PATCH 26/99] Adopt new PIF builder in SwiftBuildSupport (#8454) ### Motivation: Switch from the legacy PIF builder in `SwiftBuildSupport` to the new one, introduced by PR #8405. The new PIF builder (i.e., `SwiftBuildSupport/PackagePIFBuilder*.swift`) is the exact same we use in Xcode. ### Modifications: Replaces the old PIF builder (i.e., `SwiftBuildSupport/PIF.swift` and `SwiftBuildSupport/PIFBuilder.swift` ) with the new one (i.e., `SwiftBuildSupport/PackagePIFBuilder*.swift`). ### Result: The new PIF builder now fully replaces the legacy PIF builder. In particular, all these Swift Build tests are passing (i.e., same as before this PR): * BuildPlanSwiftBuildTests * APIDiffSwiftBuildTests * BuildCommandSwiftBuildTests * PackageCommandSwiftBuildTests * RunCommandSwiftBuildTests * TestCommandSwiftBuildTests I also improved the PIF logging too. Try the `--very-verbose` option to see what I mean :-) Tracked by rdar://147527170. --- .../Tests/IntegrationTests/SwiftPMTests.swift | 19 +- Sources/SwiftBuildSupport/BuildSystem.swift | 2 +- Sources/SwiftBuildSupport/PIF.swift | 1253 +--------- Sources/SwiftBuildSupport/PIFBuilder.swift | 2021 ++--------------- .../PackagePIFBuilder+Helpers.swift | 112 +- .../SwiftBuildSupport/PackagePIFBuilder.swift | 99 +- .../PackagePIFProjectBuilder+Modules.swift | 72 +- .../PackagePIFProjectBuilder+Products.swift | 80 +- .../PackagePIFProjectBuilder.swift | 17 +- Tests/BuildTests/BuildPlanTests.swift | 5 +- Tests/CommandsTests/BuildCommandTests.swift | 20 +- 11 files changed, 599 insertions(+), 3101 deletions(-) diff --git a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift index b35d0f6e274..770d046ee14 100644 --- a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift @@ -75,10 +75,15 @@ private struct SwiftPMTests { } @Test( + .requireHostOS(.windows, when: false), .requireThreadSafeWorkingDirectory, .bug( "https://github.com/swiftlang/swift-package-manager/issues/8416", - "swift run using --build-system swiftbuild fails to run executable" + "[Linux] swift run using --build-system swiftbuild fails to run executable" + ), + .bug( + "https://github.com/swiftlang/swift-package-manager/issues/8514", + "[Windows] Integration test SwiftPMTests.packageInitExecutable with --build-system swiftbuild is skipped" ), arguments: BuildSystemProvider.allCases ) @@ -119,12 +124,12 @@ private struct SwiftPMTests { try localFileSystem.createDirectory(packagePath) try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "library") try withKnownIssue( - """ - Linux: /lib/x86_64-linux-gnu/Scrt1.o:function _start: error: undefined reference to 'main' - Windows: lld-link: error: subsystem must be defined - MacOS: Could not find or use auto-linked library 'Testing': library 'Testing' not found - """, - isIntermittent: true + """ + Linux: /lib/x86_64-linux-gnu/Scrt1.o:function _start: error: undefined reference to 'main' + Windows: lld-link: error: subsystem must be defined + MacOS: Could not find or use auto-linked library 'Testing': library 'Testing' not found + """, + isIntermittent: true ) { try sh(swiftBuild, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue, "--vv") let (stdout, stderr) = try sh( diff --git a/Sources/SwiftBuildSupport/BuildSystem.swift b/Sources/SwiftBuildSupport/BuildSystem.swift index 0418c5d3ce9..41f955eb3b6 100644 --- a/Sources/SwiftBuildSupport/BuildSystem.swift +++ b/Sources/SwiftBuildSupport/BuildSystem.swift @@ -14,7 +14,7 @@ extension BuildSubset { var pifTargetName: String { switch self { case .product(let name, _): - _PackagePIFProjectBuilder.targetName(for: name) + targetName(forProductName: name) case .target(let name, _): name case .allExcludingTests: diff --git a/Sources/SwiftBuildSupport/PIF.swift b/Sources/SwiftBuildSupport/PIF.swift index fa295895139..8c74f90ffcb 100644 --- a/Sources/SwiftBuildSupport/PIF.swift +++ b/Sources/SwiftBuildSupport/PIF.swift @@ -17,6 +17,9 @@ import PackageModel import struct TSCBasic.ByteString +#if canImport(SwiftBuild) +import enum SwiftBuild.ProjectModel + /// The Project Interchange Format (PIF) is a structured representation of the /// project model created by clients to send to SwiftBuild. /// @@ -27,39 +30,40 @@ import struct TSCBasic.ByteString /// between builds which use different schemes or configurations), and can be /// incrementally updated by clients when something changes. public enum PIF { - /// This is used as part of the signature for the high-level PIF objects, to ensure that changes to the PIF schema - /// are represented by the objects which do not use a content-based signature scheme (workspaces and projects, - /// currently). - static let schemaVersion = 11 - /// The type used for identifying PIF objects. - public typealias GUID = String - + public typealias GUID = ProjectModel.GUID + /// The top-level PIF object. public struct TopLevelObject: Encodable { public let workspace: PIF.Workspace - + public init(workspace: PIF.Workspace) { self.workspace = workspace } - + public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() - + // Encode the workspace. try container.encode(workspace) - + // Encode the projects and their targets. for project in workspace.projects { try container.encode(project) - - for target in project.targets { - try container.encode(target) + let targets = project.underlying.targets + + for target in targets where !target.id.hasSuffix(.dynamic) { + try container.encode(Target(wrapping: target)) + } + + // Add *dynamic variants* at the end just to have a clear split from other targets. + for target in targets where target.id.hasSuffix(.dynamic) { + try container.encode(Target(wrapping: target)) } } } } - + /// Represents a high-level PIF object. /// /// For instance, a JSON serialized *workspace* might look like this: @@ -82,1219 +86,202 @@ public enum PIF { class var type: String { fatalError("\(self) missing implementation") } - - let type: String? - + + let type: String + fileprivate init() { - type = Swift.type(of: self).type + type = Self.type } - + fileprivate enum CodingKeys: CodingKey { case type case signature, contents // Used by subclasses. } - + public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(Swift.type(of: self).type, forKey: .type) + var superContainer = encoder.container(keyedBy: CodingKeys.self) + try superContainer.encode(type, forKey: .type) } - + required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - type = try container.decode(String.self, forKey: .type) + let superContainer = try decoder.container(keyedBy: CodingKeys.self) + self.type = try superContainer.decode(String.self, forKey: .type) + + guard self.type == Self.type else { + throw InternalError("Expected same type for high-level object: \(self.type)") + } } } - + + /// The high-level PIF *workspace* object. public final class Workspace: HighLevelObject { override class var type: String { "workspace" } - + public let guid: GUID public var name: String public var path: AbsolutePath public var projects: [Project] var signature: String? - public init(guid: GUID, name: String, path: AbsolutePath, projects: [Project]) { - precondition(!guid.isEmpty) + public init(guid: GUID, name: String, path: AbsolutePath, projects: [ProjectModel.Project]) { + precondition(!guid.value.isEmpty) precondition(!name.isEmpty) - precondition(Set(projects.map({ $0.guid })).count == projects.count) - + precondition(Set(projects.map(\.id)).count == projects.count) + self.guid = guid self.name = name self.path = path - self.projects = projects + self.projects = projects.map { Project(wrapping: $0) } super.init() } - + private enum CodingKeys: CodingKey { - case guid, name, path, projects, signature + case guid, name, path, projects } - + public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) + var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) + + try contents.encode("\(guid)", forKey: .guid) try contents.encode(name, forKey: .name) try contents.encode(path, forKey: .path) - + try contents.encode(projects.map(\.signature), forKey: .projects) + if encoder.userInfo.keys.contains(.encodeForSwiftBuild) { guard let signature else { - throw InternalError("Expected to have workspace signature when encoding for SwiftBuild") + throw InternalError("Expected to have workspace *signature* when encoding for SwiftBuild") } try superContainer.encode(signature, forKey: .signature) - try contents.encode(projects.map({ $0.signature }), forKey: .projects) - } else { - try contents.encode(projects, forKey: .projects) } } - + public required init(from decoder: Decoder) throws { let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - - let guidString = try contents.decode(GUID.self, forKey: .guid) - self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) + + self.guid = try contents.decode(GUID.self, forKey: .guid) self.name = try contents.decode(String.self, forKey: .name) self.path = try contents.decode(AbsolutePath.self, forKey: .path) self.projects = try contents.decode([Project].self, forKey: .projects) + try super.init(from: decoder) } } - - /// A PIF project, consisting of a tree of groups and file references, a list of targets, and some additional - /// information. + + /// A high-level PIF *project* object. public final class Project: HighLevelObject { override class var type: String { "project" } - - public let guid: GUID - public var name: String - public var path: AbsolutePath - public var projectDirectory: AbsolutePath - public var developmentRegion: String - public var buildConfigurations: [BuildConfiguration] - public var targets: [BaseTarget] - public var groupTree: Group + + public var underlying: ProjectModel.Project var signature: String? - - public init( - guid: GUID, - name: String, - path: AbsolutePath, - projectDirectory: AbsolutePath, - developmentRegion: String, - buildConfigurations: [BuildConfiguration], - targets: [BaseTarget], - groupTree: Group - ) { - precondition(!guid.isEmpty) - precondition(!name.isEmpty) - precondition(!developmentRegion.isEmpty) - precondition(Set(targets.map({ $0.guid })).count == targets.count) - precondition(Set(buildConfigurations.map({ $0.guid })).count == buildConfigurations.count) - - self.guid = guid - self.name = name - self.path = path - self.projectDirectory = projectDirectory - self.developmentRegion = developmentRegion - self.buildConfigurations = buildConfigurations - self.targets = targets - self.groupTree = groupTree + var id: ProjectModel.GUID { underlying.id } + + public init(wrapping underlying: ProjectModel.Project) { + precondition(!underlying.name.isEmpty) + precondition(!underlying.id.value.isEmpty) + precondition(!underlying.path.isEmpty) + precondition(!underlying.projectDir.isEmpty) + + precondition(Set(underlying.targets.map(\.id)).count == underlying.targets.count) + precondition(Set(underlying.buildConfigs.map(\.id)).count == underlying.buildConfigs.count) + + self.underlying = underlying super.init() } - - private enum CodingKeys: CodingKey { - case guid, projectName, projectIsPackage, path, projectDirectory, developmentRegion, defaultConfigurationName, buildConfigurations, targets, groupTree, signature - } - - public override func encode(to encoder: Encoder) throws { + + public override func encode(to encoder: any Encoder) throws { try super.encode(to: encoder) var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) - var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) - try contents.encode(name, forKey: .projectName) - try contents.encode("true", forKey: .projectIsPackage) - try contents.encode(path, forKey: .path) - try contents.encode(projectDirectory, forKey: .projectDirectory) - try contents.encode(developmentRegion, forKey: .developmentRegion) - try contents.encode("Release", forKey: .defaultConfigurationName) - try contents.encode(buildConfigurations, forKey: .buildConfigurations) + try superContainer.encode(underlying, forKey: .contents) if encoder.userInfo.keys.contains(.encodeForSwiftBuild) { guard let signature else { - throw InternalError("Expected to have project signature when encoding for SwiftBuild") + throw InternalError("Expected to have project *signature* when encoding for SwiftBuild") } try superContainer.encode(signature, forKey: .signature) - try contents.encode(targets.map{ $0.signature }, forKey: .targets) - } else { - try contents.encode(targets, forKey: .targets) } - - try contents.encode(groupTree, forKey: .groupTree) } - + public required init(from decoder: Decoder) throws { let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) - let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - - let guidString = try contents.decode(GUID.self, forKey: .guid) - self.guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - self.name = try contents.decode(String.self, forKey: .projectName) - self.path = try contents.decode(AbsolutePath.self, forKey: .path) - self.projectDirectory = try contents.decode(AbsolutePath.self, forKey: .projectDirectory) - self.developmentRegion = try contents.decode(String.self, forKey: .developmentRegion) - self.buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations) - - let untypedTargets = try contents.decode([UntypedTarget].self, forKey: .targets) - var targetContainer = try contents.nestedUnkeyedContainer(forKey: .targets) - self.targets = try untypedTargets.map { target in - let type = target.contents.type - switch type { - case "aggregate": - return try targetContainer.decode(AggregateTarget.self) - case "standard", "packageProduct": - return try targetContainer.decode(Target.self) - default: - throw InternalError("unknown target type \(type)") - } - } - - self.groupTree = try contents.decode(Group.self, forKey: .groupTree) + self.underlying = try superContainer.decode(ProjectModel.Project.self, forKey: .contents) + try super.init(from: decoder) } } - - /// Abstract base class for all items in the group hierarchy. - public class Reference: HighLevelObject { - /// Determines the base path for a reference's relative path. - public enum SourceTree: String, Codable { - - /// Indicates that the path is relative to the source root (i.e. the "project directory"). - case sourceRoot = "SOURCE_ROOT" - - /// Indicates that the path is relative to the path of the parent group. - case group = "" - - /// Indicates that the path is relative to the effective build directory (which varies depending on active - /// scheme, active run destination, or even an overridden build setting. - case builtProductsDir = "BUILT_PRODUCTS_DIR" - - /// Indicates that the path is an absolute path. - case absolute = "" - } - - public let guid: GUID - - /// Relative path of the reference. It is usually a literal, but may in fact contain build settings. - public var path: String - - /// Determines the base path for the reference's relative path. - public var sourceTree: SourceTree - - /// Name of the reference, if different from the last path component (if not set, the last path component will - /// be used as the name). - public var name: String? - - fileprivate init( - guid: GUID, - path: String, - sourceTree: SourceTree, - name: String? - ) { - precondition(!guid.isEmpty) - precondition(!(name?.isEmpty ?? false)) - - self.guid = guid - self.path = path - self.sourceTree = sourceTree - self.name = name + + /// A high-level PIF *target* object. + private final class Target: HighLevelObject { + override class var type: String { "target" } + + public var underlying: ProjectModel.BaseTarget + var id: ProjectModel.GUID { underlying.id } + + public init(wrapping underlying: ProjectModel.BaseTarget) { + precondition(!underlying.id.value.isEmpty) + precondition(!underlying.common.name.isEmpty) + + self.underlying = underlying super.init() } - - private enum CodingKeys: CodingKey { - case guid, sourceTree, path, name, type - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(guid, forKey: .guid) - try container.encode(sourceTree, forKey: .sourceTree) - try container.encode(path, forKey: .path) - try container.encode(name ?? path, forKey: .name) - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.guid = try container.decode(String.self, forKey: .guid) - self.sourceTree = try container.decode(SourceTree.self, forKey: .sourceTree) - self.path = try container.decode(String.self, forKey: .path) - self.name = try container.decodeIfPresent(String.self, forKey: .name) - try super.init(from: decoder) - } - } - - /// A reference to a file system entity (a file, folder, etc). - public final class FileReference: Reference { - override class var type: String { "file" } - - public var fileType: String - - public init( - guid: GUID, - path: String, - sourceTree: SourceTree = .group, - name: String? = nil, - fileType: String? = nil - ) { - self.fileType = fileType ?? FileReference.fileTypeIdentifier(forPath: path) - super.init(guid: guid, path: path, sourceTree: sourceTree, name: name) - } - - private enum CodingKeys: CodingKey { - case fileType - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(fileType, forKey: .fileType) - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.fileType = try container.decode(String.self, forKey: .fileType) - try super.init(from: decoder) - } - } - - /// A group that can contain References (FileReferences and other Groups). The resolved path of a group is used as - /// the base path for any child references whose source tree type is GroupRelative. - public final class Group: Reference { - override class var type: String { "group" } - - public var children: [Reference] - - public init( - guid: GUID, - path: String, - sourceTree: SourceTree = .group, - name: String? = nil, - children: [Reference] - ) { - precondition( - Set(children.map({ $0.guid })).count == children.count, - "multiple group children with the same guid: \(children.map({ $0.guid }))" - ) - - self.children = children - - super.init(guid: guid, path: path, sourceTree: sourceTree, name: name) - } - - private enum CodingKeys: CodingKey { - case children, type - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(children, forKey: .children) - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let untypedChildren = try container.decode([HighLevelObject].self, forKey: .children) - var childrenContainer = try container.nestedUnkeyedContainer(forKey: .children) - - self.children = try untypedChildren.map { child in - switch child.type { - case Group.type: - return try childrenContainer.decode(Group.self) - case FileReference.type: - return try childrenContainer.decode(FileReference.self) - default: - throw InternalError("unknown reference type \(child.type ?? "")") - } - } - - try super.init(from: decoder) - } - } - - /// Represents a dependency on another target (identified by its PIF GUID). - public struct TargetDependency: Codable { - /// Identifier of depended-upon target. - public var targetGUID: String - - /// The platform filters for this target dependency. - public var platformFilters: [PlatformFilter] - - public init(targetGUID: String, platformFilters: [PlatformFilter] = []) { - self.targetGUID = targetGUID - self.platformFilters = platformFilters - } - - private enum CodingKeys: CodingKey { - case guid, platformFilters - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("\(targetGUID)@\(schemaVersion)", forKey: .guid) - - if !platformFilters.isEmpty { - try container.encode(platformFilters, forKey: .platformFilters) - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let targetGUIDString = try container.decode(String.self, forKey: .guid) - self.targetGUID = String(targetGUIDString.dropLast("\(schemaVersion)".count + 1)) - platformFilters = try container.decodeIfPresent([PlatformFilter].self, forKey: .platformFilters) ?? [] - } - } - - public class BaseTarget: HighLevelObject { - class override var type: String { "target" } - public let guid: GUID - public var name: String - public var buildConfigurations: [BuildConfiguration] - public var buildPhases: [BuildPhase] - public var dependencies: [TargetDependency] - public var impartedBuildProperties: ImpartedBuildProperties - var signature: String? - - fileprivate init( - guid: GUID, - name: String, - buildConfigurations: [BuildConfiguration], - buildPhases: [BuildPhase], - dependencies: [TargetDependency], - impartedBuildSettings: PIF.BuildSettings, - signature: String? - ) { - self.guid = guid - self.name = name - self.buildConfigurations = buildConfigurations - self.buildPhases = buildPhases - self.dependencies = dependencies - impartedBuildProperties = ImpartedBuildProperties(settings: impartedBuildSettings) - self.signature = signature - super.init() - } - - public required init(from decoder: Decoder) throws { - throw InternalError("init(from:) has not been implemented") - } - } - - public final class AggregateTarget: BaseTarget { - public init( - guid: GUID, - name: String, - buildConfigurations: [BuildConfiguration], - buildPhases: [BuildPhase], - dependencies: [TargetDependency], - impartedBuildSettings: PIF.BuildSettings - ) { - super.init( - guid: guid, - name: name, - buildConfigurations: buildConfigurations, - buildPhases: buildPhases, - dependencies: dependencies, - impartedBuildSettings: impartedBuildSettings, - signature: nil - ) - } - - private enum CodingKeys: CodingKey { - case type, guid, name, buildConfigurations, buildPhases, dependencies, impartedBuildProperties, signature - } - - public override func encode(to encoder: Encoder) throws { + + public override func encode(to encoder: any Encoder) throws { try super.encode(to: encoder) var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) - var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - try contents.encode("aggregate", forKey: .type) - try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) - try contents.encode(name, forKey: .name) - try contents.encode(buildConfigurations, forKey: .buildConfigurations) - try contents.encode(buildPhases, forKey: .buildPhases) - try contents.encode(dependencies, forKey: .dependencies) - try contents.encode(impartedBuildProperties, forKey: .impartedBuildProperties) + try superContainer.encode(underlying, forKey: .contents) if encoder.userInfo.keys.contains(.encodeForSwiftBuild) { - guard let signature else { - throw InternalError("Expected to have \(Swift.type(of: self)) signature when encoding for SwiftBuild") + guard let signature = underlying.common.signature else { + throw InternalError("Expected to have target *signature* when encoding for SwiftBuild") } try superContainer.encode(signature, forKey: .signature) } } - + public required init(from decoder: Decoder) throws { + // FIXME: Remove all support for decoding PIF objects in SwiftBuildSupport? rdar://149003797 + fatalError("Decoding not implemented") + /* let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) - let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - - let guidString = try contents.decode(GUID.self, forKey: .guid) - let guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - - let name = try contents.decode(String.self, forKey: .name) - let buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations) - - let untypedBuildPhases = try contents.decode([HighLevelObject].self, forKey: .buildPhases) - var buildPhasesContainer = try contents.nestedUnkeyedContainer(forKey: .buildPhases) - - let buildPhases: [BuildPhase] = try untypedBuildPhases.map { - guard let type = $0.type else { - throw InternalError("Expected type in build phase \($0)") - } - return try BuildPhase.decode(container: &buildPhasesContainer, type: type) - } - - let dependencies = try contents.decode([TargetDependency].self, forKey: .dependencies) - let impartedBuildProperties = try contents.decode(BuildSettings.self, forKey: .impartedBuildProperties) - - super.init( - guid: guid, - name: name, - buildConfigurations: buildConfigurations, - buildPhases: buildPhases, - dependencies: dependencies, - impartedBuildSettings: impartedBuildProperties, - signature: nil - ) - } - } - - /// An Xcode target, representing a single entity to build. - public final class Target: BaseTarget { - public enum ProductType: String, Codable { - case application = "com.apple.product-type.application" - case staticArchive = "com.apple.product-type.library.static" - case objectFile = "com.apple.product-type.objfile" - case dynamicLibrary = "com.apple.product-type.library.dynamic" - case framework = "com.apple.product-type.framework" - case executable = "com.apple.product-type.tool" - case unitTest = "com.apple.product-type.bundle.unit-test" - case bundle = "com.apple.product-type.bundle" - case packageProduct = "packageProduct" - } - - public var productName: String - public var productType: ProductType - - public init( - guid: GUID, - name: String, - productType: ProductType, - productName: String, - buildConfigurations: [BuildConfiguration], - buildPhases: [BuildPhase], - dependencies: [TargetDependency], - impartedBuildSettings: PIF.BuildSettings - ) { - self.productType = productType - self.productName = productName - - super.init( - guid: guid, - name: name, - buildConfigurations: buildConfigurations, - buildPhases: buildPhases, - dependencies: dependencies, - impartedBuildSettings: impartedBuildSettings, - signature: nil - ) - } - - private enum CodingKeys: CodingKey { - case guid, name, dependencies, buildConfigurations, type, frameworksBuildPhase, productTypeIdentifier, productReference, buildRules, buildPhases, impartedBuildProperties, signature - } - - override public func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) - var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - try contents.encode("\(guid)@\(schemaVersion)", forKey: .guid) - try contents.encode(name, forKey: .name) - try contents.encode(dependencies, forKey: .dependencies) - try contents.encode(buildConfigurations, forKey: .buildConfigurations) - - if encoder.userInfo.keys.contains(.encodeForSwiftBuild) { - guard let signature else { - throw InternalError("Expected to have \(Swift.type(of: self)) signature when encoding for SwiftBuild") - } - try superContainer.encode(signature, forKey: .signature) - } - - if productType == .packageProduct { - try contents.encode("packageProduct", forKey: .type) - - // Add the framework build phase, if present. - if let phase = buildPhases.first as? PIF.FrameworksBuildPhase { - try contents.encode(phase, forKey: .frameworksBuildPhase) - } - } else { - try contents.encode("standard", forKey: .type) - try contents.encode(productType, forKey: .productTypeIdentifier) - - let productReference = [ - "type": "file", - "guid": "PRODUCTREF-\(guid)", - "name": productName, - ] - try contents.encode(productReference, forKey: .productReference) - - try contents.encode([String](), forKey: .buildRules) - try contents.encode(buildPhases, forKey: .buildPhases) - try contents.encode(impartedBuildProperties, forKey: .impartedBuildProperties) - } - } - - public required init(from decoder: Decoder) throws { - let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) - let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - - let guidString = try contents.decode(GUID.self, forKey: .guid) - let guid = String(guidString.dropLast("\(schemaVersion)".count + 1)) - let name = try contents.decode(String.self, forKey: .name) - let buildConfigurations = try contents.decode([BuildConfiguration].self, forKey: .buildConfigurations) - let dependencies = try contents.decode([TargetDependency].self, forKey: .dependencies) - - let type = try contents.decode(String.self, forKey: .type) - - let buildPhases: [BuildPhase] - let impartedBuildProperties: ImpartedBuildProperties - - if type == "packageProduct" { - self.productType = .packageProduct - self.productName = "" - let fwkBuildPhase = try contents.decodeIfPresent(FrameworksBuildPhase.self, forKey: .frameworksBuildPhase) - buildPhases = fwkBuildPhase.map{ [$0] } ?? [] - impartedBuildProperties = ImpartedBuildProperties(settings: BuildSettings()) - } else if type == "standard" { - self.productType = try contents.decode(ProductType.self, forKey: .productTypeIdentifier) - - let productReference = try contents.decode([String: String].self, forKey: .productReference) - self.productName = productReference["name"]! - - let untypedBuildPhases = try contents.decodeIfPresent([HighLevelObject].self, forKey: .buildPhases) ?? [] - var buildPhasesContainer = try contents.nestedUnkeyedContainer(forKey: .buildPhases) - - buildPhases = try untypedBuildPhases.map { - guard let type = $0.type else { - throw InternalError("Expected type in build phase \($0)") - } - return try BuildPhase.decode(container: &buildPhasesContainer, type: type) - } - - impartedBuildProperties = try contents.decode(ImpartedBuildProperties.self, forKey: .impartedBuildProperties) - } else { - throw InternalError("Unhandled target type \(type)") - } - - super.init( - guid: guid, - name: name, - buildConfigurations: buildConfigurations, - buildPhases: buildPhases, - dependencies: dependencies, - impartedBuildSettings: impartedBuildProperties.buildSettings, - signature: nil - ) - } - } - - /// Abstract base class for all build phases in a target. - public class BuildPhase: HighLevelObject { - static func decode(container: inout UnkeyedDecodingContainer, type: String) throws -> BuildPhase { - switch type { - case HeadersBuildPhase.type: - return try container.decode(HeadersBuildPhase.self) - case SourcesBuildPhase.type: - return try container.decode(SourcesBuildPhase.self) - case FrameworksBuildPhase.type: - return try container.decode(FrameworksBuildPhase.self) - case ResourcesBuildPhase.type: - return try container.decode(ResourcesBuildPhase.self) - default: - throw InternalError("unknown build phase \(type)") - } - } - - public let guid: GUID - public var buildFiles: [BuildFile] - - public init(guid: GUID, buildFiles: [BuildFile]) { - precondition(!guid.isEmpty) - - self.guid = guid - self.buildFiles = buildFiles - super.init() - } - - private enum CodingKeys: CodingKey { - case guid, buildFiles - } - - public override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(guid, forKey: .guid) - try container.encode(buildFiles, forKey: .buildFiles) - } - - public required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.guid = try container.decode(GUID.self, forKey: .guid) - self.buildFiles = try container.decode([BuildFile].self, forKey: .buildFiles) + self.underlying = try superContainer.decode(ProjectModel.BaseTarget.self, forKey: .contents) + try super.init(from: decoder) + */ } } - - /// A "headers" build phase, i.e. one that copies headers into a directory of the product, after suitable - /// processing. - public final class HeadersBuildPhase: BuildPhase { - override class var type: String { "com.apple.buildphase.headers" } - } - - /// A "sources" build phase, i.e. one that compiles sources and provides them to be linked into the executable code - /// of the product. - public final class SourcesBuildPhase: BuildPhase { - override class var type: String { "com.apple.buildphase.sources" } - } - - /// A "frameworks" build phase, i.e. one that links compiled code and libraries into the executable of the product. - public final class FrameworksBuildPhase: BuildPhase { - override class var type: String { "com.apple.buildphase.frameworks" } - } - - public final class ResourcesBuildPhase: BuildPhase { - override class var type: String { "com.apple.buildphase.resources" } - } - - /// A build file, representing the membership of either a file or target product reference in a build phase. - public struct BuildFile: Codable { - public enum Reference { - case file(guid: PIF.GUID) - case target(guid: PIF.GUID) - } - - public enum HeaderVisibility: String, Codable { - case `public` = "public" - case `private` = "private" - } - - public let guid: GUID - public var reference: Reference - public var headerVisibility: HeaderVisibility? = nil - public var platformFilters: [PlatformFilter] - - public init(guid: GUID, file: FileReference, platformFilters: [PlatformFilter], headerVisibility: HeaderVisibility? = nil) { - self.guid = guid - self.reference = .file(guid: file.guid) - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - public init(guid: GUID, fileGUID: PIF.GUID, platformFilters: [PlatformFilter], headerVisibility: HeaderVisibility? = nil) { - self.guid = guid - self.reference = .file(guid: fileGUID) - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - public init(guid: GUID, target: PIF.BaseTarget, platformFilters: [PlatformFilter], headerVisibility: HeaderVisibility? = nil) { - self.guid = guid - self.reference = .target(guid: target.guid) - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - public init(guid: GUID, targetGUID: PIF.GUID, platformFilters: [PlatformFilter], headerVisibility: HeaderVisibility? = nil) { - self.guid = guid - self.reference = .target(guid: targetGUID) - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - public init(guid: GUID, reference: Reference, platformFilters: [PlatformFilter], headerVisibility: HeaderVisibility? = nil) { - self.guid = guid - self.reference = reference - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - private enum CodingKeys: CodingKey { - case guid, platformFilters, fileReference, targetReference, headerVisibility - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(guid, forKey: .guid) - try container.encode(platformFilters, forKey: .platformFilters) - try container.encodeIfPresent(headerVisibility, forKey: .headerVisibility) - - switch self.reference { - case .file(let fileGUID): - try container.encode(fileGUID, forKey: .fileReference) - case .target(let targetGUID): - try container.encode("\(targetGUID)@\(schemaVersion)", forKey: .targetReference) - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - guid = try container.decode(GUID.self, forKey: .guid) - platformFilters = try container.decode([PlatformFilter].self, forKey: .platformFilters) - headerVisibility = try container.decodeIfPresent(HeaderVisibility.self, forKey: .headerVisibility) - - if container.allKeys.contains(.fileReference) { - reference = try .file(guid: container.decode(GUID.self, forKey: .fileReference)) - } else if container.allKeys.contains(.targetReference) { - let targetGUIDString = try container.decode(GUID.self, forKey: .targetReference) - let targetGUID = String(targetGUIDString.dropLast("\(schemaVersion)".count + 1)) - reference = .target(guid: targetGUID) - } else { - throw InternalError("Expected \(CodingKeys.fileReference) or \(CodingKeys.targetReference) in the keys") - } - } - } - - /// Represents a generic platform filter. - public struct PlatformFilter: Codable, Equatable { - /// The name of the platform (`LC_BUILD_VERSION`). - /// - /// Example: macos, ios, watchos, tvos. - public var platform: String - - /// The name of the environment (`LC_BUILD_VERSION`) - /// - /// Example: simulator, maccatalyst. - public var environment: String - - public init(platform: String, environment: String = "") { - self.platform = platform - self.environment = environment - } - } - - /// A build configuration, which is a named collection of build settings. - public struct BuildConfiguration: Codable { - public let guid: GUID - public var name: String - public var buildSettings: BuildSettings - public let impartedBuildProperties: ImpartedBuildProperties - - public init(guid: GUID, name: String, buildSettings: BuildSettings, impartedBuildProperties: ImpartedBuildProperties = ImpartedBuildProperties(settings: BuildSettings())) { - precondition(!guid.isEmpty) - precondition(!name.isEmpty) - - self.guid = guid - self.name = name - self.buildSettings = buildSettings - self.impartedBuildProperties = impartedBuildProperties - } - } - - public struct ImpartedBuildProperties: Codable { - public var buildSettings: BuildSettings - - public init(settings: BuildSettings) { - self.buildSettings = settings - } - } - - /// A set of build settings, which is represented as a struct of optional build settings. This is not optimally - /// efficient, but it is great for code completion and type-checking. - public struct BuildSettings: Codable { - public enum SingleValueSetting: String, Codable { - case APPLICATION_EXTENSION_API_ONLY - case BUILT_PRODUCTS_DIR - case CLANG_CXX_LANGUAGE_STANDARD - case CLANG_ENABLE_MODULES - case CLANG_ENABLE_OBJC_ARC - case CODE_SIGNING_REQUIRED - case CODE_SIGN_IDENTITY - case COMBINE_HIDPI_IMAGES - case COPY_PHASE_STRIP - case DEBUG_INFORMATION_FORMAT - case DEFINES_MODULE - case DRIVERKIT_DEPLOYMENT_TARGET - case DYLIB_INSTALL_NAME_BASE - case EMBEDDED_CONTENT_CONTAINS_SWIFT - case ENABLE_NS_ASSERTIONS - case ENABLE_TESTABILITY - case ENABLE_TESTING_SEARCH_PATHS - case ENTITLEMENTS_REQUIRED - case EXECUTABLE_PREFIX - case GENERATE_INFOPLIST_FILE - case GCC_C_LANGUAGE_STANDARD - case GCC_OPTIMIZATION_LEVEL - case GENERATE_MASTER_OBJECT_FILE - case INFOPLIST_FILE - case IPHONEOS_DEPLOYMENT_TARGET - case KEEP_PRIVATE_EXTERNS - case CLANG_COVERAGE_MAPPING_LINKER_ARGS - case MACH_O_TYPE - case MACOSX_DEPLOYMENT_TARGET - case MODULEMAP_FILE - case MODULEMAP_FILE_CONTENTS - case MODULEMAP_PATH - case MODULE_CACHE_DIR - case ONLY_ACTIVE_ARCH - case PACKAGE_RESOURCE_BUNDLE_NAME - case PACKAGE_RESOURCE_TARGET_KIND - case PRODUCT_BUNDLE_IDENTIFIER - case PRODUCT_MODULE_NAME - case PRODUCT_NAME - case PROJECT_NAME - case SDKROOT - case SDK_VARIANT - case SKIP_INSTALL - case INSTALL_PATH - case SUPPORTS_MACCATALYST - case SWIFT_SERIALIZE_DEBUGGING_OPTIONS - case SWIFT_INSTALL_OBJC_HEADER - case SWIFT_OBJC_INTERFACE_HEADER_NAME - case SWIFT_OBJC_INTERFACE_HEADER_DIR - case SWIFT_OPTIMIZATION_LEVEL - case SWIFT_VERSION - case TARGET_NAME - case TARGET_BUILD_DIR - case TVOS_DEPLOYMENT_TARGET - case USE_HEADERMAP - case USES_SWIFTPM_UNSAFE_FLAGS - case WATCHOS_DEPLOYMENT_TARGET - case XROS_DEPLOYMENT_TARGET - case MARKETING_VERSION - case CURRENT_PROJECT_VERSION - case SWIFT_EMIT_MODULE_INTERFACE - case GENERATE_RESOURCE_ACCESSORS - } - - public enum MultipleValueSetting: String, Codable { - case EMBED_PACKAGE_RESOURCE_BUNDLE_NAMES - case FRAMEWORK_SEARCH_PATHS - case GCC_PREPROCESSOR_DEFINITIONS - case HEADER_SEARCH_PATHS - case LD_RUNPATH_SEARCH_PATHS - case LIBRARY_SEARCH_PATHS - case OTHER_CFLAGS - case OTHER_CPLUSPLUSFLAGS - case OTHER_LDFLAGS - case OTHER_LDRFLAGS - case OTHER_SWIFT_FLAGS - case PRELINK_FLAGS - case SPECIALIZATION_SDK_OPTIONS - case SUPPORTED_PLATFORMS - case SWIFT_ACTIVE_COMPILATION_CONDITIONS - case SWIFT_MODULE_ALIASES - } - - public enum Platform: String, CaseIterable, Codable { - case macOS = "macos" - case macCatalyst = "maccatalyst" - case iOS = "ios" - case tvOS = "tvos" - case watchOS = "watchos" - case driverKit = "driverkit" - case linux - - public var packageModelPlatform: PackageModel.Platform { - switch self { - case .macOS: return .macOS - case .macCatalyst: return .macCatalyst - case .iOS: return .iOS - case .tvOS: return .tvOS - case .watchOS: return .watchOS - case .driverKit: return .driverKit - case .linux: return .linux - } - } - - public var conditions: [String] { - let filters = [PackageCondition(platforms: [packageModelPlatform])].toPlatformFilters().map { filter in - if filter.environment.isEmpty { - return filter.platform - } else { - return "\(filter.platform)-\(filter.environment)" - } - }.sorted() - return ["__platform_filter=\(filters.joined(separator: ";"))"] - } - } - - public private(set) var platformSpecificSingleValueSettings = OrderedDictionary>() - public private(set) var platformSpecificMultipleValueSettings = OrderedDictionary>() - public private(set) var singleValueSettings: OrderedDictionary = [:] - public private(set) var multipleValueSettings: OrderedDictionary = [:] - - public subscript(_ setting: SingleValueSetting) -> String? { - get { singleValueSettings[setting] } - set { singleValueSettings[setting] = newValue } - } - - public subscript(_ setting: SingleValueSetting, for platform: Platform) -> String? { - get { platformSpecificSingleValueSettings[platform]?[setting] } - set { platformSpecificSingleValueSettings[platform, default: [:]][setting] = newValue } - } - - public subscript(_ setting: SingleValueSetting, default defaultValue: @autoclosure () -> String) -> String { - get { singleValueSettings[setting, default: defaultValue()] } - set { singleValueSettings[setting] = newValue } - } - - public subscript(_ setting: MultipleValueSetting) -> [String]? { - get { multipleValueSettings[setting] } - set { multipleValueSettings[setting] = newValue } - } - - public subscript(_ setting: MultipleValueSetting, for platform: Platform) -> [String]? { - get { platformSpecificMultipleValueSettings[platform]?[setting] } - set { platformSpecificMultipleValueSettings[platform, default: [:]][setting] = newValue } - } - - public subscript( - _ setting: MultipleValueSetting, - default defaultValue: @autoclosure () -> [String] - ) -> [String] { - get { multipleValueSettings[setting, default: defaultValue()] } - set { multipleValueSettings[setting] = newValue } - } - - public subscript( - _ setting: MultipleValueSetting, - for platform: Platform, - default defaultValue: @autoclosure () -> [String] - ) -> [String] { - get { platformSpecificMultipleValueSettings[platform, default: [:]][setting, default: defaultValue()] } - set { platformSpecificMultipleValueSettings[platform, default: [:]][setting] = newValue } - } - - public init() { - } - - private enum CodingKeys: CodingKey { - case platformSpecificSingleValueSettings, platformSpecificMultipleValueSettings, singleValueSettings, multipleValueSettings - } - - public func encode(to encoder: Encoder) throws { - if encoder.userInfo.keys.contains(.encodeForSwiftBuild) { - return try encodeForSwiftBuild(to: encoder) - } - - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(platformSpecificSingleValueSettings, forKey: .platformSpecificSingleValueSettings) - try container.encode(platformSpecificMultipleValueSettings, forKey: .platformSpecificMultipleValueSettings) - try container.encode(singleValueSettings, forKey: .singleValueSettings) - try container.encode(multipleValueSettings, forKey: .multipleValueSettings) - } - - private func encodeForSwiftBuild(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StringKey.self) - - for (key, value) in singleValueSettings { - try container.encode(value, forKey: StringKey(key.rawValue)) - } - - for (key, value) in multipleValueSettings { - try container.encode(value, forKey: StringKey(key.rawValue)) - } - - for (platform, values) in platformSpecificSingleValueSettings { - for condition in platform.conditions { - for (key, value) in values { - try container.encode(value, forKey: "\(key.rawValue)[\(condition)]") - } - } - } - - for (platform, values) in platformSpecificMultipleValueSettings { - for condition in platform.conditions { - for (key, value) in values { - try container.encode(value, forKey: "\(key.rawValue)[\(condition)]") - } - } - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - platformSpecificSingleValueSettings = try container.decodeIfPresent(OrderedDictionary>.self, forKey: .platformSpecificSingleValueSettings) ?? .init() - platformSpecificMultipleValueSettings = try container.decodeIfPresent(OrderedDictionary>.self, forKey: .platformSpecificMultipleValueSettings) ?? .init() - singleValueSettings = try container.decodeIfPresent(OrderedDictionary.self, forKey: .singleValueSettings) ?? [:] - multipleValueSettings = try container.decodeIfPresent(OrderedDictionary.self, forKey: .multipleValueSettings) ?? [:] - } - } -} - -/// Represents a filetype recognized by the Xcode build system. -public struct SwiftBuildFileType: CaseIterable { - public static let xcassets: SwiftBuildFileType = SwiftBuildFileType( - fileType: "xcassets", - fileTypeIdentifier: "folder.abstractassetcatalog" - ) - - public static let xcdatamodeld: SwiftBuildFileType = SwiftBuildFileType( - fileType: "xcdatamodeld", - fileTypeIdentifier: "wrapper.xcdatamodeld" - ) - - public static let xcdatamodel: SwiftBuildFileType = SwiftBuildFileType( - fileType: "xcdatamodel", - fileTypeIdentifier: "wrapper.xcdatamodel" - ) - - public static let xcmappingmodel: SwiftBuildFileType = SwiftBuildFileType( - fileType: "xcmappingmodel", - fileTypeIdentifier: "wrapper.xcmappingmodel" - ) - - public static let allCases: [SwiftBuildFileType] = [ - .xcdatamodeld, - .xcdatamodel, - .xcmappingmodel, - ] - - public let fileTypes: Set - public let fileTypeIdentifier: String - - private init(fileTypes: Set, fileTypeIdentifier: String) { - self.fileTypes = fileTypes - self.fileTypeIdentifier = fileTypeIdentifier - } - - private init(fileType: String, fileTypeIdentifier: String) { - self.init(fileTypes: [fileType], fileTypeIdentifier: fileTypeIdentifier) - } -} - -fileprivate struct StringKey: CodingKey, ExpressibleByStringInterpolation { - var stringValue: String - var intValue: Int? - - init(stringLiteral stringValue: String) { - self.stringValue = stringValue - } - - init(stringValue value: String) { - self.stringValue = value - } - - init(_ value: String) { - self.stringValue = value - } - - init?(intValue: Int) { - assertionFailure("does not support integer keys") - return nil - } } -extension PIF.FileReference { - fileprivate static func fileTypeIdentifier(forPath path: String) -> String { - let pathExtension: String? - if let path = try? AbsolutePath(validating: path) { - pathExtension = path.extension - } else if let path = try? RelativePath(validating: path) { - pathExtension = path.extension - } else { - pathExtension = nil - } - - switch pathExtension { - case "a": - return "archive.ar" - case "s", "S": - return "sourcecode.asm" - case "c": - return "sourcecode.c.c" - case "cl": - return "sourcecode.opencl" - case "cpp", "cp", "cxx", "cc", "c++", "C", "tcc": - return "sourcecode.cpp.cpp" - case "d": - return "sourcecode.dtrace" - case "defs", "mig": - return "sourcecode.mig" - case "m": - return "sourcecode.c.objc" - case "mm", "M": - return "sourcecode.cpp.objcpp" - case "metal": - return "sourcecode.metal" - case "l", "lm", "lmm", "lpp", "lp", "lxx": - return "sourcecode.lex" - case "swift": - return "sourcecode.swift" - case "y", "ym", "ymm", "ypp", "yp", "yxx": - return "sourcecode.yacc" - - case "xcassets": - return "folder.assetcatalog" - case "xcstrings": - return "text.json.xcstrings" - case "storyboard": - return "file.storyboard" - case "xib": - return "file.xib" - - case "xcframework": - return "wrapper.xcframework" - - default: - return pathExtension.flatMap({ pathExtension in - SwiftBuildFileType.allCases.first(where:{ $0.fileTypes.contains(pathExtension) }) - })?.fileTypeIdentifier ?? "file" - } - } -} +// MARK: - PIF Signature Support extension CodingUserInfoKey { - public static let encodingPIFSignature: CodingUserInfoKey = CodingUserInfoKey(rawValue: "encodingPIFSignature")! - /// Perform the encoding for SwiftBuild consumption. public static let encodeForSwiftBuild: CodingUserInfoKey = CodingUserInfoKey(rawValue: "encodeForXCBuild")! } -private struct UntypedTarget: Decodable { - struct TargetContents: Decodable { - let type: String - } - let contents: TargetContents -} - -// MARK: - PIF Signature Support - -protocol PIFSignableObject: AnyObject { - var signature: String? { get set } -} -extension PIF.Workspace: PIFSignableObject {} -extension PIF.Project: PIFSignableObject {} -extension PIF.BaseTarget: PIFSignableObject {} - extension PIF { - /// Add signature to workspace and its subobjects. - public static func sign(_ workspace: PIF.Workspace) throws { + /// Add signature to workspace and its high-level subobjects. + static func sign(workspace: PIF.Workspace) throws { let encoder = JSONEncoder.makeWithDefaults() - func sign(_ obj: T) throws { + func signature(of obj: some Encodable) throws -> String { let signatureContent = try encoder.encode(obj) - let bytes = ByteString(signatureContent) - obj.signature = bytes.sha256Checksum + let signatureBytes = ByteString(signatureContent) + let signature = signatureBytes.sha256Checksum + return signature } - let projects = workspace.projects - try projects.flatMap{ $0.targets }.forEach(sign) - try projects.forEach(sign) - try sign(workspace) + for project in workspace.projects { + for targetIndex in project.underlying.targets.indices { + let targetSignature = try signature(of: project.underlying.targets[targetIndex]) + project.underlying.targets[targetIndex].common.signature = targetSignature + } + project.signature = try signature(of: project) + } + workspace.signature = try signature(of: workspace) } } + +#endif // SwiftBuild diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index e5a4762c926..8c21abad34c 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -22,6 +22,10 @@ import SPMBuildCore import func TSCBasic.memoize import func TSCBasic.topologicalSort +#if canImport(SwiftBuild) +import enum SwiftBuild.ProjectModel +#endif + /// The parameters required by `PIFBuilder`. struct PIFBuilderParameters { let triple: Triple @@ -50,10 +54,10 @@ struct PIFBuilderParameters { /// PIF object builder for a package graph. public final class PIFBuilder { - /// Name of the PIF target aggregating all targets (excluding tests). + /// Name of the PIF target aggregating all targets (*excluding* tests). public static let allExcludingTestsTargetName = "AllExcludingTests" - /// Name of the PIF target aggregating all targets (including tests). + /// Name of the PIF target aggregating all targets (*including* tests). public static let allIncludingTestsTargetName = "AllIncludingTests" /// The package graph to build from. @@ -68,8 +72,6 @@ public final class PIFBuilder { /// The file system to read from. let fileSystem: FileSystem - private var pif: PIF.TopLevelObject? - /// Creates a `PIFBuilder` instance. /// - Parameters: /// - graph: The package graph to build from. @@ -97,6 +99,7 @@ public final class PIFBuilder { prettyPrint: Bool = true, preservePIFModelStructure: Bool = false ) throws -> String { + #if canImport(SwiftBuild) let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder() if !preservePIFModelStructure { @@ -105,41 +108,75 @@ public final class PIFBuilder { let topLevelObject = try self.construct() - // Sign the pif objects before encoding it for SwiftBuild. - try PIF.sign(topLevelObject.workspace) + // Sign the PIF objects before encoding it for Swift Build. + try PIF.sign(workspace: topLevelObject.workspace) let pifData = try encoder.encode(topLevelObject) - return String(decoding: pifData, as: UTF8.self) + let pifString = String(decoding: pifData, as: UTF8.self) + + return pifString + #else + fatalError("Swift Build support is not linked in.") + #endif } + + #if canImport(SwiftBuild) + + private var cachedPIF: PIF.TopLevelObject? /// Constructs a `PIF.TopLevelObject` representing the package graph. - public func construct() throws -> PIF.TopLevelObject { - try memoize(to: &self.pif) { - let rootPackage = self.graph.rootPackages[self.graph.rootPackages.startIndex] + private func construct() throws -> PIF.TopLevelObject { + try memoize(to: &self.cachedPIF) { + guard let rootPackage = self.graph.rootPackages.only else { + if self.graph.rootPackages.isEmpty { + throw PIFGenerationError.rootPackageNotFound + } else { + throw PIFGenerationError.multipleRootPackagesFound + } + } let sortedPackages = self.graph.packages .sorted { $0.manifest.displayName < $1.manifest.displayName } // TODO: use identity instead? - var projects: [PIFProjectBuilder] = try sortedPackages.map { package in - try _PackagePIFProjectBuilder( - package: package, - parameters: self.parameters, - fileSystem: self.fileSystem, + + let packagesAndProjects: [(ResolvedPackage, ProjectModel.Project)] = try sortedPackages.map { package in + let packagePIFBuilderDelegate = PackagePIFBuilderDelegate( + package: package + ) + let packagePIFBuilder = PackagePIFBuilder( + modulesGraph: self.graph, + resolvedPackage: package, + packageManifest: package.manifest, + delegate: packagePIFBuilderDelegate, + buildToolPluginResultsByTargetName: [:], + createDylibForDynamicProducts: self.parameters.shouldCreateDylibForDynamicProducts, + packageDisplayVersion: package.manifest.displayName, observabilityScope: self.observabilityScope ) + + try packagePIFBuilder.build() + return (package, packagePIFBuilder.pifProject) } + + var projects = packagesAndProjects.map(\.1) + projects.append( + try buildAggregateProject( + packagesAndProjects: packagesAndProjects, + observabilityScope: observabilityScope + ) + ) - projects.append(AggregatePIFProjectBuilder(projects: projects)) - - let workspace = try PIF.Workspace( + let workspace = PIF.Workspace( guid: "Workspace:\(rootPackage.path.pathString)", name: rootPackage.manifest.displayName, // TODO: use identity instead? path: rootPackage.path, - projects: projects.map { try $0.construct() } + projects: projects ) return PIF.TopLevelObject(workspace: workspace) } } + + #endif // Convenience method for generating PIF. public static func generatePIF( @@ -160,1811 +197,208 @@ public final class PIFBuilder { } } -class PIFProjectBuilder { - let groupTree: PIFGroupBuilder - private(set) var targets: [PIFBaseTargetBuilder] - private(set) var buildConfigurations: [PIFBuildConfigurationBuilder] - - @DelayedImmutable - var guid: PIF.GUID - @DelayedImmutable - var name: String - @DelayedImmutable - var path: AbsolutePath - @DelayedImmutable - var projectDirectory: AbsolutePath - @DelayedImmutable - var developmentRegion: String +#if canImport(SwiftBuild) - fileprivate init() { - self.groupTree = PIFGroupBuilder(path: "") - self.targets = [] - self.buildConfigurations = [] +fileprivate final class PackagePIFBuilderDelegate: PackagePIFBuilder.BuildDelegate { + let package: ResolvedPackage + + init(package: ResolvedPackage) { + self.package = package } - - /// Creates and adds a new empty build configuration, i.e. one that does not initially have any build settings. - /// The name must not be empty and must not be equal to the name of any existing build configuration in the target. - @discardableResult - func addBuildConfiguration( - name: String, - settings: PIF.BuildSettings = PIF.BuildSettings(), - impartedBuildProperties: PIF.ImpartedBuildProperties = PIF - .ImpartedBuildProperties(settings: PIF.BuildSettings()) - ) -> PIFBuildConfigurationBuilder { - let builder = PIFBuildConfigurationBuilder( - name: name, - settings: settings, - impartedBuildProperties: impartedBuildProperties - ) - self.buildConfigurations.append(builder) - return builder + + var isRootPackage: Bool { + self.package.manifest.packageKind.isRoot } - - /// Creates and adds a new empty target, i.e. one that does not initially have any build phases. If provided, - /// the ID must be non-empty and unique within the PIF workspace; if not provided, an arbitrary guaranteed-to-be- - /// unique identifier will be assigned. The name must not be empty and must not be equal to the name of any existing - /// target in the project. - @discardableResult - func addTarget( - guid: PIF.GUID, - name: String, - productType: PIF.Target.ProductType, - productName: String - ) -> PIFTargetBuilder { - let target = PIFTargetBuilder(guid: guid, name: name, productType: productType, productName: productName) - self.targets.append(target) - return target + + var hostsOnlyPackages: Bool { + false } - - @discardableResult - func addAggregateTarget(guid: PIF.GUID, name: String) -> PIFAggregateTargetBuilder { - let target = PIFAggregateTargetBuilder(guid: guid, name: name) - self.targets.append(target) - return target + + var isUserManaged: Bool { + true } - - func construct() throws -> PIF.Project { - let buildConfigurations = self.buildConfigurations.map { builder -> PIF.BuildConfiguration in - builder.guid = "\(self.guid)::BUILDCONFIG_\(builder.name)" - return builder.construct() - } - - // Construct group tree before targets to make sure file references have GUIDs. - groupTree.guid = "\(self.guid)::MAINGROUP" - let groupTree = self.groupTree.construct() as! PIF.Group - let targets = try self.targets.map { try $0.construct() } - - return PIF.Project( - guid: self.guid, - name: self.name, - path: self.path, - projectDirectory: self.projectDirectory, - developmentRegion: self.developmentRegion, - buildConfigurations: buildConfigurations, - targets: targets, - groupTree: groupTree - ) + + var isBranchOrRevisionBased: Bool { + false } -} - -final class _PackagePIFProjectBuilder: PIFProjectBuilder { - private let package: ResolvedPackage - private let parameters: PIFBuilderParameters - private let fileSystem: FileSystem - private let observabilityScope: ObservabilityScope - private var binaryGroup: PIFGroupBuilder! - private let executableTargetProductMap: [ResolvedModule.ID: ResolvedProduct] - - var isRootPackage: Bool { self.package.manifest.packageKind.isRoot } - - init( - package: ResolvedPackage, - parameters: PIFBuilderParameters, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope - ) throws { - self.package = package - self.parameters = parameters - self.fileSystem = fileSystem - self.observabilityScope = observabilityScope.makeChildScope( - description: "Package PIF Builder", - metadata: package.underlying.diagnosticsMetadata - ) - - self.executableTargetProductMap = try Dictionary( - throwingUniqueKeysWithValues: package.products - .filter { $0.type == .executable } - .map { ($0.mainTarget.id, $0) } - ) - - super.init() - - self.guid = package.pifProjectGUID - self.name = package.manifest.displayName // TODO: use identity instead? - self.path = package.path - self.projectDirectory = package.path - self.developmentRegion = package.manifest.defaultLocalization ?? "en" - self.binaryGroup = groupTree.addGroup(path: "/", sourceTree: .absolute, name: "Binaries") - - // Configure the project-wide build settings. First we set those that are in common between the "Debug" and - // "Release" configurations, and then we set those that are different. - var settings = PIF.BuildSettings() - settings[.PRODUCT_NAME] = "$(TARGET_NAME)" - settings[.SUPPORTED_PLATFORMS] = ["$(AVAILABLE_PLATFORMS)"] - settings[.SDKROOT] = "auto" - settings[.SDK_VARIANT] = "auto" - settings[.SKIP_INSTALL] = "YES" - settings[.MACOSX_DEPLOYMENT_TARGET] = package.deploymentTarget(for: .macOS) - settings[.IPHONEOS_DEPLOYMENT_TARGET] = package.deploymentTarget(for: .iOS) - settings[.IPHONEOS_DEPLOYMENT_TARGET, for: .macCatalyst] = package.deploymentTarget(for: .macCatalyst) - settings[.TVOS_DEPLOYMENT_TARGET] = package.deploymentTarget(for: .tvOS) - settings[.WATCHOS_DEPLOYMENT_TARGET] = package.deploymentTarget(for: .watchOS) - settings[.XROS_DEPLOYMENT_TARGET] = package.deploymentTarget(for: .visionOS) - settings[.DRIVERKIT_DEPLOYMENT_TARGET] = package.deploymentTarget(for: .driverKit) - settings[.DYLIB_INSTALL_NAME_BASE] = "@rpath" - settings[.USE_HEADERMAP] = "NO" - settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS] = ["$(inherited)", "SWIFT_PACKAGE"] - settings[.GCC_PREPROCESSOR_DEFINITIONS] = ["$(inherited)", "SWIFT_PACKAGE"] - -#if os(macOS) - // Objective-C support only for macOS - settings[.CLANG_ENABLE_OBJC_ARC] = "YES" -#endif - - settings[.KEEP_PRIVATE_EXTERNS] = "NO" - // We currently deliberately do not support Swift ObjC interface headers. - settings[.SWIFT_INSTALL_OBJC_HEADER] = "NO" - settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME] = "" - settings[.OTHER_LDRFLAGS] = [] - - // This will add the XCTest related search paths automatically - // (including the Swift overlays). - settings[.ENABLE_TESTING_SEARCH_PATHS] = "YES" - - // XCTest search paths should only be specified for certain platforms (watchOS doesn't have XCTest). - for platform: PIF.BuildSettings.Platform in [.macOS, .iOS, .tvOS] { - settings[.FRAMEWORK_SEARCH_PATHS, for: platform, default: ["$(inherited)"]] - .append("$(PLATFORM_DIR)/Developer/Library/Frameworks") - } - - PlatformRegistry.default.knownPlatforms.forEach { - guard let platform = PIF.BuildSettings.Platform.from(platform: $0) else { return } - let supportedPlatform = package.getSupportedPlatform(for: $0, usingXCTest: false) - if !supportedPlatform.options.isEmpty { - settings[.SPECIALIZATION_SDK_OPTIONS, for: platform] = supportedPlatform.options - } - } - - // Disable signing for all the things since there is no way to configure - // signing information in packages right now. - settings[.ENTITLEMENTS_REQUIRED] = "NO" - settings[.CODE_SIGNING_REQUIRED] = "NO" - settings[.CODE_SIGN_IDENTITY] = "" - - var debugSettings = settings - debugSettings[.COPY_PHASE_STRIP] = "NO" - debugSettings[.DEBUG_INFORMATION_FORMAT] = "dwarf" - debugSettings[.ENABLE_NS_ASSERTIONS] = "YES" - debugSettings[.GCC_OPTIMIZATION_LEVEL] = "0" - debugSettings[.ONLY_ACTIVE_ARCH] = "YES" - debugSettings[.SWIFT_OPTIMIZATION_LEVEL] = "-Onone" - debugSettings[.ENABLE_TESTABILITY] = "YES" - debugSettings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS, default: []].append("DEBUG") - debugSettings[.GCC_PREPROCESSOR_DEFINITIONS, default: ["$(inherited)"]].append("DEBUG=1") - addBuildConfiguration(name: "Debug", settings: debugSettings) - - var releaseSettings = settings - releaseSettings[.COPY_PHASE_STRIP] = "YES" - releaseSettings[.DEBUG_INFORMATION_FORMAT] = "dwarf-with-dsym" - releaseSettings[.GCC_OPTIMIZATION_LEVEL] = "s" - releaseSettings[.SWIFT_OPTIMIZATION_LEVEL] = "-Owholemodule" - - if parameters.enableTestability { - releaseSettings[.ENABLE_TESTABILITY] = "YES" - } - - addBuildConfiguration(name: "Release", settings: releaseSettings) - - for product in package.products.sorted(by: { $0.name < $1.name }) { - let productScope = observabilityScope.makeChildScope( - description: "Adding \(product.name) product", - metadata: package.underlying.diagnosticsMetadata - ) - - productScope.trap { try self.addTarget(for: product) } - } - - for target in package.modules.sorted(by: { $0.name < $1.name }) { - let targetScope = observabilityScope.makeChildScope( - description: "Adding \(target.name) module", - metadata: package.underlying.diagnosticsMetadata - ) - targetScope.trap { try self.addTarget(for: target) } - } - - if self.binaryGroup.children.isEmpty { - groupTree.removeChild(self.binaryGroup) - } + + func customProductType(forExecutable product: PackageModel.Product) -> ProjectModel.Target.ProductType? { + nil } - - private func addTarget(for product: ResolvedProduct) throws { - switch product.type { - case .executable, .snippet, .test: - try self.addMainModuleTarget(for: product) - case .library: - self.addLibraryTarget(for: product) - case .plugin, .macro: - return - } + + func deviceFamilyIDs() -> Set { + [] } - - private func addTarget(for target: ResolvedModule) throws { - switch target.type { - case .library: - try self.addLibraryTarget(for: target) - case .systemModule: - try self.addSystemTarget(for: target) - case .executable, .snippet, .test: - // Skip executable module targets and test module targets (they will have been dealt with as part of the - // products to which they belong). - return - case .binary: - // Binary target don't need to be built. - return - case .plugin: - // Package plugin modules. - return - case .macro: - // Macros are not supported when using SwiftBuild, similar to package plugins. - return - } + + var shouldiOSPackagesBuildForARM64e: Bool { + false } - - private func targetName(for product: ResolvedProduct) -> String { - Self.targetName(for: product.name) + + var isPluginExecutionSandboxingDisabled: Bool { + false } - - static func targetName(for productName: String) -> String { - "\(productName)_\(String(productName.hash, radix: 16, uppercase: true))_PackageProduct" + + func configureProjectBuildSettings(_ buildSettings: inout ProjectModel.BuildSettings) { + /* empty */ } - - private func addMainModuleTarget(for product: ResolvedProduct) throws { - let productType: PIF.Target.ProductType = product.type == .executable ? .executable : .unitTest - let pifTarget = self.addTarget( - guid: product.pifTargetGUID, - name: self.targetName(for: product), - productType: productType, - productName: "\(product.name)\(parameters.triple.executableExtension)" - ) - - // We'll be infusing the product's main module target into the one for the product itself. - let mainTarget = product.mainTarget - - self.addSources(mainTarget.sources, to: pifTarget) - - let dependencies = try! topologicalSort(mainTarget.dependencies) { $0.packageDependencies }.sorted() - for dependency in dependencies { - self.addDependency(to: dependency, in: pifTarget, linkProduct: true) - } - - // Configure the target-wide build settings. The details depend on the kind of product we're building, but are - // in general the ones that are suitable for end-product artifacts such as executables and test bundles. - var settings = PIF.BuildSettings() - settings[.TARGET_NAME] = product.name - settings[.PACKAGE_RESOURCE_TARGET_KIND] = "regular" - settings[.PRODUCT_NAME] = "$(TARGET_NAME)" - settings[.PRODUCT_MODULE_NAME] = mainTarget.c99name - settings[.PRODUCT_BUNDLE_IDENTIFIER] = product.name - settings[.CLANG_ENABLE_MODULES] = "YES" - settings[.DEFINES_MODULE] = "YES" - - if product.type == .executable || product.type == .test { - if let darwinPlatform = parameters.triple.darwinPlatform { - settings[.LIBRARY_SEARCH_PATHS] = [ - "$(inherited)", - "\(self.parameters.toolchainLibDir.pathString)/swift/\(darwinPlatform.platformName)", - ] - } - } - - // Tests can have a custom deployment target based on the minimum supported by XCTest. - if mainTarget.underlying.type == .test { - settings[.MACOSX_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .macOS, usingXCTest: true) - settings[.IPHONEOS_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .iOS, usingXCTest: true) - settings[.TVOS_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .tvOS, usingXCTest: true) - settings[.WATCHOS_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .watchOS, usingXCTest: true) - settings[.XROS_DEPLOYMENT_TARGET] = mainTarget.deploymentTarget(for: .visionOS, usingXCTest: true) - } - - if product.type == .executable { - // Setup install path for executables if it's in root of a pure Swift package. - if self.isRootPackage { - settings[.SKIP_INSTALL] = "NO" - settings[.INSTALL_PATH] = "/usr/local/bin" - settings[.LD_RUNPATH_SEARCH_PATHS, default: ["$(inherited)"]].append("@executable_path/../lib") - } - } else { - // FIXME: we shouldn't always include both the deep and shallow bundle paths here, but for that we'll need - // rdar://problem/31867023 - settings[.LD_RUNPATH_SEARCH_PATHS, default: ["$(inherited)"]] += - ["@loader_path/Frameworks", "@loader_path/../Frameworks"] - settings[.GENERATE_INFOPLIST_FILE] = "YES" - } - - if let clangTarget = mainTarget.underlying as? ClangModule { - // Let the target itself find its own headers. - settings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) - settings[.GCC_C_LANGUAGE_STANDARD] = clangTarget.cLanguageStandard - settings[.CLANG_CXX_LANGUAGE_STANDARD] = clangTarget.cxxLanguageStandard - } else if let swiftTarget = mainTarget.underlying as? SwiftModule { - try settings.addSwiftVersionSettings(target: swiftTarget, parameters: self.parameters) - settings.addCommonSwiftSettings(package: self.package, target: mainTarget, parameters: self.parameters) - } - - if let resourceBundle = addResourceBundle(for: mainTarget, in: pifTarget) { - settings[.PACKAGE_RESOURCE_BUNDLE_NAME] = resourceBundle - settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" - } - - // For targets, we use the common build settings for both the "Debug" and the "Release" configurations (all - // differentiation is at the project level). - var debugSettings = settings - var releaseSettings = settings - - var impartedSettings = PIF.BuildSettings() - try self.addManifestBuildSettings( - from: mainTarget.underlying, - debugSettings: &debugSettings, - releaseSettings: &releaseSettings, - impartedSettings: &impartedSettings - ) - - let impartedBuildProperties = PIF.ImpartedBuildProperties(settings: impartedSettings) - pifTarget.addBuildConfiguration( - name: "Debug", - settings: debugSettings, - impartedBuildProperties: impartedBuildProperties - ) - pifTarget.addBuildConfiguration( - name: "Release", - settings: releaseSettings, - impartedBuildProperties: impartedBuildProperties - ) + + func configureSourceModuleBuildSettings(sourceModule: ResolvedModule, settings: inout ProjectModel.BuildSettings) { + /* empty */ } - - private func addLibraryTarget(for product: ResolvedProduct) { - // For the name of the product reference - let pifTargetProductName: String - let productType: PIF.Target.ProductType - if product.type == .library(.dynamic) { - if self.parameters.shouldCreateDylibForDynamicProducts { - pifTargetProductName = "\(parameters.triple.dynamicLibraryPrefix)\(product.name)\(parameters.triple.dynamicLibraryExtension)" - productType = .dynamicLibrary - } else { - pifTargetProductName = product.name + ".framework" - productType = .framework - } - } else { - pifTargetProductName = "lib\(product.name)\(parameters.triple.staticLibraryExtension)" - productType = .packageProduct - } - - // Create a special kind of .packageProduct PIF target that just "groups" a set of targets for clients to - // depend on. SwiftBuild will not produce a separate artifact for a package product, but will instead consider any - // dependency on the package product to be a dependency on the whole set of targets on which the package product - // depends. - let pifTarget = self.addTarget( - guid: product.pifTargetGUID, - name: self.targetName(for: product), - productType: productType, - productName: pifTargetProductName - ) - - // Handle the dependencies of the targets in the product (and link against them, which in the case of a package - // product, really just means that clients should link against them). - let dependencies = product.recursivePackageDependencies() - for dependency in dependencies { - switch dependency { - case .module(let target, let conditions): - if target.type != .systemModule { - self.addDependency(to: target, in: pifTarget, conditions: conditions, linkProduct: true) - } - case .product(let product, let conditions): - self.addDependency(to: product, in: pifTarget, conditions: conditions, linkProduct: true) - } - } - - var settings = PIF.BuildSettings() - let usesUnsafeFlags = dependencies.contains { $0.module?.underlying.usesUnsafeFlags == true } - settings[.USES_SWIFTPM_UNSAFE_FLAGS] = usesUnsafeFlags ? "YES" : "NO" - - // If there are no system modules in the dependency graph, mark the target as extension-safe. - let dependsOnAnySystemModules = dependencies.contains { $0.module?.type == .systemModule } - if !dependsOnAnySystemModules { - settings[.APPLICATION_EXTENSION_API_ONLY] = "YES" - } - - // Add other build settings when we're building an actual dylib. - if product.type == .library(.dynamic) { - settings[.TARGET_NAME] = product.name - settings[.PRODUCT_NAME] = "$(TARGET_NAME)" - settings[.PRODUCT_MODULE_NAME] = product.name - settings[.PRODUCT_BUNDLE_IDENTIFIER] = product.name - settings[.EXECUTABLE_PREFIX] = parameters.triple.dynamicLibraryPrefix - settings[.CLANG_ENABLE_MODULES] = "YES" - settings[.DEFINES_MODULE] = "YES" - settings[.SKIP_INSTALL] = "NO" - settings[.INSTALL_PATH] = "/usr/local/lib" - if let darwinPlatform = parameters.triple.darwinPlatform { - settings[.LIBRARY_SEARCH_PATHS] = [ - "$(inherited)", - "\(self.parameters.toolchainLibDir.pathString)/swift/\(darwinPlatform.platformName)", - ] - } - - if !self.parameters.shouldCreateDylibForDynamicProducts { - settings[.GENERATE_INFOPLIST_FILE] = "YES" - // If the built framework is named same as one of the target in the package, it can be picked up - // automatically during indexing since the build system always adds a -F flag to the built products dir. - // To avoid this problem, we build all package frameworks in a subdirectory. - settings[.BUILT_PRODUCTS_DIR] = "$(BUILT_PRODUCTS_DIR)/PackageFrameworks" - settings[.TARGET_BUILD_DIR] = "$(TARGET_BUILD_DIR)/PackageFrameworks" - - // Set the project and marketing version for the framework because the app store requires these to be - // present. The AppStore requires bumping the project version when ingesting new builds but that's for - // top-level apps and not frameworks embedded inside it. - settings[.MARKETING_VERSION] = "1.0" // Version - settings[.CURRENT_PROJECT_VERSION] = "1" // Build - } - - pifTarget.addSourcesBuildPhase() - } - - pifTarget.addBuildConfiguration(name: "Debug", settings: settings) - pifTarget.addBuildConfiguration(name: "Release", settings: settings) + + func customInstallPath(product: PackageModel.Product) -> String? { + nil } - - private func addLibraryTarget(for target: ResolvedModule) throws { - let pifTarget = self.addTarget( - guid: target.pifTargetGUID, - name: target.name, - productType: .objectFile, - productName: "\(target.name)_Module.o" - ) - - var settings = PIF.BuildSettings() - settings[.TARGET_NAME] = target.name + "_Module" - settings[.PACKAGE_RESOURCE_TARGET_KIND] = "regular" - settings[.PRODUCT_NAME] = "$(TARGET_NAME)" - settings[.PRODUCT_MODULE_NAME] = target.c99name - settings[.PRODUCT_BUNDLE_IDENTIFIER] = target.name - settings[.CLANG_ENABLE_MODULES] = "YES" - settings[.DEFINES_MODULE] = "YES" - settings[.MACH_O_TYPE] = "mh_object" - settings[.GENERATE_MASTER_OBJECT_FILE] = "NO" - // Disable code coverage linker flags since we're producing .o files. Otherwise, we will run into duplicated - // symbols when there are more than one targets that produce .o as their product. - settings[.CLANG_COVERAGE_MAPPING_LINKER_ARGS] = "NO" - if let aliases = target.moduleAliases { - settings[.SWIFT_MODULE_ALIASES] = aliases.map { $0.key + "=" + $0.value } - } - - // Create a set of build settings that will be imparted to any target that depends on this one. - var impartedSettings = PIF.BuildSettings() - - let generatedModuleMapDir = "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)" - let moduleMapFile = "\(generatedModuleMapDir)/\(target.name).modulemap" - let moduleMapFileContents: String? - let shouldImpartModuleMap: Bool - - if let clangTarget = target.underlying as? ClangModule { - // Let the target itself find its own headers. - settings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) - settings[.GCC_C_LANGUAGE_STANDARD] = clangTarget.cLanguageStandard - settings[.CLANG_CXX_LANGUAGE_STANDARD] = clangTarget.cxxLanguageStandard - - // Also propagate this search path to all direct and indirect clients. - impartedSettings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) - - if !self.fileSystem.exists(clangTarget.moduleMapPath) { - impartedSettings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]] += - ["-Xcc", "-fmodule-map-file=\(moduleMapFile)"] - - moduleMapFileContents = """ - module \(target.c99name) { - umbrella "\(clangTarget.includeDir.pathString)" - export * - } - """ - - shouldImpartModuleMap = true - } else { - moduleMapFileContents = nil - shouldImpartModuleMap = false - } - } else if let swiftTarget = target.underlying as? SwiftModule { - try settings.addSwiftVersionSettings(target: swiftTarget, parameters: self.parameters) - - // Generate ObjC compatibility header for Swift library targets. - settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR] = "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)" - settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME] = "\(target.name)-Swift.h" - - settings.addCommonSwiftSettings(package: self.package, target: target, parameters: self.parameters) - - moduleMapFileContents = """ - module \(target.c99name) { - header "\(target.name)-Swift.h" - export * - } - """ - - shouldImpartModuleMap = true - } else { - throw InternalError("unexpected target") - } - - if let moduleMapFileContents { - settings[.MODULEMAP_PATH] = moduleMapFile - settings[.MODULEMAP_FILE_CONTENTS] = moduleMapFileContents - } - - // Pass the path of the module map up to all direct and indirect clients. - if shouldImpartModuleMap { - impartedSettings[.OTHER_CFLAGS, default: ["$(inherited)"]].append("-fmodule-map-file=\(moduleMapFile)") - } - impartedSettings[.OTHER_LDRFLAGS] = [] - - if target.underlying.isCxx { - impartedSettings[.OTHER_LDFLAGS, default: ["$(inherited)"]].append("-lc++") - } - -#if os(macOS) - // radar://112671586 supress unnecessary warnings, only for macOS where the linker supports this flag - impartedSettings[.OTHER_LDFLAGS, default: ["$(inherited)"]].append("-Wl,-no_warn_duplicate_libraries") -#endif - - self.addSources(target.sources, to: pifTarget) - - // Handle the target's dependencies (but don't link against them). - let dependencies = try! topologicalSort(target.dependencies) { $0.packageDependencies }.sorted() - for dependency in dependencies { - self.addDependency(to: dependency, in: pifTarget, linkProduct: false) - } - - if let resourceBundle = addResourceBundle(for: target, in: pifTarget) { - settings[.PACKAGE_RESOURCE_BUNDLE_NAME] = resourceBundle - settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" - impartedSettings[.EMBED_PACKAGE_RESOURCE_BUNDLE_NAMES, default: ["$(inherited)"]].append(resourceBundle) - } - - // For targets, we use the common build settings for both the "Debug" and the "Release" configurations (all - // differentiation is at the project level). - var debugSettings = settings - var releaseSettings = settings - - try addManifestBuildSettings( - from: target.underlying, - debugSettings: &debugSettings, - releaseSettings: &releaseSettings, - impartedSettings: &impartedSettings - ) - - let impartedBuildProperties = PIF.ImpartedBuildProperties(settings: impartedSettings) - pifTarget.addBuildConfiguration( - name: "Debug", - settings: debugSettings, - impartedBuildProperties: impartedBuildProperties - ) - pifTarget.addBuildConfiguration( - name: "Release", - settings: releaseSettings, - impartedBuildProperties: impartedBuildProperties - ) - pifTarget.impartedBuildSettings = impartedSettings + + func customExecutableName(product: PackageModel.Product) -> String? { + nil } - - private func addSystemTarget(for target: ResolvedModule) throws { - guard let systemTarget = target.underlying as? SystemLibraryModule else { - throw InternalError("unexpected target type") - } - - // Impart the header search path to all direct and indirect clients. - var impartedSettings = PIF.BuildSettings() - - var cFlags: [String] = [] - for result in try pkgConfigArgs( - for: systemTarget, - pkgConfigDirectories: self.parameters.pkgConfigDirectories, - sdkRootPath: self.parameters.sdkRootPath, - fileSystem: self.fileSystem, - observabilityScope: self.observabilityScope - ) { - if let error = result.error { - self.observabilityScope.emit( - warning: "\(error.interpolationDescription)", - metadata: .pkgConfig(pcFile: result.pkgConfigName, targetName: target.name) - ) - } else { - cFlags = result.cFlags - impartedSettings[.OTHER_LDFLAGS, default: ["$(inherited)"]] += result.libs - } - } - - impartedSettings[.OTHER_LDRFLAGS] = [] - impartedSettings[.OTHER_CFLAGS, default: ["$(inherited)"]] += - ["-fmodule-map-file=\(systemTarget.moduleMapPath)"] + cFlags - impartedSettings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]] += - ["-Xcc", "-fmodule-map-file=\(systemTarget.moduleMapPath)"] + cFlags - let impartedBuildProperties = PIF.ImpartedBuildProperties(settings: impartedSettings) - - // Create an aggregate PIF target (which doesn't have an actual product). - let pifTarget = addAggregateTarget(guid: target.pifTargetGUID, name: target.name) - pifTarget.addBuildConfiguration( - name: "Debug", - settings: PIF.BuildSettings(), - impartedBuildProperties: impartedBuildProperties - ) - pifTarget.addBuildConfiguration( - name: "Release", - settings: PIF.BuildSettings(), - impartedBuildProperties: impartedBuildProperties - ) - pifTarget.impartedBuildSettings = impartedSettings + + func customLibraryType(product: PackageModel.Product) -> PackageModel.ProductType.LibraryType? { + nil } - - private func addSources(_ sources: Sources, to pifTarget: PIFTargetBuilder) { - // Create a group for the target's source files. For now we use an absolute path for it, but we should really - // make it be container-relative, since it's always inside the package directory. - let targetGroup = groupTree.addGroup( - path: sources.root.relative(to: self.package.path).pathString, - sourceTree: .group - ) - - // Add a source file reference for each of the source files, and also an indexable-file URL for each one. - for path in sources.relativePaths { - pifTarget.addSourceFile(targetGroup.addFileReference(path: path.pathString, sourceTree: .group)) - } + + func customSDKOptions(forPlatform: PackageModel.Platform) -> [String] { + [] } - - private func addDependency( - to dependency: ResolvedModule.Dependency, - in pifTarget: PIFTargetBuilder, - linkProduct: Bool + + func addCustomTargets(pifProject: SwiftBuild.ProjectModel.Project) throws -> [PackagePIFBuilder.ModuleOrProduct] { + return [] + } + + func shouldSuppressProductDependency(product: PackageModel.Product, buildSettings: inout SwiftBuild.ProjectModel.BuildSettings) -> Bool { + false + } + + func shouldSetInstallPathForDynamicLib(productName: String) -> Bool { + false + } + + func configureLibraryProduct( + product: PackageModel.Product, + target: WritableKeyPath, + additionalFiles: WritableKeyPath ) { - switch dependency { - case .module(let target, let conditions): - self.addDependency( - to: target, - in: pifTarget, - conditions: conditions, - linkProduct: linkProduct - ) - case .product(let product, let conditions): - self.addDependency( - to: product, - in: pifTarget, - conditions: conditions, - linkProduct: linkProduct - ) - } + /* empty */ + } + + func suggestAlignedPlatformVersionGiveniOSVersion(platform: PackageModel.Platform, iOSVersion: PackageModel.PlatformVersion) -> String? { + nil + } + + func validateMacroFingerprint(for macroModule: ResolvedModule) -> Bool { + true } +} - private func addDependency( - to target: ResolvedModule, - in pifTarget: PIFTargetBuilder, - conditions: [PackageCondition], - linkProduct: Bool +fileprivate func buildAggregateProject( + packagesAndProjects: [(package: ResolvedPackage, project: ProjectModel.Project)], + observabilityScope: ObservabilityScope +) throws -> ProjectModel.Project { + precondition(!packagesAndProjects.isEmpty) + + var aggregateProject = ProjectModel.Project( + id: "AGGREGATE", + path: packagesAndProjects[0].project.path, + projectDir: packagesAndProjects[0].project.projectDir, + name: "Aggregate", + developmentRegion: "en" + ) + observabilityScope.logPIF(.debug, "Created project '\(aggregateProject.id)' with name '\(aggregateProject.name)'") + + var settings = ProjectModel.BuildSettings() + settings[.PRODUCT_NAME] = "$(TARGET_NAME)" + settings[.SUPPORTED_PLATFORMS] = ["$(AVAILABLE_PLATFORMS)"] + settings[.SDKROOT] = "auto" + settings[.SDK_VARIANT] = "auto" + settings[.SKIP_INSTALL] = "YES" + + aggregateProject.addBuildConfig { id in BuildConfig(id: id, name: "Debug", settings: settings) } + aggregateProject.addBuildConfig { id in BuildConfig(id: id, name: "Release", settings: settings) } + + func addEmptyBuildConfig( + to targetKeyPath: WritableKeyPath, + name: String ) { - // Only add the binary target as a library when we want to link against the product. - if let binaryTarget = target.underlying as? BinaryModule { - let ref = self.binaryGroup.addFileReference(path: binaryTarget.artifactPath.pathString) - pifTarget.addLibrary(ref, platformFilters: conditions.toPlatformFilters()) - } else { - // If this is an executable target, the dependency should be to the PIF target created from the its - // product, as we don't have PIF targets corresponding to executable targets. - let targetGUID = self.executableTargetProductMap[target.id]?.pifTargetGUID ?? target.pifTargetGUID - let linkProduct = linkProduct && target.type != .systemModule && target.type != .executable - pifTarget.addDependency( - toTargetWithGUID: targetGUID, - platformFilters: conditions.toPlatformFilters(), - linkProduct: linkProduct - ) + let emptySettings = BuildSettings() + aggregateProject[keyPath: targetKeyPath].common.addBuildConfig { id in + BuildConfig(id: id, name: name, settings: emptySettings) } } - - private func addDependency( - to product: ResolvedProduct, - in pifTarget: PIFTargetBuilder, - conditions: [PackageCondition], - linkProduct: Bool - ) { - pifTarget.addDependency( - toTargetWithGUID: product.pifTargetGUID, - platformFilters: conditions.toPlatformFilters(), - linkProduct: linkProduct + + let allIncludingTestsTargetKeyPath = try aggregateProject.addAggregateTarget { _ in + ProjectModel.AggregateTarget( + id: "ALL-INCLUDING-TESTS", + name: PIFBuilder.allIncludingTestsTargetName ) } - - private func addResourceBundle(for target: ResolvedModule, in pifTarget: PIFTargetBuilder) -> String? { - guard !target.underlying.resources.isEmpty else { - return nil - } - - let bundleName = "\(package.manifest.displayName)_\(target.name)" // TODO: use identity instead? - let resourcesTarget = self.addTarget( - guid: target.pifResourceTargetGUID, - name: bundleName, - productType: .bundle, - productName: bundleName - ) - - pifTarget.addDependency( - toTargetWithGUID: resourcesTarget.guid, - platformFilters: [], - linkProduct: false + addEmptyBuildConfig(to: allIncludingTestsTargetKeyPath, name: "Debug") + addEmptyBuildConfig(to: allIncludingTestsTargetKeyPath, name: "Release") + + let allExcludingTestsTargetKeyPath = try aggregateProject.addAggregateTarget { _ in + ProjectModel.AggregateTarget( + id: "ALL-EXCLUDING-TESTS", + name: PIFBuilder.allExcludingTestsTargetName ) - - var settings = PIF.BuildSettings() - settings[.TARGET_NAME] = bundleName - settings[.PRODUCT_NAME] = "$(TARGET_NAME)" - settings[.PRODUCT_MODULE_NAME] = bundleName - let bundleIdentifier = "\(package.manifest.displayName).\(target.name).resources" - .spm_mangledToBundleIdentifier() // TODO: use identity instead? - settings[.PRODUCT_BUNDLE_IDENTIFIER] = bundleIdentifier - settings[.GENERATE_INFOPLIST_FILE] = "YES" - settings[.PACKAGE_RESOURCE_TARGET_KIND] = "resource" - - resourcesTarget.addBuildConfiguration(name: "Debug", settings: settings) - resourcesTarget.addBuildConfiguration(name: "Release", settings: settings) - - let coreDataFileTypes = [SwiftBuildFileType.xcdatamodeld, .xcdatamodel].flatMap(\.fileTypes) - for resource in target.underlying.resources { - // FIXME: Handle rules here. - let resourceFile = groupTree.addFileReference( - path: resource.path.pathString, - sourceTree: .absolute - ) - - // CoreData files should also be in the actual target because they can end up generating code during the - // build. The build system will only perform codegen tasks for the main target in this case. - if coreDataFileTypes.contains(resource.path.extension ?? "") { - pifTarget.addSourceFile(resourceFile) - } - - // Asset Catalogs need to be included in the sources target for generated asset symbols. - if SwiftBuildFileType.xcassets.fileTypes.contains(resource.path.extension ?? "") { - pifTarget.addSourceFile(resourceFile) - } - - resourcesTarget.addResourceFile(resourceFile) - } - - let targetGroup = groupTree.addGroup(path: "/", sourceTree: .group) - pifTarget.addResourceFile(targetGroup.addFileReference( - path: "\(bundleName)\(parameters.triple.nsbundleExtension)", - sourceTree: .builtProductsDir - )) - - return bundleName - } - - // Add inferred build settings for a particular value for a manifest setting and value. - private func addInferredBuildSettings( - for setting: PIF.BuildSettings.MultipleValueSetting, - value: [String], - platform: PIF.BuildSettings.Platform? = nil, - configuration: BuildConfiguration, - settings: inout PIF.BuildSettings - ) { - // Automatically set SWIFT_EMIT_MODULE_INTERFACE if the package author uses unsafe flags to enable - // library evolution (this is needed until there is a way to specify this in the package manifest). - if setting == .OTHER_SWIFT_FLAGS && value.contains("-enable-library-evolution") { - settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES" - } } - - // Apply target-specific build settings defined in the manifest. - private func addManifestBuildSettings( - from target: Module, - debugSettings: inout PIF.BuildSettings, - releaseSettings: inout PIF.BuildSettings, - impartedSettings: inout PIF.BuildSettings - ) throws { - for (setting, assignments) in target.buildSettings.pifAssignments { - for assignment in assignments { - var value = assignment.value - if setting == .HEADER_SEARCH_PATHS { - value = try value - .map { try AbsolutePath(validating: $0, relativeTo: target.sources.root).pathString } + addEmptyBuildConfig(to: allExcludingTestsTargetKeyPath, name: "Debug") + addEmptyBuildConfig(to: allExcludingTestsTargetKeyPath, name: "Release") + + for (package, packageProject) in packagesAndProjects where package.manifest.packageKind.isRoot { + for target in packageProject.targets { + switch target { + case .target(let target): + guard !target.id.hasSuffix(.dynamic) else { + // Otherwise we hit a bunch of "Unknown multiple commands produce: ..." errors, + // as the build artifacts from "PACKAGE-TARGET:Foo" + // conflicts with those from "PACKAGE-TARGET:Foo-dynamic". + continue } - - if let platforms = assignment.platforms { - for platform in platforms { - for configuration in assignment.configurations { - switch configuration { - case .debug: - debugSettings[setting, for: platform, default: ["$(inherited)"]] += value - self.addInferredBuildSettings( - for: setting, - value: value, - platform: platform, - configuration: .debug, - settings: &debugSettings - ) - case .release: - releaseSettings[setting, for: platform, default: ["$(inherited)"]] += value - self.addInferredBuildSettings( - for: setting, - value: value, - platform: platform, - configuration: .release, - settings: &releaseSettings - ) - } - } - - if setting == .OTHER_LDFLAGS { - impartedSettings[setting, for: platform, default: ["$(inherited)"]] += value - } - } - } else { - for configuration in assignment.configurations { - switch configuration { - case .debug: - debugSettings[setting, default: ["$(inherited)"]] += value - self.addInferredBuildSettings( - for: setting, - value: value, - configuration: .debug, - settings: &debugSettings - ) - case .release: - releaseSettings[setting, default: ["$(inherited)"]] += value - self.addInferredBuildSettings( - for: setting, - value: value, - configuration: .release, - settings: &releaseSettings - ) - } - } - - if setting == .OTHER_LDFLAGS { - impartedSettings[setting, default: ["$(inherited)"]] += value - } + + aggregateProject[keyPath: allIncludingTestsTargetKeyPath].common.addDependency( + on: target.id, + platformFilters: [], + linkProduct: false + ) + if target.productType != .unitTest { + aggregateProject[keyPath: allExcludingTestsTargetKeyPath].common.addDependency( + on: target.id, + platformFilters: [], + linkProduct: false + ) } + case .aggregate: + break } } } + + do { + let allIncludingTests = aggregateProject[keyPath: allIncludingTestsTargetKeyPath] + let allExcludingTests = aggregateProject[keyPath: allExcludingTestsTargetKeyPath] + + observabilityScope.logPIF( + .debug, + indent: 1, + "Created target '\(allIncludingTests.id)' with name '\(allIncludingTests.name)' " + + "and \(allIncludingTests.common.dependencies.count) (unlinked) dependencies" + ) + observabilityScope.logPIF( + .debug, + indent: 1, + "Created target '\(allExcludingTests.id)' with name '\(allExcludingTests.name)' " + + "and \(allExcludingTests.common.dependencies.count) (unlinked) dependencies" + ) + } + + return aggregateProject } -final class AggregatePIFProjectBuilder: PIFProjectBuilder { - init(projects: [PIFProjectBuilder]) { - super.init() - - guid = "AGGREGATE" - name = "Aggregate" - path = projects[0].path - projectDirectory = projects[0].projectDirectory - developmentRegion = "en" - - var settings = PIF.BuildSettings() - settings[.PRODUCT_NAME] = "$(TARGET_NAME)" - settings[.SUPPORTED_PLATFORMS] = ["$(AVAILABLE_PLATFORMS)"] - settings[.SDKROOT] = "auto" - settings[.SDK_VARIANT] = "auto" - settings[.SKIP_INSTALL] = "YES" - - addBuildConfiguration(name: "Debug", settings: settings) - addBuildConfiguration(name: "Release", settings: settings) - - let allExcludingTestsTarget = addAggregateTarget( - guid: "ALL-EXCLUDING-TESTS", - name: PIFBuilder.allExcludingTestsTargetName - ) - - allExcludingTestsTarget.addBuildConfiguration(name: "Debug") - allExcludingTestsTarget.addBuildConfiguration(name: "Release") - - let allIncludingTestsTarget = addAggregateTarget( - guid: "ALL-INCLUDING-TESTS", - name: PIFBuilder.allIncludingTestsTargetName - ) - - allIncludingTestsTarget.addBuildConfiguration(name: "Debug") - allIncludingTestsTarget.addBuildConfiguration(name: "Release") - - for case let project as _PackagePIFProjectBuilder in projects where project.isRootPackage { - for case let target as PIFTargetBuilder in project.targets { - if target.productType != .unitTest { - allExcludingTestsTarget.addDependency( - toTargetWithGUID: target.guid, - platformFilters: [], - linkProduct: false - ) - } - - allIncludingTestsTarget.addDependency( - toTargetWithGUID: target.guid, - platformFilters: [], - linkProduct: false - ) - } - } - } -} - -protocol PIFReferenceBuilder: AnyObject { - var guid: String { get set } - - func construct() -> PIF.Reference -} - -final class PIFFileReferenceBuilder: PIFReferenceBuilder { - let path: String - let sourceTree: PIF.Reference.SourceTree - let name: String? - let fileType: String? - - @DelayedImmutable - var guid: String - - init(path: String, sourceTree: PIF.Reference.SourceTree, name: String? = nil, fileType: String? = nil) { - self.path = path - self.sourceTree = sourceTree - self.name = name - self.fileType = fileType - } - - func construct() -> PIF.Reference { - PIF.FileReference( - guid: self.guid, - path: self.path, - sourceTree: self.sourceTree, - name: self.name, - fileType: self.fileType - ) - } -} - -final class PIFGroupBuilder: PIFReferenceBuilder { - let path: String - let sourceTree: PIF.Reference.SourceTree - let name: String? - private(set) var children: [PIFReferenceBuilder] - - @DelayedImmutable - var guid: PIF.GUID - - init(path: String, sourceTree: PIF.Reference.SourceTree = .group, name: String? = nil) { - self.path = path - self.sourceTree = sourceTree - self.name = name - self.children = [] - } - - /// Creates and appends a new Group to the list of children. The new group is returned so that it can be configured. - func addGroup( - path: String, - sourceTree: PIF.Reference.SourceTree = .group, - name: String? = nil - ) -> PIFGroupBuilder { - let group = PIFGroupBuilder(path: path, sourceTree: sourceTree, name: name) - self.children.append(group) - return group - } - - /// Creates and appends a new FileReference to the list of children. - func addFileReference( - path: String, - sourceTree: PIF.Reference.SourceTree = .group, - name: String? = nil, - fileType: String? = nil - ) -> PIFFileReferenceBuilder { - let file = PIFFileReferenceBuilder(path: path, sourceTree: sourceTree, name: name, fileType: fileType) - self.children.append(file) - return file - } - - func removeChild(_ reference: PIFReferenceBuilder) { - self.children.removeAll { $0 === reference } - } - - func construct() -> PIF.Reference { - let children = self.children.enumerated().map { kvp -> PIF.Reference in - let (index, builder) = kvp - builder.guid = "\(self.guid)::REF_\(index)" - return builder.construct() - } - - return PIF.Group( - guid: self.guid, - path: self.path, - sourceTree: self.sourceTree, - name: self.name, - children: children - ) - } -} - -class PIFBaseTargetBuilder { - public let guid: PIF.GUID - public let name: String - public fileprivate(set) var buildConfigurations: [PIFBuildConfigurationBuilder] - public fileprivate(set) var buildPhases: [PIFBuildPhaseBuilder] - public fileprivate(set) var dependencies: [PIF.TargetDependency] - public fileprivate(set) var impartedBuildSettings: PIF.BuildSettings - - fileprivate init(guid: PIF.GUID, name: String) { - self.guid = guid - self.name = name - self.buildConfigurations = [] - self.buildPhases = [] - self.dependencies = [] - self.impartedBuildSettings = PIF.BuildSettings() - } - - /// Creates and adds a new empty build configuration, i.e. one that does not initially have any build settings. - /// The name must not be empty and must not be equal to the name of any existing build configuration in the - /// target. - @discardableResult - public func addBuildConfiguration( - name: String, - settings: PIF.BuildSettings = PIF.BuildSettings(), - impartedBuildProperties: PIF.ImpartedBuildProperties = PIF - .ImpartedBuildProperties(settings: PIF.BuildSettings()) - ) -> PIFBuildConfigurationBuilder { - let builder = PIFBuildConfigurationBuilder( - name: name, - settings: settings, - impartedBuildProperties: impartedBuildProperties - ) - self.buildConfigurations.append(builder) - return builder - } - - func construct() throws -> PIF.BaseTarget { - throw InternalError("implement in subclass") - } - - /// Adds a "headers" build phase, i.e. one that copies headers into a directory of the product, after suitable - /// processing. - @discardableResult - func addHeadersBuildPhase() -> PIFHeadersBuildPhaseBuilder { - let buildPhase = PIFHeadersBuildPhaseBuilder() - self.buildPhases.append(buildPhase) - return buildPhase - } - - /// Adds a "sources" build phase, i.e. one that compiles sources and provides them to be linked into the - /// executable code of the product. - @discardableResult - func addSourcesBuildPhase() -> PIFSourcesBuildPhaseBuilder { - let buildPhase = PIFSourcesBuildPhaseBuilder() - self.buildPhases.append(buildPhase) - return buildPhase - } - - /// Adds a "frameworks" build phase, i.e. one that links compiled code and libraries into the executable of the - /// product. - @discardableResult - func addFrameworksBuildPhase() -> PIFFrameworksBuildPhaseBuilder { - let buildPhase = PIFFrameworksBuildPhaseBuilder() - self.buildPhases.append(buildPhase) - return buildPhase - } - - @discardableResult - func addResourcesBuildPhase() -> PIFResourcesBuildPhaseBuilder { - let buildPhase = PIFResourcesBuildPhaseBuilder() - self.buildPhases.append(buildPhase) - return buildPhase - } - - /// Adds a dependency on another target. It is the caller's responsibility to avoid creating dependency cycles. - /// A dependency of one target on another ensures that the other target is built first. If `linkProduct` is - /// true, the receiver will also be configured to link against the product produced by the other target (this - /// presumes that the product type is one that can be linked against). - func addDependency(toTargetWithGUID targetGUID: String, platformFilters: [PIF.PlatformFilter], linkProduct: Bool) { - self.dependencies.append(.init(targetGUID: targetGUID, platformFilters: platformFilters)) - if linkProduct { - let frameworksPhase = self.buildPhases.first { $0 is PIFFrameworksBuildPhaseBuilder } - ?? self.addFrameworksBuildPhase() - frameworksPhase.addBuildFile(toTargetWithGUID: targetGUID, platformFilters: platformFilters) - } - } - - /// Convenience function to add a file reference to the Headers build phase, after creating it if needed. - @discardableResult - public func addHeaderFile( - _ fileReference: PIFFileReferenceBuilder, - headerVisibility: PIF.BuildFile.HeaderVisibility - ) -> PIFBuildFileBuilder { - let headerPhase = self.buildPhases.first { $0 is PIFHeadersBuildPhaseBuilder } ?? self.addHeadersBuildPhase() - return headerPhase.addBuildFile(to: fileReference, platformFilters: [], headerVisibility: headerVisibility) - } - - /// Convenience function to add a file reference to the Sources build phase, after creating it if needed. - @discardableResult - public func addSourceFile(_ fileReference: PIFFileReferenceBuilder) -> PIFBuildFileBuilder { - let sourcesPhase = self.buildPhases.first { $0 is PIFSourcesBuildPhaseBuilder } ?? self.addSourcesBuildPhase() - return sourcesPhase.addBuildFile(to: fileReference, platformFilters: []) - } - - /// Convenience function to add a file reference to the Frameworks build phase, after creating it if needed. - @discardableResult - public func addLibrary( - _ fileReference: PIFFileReferenceBuilder, - platformFilters: [PIF.PlatformFilter] - ) -> PIFBuildFileBuilder { - let frameworksPhase = self.buildPhases.first { $0 is PIFFrameworksBuildPhaseBuilder } ?? self - .addFrameworksBuildPhase() - return frameworksPhase.addBuildFile(to: fileReference, platformFilters: platformFilters) - } - - @discardableResult - public func addResourceFile(_ fileReference: PIFFileReferenceBuilder) -> PIFBuildFileBuilder { - let resourcesPhase = self.buildPhases.first { $0 is PIFResourcesBuildPhaseBuilder } ?? self - .addResourcesBuildPhase() - return resourcesPhase.addBuildFile(to: fileReference, platformFilters: []) - } - - fileprivate func constructBuildConfigurations() -> [PIF.BuildConfiguration] { - self.buildConfigurations.map { builder -> PIF.BuildConfiguration in - builder.guid = "\(self.guid)::BUILDCONFIG_\(builder.name)" - return builder.construct() - } - } - - fileprivate func constructBuildPhases() throws -> [PIF.BuildPhase] { - try self.buildPhases.enumerated().map { kvp in - let (index, builder) = kvp - builder.guid = "\(self.guid)::BUILDPHASE_\(index)" - return try builder.construct() - } - } -} - -final class PIFAggregateTargetBuilder: PIFBaseTargetBuilder { - override func construct() throws -> PIF.BaseTarget { - try PIF.AggregateTarget( - guid: guid, - name: name, - buildConfigurations: constructBuildConfigurations(), - buildPhases: self.constructBuildPhases(), - dependencies: dependencies, - impartedBuildSettings: impartedBuildSettings - ) - } -} - -final class PIFTargetBuilder: PIFBaseTargetBuilder { - let productType: PIF.Target.ProductType - let productName: String - var productReference: PIF.FileReference? = nil - - public init(guid: PIF.GUID, name: String, productType: PIF.Target.ProductType, productName: String) { - self.productType = productType - self.productName = productName - super.init(guid: guid, name: name) - } - - override func construct() throws -> PIF.BaseTarget { - try PIF.Target( - guid: guid, - name: name, - productType: self.productType, - productName: self.productName, - buildConfigurations: constructBuildConfigurations(), - buildPhases: self.constructBuildPhases(), - dependencies: dependencies, - impartedBuildSettings: impartedBuildSettings - ) - } -} - -class PIFBuildPhaseBuilder { - public private(set) var buildFiles: [PIFBuildFileBuilder] - - @DelayedImmutable - var guid: PIF.GUID - - fileprivate init() { - self.buildFiles = [] - } - - /// Adds a new build file builder that refers to a file reference. - /// - Parameters: - /// - file: The builder for the file reference. - @discardableResult - func addBuildFile( - to file: PIFFileReferenceBuilder, - platformFilters: [PIF.PlatformFilter], - headerVisibility: PIF.BuildFile.HeaderVisibility? = nil - ) -> PIFBuildFileBuilder { - let builder = PIFBuildFileBuilder( - file: file, - platformFilters: platformFilters, - headerVisibility: headerVisibility - ) - self.buildFiles.append(builder) - return builder - } - - /// Adds a new build file builder that refers to a target GUID. - /// - Parameters: - /// - targetGUID: The GIUD referencing the target. - @discardableResult - func addBuildFile( - toTargetWithGUID targetGUID: PIF.GUID, - platformFilters: [PIF.PlatformFilter] - ) -> PIFBuildFileBuilder { - let builder = PIFBuildFileBuilder(targetGUID: targetGUID, platformFilters: platformFilters) - self.buildFiles.append(builder) - return builder - } - - func construct() throws -> PIF.BuildPhase { - throw InternalError("implement in subclass") - } - - fileprivate func constructBuildFiles() -> [PIF.BuildFile] { - self.buildFiles.enumerated().map { kvp -> PIF.BuildFile in - let (index, builder) = kvp - builder.guid = "\(self.guid)::\(index)" - return builder.construct() - } - } -} - -final class PIFHeadersBuildPhaseBuilder: PIFBuildPhaseBuilder { - override func construct() -> PIF.BuildPhase { - PIF.HeadersBuildPhase(guid: guid, buildFiles: constructBuildFiles()) - } -} - -final class PIFSourcesBuildPhaseBuilder: PIFBuildPhaseBuilder { - override func construct() -> PIF.BuildPhase { - PIF.SourcesBuildPhase(guid: guid, buildFiles: constructBuildFiles()) - } -} - -final class PIFFrameworksBuildPhaseBuilder: PIFBuildPhaseBuilder { - override func construct() -> PIF.BuildPhase { - PIF.FrameworksBuildPhase(guid: guid, buildFiles: constructBuildFiles()) - } -} - -final class PIFResourcesBuildPhaseBuilder: PIFBuildPhaseBuilder { - override func construct() -> PIF.BuildPhase { - PIF.ResourcesBuildPhase(guid: guid, buildFiles: constructBuildFiles()) - } -} - -final class PIFBuildFileBuilder { - private enum Reference { - case file(builder: PIFFileReferenceBuilder) - case target(guid: PIF.GUID) - - var pifReference: PIF.BuildFile.Reference { - switch self { - case .file(let builder): - .file(guid: builder.guid) - case .target(let guid): - .target(guid: guid) - } - } - } - - private let reference: Reference - - @DelayedImmutable - var guid: PIF.GUID - - let platformFilters: [PIF.PlatformFilter] - - let headerVisibility: PIF.BuildFile.HeaderVisibility? - - fileprivate init( - file: PIFFileReferenceBuilder, - platformFilters: [PIF.PlatformFilter], - headerVisibility: PIF.BuildFile.HeaderVisibility? = nil - ) { - self.reference = .file(builder: file) - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - fileprivate init( - targetGUID: PIF.GUID, - platformFilters: [PIF.PlatformFilter], - headerVisibility: PIF.BuildFile.HeaderVisibility? = nil - ) { - self.reference = .target(guid: targetGUID) - self.platformFilters = platformFilters - self.headerVisibility = headerVisibility - } - - func construct() -> PIF.BuildFile { - PIF.BuildFile( - guid: self.guid, - reference: self.reference.pifReference, - platformFilters: self.platformFilters, - headerVisibility: self.headerVisibility - ) - } -} - -final class PIFBuildConfigurationBuilder { - let name: String - let settings: PIF.BuildSettings - let impartedBuildProperties: PIF.ImpartedBuildProperties - - @DelayedImmutable - var guid: PIF.GUID - - public init(name: String, settings: PIF.BuildSettings, impartedBuildProperties: PIF.ImpartedBuildProperties) { - precondition(!name.isEmpty) - self.name = name - self.settings = settings - self.impartedBuildProperties = impartedBuildProperties - } - - func construct() -> PIF.BuildConfiguration { - PIF.BuildConfiguration( - guid: self.guid, - name: self.name, - buildSettings: self.settings, - impartedBuildProperties: self.impartedBuildProperties - ) - } -} - -struct XCBBuildParameters: Encodable { - struct RunDestination: Encodable { - var platform: String - var sdk: String - var sdkVariant: String? - var targetArchitecture: String - var supportedArchitectures: [String] - var disableOnlyActiveArch: Bool - } - - struct XCBSettingsTable: Encodable { - var table: [String: String] - } - - struct SettingsOverride: Encodable { - var synthesized: XCBSettingsTable? = nil - } - - var configurationName: String - var overrides: SettingsOverride - var activeRunDestination: RunDestination -} - -// Helper functions to consistently generate a PIF target identifier string for a product/target/resource bundle in a -// package. This format helps make sure that there is no collision with any other PIF targets, and in particular that a -// PIF target and a PIF product can have the same name (as they often do). - -extension ResolvedPackage { - var pifProjectGUID: PIF.GUID { "PACKAGE:\(manifest.packageLocation)" } -} - -extension ResolvedProduct { - var pifTargetGUID: PIF.GUID { "PACKAGE-PRODUCT:\(name)" } - - var mainTarget: ResolvedModule { - modules.first { $0.type == underlying.type._targetType }! - } - - /// Returns the recursive dependencies, limited to the target's package, which satisfy the input build environment, - /// based on their conditions and in a stable order. - /// - Parameters: - /// - environment: The build environment to use to filter dependencies on. - public func recursivePackageDependencies() -> [ResolvedModule.Dependency] { - let initialDependencies = modules.map { ResolvedModule.Dependency.module($0, conditions: []) } - return try! topologicalSort(initialDependencies) { dependency in - dependency.packageDependencies - }.sorted() - } -} - -extension ResolvedModule { - var pifTargetGUID: PIF.GUID { "PACKAGE-TARGET:\(name)" } - var pifResourceTargetGUID: PIF.GUID { "PACKAGE-RESOURCE:\(name)" } -} - -extension [ResolvedModule.Dependency] { - /// Sorts to get products first, sorted by name, followed by targets, sorted by name. - func sorted() -> [ResolvedModule.Dependency] { - self.sorted { lhsDependency, rhsDependency in - switch (lhsDependency, rhsDependency) { - case (.product, .module): - true - case (.module, .product): - false - case (.product(let lhsProduct, _), .product(let rhsProduct, _)): - lhsProduct.name < rhsProduct.name - case (.module(let lhsTarget, _), .module(let rhsTarget, _)): - lhsTarget.name < rhsTarget.name - } - } - } -} - -extension ResolvedPackage { - func deploymentTarget(for platform: PackageModel.Platform, usingXCTest: Bool = false) -> String? { - self.getSupportedPlatform(for: platform, usingXCTest: usingXCTest).version.versionString - } -} - -extension ResolvedModule { - func deploymentTarget(for platform: PackageModel.Platform, usingXCTest: Bool = false) -> String? { - self.getSupportedPlatform(for: platform, usingXCTest: usingXCTest).version.versionString - } -} - -extension Module { - var isCxx: Bool { - (self as? ClangModule)?.isCXX ?? false - } -} - -extension ProductType { - var _targetType: Module.Kind { - switch self { - case .executable: - .executable - case .snippet: - .snippet - case .test: - .test - case .library: - .library - case .plugin: - .plugin - case .macro: - .macro - } - } -} - -private struct PIFBuildSettingAssignment { - /// The assignment value. - let value: [String] - - /// The configurations this assignment applies to. - let configurations: [BuildConfiguration] - - /// The platforms this assignment is restrained to, or nil to apply to all platforms. - let platforms: [PIF.BuildSettings.Platform]? -} - -extension PackageModel.BuildSettings.AssignmentTable { - fileprivate var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] { - var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] = [:] - - for (declaration, assignments) in self.assignments { - for assignment in assignments { - let setting: PIF.BuildSettings.MultipleValueSetting - let value: [String] - - switch declaration { - case .LINK_LIBRARIES: - setting = .OTHER_LDFLAGS - value = assignment.values.map { "-l\($0)" } - case .LINK_FRAMEWORKS: - setting = .OTHER_LDFLAGS - value = assignment.values.flatMap { ["-framework", $0] } - default: - guard let parsedSetting = PIF.BuildSettings.MultipleValueSetting(rawValue: declaration.name) else { - continue - } - setting = parsedSetting - value = assignment.values - } - - let pifAssignment = PIFBuildSettingAssignment( - value: value, - configurations: assignment.configurations, - platforms: assignment.pifPlatforms - ) - - pifAssignments[setting, default: []].append(pifAssignment) - } - } - - return pifAssignments - } -} - -extension PackageModel.BuildSettings.Assignment { - fileprivate var configurations: [BuildConfiguration] { - if let configurationCondition = conditions.lazy.compactMap(\.configurationCondition).first { - [configurationCondition.configuration] - } else { - BuildConfiguration.allCases - } - } - - fileprivate var pifPlatforms: [PIF.BuildSettings.Platform]? { - if let platformsCondition = conditions.lazy.compactMap(\.platformsCondition).first { - platformsCondition.platforms.compactMap { PIF.BuildSettings.Platform(rawValue: $0.name) } - } else { - nil - } - } -} - -@propertyWrapper -public struct DelayedImmutable { - private var _value: Value? = nil - - public init() {} - - public var wrappedValue: Value { - get { - guard let value = _value else { - fatalError("property accessed before being initialized") - } - return value - } - set { - if self._value != nil { - fatalError("property initialized twice") - } - self._value = newValue - } - } -} - -extension [PackageCondition] { - func toPlatformFilters() -> [PIF.PlatformFilter] { - var result: [PIF.PlatformFilter] = [] - let platformConditions = self.compactMap(\.platformsCondition).flatMap(\.platforms) - - for condition in platformConditions { - switch condition { - case .macOS: - result += PIF.PlatformFilter.macOSFilters - - case .macCatalyst: - result += PIF.PlatformFilter.macCatalystFilters - - case .iOS: - result += PIF.PlatformFilter.iOSFilters - - case .tvOS: - result += PIF.PlatformFilter.tvOSFilters - - case .watchOS: - result += PIF.PlatformFilter.watchOSFilters - - case .visionOS: - result += PIF.PlatformFilter.visionOSFilters - - case .linux: - result += PIF.PlatformFilter.linuxFilters - - case .android: - result += PIF.PlatformFilter.androidFilters - - case .windows: - result += PIF.PlatformFilter.windowsFilters - - case .driverKit: - result += PIF.PlatformFilter.driverKitFilters - - case .wasi: - result += PIF.PlatformFilter.webAssemblyFilters - - case .openbsd: - result += PIF.PlatformFilter.openBSDFilters - - case .freebsd: - result += PIF.PlatformFilter.freeBSDFilters - - default: - assertionFailure("Unhandled platform condition: \(condition)") - } - } - return result - } -} - -extension PIF.PlatformFilter { - /// macOS platform filters. - public static let macOSFilters: [PIF.PlatformFilter] = [.init(platform: "macos")] - - /// Mac Catalyst platform filters. - public static let macCatalystFilters: [PIF.PlatformFilter] = [ - .init(platform: "ios", environment: "maccatalyst"), - ] - - /// iOS platform filters. - public static let iOSFilters: [PIF.PlatformFilter] = [ - .init(platform: "ios"), - .init(platform: "ios", environment: "simulator"), - ] - - /// tvOS platform filters. - public static let tvOSFilters: [PIF.PlatformFilter] = [ - .init(platform: "tvos"), - .init(platform: "tvos", environment: "simulator"), - ] - - /// watchOS platform filters. - public static let watchOSFilters: [PIF.PlatformFilter] = [ - .init(platform: "watchos"), - .init(platform: "watchos", environment: "simulator"), - ] - - /// DriverKit platform filters. - public static let driverKitFilters: [PIF.PlatformFilter] = [ - .init(platform: "driverkit"), - ] - - /// Windows platform filters. - public static let windowsFilters: [PIF.PlatformFilter] = [ - .init(platform: "windows", environment: "msvc"), - .init(platform: "windows", environment: "gnu"), - ] - - /// Android platform filters. - public static let androidFilters: [PIF.PlatformFilter] = [ - .init(platform: "linux", environment: "android"), - .init(platform: "linux", environment: "androideabi"), - ] - - /// Common Linux platform filters. - public static let linuxFilters: [PIF.PlatformFilter] = ["", "eabi", "gnu", "gnueabi", "gnueabihf"].map { - .init(platform: "linux", environment: $0) - } - - /// OpenBSD filters. - public static let openBSDFilters: [PIF.PlatformFilter] = [ - .init(platform: "openbsd"), - ] - - /// WebAssembly platform filters. - public static let webAssemblyFilters: [PIF.PlatformFilter] = [ - .init(platform: "wasi"), - ] - - /// VisionOS platform filters. - public static let visionOSFilters: [PIF.PlatformFilter] = [ - .init(platform: "xros"), - .init(platform: "xros", environment: "simulator"), - .init(platform: "visionos"), - .init(platform: "visionos", environment: "simulator"), - ] - - /// FreeBSD filters. - public static let freeBSDFilters: [PIF.PlatformFilter] = [ - .init(platform: "freebsd"), - ] -} - -extension PIF.BuildSettings { - fileprivate mutating func addSwiftVersionSettings( - target: SwiftModule, - parameters: PIFBuilderParameters - ) throws { - guard let versionAssignments = target.buildSettings.assignments[.SWIFT_VERSION] else { - // This should never happens in practice because there is always a default tools version based value. - return - } - - func isSupportedVersion(_ version: SwiftLanguageVersion) -> Bool { - parameters.supportedSwiftVersions.isEmpty || parameters.supportedSwiftVersions.contains(version) - } - - func computeEffectiveSwiftVersions(for versions: [SwiftLanguageVersion]) -> [String] { - versions - .filter { target.declaredSwiftVersions.contains($0) } - .filter { isSupportedVersion($0) }.map(\.description) - } - - func computeEffectiveTargetVersion(for assignment: PackageModel.BuildSettings.Assignment) throws -> String { - let versions = assignment.values.compactMap { SwiftLanguageVersion(string: $0) } - if let effectiveVersion = computeEffectiveSwiftVersions(for: versions).last { - return effectiveVersion - } - - throw PIFGenerationError.unsupportedSwiftLanguageVersions( - targetName: target.name, - versions: versions, - supportedVersions: parameters.supportedSwiftVersions - ) - } - - var toolsSwiftVersion: SwiftLanguageVersion? = nil - // First, check whether there are any target specific settings. - for assignment in versionAssignments { - if assignment.default { - toolsSwiftVersion = assignment.values.first.flatMap { .init(string: $0) } - continue - } - - if assignment.conditions.isEmpty { - self[.SWIFT_VERSION] = try computeEffectiveTargetVersion(for: assignment) - continue - } - - for condition in assignment.conditions { - if let platforms = condition.platformsCondition { - for platform: Platform in platforms.platforms.compactMap({ .init(rawValue: $0.name) }) { - self[.SWIFT_VERSION, for: platform] = try computeEffectiveTargetVersion(for: assignment) - } - } - } - } - - // If there were no target specific assignments, let's add a fallback tools version based value. - if let toolsSwiftVersion, self[.SWIFT_VERSION] == nil { - // Use tools based version if it's supported. - if isSupportedVersion(toolsSwiftVersion) { - self[.SWIFT_VERSION] = toolsSwiftVersion.description - return - } - - // Otherwise pick the newest supported tools version based value. - - // We have to normalize to two component strings to match the results from SwiftBuild w.r.t. to hashing of - // `SwiftLanguageVersion` instances. - let normalizedDeclaredVersions = Set(target.declaredSwiftVersions.compactMap { - SwiftLanguageVersion(string: "\($0.major).\($0.minor)") - }) - - let declaredSwiftVersions = Array( - normalizedDeclaredVersions - .intersection(parameters.supportedSwiftVersions) - ).sorted(by: >) - if let swiftVersion = declaredSwiftVersions.first { - self[.SWIFT_VERSION] = swiftVersion.description - return - } - - throw PIFGenerationError.unsupportedSwiftLanguageVersions( - targetName: target.name, - versions: Array(normalizedDeclaredVersions), - supportedVersions: parameters.supportedSwiftVersions - ) - } - } - - fileprivate mutating func addCommonSwiftSettings( - package: ResolvedPackage, - target: ResolvedModule, - parameters: PIFBuilderParameters - ) { - let packageOptions = package.packageNameArgument( - target: target, - isPackageNameSupported: parameters.isPackageAccessModifierSupported - ) - if !packageOptions.isEmpty { - self[.OTHER_SWIFT_FLAGS] = packageOptions - } - } -} - -extension PIF.BuildSettings.Platform { - fileprivate static func from(platform: PackageModel.Platform) -> PIF.BuildSettings.Platform? { - switch platform { - case .iOS: .iOS - case .linux: .linux - case .macCatalyst: .macCatalyst - case .macOS: .macOS - case .tvOS: .tvOS - case .watchOS: .watchOS - case .driverKit: .driverKit - default: nil - } - } -} +#endif public enum PIFGenerationError: Error { + case rootPackageNotFound, multipleRootPackagesFound + case unsupportedSwiftLanguageVersions( targetName: String, versions: [SwiftLanguageVersion], @@ -1975,12 +409,19 @@ public enum PIFGenerationError: Error { extension PIFGenerationError: CustomStringConvertible { public var description: String { switch self { + case .rootPackageNotFound: + "No root package was found" + + case .multipleRootPackagesFound: + "Multiple root packages were found, making the PIF generation (root packages) ordering sensitive" + case .unsupportedSwiftLanguageVersions( targetName: let target, versions: let given, supportedVersions: let supported ): - "None of the Swift language versions used in target '\(target)' settings are supported. (given: \(given), supported: \(supported))" + "None of the Swift language versions used in target '\(target)' settings are supported." + + " (given: \(given), supported: \(supported))" } } } diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index f8819eaf0cf..e1689c1b086 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -55,17 +55,27 @@ import struct PackageGraph.ResolvedProduct import func PackageLoading.pkgConfigArgs +// TODO: Move this back to `PackagePIFBuilder` once we get rid of `#if canImport(SwiftBuild)`. +func targetName(forProductName name: String, suffix: String? = nil) -> String { + let suffix = suffix ?? "" + return "\(name)\(suffix)-product" +} + #if canImport(SwiftBuild) import enum SwiftBuild.ProjectModel // MARK: - PIF GUID Helpers -enum TargetGUIDSuffix: String { +enum TargetSuffix: String { case testable, dynamic + + func hasSuffix(id: GUID) -> Bool { + id.value.hasSuffix("-\(self.rawValue)") + } } -extension TargetGUIDSuffix? { +extension TargetSuffix? { func description(forName name: String) -> String { switch self { case .some(let suffix): @@ -76,34 +86,45 @@ extension TargetGUIDSuffix? { } } +extension GUID { + func hasSuffix(_ suffix: TargetSuffix) -> Bool { + self.value.hasSuffix("-\(suffix.rawValue)") + } +} + extension PackageModel.Module { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { + var pifTargetGUID: GUID { pifTargetGUID(suffix: nil) } + + func pifTargetGUID(suffix: TargetSuffix?) -> GUID { PackagePIFBuilder.targetGUID(forModuleName: self.name, suffix: suffix) } } extension PackageGraph.ResolvedModule { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { + var pifTargetGUID: GUID { pifTargetGUID(suffix: nil) } + + func pifTargetGUID(suffix: TargetSuffix?) -> GUID { self.underlying.pifTargetGUID(suffix: suffix) } } extension PackageModel.Product { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { + var pifTargetGUID: GUID { pifTargetGUID(suffix: nil) } + + func pifTargetGUID(suffix: TargetSuffix?) -> GUID { PackagePIFBuilder.targetGUID(forProductName: self.name, suffix: suffix) } } extension PackageGraph.ResolvedProduct { - func pifTargetGUID(suffix: TargetGUIDSuffix? = nil) -> GUID { + var pifTargetGUID: GUID { pifTargetGUID(suffix: nil) } + + func pifTargetGUID(suffix: TargetSuffix?) -> GUID { self.underlying.pifTargetGUID(suffix: suffix) } - /// Helper function to consistently generate a target name string for a product in a package. - /// This format helps make sure that targets and products with the same name (as they often have) have different - /// target names in the PIF. - func targetNameForProduct(suffix: String = "") -> String { - "\(name)\(suffix) product" + func targetName(suffix: TargetSuffix? = nil) -> String { + PackagePIFBuilder.targetName(forProductName: self.name, suffix: suffix) } } @@ -112,7 +133,7 @@ extension PackagePIFBuilder { /// /// This format helps make sure that there is no collision with any other PIF targets, /// and in particular that a PIF target and a PIF product can have the same name (as they often do). - static func targetGUID(forModuleName name: String, suffix: TargetGUIDSuffix? = nil) -> GUID { + static func targetGUID(forModuleName name: String, suffix: TargetSuffix? = nil) -> GUID { let suffixDescription = suffix.description(forName: name) return "PACKAGE-TARGET:\(name)\(suffixDescription)" } @@ -121,10 +142,17 @@ extension PackagePIFBuilder { /// /// This format helps make sure that there is no collision with any other PIF targets, /// and in particular that a PIF target and a PIF product can have the same name (as they often do). - static func targetGUID(forProductName name: String, suffix: TargetGUIDSuffix? = nil) -> GUID { + static func targetGUID(forProductName name: String, suffix: TargetSuffix? = nil) -> GUID { let suffixDescription = suffix.description(forName: name) return "PACKAGE-PRODUCT:\(name)\(suffixDescription)" } + + /// Helper function to consistently generate a target name string for a product in a package. + /// This format helps make sure that targets and products with the same name (as they often have) have different + /// target names in the PIF. + static func targetName(forProductName name: String, suffix: TargetSuffix? = nil) -> String { + return SwiftBuildSupport.targetName(forProductName: name, suffix: suffix?.rawValue) + } } // MARK: - SwiftPM PackageModel Helpers @@ -796,7 +824,14 @@ extension TSCUtility.Version { } } -// MARK: - Swift Build PIF Helpers +// MARK: - Swift Build ProjectModel Helpers + +/// Helpful for logging. +extension ProjectModel.GUID: @retroactive CustomStringConvertible { + public var description: String { + value + } +} extension ProjectModel.BuildSettings { subscript(_ setting: MultipleValueSetting, default defaultValue: [String]) -> [String] { @@ -1013,6 +1048,55 @@ extension ProjectModel.BuildSettings.Declaration { } } +// MARK: - ObservabilityScope Helpers + +extension ObservabilityScope { + /// Logs an informational PIF message (intended for developers, not end users). + func logPIF( + _ severity: Diagnostic.Severity = .debug, + indent: UInt = 0, + _ message: String, + sourceFile: StaticString = #fileID, + sourceLine: UInt = #line + ) { + var metadata = ObservabilityMetadata() + metadata.sourceLocation = SourceLocation(sourceFile, sourceLine) + + let indentation = String(repeating: " ", count: Int(indent)) + let message = "PIF: \(indentation)\(message)" + + let diagnostic = Diagnostic(severity: severity, message: message, metadata: metadata) + self.emit(diagnostic) + } +} + +extension ObservabilityMetadata { + public var sourceLocation: SourceLocation? { + get { + self[SourceLocationKey.self] + } + set { + self[SourceLocationKey.self] = newValue + } + } + + private enum SourceLocationKey: Key { + typealias Value = SourceLocation + } +} + +public struct SourceLocation: Sendable { + public let file: StaticString + public let line: UInt + + public init(_ file: StaticString, _ line: UInt) { + precondition(file.description.hasContent) + + self.file = file + self.line = line + } +} + // MARK: - General Helpers extension SourceControlURL { diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index c2582c5c8bd..88a2bee948f 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -59,18 +59,14 @@ public final class PackagePIFBuilder { /// Scope for logging informational debug messages (intended for developers, not end users). let observabilityScope: ObservabilityScope - /// Logs an informational debug message (intended for developers, not end users). + /// Logs an informational message (intended for developers, not end users). func log( _ severity: Diagnostic.Severity, _ message: String, sourceFile: StaticString = #fileID, sourceLine: UInt = #line ) { - var metadata = ObservabilityMetadata() - metadata.sourceLocation = SourceLocation(sourceFile, sourceLine) - - let diagnostic = Diagnostic(severity: severity, message: message, metadata: metadata) - self.observabilityScope.emit(diagnostic) + self.observabilityScope.logPIF(severity, message, sourceFile: sourceFile, sourceLine: sourceLine) } unowned let delegate: BuildDelegate @@ -84,15 +80,15 @@ public final class PackagePIFBuilder { /// If a pure Swift package is open in the workspace. var hostsOnlyPackages: Bool { get } - /// Returns `true` if the package is managed by the user (i.e., the user is allowed to modify its sources, - /// package structure, etc). + /// Returns `true` if the package is managed by the user + /// (i.e., the user is allowed to modify its sources, package structure, etc). var isUserManaged: Bool { get } /// Whether or not this package is required by *branch* or *revision*. var isBranchOrRevisionBased: Bool { get } - /// For executables — only executables for now — we check to see if there is a custom package product type - /// provider that can provide this information. + /// For executables — only executables for now — we check to see if there is a + /// custom package product type provider that can provide this information. func customProductType(forExecutable product: PackageModel.Product) -> ProjectModel.Target.ProductType? /// Returns all *device family* IDs for all SDK variants. @@ -373,25 +369,24 @@ public final class PackagePIFBuilder { /// Build the PIF. @discardableResult public func build() throws -> [ModuleOrProduct] { - self.log(.info, "Building PIF for package \(self.package.identity)") - - var builder = PackagePIFProjectBuilder(createForPackage: package, builder: self) - self.addProjectBuildSettings(&builder) + self.log( + .info, + "Building PIF project for package '\(self.package.identity)' " + + "(\(package.products.count) products, \(package.modules.count) modules)" + ) - self._pifProject = builder.project + var projectBuilder = PackagePIFProjectBuilder(createForPackage: package, builder: self) + self.addProjectBuildSettings(&projectBuilder) // - // Construct PIF *targets* (for modules, products, and test bundles) based on the contents of the parsed - // package. - // These PIF targets will be sent down to Swift Build. + // Construct PIF *targets* (for modules, products, and test bundles) based on the contents + // of the parsed package. These PIF targets will be sent down to Swift Build. // // We also track all constructed objects as `ModuleOrProduct` value for easy introspection by clients. // In SwiftPM a product is a codeless entity with a reference to the modules(s) that contains the - // implementation. - // In order to avoid creating two ModuleOrProducts for each product in the package, the logic below creates a - // single - // unified ModuleOrProduct from the combination of a product and the single target that contains its - // implementation. + // implementation. In order to avoid creating two ModuleOrProducts for each product in the package, + // the logic below creates a single unified ModuleOrProduct from the combination of a product + // and the single target that contains its implementation. // // Products. SwiftPM considers unit tests to be products, so in this discussion, the term *product* // refers to an *executable*, a *library*, or an *unit test*. @@ -402,54 +397,58 @@ public final class PackagePIFBuilder { // the structure of the client(s). // + self.log(.debug, "Processing \(package.products.count) products:") + // For each of the **products** in the package we create a corresponding `PIFTarget` of the appropriate type. for product in self.package.products { switch product.type { case .library(.static): let libraryType = self.delegate.customLibraryType(product: product.underlying) ?? .static - try builder.makeLibraryProduct(product, type: libraryType) + try projectBuilder.makeLibraryProduct(product, type: libraryType) case .library(.dynamic): let libraryType = self.delegate.customLibraryType(product: product.underlying) ?? .dynamic - try builder.makeLibraryProduct(product, type: libraryType) + try projectBuilder.makeLibraryProduct(product, type: libraryType) case .library(.automatic): // Check if this is a system library product. if product.isSystemLibraryProduct { - try builder.makeSystemLibraryProduct(product) + try projectBuilder.makeSystemLibraryProduct(product) } else { // Otherwise, it is a regular library product. let libraryType = self.delegate.customLibraryType(product: product.underlying) ?? .automatic - try builder.makeLibraryProduct(product, type: libraryType) + try projectBuilder.makeLibraryProduct(product, type: libraryType) } case .executable, .test: - try builder.makeMainModuleProduct(product) + try projectBuilder.makeMainModuleProduct(product) case .plugin: - try builder.makePluginProduct(product) + try projectBuilder.makePluginProduct(product) case .snippet, .macro: break // TODO: Double-check what's going on here as we skip snippet modules too (rdar://147705448) } } + self.log(.debug, "Processing \(package.modules.count) modules:") + // For each of the **modules** in the package other than those that are the *main* module of a product // —— which we've already dealt with above —— we create a corresponding `PIFTarget` of the appropriate type. for module in self.package.modules { switch module.type { case .executable: - try builder.makeTestableExecutableSourceModule(module) + try projectBuilder.makeTestableExecutableSourceModule(module) case .snippet: // Already handled as a product. Note that snippets don't need testable modules. break case .library: - try builder.makeLibraryModule(module) + try projectBuilder.makeLibraryModule(module) case .systemModule: - try builder.makeSystemLibraryModule(module) + try projectBuilder.makeSystemLibraryModule(module) case .test: // Skip test module targets. @@ -461,17 +460,18 @@ public final class PackagePIFBuilder { break case .plugin: - try builder.makePluginModule(module) + try projectBuilder.makePluginModule(module) case .macro: - try builder.makeMacroModule(module) + try projectBuilder.makeMacroModule(module) } } - let customModulesAndProducts = try delegate.addCustomTargets(pifProject: builder.project) - builder.builtModulesAndProducts.append(contentsOf: customModulesAndProducts) + let customModulesAndProducts = try delegate.addCustomTargets(pifProject: projectBuilder.project) + projectBuilder.builtModulesAndProducts.append(contentsOf: customModulesAndProducts) - return builder.builtModulesAndProducts + self._pifProject = projectBuilder.project + return projectBuilder.builtModulesAndProducts } /// Configure the project-wide build settings. @@ -661,29 +661,4 @@ extension PackagePIFBuilder.LinkedPackageBinary { } } -extension ObservabilityMetadata { - public var sourceLocation: SourceLocation? { - get { - self[SourceLocationKey.self] - } - set { - self[SourceLocationKey.self] = newValue - } - } - - private enum SourceLocationKey: Key { - typealias Value = SourceLocation - } -} - -public struct SourceLocation: Sendable { - public let file: StaticString - public let line: UInt - - public init(_ file: StaticString, _ line: UInt) { - self.file = file - self.line = line - } -} - #endif diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index 89d7d1f7b16..bc17d22ee15 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -39,7 +39,7 @@ extension PackagePIFProjectBuilder { // Create an executable PIF target in order to get specialization. let pluginTargetKeyPath = try self.project.addTarget { _ in ProjectModel.Target( - id: pluginModule.pifTargetGUID(), + id: pluginModule.pifTargetGUID, productType: .executable, name: pluginModule.name, productName: pluginModule.name @@ -47,7 +47,11 @@ extension PackagePIFProjectBuilder { } do { let pluginTarget = self.project[keyPath: pluginTargetKeyPath] - log(.debug, "Created \(pluginTarget.productType) '\(pluginTarget.id)' with name '\(pluginTarget.name)'") + log( + .debug, + "Created target '\(pluginTarget.id)' of type " + + "\(pluginTarget.productType) and name '\(pluginTarget.name)'" + ) } var buildSettings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings @@ -73,20 +77,20 @@ extension PackagePIFProjectBuilder { if let productDependency { self.project[keyPath: pluginTargetKeyPath].common.addDependency( - on: productDependency.pifTargetGUID(), + on: productDependency.pifTargetGUID, platformFilters: dependencyPlatformFilters ) - log(.debug, indent: 1, "Added dependency on product '\(productDependency.pifTargetGUID())'") + log(.debug, indent: 1, "Added dependency on product '\(productDependency.pifTargetGUID)'") } else { log( .debug, indent: 1, - "Could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID())'" + "Could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID)'" ) } case .library, .systemModule, .test, .binary, .plugin, .macro: - let dependencyGUID = moduleDependency.pifTargetGUID() + let dependencyGUID = moduleDependency.pifTargetGUID self.project[keyPath: pluginTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: dependencyPlatformFilters @@ -104,7 +108,7 @@ extension PackagePIFProjectBuilder { product: productDependency.underlying, buildSettings: &buildSettings ) { - let dependencyGUID = productDependency.pifTargetGUID() + let dependencyGUID = productDependency.pifTargetGUID let dependencyPlatformFilters = packageConditions .toPlatformFilter(toolsVersion: self.package.manifest.toolsVersion) @@ -215,7 +219,7 @@ extension PackagePIFProjectBuilder { // MARK: - Source Modules - enum SourceModuleType { + enum SourceModuleType: String { case dynamicLibrary case staticLibrary case executable @@ -228,36 +232,36 @@ extension PackagePIFProjectBuilder { private mutating func buildSourceModule( _ sourceModule: PackageGraph.ResolvedModule, type desiredModuleType: SourceModuleType, - targetSuffix: TargetGUIDSuffix? = nil, + targetSuffix: TargetSuffix? = nil, addBuildToolPluginCommands: Bool = true, inputResourceBundleName: String? = nil ) throws -> (PackagePIFBuilder.ModuleOrProduct, resourceBundleName: String?) { precondition(sourceModule.isSourceModule) - let pifTargetName: String + let pifProductName: String let executableName: String let productType: ProjectModel.Target.ProductType switch desiredModuleType { case .dynamicLibrary: if pifBuilder.createDylibForDynamicProducts { // We are re-using this default for dynamic targets as well. - pifTargetName = "lib\(sourceModule.name).dylib" - executableName = pifTargetName + pifProductName = "lib\(sourceModule.name).dylib" + executableName = pifProductName productType = .dynamicLibrary } else { - pifTargetName = sourceModule.name + ".framework" + pifProductName = sourceModule.name + ".framework" executableName = sourceModule.name productType = .framework } case .staticLibrary, .executable: - pifTargetName = "\(sourceModule.name).o" - executableName = pifTargetName + pifProductName = "\(sourceModule.name).o" + executableName = pifProductName productType = .objectFile case .macro: - pifTargetName = sourceModule.name - executableName = pifTargetName + pifProductName = sourceModule.name + executableName = pifProductName productType = .hostBuildTool } @@ -276,17 +280,17 @@ extension PackagePIFProjectBuilder { ProjectModel.Target( id: sourceModule.pifTargetGUID(suffix: targetSuffix), productType: productType, - name: sourceModule.name, - productName: pifTargetName, + name: "\(sourceModule.name)", + productName: pifProductName, approvedByUser: approvedByUser ) } do { - let sourceModuleTarget = self.project[keyPath: sourceModuleTargetKeyPath] + let sourceModule = self.project[keyPath: sourceModuleTargetKeyPath] log( .debug, - "Created \(sourceModuleTarget.productType) '\(sourceModuleTarget.id)' " + - "with name '\(sourceModuleTarget.name)' and product name '\(sourceModuleTarget.productName)'" + "Created target '\(sourceModule.id)' of type '\(sourceModule.productType)' " + + "with name '\(sourceModule.name)' and product name '\(sourceModule.productName)'" ) } @@ -507,7 +511,7 @@ extension PackagePIFProjectBuilder { if enableDuplicateLinkageCulling { baselineOTHER_LDFLAGS = [ "-Wl,-no_warn_duplicate_libraries", - "$(inherited)", + "$(inherited)" ] } else { baselineOTHER_LDFLAGS = ["$(inherited)"] @@ -517,7 +521,7 @@ extension PackagePIFProjectBuilder { log( .debug, indent: 1, - "Added '\(String(describing: impartedSettings[.OTHER_LDFLAGS]))' to imparted OTHER_LDFLAGS" + "Added '\(impartedSettings[.OTHER_LDFLAGS]!)' to imparted OTHER_LDFLAGS" ) // This should be only for dynamic targets, but that isn't possible today. @@ -527,7 +531,7 @@ extension PackagePIFProjectBuilder { log( .debug, indent: 1, - "Added '\(String(describing: impartedSettings[.FRAMEWORK_SEARCH_PATHS]))' to imparted FRAMEWORK_SEARCH_PATHS" + "Added '\(impartedSettings[.FRAMEWORK_SEARCH_PATHS]!)' to imparted FRAMEWORK_SEARCH_PATHS" ) // Set the appropriate language versions. @@ -625,16 +629,16 @@ extension PackagePIFProjectBuilder { .productRepresentingDependencyOfBuildPlugin(in: moduleMainProducts) { self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( - on: product.pifTargetGUID(), + on: product.pifTargetGUID, platformFilters: dependencyPlatformFilters, linkProduct: false ) - log(.debug, indent: 1, "Added dependency on product '\(product.pifTargetGUID())'") + log(.debug, indent: 1, "Added dependency on product '\(product.pifTargetGUID)'") } else { log( .debug, indent: 1, - "Could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID())'" + "Could not find a build plugin product to depend on for target '\(moduleDependency.pifTargetGUID)'" ) } @@ -666,7 +670,7 @@ extension PackagePIFProjectBuilder { log(.debug, indent: 1, "Added use of binary library '\(moduleDependency.path)'") case .plugin: - let dependencyGUID = moduleDependency.pifTargetGUID() + let dependencyGUID = moduleDependency.pifTargetGUID self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: dependencyPlatformFilters, @@ -676,14 +680,14 @@ extension PackagePIFProjectBuilder { case .library, .test, .macro, .systemModule: self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( - on: moduleDependency.pifTargetGUID(), + on: moduleDependency.pifTargetGUID, platformFilters: dependencyPlatformFilters, linkProduct: shouldLinkProduct ) log( .debug, indent: 1, - "Added \(shouldLinkProduct ? "linked " : "")dependency on target '\(moduleDependency.pifTargetGUID())'" + "Added \(shouldLinkProduct ? "linked " : "")dependency on target '\(moduleDependency.pifTargetGUID)'" ) } @@ -702,7 +706,7 @@ extension PackagePIFProjectBuilder { let shouldLinkProduct = shouldLinkProduct && productDependency.isLinkable self.project[keyPath: sourceModuleTargetKeyPath].common.addDependency( - on: productDependency.pifTargetGUID(), + on: productDependency.pifTargetGUID, platformFilters: dependencyPlatformFilters, linkProduct: shouldLinkProduct ) @@ -810,7 +814,7 @@ extension PackagePIFProjectBuilder { // Create an aggregate PIF target (which doesn't have an actual product). let systemLibraryTargetKeyPath = try self.project.addAggregateTarget { _ in ProjectModel.AggregateTarget( - id: resolvedSystemLibrary.pifTargetGUID(), + id: resolvedSystemLibrary.pifTargetGUID, name: resolvedSystemLibrary.name ) } @@ -818,7 +822,7 @@ extension PackagePIFProjectBuilder { let systemLibraryTarget = self.project[keyPath: systemLibraryTargetKeyPath] log( .debug, - "Created \(type(of: systemLibraryTarget)) '\(systemLibraryTarget.id)' with name '\(systemLibraryTarget.name)'" + "Created aggregate target '\(systemLibraryTarget.id)' with name '\(systemLibraryTarget.name)'" ) } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index e44f2b099ff..5bf30c1397a 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -74,9 +74,9 @@ extension PackagePIFProjectBuilder { // It's not a library product, so create a regular PIF target of the appropriate product type. let mainModuleTargetKeyPath = try self.project.addTarget { _ in ProjectModel.Target( - id: product.pifTargetGUID(), + id: product.pifTargetGUID, productType: pifProductType, - name: product.name, + name: product.targetName(), productName: product.name ) } @@ -84,8 +84,8 @@ extension PackagePIFProjectBuilder { let mainModuleTarget = self.project[keyPath: mainModuleTargetKeyPath] log( .debug, - "Created \(mainModuleTarget.productType)) '\(mainModuleTarget.id)' " + - "with name '\(mainModuleTarget.name)' and product name '\(mainModuleTarget.productName)'" + "Created target '\(mainModuleTarget.id)' of type '\(mainModuleTarget.productType)' " + + "with name '\(mainModuleTarget.name)' and product name '\(mainModuleTarget.productName)'" ) } @@ -93,7 +93,7 @@ extension PackagePIFProjectBuilder { // a main-module product but, for diagnostic purposes, we warn about any that we do come across. if product.otherModules.hasContent { let otherModuleNames = product.otherModules.map(\.name).joined(separator: ",") - log(.debug, ".. warning: ignored unexpected other module targets \(otherModuleNames)") + log(.debug, indent: 1, "Warning: ignored unexpected other module targets \(otherModuleNames)") } // Deal with any generated source files or resource files. @@ -124,7 +124,7 @@ extension PackagePIFProjectBuilder { settings[.LD_RUNPATH_SEARCH_PATHS] = [ "@loader_path/Frameworks", "@loader_path/../Frameworks", - "$(inherited)", + "$(inherited)" ] settings[.GENERATE_INFOPLIST_FILE] = "YES" settings[.SKIP_INSTALL] = "NO" @@ -342,7 +342,7 @@ extension PackagePIFProjectBuilder { log(.debug, indent: 1, "Added use of binary library '\(moduleDependency.path)'") case .plugin: - let dependencyId = moduleDependency.pifTargetGUID() + let dependencyId = moduleDependency.pifTargetGUID self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: dependencyId, platformFilters: packageConditions @@ -352,7 +352,7 @@ extension PackagePIFProjectBuilder { log(.debug, indent: 1, "Added use of plugin target '\(dependencyId)'") case .macro: - let dependencyId = moduleDependency.pifTargetGUID() + let dependencyId = moduleDependency.pifTargetGUID self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: dependencyId, platformFilters: packageConditions @@ -398,7 +398,7 @@ extension PackagePIFProjectBuilder { // (i.e., we infuse the product's main module target into the one for the product itself). let productDependency = modulesGraph.allProducts.only { $0.name == moduleDependency.name } if let productDependency { - let productDependencyGUID = productDependency.pifTargetGUID() + let productDependencyGUID = productDependency.pifTargetGUID self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: productDependencyGUID, platformFilters: packageConditions @@ -423,7 +423,7 @@ extension PackagePIFProjectBuilder { case .library, .systemModule, .test: let shouldLinkProduct = moduleDependency.type != .systemModule - let dependencyGUID = moduleDependency.pifTargetGUID() + let dependencyGUID = moduleDependency.pifTargetGUID self.project[keyPath: mainModuleTargetKeyPath].common.addDependency( on: dependencyGUID, platformFilters: packageConditions @@ -511,14 +511,14 @@ extension PackagePIFProjectBuilder { if !pifBuilder.delegate.shouldSuppressProductDependency(product: product.underlying, buildSettings: &settings) { let shouldLinkProduct = isLinkable self.project[keyPath: targetKeyPath].common.addDependency( - on: product.pifTargetGUID(), + on: product.pifTargetGUID, platformFilters: packageConditions.toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: shouldLinkProduct ) log( .debug, indent: 1, - "Added \(shouldLinkProduct ? "linked " : "")dependency on product '\(product.pifTargetGUID()))'" + "Added \(shouldLinkProduct ? "linked " : "")dependency on product '\(product.pifTargetGUID)'" ) } } @@ -570,21 +570,21 @@ extension PackagePIFProjectBuilder { private mutating func buildLibraryProduct( _ product: PackageGraph.ResolvedProduct, type desiredProductType: ProductType.LibraryType, - targetSuffix: TargetGUIDSuffix? = nil, + targetSuffix: TargetSuffix? = nil, embedResources: Bool ) throws -> PackagePIFBuilder.ModuleOrProduct { precondition(product.type.isLibrary) // FIXME: Cleanup this mess with - let pifTargetProductName: String + let pifProductName: String let executableName: String let productType: ProjectModel.Target.ProductType if desiredProductType == .dynamic { if pifBuilder.createDylibForDynamicProducts { - pifTargetProductName = "lib\(product.name).dylib" - executableName = pifTargetProductName + pifProductName = "lib\(product.name).dylib" + executableName = pifProductName productType = .dynamicLibrary } else { // If a product is explicitly declared dynamic, we preserve its name, @@ -600,12 +600,12 @@ extension PackagePIFProjectBuilder { } else { executableName = PackagePIFBuilder.computePackageProductFrameworkName(productName: product.name) } - pifTargetProductName = "\(executableName).framework" + pifProductName = "\(executableName).framework" productType = .framework } } else { - pifTargetProductName = "lib\(product.name).a" - executableName = pifTargetProductName + pifProductName = "lib\(product.name).a" + executableName = pifProductName productType = .packageProduct } @@ -617,8 +617,8 @@ extension PackagePIFProjectBuilder { ProjectModel.Target( id: product.pifTargetGUID(suffix: targetSuffix), productType: productType, - name: product.name, - productName: pifTargetProductName + name: product.targetName(suffix: targetSuffix), + productName: pifProductName ) } do { @@ -626,7 +626,7 @@ extension PackagePIFProjectBuilder { log( .debug, "Created target '\(librayTarget.id)' of type '\(librayTarget.productType)' with " + - "name '\(librayTarget.name)' and product name '\(librayTarget.productName)'" + "name '\(librayTarget.name)' and product name '\(librayTarget.productName)'" ) } @@ -647,11 +647,11 @@ extension PackagePIFProjectBuilder { // SwiftBuild won't actually link them, but will instead impart linkage to any clients that // link against the package product. self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( - on: module.pifTargetGUID(), + on: module.pifTargetGUID, platformFilters: [], linkProduct: true ) - log(.debug, indent: 1, "Added linked dependency on target '\(module.pifTargetGUID())'") + log(.debug, indent: 1, "Added linked dependency on target '\(module.pifTargetGUID)'") } for module in product.modules where module.underlying.isSourceModule && module.resources.hasContent { @@ -686,7 +686,7 @@ extension PackagePIFProjectBuilder { if desiredProductType == .dynamic { settings.configureDynamicSettings( productName: product.name, - targetName: product.targetNameForProduct(), + targetName: product.targetName(), executableName: executableName, packageIdentity: package.identity, packageName: package.identity.c99name, @@ -746,7 +746,7 @@ extension PackagePIFProjectBuilder { } if moduleDependency.type == .plugin { - let dependencyId = moduleDependency.pifTargetGUID() + let dependencyId = moduleDependency.pifTargetGUID self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( on: dependencyId, platformFilters: packageConditions @@ -769,28 +769,28 @@ extension PackagePIFProjectBuilder { .productRepresentingDependencyOfBuildPlugin(in: mainModuleProducts) { self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( - on: product.pifTargetGUID(), + on: product.pifTargetGUID, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: false ) - log(.debug, indent: 1, "Added dependency on product '\(product.pifTargetGUID())'") + log(.debug, indent: 1, "Added dependency on product '\(product.pifTargetGUID)'") return } else { log( .debug, indent: 1, - "Could not find a build plugin product to depend on for target '\(product.pifTargetGUID()))'" + "Could not find a build plugin product to depend on for target '\(product.pifTargetGUID)'" ) } } self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( - on: moduleDependency.pifTargetGUID(), + on: moduleDependency.pifTargetGUID, platformFilters: packageConditions.toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: true ) - log(.debug, indent: 1, "Added linked dependency on target '\(moduleDependency.pifTargetGUID()))'") + log(.debug, indent: 1, "Added linked dependency on target '\(moduleDependency.pifTargetGUID)'") case .product(let productDependency, let packageConditions): // Do not add a dependency for binary-only executable products since they are not part of the build. @@ -804,7 +804,7 @@ extension PackagePIFProjectBuilder { ) { let shouldLinkProduct = productDependency.isLinkable self.project[keyPath: librayUmbrellaTargetKeyPath].common.addDependency( - on: productDependency.pifTargetGUID(), + on: productDependency.pifTargetGUID, platformFilters: packageConditions .toPlatformFilter(toolsVersion: package.manifest.toolsVersion), linkProduct: shouldLinkProduct @@ -812,7 +812,7 @@ extension PackagePIFProjectBuilder { log( .debug, indent: 1, - "Added \(shouldLinkProduct ? "linked" : "") dependency on product '\(productDependency.pifTargetGUID()))'" + "Added \(shouldLinkProduct ? "linked" : "") dependency on product '\(productDependency.pifTargetGUID)'" ) } } @@ -878,9 +878,9 @@ extension PackagePIFProjectBuilder { let systemLibraryTargetKeyPath = try self.project.addTarget { _ in ProjectModel.Target( - id: product.pifTargetGUID(), + id: product.pifTargetGUID, productType: .packageProduct, - name: product.name, + name: product.targetName(), productName: product.name ) } @@ -889,7 +889,7 @@ extension PackagePIFProjectBuilder { log( .debug, "Created target '\(systemLibraryTarget.id)' of type '\(systemLibraryTarget.productType)' " + - "with name '\(systemLibraryTarget.name)' and product name '\(systemLibraryTarget.productName)'" + "with name '\(systemLibraryTarget.name)' and product name '\(systemLibraryTarget.productName)'" ) } @@ -902,7 +902,7 @@ extension PackagePIFProjectBuilder { } self.project[keyPath: systemLibraryTargetKeyPath].common.addDependency( - on: product.systemModule!.pifTargetGUID(), + on: product.systemModule!.pifTargetGUID, platformFilters: [], linkProduct: false ) @@ -929,8 +929,8 @@ extension PackagePIFProjectBuilder { let pluginTargetKeyPath = try self.project.addAggregateTarget { _ in ProjectModel.AggregateTarget( - id: pluginProduct.pifTargetGUID(), - name: pluginProduct.name + id: pluginProduct.pifTargetGUID, + name: pluginProduct.targetName() ) } do { @@ -948,7 +948,7 @@ extension PackagePIFProjectBuilder { for pluginModule in pluginProduct.pluginModules! { self.project[keyPath: pluginTargetKeyPath].common.addDependency( - on: pluginModule.pifTargetGUID(), + on: pluginModule.pifTargetGUID, platformFilters: [] ) } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index 149d97e84ab..bd7a36b4895 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -71,13 +71,18 @@ struct PackagePIFProjectBuilder { func log( _ severity: Diagnostic.Severity, - indent: Int = 0, + indent: UInt = 0, _ message: String, sourceFile: StaticString = #fileID, sourceLine: UInt = #line ) { - let levelPrefix = String(repeating: " ", count: indent) - self.pifBuilder.log(severity, levelPrefix + message, sourceFile: sourceFile, sourceLine: sourceLine) + self.pifBuilder.observabilityScope.logPIF( + severity, + indent: indent, + message, + sourceFile: sourceFile, + sourceLine: sourceLine + ) } init(createForPackage package: PackageGraph.ResolvedPackage, builder: PackagePIFBuilder) { @@ -188,7 +193,7 @@ struct PackagePIFProjectBuilder { for pluginModule in module.pluginsAppliedToModule { self.project[keyPath: resourcesTargetKeyPath].common.addDependency( - on: pluginModule.pifTargetGUID(), + on: pluginModule.pifTargetGUID, platformFilters: [], linkProduct: false ) @@ -197,8 +202,8 @@ struct PackagePIFProjectBuilder { self.log( .debug, indent: 1, - "Created \(type(of: resourcesTarget)) '\(resourcesTarget.id)' of type '\(resourcesTarget.productType)' " + - "with name '\(resourcesTarget.name)' and product name '\(resourcesTarget.productName)'" + "Created target '\(resourcesTarget.id)' of type '\(resourcesTarget.productType)' " + + "with name '\(resourcesTarget.name)' and product name '\(resourcesTarget.productName)'" ) var settings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 423afcd0e04..449cde066fc 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -7011,8 +7011,11 @@ class BuildPlanSwiftBuildTests: BuildPlanTestCase { #if os(Linux) if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { - throw XCTSkip("Skipping SwiftBuild testing on Amazon Linux because of platform issues.") + throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") } + // Linking error: "/usr/bin/ld.gold: fatal error: -pie and -static are incompatible". + // Tracked by GitHub issue: https://github.com/swiftlang/swift-package-manager/issues/8499 + throw XCTSkip("Skipping Swift Build testing on Linux because of linking issues.") #endif try await super.testPackageNameFlag() diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 20cfedce6d8..e4c607eff96 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -862,13 +862,18 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { try await fixture(name: "Miscellaneous/ParseableInterfaces") { fixturePath in do { let result = try await build(["--enable-parseable-module-interfaces"], packagePath: fixturePath) - XCTAssertMatch(result.moduleContents, [.regex(#"A\.swiftmodule\/.*\.swiftinterface"#)]) - XCTAssertMatch(result.moduleContents, [.regex(#"B\.swiftmodule\/.*\.swiftmodule"#)]) + XCTAssertMatch(result.moduleContents, [.regex(#"A[.]swiftmodule[/].*[.]swiftinterface"#)]) + XCTAssertMatch(result.moduleContents, [.regex(#"B[.]swiftmodule[/].*[.]swiftmodule"#)]) } catch SwiftPMError.executionFailure(_, _, let stderr) { XCTFail(stderr) } } } + + override func testAutomaticParseableInterfacesWithLibraryEvolution() async throws { + throw XCTSkip("SWBINTTODO: Test failed because of missing 'A.swiftmodule/*.swiftinterface' files") + // TODO: We still need to override this test just like we did for `testParseableInterfaces` above. + } override func testBinSymlink() async throws { try await fixture(name: "ValidLayouts/SingleModule/ExecutableNew") { fixturePath in @@ -900,17 +905,6 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { #endif } - override func testAutomaticParseableInterfacesWithLibraryEvolution() async throws { - try await fixture(name: "Miscellaneous/LibraryEvolution") { fixturePath in - do { - let result = try await build([], packagePath: fixturePath) - XCTAssertMatch( - result.moduleContents, [.regex(#"A\.swiftmodule\/.*\.swiftinterface"#)]) - XCTAssertMatch(result.moduleContents, [.regex(#"B\.swiftmodule\/.*\.swiftmodule"#)]) - } - } - } - override func testImportOfMissedDepWarning() async throws { throw XCTSkip("SWBINTTODO: Test fails because the warning message regarding missing imports is expected to be more verbose and actionable at the SwiftPM level with mention of the involved targets. This needs to be investigated. See case targetDiagnostic(TargetDiagnosticInfo) as a message type that may help.") } From 2c4c785d77cb38c40f4b000a8d46d4e6e6e0c981 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Thu, 17 Apr 2025 01:54:04 -0700 Subject: [PATCH 27/99] Tests: Fix 22 warning in BuildCommandTests (#8509) ### Motivation: Reduce noise from solvable warnings. ### Modifications: We should `throw XCTSkip("...")` but `try XCTSkipIf(true, "...")`. The BuildCommandTests was mixing both (i.e., `try XCTSkip("...")`), making the test a nop actually. From 9fb4efb7521e10254e5cb50c167d38576cdd61ba Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Thu, 17 Apr 2025 00:14:16 -0700 Subject: [PATCH 28/99] Add an .editorconfig file This encodes our indentation size and line ending preferences and such. This is an exact copy of the .editorconfig in swift-build, which uses the same conventions. --- .editorconfig | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..ac8a0df1bb3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +xcode_trim_whitespace_on_empty_lines = true + +[*.{yml,yaml}] +indent_size = 2 From 95ce2a3b1757db1d1d584968efada23065e3f4c3 Mon Sep 17 00:00:00 2001 From: Shawn Hyam Date: Fri, 18 Apr 2025 01:37:32 -0400 Subject: [PATCH 29/99] Make sure the workspace state is saved after adding/removing artifacts. (#8475) --- Sources/Basics/CMakeLists.txt | 1 + .../Workspace/Workspace+BinaryArtifacts.swift | 65 ++++++++++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/Sources/Basics/CMakeLists.txt b/Sources/Basics/CMakeLists.txt index 1b56ad2d5e3..1ee88ac29d8 100644 --- a/Sources/Basics/CMakeLists.txt +++ b/Sources/Basics/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(Basics Concurrency/ThreadSafeArrayStore.swift Concurrency/ThreadSafeBox.swift Concurrency/ThreadSafeKeyValueStore.swift + Concurrency/ThrowingDefer.swift Concurrency/TokenBucket.swift DispatchTimeInterval+Extensions.swift Environment/Environment.swift diff --git a/Sources/Workspace/Workspace+BinaryArtifacts.swift b/Sources/Workspace/Workspace+BinaryArtifacts.swift index 3fb72f3b984..a99f895d09d 100644 --- a/Sources/Workspace/Workspace+BinaryArtifacts.swift +++ b/Sources/Workspace/Workspace+BinaryArtifacts.swift @@ -155,7 +155,7 @@ extension Workspace { if !indexFiles.isEmpty { let errors = ThreadSafeArrayStore() - zipArtifacts.append(contentsOf: try await withThrowingTaskGroup( + try await zipArtifacts.append(contentsOf: withThrowingTaskGroup( of: RemoteArtifact?.self, returning: [RemoteArtifact].self ) { group in @@ -164,7 +164,8 @@ extension Workspace { group.addTask { var request = HTTPClient.Request(method: .get, url: indexFile.url) request.options.validResponseCodes = [200] - request.options.authorizationProvider = self.authorizationProvider?.httpAuthorizationHeader(for:) + request.options.authorizationProvider = self.authorizationProvider? + .httpAuthorizationHeader(for:) do { let response = try await self.httpClient.execute(request) guard let body = response.body else { @@ -227,7 +228,10 @@ extension Workspace { group.addTask { () -> ManagedArtifact? in let destinationDirectory = artifactsDirectory .appending(components: [artifact.packageRef.identity.description, artifact.targetName]) - guard observabilityScope.trap({ try fileSystem.createDirectory(destinationDirectory, recursive: true) }) + guard observabilityScope.trap({ try fileSystem.createDirectory( + destinationDirectory, + recursive: true + ) }) else { return nil } @@ -295,7 +299,8 @@ extension Workspace { return nil } - observabilityScope.emit(debug: "extracting \(archivePath) to \(tempExtractionDirectory)") + observabilityScope + .emit(debug: "extracting \(archivePath) to \(tempExtractionDirectory)") do { try await self.archiver.extract( from: archivePath, @@ -326,7 +331,8 @@ extension Workspace { debug: "no first level component stripping needed for \(tempExtractionDirectory)" ) } - let content = try self.fileSystem.getDirectoryContents(tempExtractionDirectory) + let content = try self.fileSystem + .getDirectoryContents(tempExtractionDirectory) // copy from temp location to actual location for file in content { let source = tempExtractionDirectory @@ -350,8 +356,8 @@ extension Workspace { observabilityScope: observabilityScope ) else { observabilityScope.emit(BinaryArtifactsManagerError.remoteArtifactNotFound( - artifactURL: artifact.url, - targetName: artifact.targetName + artifactURL: artifact.url, + targetName: artifact.targetName )) return nil } @@ -432,7 +438,7 @@ extension Workspace { artifactsDirectory: AbsolutePath, observabilityScope: ObservabilityScope ) async throws -> [ManagedArtifact] { - return try await withThrowingTaskGroup(of: ManagedArtifact?.self) { group in + try await withThrowingTaskGroup(of: ManagedArtifact?.self) { group in for artifact in artifacts { group.addTask { () -> ManagedArtifact? in let destinationDirectory = artifactsDirectory @@ -458,7 +464,9 @@ extension Workspace { acceptableExtensions: BinaryModule.Kind.allCases.map(\.fileExtension) ) { observabilityScope - .emit(debug: "stripping first level component from \(tempExtractionDirectory)") + .emit( + debug: "stripping first level component from \(tempExtractionDirectory)" + ) try self.fileSystem.stripFirstLevel(of: tempExtractionDirectory) } else { observabilityScope.emit( @@ -564,7 +572,7 @@ extension Workspace { artifact: RemoteArtifact, destination: AbsolutePath, observabilityScope: ObservabilityScope, - progress: @escaping @Sendable (Int64, Optional) -> Void + progress: @escaping @Sendable (Int64, Int64?) -> Void ) async throws -> Bool { // not using cache, download directly guard let cachePath = self.cachePath else { @@ -591,7 +599,8 @@ extension Workspace { let cachedArtifactPath = cachePath.appending(cacheKey) if self.fileSystem.exists(cachedArtifactPath) { - observabilityScope.emit(debug: "copying cached binary artifact for \(artifact.url) from \(cachedArtifactPath)") + observabilityScope + .emit(debug: "copying cached binary artifact for \(artifact.url) from \(cachedArtifactPath)") self.delegate?.willDownloadBinaryArtifact(from: artifact.url.absoluteString, fromCache: true) // copy from cache to destination @@ -600,7 +609,8 @@ extension Workspace { } // download to the cache - observabilityScope.emit(debug: "downloading binary artifact for \(artifact.url) to cache at \(cachedArtifactPath)") + observabilityScope + .emit(debug: "downloading binary artifact for \(artifact.url) to cache at \(cachedArtifactPath)") self.delegate?.willDownloadBinaryArtifact(from: artifact.url.absoluteString, fromCache: false) @@ -623,7 +633,7 @@ extension Workspace { artifact: RemoteArtifact, destination: AbsolutePath, observabilityScope: ObservabilityScope, - progress: @escaping @Sendable (Int64, Optional) -> Void + progress: @escaping @Sendable (Int64, Int64?) -> Void ) async throws { observabilityScope.emit(debug: "downloading \(artifact.url) to \(destination)") @@ -800,6 +810,26 @@ extension Workspace { manifests: DependencyManifests, addedOrUpdatedPackages: [PackageReference], observabilityScope: ObservabilityScope + ) async throws { + try await withAsyncThrowing { + try await self._updateBinaryArtifacts( + manifests: manifests, + addedOrUpdatedPackages: addedOrUpdatedPackages, + observabilityScope: observabilityScope + ) + } defer: { + // Make sure the workspace state is saved exactly once, even if the method exits early. + // Files may have been deleted, download, etc. and the state needs to reflect that. + await observabilityScope.trap { + try await self.state.save() + } + } + } + + private func _updateBinaryArtifacts( + manifests: DependencyManifests, + addedOrUpdatedPackages: [PackageReference], + observabilityScope: ObservabilityScope ) async throws { let manifestArtifacts = try self.binaryArtifactsManager.parseArtifacts( from: manifests, @@ -893,7 +923,10 @@ extension Workspace { // Remove the artifacts and directories which are not needed anymore. await observabilityScope.trap { for artifact in artifactsToRemove { - await state.artifacts.remove(packageIdentity: artifact.packageRef.identity, targetName: artifact.targetName) + await state.artifacts.remove( + packageIdentity: artifact.packageRef.identity, + targetName: artifact.targetName + ) if isAtArtifactsDirectory(artifact) { try fileSystem.removeFileTree(artifact.path) @@ -937,10 +970,6 @@ extension Workspace { throw Diagnostics.fatalError } - await observabilityScope.trap { - try await self.state.save() - } - func isAtArtifactsDirectory(_ artifact: ManagedArtifact) -> Bool { artifact.path.isDescendant(of: self.location.artifactsDirectory) } From b9383282a24670b1b35197965282c2f7e33e168e Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Wed, 23 Apr 2025 02:42:05 -0400 Subject: [PATCH 30/99] build-using-self use a self build of swift-test instead (#8548) We are seeing hangs in swift test on Windows. Instead of build-using-self using the underlying toolchain, build swift-test and use that for the tests. When we make improvements to it to fix the underlying problem, we will be able to take advantage of that fix right away. --- .../IntegrationTestSupport/Helpers.swift | 2 +- Utilities/build-using-self | 39 +++++++++---------- Utilities/test-toolchain | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift b/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift index 80f0963a563..eb59b855742 100644 --- a/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift +++ b/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift @@ -106,7 +106,7 @@ public let lldb: AbsolutePath = { }() public let swiftpmBinaryDirectory: AbsolutePath = { - if let environmentPath = ProcessInfo.processInfo.environment["SWIFTPM_BIN_DIR"] { + if let environmentPath = ProcessInfo.processInfo.environment["SWIFTPM_CUSTOM_BIN_DIR"] { return try! AbsolutePath(validating: environmentPath) } diff --git a/Utilities/build-using-self b/Utilities/build-using-self index 9ccf8706792..3b59152b9ba 100755 --- a/Utilities/build-using-self +++ b/Utilities/build-using-self @@ -102,8 +102,8 @@ def is_on_darwin() -> bool: def set_environment(*, swiftpm_bin_dir: pathlib.Path,) -> None: os.environ["SWIFTCI_IS_SELF_HOSTED"] = "1" - # Set the SWIFTPM_BIN_DIR path - os.environ["SWIFTPM_BIN_DIR"] = str(swiftpm_bin_dir) + # Set the SWIFTPM_CUSTOM_BIN_DIR path + os.environ["SWIFTPM_CUSTOM_BIN_DIR"] = str(swiftpm_bin_dir) # Ensure SDKROOT is configure if is_on_darwin(): @@ -133,6 +133,7 @@ def run_bootstrap(swiftpm_bin_dir: pathlib.Path) -> None: def main() -> None: args = get_arguments() + ignore = "-Xlinker /ignore:4217" if os.name == "nt" else "" logging.getLogger().setLevel(logging.DEBUG if args.is_verbose else logging.INFO) logging.debug("Args: %r", args) @@ -144,31 +145,27 @@ def main() -> None: shlex.split("swift --version"), ) - # call( - # shlex.split("swift package reset"), - # ) call( shlex.split("swift package update"), ) call( - shlex.split(f"swift build --configuration {args.config}"), - ) - swift_testing_arg= "--enable-swift-testing" if args.enable_swift_testing else "" - xctest_arg= "--enable-xctest" if args.enable_swift_testing else "" - call( - shlex.split(f"swift test --configuration {args.config} --parallel {swift_testing_arg} {xctest_arg}"), + shlex.split(f"swift build --configuration {args.config} {ignore}"), ) - with change_directory(REPO_ROOT_PATH / "IntegrationTests"): - call( - shlex.split("swift package update"), - ) - call( - shlex.split( - f"{swiftpm_bin_dir / 'swift-test'} --parallel", - posix=(os.name == "posix"), # must be set correctly, otherwhsie shlex.split("C:\\Foo\\bar") become ['CFoobar'] - ), - ) + if os.name != "nt": # turn off for Windows until we get the hang resolved + swift_testing_arg= "--enable-swift-testing" if args.enable_swift_testing else "" + xctest_arg= "--enable-xctest" if args.enable_swift_testing else "" + call( + shlex.split(f"swift run swift-test --configuration {args.config} --parallel {swift_testing_arg} {xctest_arg} --scratch-path .test {ignore}"), + ) + + integration_test_dir = REPO_ROOT_PATH / "IntegrationTests" + call( + shlex.split(f"swift package --package-path {integration_test_dir} update"), + ) + call( + shlex.split(f"swift run swift-test --package-path {integration_test_dir} --parallel {ignore}"), + ) if is_on_darwin(): run_bootstrap(swiftpm_bin_dir=swiftpm_bin_dir) diff --git a/Utilities/test-toolchain b/Utilities/test-toolchain index 2eb878cea3a..47eae638667 100755 --- a/Utilities/test-toolchain +++ b/Utilities/test-toolchain @@ -128,7 +128,7 @@ def get_env(args): if args.lldb_path: env['LLDB_PATH'] = args.lldb_path if args.swiftpm_bin_dir: - env["SWIFTPM_BIN_DIR"] = args.swiftpm_bin_dir + env["SWIFTPM_CUSTOM_BIN_DIR"] = args.swiftpm_bin_dir return env From 2bc3390f81e021646ab2cfcf3ceff330bc1b2333 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Wed, 23 Apr 2025 03:03:52 -0400 Subject: [PATCH 31/99] Disable failing SwiftBuild test on AmazonLinux 2 (#8546) A SwiftBuild test is currently failing on Amazon Linux 2. Disable this test for now and link it to a GitHub issue. --- Tests/CommandsTests/BuildCommandTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index e4c607eff96..ca39fc6bbce 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -859,6 +859,12 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { } override func testParseableInterfaces() async throws { + #if os(Linux) + if FileManager.default.contents(atPath: "/etc/system-release") + .map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { + throw XCTSkip("https://github.com/swiftlang/swift-package-manager/issues/8545: Test currently fails on Amazon Linux 2") + } + #endif try await fixture(name: "Miscellaneous/ParseableInterfaces") { fixturePath in do { let result = try await build(["--enable-parseable-module-interfaces"], packagePath: fixturePath) From ffdc0c026c0541f5af8b391489705e24fba38788 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Wed, 23 Apr 2025 01:38:16 -0700 Subject: [PATCH 32/99] Tests: Run packageInitExecutable test on all platforms for native build system (#8527) ### Motivation: Increase code coverage for our integration tests. This is an issue I introduced in #8454 while ignoring `--build-system swiftbuild` issues on Windows (i.e., actually this specific issue #8514). I accidentally skipped the packageInitExecutable test for `--build-system native` as well (which should be green). This PR fixes that. ### Modifications: Swift Testing currently doesn't support combining *conditional traits* (e.g.,`skipHostOS(.windows)`) with *parametric tests*. So the 2nd best approach is to split the tests as I just did. --- .../Tests/IntegrationTests/SwiftPMTests.swift | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift index 770d046ee14..3f673e5509c 100644 --- a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift @@ -75,7 +75,15 @@ private struct SwiftPMTests { } @Test( - .requireHostOS(.windows, when: false), + .requireThreadSafeWorkingDirectory, + arguments: [BuildSystemProvider.native] + ) + func packageInitExecutable(_ buildSystemProvider: BuildSystemProvider) throws { + try _packageInitExecutable(buildSystemProvider) + } + + @Test( + .skipHostOS(.windows), .requireThreadSafeWorkingDirectory, .bug( "https://github.com/swiftlang/swift-package-manager/issues/8416", @@ -85,30 +93,31 @@ private struct SwiftPMTests { "https://github.com/swiftlang/swift-package-manager/issues/8514", "[Windows] Integration test SwiftPMTests.packageInitExecutable with --build-system swiftbuild is skipped" ), - arguments: BuildSystemProvider.allCases + arguments: [BuildSystemProvider.swiftbuild] ) - func packageInitExecutable(_ buildSystemProvider: BuildSystemProvider) throws { - // Executable - do { - try withTemporaryDirectory { tmpDir in - let packagePath = tmpDir.appending(component: "foo") - try localFileSystem.createDirectory(packagePath) - try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "executable") - try sh(swiftBuild, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue) - - try withKnownIssue("Error while loading shared libraries: libswiftCore.so: cannot open shared object file: No such file or directory") { - // The 'native' build system uses 'swiftc' as the linker driver, which adds an RUNPATH to the swift runtime libraries in the SDK. - // 'swiftbuild' directly calls clang, which does not add the extra RUNPATH, so runtime libraries cannot be found. - let (stdout, stderr) = try sh( - swiftRun, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue - ) - #expect(!stderr.contains("error:")) - #expect(stdout.contains("Hello, world!")) - } when: { - buildSystemProvider == .swiftbuild && ProcessInfo.hostOperatingSystem == .linux - } + func packageInitExecutablSkipWindows(_ buildSystemProvider: BuildSystemProvider) throws { + try _packageInitExecutable(buildSystemProvider) + } + + private func _packageInitExecutable(_ buildSystemProvider: BuildSystemProvider) throws { + try withTemporaryDirectory { tmpDir in + let packagePath = tmpDir.appending(component: "foo") + try localFileSystem.createDirectory(packagePath) + try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "executable") + try sh(swiftBuild, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue) + + try withKnownIssue("Error while loading shared libraries: libswiftCore.so: cannot open shared object file: No such file or directory") { + // The 'native' build system uses 'swiftc' as the linker driver, which adds an RUNPATH to the swift runtime libraries in the SDK. + // 'swiftbuild' directly calls clang, which does not add the extra RUNPATH, so runtime libraries cannot be found. + let (stdout, stderr) = try sh( + swiftRun, "--package-path", packagePath, "--build-system", buildSystemProvider.rawValue + ) + #expect(!stderr.contains("error:")) + #expect(stdout.contains("Hello, world!")) + } when: { + buildSystemProvider == .swiftbuild && ProcessInfo.hostOperatingSystem == .linux + } } - } } @Test( From 6b256f36d86bc134575940f1aa2009133594d057 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 23 Apr 2025 11:30:55 -0400 Subject: [PATCH 33/99] Adopt MemberImportVisibility (#8525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adopt the experimental feature MemberImportVisibility. ### Motivation: Due to a weakness in the existing Swift language implementation, a public extension declared in a library module can be accessible to source code that doesn’t directly import that module. ### Modifications: Adopt MemberImportVisibility on all targets and adjust imports to be explicit. There were a few places where ambiguities were exposed. The most apparent one is the ambiguity between Basics.AbsolutePath and TSCBasics.AbsolutePath. In this case they're functionally equivalent since the Basics version just type aliases the TSCBasics version. There were a few others where identically named methods were defined in both a SwiftPM module and an imported dependency. ### Result: SwiftPM compiles with no code included without a corresponding explicit import. --- Package.swift | 81 ++++++++----- Sources/Basics/AuthorizationProvider.swift | 1 + .../Collections/Dictionary+Extensions.swift | 2 + .../ThrottledProgressAnimation.swift | 1 + Sources/Basics/SQLite.swift | 1 + Sources/Basics/Triple+Basics.swift | 1 + .../ProductBuildDescription.swift | 3 +- .../SwiftModuleBuildDescription.swift | 1 + .../LLBuildManifestBuilder+Clang.swift | 1 + .../LLBuildManifestBuilder+Product.swift | 2 + .../LLBuildManifestBuilder+Resources.swift | 2 + .../LLBuildManifestBuilder+Swift.swift | 8 +- Sources/Build/BuildPlan/BuildPlan+Clang.swift | 5 + Sources/Build/BuildPlan/BuildPlan+Swift.swift | 4 + Sources/Build/BuildPlan/BuildPlan.swift | 21 ++-- Sources/Build/LLBuildDescription.swift | 1 + Sources/Build/SwiftCompilerOutputParser.swift | 1 + .../Commands/PackageCommands/APIDiff.swift | 2 + .../PackageCommands/AddDependency.swift | 2 + .../Commands/PackageCommands/AddProduct.swift | 2 + .../Commands/PackageCommands/AddTarget.swift | 2 + .../PackageCommands/AddTargetDependency.swift | 2 + .../PackageCommands/ArchiveSource.swift | 2 + .../PackageCommands/CompletionCommand.swift | 3 + Sources/Commands/PackageCommands/Config.swift | 1 + .../Commands/PackageCommands/Describe.swift | 2 + .../PackageCommands/DumpCommands.swift | 4 + .../PackageCommands/EditCommands.swift | 1 + Sources/Commands/PackageCommands/Format.swift | 5 +- Sources/Commands/PackageCommands/Init.swift | 1 + .../Commands/PackageCommands/Install.swift | 3 + Sources/Commands/PackageCommands/Learn.swift | 1 + .../PackageCommands/PluginCommand.swift | 6 +- .../PackageCommands/ResetCommands.swift | 1 + .../Commands/PackageCommands/Resolve.swift | 2 + .../PackageCommands/ShowExecutables.swift | 2 + Sources/Commands/PackageCommands/Update.swift | 1 + .../Commands/Snippets/Cards/SnippetCard.swift | 3 + .../Snippets/Cards/SnippetGroupCard.swift | 1 + Sources/Commands/Snippets/Cards/TopCard.swift | 1 + Sources/Commands/SwiftRunCommand.swift | 1 + Sources/Commands/SwiftTestCommand.swift | 1 + .../Utilities/DependenciesSerializer.swift | 2 + .../Commands/Utilities/MultiRootSupport.swift | 8 +- .../Utilities/SymbolGraphExtract.swift | 1 + .../Commands/Utilities/TestingSupport.swift | 1 + Sources/CoreCommands/BuildSystemSupport.swift | 1 + .../SwiftCommandObservabilityHandler.swift | 1 + .../DriverSupport/DriverSupportUtils.swift | 3 +- Sources/LLBuildManifest/LLBuildManifest.swift | 50 ++++---- .../PackageCollections+Validation.swift | 1 + .../PackageCollections.swift | 1 + .../JSONPackageCollectionProvider.swift | 3 +- ...FilePackageCollectionsSourcesStorage.swift | 5 +- .../SQLitePackageCollectionsStorage.swift | 1 + .../PackageCollectionSigning.swift | 1 + .../PackageDependency.swift | 5 + .../FilePackageFingerprintStorage.swift | 5 +- .../PackageFingerprintStorage.swift | 1 + .../PackageGraph/ModulesGraph+Loading.swift | 1 + Sources/PackageGraph/ModulesGraph.swift | 16 +-- .../PubGrub/ContainerProvider.swift | 1 + .../PubGrub/DiagnosticReportBuilder.swift | 4 + .../Resolution/ResolvedModule.swift | 1 + .../ManifestLoader+Validation.swift | 8 +- .../PackageLoading/ModuleMapGenerator.swift | 39 +++--- Sources/PackageLoading/PackageBuilder.swift | 2 + Sources/PackageLoading/PkgConfig.swift | 38 +++--- Sources/PackageLoading/Platform.swift | 7 +- ...RegistryReleaseMetadataSerialization.swift | 5 +- Sources/PackageLoading/Target+PkgConfig.swift | 1 + .../PackageLoading/TargetSourcesBuilder.swift | 57 ++++----- .../ArtifactsArchiveMetadata.swift | 7 +- .../MinimumDeploymentTarget.swift | 12 +- Sources/PackageModel/Module/ClangModule.swift | 15 +-- Sources/PackageModel/Module/Module.swift | 1 + .../PackageModel/Module/PluginModule.swift | 2 + Sources/PackageModel/PackageIdentity.swift | 5 +- .../PackageModel/Snippets/Model/Snippet.swift | 1 + Sources/PackageModel/SwiftSDKs/SwiftSDK.swift | 113 +++++++++--------- Sources/PackageModel/UserToolchain.swift | 17 +-- Sources/PackageModelSyntax/AddTarget.swift | 1 + .../ManifestSyntaxRepresentable.swift | 1 + .../PackageDependency+Syntax.swift | 1 + .../ProductDescription+Syntax.swift | 1 + .../PackageModelSyntax/SyntaxEditUtils.swift | 1 + .../TargetDescription+Syntax.swift | 2 +- Sources/PackagePlugin/Utilities.swift | 2 + Sources/PackageRegistry/ChecksumTOFU.swift | 1 + .../RegistryDownloadsManager.swift | 29 ++--- .../PackageRegistry/SignatureValidation.swift | 3 +- .../PackageRegistryCommand+Auth.swift | 3 + .../PackageRegistryCommand+Publish.swift | 1 + .../PackageRegistryCommand.swift | 1 + .../PackageSigning/CertificateStores.swift | 2 + .../FilePackageSigningEntityStorage.swift | 5 +- Sources/PackageSigning/SigningIdentity.swift | 1 + Sources/PackageSigning/X509Extensions.swift | 1 + .../BinaryTarget+Extensions.swift | 7 +- .../BuildParameters/BuildParameters.swift | 35 +++--- .../Plugins/PluginContextSerializer.swift | 10 +- .../Plugins/PluginScriptRunner.swift | 24 ++-- .../ResolvedPackage+Extensions.swift | 3 + Sources/SourceControl/RepositoryManager.swift | 29 ++--- Sources/SwiftBuildSupport/PIFBuilder.swift | 3 +- .../PackagePIFProjectBuilder+Modules.swift | 1 + .../PackagePIFProjectBuilder+Products.swift | 3 +- .../PackagePIFProjectBuilder.swift | 1 + Sources/SwiftSDKCommand/RemoveSwiftSDK.swift | 5 +- Sources/Workspace/InitPackage.swift | 1 + Sources/Workspace/ManagedArtifact.swift | 9 +- Sources/Workspace/ManagedDependency.swift | 21 ++-- Sources/Workspace/ManagedPrebuilt.swift | 3 +- .../RegistryPackageContainer.swift | 5 +- .../Workspace/Workspace+BinaryArtifacts.swift | 1 + Sources/Workspace/Workspace+Editing.swift | 3 + .../Workspace+PackageContainer.swift | 3 + Sources/Workspace/Workspace+Prebuilts.swift | 1 + .../Workspace+ResolvedPackages.swift | 2 + .../Workspace/Workspace+SourceControl.swift | 2 + Sources/Workspace/Workspace+State.swift | 39 +++--- Sources/XCBuildSupport/PIFBuilder.swift | 3 +- .../CrossCompilationPackageGraphTests.swift | 1 + .../PackageGraphTests/ModulesGraphTests.swift | 4 +- .../TopologicalSortTests.swift | 2 +- .../VersionSetSpecifierTests.swift | 1 + 126 files changed, 557 insertions(+), 352 deletions(-) diff --git a/Package.swift b/Package.swift index 93baca641c2..d55eba42c2b 100644 --- a/Package.swift +++ b/Package.swift @@ -35,6 +35,19 @@ if let resourceDirPath = ProcessInfo.processInfo.environment["SWIFTCI_INSTALL_RP packageLibraryLinkSettings = [] } +// Common experimental flags to be added to all targets. +let commonExperimentalFeatures: [SwiftSetting] = [ + .enableExperimentalFeature("MemberImportVisibility"), +] + +// Certain targets fail to compile with MemberImportVisibility enabled on 6.0.3 +// but work with >=6.1. These targets opt in to using `swift6CompatibleExperimentalFeatures`. +#if swift(>=6.1) +let swift6CompatibleExperimentalFeatures = commonExperimentalFeatures +#else +let swift6CompatibleExperimentalFeatures: [SwiftSetting] = [] +#endif + /** SwiftPMDataModel is the subset of SwiftPM product that includes just its data model. This allows some clients (such as IDEs) that use SwiftPM's data model but not its build system to not have to depend on SwiftDriver, SwiftLLBuild, etc. We should probably have better names here, @@ -152,7 +165,7 @@ let package = Package( .target( name: "PackageDescription", exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .define("USE_IMPL_ONLY_IMPORTS"), .unsafeFlags(["-package-description-version", "999.0"]), .unsafeFlags(["-enable-library-evolution"]), @@ -169,7 +182,7 @@ let package = Package( // AppleProductTypes library when they import it without further // messing with the manifest loader. dependencies: ["PackageDescription"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-package-description-version", "999.0"]), .unsafeFlags(["-enable-library-evolution"], .when(platforms: [.macOS])), .unsafeFlags(["-Xfrontend", "-module-link-name", "-Xfrontend", "AppleProductTypes"]) @@ -181,7 +194,7 @@ let package = Package( .target( name: "PackagePlugin", exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-package-description-version", "999.0"]), .unsafeFlags(["-enable-library-evolution"]), ], @@ -199,7 +212,7 @@ let package = Package( "SPMBuildCore", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .enableExperimentalFeature("AccessLevelOnImport"), .unsafeFlags(["-static"]), ] @@ -215,7 +228,7 @@ let package = Package( .product(name: "SystemPackage", package: "swift-system"), ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .enableExperimentalFeature("StrictConcurrency"), .enableExperimentalFeature("AccessLevelOnImport"), .enableExperimentalFeature("InternalImportsByDefault"), @@ -235,7 +248,7 @@ let package = Package( .product(name: "SystemPackage", package: "swift-system"), ], exclude: ["CMakeLists.txt", "Vendor/README.md"], - swiftSettings: [ + swiftSettings: swift6CompatibleExperimentalFeatures + [ .enableExperimentalFeature("StrictConcurrency"), .enableExperimentalFeature("AccessLevelOnImport"), .unsafeFlags(["-static"]), @@ -247,7 +260,7 @@ let package = Package( name: "LLBuildManifest", dependencies: ["Basics"], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -263,7 +276,7 @@ let package = Package( "PackageSigning", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -276,7 +289,7 @@ let package = Package( "PackageModel", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -286,7 +299,7 @@ let package = Package( name: "SPMLLBuild", dependencies: ["Basics"], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -298,7 +311,7 @@ let package = Package( name: "PackageModel", dependencies: ["Basics"], exclude: ["CMakeLists.txt", "README.md"], - swiftSettings: [ + swiftSettings: swift6CompatibleExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -312,7 +325,7 @@ let package = Package( "PackageModel", ] + swiftSyntaxDependencies(["SwiftBasicFormat", "SwiftDiagnostics", "SwiftIDEUtils", "SwiftParser", "SwiftSyntax", "SwiftSyntaxBuilder"]), exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -326,7 +339,7 @@ let package = Package( "SourceControl", ], exclude: ["CMakeLists.txt", "README.md"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -343,7 +356,7 @@ let package = Package( .product(name: "OrderedCollections", package: "swift-collections"), ], exclude: ["CMakeLists.txt", "README.md"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -357,7 +370,7 @@ let package = Package( exclude: [ "Formats/v1.md", ], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -372,7 +385,7 @@ let package = Package( "PackageModel", "SourceControl", ], - swiftSettings: [ + swiftSettings: swift6CompatibleExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -385,7 +398,7 @@ let package = Package( "Basics", "PackageCollectionsModel", ], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -397,7 +410,7 @@ let package = Package( "PackageModel", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -411,7 +424,7 @@ let package = Package( "PackageModel", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -427,7 +440,7 @@ let package = Package( .product(name: "OrderedCollections", package: "swift-collections"), ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -445,7 +458,7 @@ let package = Package( "DriverSupport", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -457,7 +470,7 @@ let package = Package( .product(name: "SwiftDriver", package: "swift-driver"), ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -470,7 +483,7 @@ let package = Package( .product(name: "OrderedCollections", package: "swift-collections"), ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -480,7 +493,8 @@ let package = Package( "SPMBuildCore", "PackageGraph", ], - exclude: ["CMakeLists.txt", "README.md"] + exclude: ["CMakeLists.txt", "README.md"], + swiftSettings: commonExperimentalFeatures ), .target( /** High level functionality */ @@ -497,7 +511,7 @@ let package = Package( .product(name: "OrderedCollections", package: "swift-collections"), ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -511,7 +525,7 @@ let package = Package( "PackageRegistry", "PackageSigning", ], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -533,7 +547,7 @@ let package = Package( "SwiftBuildSupport", ], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -555,7 +569,7 @@ let package = Package( "SwiftBuildSupport", ] + swiftSyntaxDependencies(["SwiftIDEUtils"]), exclude: ["CMakeLists.txt", "README.md"], - swiftSettings: [ + swiftSettings: swift6CompatibleExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -571,7 +585,7 @@ let package = Package( "PackageModel", ], exclude: ["CMakeLists.txt", "README.md"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -587,7 +601,7 @@ let package = Package( "PackageCollections", "PackageModel", ], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -609,7 +623,7 @@ let package = Package( "SPMBuildCore", "Workspace", ], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-static"]), ] ), @@ -717,7 +731,7 @@ let package = Package( name: "CompilerPluginSupport", dependencies: ["PackageDescription"], exclude: ["CMakeLists.txt"], - swiftSettings: [ + swiftSettings: commonExperimentalFeatures + [ .unsafeFlags(["-package-description-version", "999.0"]), .unsafeFlags(["-enable-library-evolution"]), ] @@ -840,7 +854,8 @@ let package = Package( ), .testTarget( name: "PackageGraphTests", - dependencies: ["PackageGraph", "_InternalTestSupport"] + dependencies: ["PackageGraph", "_InternalTestSupport"], + swiftSettings: commonExperimentalFeatures ), .testTarget( name: "PackageGraphPerformanceTests", diff --git a/Sources/Basics/AuthorizationProvider.swift b/Sources/Basics/AuthorizationProvider.swift index 5dcdbd3255d..85e7b1a7c51 100644 --- a/Sources/Basics/AuthorizationProvider.swift +++ b/Sources/Basics/AuthorizationProvider.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import TSCBasic import struct Foundation.Data import struct Foundation.Date import struct Foundation.URL diff --git a/Sources/Basics/Collections/Dictionary+Extensions.swift b/Sources/Basics/Collections/Dictionary+Extensions.swift index f31e0b57104..f08ff1d15ee 100644 --- a/Sources/Basics/Collections/Dictionary+Extensions.swift +++ b/Sources/Basics/Collections/Dictionary+Extensions.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import TSCBasic + extension Dictionary { @inlinable @discardableResult diff --git a/Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift b/Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift index 30f006c873a..b5b3597f15c 100644 --- a/Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift +++ b/Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import _Concurrency +import TSCUtility /// A progress animation wrapper that throttles updates to a given interval. final class ThrottledProgressAnimation: ProgressAnimationProtocol { diff --git a/Sources/Basics/SQLite.swift b/Sources/Basics/SQLite.swift index 803490b4865..cab2374f3c3 100644 --- a/Sources/Basics/SQLite.swift +++ b/Sources/Basics/SQLite.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import TSCBasic import Foundation #if SWIFT_PACKAGE && (os(Windows) || os(Android)) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index 8247465475f..9773457033b 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import TSCUtility import enum TSCBasic.JSON extension Triple { diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index 742c75154db..a98633d91ad 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -18,6 +18,7 @@ import PackageModel import OrderedCollections import SPMBuildCore +import TSCUtility import struct TSCBasic.SortedArray @@ -434,7 +435,7 @@ extension SortedArray where Element == AbsolutePath { } } -extension Triple { +extension Basics.Triple { var supportsFrameworks: Bool { return self.isDarwin() } diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 4eac7878727..347e4c9ca35 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -15,6 +15,7 @@ import Basics import Foundation import PackageGraph import PackageLoading +import TSCUtility @_spi(SwiftPMInternal) import PackageModel diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift index 69962b11bc2..34476408fff 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift @@ -16,6 +16,7 @@ import struct Basics.InternalError import class Basics.ObservabilityScope import struct PackageGraph.ResolvedModule import PackageModel +import SPMBuildCore extension LLBuildManifestBuilder { /// Create a llbuild target for a Clang target description. diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift index 9817039a4fd..33eb6f4558f 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import PackageModel + import struct Basics.AbsolutePath import struct Basics.InternalError import struct LLBuildManifest.Node diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift index 4d72ff35076..1f185cb4ce8 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift @@ -13,6 +13,8 @@ import struct LLBuildManifest.Node import struct Basics.RelativePath +import PackageModel + extension LLBuildManifestBuilder { /// Adds command for creating the resources bundle of the given target. /// diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index 11668bbe1b3..b2679e95b5a 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -24,10 +24,14 @@ import struct Basics.Environment #if USE_IMPL_ONLY_IMPORTS @_implementationOnly import class DriverSupport.SPMSwiftDriverExecutor +@_implementationOnly import Foundation @_implementationOnly import SwiftDriver +@_implementationOnly import TSCUtility #else import class DriverSupport.SPMSwiftDriverExecutor +import Foundation import SwiftDriver +import TSCUtility #endif import PackageModel @@ -81,7 +85,8 @@ extension LLBuildManifestBuilder { args: commandLine, diagnosticsOutput: .handler(self.observabilityScope.makeDiagnosticsHandler()), fileSystem: self.fileSystem, - executor: executor + executor: executor, + compilerIntegratedTooling: false ) try driver.checkLDPathOption(commandLine: commandLine) @@ -297,6 +302,7 @@ extension LLBuildManifestBuilder { args: commandLine, fileSystem: self.fileSystem, executor: executor, + compilerIntegratedTooling: false, externalTargetModuleDetailsMap: dependencyModuleDetailsMap, interModuleDependencyOracle: dependencyOracle ) diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift index ca83e4b2e6a..eef1675a3e9 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Clang.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift @@ -10,6 +10,11 @@ // //===----------------------------------------------------------------------===// +import Basics +import PackageGraph +import PackageLoading +import SPMBuildCore + import class PackageModel.BinaryModule import class PackageModel.ClangModule import class PackageModel.SwiftModule diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index f0c0b256cbb..d04c86e6033 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -16,6 +16,10 @@ import class PackageModel.BinaryModule import class PackageModel.ClangModule import class PackageModel.SystemLibraryModule +import PackageGraph +import PackageLoading +import SPMBuildCore + extension BuildPlan { func plan(swiftTarget: SwiftModuleBuildDescription) throws { // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 01ffe8ec665..22a9727191f 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -19,6 +19,7 @@ import PackageGraph import PackageLoading import PackageModel import SPMBuildCore +import TSCBasic #if USE_IMPL_ONLY_IMPORTS @_implementationOnly import SwiftDriver @@ -90,7 +91,7 @@ extension [String] { extension BuildParameters { /// Returns the directory to be used for module cache. - public var moduleCache: AbsolutePath { + public var moduleCache: Basics.AbsolutePath { get throws { // FIXME: We use this hack to let swiftpm's functional test use shared // cache so it doesn't become painfully slow. @@ -168,9 +169,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Return value of `inputs()` package enum Input { /// Any file in this directory affects the build plan - case directoryStructure(AbsolutePath) + case directoryStructure(Basics.AbsolutePath) /// The file at the given path affects the build plan - case file(AbsolutePath) + case file(Basics.AbsolutePath) } public enum Error: Swift.Error, CustomStringConvertible, Equatable { @@ -276,7 +277,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { pluginConfiguration: PluginConfiguration? = nil, pluginTools: [ResolvedModule.ID: [String: PluginTool]] = [:], additionalFileRules: [FileRuleDescription] = [], - pkgConfigDirectories: [AbsolutePath] = [], + pkgConfigDirectories: [Basics.AbsolutePath] = [], disableSandbox: Bool = false, fileSystem: any FileSystem, observabilityScope: ObservabilityScope @@ -698,7 +699,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { .map { .directoryStructure($0) } // Add the output paths of any prebuilds that were run, so that we redo the plan if they change. - var derivedSourceDirPaths: [AbsolutePath] = [] + var derivedSourceDirPaths: [Basics.AbsolutePath] = [] for result in self.prebuildCommandResults.values.flatMap({ $0 }) { derivedSourceDirPaths.append(contentsOf: result.outputDirectories) } @@ -768,7 +769,7 @@ extension BuildPlan { modulesGraph: ModulesGraph, tools: [ResolvedModule.ID: [String: PluginTool]], additionalFileRules: [FileRuleDescription], - pkgConfigDirectories: [AbsolutePath], + pkgConfigDirectories: [Basics.AbsolutePath], fileSystem: any FileSystem, observabilityScope: ObservabilityScope, surfaceDiagnostics: Bool = false @@ -895,8 +896,8 @@ extension BuildPlan { try pluginResults.map { pluginResult in // As we go we will collect a list of prebuild output directories whose contents should be input to the // build, and a list of the files in those directories after running the commands. - var derivedFiles: [AbsolutePath] = [] - var prebuildOutputDirs: [AbsolutePath] = [] + var derivedFiles: [Basics.AbsolutePath] = [] + var prebuildOutputDirs: [Basics.AbsolutePath] = [] for command in pluginResult.prebuildCommands { observabilityScope .emit( @@ -1248,7 +1249,7 @@ extension Basics.Diagnostic { extension BuildParameters { /// Returns a named bundle's path inside the build directory. - func bundlePath(named name: String) -> AbsolutePath { + func bundlePath(named name: String) -> Basics.AbsolutePath { self.buildPath.appending(component: name + self.triple.nsbundleExtension) } } @@ -1257,7 +1258,7 @@ extension BuildParameters { func generateResourceInfoPlist( fileSystem: FileSystem, target: ResolvedModule, - path: AbsolutePath + path: Basics.AbsolutePath ) throws -> Bool { guard let defaultLocalization = target.defaultLocalization else { return false diff --git a/Sources/Build/LLBuildDescription.swift b/Sources/Build/LLBuildDescription.swift index 81aeec613dd..8149a687129 100644 --- a/Sources/Build/LLBuildDescription.swift +++ b/Sources/Build/LLBuildDescription.swift @@ -15,6 +15,7 @@ import Foundation import LLBuildManifest import SPMBuildCore import PackageGraph +import PackageModel import struct TSCBasic.ByteString diff --git a/Sources/Build/SwiftCompilerOutputParser.swift b/Sources/Build/SwiftCompilerOutputParser.swift index 11eaf585d58..9f06aae52fe 100644 --- a/Sources/Build/SwiftCompilerOutputParser.swift +++ b/Sources/Build/SwiftCompilerOutputParser.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import Basics import Foundation import class TSCUtility.JSONMessageStreamingParser diff --git a/Sources/Commands/PackageCommands/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift index 416c7a40057..3e114341fe8 100644 --- a/Sources/Commands/PackageCommands/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -17,6 +17,8 @@ import Dispatch import PackageGraph import PackageModel import SourceControl +import SPMBuildCore +import TSCUtility import _Concurrency struct DeprecatedAPIDiff: ParsableCommand { diff --git a/Sources/Commands/PackageCommands/AddDependency.swift b/Sources/Commands/PackageCommands/AddDependency.swift index 76b32169c9f..a968965b118 100644 --- a/Sources/Commands/PackageCommands/AddDependency.swift +++ b/Sources/Commands/PackageCommands/AddDependency.swift @@ -13,6 +13,8 @@ import ArgumentParser import Basics import CoreCommands +import Foundation +import PackageGraph import PackageModel import PackageModelSyntax import SwiftParser diff --git a/Sources/Commands/PackageCommands/AddProduct.swift b/Sources/Commands/PackageCommands/AddProduct.swift index 4a60bf1e6db..787dc84ea99 100644 --- a/Sources/Commands/PackageCommands/AddProduct.swift +++ b/Sources/Commands/PackageCommands/AddProduct.swift @@ -13,6 +13,8 @@ import ArgumentParser import Basics import CoreCommands +import Foundation +import PackageGraph import PackageModel import PackageModelSyntax import SwiftParser diff --git a/Sources/Commands/PackageCommands/AddTarget.swift b/Sources/Commands/PackageCommands/AddTarget.swift index 8d9bea9fe5a..a1fb583d15e 100644 --- a/Sources/Commands/PackageCommands/AddTarget.swift +++ b/Sources/Commands/PackageCommands/AddTarget.swift @@ -13,6 +13,8 @@ import ArgumentParser import Basics import CoreCommands +import Foundation +import PackageGraph import PackageModel import PackageModelSyntax import SwiftParser diff --git a/Sources/Commands/PackageCommands/AddTargetDependency.swift b/Sources/Commands/PackageCommands/AddTargetDependency.swift index d629469d4d7..9c888546b5b 100644 --- a/Sources/Commands/PackageCommands/AddTargetDependency.swift +++ b/Sources/Commands/PackageCommands/AddTargetDependency.swift @@ -13,6 +13,8 @@ import ArgumentParser import Basics import CoreCommands +import Foundation +import PackageGraph import PackageModel import PackageModelSyntax import SwiftParser diff --git a/Sources/Commands/PackageCommands/ArchiveSource.swift b/Sources/Commands/PackageCommands/ArchiveSource.swift index bf06cf70c9a..807a9f1a6c8 100644 --- a/Sources/Commands/PackageCommands/ArchiveSource.swift +++ b/Sources/Commands/PackageCommands/ArchiveSource.swift @@ -13,6 +13,8 @@ import ArgumentParser import Basics import CoreCommands +import PackageGraph +import PackageModel import SourceControl extension SwiftPackageCommand { diff --git a/Sources/Commands/PackageCommands/CompletionCommand.swift b/Sources/Commands/PackageCommands/CompletionCommand.swift index c73710aa8ae..10f51bd0676 100644 --- a/Sources/Commands/PackageCommands/CompletionCommand.swift +++ b/Sources/Commands/PackageCommands/CompletionCommand.swift @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import Basics import CoreCommands +import PackageModel +import PackageGraph import var TSCBasic.stdoutStream diff --git a/Sources/Commands/PackageCommands/Config.swift b/Sources/Commands/PackageCommands/Config.swift index 0d61c59a449..6f4b14220b0 100644 --- a/Sources/Commands/PackageCommands/Config.swift +++ b/Sources/Commands/PackageCommands/Config.swift @@ -13,6 +13,7 @@ import ArgumentParser import Basics import CoreCommands +import PackageGraph import Workspace import var TSCBasic.stderrStream diff --git a/Sources/Commands/PackageCommands/Describe.swift b/Sources/Commands/PackageCommands/Describe.swift index 8edeb96b2dc..4392b0040fe 100644 --- a/Sources/Commands/PackageCommands/Describe.swift +++ b/Sources/Commands/PackageCommands/Describe.swift @@ -15,6 +15,8 @@ import Basics import CoreCommands import Foundation import PackageModel +import PackageGraph +import Workspace import struct TSCBasic.StringError diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index be0d9f18b19..3d40e42644e 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -15,6 +15,10 @@ import Basics import CoreCommands import Foundation import PackageModel +import PackageGraph +import SPMBuildCore +import TSCBasic +import Workspace import XCBuildSupport struct DumpSymbolGraph: AsyncSwiftCommand { diff --git a/Sources/Commands/PackageCommands/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift index 6e3bbdc4a0c..f0966e2378d 100644 --- a/Sources/Commands/PackageCommands/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -14,6 +14,7 @@ import ArgumentParser import Basics import CoreCommands import SourceControl +import Workspace extension SwiftPackageCommand { struct Edit: AsyncSwiftCommand { diff --git a/Sources/Commands/PackageCommands/Format.swift b/Sources/Commands/PackageCommands/Format.swift index c114a5e25ca..29399d5ec8c 100644 --- a/Sources/Commands/PackageCommands/Format.swift +++ b/Sources/Commands/PackageCommands/Format.swift @@ -14,6 +14,9 @@ import ArgumentParser import Basics import CoreCommands import PackageModel +import PackageGraph +import TSCBasic +import Workspace import class Basics.AsyncProcess @@ -34,7 +37,7 @@ extension SwiftPackageCommand { func run(_ swiftCommandState: SwiftCommandState) async throws { // Look for swift-format binary. // FIXME: This should be moved to user toolchain. - let swiftFormatInEnv = lookupExecutablePath(filename: Environment.current["SWIFT_FORMAT"]) + let swiftFormatInEnv = Basics.lookupExecutablePath(filename: Environment.current["SWIFT_FORMAT"]) guard let swiftFormat = swiftFormatInEnv ?? AsyncProcess.findExecutable("swift-format") else { swiftCommandState.observabilityScope.emit(error: "Could not find swift-format in PATH or SWIFT_FORMAT") throw TSCUtility.Diagnostics.fatalError diff --git a/Sources/Commands/PackageCommands/Init.swift b/Sources/Commands/PackageCommands/Init.swift index 3cc7f87fa24..5eb8293ee2a 100644 --- a/Sources/Commands/PackageCommands/Init.swift +++ b/Sources/Commands/PackageCommands/Init.swift @@ -16,6 +16,7 @@ import Basics @_spi(SwiftPMInternal) import CoreCommands +import PackageModel import Workspace import SPMBuildCore diff --git a/Sources/Commands/PackageCommands/Install.swift b/Sources/Commands/PackageCommands/Install.swift index 1401819b47a..87a0a16c493 100644 --- a/Sources/Commands/PackageCommands/Install.swift +++ b/Sources/Commands/PackageCommands/Install.swift @@ -14,8 +14,11 @@ import ArgumentParser import struct Basics.Environment import CoreCommands import Foundation +import PackageGraph import PackageModel +import SPMBuildCore import TSCBasic +import Workspace extension SwiftPackageCommand { struct Install: AsyncSwiftCommand { diff --git a/Sources/Commands/PackageCommands/Learn.swift b/Sources/Commands/PackageCommands/Learn.swift index 72a9843abdb..176f7cd45fa 100644 --- a/Sources/Commands/PackageCommands/Learn.swift +++ b/Sources/Commands/PackageCommands/Learn.swift @@ -13,6 +13,7 @@ import ArgumentParser import Basics import CoreCommands +import Foundation import PackageGraph import PackageModel diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index 5f05d421f42..fcc9b73192c 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -15,10 +15,12 @@ import Basics import _Concurrency import CoreCommands import Dispatch - +import SPMBuildCore import PackageGraph - import PackageModel +import TSCBasic +import TSCUtility +import Workspace struct PluginCommand: AsyncSwiftCommand { static let configuration = CommandConfiguration( diff --git a/Sources/Commands/PackageCommands/ResetCommands.swift b/Sources/Commands/PackageCommands/ResetCommands.swift index 7f010d0932d..2b8e5c02708 100644 --- a/Sources/Commands/PackageCommands/ResetCommands.swift +++ b/Sources/Commands/PackageCommands/ResetCommands.swift @@ -12,6 +12,7 @@ import ArgumentParser import CoreCommands +import Workspace extension SwiftPackageCommand { struct Clean: SwiftCommand { diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index cae63da126b..baf18eb9e7b 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -11,8 +11,10 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import Basics import CoreCommands import TSCUtility +import Workspace import struct PackageGraph.TraitConfiguration diff --git a/Sources/Commands/PackageCommands/ShowExecutables.swift b/Sources/Commands/PackageCommands/ShowExecutables.swift index fb08f22ee90..c1e50248b19 100644 --- a/Sources/Commands/PackageCommands/ShowExecutables.swift +++ b/Sources/Commands/PackageCommands/ShowExecutables.swift @@ -14,6 +14,8 @@ import ArgumentParser import Basics import CoreCommands import Foundation +import PackageModel +import PackageGraph import Workspace struct ShowExecutables: AsyncSwiftCommand { diff --git a/Sources/Commands/PackageCommands/Update.swift b/Sources/Commands/PackageCommands/Update.swift index 31b3738af23..36a09a1aa75 100644 --- a/Sources/Commands/PackageCommands/Update.swift +++ b/Sources/Commands/PackageCommands/Update.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import Basics import CoreCommands import Dispatch import PackageModel diff --git a/Sources/Commands/Snippets/Cards/SnippetCard.swift b/Sources/Commands/Snippets/Cards/SnippetCard.swift index f6ddd901137..d54b916ccf3 100644 --- a/Sources/Commands/Snippets/Cards/SnippetCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetCard.swift @@ -12,7 +12,10 @@ import Basics import CoreCommands +import Foundation import PackageModel +import PackageGraph +import SPMBuildCore import func TSCBasic.exec import enum TSCBasic.ProcessEnv diff --git a/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift b/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift index cd5e070f1b6..1a27442a5c9 100644 --- a/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift @@ -12,6 +12,7 @@ import CoreCommands import PackageModel +import TSCUtility /// A card showing the snippets in a ``SnippetGroup``. struct SnippetGroupCard: Card { diff --git a/Sources/Commands/Snippets/Cards/TopCard.swift b/Sources/Commands/Snippets/Cards/TopCard.swift index a1e24c39976..614a20b6080 100644 --- a/Sources/Commands/Snippets/Cards/TopCard.swift +++ b/Sources/Commands/Snippets/Cards/TopCard.swift @@ -14,6 +14,7 @@ import CoreCommands import Foundation import PackageGraph import PackageModel +import TSCUtility /// The top menu card for a package's help contents, including snippets. struct TopCard: Card { diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index 07d6b3b138f..21c455edd55 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -16,6 +16,7 @@ import CoreCommands import Foundation import PackageGraph import PackageModel +import SPMBuildCore import enum TSCBasic.ProcessEnv import func TSCBasic.exec diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index c4c6b45bc62..aefa252850e 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -28,6 +28,7 @@ import PackageGraph import PackageModel import SPMBuildCore +import TSCUtility import func TSCLibc.exit import Workspace diff --git a/Sources/Commands/Utilities/DependenciesSerializer.swift b/Sources/Commands/Utilities/DependenciesSerializer.swift index 25190f011c4..d58e5c58162 100644 --- a/Sources/Commands/Utilities/DependenciesSerializer.swift +++ b/Sources/Commands/Utilities/DependenciesSerializer.swift @@ -10,8 +10,10 @@ // //===----------------------------------------------------------------------===// +import Basics import PackageModel import PackageGraph +import TSCUtility import enum TSCBasic.JSON import protocol TSCBasic.OutputByteStream diff --git a/Sources/Commands/Utilities/MultiRootSupport.swift b/Sources/Commands/Utilities/MultiRootSupport.swift index 7ee468a81f5..7cd42e877bc 100644 --- a/Sources/Commands/Utilities/MultiRootSupport.swift +++ b/Sources/Commands/Utilities/MultiRootSupport.swift @@ -13,6 +13,8 @@ import Basics import CoreCommands import Foundation +import TSCBasic + #if canImport(FoundationXML) import FoundationXML #endif @@ -44,7 +46,7 @@ public struct XcodeWorkspaceLoader: WorkspaceLoader { } /// Load the given workspace and return the file ref paths from it. - public func load(workspace: AbsolutePath) throws -> [AbsolutePath] { + public func load(workspace: Basics.AbsolutePath) throws -> [Basics.AbsolutePath] { let path = workspace.appending("contents.xcworkspacedata") let contents: Data = try self.fileSystem.readFileContents(path) @@ -56,9 +58,9 @@ public struct XcodeWorkspaceLoader: WorkspaceLoader { } /// Convert the parsed result into absolute paths. - var result: [AbsolutePath] = [] + var result: [Basics.AbsolutePath] = [] for location in delegate.locations { - let path: AbsolutePath + let path: Basics.AbsolutePath switch location.kind { case .absolute: diff --git a/Sources/Commands/Utilities/SymbolGraphExtract.swift b/Sources/Commands/Utilities/SymbolGraphExtract.swift index cadf1b2a4b4..f18f9aa1b76 100644 --- a/Sources/Commands/Utilities/SymbolGraphExtract.swift +++ b/Sources/Commands/Utilities/SymbolGraphExtract.swift @@ -12,6 +12,7 @@ import ArgumentParser import Basics +import Foundation import PackageGraph import PackageModel import SPMBuildCore diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index 84206c5e4dd..5eae29a06aa 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -14,6 +14,7 @@ import Basics import CoreCommands import PackageModel import SPMBuildCore +import TSCUtility import Workspace import struct TSCBasic.FileSystemError diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index 9407a10f8a4..28fa15d1407 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -16,6 +16,7 @@ import SPMBuildCore import XCBuildSupport import SwiftBuildSupport import PackageGraph +import Workspace import class Basics.ObservabilityScope import struct PackageGraph.ModulesGraph diff --git a/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift b/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift index 59b52d15d21..f555d2331d7 100644 --- a/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift +++ b/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift @@ -13,6 +13,7 @@ @_spi(SwiftPMInternal) import Basics import Dispatch +import PackageModel import protocol TSCBasic.OutputByteStream import class TSCBasic.TerminalController diff --git a/Sources/DriverSupport/DriverSupportUtils.swift b/Sources/DriverSupport/DriverSupportUtils.swift index 497bd403373..1497ec1b94a 100644 --- a/Sources/DriverSupport/DriverSupportUtils.swift +++ b/Sources/DriverSupport/DriverSupportUtils.swift @@ -12,7 +12,7 @@ @_spi(SwiftPMInternal) import Basics - +import Foundation import PackageModel import SwiftDriver import class TSCBasic.Process @@ -42,6 +42,7 @@ public enum DriverSupport { let driver = try Driver( args: ["swiftc"], executor: executor, + compilerIntegratedTooling: false, compilerExecutableDir: TSCAbsolutePath(toolchain.swiftCompilerPath.parentDirectory) ) let supportedFlagSet = Set(driver.supportedFrontendFlags.map { $0.trimmingCharacters(in: ["-"]) }) diff --git a/Sources/LLBuildManifest/LLBuildManifest.swift b/Sources/LLBuildManifest/LLBuildManifest.swift index cc0cd4b74e0..520db084769 100644 --- a/Sources/LLBuildManifest/LLBuildManifest.swift +++ b/Sources/LLBuildManifest/LLBuildManifest.swift @@ -12,6 +12,8 @@ import Basics import Foundation +import TSCBasic +import TSCUtility import class Basics.AsyncProcess @@ -59,7 +61,7 @@ public enum WriteAuxiliary { public static let name = "link-file-list" // FIXME: We should extend the `InProcessTool` support to allow us to specify these in a typed way, but today we have to flatten all the inputs into a generic `Node` array (rdar://109844243). - public static func computeInputs(objects: [AbsolutePath]) -> [Node] { + public static func computeInputs(objects: [Basics.AbsolutePath]) -> [Node] { return [.virtual(Self.name)] + objects.map { Node.file($0) } } @@ -88,7 +90,7 @@ public enum WriteAuxiliary { public struct SourcesFileList: AuxiliaryFileType { public static let name = "sources-file-list" - public static func computeInputs(sources: [AbsolutePath]) -> [Node] { + public static func computeInputs(sources: [Basics.AbsolutePath]) -> [Node] { return [.virtual(Self.name)] + sources.map { Node.file($0) } } @@ -114,7 +116,7 @@ public enum WriteAuxiliary { public struct SwiftGetVersion: AuxiliaryFileType { public static let name = "swift-get-version" - public static func computeInputs(swiftCompilerPath: AbsolutePath) -> [Node] { + public static func computeInputs(swiftCompilerPath: Basics.AbsolutePath) -> [Node] { return [.virtual(Self.name), .file(swiftCompilerPath)] } @@ -122,7 +124,7 @@ public enum WriteAuxiliary { guard let swiftCompilerPathString = inputs.first(where: { $0.kind == .file })?.name else { throw Error.unknownSwiftCompilerPath } - let swiftCompilerPath = try AbsolutePath(validating: swiftCompilerPathString) + let swiftCompilerPath = try Basics.AbsolutePath(validating: swiftCompilerPathString) return try AsyncProcess.checkNonZeroExit(args: swiftCompilerPath.pathString, "-version") } @@ -164,7 +166,7 @@ public enum WriteAuxiliary { public struct EmbeddedResources: AuxiliaryFileType { public static let name = "embedded-resources" - public static func computeInputs(resources: [AbsolutePath]) -> [Node] { + public static func computeInputs(resources: [Basics.AbsolutePath]) -> [Node] { return [.virtual(Self.name)] + resources.map { Node.file($0) } } @@ -178,7 +180,7 @@ public enum WriteAuxiliary { """ for input in inputs where input.kind == .file { - let resourcePath = try AbsolutePath(validating: input.name) + let resourcePath = try Basics.AbsolutePath(validating: input.name) let variableName = resourcePath.basename.spm_mangledToC99ExtendedIdentifier() let fileContent = try Data(contentsOf: URL(fileURLWithPath: resourcePath.pathString)).map { String($0) }.joined(separator: ",") @@ -267,7 +269,7 @@ public struct LLBuildManifest { addCommand(name: name, tool: tool) } - public mutating func addEntitlementPlistCommand(entitlement: String, outputPath: AbsolutePath) { + public mutating func addEntitlementPlistCommand(entitlement: String, outputPath: Basics.AbsolutePath) { let inputs = WriteAuxiliary.EntitlementPlist.computeInputs(entitlement: entitlement) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) let name = outputPath.pathString @@ -275,8 +277,8 @@ public struct LLBuildManifest { } public mutating func addWriteLinkFileListCommand( - objects: [AbsolutePath], - linkFileListPath: AbsolutePath + objects: [Basics.AbsolutePath], + linkFileListPath: Basics.AbsolutePath ) { let inputs = WriteAuxiliary.LinkFileList.computeInputs(objects: objects) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: linkFileListPath) @@ -285,8 +287,8 @@ public struct LLBuildManifest { } public mutating func addWriteSourcesFileListCommand( - sources: [AbsolutePath], - sourcesFileListPath: AbsolutePath + sources: [Basics.AbsolutePath], + sourcesFileListPath: Basics.AbsolutePath ) { let inputs = WriteAuxiliary.SourcesFileList.computeInputs(sources: sources) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: sourcesFileListPath) @@ -295,8 +297,8 @@ public struct LLBuildManifest { } public mutating func addSwiftGetVersionCommand( - swiftCompilerPath: AbsolutePath, - swiftVersionFilePath: AbsolutePath + swiftCompilerPath: Basics.AbsolutePath, + swiftVersionFilePath: Basics.AbsolutePath ) { let inputs = WriteAuxiliary.SwiftGetVersion.computeInputs(swiftCompilerPath: swiftCompilerPath) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: swiftVersionFilePath, alwaysOutOfDate: true) @@ -304,7 +306,7 @@ public struct LLBuildManifest { addCommand(name: name, tool: tool) } - public mutating func addWriteInfoPlistCommand(principalClass: String, outputPath: AbsolutePath) { + public mutating func addWriteInfoPlistCommand(principalClass: String, outputPath: Basics.AbsolutePath) { let inputs = WriteAuxiliary.XCTestInfoPlist.computeInputs(principalClass: principalClass) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) let name = outputPath.pathString @@ -312,8 +314,8 @@ public struct LLBuildManifest { } public mutating func addWriteEmbeddedResourcesCommand( - resources: [AbsolutePath], - outputPath: AbsolutePath + resources: [Basics.AbsolutePath], + outputPath: Basics.AbsolutePath ) { let inputs = WriteAuxiliary.EmbeddedResources.computeInputs(resources: resources) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) @@ -393,19 +395,19 @@ public struct LLBuildManifest { name: String, inputs: [Node], outputs: [Node], - executable: AbsolutePath, + executable: Basics.AbsolutePath, moduleName: String, moduleAliases: [String: String]?, - moduleOutputPath: AbsolutePath, - importPath: AbsolutePath, - tempsPath: AbsolutePath, - objects: [AbsolutePath], + moduleOutputPath: Basics.AbsolutePath, + importPath: Basics.AbsolutePath, + tempsPath: Basics.AbsolutePath, + objects: [Basics.AbsolutePath], otherArguments: [String], - sources: [AbsolutePath], - fileList: AbsolutePath, + sources: [Basics.AbsolutePath], + fileList: Basics.AbsolutePath, isLibrary: Bool, wholeModuleOptimization: Bool, - outputFileMapPath: AbsolutePath, + outputFileMapPath: Basics.AbsolutePath, prepareForIndexing: Bool ) { let tool = SwiftCompilerTool( diff --git a/Sources/PackageCollections/PackageCollections+Validation.swift b/Sources/PackageCollections/PackageCollections+Validation.swift index 3940021555d..c0e7bbd5c2e 100644 --- a/Sources/PackageCollections/PackageCollections+Validation.swift +++ b/Sources/PackageCollections/PackageCollections+Validation.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import Foundation import PackageCollectionsModel import PackageModel diff --git a/Sources/PackageCollections/PackageCollections.swift b/Sources/PackageCollections/PackageCollections.swift index b37eca329b2..17db7491bf1 100644 --- a/Sources/PackageCollections/PackageCollections.swift +++ b/Sources/PackageCollections/PackageCollections.swift @@ -13,6 +13,7 @@ import _Concurrency import Basics import PackageModel +import Foundation import protocol TSCBasic.Closable diff --git a/Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift b/Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift index 1df42244400..4e24bb717e0 100644 --- a/Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift +++ b/Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift @@ -24,6 +24,7 @@ import PackageCollectionsModel import PackageCollectionsSigning import PackageModel import SourceControl +import TSCBasic import struct TSCUtility.Version @@ -64,7 +65,7 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider { self.httpClient = customHTTPClient ?? Self.makeDefaultHTTPClient() self.signatureValidator = customSignatureValidator ?? PackageCollectionSigning( trustedRootCertsDir: configuration.trustedRootCertsDir ?? - (try? fileSystem.swiftPMConfigurationDirectory.appending("trust-root-certs").asURL) ?? AbsolutePath.root.asURL, + (try? fileSystem.swiftPMConfigurationDirectory.appending("trust-root-certs").asURL) ?? Basics.AbsolutePath.root.asURL, additionalTrustedRootCerts: sourceCertPolicy.allRootCerts.map { Array($0) }, observabilityScope: observabilityScope ) diff --git a/Sources/PackageCollections/Storage/FilePackageCollectionsSourcesStorage.swift b/Sources/PackageCollections/Storage/FilePackageCollectionsSourcesStorage.swift index b3760d3b246..ddc24c23f2b 100644 --- a/Sources/PackageCollections/Storage/FilePackageCollectionsSourcesStorage.swift +++ b/Sources/PackageCollections/Storage/FilePackageCollectionsSourcesStorage.swift @@ -13,6 +13,7 @@ import _Concurrency import Basics import Dispatch +import TSCBasic import struct Foundation.Data import class Foundation.JSONDecoder import class Foundation.JSONEncoder @@ -20,12 +21,12 @@ import struct Foundation.URL struct FilePackageCollectionsSourcesStorage: PackageCollectionsSourcesStorage { let fileSystem: FileSystem - let path: AbsolutePath + let path: Basics.AbsolutePath private let encoder: JSONEncoder private let decoder: JSONDecoder - init(fileSystem: FileSystem, path: AbsolutePath? = nil) { + init(fileSystem: FileSystem, path: Basics.AbsolutePath? = nil) { self.fileSystem = fileSystem self.path = path ?? (try? fileSystem.swiftPMConfigurationDirectory.appending("collections.json")) ?? .root diff --git a/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift b/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift index 30bd2589136..295a496deb7 100644 --- a/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift +++ b/Sources/PackageCollections/Storage/SQLitePackageCollectionsStorage.swift @@ -20,6 +20,7 @@ import class Foundation.JSONEncoder import class Foundation.NSLock import struct Foundation.URL import PackageModel +import TSCUtility import protocol TSCBasic.Closable diff --git a/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift b/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift index 0903c0fccad..0dbc770acbe 100644 --- a/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift +++ b/Sources/PackageCollectionsSigning/PackageCollectionSigning.swift @@ -15,6 +15,7 @@ import Basics import Dispatch import Foundation import PackageCollectionsModel +import SwiftASN1 #if USE_IMPL_ONLY_IMPORTS @_implementationOnly import _CryptoExtras diff --git a/Sources/PackageDescription/PackageDependency.swift b/Sources/PackageDescription/PackageDependency.swift index 60dbe1f426d..594736e558c 100644 --- a/Sources/PackageDescription/PackageDependency.swift +++ b/Sources/PackageDescription/PackageDependency.swift @@ -10,6 +10,11 @@ // //===----------------------------------------------------------------------===// +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import Foundation +#else +import Foundation +#endif extension Package { /// A package dependency of a Swift package. diff --git a/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift b/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift index 54f3c11af01..a0af231bd6c 100644 --- a/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift +++ b/Sources/PackageFingerprint/FilePackageFingerprintStorage.swift @@ -14,17 +14,18 @@ import Basics import Dispatch import Foundation import PackageModel +import TSCBasic import struct TSCUtility.Version public struct FilePackageFingerprintStorage: PackageFingerprintStorage { let fileSystem: FileSystem - let directoryPath: AbsolutePath + let directoryPath: Basics.AbsolutePath private let encoder: JSONEncoder private let decoder: JSONDecoder - public init(fileSystem: FileSystem, directoryPath: AbsolutePath) { + public init(fileSystem: FileSystem, directoryPath: Basics.AbsolutePath) { self.fileSystem = fileSystem self.directoryPath = directoryPath diff --git a/Sources/PackageFingerprint/PackageFingerprintStorage.swift b/Sources/PackageFingerprint/PackageFingerprintStorage.swift index 94355c02a0c..397a8e3f308 100644 --- a/Sources/PackageFingerprint/PackageFingerprintStorage.swift +++ b/Sources/PackageFingerprint/PackageFingerprintStorage.swift @@ -13,6 +13,7 @@ import Basics import Dispatch import PackageModel +import TSCBasic import struct TSCUtility.Version diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index e91f2d8f0a2..03b8ebd1e23 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -14,6 +14,7 @@ import Basics import OrderedCollections import PackageLoading import PackageModel +import Foundation import func TSCBasic.bestMatch import func TSCBasic.findCycle diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index d0ed47cfdb4..928f3c9d405 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -10,12 +10,14 @@ // //===----------------------------------------------------------------------===// -import protocol Basics.FileSystem -import class Basics.ObservabilityScope -import struct Basics.IdentifiableSet import OrderedCollections import PackageLoading import PackageModel +import TSCBasic + +import protocol Basics.FileSystem +import class Basics.ObservabilityScope +import struct Basics.IdentifiableSet enum PackageGraphError: Swift.Error { /// Indicates a non-root package with no modules. @@ -268,7 +270,7 @@ public struct ModulesGraph { for module in rootModules where module.type == .executable { // Find all dependencies of this module within its package. Note that we do not traverse plugin usages. - let dependencies = try topologicalSort(module.dependencies, successors: { + let dependencies = try topologicalSortIdentifiable(module.dependencies, successors: { $0.dependencies.compactMap{ $0.module }.filter{ $0.type != .plugin }.map{ .module($0, conditions: []) } }).compactMap({ $0.module }) @@ -399,12 +401,12 @@ enum GraphError: Error { /// /// - Complexity: O(v + e) where (v, e) are the number of vertices and edges /// reachable from the input nodes via the relation. -func topologicalSort( +func topologicalSortIdentifiable( _ nodes: [T], successors: (T) throws -> [T] ) throws -> [T] { // Implements a topological sort via recursion and reverse postorder DFS. func visit(_ node: T, - _ stack: inout OrderedSet, _ visited: inout Set, _ result: inout [T], + _ stack: inout OrderedCollections.OrderedSet, _ visited: inout Set, _ result: inout [T], _ successors: (T) throws -> [T]) throws { // Mark this node as visited -- we are done if it already was. if !visited.insert(node.id).inserted { @@ -431,7 +433,7 @@ func topologicalSort( // FIXME: This should use a stack not recursion. var visited = Set() var result = [T]() - var stack = OrderedSet() + var stack = OrderedCollections.OrderedSet() for node in nodes { precondition(stack.isEmpty) stack.append(node.id) diff --git a/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift index 8352c146820..6508e5e145d 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift @@ -13,6 +13,7 @@ import Basics import Dispatch import PackageModel +import TSCBasic /// An utility class around PackageContainerProvider that allows "prefetching" the containers /// in parallel. The basic idea is to kick off container fetching before starting the resolution diff --git a/Sources/PackageGraph/Resolution/PubGrub/DiagnosticReportBuilder.swift b/Sources/PackageGraph/Resolution/PubGrub/DiagnosticReportBuilder.swift index 7792390d1eb..c1b6275dcee 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/DiagnosticReportBuilder.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/DiagnosticReportBuilder.swift @@ -10,6 +10,10 @@ // //===----------------------------------------------------------------------===// +import Foundation +import PackageModel +import TSCUtility + struct DiagnosticReportBuilder { let rootNode: DependencyResolutionNode let incompatibilities: [DependencyResolutionNode: [Incompatibility]] diff --git a/Sources/PackageGraph/Resolution/ResolvedModule.swift b/Sources/PackageGraph/Resolution/ResolvedModule.swift index c37e82c26a6..5f4aec5625c 100644 --- a/Sources/PackageGraph/Resolution/ResolvedModule.swift +++ b/Sources/PackageGraph/Resolution/ResolvedModule.swift @@ -12,6 +12,7 @@ import PackageModel +import func TSCBasic.topologicalSort import struct Basics.IdentifiableSet @available(*, deprecated, renamed: "ResolvedModule") diff --git a/Sources/PackageLoading/ManifestLoader+Validation.swift b/Sources/PackageLoading/ManifestLoader+Validation.swift index 285736c1198..a0213af9239 100644 --- a/Sources/PackageLoading/ManifestLoader+Validation.swift +++ b/Sources/PackageLoading/ManifestLoader+Validation.swift @@ -13,6 +13,8 @@ import Basics import Foundation import PackageModel +import TSCBasic +import TSCUtility public struct ManifestValidator { static var supportedLocalBinaryDependencyExtensions: [String] { @@ -160,7 +162,7 @@ public struct ManifestValidator { continue } - guard let relativePath = try? RelativePath(validating: path) else { + guard let relativePath = try? Basics.RelativePath(validating: path) else { diagnostics.append(.invalidLocalBinaryPath(path: path, targetName: target.name)) continue } @@ -274,7 +276,7 @@ public struct ManifestValidator { } public protocol ManifestSourceControlValidator { - func isValidDirectory(_ path: AbsolutePath) throws -> Bool + func isValidDirectory(_ path: Basics.AbsolutePath) throws -> Bool } extension Basics.Diagnostic { @@ -344,7 +346,7 @@ extension Basics.Diagnostic { } } - static func invalidSourceControlDirectory(_ path: AbsolutePath, underlyingError: Error? = nil) -> Self { + static func invalidSourceControlDirectory(_ path: Basics.AbsolutePath, underlyingError: Error? = nil) -> Self { .error("cannot clone from local directory \(path)\nPlease git init or use \"path:\" for \(path)\(errorSuffix(underlyingError))") } diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 3009970eb3a..5f9cb4cb09d 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -13,11 +13,12 @@ import Basics import Foundation import PackageModel +import TSCBasic /// Name of the module map file recognized by the Clang and Swift compilers. public let moduleMapFilename = "module.modulemap" -extension AbsolutePath { +extension Basics.AbsolutePath { fileprivate var moduleEscapedPathString: String { return self.pathString.replacing("\\", with: "\\\\") } @@ -25,27 +26,27 @@ extension AbsolutePath { /// A protocol for targets which might have a modulemap. protocol ModuleMapProtocol { - var moduleMapPath: AbsolutePath { get } + var moduleMapPath: Basics.AbsolutePath { get } - var moduleMapDirectory: AbsolutePath { get } + var moduleMapDirectory: Basics.AbsolutePath { get } } extension SystemLibraryModule: ModuleMapProtocol { - var moduleMapDirectory: AbsolutePath { + var moduleMapDirectory: Basics.AbsolutePath { return path } - public var moduleMapPath: AbsolutePath { + public var moduleMapPath: Basics.AbsolutePath { return moduleMapDirectory.appending(component: moduleMapFilename) } } extension ClangModule: ModuleMapProtocol { - var moduleMapDirectory: AbsolutePath { + var moduleMapDirectory: Basics.AbsolutePath { return includeDir } - public var moduleMapPath: AbsolutePath { + public var moduleMapPath: Basics.AbsolutePath { return moduleMapDirectory.appending(component: moduleMapFilename) } } @@ -75,12 +76,12 @@ public struct ModuleMapGenerator { private let moduleName: String /// The target's public-headers directory. - private let publicHeadersDir: AbsolutePath + private let publicHeadersDir: Basics.AbsolutePath /// The file system to be used. private let fileSystem: FileSystem - public init(targetName: String, moduleName: String, publicHeadersDir: AbsolutePath, fileSystem: FileSystem) { + public init(targetName: String, moduleName: String, publicHeadersDir: Basics.AbsolutePath, fileSystem: FileSystem) { self.targetName = targetName self.moduleName = moduleName self.publicHeadersDir = publicHeadersDir @@ -110,7 +111,7 @@ public struct ModuleMapGenerator { } // Next try to get the entries in the public-headers directory. - let entries: Set + let entries: Set do { entries = try Set(fileSystem.getDirectoryContents(publicHeadersDir).map({ publicHeadersDir.appending(component: $0) })) } @@ -174,7 +175,7 @@ public struct ModuleMapGenerator { } /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any diagnostics are added to the receiver's diagnostics engine. - public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath) throws { + public func generateModuleMap(type: GeneratedModuleMapType, at path: Basics.AbsolutePath) throws { var moduleMap = "module \(moduleName) {\n" switch type { case .umbrellaHeader(let hdr): @@ -205,8 +206,8 @@ public struct ModuleMapGenerator { /// A type of module map to generate. public enum GeneratedModuleMapType { - case umbrellaHeader(AbsolutePath) - case umbrellaDirectory(AbsolutePath) + case umbrellaHeader(Basics.AbsolutePath) + case umbrellaDirectory(Basics.AbsolutePath) } public extension ModuleMapType { @@ -223,32 +224,32 @@ public extension ModuleMapType { private extension Basics.Diagnostic { /// Warning emitted if the public-headers directory is missing. - static func missingPublicHeadersDirectory(targetName: String, publicHeadersDir: AbsolutePath) -> Self { + static func missingPublicHeadersDirectory(targetName: String, publicHeadersDir: Basics.AbsolutePath) -> Self { .warning("no include directory found for target '\(targetName)'; libraries cannot be imported without public headers") } /// Error emitted if the public-headers directory is inaccessible. - static func inaccessiblePublicHeadersDirectory(targetName: String, publicHeadersDir: AbsolutePath, fileSystemError: Error) -> Self { + static func inaccessiblePublicHeadersDirectory(targetName: String, publicHeadersDir: Basics.AbsolutePath, fileSystemError: Error) -> Self { .error("cannot access public-headers directory for target '\(targetName)': \(String(describing: fileSystemError))") } /// Warning emitted if a misnamed umbrella header was found. - static func misnamedUmbrellaHeader(misnamedUmbrellaHeader: AbsolutePath, umbrellaHeader: AbsolutePath) -> Self { + static func misnamedUmbrellaHeader(misnamedUmbrellaHeader: Basics.AbsolutePath, umbrellaHeader: Basics.AbsolutePath) -> Self { .warning("\(misnamedUmbrellaHeader) should be renamed to \(umbrellaHeader) to be used as an umbrella header") } /// Error emitted if there are directories next to a top-level umbrella header. - static func umbrellaHeaderHasSiblingDirectories(targetName: String, umbrellaHeader: AbsolutePath, siblingDirs: Set) -> Self { + static func umbrellaHeaderHasSiblingDirectories(targetName: String, umbrellaHeader: Basics.AbsolutePath, siblingDirs: Set) -> Self { .error("target '\(targetName)' has invalid header layout: umbrella header found at '\(umbrellaHeader)', but directories exist next to it: \(siblingDirs.map({ String(describing: $0) }).sorted().joined(separator: ", ")); consider removing them") } /// Error emitted if there are other directories next to the parent directory of a nested umbrella header. - static func umbrellaHeaderParentDirHasSiblingDirectories(targetName: String, umbrellaHeader: AbsolutePath, siblingDirs: Set) -> Self { + static func umbrellaHeaderParentDirHasSiblingDirectories(targetName: String, umbrellaHeader: Basics.AbsolutePath, siblingDirs: Set) -> Self { .error("target '\(targetName)' has invalid header layout: umbrella header found at '\(umbrellaHeader)', but more than one directory exists next to its parent directory: \(siblingDirs.map({ String(describing: $0) }).sorted().joined(separator: ", ")); consider reducing them to one") } /// Error emitted if there are other headers next to the parent directory of a nested umbrella header. - static func umbrellaHeaderParentDirHasSiblingHeaders(targetName: String, umbrellaHeader: AbsolutePath, siblingHeaders: Set) -> Self { + static func umbrellaHeaderParentDirHasSiblingHeaders(targetName: String, umbrellaHeader: Basics.AbsolutePath, siblingHeaders: Set) -> Self { .error("target '\(targetName)' has invalid header layout: umbrella header found at '\(umbrellaHeader)', but additional header files exist: \((siblingHeaders.map({ String(describing: $0) }).sorted().joined(separator: ", "))); consider reducing them to one") } } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 5e8f0c81122..8524e8ff707 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -14,6 +14,8 @@ import Basics import Dispatch import OrderedCollections import PackageModel +import Foundation +import TSCUtility import func TSCBasic.findCycle import struct TSCBasic.KeyedPair diff --git a/Sources/PackageLoading/PkgConfig.swift b/Sources/PackageLoading/PkgConfig.swift index b98d01932d5..86509d0f5cd 100644 --- a/Sources/PackageLoading/PkgConfig.swift +++ b/Sources/PackageLoading/PkgConfig.swift @@ -13,6 +13,8 @@ import Basics import Foundation import OrderedCollections +import TSCUtility +import TSCBasic import class Basics.AsyncProcess @@ -22,7 +24,7 @@ public struct PkgConfig { public let name: String /// The path to the definition file. - public let pcFile: AbsolutePath + public let pcFile: Basics.AbsolutePath /// The list of C compiler flags in the definition. public let cFlags: [String] @@ -44,9 +46,9 @@ public struct PkgConfig { /// - throws: PkgConfigError public init( name: String, - additionalSearchPaths: [AbsolutePath]? = .none, - brewPrefix: AbsolutePath? = .none, - sysrootDir: AbsolutePath? = .none, + additionalSearchPaths: [Basics.AbsolutePath]? = .none, + brewPrefix: Basics.AbsolutePath? = .none, + sysrootDir: Basics.AbsolutePath? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { @@ -63,16 +65,16 @@ public struct PkgConfig { private init( name: String, - additionalSearchPaths: [AbsolutePath], - brewPrefix: AbsolutePath?, - sysrootDir: AbsolutePath?, + additionalSearchPaths: [Basics.AbsolutePath], + brewPrefix: Basics.AbsolutePath?, + sysrootDir: Basics.AbsolutePath?, loadingContext: LoadingContext, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { loadingContext.pkgConfigStack.append(name) - if let path = try? AbsolutePath(validating: name) { + if let path = try? Basics.AbsolutePath(validating: name) { guard fileSystem.isFile(path) else { throw PkgConfigError.couldNotFindConfigFile(name: name) } self.name = path.basenameWithoutExt self.pcFile = path @@ -127,7 +129,7 @@ public struct PkgConfig { loadingContext.pkgConfigStack.removeLast(); } - private static var envSearchPaths: [AbsolutePath] { + private static var envSearchPaths: [Basics.AbsolutePath] { get throws { if let configPath = Environment.current["PKG_CONFIG_PATH"] { #if os(Windows) @@ -158,7 +160,7 @@ extension PkgConfig { /// See: https://www.freedesktop.org/wiki/Software/pkg-config/ // This is only internal so it can be unit tested. internal struct PkgConfigParser { - public let pcFile: AbsolutePath + public let pcFile: Basics.AbsolutePath private let fileSystem: FileSystem public private(set) var variables = [String: String]() public private(set) var dependencies = [String]() @@ -167,7 +169,7 @@ internal struct PkgConfigParser { public private(set) var libs = [String]() public private(set) var sysrootDir: String? - public init(pcFile: AbsolutePath, fileSystem: FileSystem, sysrootDir: String?) throws { + public init(pcFile: Basics.AbsolutePath, fileSystem: FileSystem, sysrootDir: String?) throws { guard fileSystem.isFile(pcFile) else { throw StringError("invalid pcfile \(pcFile)") } @@ -268,7 +270,7 @@ internal struct PkgConfigParser { // Add pc_sysrootdir variable. This is the path of the sysroot directory for pc files. // pkgconf does not define pc_sysrootdir if the path of the .pc file is outside sysrootdir. // SwiftPM does not currently make that check. - variables["pc_sysrootdir"] = sysrootDir ?? AbsolutePath.root.pathString + variables["pc_sysrootdir"] = sysrootDir ?? Basics.AbsolutePath.root.pathString let fileContents: String = try fileSystem.readFileContents(pcFile) for line in fileContents.components(separatedBy: "\n") { @@ -461,7 +463,7 @@ internal struct PCFileFinder { /// FIXME: This shouldn't use a static variable, since the first lookup /// will cache the result of whatever `brewPrefix` was passed in. It is /// also not threadsafe. - public private(set) static var pkgConfigPaths: [AbsolutePath]? // FIXME: @testable(internal) + public private(set) static var pkgConfigPaths: [Basics.AbsolutePath]? // FIXME: @testable(internal) private static var shouldEmitPkgConfigPathsDiagnostic = false /// The built-in search path list. @@ -469,7 +471,7 @@ internal struct PCFileFinder { /// By default, this is combined with the search paths inferred from /// `pkg-config` itself. static let searchPaths = [ - try? AbsolutePath(validating: "/usr/local/lib/pkgconfig"), + try? Basics.AbsolutePath(validating: "/usr/local/lib/pkgconfig"), try? AbsolutePath(validating: "/usr/local/share/pkgconfig"), try? AbsolutePath(validating: "/usr/lib/pkgconfig"), try? AbsolutePath(validating: "/usr/share/pkgconfig"), @@ -498,11 +500,11 @@ internal struct PCFileFinder { } } - public init(brewPrefix: AbsolutePath?) { + public init(brewPrefix: Basics.AbsolutePath?) { self.init(pkgConfigPath: brewPrefix?.appending(components: "bin", "pkg-config").pathString ?? "pkg-config") } - public init(pkgConfig: AbsolutePath? = .none) { + public init(pkgConfig: Basics.AbsolutePath? = .none) { self.init(pkgConfigPath: pkgConfig?.pathString ?? "pkg-config") } @@ -516,10 +518,10 @@ internal struct PCFileFinder { public func locatePCFile( name: String, - customSearchPaths: [AbsolutePath], + customSearchPaths: [Basics.AbsolutePath], fileSystem: FileSystem, observabilityScope: ObservabilityScope - ) throws -> AbsolutePath { + ) throws -> Basics.AbsolutePath { // FIXME: We should consider building a registry for all items in the // search paths, which is likely to be substantially more efficient if // we end up searching for a reasonably sized number of packages. diff --git a/Sources/PackageLoading/Platform.swift b/Sources/PackageLoading/Platform.swift index b98a3848cf4..cb7ddeecca8 100644 --- a/Sources/PackageLoading/Platform.swift +++ b/Sources/PackageLoading/Platform.swift @@ -12,12 +12,13 @@ import Basics import Foundation +import TSCBasic import class Basics.AsyncProcess private func isAndroid() -> Bool { - (try? localFileSystem.isFile(AbsolutePath(validating: "/system/bin/toolchain"))) ?? false || - (try? localFileSystem.isFile(AbsolutePath(validating: "/system/bin/toybox"))) ?? false + (try? Basics.localFileSystem.isFile(AbsolutePath(validating: "/system/bin/toolchain"))) ?? false || + (try? Basics.localFileSystem.isFile(AbsolutePath(validating: "/system/bin/toybox"))) ?? false } public enum Platform: Equatable, Sendable { @@ -48,7 +49,7 @@ extension Platform { case "freebsd"?: return .freebsd case "linux"?: - return Platform.findCurrentPlatformLinux(localFileSystem) + return Platform.findCurrentPlatformLinux(Basics.localFileSystem) default: return nil } diff --git a/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift b/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift index cf60cd9a211..31dd959ea30 100644 --- a/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift +++ b/Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift @@ -13,6 +13,7 @@ import Basics import Foundation import PackageModel +import TSCBasic public enum RegistryReleaseMetadataStorage { public static let fileName = ".registry-metadata" @@ -20,13 +21,13 @@ public enum RegistryReleaseMetadataStorage { private static let encoder = JSONEncoder.makeWithDefaults() private static let decoder = JSONDecoder.makeWithDefaults() - public static func save(_ metadata: RegistryReleaseMetadata, to path: AbsolutePath, fileSystem: FileSystem) throws { + public static func save(_ metadata: RegistryReleaseMetadata, to path: Basics.AbsolutePath, fileSystem: FileSystem) throws { let codableMetadata = CodableRegistryReleaseMetadata(metadata) let data = try Self.encoder.encode(codableMetadata) try fileSystem.writeFileContents(path, data: data) } - public static func load(from path: AbsolutePath, fileSystem: FileSystem) throws -> RegistryReleaseMetadata { + public static func load(from path: Basics.AbsolutePath, fileSystem: FileSystem) throws -> RegistryReleaseMetadata { let codableMetadata = try Self.decoder.decode( path: path, fileSystem: fileSystem, diff --git a/Sources/PackageLoading/Target+PkgConfig.swift b/Sources/PackageLoading/Target+PkgConfig.swift index 8ed3d9db588..49b6fc30be7 100644 --- a/Sources/PackageLoading/Target+PkgConfig.swift +++ b/Sources/PackageLoading/Target+PkgConfig.swift @@ -12,6 +12,7 @@ import Basics import PackageModel +import Foundation import class Basics.AsyncProcess import struct TSCBasic.RegEx diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index 0d22e06101e..17f36fb2cbe 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -13,6 +13,7 @@ import Basics import Foundation import PackageModel +import TSCBasic /// A utility to compute the source/resource files of a target. public struct TargetSourcesBuilder { @@ -23,19 +24,19 @@ public struct TargetSourcesBuilder { public let packageKind: PackageReference.Kind /// The package path. - public let packagePath: AbsolutePath + public let packagePath: Basics.AbsolutePath /// The target for which we're computing source/resource files. public let target: TargetDescription /// The path of the target. - public let targetPath: AbsolutePath + public let targetPath: Basics.AbsolutePath /// The list of declared sources in the package manifest. - public let declaredSources: [AbsolutePath]? + public let declaredSources: [Basics.AbsolutePath]? /// The list of declared resources in the package manifest. - public let declaredResources: [(path: AbsolutePath, rule: TargetDescription.Resource.Rule)] + public let declaredResources: [(path: Basics.AbsolutePath, rule: TargetDescription.Resource.Rule)] /// The default localization. public let defaultLocalization: String? @@ -47,7 +48,7 @@ public struct TargetSourcesBuilder { public let toolsVersion: ToolsVersion /// The set of paths that should be excluded from any consideration. - public let excludedPaths: Set + public let excludedPaths: Set /// The set of opaque directories extensions (should not be treated as source) private let opaqueDirectoriesExtensions: Set @@ -62,9 +63,9 @@ public struct TargetSourcesBuilder { public init( packageIdentity: PackageIdentity, packageKind: PackageReference.Kind, - packagePath: AbsolutePath, + packagePath: Basics.AbsolutePath, target: TargetDescription, - path: AbsolutePath, + path: Basics.AbsolutePath, defaultLocalization: String?, additionalFileRules: [FileRuleDescription], toolsVersion: ToolsVersion, @@ -136,7 +137,7 @@ public struct TargetSourcesBuilder { } @discardableResult - private func validTargetPath(at: AbsolutePath) -> Error? { + private func validTargetPath(at: Basics.AbsolutePath) -> Error? { // Check if paths that are enumerated in targets: [] exist guard self.fileSystem.exists(at) else { return StringError("File not found") @@ -165,11 +166,11 @@ public struct TargetSourcesBuilder { } /// Run the builder to produce the sources of the target. - public func run() throws -> (sources: Sources, resources: [Resource], headers: [AbsolutePath], ignored: [AbsolutePath], others: [AbsolutePath]) { + public func run() throws -> (sources: Sources, resources: [Resource], headers: [Basics.AbsolutePath], ignored: [Basics.AbsolutePath], others: [Basics.AbsolutePath]) { let contents = self.computeContents() - var pathToRule: [AbsolutePath: FileRuleDescription.Rule] = [:] + var pathToRule: [Basics.AbsolutePath: FileRuleDescription.Rule] = [:] - var handledResources = [AbsolutePath]() + var handledResources = [Basics.AbsolutePath]() for path in contents { pathToRule[path] = Self.computeRule( for: path, @@ -222,7 +223,7 @@ public struct TargetSourcesBuilder { } /// Compute the rule for the given path. - private static func computeRule(for path: AbsolutePath, + private static func computeRule(for path: Basics.AbsolutePath, toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], observabilityScope: ObservabilityScope) -> FileRuleDescription.Rule { @@ -232,12 +233,12 @@ public struct TargetSourcesBuilder { } private static func computeRule( - for path: AbsolutePath, + for path: Basics.AbsolutePath, toolsVersion: ToolsVersion, rules: [FileRuleDescription], - declaredResources: [(path: AbsolutePath, rule: TargetDescription.Resource.Rule)], - declaredSources: [AbsolutePath]?, - matchingResourceRuleHandler: (AbsolutePath) -> () = { _ in }, + declaredResources: [(path: Basics.AbsolutePath, rule: TargetDescription.Resource.Rule)], + declaredSources: [Basics.AbsolutePath]?, + matchingResourceRuleHandler: (Basics.AbsolutePath) -> () = { _ in }, observabilityScope: ObservabilityScope ) -> FileRuleDescription.Rule { var matchedRule: FileRuleDescription.Rule = .none @@ -303,7 +304,7 @@ public struct TargetSourcesBuilder { } /// Returns the `Resource` file associated with a file and a particular rule, if there is one. - private static func resource(for path: AbsolutePath, with rule: FileRuleDescription.Rule, defaultLocalization: String?, targetName: String, targetPath: AbsolutePath, observabilityScope: ObservabilityScope) -> Resource? { + private static func resource(for path: Basics.AbsolutePath, with rule: FileRuleDescription.Rule, defaultLocalization: String?, targetName: String, targetPath: Basics.AbsolutePath, observabilityScope: ObservabilityScope) -> Resource? { switch rule { case .compile, .header, .none, .modulemap, .ignored: return nil @@ -342,7 +343,7 @@ public struct TargetSourcesBuilder { } } - private func resource(for path: AbsolutePath, with rule: FileRuleDescription.Rule) -> Resource? { + private func resource(for path: Basics.AbsolutePath, with rule: FileRuleDescription.Rule) -> Resource? { return Self.resource(for: path, with: rule, defaultLocalization: defaultLocalization, targetName: target.name, targetPath: targetPath, observabilityScope: observabilityScope) } @@ -406,7 +407,7 @@ public struct TargetSourcesBuilder { } /// Returns true if the given path is a declared source. - func isDeclaredSource(_ path: AbsolutePath) -> Bool { + func isDeclaredSource(_ path: Basics.AbsolutePath) -> Bool { return path == targetPath || declaredSources?.contains(path) == true } @@ -414,9 +415,9 @@ public struct TargetSourcesBuilder { /// /// This avoids recursing into certain directories like exclude or the /// ones that should be copied as-is. - public func computeContents() -> [AbsolutePath] { - var contents: [AbsolutePath] = [] - var queue: [AbsolutePath] = [targetPath] + public func computeContents() -> [Basics.AbsolutePath] { + var contents: [Basics.AbsolutePath] = [] + var queue: [Basics.AbsolutePath] = [targetPath] // Ignore xcodeproj and playground directories. var ignoredDirectoryExtensions = ["xcodeproj", "playground", "xcworkspace"] @@ -518,8 +519,8 @@ public struct TargetSourcesBuilder { return contents } - public static func computeContents(for generatedFiles: [AbsolutePath], toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], defaultLocalization: String?, targetName: String, targetPath: AbsolutePath, observabilityScope: ObservabilityScope) -> (sources: [AbsolutePath], resources: [Resource]) { - var sources = [AbsolutePath]() + public static func computeContents(for generatedFiles: [Basics.AbsolutePath], toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], defaultLocalization: String?, targetName: String, targetPath: Basics.AbsolutePath, observabilityScope: ObservabilityScope) -> (sources: [Basics.AbsolutePath], resources: [Resource]) { + var sources = [Basics.AbsolutePath]() var resources = [Resource]() generatedFiles.forEach { absPath in @@ -613,7 +614,7 @@ public struct FileRuleDescription: Sendable { } /// Match the given path to the rule. - public func match(path: AbsolutePath, toolsVersion: ToolsVersion) -> Bool { + public func match(path: Basics.AbsolutePath, toolsVersion: ToolsVersion) -> Bool { if toolsVersion < self.toolsVersion { return false } @@ -800,12 +801,12 @@ extension Resource { } extension Basics.Diagnostic { - static func symlinkInSources(symlink: RelativePath, targetName: String) -> Self { + static func symlinkInSources(symlink: Basics.RelativePath, targetName: String) -> Self { .warning("ignoring symlink at '\(symlink)' in target '\(targetName)'") } static func localizationDirectoryContainsSubDirectories( - localizationDirectory: RelativePath, + localizationDirectory: Basics.RelativePath, targetName: String ) -> Self { .error("localization directory '\(localizationDirectory)' in target '\(targetName)' contains sub-directories, which is forbidden") @@ -839,7 +840,7 @@ extension PackageReference.Kind { } extension PackageModel.Resource { - fileprivate var destinationForGrouping: RelativePath? { + fileprivate var destinationForGrouping: Basics.RelativePath? { do { return try self.destination } catch { diff --git a/Sources/PackageModel/ArtifactsArchiveMetadata.swift b/Sources/PackageModel/ArtifactsArchiveMetadata.swift index b76333ae4a0..1c07c2ed035 100644 --- a/Sources/PackageModel/ArtifactsArchiveMetadata.swift +++ b/Sources/PackageModel/ArtifactsArchiveMetadata.swift @@ -12,6 +12,7 @@ import Basics import Foundation +import TSCBasic import struct TSCUtility.Version @@ -51,10 +52,10 @@ public struct ArtifactsArchiveMetadata: Equatable { } public struct Variant: Equatable { - public let path: RelativePath + public let path: Basics.RelativePath public let supportedTriples: [Triple]? - public init(path: RelativePath, supportedTriples: [Triple]?) { + public init(path: Basics.RelativePath, supportedTriples: [Triple]?) { self.path = path self.supportedTriples = supportedTriples } @@ -62,7 +63,7 @@ public struct ArtifactsArchiveMetadata: Equatable { } extension ArtifactsArchiveMetadata { - public static func parse(fileSystem: FileSystem, rootPath: AbsolutePath) throws -> ArtifactsArchiveMetadata { + public static func parse(fileSystem: FileSystem, rootPath: Basics.AbsolutePath) throws -> ArtifactsArchiveMetadata { let path = rootPath.appending("info.json") guard fileSystem.exists(path) else { throw StringError("ArtifactsArchive info.json not found at '\(rootPath)'") diff --git a/Sources/PackageModel/MinimumDeploymentTarget.swift b/Sources/PackageModel/MinimumDeploymentTarget.swift index 77f507f0345..d2510e323ce 100644 --- a/Sources/PackageModel/MinimumDeploymentTarget.swift +++ b/Sources/PackageModel/MinimumDeploymentTarget.swift @@ -11,12 +11,14 @@ //===----------------------------------------------------------------------===// import Basics +import Foundation +import TSCUtility import class Basics.AsyncProcess public struct MinimumDeploymentTarget { private struct MinimumDeploymentTargetKey: Hashable { - let binaryPath: AbsolutePath + let binaryPath: Basics.AbsolutePath let platform: PackageModel.Platform } @@ -28,7 +30,7 @@ public struct MinimumDeploymentTarget { private init() { } - public func computeMinimumDeploymentTarget(of binaryPath: AbsolutePath, platform: PackageModel.Platform) throws -> PlatformVersion { + public func computeMinimumDeploymentTarget(of binaryPath: Basics.AbsolutePath, platform: PackageModel.Platform) throws -> PlatformVersion { try self.minimumDeploymentTargets.memoize(MinimumDeploymentTargetKey(binaryPath: binaryPath, platform: platform)) { return try Self.computeMinimumDeploymentTarget(of: binaryPath, platform: platform) ?? platform.oldestSupportedVersion } @@ -40,7 +42,7 @@ public struct MinimumDeploymentTarget { } } - static func computeMinimumDeploymentTarget(of binaryPath: AbsolutePath, platform: PackageModel.Platform) throws -> PlatformVersion? { + static func computeMinimumDeploymentTarget(of binaryPath: Basics.AbsolutePath, platform: PackageModel.Platform) throws -> PlatformVersion? { guard let (_, platformName) = platform.sdkNameAndPlatform else { return nil } @@ -58,8 +60,8 @@ public struct MinimumDeploymentTarget { static func computeXCTestMinimumDeploymentTarget(with runResult: AsyncProcessResult, platform: PackageModel.Platform) throws -> PlatformVersion? { guard let output = try runResult.utf8Output().spm_chuzzle() else { return nil } - let sdkPath = try AbsolutePath(validating: output) - let xcTestPath = try AbsolutePath(validating: "Developer/Library/Frameworks/XCTest.framework/XCTest", relativeTo: sdkPath) + let sdkPath = try Basics.AbsolutePath(validating: output) + let xcTestPath = try Basics.AbsolutePath(validating: "Developer/Library/Frameworks/XCTest.framework/XCTest", relativeTo: sdkPath) return try computeMinimumDeploymentTarget(of: xcTestPath, platform: platform) } diff --git a/Sources/PackageModel/Module/ClangModule.swift b/Sources/PackageModel/Module/ClangModule.swift index f1506aeb15e..bf066a6ecea 100644 --- a/Sources/PackageModel/Module/ClangModule.swift +++ b/Sources/PackageModel/Module/ClangModule.swift @@ -12,6 +12,7 @@ import struct Basics.AbsolutePath import struct Basics.StringError +import TSCBasic @available(*, deprecated, renamed: "ClangModule") public typealias ClangTarget = ClangModule @@ -24,7 +25,7 @@ public final class ClangModule: Module { public static let defaultPublicHeadersComponent = "include" /// The path to include directory. - public let includeDir: AbsolutePath + public let includeDir: Basics.AbsolutePath /// The target's module map type, which determines whether this target vends a custom module map, a generated module map, or no module map at all. public let moduleMapType: ModuleMapType @@ -32,7 +33,7 @@ public final class ClangModule: Module { /// The headers present in the target. /// /// Note that this contains both public and non-public headers. - public let headers: [AbsolutePath] + public let headers: [Basics.AbsolutePath] /// True if this is a C++ target. public let isCXX: Bool @@ -48,15 +49,15 @@ public final class ClangModule: Module { potentialBundleName: String? = nil, cLanguageStandard: String?, cxxLanguageStandard: String?, - includeDir: AbsolutePath, + includeDir: Basics.AbsolutePath, moduleMapType: ModuleMapType, - headers: [AbsolutePath] = [], + headers: [Basics.AbsolutePath] = [], type: Kind, - path: AbsolutePath, + path: Basics.AbsolutePath, sources: Sources, resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], + ignored: [Basics.AbsolutePath] = [], + others: [Basics.AbsolutePath] = [], dependencies: [Module.Dependency] = [], buildSettings: BuildSettings.AssignmentTable = .init(), buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [], diff --git a/Sources/PackageModel/Module/Module.swift b/Sources/PackageModel/Module/Module.swift index 023033344f2..786e99de9f3 100644 --- a/Sources/PackageModel/Module/Module.swift +++ b/Sources/PackageModel/Module/Module.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import TSCUtility @available(*, deprecated, renamed: "Module") public typealias Target = Module diff --git a/Sources/PackageModel/Module/PluginModule.swift b/Sources/PackageModel/Module/PluginModule.swift index 374d7be036b..94c5d8eed1c 100644 --- a/Sources/PackageModel/Module/PluginModule.swift +++ b/Sources/PackageModel/Module/PluginModule.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Basics + @available(*, deprecated, renamed: "PluginModule") public typealias PluginTarget = PluginModule diff --git a/Sources/PackageModel/PackageIdentity.swift b/Sources/PackageModel/PackageIdentity.swift index eee8b9538a4..bbc1b248694 100644 --- a/Sources/PackageModel/PackageIdentity.swift +++ b/Sources/PackageModel/PackageIdentity.swift @@ -12,6 +12,7 @@ import Basics import Foundation +import TSCBasic /// The canonical identifier for a package, based on its source location. public struct PackageIdentity: CustomStringConvertible, Sendable { @@ -39,7 +40,7 @@ public struct PackageIdentity: CustomStringConvertible, Sendable { /// Creates a package identity from a file path. /// - Parameter path: An absolute path to the package. - public init(path: AbsolutePath) { + public init(path: Basics.AbsolutePath) { self.description = PackageIdentityParser(path.pathString).description } @@ -310,7 +311,7 @@ struct PackageIdentityParser { } /// Compute the default name of a package given its path. - public static func computeDefaultName(fromPath path: AbsolutePath) -> String { + public static func computeDefaultName(fromPath path: Basics.AbsolutePath) -> String { Self.computeDefaultName(fromLocation: path.pathString) } diff --git a/Sources/PackageModel/Snippets/Model/Snippet.swift b/Sources/PackageModel/Snippets/Model/Snippet.swift index d44e8753d31..a74e3d92650 100644 --- a/Sources/PackageModel/Snippets/Model/Snippet.swift +++ b/Sources/PackageModel/Snippets/Model/Snippet.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import Foundation public struct Snippet { public var path: AbsolutePath diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index 066d29c1147..aacfad57428 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -12,6 +12,7 @@ import Basics import Foundation +import TSCBasic import class Basics.AsyncProcess @@ -20,7 +21,7 @@ import struct TSCUtility.Version /// Errors related to Swift SDKs. public enum SwiftSDKError: Swift.Error { /// A bundle archive should contain at least one directory with the `.artifactbundle` extension. - case invalidBundleArchive(AbsolutePath) + case invalidBundleArchive(Basics.AbsolutePath) /// A passed argument is neither a valid file system path nor a URL. case invalidPathOrURL(String) @@ -41,10 +42,10 @@ public enum SwiftSDKError: Swift.Error { case invalidBundleName(String) /// No valid Swift SDKs were decoded from a metadata file. - case noSwiftSDKDecoded(AbsolutePath) + case noSwiftSDKDecoded(Basics.AbsolutePath) /// Path used for storing Swift SDK configuration data is not a directory. - case pathIsNotDirectory(AbsolutePath) + case pathIsNotDirectory(Basics.AbsolutePath) /// Swift SDK metadata couldn't be serialized with the latest serialization schema, potentially because it /// was deserialized from an earlier incompatible schema version or initialized manually with properties @@ -63,7 +64,7 @@ public enum SwiftSDKError: Swift.Error { #if os(macOS) /// Quarantine attribute should be removed by the `xattr` command from an installed bundle. - case quarantineAttributePresent(bundlePath: AbsolutePath) + case quarantineAttributePresent(bundlePath: Basics.AbsolutePath) #endif } @@ -191,7 +192,7 @@ public struct SwiftSDK: Equatable { /// Root directory path of the SDK used to compile for the target triple. @available(*, deprecated, message: "use `pathsConfiguration.sdkRootPath` instead") - public var sdk: AbsolutePath? { + public var sdk: Basics.AbsolutePath? { get { sdkRootDir } @@ -202,7 +203,7 @@ public struct SwiftSDK: Equatable { /// Root directory path of the SDK used to compile for the target triple. @available(*, deprecated, message: "use `pathsConfiguration.sdkRootPath` instead") - public var sdkRootDir: AbsolutePath? { + public var sdkRootDir: Basics.AbsolutePath? { get { pathsConfiguration.sdkRootPath } @@ -213,13 +214,13 @@ public struct SwiftSDK: Equatable { /// Path to a directory containing the toolchain (compilers/linker) to be used for the compilation. @available(*, deprecated, message: "use `toolset.rootPaths` instead") - public var binDir: AbsolutePath { + public var binDir: Basics.AbsolutePath { toolchainBinDir } /// Path to a directory containing the toolchain (compilers/linker) to be used for the compilation. @available(*, deprecated, message: "use `toolset.rootPaths` instead") - public var toolchainBinDir: AbsolutePath { + public var toolchainBinDir: Basics.AbsolutePath { toolset.rootPaths[0] } @@ -260,12 +261,12 @@ public struct SwiftSDK: Equatable { public struct PathsConfiguration: Equatable { public init( - sdkRootPath: AbsolutePath?, - swiftResourcesPath: AbsolutePath? = nil, - swiftStaticResourcesPath: AbsolutePath? = nil, - includeSearchPaths: [AbsolutePath]? = nil, - librarySearchPaths: [AbsolutePath]? = nil, - toolsetPaths: [AbsolutePath]? = nil + sdkRootPath: Basics.AbsolutePath?, + swiftResourcesPath: Basics.AbsolutePath? = nil, + swiftStaticResourcesPath: Basics.AbsolutePath? = nil, + includeSearchPaths: [Basics.AbsolutePath]? = nil, + librarySearchPaths: [Basics.AbsolutePath]? = nil, + toolsetPaths: [Basics.AbsolutePath]? = nil ) { self.sdkRootPath = sdkRootPath self.swiftResourcesPath = swiftResourcesPath @@ -276,22 +277,22 @@ public struct SwiftSDK: Equatable { } /// Root directory path of the SDK used to compile for the target triple. - public var sdkRootPath: AbsolutePath? + public var sdkRootPath: Basics.AbsolutePath? /// Path containing Swift resources for dynamic linking. - public var swiftResourcesPath: AbsolutePath? + public var swiftResourcesPath: Basics.AbsolutePath? /// Path containing Swift resources for static linking. - public var swiftStaticResourcesPath: AbsolutePath? + public var swiftStaticResourcesPath: Basics.AbsolutePath? /// Array of paths containing headers. - public var includeSearchPaths: [AbsolutePath]? + public var includeSearchPaths: [Basics.AbsolutePath]? /// Array of paths containing libraries. - public var librarySearchPaths: [AbsolutePath]? + public var librarySearchPaths: [Basics.AbsolutePath]? /// Array of paths containing toolset files. - public var toolsetPaths: [AbsolutePath]? + public var toolsetPaths: [Basics.AbsolutePath]? /// Initialize paths configuration from values deserialized using v3 schema. /// - Parameters: @@ -299,7 +300,7 @@ public struct SwiftSDK: Equatable { /// - swiftSDKDirectory: directory used for converting relative paths in `properties` to absolute paths. fileprivate init( _ properties: SerializedDestinationV3.TripleProperties, - swiftSDKDirectory: AbsolutePath? = nil + swiftSDKDirectory: Basics.AbsolutePath? = nil ) throws { if let swiftSDKDirectory { self.init( @@ -346,7 +347,7 @@ public struct SwiftSDK: Equatable { /// - Parameters: /// - properties: properties of a Swift SDK for the given triple. /// - swiftSDKDirectory: directory used for converting relative paths in `properties` to absolute paths. - fileprivate init(_ properties: SwiftSDKMetadataV4.TripleProperties, swiftSDKDirectory: AbsolutePath? = nil) throws { + fileprivate init(_ properties: SwiftSDKMetadataV4.TripleProperties, swiftSDKDirectory: Basics.AbsolutePath? = nil) throws { if let swiftSDKDirectory { self.init( sdkRootPath: try AbsolutePath(validating: properties.sdkRootPath, relativeTo: swiftSDKDirectory), @@ -422,8 +423,8 @@ public struct SwiftSDK: Equatable { @available(*, deprecated, message: "use `init(targetTriple:sdkRootDir:toolset:)` instead") public init( target: Triple? = nil, - sdk: AbsolutePath?, - binDir: AbsolutePath, + sdk: Basics.AbsolutePath?, + binDir: Basics.AbsolutePath, extraCCFlags: [String] = [], extraSwiftCFlags: [String] = [], extraCPPFlags: [String] = [] @@ -445,8 +446,8 @@ public struct SwiftSDK: Equatable { public init( hostTriple: Triple? = nil, targetTriple: Triple? = nil, - sdkRootDir: AbsolutePath?, - toolchainBinDir: AbsolutePath, + sdkRootDir: Basics.AbsolutePath?, + toolchainBinDir: Basics.AbsolutePath, extraFlags: BuildFlags = BuildFlags() ) { self.init( @@ -501,7 +502,7 @@ public struct SwiftSDK: Equatable { /// Returns the bin directory for the host. private static func hostBinDir( fileSystem: FileSystem - ) throws -> AbsolutePath { + ) throws -> Basics.AbsolutePath { guard let cwd = fileSystem.currentWorkingDirectory else { return try AbsolutePath(validating: CommandLine.arguments[0]).parentDirectory } @@ -511,8 +512,8 @@ public struct SwiftSDK: Equatable { /// The Swift SDK describing the host platform. @available(*, deprecated, renamed: "hostSwiftSDK") public static func hostDestination( - _ binDir: AbsolutePath? = nil, - originalWorkingDirectory: AbsolutePath? = nil, + _ binDir: Basics.AbsolutePath? = nil, + originalWorkingDirectory: Basics.AbsolutePath? = nil, environment: Environment ) throws -> SwiftSDK { try self.hostSwiftSDK(binDir, environment: environment) @@ -520,10 +521,10 @@ public struct SwiftSDK: Equatable { /// The Swift SDK for the host platform. public static func hostSwiftSDK( - _ binDir: AbsolutePath? = nil, + _ binDir: Basics.AbsolutePath? = nil, environment: Environment = .current, observabilityScope: ObservabilityScope? = nil, - fileSystem: any FileSystem = localFileSystem + fileSystem: any FileSystem = Basics.localFileSystem ) throws -> SwiftSDK { try self.systemSwiftSDK( binDir, @@ -538,10 +539,10 @@ public struct SwiftSDK: Equatable { /// Equivalent to `hostSwiftSDK`, except on macOS, where passing a non-nil `darwinPlatformOverride` /// will result in the SDK for the corresponding Darwin platform. private static func systemSwiftSDK( - _ binDir: AbsolutePath? = nil, + _ binDir: Basics.AbsolutePath? = nil, environment: Environment = .current, observabilityScope: ObservabilityScope? = nil, - fileSystem: any FileSystem = localFileSystem, + fileSystem: any FileSystem = Basics.localFileSystem, darwinPlatformOverride: DarwinPlatform? = nil ) throws -> SwiftSDK { // Select the correct binDir. @@ -549,10 +550,10 @@ public struct SwiftSDK: Equatable { print("SWIFTPM_CUSTOM_BINDIR was deprecated in favor of SWIFTPM_CUSTOM_BIN_DIR") } let customBinDir = (environment["SWIFTPM_CUSTOM_BIN_DIR"] ?? environment["SWIFTPM_CUSTOM_BINDIR"]) - .flatMap { try? AbsolutePath(validating: $0) } + .flatMap { try? Basics.AbsolutePath(validating: $0) } let binDir = try customBinDir ?? binDir ?? SwiftSDK.hostBinDir(fileSystem: fileSystem) - let sdkPath: AbsolutePath? + let sdkPath: Basics.AbsolutePath? #if os(macOS) let darwinPlatform = darwinPlatformOverride ?? .macOS // Get the SDK. @@ -618,17 +619,17 @@ public struct SwiftSDK: Equatable { /// - SeeAlso: ``sdkPlatformPaths(for:environment:)`` public struct PlatformPaths { /// Paths of directories containing auxiliary platform frameworks. - public var frameworks: [AbsolutePath] + public var frameworks: [Basics.AbsolutePath] /// Paths of directories containing auxiliary platform libraries. - public var libraries: [AbsolutePath] + public var libraries: [Basics.AbsolutePath] } /// Returns `macosx` sdk platform framework path. @available(*, deprecated, message: "use sdkPlatformPaths(for:) instead") public static func sdkPlatformFrameworkPaths( environment: Environment = .current - ) throws -> (fwk: AbsolutePath, lib: AbsolutePath) { + ) throws -> (fwk: Basics.AbsolutePath, lib: Basics.AbsolutePath) { let paths = try sdkPlatformPaths(for: .macOS, environment: environment) guard let frameworkPath = paths.frameworks.first else { throw StringError("could not determine SDK platform framework path") @@ -659,15 +660,15 @@ public struct SwiftSDK: Equatable { } // For testing frameworks. - let frameworksPath = try AbsolutePath(validating: platformPath).appending( + let frameworksPath = try Basics.AbsolutePath(validating: platformPath).appending( components: "Developer", "Library", "Frameworks" ) - let privateFrameworksPath = try AbsolutePath(validating: platformPath).appending( + let privateFrameworksPath = try Basics.AbsolutePath(validating: platformPath).appending( components: "Developer", "Library", "PrivateFrameworks" ) // For testing libraries. - let librariesPath = try AbsolutePath(validating: platformPath).appending( + let librariesPath = try Basics.AbsolutePath(validating: platformPath).appending( components: "Developer", "usr", "lib" ) @@ -711,11 +712,11 @@ public struct SwiftSDK: Equatable { public static func deriveTargetSwiftSDK( hostSwiftSDK: SwiftSDK, hostTriple: Triple, - customToolsets: [AbsolutePath] = [], - customCompileDestination: AbsolutePath? = nil, + customToolsets: [Basics.AbsolutePath] = [], + customCompileDestination: Basics.AbsolutePath? = nil, customCompileTriple: Triple? = nil, - customCompileToolchain: AbsolutePath? = nil, - customCompileSDK: AbsolutePath? = nil, + customCompileToolchain: Basics.AbsolutePath? = nil, + customCompileSDK: Basics.AbsolutePath? = nil, swiftSDKSelector: String? = nil, architectures: [String] = [], store: SwiftSDKBundleStore, @@ -833,7 +834,7 @@ public struct SwiftSDK: Equatable { /// Note: Use this operation if you want new root path to take priority over existing paths. /// /// - Parameter toolsetRootPath: new path to add to Swift SDK's toolset. - public mutating func prepend(toolsetRootPath path: AbsolutePath) { + public mutating func prepend(toolsetRootPath path: Basics.AbsolutePath) { self.toolset.rootPaths.insert(path, at: 0) } @@ -843,7 +844,7 @@ public struct SwiftSDK: Equatable { /// have a lower priority vs. existing paths. /// /// - Parameter toolsetRootPath: new path to add to Swift SDK's toolset. - public mutating func append(toolsetRootPath: AbsolutePath) { + public mutating func append(toolsetRootPath: Basics.AbsolutePath) { self.toolset.rootPaths.append(toolsetRootPath) } } @@ -851,7 +852,7 @@ public struct SwiftSDK: Equatable { extension SwiftSDK { /// Load a ``SwiftSDK`` description from a JSON representation from disk. public static func decode( - fromFile path: AbsolutePath, + fromFile path: Basics.AbsolutePath, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> [SwiftSDK] { @@ -874,7 +875,7 @@ extension SwiftSDK { /// Load a ``SwiftSDK`` description from a semantically versioned JSON representation from disk. private static func decode( semanticVersion: SemanticVersionInfo, - fromFile path: AbsolutePath, + fromFile path: Basics.AbsolutePath, fileSystem: FileSystem, decoder: JSONDecoder, observabilityScope: ObservabilityScope @@ -946,7 +947,7 @@ extension SwiftSDK { targetTriple: Triple, properties: SwiftSDKMetadataV4.TripleProperties, toolset: Toolset = .init(), - swiftSDKDirectory: AbsolutePath? = nil + swiftSDKDirectory: Basics.AbsolutePath? = nil ) throws { self.init( targetTriple: targetTriple, @@ -965,7 +966,7 @@ extension SwiftSDK { targetTriple: Triple, properties: SerializedDestinationV3.TripleProperties, toolset: Toolset = .init(), - swiftSDKDirectory: AbsolutePath? = nil + swiftSDKDirectory: Basics.AbsolutePath? = nil ) throws { self.init( targetTriple: targetTriple, @@ -977,7 +978,7 @@ extension SwiftSDK { /// Load a ``SwiftSDK`` description from a legacy JSON representation from disk. private init( legacy version: VersionInfo, - fromFile path: AbsolutePath, + fromFile path: Basics.AbsolutePath, fileSystem: FileSystem, decoder: JSONDecoder ) throws { @@ -1089,8 +1090,8 @@ private struct SemanticVersionInfo: Decodable { /// Represents v1 schema of `destination.json` files used for cross-compilation. private struct SerializedDestinationV1: Codable { let target: String? - let sdk: AbsolutePath? - let binDir: AbsolutePath + let sdk: Basics.AbsolutePath? + let binDir: Basics.AbsolutePath let extraCCFlags: [String] let extraSwiftCFlags: [String] let extraCPPFlags: [String] @@ -1169,13 +1170,13 @@ struct SwiftSDKMetadataV4: Decodable { let targetTriples: [String: TripleProperties] } -extension Optional where Wrapped == AbsolutePath { +extension Optional where Wrapped == Basics.AbsolutePath { fileprivate var configurationString: String { self?.pathString ?? "not set" } } -extension Optional where Wrapped == [AbsolutePath] { +extension Optional where Wrapped == [Basics.AbsolutePath] { fileprivate var configurationString: String { self?.map(\.pathString).description ?? "not set" } diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index 5a38f747171..7b97b2d8c64 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -12,6 +12,7 @@ import Basics import Foundation +import TSCUtility import class Basics.AsyncProcess @@ -69,9 +70,9 @@ public final class UserToolchain: Toolchain { /// The target triple that should be used for compilation. @available(*, deprecated, renamed: "targetTriple") - public var triple: Triple { targetTriple } + public var triple: Basics.Triple { targetTriple } - public let targetTriple: Triple + public let targetTriple: Basics.Triple /// The list of CPU architectures to build for. public let architectures: [String]? @@ -162,7 +163,7 @@ public final class UserToolchain: Toolchain { // MARK: - public API public static func determineLibrarian( - triple: Triple, + triple: Basics.Triple, binDirectories: [AbsolutePath], useXcrun: Bool, environment: Environment, @@ -405,7 +406,7 @@ public final class UserToolchain: Toolchain { #endif internal static func deriveSwiftCFlags( - triple: Triple, + triple: Basics.Triple, swiftSDK: SwiftSDK, environment: Environment, fileSystem: any FileSystem @@ -839,7 +840,7 @@ public final class UserToolchain: Toolchain { return .init(swiftCompilerPath: swiftCompilerPath) } - private static func derivePluginServerPath(triple: Triple) throws -> AbsolutePath? { + private static func derivePluginServerPath(triple: Basics.Triple) throws -> AbsolutePath? { if triple.isDarwin() { let pluginServerPathFindArgs = ["/usr/bin/xcrun", "--find", "swift-plugin-server"] if let path = try? AsyncProcess.checkNonZeroExit(arguments: pluginServerPathFindArgs, environment: [:]) @@ -889,7 +890,7 @@ public final class UserToolchain: Toolchain { // TODO: We should have some general utility to find tools. private static func deriveXCTestPath( swiftSDK: SwiftSDK, - triple: Triple, + triple: Basics.Triple, environment: Environment, fileSystem: any FileSystem ) throws -> AbsolutePath? { @@ -970,9 +971,9 @@ public final class UserToolchain: Toolchain { /// Find the swift-testing path if it is within a path that will need extra search paths. private static func deriveSwiftTestingPath( - derivedSwiftCompiler: AbsolutePath, + derivedSwiftCompiler: Basics.AbsolutePath, swiftSDK: SwiftSDK, - triple: Triple, + triple: Basics.Triple, environment: Environment, fileSystem: any FileSystem ) throws -> AbsolutePath? { diff --git a/Sources/PackageModelSyntax/AddTarget.swift b/Sources/PackageModelSyntax/AddTarget.swift index addb4e4886a..b6a081a7d67 100644 --- a/Sources/PackageModelSyntax/AddTarget.swift +++ b/Sources/PackageModelSyntax/AddTarget.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import Foundation import PackageModel import SwiftParser import SwiftSyntax diff --git a/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift b/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift index 1b40b2d592a..146bc6e3b74 100644 --- a/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift +++ b/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift @@ -12,6 +12,7 @@ import Basics import SwiftSyntax +import SwiftSyntaxBuilder /// Describes an entity in the package model that can be represented as /// a syntax node. diff --git a/Sources/PackageModelSyntax/PackageDependency+Syntax.swift b/Sources/PackageModelSyntax/PackageDependency+Syntax.swift index b8a7445938e..6133b9d7344 100644 --- a/Sources/PackageModelSyntax/PackageDependency+Syntax.swift +++ b/Sources/PackageModelSyntax/PackageDependency+Syntax.swift @@ -14,6 +14,7 @@ import Basics import PackageModel import SwiftParser import SwiftSyntax +import SwiftSyntaxBuilder import struct TSCUtility.Version extension MappablePackageDependency.Kind: ManifestSyntaxRepresentable { diff --git a/Sources/PackageModelSyntax/ProductDescription+Syntax.swift b/Sources/PackageModelSyntax/ProductDescription+Syntax.swift index eed6650dfce..614d5db8bf4 100644 --- a/Sources/PackageModelSyntax/ProductDescription+Syntax.swift +++ b/Sources/PackageModelSyntax/ProductDescription+Syntax.swift @@ -13,6 +13,7 @@ import Basics import PackageModel import SwiftSyntax +import SwiftSyntaxBuilder import SwiftParser extension ProductDescription: ManifestSyntaxRepresentable { diff --git a/Sources/PackageModelSyntax/SyntaxEditUtils.swift b/Sources/PackageModelSyntax/SyntaxEditUtils.swift index df64aa4c2d3..6e3c2a2a518 100644 --- a/Sources/PackageModelSyntax/SyntaxEditUtils.swift +++ b/Sources/PackageModelSyntax/SyntaxEditUtils.swift @@ -15,6 +15,7 @@ import PackageModel import SwiftBasicFormat import SwiftSyntax import SwiftParser +import SwiftSyntaxBuilder /// Default indent when we have to introduce indentation but have no context /// to get it right. diff --git a/Sources/PackageModelSyntax/TargetDescription+Syntax.swift b/Sources/PackageModelSyntax/TargetDescription+Syntax.swift index f47f6590f06..eed59e5fcae 100644 --- a/Sources/PackageModelSyntax/TargetDescription+Syntax.swift +++ b/Sources/PackageModelSyntax/TargetDescription+Syntax.swift @@ -13,9 +13,9 @@ import Basics import PackageModel import SwiftSyntax +import SwiftSyntaxBuilder import SwiftParser - extension TargetDescription: ManifestSyntaxRepresentable { /// The function name in the package manifest. private var functionName: String { diff --git a/Sources/PackagePlugin/Utilities.swift b/Sources/PackagePlugin/Utilities.swift index 9c4e4d0c35d..d84382d8139 100644 --- a/Sources/PackagePlugin/Utilities.swift +++ b/Sources/PackagePlugin/Utilities.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Foundation + extension Package { /// The list of targets matching the given names. Throws an error if any of /// the targets cannot be found. diff --git a/Sources/PackageRegistry/ChecksumTOFU.swift b/Sources/PackageRegistry/ChecksumTOFU.swift index 81eef2d4ec4..137d3326bc9 100644 --- a/Sources/PackageRegistry/ChecksumTOFU.swift +++ b/Sources/PackageRegistry/ChecksumTOFU.swift @@ -16,6 +16,7 @@ import Dispatch import Basics import PackageFingerprint import PackageModel +import Foundation import struct TSCUtility.Version diff --git a/Sources/PackageRegistry/RegistryDownloadsManager.swift b/Sources/PackageRegistry/RegistryDownloadsManager.swift index 307d9c0c0c2..cbe33d03aa3 100644 --- a/Sources/PackageRegistry/RegistryDownloadsManager.swift +++ b/Sources/PackageRegistry/RegistryDownloadsManager.swift @@ -16,6 +16,7 @@ import Dispatch import Foundation import PackageLoading import PackageModel +import TSCBasic import struct TSCUtility.Version @@ -23,8 +24,8 @@ public class RegistryDownloadsManager: AsyncCancellable { public typealias Delegate = RegistryDownloadsManagerDelegate private let fileSystem: FileSystem - private let path: AbsolutePath - private let cachePath: AbsolutePath? + private let path: Basics.AbsolutePath + private let cachePath: Basics.AbsolutePath? private let registryClient: RegistryClient private let delegate: Delegate? @@ -33,13 +34,13 @@ public class RegistryDownloadsManager: AsyncCancellable { let version: Version } - private var pendingLookups = [PackageLookup: Task]() + private var pendingLookups = [PackageLookup: Task]() private var pendingLookupsLock = NSLock() public init( fileSystem: FileSystem, - path: AbsolutePath, - cachePath: AbsolutePath?, + path: Basics.AbsolutePath, + cachePath: Basics.AbsolutePath?, registryClient: RegistryClient, delegate: Delegate? ) { @@ -55,9 +56,9 @@ public class RegistryDownloadsManager: AsyncCancellable { version: Version, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue - ) async throws -> AbsolutePath { - let packageRelativePath: RelativePath - let packagePath: AbsolutePath + ) async throws -> Basics.AbsolutePath { + let packageRelativePath: Basics.RelativePath + let packagePath: Basics.AbsolutePath packageRelativePath = try package.downloadPath(version: version) packagePath = self.path.appending(packageRelativePath) @@ -127,7 +128,7 @@ public class RegistryDownloadsManager: AsyncCancellable { observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { callbackQueue.asyncResult(completion) { try await self.lookup( @@ -147,7 +148,7 @@ public class RegistryDownloadsManager: AsyncCancellable { private func downloadAndPopulateCache( package: PackageIdentity, version: Version, - packagePath: AbsolutePath, + packagePath: Basics.AbsolutePath, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue ) async throws -> FetchDetails { @@ -302,7 +303,7 @@ public class RegistryDownloadsManager: AsyncCancellable { } } - private func initializeCacheIfNeeded(cachePath: AbsolutePath) throws { + private func initializeCacheIfNeeded(cachePath: Basics.AbsolutePath) throws { if !self.fileSystem.exists(cachePath) { try self.fileSystem.createDirectory(cachePath, recursive: true) } @@ -345,7 +346,7 @@ extension RegistryDownloadsManager { } extension FileSystem { - func validPackageDirectory(_ path: AbsolutePath) throws -> Bool { + func validPackageDirectory(_ path: Basics.AbsolutePath) throws -> Bool { if !self.exists(path) { return false } @@ -354,14 +355,14 @@ extension FileSystem { } extension PackageIdentity { - internal func downloadPath() throws -> RelativePath { + internal func downloadPath() throws -> Basics.RelativePath { guard let registryIdentity = self.registry else { throw StringError("invalid package identifier \(self), expected registry scope and name") } return try RelativePath(validating: registryIdentity.scope.description).appending(component: registryIdentity.name.description) } - internal func downloadPath(version: Version) throws -> RelativePath { + internal func downloadPath(version: Version) throws -> Basics.RelativePath { try self.downloadPath().appending(component: version.description) } } diff --git a/Sources/PackageRegistry/SignatureValidation.swift b/Sources/PackageRegistry/SignatureValidation.swift index 8e1631dd53c..bf5169ff138 100644 --- a/Sources/PackageRegistry/SignatureValidation.swift +++ b/Sources/PackageRegistry/SignatureValidation.swift @@ -18,6 +18,7 @@ import Basics import PackageLoading import PackageModel import PackageSigning +import TSCBasic import struct TSCUtility.Version @@ -590,7 +591,7 @@ extension VerifierConfiguration { // Load trusted roots from configured directory if let trustedRootsDirectoryPath = configuration.trustedRootCertificatesPath { - let trustedRootsDirectory: AbsolutePath + let trustedRootsDirectory: Basics.AbsolutePath do { trustedRootsDirectory = try AbsolutePath(validating: trustedRootsDirectoryPath) } catch { diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift index 690253a4ea3..d5658857f10 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift @@ -15,8 +15,11 @@ import Basics import Commands import CoreCommands import Foundation +import PackageFingerprint import PackageModel import PackageRegistry +import PackageSigning +import Workspace import struct TSCBasic.SHA256 diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift index e2f4550ac60..1b1ffc700fb 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift @@ -16,6 +16,7 @@ import Commands import CoreCommands import Foundation import PackageModel +import PackageFingerprint import PackageRegistry import PackageSigning import Workspace diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift index 2861b53e336..0b7cd4b88e7 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift @@ -12,6 +12,7 @@ import ArgumentParser import Basics +import Commands import CoreCommands import Foundation import PackageModel diff --git a/Sources/PackageSigning/CertificateStores.swift b/Sources/PackageSigning/CertificateStores.swift index 48d3bf22e90..7d001f8484f 100644 --- a/Sources/PackageSigning/CertificateStores.swift +++ b/Sources/PackageSigning/CertificateStores.swift @@ -12,8 +12,10 @@ #if USE_IMPL_ONLY_IMPORTS @_implementationOnly import X509 +@_implementationOnly import SwiftASN1 #else import X509 +import SwiftASN1 #endif enum Certificates { diff --git a/Sources/PackageSigning/SigningEntity/FilePackageSigningEntityStorage.swift b/Sources/PackageSigning/SigningEntity/FilePackageSigningEntityStorage.swift index 91eb7287cbe..28fa216ecbc 100644 --- a/Sources/PackageSigning/SigningEntity/FilePackageSigningEntityStorage.swift +++ b/Sources/PackageSigning/SigningEntity/FilePackageSigningEntityStorage.swift @@ -14,17 +14,18 @@ import Basics import Dispatch import Foundation import PackageModel +import TSCBasic import struct TSCUtility.Version public struct FilePackageSigningEntityStorage: PackageSigningEntityStorage { let fileSystem: FileSystem - let directoryPath: AbsolutePath + let directoryPath: Basics.AbsolutePath private let encoder: JSONEncoder private let decoder: JSONDecoder - public init(fileSystem: FileSystem, directoryPath: AbsolutePath) { + public init(fileSystem: FileSystem, directoryPath: Basics.AbsolutePath) { self.fileSystem = fileSystem self.directoryPath = directoryPath diff --git a/Sources/PackageSigning/SigningIdentity.swift b/Sources/PackageSigning/SigningIdentity.swift index 084bc67d70d..ded10b2a483 100644 --- a/Sources/PackageSigning/SigningIdentity.swift +++ b/Sources/PackageSigning/SigningIdentity.swift @@ -27,6 +27,7 @@ import X509 #endif import Basics +import TSCBasic public protocol SigningIdentity {} diff --git a/Sources/PackageSigning/X509Extensions.swift b/Sources/PackageSigning/X509Extensions.swift index 7045a22ac81..c6f40cbb957 100644 --- a/Sources/PackageSigning/X509Extensions.swift +++ b/Sources/PackageSigning/X509Extensions.swift @@ -29,6 +29,7 @@ import X509 #endif import Basics +import TSCBasic #if canImport(Security) extension Certificate { diff --git a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift index 088e7616325..ee91e7ed2b2 100644 --- a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift +++ b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift @@ -14,14 +14,15 @@ import Basics import Foundation import PackageGraph import PackageModel +import TSCBasic /// Information about a library from a binary dependency. public struct LibraryInfo: Equatable { /// The path to the binary. - public let libraryPath: AbsolutePath + public let libraryPath: Basics.AbsolutePath /// The paths to the headers directories. - public let headersPaths: [AbsolutePath] + public let headersPaths: [Basics.AbsolutePath] } /// Information about an executable from a binary dependency. @@ -30,7 +31,7 @@ public struct ExecutableInfo: Equatable { public let name: String /// The path to the executable. - public let executablePath: AbsolutePath + public let executablePath: Basics.AbsolutePath /// Supported triples, e.g. `x86_64-apple-macosx` public let supportedTriples: [Triple] diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index e4650254a14..a7e926d2c2c 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -14,6 +14,7 @@ import Basics import class Foundation.ProcessInfo import PackageModel import PackageGraph +import TSCBasic public struct BuildParameters: Encodable { public enum PrepareForIndexingMode: Encodable { @@ -50,7 +51,7 @@ public struct BuildParameters: Encodable { public var destination: Destination /// The path to the data directory. - public var dataPath: AbsolutePath + public var dataPath: Basics.AbsolutePath /// The build configuration. public var configuration: BuildConfiguration @@ -69,7 +70,7 @@ public struct BuildParameters: Encodable { public var flags: BuildFlags /// An array of paths to search for pkg-config `.pc` files. - public var pkgConfigDirectories: [AbsolutePath] + public var pkgConfigDirectories: [Basics.AbsolutePath] /// The architectures to build for. // FIXME: this may be inconsistent with `targetTriple`. @@ -146,13 +147,13 @@ public struct BuildParameters: Encodable { public init( destination: Destination, - dataPath: AbsolutePath, + dataPath: Basics.AbsolutePath, configuration: BuildConfiguration, toolchain: Toolchain, triple: Triple? = nil, flags: BuildFlags, buildSystemKind: BuildSystemProvider.Kind = .native, - pkgConfigDirectories: [AbsolutePath] = [], + pkgConfigDirectories: [Basics.AbsolutePath] = [], architectures: [String]? = nil, workers: UInt32 = UInt32(ProcessInfo.processInfo.activeProcessorCount), shouldCreateDylibForDynamicProducts: Bool = true, @@ -223,7 +224,7 @@ public struct BuildParameters: Encodable { } /// The path to the build directory (inside the data directory). - public var buildPath: AbsolutePath { + public var buildPath: Basics.AbsolutePath { // TODO: query the build system for this. switch buildSystemKind { case .xcode, .swiftbuild: @@ -240,47 +241,47 @@ public struct BuildParameters: Encodable { } /// The path to the index store directory. - public var indexStore: AbsolutePath { + public var indexStore: Basics.AbsolutePath { assert(indexStoreMode != .off, "index store is disabled") return buildPath.appending(components: "index", "store") } /// The path to the code coverage directory. - public var codeCovPath: AbsolutePath { + public var codeCovPath: Basics.AbsolutePath { return buildPath.appending("codecov") } /// The path to the code coverage profdata file. - public var codeCovDataFile: AbsolutePath { + public var codeCovDataFile: Basics.AbsolutePath { return codeCovPath.appending("default.profdata") } - public var llbuildManifest: AbsolutePath { + public var llbuildManifest: Basics.AbsolutePath { // FIXME: this path isn't specific to `BuildParameters` due to its use of `..` // FIXME: it should be calculated in a different place return dataPath.appending(components: "..", configuration.dirname + ".yaml") } - public var pifManifest: AbsolutePath { + public var pifManifest: Basics.AbsolutePath { // FIXME: this path isn't specific to `BuildParameters` due to its use of `..` // FIXME: it should be calculated in a different place return dataPath.appending(components: "..", "manifest.pif") } - public var buildDescriptionPath: AbsolutePath { + public var buildDescriptionPath: Basics.AbsolutePath { // FIXME: this path isn't specific to `BuildParameters`, should be moved one directory level higher return buildPath.appending(components: "description.json") } - public var testOutputPath: AbsolutePath { + public var testOutputPath: Basics.AbsolutePath { return buildPath.appending(component: "testOutput.txt") } /// Returns the path to the binary of a product for the current build parameters. - public func binaryPath(for product: ResolvedProduct) throws -> AbsolutePath { + public func binaryPath(for product: ResolvedProduct) throws -> Basics.AbsolutePath { return try buildPath.appending(binaryRelativePath(for: product)) } - public func macroBinaryPath(_ module: ResolvedModule) throws -> AbsolutePath { + public func macroBinaryPath(_ module: ResolvedModule) throws -> Basics.AbsolutePath { assert(module.type == .macro) #if BUILD_MACROS_AS_DYLIBS return buildPath.appending(try dynamicLibraryPath(for: module.name)) @@ -290,17 +291,17 @@ public struct BuildParameters: Encodable { } /// Returns the path to the dynamic library of a product for the current build parameters. - private func dynamicLibraryPath(for name: String) throws -> RelativePath { + private func dynamicLibraryPath(for name: String) throws -> Basics.RelativePath { try RelativePath(validating: "\(self.triple.dynamicLibraryPrefix)\(name)\(self.suffix)\(self.triple.dynamicLibraryExtension)") } /// Returns the path to the executable of a product for the current build parameters. - package func executablePath(for name: String) throws -> RelativePath { + package func executablePath(for name: String) throws -> Basics.RelativePath { try RelativePath(validating: "\(name)\(self.suffix)\(self.triple.executableExtension)") } /// Returns the path to the binary of a product for the current build parameters, relative to the build directory. - public func binaryRelativePath(for product: ResolvedProduct) throws -> RelativePath { + public func binaryRelativePath(for product: ResolvedProduct) throws -> Basics.RelativePath { switch product.type { case .executable, .snippet: return try executablePath(for: product.name) diff --git a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index abb2f6d67c5..83b3b90321d 100644 --- a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -15,6 +15,8 @@ import Foundation import PackageGraph import PackageLoading import PackageModel +import TSCBasic +import TSCUtility typealias WireInput = HostToPluginMessage.InputContext @@ -24,10 +26,10 @@ internal struct PluginContextSerializer { let fileSystem: FileSystem let modulesGraph: ModulesGraph let buildEnvironment: BuildEnvironment - let pkgConfigDirectories: [AbsolutePath] - let sdkRootPath: AbsolutePath? + let pkgConfigDirectories: [Basics.AbsolutePath] + let sdkRootPath: Basics.AbsolutePath? var paths: [WireInput.URL] = [] - var pathsToIds: [AbsolutePath: WireInput.URL.Id] = [:] + var pathsToIds: [Basics.AbsolutePath: WireInput.URL.Id] = [:] var targets: [WireInput.Target] = [] var targetsToWireIDs: [ResolvedModule.ID: WireInput.Target.Id] = [:] var products: [WireInput.Product] = [] @@ -42,7 +44,7 @@ internal struct PluginContextSerializer { /// Adds a path to the serialized structure, if it isn't already there. /// Either way, this function returns the path's wire ID. - mutating func serialize(path: AbsolutePath) throws -> WireInput.URL.Id { + mutating func serialize(path: Basics.AbsolutePath) throws -> WireInput.URL.Id { // If we've already seen the path, just return the wire ID we already assigned to it. if let id = pathsToIds[path] { return id } diff --git a/Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift b/Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift index f8d6b569062..fda28b919a0 100644 --- a/Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift +++ b/Sources/SPMBuildCore/Plugins/PluginScriptRunner.swift @@ -16,6 +16,8 @@ import Foundation import PackageModel import PackageLoading import PackageGraph +import TSCBasic +import TSCUtility /// Implements the mechanics of running and communicating with a plugin (implemented as a set of Swift source files). In most environments this is done by compiling the code to an executable, invoking it as a sandboxed subprocess, and communicating with it using pipes. Specific implementations are free to implement things differently, however. public protocol PluginScriptRunner { @@ -23,7 +25,7 @@ public protocol PluginScriptRunner { /// Public protocol function that starts compiling the plugin script to an executable. The name is used as the basename for the executable and auxiliary files. The tools version controls the availability of APIs in PackagePlugin, and should be set to the tools version of the package that defines the plugin (not of the target to which it is being applied). This function returns immediately and then calls the completion handler on the callback queue when compilation ends. @available(*, noasync, message: "Use the async alternative") func compilePluginScript( - sourceFiles: [AbsolutePath], + sourceFiles: [Basics.AbsolutePath], pluginName: String, toolsVersion: ToolsVersion, observabilityScope: ObservabilityScope, @@ -43,13 +45,13 @@ public protocol PluginScriptRunner { /// /// Every concrete implementation should cache any intermediates as necessary to avoid redundant work. func runPluginScript( - sourceFiles: [AbsolutePath], + sourceFiles: [Basics.AbsolutePath], pluginName: String, initialMessage: Data, toolsVersion: ToolsVersion, - workingDirectory: AbsolutePath, - writableDirectories: [AbsolutePath], - readOnlyDirectories: [AbsolutePath], + workingDirectory: Basics.AbsolutePath, + writableDirectories: [Basics.AbsolutePath], + readOnlyDirectories: [Basics.AbsolutePath], allowNetworkConnections: [SandboxNetworkPermission], fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -60,12 +62,12 @@ public protocol PluginScriptRunner { /// Returns the Triple that represents the host for which plugin script tools should be built, or for which binary /// tools should be selected. - var hostTriple: Triple { get throws } + var hostTriple: Basics.Triple { get throws } } public extension PluginScriptRunner { func compilePluginScript( - sourceFiles: [AbsolutePath], + sourceFiles: [Basics.AbsolutePath], pluginName: String, toolsVersion: ToolsVersion, observabilityScope: ObservabilityScope, @@ -118,10 +120,10 @@ public struct PluginCompilationResult: Equatable { public var commandLine: [String] /// Path of the compiled executable. - public var executableFile: AbsolutePath + public var executableFile: Basics.AbsolutePath /// Path of the libClang diagnostics file emitted by the compiler. - public var diagnosticsFile: AbsolutePath + public var diagnosticsFile: Basics.AbsolutePath /// Any output emitted by the compiler (stdout and stderr combined). public var rawCompilerOutput: String @@ -132,8 +134,8 @@ public struct PluginCompilationResult: Equatable { public init( succeeded: Bool, commandLine: [String], - executableFile: AbsolutePath, - diagnosticsFile: AbsolutePath, + executableFile: Basics.AbsolutePath, + diagnosticsFile: Basics.AbsolutePath, compilerOutput rawCompilerOutput: String, cached: Bool ) { diff --git a/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift b/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift index 509a377a236..e70e3bcdbbc 100644 --- a/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift +++ b/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +import PackageModel +import TSCUtility + import struct PackageGraph.ResolvedPackage import struct PackageGraph.ResolvedModule diff --git a/Sources/SourceControl/RepositoryManager.swift b/Sources/SourceControl/RepositoryManager.swift index d408d044b62..ae5a6d26fa2 100644 --- a/Sources/SourceControl/RepositoryManager.swift +++ b/Sources/SourceControl/RepositoryManager.swift @@ -15,16 +15,17 @@ import _Concurrency import Dispatch import Foundation import PackageModel +import TSCBasic /// Manages a collection of bare repositories. public class RepositoryManager: Cancellable { public typealias Delegate = RepositoryManagerDelegate /// The path under which repositories are stored. - public let path: AbsolutePath + public let path: Basics.AbsolutePath /// The path to the directory where all cached git repositories are stored. - private let cachePath: AbsolutePath? + private let cachePath: Basics.AbsolutePath? // used in tests to disable skipping of local packages. private let cacheLocalPackages: Bool @@ -67,9 +68,9 @@ public class RepositoryManager: Cancellable { /// - delegate: The repository manager delegate. public init( fileSystem: FileSystem, - path: AbsolutePath, + path: Basics.AbsolutePath, provider: RepositoryProvider, - cachePath: AbsolutePath? = .none, + cachePath: Basics.AbsolutePath? = .none, cacheLocalPackages: Bool = false, maxConcurrentOperations: Int? = .none, initializationWarningHandler: (String) -> Void, @@ -306,7 +307,7 @@ public class RepositoryManager: Cancellable { private func fetchAndPopulateCache( package: PackageIdentity, handle: RepositoryHandle, - repositoryPath: AbsolutePath, + repositoryPath: Basics.AbsolutePath, updateStrategy: RepositoryUpdateStrategy, observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue @@ -408,7 +409,7 @@ public class RepositoryManager: Cancellable { } /// Open a working copy checkout at a path - public func openWorkingCopy(at path: AbsolutePath) throws -> WorkingCheckout { + public func openWorkingCopy(at path: Basics.AbsolutePath) throws -> WorkingCheckout { try self.provider.openWorkingCopy(at: path) } @@ -430,7 +431,7 @@ public class RepositoryManager: Cancellable { /// Create a working copy of the repository from a handle. private func createWorkingCopy( _ handle: RepositoryHandle, - at destinationPath: AbsolutePath, + at destinationPath: Basics.AbsolutePath, editable: Bool ) throws -> WorkingCheckout { try self.provider.createWorkingCopy( @@ -448,12 +449,12 @@ public class RepositoryManager: Cancellable { } /// Returns true if the directory is valid git location. - public func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { + public func isValidDirectory(_ directory: Basics.AbsolutePath) throws -> Bool { try self.provider.isValidDirectory(directory) } /// Returns true if the directory is valid git location for the specified repository - public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + public func isValidDirectory(_ directory: Basics.AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { try self.provider.isValidDirectory(directory, for: repository) } @@ -472,7 +473,7 @@ public class RepositoryManager: Cancellable { } /// Sets up the cache directories if they don't already exist. - private func initializeCacheIfNeeded(cachePath: AbsolutePath) throws { + private func initializeCacheIfNeeded(cachePath: Basics.AbsolutePath) throws { // Create the supplied cache directory. if !self.fileSystem.exists(cachePath) { try self.fileSystem.createDirectory(cachePath, recursive: true) @@ -526,10 +527,10 @@ extension RepositoryManager { /// /// This is intentionally hidden from the clients so that the manager is /// allowed to move repositories transparently. - fileprivate let subpath: RelativePath + fileprivate let subpath: Basics.RelativePath /// Create a handle. - fileprivate init(manager: RepositoryManager, repository: RepositorySpecifier, subpath: RelativePath) { + fileprivate init(manager: RepositoryManager, repository: RepositorySpecifier, subpath: Basics.RelativePath) { self.manager = manager self.repository = repository self.subpath = subpath @@ -547,7 +548,7 @@ extension RepositoryManager { /// expected to be non-existent when called. /// /// - editable: The clone is expected to be edited by user. - public func createWorkingCopy(at path: AbsolutePath, editable: Bool) throws -> WorkingCheckout { + public func createWorkingCopy(at path: Basics.AbsolutePath, editable: Bool) throws -> WorkingCheckout { return try self.manager.createWorkingCopy(self, at: path, editable: editable) } } @@ -596,7 +597,7 @@ extension RepositoryManager.RepositoryHandle: CustomStringConvertible { extension RepositorySpecifier { // relative path where the repository should be stored - internal func storagePath() throws -> RelativePath { + internal func storagePath() throws -> Basics.RelativePath { return try RelativePath(validating: self.fileSystemIdentifier) } diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 8c21abad34c..56f91274912 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -15,6 +15,7 @@ import Foundation import PackageGraph import PackageLoading import PackageModel +import TSCUtility @_spi(SwiftPMInternal) import SPMBuildCore @@ -28,7 +29,7 @@ import enum SwiftBuild.ProjectModel /// The parameters required by `PIFBuilder`. struct PIFBuilderParameters { - let triple: Triple + let triple: Basics.Triple /// Whether the toolchain supports `-package-name` option. let isPackageAccessModifierSupported: Bool diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index bc17d22ee15..ca71fa6f7f3 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import TSCUtility import struct Basics.AbsolutePath import class Basics.ObservabilitySystem diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index 5bf30c1397a..d9cb5d42b7f 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import TSCUtility import struct Basics.AbsolutePath import class Basics.ObservabilitySystem @@ -989,7 +990,7 @@ extension PackagePIFProjectBuilder { private struct PackageRegistrySignature: Encodable { enum Source: Encodable { - case registry(url: URL) + case registry(url: Foundation.URL) } let packageIdentity: String diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index bd7a36b4895..1b8648f1622 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import TSCUtility import struct Basics.AbsolutePath import struct Basics.Diagnostic diff --git a/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift b/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift index 78bc3248a0d..acb75dff6a3 100644 --- a/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift @@ -14,6 +14,7 @@ import ArgumentParser import Basics import CoreCommands import PackageModel +import TSCBasic package struct RemoveSwiftSDK: SwiftSDKSubcommand { package static let configuration = CommandConfiguration( @@ -33,12 +34,12 @@ package struct RemoveSwiftSDK: SwiftSDKSubcommand { func run( hostTriple: Triple, - _ swiftSDKsDirectory: AbsolutePath, + _ swiftSDKsDirectory: Basics.AbsolutePath, _ observabilityScope: ObservabilityScope ) async throws { let artifactBundleDirectory = swiftSDKsDirectory.appending(component: self.sdkIDOrBundleName) - let removedBundleDirectory: AbsolutePath + let removedBundleDirectory: Basics.AbsolutePath if fileSystem.exists(artifactBundleDirectory) { try fileSystem.removeFileTree(artifactBundleDirectory) diff --git a/Sources/Workspace/InitPackage.swift b/Sources/Workspace/InitPackage.swift index 1eb4596918d..1c1fc6b8953 100644 --- a/Sources/Workspace/InitPackage.swift +++ b/Sources/Workspace/InitPackage.swift @@ -13,6 +13,7 @@ import Basics import PackageModel import SPMBuildCore +import TSCUtility import protocol TSCBasic.OutputByteStream diff --git a/Sources/Workspace/ManagedArtifact.swift b/Sources/Workspace/ManagedArtifact.swift index 013d938c3a4..3919250eae0 100644 --- a/Sources/Workspace/ManagedArtifact.swift +++ b/Sources/Workspace/ManagedArtifact.swift @@ -14,6 +14,7 @@ import Basics import PackageGraph import PackageModel import SourceControl +import TSCBasic extension Workspace { /// A downloaded artifact managed by the workspace. @@ -28,7 +29,7 @@ extension Workspace { public let source: Source /// The path of the artifact on disk - public let path: AbsolutePath + public let path: Basics.AbsolutePath public let kind: BinaryModule.Kind @@ -36,7 +37,7 @@ extension Workspace { packageRef: PackageReference, targetName: String, source: Source, - path: AbsolutePath, + path: Basics.AbsolutePath, kind: BinaryModule.Kind ) { self.packageRef = packageRef @@ -52,7 +53,7 @@ extension Workspace { targetName: String, url: String, checksum: String, - path: AbsolutePath, + path: Basics.AbsolutePath, kind: BinaryModule.Kind ) -> ManagedArtifact { return ManagedArtifact( @@ -68,7 +69,7 @@ extension Workspace { public static func local( packageRef: PackageReference, targetName: String, - path: AbsolutePath, + path: Basics.AbsolutePath, kind: BinaryModule.Kind, checksum: String? = nil ) -> ManagedArtifact { diff --git a/Sources/Workspace/ManagedDependency.swift b/Sources/Workspace/ManagedDependency.swift index 30d810c88c2..dfbe53dd111 100644 --- a/Sources/Workspace/ManagedDependency.swift +++ b/Sources/Workspace/ManagedDependency.swift @@ -14,6 +14,7 @@ import Basics import PackageGraph import PackageModel import SourceControl +import TSCBasic import struct TSCUtility.Version @@ -26,7 +27,7 @@ extension Workspace { /// Represents the state of the managed dependency. public indirect enum State: Equatable, CustomStringConvertible { /// The dependency is a local package on the file system. - case fileSystem(AbsolutePath) + case fileSystem(Basics.AbsolutePath) /// The dependency is a managed source control checkout. case sourceControlCheckout(CheckoutState) @@ -39,9 +40,9 @@ extension Workspace { /// If the path is non-nil, the dependency is managed by a user and is /// located at the path. In other words, this dependency is being used /// for top of the tree style development. - case edited(basedOn: ManagedDependency?, unmanagedPath: AbsolutePath?) + case edited(basedOn: ManagedDependency?, unmanagedPath: Basics.AbsolutePath?) - case custom(version: Version, path: AbsolutePath) + case custom(version: Version, path: Basics.AbsolutePath) public var description: String { switch self { @@ -66,12 +67,12 @@ extension Workspace { public let state: State /// The checked out path of the dependency on disk, relative to the workspace checkouts path. - public let subpath: RelativePath + public let subpath: Basics.RelativePath internal init( packageRef: PackageReference, state: State, - subpath: RelativePath + subpath: Basics.RelativePath ) { self.packageRef = packageRef self.subpath = subpath @@ -84,7 +85,7 @@ extension Workspace { /// - Parameters: /// - subpath: The subpath inside the editable directory. /// - unmanagedPath: A custom absolute path instead of the subpath. - public func edited(subpath: RelativePath, unmanagedPath: AbsolutePath?) throws -> ManagedDependency { + public func edited(subpath: Basics.RelativePath, unmanagedPath: Basics.AbsolutePath?) throws -> ManagedDependency { guard case .sourceControlCheckout = self.state else { throw InternalError("invalid dependency state: \(self.state)") } @@ -116,7 +117,7 @@ extension Workspace { public static func sourceControlCheckout( packageRef: PackageReference, state: CheckoutState, - subpath: RelativePath + subpath: Basics.RelativePath ) throws -> ManagedDependency { switch packageRef.kind { case .localSourceControl, .remoteSourceControl: @@ -134,7 +135,7 @@ extension Workspace { public static func registryDownload( packageRef: PackageReference, version: Version, - subpath: RelativePath + subpath: Basics.RelativePath ) throws -> ManagedDependency { guard case .registry = packageRef.kind else { throw InternalError("invalid package type: \(packageRef.kind)") @@ -149,9 +150,9 @@ extension Workspace { /// Create an edited dependency public static func edited( packageRef: PackageReference, - subpath: RelativePath, + subpath: Basics.RelativePath, basedOn: ManagedDependency?, - unmanagedPath: AbsolutePath? + unmanagedPath: Basics.AbsolutePath? ) -> ManagedDependency { return ManagedDependency( packageRef: packageRef, diff --git a/Sources/Workspace/ManagedPrebuilt.swift b/Sources/Workspace/ManagedPrebuilt.swift index 108402e3b26..9c8aafc2343 100644 --- a/Sources/Workspace/ManagedPrebuilt.swift +++ b/Sources/Workspace/ManagedPrebuilt.swift @@ -12,6 +12,7 @@ import Basics import PackageModel +import TSCBasic extension Workspace { /// A downloaded prebuilt managed by the workspace. @@ -23,7 +24,7 @@ extension Workspace { public let libraryName: String /// The path to the extracted prebuilt artifacts - public let path: AbsolutePath + public let path: Basics.AbsolutePath /// The products in the library public let products: [String] diff --git a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 1a618257df2..208b05dd2e0 100644 --- a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -17,6 +17,7 @@ import PackageGraph import PackageLoading import PackageModel import PackageRegistry +import TSCBasic import struct TSCUtility.Version @@ -178,7 +179,7 @@ public class RegistryPackageContainer: PackageContainer { throw StringError("failed locating placeholder manifest for \(preferredToolsVersion)") } // replace the fake manifest with the real manifest content - let manifestPath = AbsolutePath.root.appending(component: placeholderManifestFileName) + let manifestPath = Basics.AbsolutePath.root.appending(component: placeholderManifestFileName) try fileSystem.removeFileTree(manifestPath) try fileSystem.writeFileContents(manifestPath, string: manifestContent) // finally, load the manifest @@ -202,7 +203,7 @@ public class RegistryPackageContainer: PackageContainer { // ToolsVersionLoader is designed to scan files to decide which is the best tools-version // as such, this writes a fake manifest based on the information returned by the registry // with only the header line which is all that is needed by ToolsVersionLoader - let fileSystem = InMemoryFileSystem() + let fileSystem = Basics.InMemoryFileSystem() for manifest in manifests { let content = manifest.value.content ?? "// swift-tools-version:\(manifest.value.toolsVersion)" try fileSystem.writeFileContents(AbsolutePath.root.appending(component: manifest.key), string: content) diff --git a/Sources/Workspace/Workspace+BinaryArtifacts.swift b/Sources/Workspace/Workspace+BinaryArtifacts.swift index a99f895d09d..334dfbc9094 100644 --- a/Sources/Workspace/Workspace+BinaryArtifacts.swift +++ b/Sources/Workspace/Workspace+BinaryArtifacts.swift @@ -13,6 +13,7 @@ import Basics import Foundation import PackageLoading +import PackageGraph import PackageModel import SPMBuildCore diff --git a/Sources/Workspace/Workspace+Editing.swift b/Sources/Workspace/Workspace+Editing.swift index c38036fc04c..a403ca26931 100644 --- a/Sources/Workspace/Workspace+Editing.swift +++ b/Sources/Workspace/Workspace+Editing.swift @@ -11,6 +11,9 @@ //===----------------------------------------------------------------------===// import _Concurrency +import PackageModel +import TSCBasic + import struct Basics.AbsolutePath import class Basics.InMemoryFileSystem import class Basics.ObservabilityScope diff --git a/Sources/Workspace/Workspace+PackageContainer.swift b/Sources/Workspace/Workspace+PackageContainer.swift index 4787e3adef4..0b0a824f8fd 100644 --- a/Sources/Workspace/Workspace+PackageContainer.swift +++ b/Sources/Workspace/Workspace+PackageContainer.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +import SourceControl +import TSCBasic + import class Basics.ObservabilityScope import func Dispatch.dispatchPrecondition import class Dispatch.DispatchQueue diff --git a/Sources/Workspace/Workspace+Prebuilts.swift b/Sources/Workspace/Workspace+Prebuilts.swift index 7b58f142e98..8cca15bde2c 100644 --- a/Sources/Workspace/Workspace+Prebuilts.swift +++ b/Sources/Workspace/Workspace+Prebuilts.swift @@ -12,6 +12,7 @@ import Basics import Foundation +import OrderedCollections import PackageModel import protocol TSCBasic.HashAlgorithm diff --git a/Sources/Workspace/Workspace+ResolvedPackages.swift b/Sources/Workspace/Workspace+ResolvedPackages.swift index 4be96b417a1..3586926fba6 100644 --- a/Sources/Workspace/Workspace+ResolvedPackages.swift +++ b/Sources/Workspace/Workspace+ResolvedPackages.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import SourceControl + import class Basics.ObservabilityScope import class PackageGraph.ResolvedPackagesStore import struct PackageModel.PackageReference diff --git a/Sources/Workspace/Workspace+SourceControl.swift b/Sources/Workspace/Workspace+SourceControl.swift index c2afe146c51..773c856e17a 100644 --- a/Sources/Workspace/Workspace+SourceControl.swift +++ b/Sources/Workspace/Workspace+SourceControl.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import TSCBasic + import struct Basics.AbsolutePath import struct Basics.InternalError import class Basics.ObservabilityScope diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index adee2633287..3c395133031 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -15,6 +15,7 @@ import Foundation import PackageGraph import PackageModel import SourceControl +import TSCBasic import struct TSCUtility.Version @@ -30,14 +31,14 @@ public actor WorkspaceState { public private(set) var prebuilts: Workspace.ManagedPrebuilts /// Path to the state file. - public let storagePath: AbsolutePath + public let storagePath: Basics.AbsolutePath /// storage private let storage: WorkspaceStateStorage init( fileSystem: FileSystem, - storageDirectory: AbsolutePath, + storageDirectory: Basics.AbsolutePath, initializationWarningHandler: (String) -> Void ) { self.storagePath = storageDirectory.appending("workspace-state.json") @@ -101,12 +102,12 @@ public actor WorkspaceState { // MARK: - Serialization private struct WorkspaceStateStorage { - private let path: AbsolutePath + private let path: Basics.AbsolutePath private let fileSystem: FileSystem private let encoder = JSONEncoder.makeWithDefaults() private let decoder = JSONDecoder.makeWithDefaults() - init(path: AbsolutePath, fileSystem: FileSystem) { + init(path: Basics.AbsolutePath, fileSystem: FileSystem) { self.path = path self.fileSystem = fileSystem } @@ -285,7 +286,7 @@ extension WorkspaceStateStorage { let kind = try container.decode(String.self, forKey: .name) switch kind { case "local", "fileSystem": - let path = try container.decode(AbsolutePath.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath.self, forKey: .path) return self.init(underlying: .fileSystem(path)) case "checkout", "sourceControlCheckout": let checkout = try container.decode(CheckoutInfo.self, forKey: .checkoutState) @@ -295,14 +296,14 @@ extension WorkspaceStateStorage { return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) case "edited": - let path = try container.decode(AbsolutePath?.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( basedOn: basedOn.map { try .init($0) }, unmanagedPath: path )) case "custom": let version = try container.decode(String.self, forKey: .version) - let path = try container.decode(AbsolutePath.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath.self, forKey: .path) return try self.init(underlying: .custom( version: TSCUtility.Version(versionString: version), path: path @@ -456,7 +457,7 @@ extension WorkspaceStateStorage { struct Prebuilt: Codable { let packageRef: PackageReference let libraryName: String - let path: AbsolutePath + let path: Basics.AbsolutePath let products: [String] let cModules: [String] @@ -669,7 +670,7 @@ extension WorkspaceStateStorage { let kind = try container.decode(String.self, forKey: .name) switch kind { case "local", "fileSystem": - let path = try container.decode(AbsolutePath.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath.self, forKey: .path) return self.init(underlying: .fileSystem(path)) case "checkout", "sourceControlCheckout": let checkout = try container.decode(CheckoutInfo.self, forKey: .checkoutState) @@ -679,14 +680,14 @@ extension WorkspaceStateStorage { return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) case "edited": - let path = try container.decode(AbsolutePath?.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( basedOn: basedOn.map { try .init($0) }, unmanagedPath: path )) case "custom": let version = try container.decode(String.self, forKey: .version) - let path = try container.decode(AbsolutePath.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath.self, forKey: .path) return try self.init(underlying: .custom( version: TSCUtility.Version(versionString: version), path: path @@ -1025,7 +1026,7 @@ extension WorkspaceStateStorage { let kind = try container.decode(String.self, forKey: .name) switch kind { case "local", "fileSystem": - let path = try container.decode(AbsolutePath.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath.self, forKey: .path) return self.init(underlying: .fileSystem(path)) case "checkout", "sourceControlCheckout": let checkout = try container.decode(CheckoutInfo.self, forKey: .checkoutState) @@ -1035,14 +1036,14 @@ extension WorkspaceStateStorage { return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) case "edited": - let path = try container.decode(AbsolutePath?.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( basedOn: basedOn.map { try .init($0) }, unmanagedPath: path )) case "custom": let version = try container.decode(String.self, forKey: .version) - let path = try container.decode(AbsolutePath.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath.self, forKey: .path) return try self.init(underlying: .custom( version: TSCUtility.Version(versionString: version), path: path @@ -1215,7 +1216,7 @@ extension Workspace.ManagedDependency { extension Workspace.ManagedArtifact { fileprivate init(_ artifact: WorkspaceStateStorage.V5.Artifact) throws { - let path = try AbsolutePath(validating: artifact.path) + let path = try Basics.AbsolutePath(validating: artifact.path) try self.init( packageRef: .init(artifact.packageRef), targetName: artifact.targetName, @@ -1337,7 +1338,7 @@ extension WorkspaceStateStorage { let checkout = try container.decode(CheckoutInfo.self, forKey: .checkoutState) return try self.init(underlying: .sourceControlCheckout(.init(checkout))) case "edited": - let path = try container.decode(AbsolutePath?.self, forKey: .path) + let path = try container.decode(Basics.AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( basedOn: basedOn.map { try .init($0) }, unmanagedPath: path @@ -1458,7 +1459,7 @@ extension Workspace.ManagedDependency { extension Workspace.ManagedArtifact { fileprivate init(_ artifact: WorkspaceStateStorage.V4.Artifact) throws { - let path = try AbsolutePath(validating: artifact.path) + let path = try Basics.AbsolutePath(validating: artifact.path) try self.init( packageRef: .init(artifact.packageRef), targetName: artifact.targetName, @@ -1479,7 +1480,7 @@ extension PackageModel.PackageReference { case "local": kind = try .fileSystem(.init(validating: reference.location)) case "remote": - if let path = try? AbsolutePath(validating: reference.location) { + if let path = try? Basics.AbsolutePath(validating: reference.location) { kind = .localSourceControl(path) } else { kind = .remoteSourceControl(SourceControlURL(reference.location)) @@ -1512,7 +1513,7 @@ extension CheckoutState { // backwards compatibility for older formats extension BinaryModule.Kind { - fileprivate static func forPath(_ path: AbsolutePath) -> Self { + fileprivate static func forPath(_ path: Basics.AbsolutePath) -> Self { if let kind = allCases.first(where: { $0.fileExtension == path.extension }) { return kind } diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 93b88286edc..3d020614783 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -15,6 +15,7 @@ import Foundation import PackageGraph import PackageLoading import PackageModel +import TSCUtility @_spi(SwiftPMInternal) import SPMBuildCore @@ -24,7 +25,7 @@ import func TSCBasic.topologicalSort /// The parameters required by `PIFBuilder`. struct PIFBuilderParameters { - let triple: Triple + let triple: Basics.Triple /// Whether the toolchain supports `-package-name` option. let isPackageAccessModifierSupported: Bool diff --git a/Tests/PackageGraphTests/CrossCompilationPackageGraphTests.swift b/Tests/PackageGraphTests/CrossCompilationPackageGraphTests.swift index 5e7119d5b45..88385d33868 100644 --- a/Tests/PackageGraphTests/CrossCompilationPackageGraphTests.swift +++ b/Tests/PackageGraphTests/CrossCompilationPackageGraphTests.swift @@ -17,6 +17,7 @@ import _InternalTestSupport @testable import PackageGraph +import PackageModel import XCTest final class CrossCompilationPackageGraphTests: XCTestCase { diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index fa128410127..d840f8e189a 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -11,6 +11,8 @@ //===----------------------------------------------------------------------===// import Basics +import PackageLoading +import TSCUtility @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) @testable import PackageGraph @@ -2921,7 +2923,7 @@ final class ModulesGraphTests: XCTestCase { ] let expectedPlatformsForTests = customXCTestMinimumDeploymentTargets - .reduce(into: [Platform: PlatformVersion]()) { partialResult, entry in + .reduce(into: [PackageModel.Platform: PlatformVersion]()) { partialResult, entry in if entry.value > entry.key.oldestSupportedVersion { partialResult[entry.key] = entry.value } else { diff --git a/Tests/PackageGraphTests/TopologicalSortTests.swift b/Tests/PackageGraphTests/TopologicalSortTests.swift index ce357f1b5a3..650c340167b 100644 --- a/Tests/PackageGraphTests/TopologicalSortTests.swift +++ b/Tests/PackageGraphTests/TopologicalSortTests.swift @@ -37,7 +37,7 @@ extension Int { extension Int: @retroactive Identifiable {} private func topologicalSort(_ nodes: [Int], _ successors: [Int: [Int]]) throws -> [Int] { - return try topologicalSort(nodes, successors: { successors[$0] ?? [] }) + return try topologicalSortIdentifiable(nodes, successors: { successors[$0] ?? [] }) } private func topologicalSort(_ node: Int, _ successors: [Int: [Int]]) throws -> [Int] { return try topologicalSort([node], successors) diff --git a/Tests/PackageGraphTests/VersionSetSpecifierTests.swift b/Tests/PackageGraphTests/VersionSetSpecifierTests.swift index a79e635b02e..6fa280c8815 100644 --- a/Tests/PackageGraphTests/VersionSetSpecifierTests.swift +++ b/Tests/PackageGraphTests/VersionSetSpecifierTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Foundation +import TSCUtility import XCTest import PackageGraph From 7cf86c630358e28d690d69cad6540e2c0c291f17 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Wed, 16 Apr 2025 18:09:55 -0700 Subject: [PATCH 34/99] Add support for testable executables on Windows This uses the /ALTERNATENAME flag to link.exe, which is roughly equivalent to -alias/--defsym. This has been verified to work in a sample project. See https://devblogs.microsoft.com/oldnewthing/20200731-00/ for more info. Closes #6367 --- Sources/Build/BuildPlan/BuildPlan.swift | 20 ++++++++++++++++++++ Tests/BuildTests/BuildPlanTests.swift | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 22a9727191f..bf45b9b5263 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -152,12 +152,32 @@ extension BuildParameters { args = ["-alias", "_\(target.c99name)_main", "_main"] case .elf: args = ["--defsym", "main=\(target.c99name)_main"] + case .coff: + // If the user is specifying a custom entry point name that isn't "main", assume they may be setting WinMain or wWinMain + // and don't do any modifications ourselves. In that case the linker will infer the WINDOWS subsystem and call WinMainCRTStartup, + // which will then call the custom entry point. And WinMain/wWinMain != main, so this still won't run into duplicate symbol + // issues when called from a test target, which always uses main. + if let customEntryPointFunctionName = findCustomEntryPointFunctionName(of: target), customEntryPointFunctionName != "main" { + return nil + } + args = ["/ALTERNATENAME:main=\(target.c99name)_main", "/SUBSYSTEM:CONSOLE"] default: return nil } return args.asSwiftcLinkerFlags() } + private func findCustomEntryPointFunctionName(of target: ResolvedModule) -> String? { + let flags = createScope(for: target).evaluate(.OTHER_SWIFT_FLAGS) + var it = flags.makeIterator() + while let value = it.next() { + if value == "-Xfrontend" && it.next() == "-entry-point-function-name" && it.next() == "-Xfrontend" { + return it.next() + } + } + return nil + } + /// Returns the scoped view of build settings for a given target. func createScope(for target: ResolvedModule) -> BuildSettings.Scope { BuildSettings.Scope(target.underlying.buildSettings, environment: buildEnvironment) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 449cde066fc..81541f8013d 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -3875,7 +3875,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { observabilityScope: observability.topScope )) } - let supportingTriples: [Basics.Triple] = [.x86_64Linux, .x86_64MacOS] + let supportingTriples: [Basics.Triple] = [.x86_64Linux, .x86_64MacOS, .x86_64Windows] for triple in supportingTriples { let result = try await createResult(for: triple) let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments() @@ -3884,7 +3884,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { XCTAssertMatch(linkExe, [.contains("exe_main")]) } - let unsupportingTriples: [Basics.Triple] = [.wasi, .windows] + let unsupportingTriples: [Basics.Triple] = [.wasi] for triple in unsupportingTriples { let result = try await createResult(for: triple) let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments() From 6973cf56bef2a69f57bd1ebb4d2bbab1be3e9e5b Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Wed, 23 Apr 2025 16:33:46 -0700 Subject: [PATCH 35/99] Add FreeBSD support to SwiftBuild PIF builder (#8550) ### Motivation: Add **FreeBSD** support to SwiftBuild PIF builder (i.e., when using `--build-system swiftbuild`). ### Modifications: Just a quick change in how we map a `PackageModel.Platform` value to a `SwiftBuild.ProjectModel.BuildSettings.Platform` one. --- Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index e1689c1b086..8e4fdfc3e7a 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -969,6 +969,7 @@ extension ProjectModel.BuildSettings.Platform { case .windows: .windows case .wasi: .wasi case .openbsd: .openbsd + case .freebsd: .freebsd default: preconditionFailure("Unexpected platform: \(platform.name)") } } From 387c4e0cb88ce0e3cec7c51d0d420fbf416a9390 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 24 Apr 2025 12:52:18 +0100 Subject: [PATCH 36/99] Skip failing test in `TestCommandTests.swift` (#8553) rdar://149936169 This test is currently failing on CI (https://ci.swift.org/job/oss-swift-package-amazon-linux-2-aarch64/4188) and blocks toolchain builds for swift.org. --- Tests/CommandsTests/TestCommandTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index e4e79a69b61..436839d9fef 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -577,6 +577,7 @@ class TestCommandTestCase: CommandsBuildProviderTestCase { } func testFatalErrorDisplayedCorrectNumberOfTimesWhenSingleXCTestHasFatalErrorInBuildCompilation() async throws { + try XCTSkipIfCI() // Test for GitHub Issue #6605 // GIVEN we have a Swift Package that has a fatalError building the tests let expected = 1 From b021cefb66570f5c8dc4a35d70c2f62f790b6f13 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Apr 2025 09:25:02 -0700 Subject: [PATCH 37/99] [Build] Enable diagnostic serialization by default for swift modules (#8531) ### Motivation: Adds a flag and entry into outputs file map per file in a swift module to emit any diagnostics in serialized form. This is preliminary work to for future `swift migrate` command that needs serialized diagnostics to get the fix-its and enables future improvements like diagnostic de-duplication. ### Modifications: - Add a new flag to `compileArguments` : `-serialize-diagnostics` - Add an entry per source file an swift module that presents a path to a `.dia` file where the diagnostic information should be stored. - Add a computed property to fetch diagnostic file locations per swift module ### Result: The build would now emit serialized diagnostic files for every swift module. --- .../SwiftModuleBuildDescription.swift | 16 ++++- Tests/BuildTests/BuildPlanTests.swift | 63 +++++++++++++++++++ .../CrossCompilationBuildPlanTests.swift | 2 +- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 347e4c9ca35..89f2637ff0b 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -495,6 +495,8 @@ public final class SwiftModuleBuildDescription { args += ["-enable-batch-mode"] } + args += ["-serialize-diagnostics"] + args += self.buildParameters.indexStoreArguments(for: self.target) args += self.optimizationArguments args += self.testingArguments @@ -851,6 +853,7 @@ public final class SwiftModuleBuildDescription { let sourceFileName = source.basenameWithoutExt let partialModulePath = self.tempsPath.appending(component: sourceFileName + "~partial.swiftmodule") let swiftDepsPath = self.tempsPath.appending(component: sourceFileName + ".swiftdeps") + let diagnosticsPath = self.diagnosticFile(sourceFile: source) content += #""" @@ -872,7 +875,8 @@ public final class SwiftModuleBuildDescription { #""" "\#(objectKey)": "\#(object._nativePathString(escaped: true))", "swiftmodule": "\#(partialModulePath._nativePathString(escaped: true))", - "swift-dependencies": "\#(swiftDepsPath._nativePathString(escaped: true))" + "swift-dependencies": "\#(swiftDepsPath._nativePathString(escaped: true))", + "diagnostics": "\#(diagnosticsPath._nativePathString(escaped: true))" }\#((idx + 1) < sources.count ? "," : "") """# @@ -1055,3 +1059,13 @@ extension SwiftModuleBuildDescription { ModuleBuildDescription.swift(self).recursiveDependencies(using: plan) } } + +extension SwiftModuleBuildDescription { + package var diagnosticFiles: [AbsolutePath] { + self.sources.compactMap { self.diagnosticFile(sourceFile: $0) } + } + + private func diagnosticFile(sourceFile: AbsolutePath) -> AbsolutePath { + self.tempsPath.appending(component: "\(sourceFile.basenameWithoutExt).dia") + } +} diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 81541f8013d..914b46130a4 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -33,6 +33,7 @@ import Workspace import XCTest import struct TSCBasic.ByteString +import func TSCBasic.withTemporaryFile import enum TSCUtility.Diagnostics @@ -785,6 +786,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { exe, [ "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -803,6 +805,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { lib, [ "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -1854,6 +1857,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { [ .anySequence, "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -2355,6 +2359,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { [ .anySequence, "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -2374,6 +2379,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { [ .anySequence, "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", "-Xfrontend", @@ -2852,6 +2858,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { let matchText = try result.moduleBuildDescription(for: "exe").swift().compileArguments() let assertionText: [StringPattern] = [ "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -3153,6 +3160,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { exe, [ "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -3171,6 +3179,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { lib, [ "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -3811,6 +3820,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments() XCTAssertMatch(exe, [ "-enable-batch-mode", + "-serialize-diagnostics", "-Onone", "-enable-testing", .equal(self.j), @@ -6975,6 +6985,59 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { print(myLib.additionalFlags) XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool/include")}), "flags shouldn't contain tools items") } + + func testDiagnosticsAreMentionedInOutputsFileMap() async throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Pkg/Sources/exe/main.swift", + "/Pkg/Sources/exe/aux.swift", + "/Pkg/Sources/lib/lib.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [ + TargetDescription(name: "exe", dependencies: ["lib"]), + TargetDescription(name: "lib", dependencies: []), + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + let plan = try await mockBuildPlan( + graph: graph, + linkingParameters: .init( + shouldLinkStaticSwiftStdlib: true + ), + fileSystem: fs, + observabilityScope: observability.topScope + ) + let result = try BuildPlanResult(plan: plan) + + result.checkProductsCount(1) + result.checkTargetsCount(2) + + for module in result.targetMap { + let buildDescription = try module.swift() + + try withTemporaryFile { file in + try buildDescription.writeOutputFileMap(to: .init(file.path.pathString)) + + let fileMap = try String(bytes: fs.readFileContents(file.path).contents, encoding: .utf8) + + for diagnosticFile in buildDescription.diagnosticFiles { + XCTAssertMatch(fileMap, .contains(diagnosticFile.pathString)) + } + } + } + } } class BuildPlanNativeTests: BuildPlanTestCase { diff --git a/Tests/BuildTests/CrossCompilationBuildPlanTests.swift b/Tests/BuildTests/CrossCompilationBuildPlanTests.swift index 28321da4df8..698ad2c8183 100644 --- a/Tests/BuildTests/CrossCompilationBuildPlanTests.swift +++ b/Tests/BuildTests/CrossCompilationBuildPlanTests.swift @@ -178,7 +178,7 @@ final class CrossCompilationBuildPlanTests: XCTestCase { XCTAssertMatch( exe, [ - "-enable-batch-mode", "-Onone", "-enable-testing", + "-enable-batch-mode", "-serialize-diagnostics", "-Onone", "-enable-testing", "-j3", "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(pkgPath.appending(components: "Sources", "lib", "include"))", From bc074bd746440c5b5c37756989ab0d8f73ce7ecb Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Apr 2025 09:25:25 -0700 Subject: [PATCH 38/99] [Commands] Initial implementation of `swift package add-setting` command (#8532) ### Motivation: Adds a way to programmatically insert new settings into a package manifest. Currently only some Swift settings are supported, namely: `enable{Upcoming, Experimental}Feature`, `swiftLanguageMode` and `strictMemorySafety`; but the command could be expanded to support more Swift (C, C++, linker) settings in the future. ### Modifications: - Add a new "package" sub-command named "add-setting" that accepts a target and a list of `--swift Name[=Value]?` pairs for each new swift setting to add. Each setting would make sure that manifest is new enough to support it. - Add new manifest refactoring action - AddSwiftSetting that would add `swiftSettings:` to a target or modify the existing one. - Expands existing way to check whether manifest is too old to support custom versions. This is doe to make sure that users don't add settings that are not supported by the manifest tools version i.e. `swiftLanguageMode` was introduced in 6.0 tools. ### Result: A new `swift package add-setting --target [--swift Name[=Value]?]+` command that allows users to programmatically add new settings to package manifests. --- Sources/Commands/CMakeLists.txt | 1 + .../Commands/PackageCommands/AddSetting.swift | 148 ++++++++++ .../PackageCommands/SwiftPackageCommand.swift | 1 + .../PackageModelSyntax/AddSwiftSetting.swift | 154 ++++++++++ Sources/PackageModelSyntax/CMakeLists.txt | 1 + .../ManifestEditError.swift | 18 +- Tests/CommandsTests/PackageCommandTests.swift | 39 +++ .../ManifestEditTests.swift | 262 +++++++++++++++++- 8 files changed, 619 insertions(+), 5 deletions(-) create mode 100644 Sources/Commands/PackageCommands/AddSetting.swift create mode 100644 Sources/PackageModelSyntax/AddSwiftSetting.swift diff --git a/Sources/Commands/CMakeLists.txt b/Sources/Commands/CMakeLists.txt index 3c273289783..9f644e3d326 100644 --- a/Sources/Commands/CMakeLists.txt +++ b/Sources/Commands/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(Commands PackageCommands/AddProduct.swift PackageCommands/AddTarget.swift PackageCommands/AddTargetDependency.swift + PackageCommands/AddSetting.swift PackageCommands/APIDiff.swift PackageCommands/ArchiveSource.swift PackageCommands/CompletionCommand.swift diff --git a/Sources/Commands/PackageCommands/AddSetting.swift b/Sources/Commands/PackageCommands/AddSetting.swift new file mode 100644 index 00000000000..5a4bec1781d --- /dev/null +++ b/Sources/Commands/PackageCommands/AddSetting.swift @@ -0,0 +1,148 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Basics +import CoreCommands +import Foundation +import PackageGraph +import PackageModel +import PackageModelSyntax +import SwiftParser +import TSCBasic +import TSCUtility +import Workspace + +extension SwiftPackageCommand { + struct AddSetting: SwiftCommand { + /// The Swift language setting that can be specified on the command line. + enum SwiftSetting: String, Codable, ExpressibleByArgument, CaseIterable { + case experimentalFeature + case upcomingFeature + case languageMode + case strictMemorySafety + } + + package static let configuration = CommandConfiguration( + abstract: "Add a new setting to the manifest" + ) + + @OptionGroup(visibility: .hidden) + var globalOptions: GlobalOptions + + @Option(help: "The target to add the setting to") + var target: String + + @Option( + name: .customLong("swift"), + parsing: .unconditionalSingleValue, + help: "The Swift language setting(s) to add. Supported settings: \(SwiftSetting.allCases.map(\.rawValue).joined(separator: ", "))" + ) + var _swiftSettings: [String] + + var swiftSettings: [(SwiftSetting, String)] { + get throws { + var settings: [(SwiftSetting, String)] = [] + for rawSetting in self._swiftSettings { + let (name, value) = rawSetting.spm_split(around: "=") + + guard let setting = SwiftSetting(rawValue: name) else { + throw ValidationError("Unknown Swift language setting: \(name)") + } + + settings.append((setting, value ?? "")) + } + + return settings + } + } + + func run(_ swiftCommandState: SwiftCommandState) throws { + let workspace = try swiftCommandState.getActiveWorkspace() + guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else { + throw StringError("unknown package") + } + + try self.applyEdits(packagePath: packagePath, workspace: workspace) + } + + private func applyEdits( + packagePath: Basics.AbsolutePath, + workspace: Workspace + ) throws { + // Load the manifest file + let fileSystem = workspace.fileSystem + let manifestPath = packagePath.appending(component: Manifest.filename) + + for (setting, value) in try self.swiftSettings { + let manifestContents: ByteString + do { + manifestContents = try fileSystem.readFileContents(manifestPath) + } catch { + throw StringError("cannot find package manifest in \(manifestPath)") + } + + // Parse the manifest. + let manifestSyntax = manifestContents.withData { data in + data.withUnsafeBytes { buffer in + buffer.withMemoryRebound(to: UInt8.self) { buffer in + Parser.parse(source: buffer) + } + } + } + + let editResult: PackageEditResult + + switch setting { + case .experimentalFeature: + editResult = try AddSwiftSetting.experimentalFeature( + to: self.target, + name: value, + manifest: manifestSyntax + ) + case .upcomingFeature: + editResult = try AddSwiftSetting.upcomingFeature( + to: self.target, + name: value, + manifest: manifestSyntax + ) + case .languageMode: + guard let mode = SwiftLanguageVersion(string: value) else { + throw ValidationError("Unknown Swift language mode: \(value)") + } + + editResult = try AddSwiftSetting.languageMode( + to: self.target, + mode: mode, + manifest: manifestSyntax + ) + case .strictMemorySafety: + guard value.isEmpty else { + throw ValidationError("'strictMemorySafety' doesn't have an argument") + } + + editResult = try AddSwiftSetting.strictMemorySafety( + to: self.target, + manifest: manifestSyntax + ) + } + + try editResult.applyEdits( + to: fileSystem, + manifest: manifestSyntax, + manifestPath: manifestPath, + verbose: !self.globalOptions.logging.quiet + ) + } + } + } +} diff --git a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift index 222878ab5fc..88f6811c419 100644 --- a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift +++ b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift @@ -37,6 +37,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand { AddProduct.self, AddTarget.self, AddTargetDependency.self, + AddSetting.self, Clean.self, PurgeCache.self, Reset.self, diff --git a/Sources/PackageModelSyntax/AddSwiftSetting.swift b/Sources/PackageModelSyntax/AddSwiftSetting.swift new file mode 100644 index 00000000000..453871065f5 --- /dev/null +++ b/Sources/PackageModelSyntax/AddSwiftSetting.swift @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder +import struct TSCUtility.Version + +/// Add a swift setting to a manifest's source code. +public enum AddSwiftSetting { + /// The set of argument labels that can occur after the "targets" + /// argument in the Package initializers. + private static let argumentLabelsAfterSwiftSettings: Set = [ + "linkerSettings", + "plugins", + ] + + public static func upcomingFeature( + to target: String, + name: String, + manifest: SourceFileSyntax + ) throws -> PackageEditResult { + try self.addToTarget( + target, + name: "enableUpcomingFeature", + value: name, + firstIntroduced: .v5_8, + manifest: manifest + ) + } + + public static func experimentalFeature( + to target: String, + name: String, + manifest: SourceFileSyntax + ) throws -> PackageEditResult { + try self.addToTarget( + target, + name: "enableExperimentalFeature", + value: name, + firstIntroduced: .v5_8, + manifest: manifest + ) + } + + public static func languageMode( + to target: String, + mode: SwiftLanguageVersion, + manifest: SourceFileSyntax + ) throws -> PackageEditResult { + try self.addToTarget( + target, + name: "swiftLanguageMode", + value: mode, + firstIntroduced: .v6_0, + manifest: manifest + ) + } + + public static func strictMemorySafety( + to target: String, + manifest: SourceFileSyntax + ) throws -> PackageEditResult { + try self.addToTarget( + target, name: "strictMemorySafety", + value: String?.none, + firstIntroduced: .v6_2, + manifest: manifest + ) + } + + private static func addToTarget( + _ target: String, + name: String, + value: (some ManifestSyntaxRepresentable)?, + firstIntroduced: ToolsVersion, + manifest: SourceFileSyntax + ) throws -> PackageEditResult { + try manifest.checkManifestAtLeast(firstIntroduced) + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + guard let targetsArgument = packageCall.findArgument(labeled: "targets"), + let targetArray = targetsArgument.expression.findArrayArgument() + else { + throw ManifestEditError.cannotFindTargets + } + + guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: { + if let nameArgument = $0.findArgument(labeled: "name"), + let nameLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self), + nameLiteral.representedLiteralValue == target + { + return true + } + return false + }) else { + throw ManifestEditError.cannotFindTarget(targetName: target) + } + + if let memberRef = targetCall.calledExpression.as(MemberAccessExprSyntax.self), + memberRef.declName.baseName.text == "plugin" + { + throw ManifestEditError.cannotAddSettingsToPluginTarget + } + + let newTargetCall = if let value { + try targetCall.appendingToArrayArgument( + label: "swiftSettings", + trailingLabels: self.argumentLabelsAfterSwiftSettings, + newElement: ".\(raw: name)(\(value.asSyntax()))" + ) + } else { + try targetCall.appendingToArrayArgument( + label: "swiftSettings", + trailingLabels: self.argumentLabelsAfterSwiftSettings, + newElement: ".\(raw: name)" + ) + } + + return PackageEditResult( + manifestEdits: [ + .replace(targetCall, with: newTargetCall.description), + ] + ) + } +} + +extension SwiftLanguageVersion: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + if !Self.supportedSwiftLanguageVersions.contains(self) { + return ".version(\"\(raw: rawValue)\")" + } + + if minor == 0 { + return ".v\(raw: major)" + } + + return ".v\(raw: major)_\(raw: minor)" + } +} diff --git a/Sources/PackageModelSyntax/CMakeLists.txt b/Sources/PackageModelSyntax/CMakeLists.txt index c034d8d1705..02142c690da 100644 --- a/Sources/PackageModelSyntax/CMakeLists.txt +++ b/Sources/PackageModelSyntax/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(PackageModelSyntax AddPackageDependency.swift AddProduct.swift + AddSwiftSetting.swift AddTarget.swift AddTargetDependency.swift ManifestEditError.swift diff --git a/Sources/PackageModelSyntax/ManifestEditError.swift b/Sources/PackageModelSyntax/ManifestEditError.swift index aaaf3351166..7f02e0b2683 100644 --- a/Sources/PackageModelSyntax/ManifestEditError.swift +++ b/Sources/PackageModelSyntax/ManifestEditError.swift @@ -21,7 +21,8 @@ package enum ManifestEditError: Error { case cannotFindTargets case cannotFindTarget(targetName: String) case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax) - case oldManifest(ToolsVersion) + case oldManifest(ToolsVersion, expected: ToolsVersion) + case cannotAddSettingsToPluginTarget } extension ToolsVersion { @@ -41,8 +42,10 @@ extension ManifestEditError: CustomStringConvertible { "unable to find target named '\(name)' in package" case .cannotFindArrayLiteralArgument(argumentName: let name, node: _): "unable to find array literal for '\(name)' argument" - case .oldManifest(let version): - "package manifest version \(version) is too old: please update to manifest version \(ToolsVersion.minimumManifestEditVersion) or newer" + case .oldManifest(let version, let expectedVersion): + "package manifest version \(version) is too old: please update to manifest version \(expectedVersion) or newer" + case .cannotAddSettingsToPluginTarget: + "plugin targets do not support settings" } } } @@ -53,7 +56,14 @@ extension SourceFileSyntax { func checkEditManifestToolsVersion() throws { let toolsVersion = try ToolsVersionParser.parse(utf8String: description) if toolsVersion < ToolsVersion.minimumManifestEditVersion { - throw ManifestEditError.oldManifest(toolsVersion) + throw ManifestEditError.oldManifest(toolsVersion, expected: ToolsVersion.minimumManifestEditVersion) + } + } + + func checkManifestAtLeast(_ version: ToolsVersion) throws { + let toolsVersion = try ToolsVersionParser.parse(utf8String: description) + if toolsVersion < version { + throw ManifestEditError.oldManifest(toolsVersion, expected: version) } } } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index eb1f658d432..a28f411b731 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -1265,6 +1265,45 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { XCTAssertMatch(contents, .contains(#""MyLib""#)) } } + + func testPackageAddSetting() async throws { + try await testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageA") + try fs.createDirectory(path) + + try fs.writeFileContents(path.appending("Package.swift"), string: + """ + // swift-tools-version: 6.2 + import PackageDescription + let package = Package( + name: "A", + targets: [ .target(name: "test") ] + ) + """ + ) + + _ = try await execute([ + "add-setting", + "--target", "test", + "--swift", "languageMode=6", + "--swift", "upcomingFeature=ExistentialAny:migratable", + "--swift", "experimentalFeature=TrailingCommas", + "--swift", "strictMemorySafety" + ], packagePath: path) + + let manifest = path.appending("Package.swift") + XCTAssertFileExists(manifest) + let contents: String = try fs.readFileContents(manifest) + + XCTAssertMatch(contents, .contains(#"swiftSettings:"#)) + XCTAssertMatch(contents, .contains(#".swiftLanguageMode(.v6)"#)) + XCTAssertMatch(contents, .contains(#".enableUpcomingFeature("ExistentialAny:migratable")"#)) + XCTAssertMatch(contents, .contains(#".enableExperimentalFeature("TrailingCommas")"#)) + XCTAssertMatch(contents, .contains(#".strictMemorySafety"#)) + } + } + func testPackageEditAndUnedit() async throws { try await fixture(name: "Miscellaneous/PackageEdit") { fixturePath in let fooPath = fixturePath.appending("foo") diff --git a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift index bf76ac49683..16e1bf74f14 100644 --- a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift +++ b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift @@ -296,7 +296,7 @@ class ManifestEditTests: XCTestCase { """ ) ) { (error: ManifestEditError) in - if case .oldManifest(.v5_4) = error { + if case .oldManifest(.v5_4, .v5_5) = error { return true } else { return false @@ -614,6 +614,266 @@ class ManifestEditTests: XCTestCase { ) } } + + func testAddSwiftSettings() throws { + XCTAssertThrows( + try AddSwiftSetting.upcomingFeature( + to: "MyTest", + name: "ExistentialAny", + manifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + targets: [ + .executableTarget( + name: "MyTest" + ) + ] + ) + """ + ) + ) { (error: ManifestEditError) in + if case .oldManifest(.v5_5, .v5_8) = error { + return true + } else { + return false + } + } + + XCTAssertThrows( + try AddSwiftSetting.upcomingFeature( + to: "OtherTest", + name: "ExistentialAny", + manifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .executableTarget( + name: "MyTest" + ) + ] + ) + """ + ) + ) { (error: ManifestEditError) in + if case .cannotFindTarget("OtherTest") = error { + return true + } else { + return false + } + } + + XCTAssertThrows( + try AddSwiftSetting.upcomingFeature( + to: "MyPlugin", + name: "ExistentialAny", + manifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .plugin( + name: "MyPlugin", + capability: .buildTool + ) + ] + ) + """ + ) + ) { (error: ManifestEditError) in + if case .cannotAddSettingsToPluginTarget = error { + return true + } else { + return false + } + } + + + try assertManifestRefactor(""" + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .enableUpcomingFeature("ExistentialAny:migratable"), + ] + ), + ] + ) + """) { manifest in + try AddSwiftSetting.upcomingFeature( + to: "MyTest", + name: "ExistentialAny:migratable", + manifest: manifest + ) + } + + try assertManifestRefactor(""" + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .enableExperimentalFeature("Extern") + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.8 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .enableExperimentalFeature("Extern"), + .enableExperimentalFeature("TrailingComma"), + ] + ), + ] + ) + """) { manifest in + try AddSwiftSetting.experimentalFeature( + to: "MyTest", + name: "TrailingComma", + manifest: manifest + ) + } + + try assertManifestRefactor(""" + // swift-tools-version: 6.2 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 6.2 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .strictMemorySafety, + ] + ), + ] + ) + """) { manifest in + try AddSwiftSetting.strictMemorySafety( + to: "MyTest", + manifest: manifest + ) + } + + try assertManifestRefactor(""" + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .swiftLanguageMode(.v5), + ] + ), + ] + ) + """) { manifest in + try AddSwiftSetting.languageMode( + to: "MyTest", + mode: .init(string: "5")!, + manifest: manifest + ) + } + + // Custom language mode + try assertManifestRefactor(""" + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ] + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 6.0 + let package = Package( + name: "packages", + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + ], + swiftSettings: [ + .swiftLanguageMode(.version("6.2")), + ] + ), + ] + ) + """) { manifest in + try AddSwiftSetting.languageMode( + to: "MyTest", + mode: .init(string: "6.2")!, + manifest: manifest + ) + } + } } /// Assert that applying the moveSingleTargetSources operation to the manifest /// produces the expected file system. From e9522442e88a1508efb51b7981b769ae5204d546 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Thu, 24 Apr 2025 09:29:22 -0700 Subject: [PATCH 39/99] [Commands] Initial implementation of `swift package add-setting` command (#8532) ### Motivation: Adds a way to programmatically insert new settings into a package manifest. Currently only some Swift settings are supported, namely: `enable{Upcoming, Experimental}Feature`, `swiftLanguageMode` and `strictMemorySafety`; but the command could be expanded to support more Swift (C, C++, linker) settings in the future. ### Modifications: - Add a new "package" sub-command named "add-setting" that accepts a target and a list of `--swift Name[=Value]?` pairs for each new swift setting to add. Each setting would make sure that manifest is new enough to support it. - Add new manifest refactoring action - AddSwiftSetting that would add `swiftSettings:` to a target or modify the existing one. - Expands existing way to check whether manifest is too old to support custom versions. This is doe to make sure that users don't add settings that are not supported by the manifest tools version i.e. `swiftLanguageMode` was introduced in 6.0 tools. ### Result: A new `swift package add-setting --target [--swift Name[=Value]?]+` command that allows users to programmatically add new settings to package manifests. From b842488a5480b7a28277c3dc3b57a23f3135288d Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Thu, 24 Apr 2025 13:42:28 -0700 Subject: [PATCH 40/99] Output Swift Build PIF JSON for Graphviz visualization (#8539) ### Motivation: Improve visualization/debuggability of the PIF JSON sent over to Swift Build (when using the new `--build-system swiftbuild` option). ### Modifications: This PR introduces the `--print-pif-manifest-graph` option to write out the PIF JSON sent to Swift Build as a Graphviz file. This follows a similar design we used for the existing `--print-manifest-job-graph` option. All the serialization happens in the new `DotPIFSerializer.swift` file, with this function as the entry point: func writePIF(_ workspace: PIF.Workspace, toDOT outputStream: OutputByteStream) ### Result: With Graphviz tool (https://graphviz.org) installed (i.e., provides the `dot` command below), we can then run commands like this: $ swift build --build-system swiftbuild --print-pif-manifest-graph | dot -Tpng > PIF.png --- Sources/Commands/SwiftBuildCommand.swift | 28 ++- .../Utilities/DOTManifestSerializer.swift | 6 +- .../BuildParameters/BuildParameters.swift | 2 + .../SwiftBuildSupport/DotPIFSerializer.swift | 227 ++++++++++++++++++ Sources/SwiftBuildSupport/PIF.swift | 17 +- Sources/SwiftBuildSupport/PIFBuilder.swift | 24 +- .../PackagePIFBuilder+Helpers.swift | 1 - .../SwiftBuildSupport/SwiftBuildSystem.swift | 4 +- 8 files changed, 290 insertions(+), 19 deletions(-) create mode 100644 Sources/SwiftBuildSupport/DotPIFSerializer.swift diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 76ff6c09322..1c6dfb45fe8 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -22,6 +22,7 @@ import PackageGraph import SPMBuildCore import XCBuildSupport +import SwiftBuildSupport import class Basics.AsyncProcess import var TSCBasic.stdoutStream @@ -86,9 +87,14 @@ struct BuildCommandOptions: ParsableArguments { /// Whether to output a graphviz file visualization of the combined job graph for all targets @Flag(name: .customLong("print-manifest-job-graph"), - help: "Write the command graph for the build manifest as a graphviz file") + help: "Write the command graph for the build manifest as a Graphviz file") var printManifestGraphviz: Bool = false + /// Whether to output a graphviz file visualization of the PIF JSON sent to Swift Build. + @Flag(name: .customLong("print-pif-manifest-graph"), + help: "Write the PIF JSON sent to Swift Build as a Graphviz file") + var printPIFManifestGraphviz: Bool = false + /// Specific target to build. @Option(help: "Build the specified target") var target: String? @@ -144,7 +150,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { } let buildManifest = try await buildOperation.getBuildManifest() var serializer = DOTManifestSerializer(manifest: buildManifest) - // print to stdout + // Print to stdout. let outputStream = stdoutStream serializer.writeDOT(to: outputStream) outputStream.flush() @@ -162,8 +168,22 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { productsBuildParameters.testingParameters.enableCodeCoverage = true toolsBuildParameters.testingParameters.enableCodeCoverage = true } - - try await build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) + + if self.options.printPIFManifestGraphviz { + productsBuildParameters.printPIFManifestGraphviz = true + toolsBuildParameters.printPIFManifestGraphviz = true + } + + do { + try await build( + swiftCommandState, + subset: subset, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters + ) + } catch SwiftBuildSupport.PIFGenerationError.printedPIFManifestGraphviz { + throw ExitCode.success + } } private func build( diff --git a/Sources/Commands/Utilities/DOTManifestSerializer.swift b/Sources/Commands/Utilities/DOTManifestSerializer.swift index 234685c51be..448b41643d1 100644 --- a/Sources/Commands/Utilities/DOTManifestSerializer.swift +++ b/Sources/Commands/Utilities/DOTManifestSerializer.swift @@ -14,7 +14,7 @@ import LLBuildManifest import protocol TSCBasic.OutputByteStream -/// Serializes an LLBuildManifest graph to a .dot file +/// Serializes an LLBuildManifest graph to a .dot file. struct DOTManifestSerializer { var kindCounter = [String: Int]() var hasEmittedStyling = Set() @@ -25,7 +25,7 @@ struct DOTManifestSerializer { self.manifest = manifest } - /// Gets a unique label for a job name + /// Gets a unique label for a job name. mutating func label(for command: Command) -> String { let toolName = "\(type(of: command.tool).name)" var label = toolName @@ -36,7 +36,7 @@ struct DOTManifestSerializer { return label } - /// Quote the name and escape the quotes and backslashes + /// Quote the name and escape the quotes and backslashes. func quoteName(_ name: String) -> String { "\"" + name.replacing("\"", with: "\\\"") .replacing("\\", with: "\\\\") + "\"" diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index a7e926d2c2c..f82f050328b 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -127,6 +127,8 @@ public struct BuildParameters: Encodable { public var shouldSkipBuilding: Bool + public var printPIFManifestGraphviz: Bool = false + /// Do minimal build to prepare for indexing public var prepareForIndexing: PrepareForIndexingMode diff --git a/Sources/SwiftBuildSupport/DotPIFSerializer.swift b/Sources/SwiftBuildSupport/DotPIFSerializer.swift new file mode 100644 index 00000000000..c7c681ecb12 --- /dev/null +++ b/Sources/SwiftBuildSupport/DotPIFSerializer.swift @@ -0,0 +1,227 @@ +// +// DotPIFSerializer.swift +// SwiftPM +// +// Created by Paulo Mattos on 2025-04-18. +// + +import Basics +import Foundation +import protocol TSCBasic.OutputByteStream + +#if canImport(SwiftBuild) +import SwiftBuild + +/// Serializes the specified PIF as a **Graphviz** directed graph. +/// +/// * [DOT command line](https://graphviz.org/doc/info/command.html) +/// * [DOT language specs](https://graphviz.org/doc/info/lang.html) +func writePIF(_ workspace: PIF.Workspace, toDOT outputStream: OutputByteStream) { + var graph = DotPIFSerializer() + + graph.node( + id: workspace.id, + label: "\n\(workspace.id)", + shape: "box3d", + color: .black, + fontsize: 7 + ) + + for project in workspace.projects.map(\.underlying) { + graph.edge(from: workspace.id, to: project.id, color: .lightskyblue) + graph.node( + id: project.id, + label: "\n\(project.id)", + shape: "box3d", + color: .gray56, + fontsize: 7 + ) + + for target in project.targets { + graph.edge(from: project.id, to: target.id, color: .lightskyblue) + + switch target { + case .target(let target): + graph.node( + id: target.id, + label: "\n\(target.id)\nproduct type: \(target.productType)\n\(target.buildPhases.summary)", + shape: "box", + color: .gray88, + fontsize: 5 + ) + + case .aggregate: + graph.node( + id: target.id, + label: "\n\(target.id)", + shape: "folder", + color: .gray88, + fontsize: 5, + style: "bold" + ) + } + + for targetDependency in target.common.dependencies { + let linked = target.isLinkedAgainst(dependencyId: targetDependency.targetId) + graph.edge(from: target.id, to: targetDependency.targetId, color: .gray40, style: linked ? "filled" : "dotted") + } + } + } + + graph.write(to: outputStream) +} + +fileprivate struct DotPIFSerializer { + private var objects: [String] = [] + + mutating func write(to outputStream: OutputByteStream) { + func write(_ object: String) { outputStream.write("\(object)\n") } + + write("digraph PIF {") + write(" dpi=400;") // i.e., MacBook Pro 16" is 226 pixels per inch (3072 x 1920). + for object in objects { + write(" \(object);") + } + write("}") + } + + mutating func node( + id: PIF.GUID, + label: String? = nil, + shape: String? = nil, + color: Color? = nil, + fontname: String? = "SF Mono Light", + fontsize: Int? = nil, + style: String? = nil, + margin: Int? = nil + ) { + var attributes: [String] = [] + + if let label { attributes.append("label=\(label.quote)") } + if let shape { attributes.append("shape=\(shape)") } + if let color { attributes.append("color=\(color)") } + + if let fontname { attributes.append("fontname=\(fontname.quote)") } + if let fontsize { attributes.append("fontsize=\(fontsize)") } + + if let style { attributes.append("style=\(style)") } + if let margin { attributes.append("margin=\(margin)") } + + var node = "\(id.quote)" + if !attributes.isEmpty { + let attributesList = attributes.joined(separator: ", ") + node += " [\(attributesList)]" + } + objects.append(node) + } + + mutating func edge( + from left: PIF.GUID, + to right: PIF.GUID, + color: Color? = nil, + style: String? = nil + ) { + var attributes: [String] = [] + + if let color { attributes.append("color=\(color)") } + if let style { attributes.append("style=\(style)") } + + var edge = "\(left.quote) -> \(right.quote)" + if !attributes.isEmpty { + let attributesList = attributes.joined(separator: ", ") + edge += " [\(attributesList)]" + } + objects.append(edge) + } + + /// Graphviz default color scheme is **X11**: + /// * https://graphviz.org/doc/info/colors.html + enum Color: String { + case black + case gray + case gray40 + case gray56 + case gray88 + case lightskyblue + } +} + +// MARK: - Helpers + +fileprivate extension ProjectModel.BaseTarget { + func isLinkedAgainst(dependencyId: ProjectModel.GUID) -> Bool { + for buildPhase in self.common.buildPhases { + switch buildPhase { + case .frameworks(let frameworksPhase): + for buildFile in frameworksPhase.files { + switch buildFile.ref { + case .reference(let id): + if dependencyId == id { return true } + case .targetProduct(let id): + if dependencyId == id { return true } + } + } + + case .sources, .shellScript, .headers, .copyFiles, .copyBundleResources: + break + } + } + return false + } +} + +fileprivate extension [ProjectModel.BuildPhase] { + var summary: String { + var phases: [String] = [] + + for buildPhase in self { + switch buildPhase { + case .sources(let sourcesPhase): + var sources = "sources: " + if sourcesPhase.files.count == 1 { + sources += "1 source file" + } else { + sources += "\(sourcesPhase.files.count) source files" + } + phases.append(sources) + + case .frameworks(let frameworksPhase): + var frameworks = "frameworks: " + if frameworksPhase.files.count == 1 { + frameworks += "1 linked target" + } else { + frameworks += "\(frameworksPhase.files.count) linked targets" + } + phases.append(frameworks) + + case .shellScript: + phases.append("shellScript: 1 shell script") + + case .headers, .copyFiles, .copyBundleResources: + break + } + } + + guard !phases.isEmpty else { return "" } + return phases.joined(separator: "\n") + } +} + +fileprivate extension PIF.GUID { + var quote: String { + self.value.quote + } +} + +fileprivate extension String { + /// Quote the name and escape the quotes and backslashes. + var quote: String { + "\"" + self + .replacing("\"", with: "\\\"") + .replacing("\\", with: "\\\\") + .replacing("\n", with: "\\n") + + "\"" + } +} + +#endif diff --git a/Sources/SwiftBuildSupport/PIF.swift b/Sources/SwiftBuildSupport/PIF.swift index 8c74f90ffcb..b455edbd32b 100644 --- a/Sources/SwiftBuildSupport/PIF.swift +++ b/Sources/SwiftBuildSupport/PIF.swift @@ -117,18 +117,18 @@ public enum PIF { public final class Workspace: HighLevelObject { override class var type: String { "workspace" } - public let guid: GUID + public let id: GUID public var name: String public var path: AbsolutePath public var projects: [Project] var signature: String? - public init(guid: GUID, name: String, path: AbsolutePath, projects: [ProjectModel.Project]) { - precondition(!guid.value.isEmpty) + public init(id: GUID, name: String, path: AbsolutePath, projects: [ProjectModel.Project]) { + precondition(!id.value.isEmpty) precondition(!name.isEmpty) precondition(Set(projects.map(\.id)).count == projects.count) - self.guid = guid + self.id = id self.name = name self.path = path self.projects = projects.map { Project(wrapping: $0) } @@ -145,7 +145,7 @@ public enum PIF { var superContainer = encoder.container(keyedBy: HighLevelObject.CodingKeys.self) var contents = superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - try contents.encode("\(guid)", forKey: .guid) + try contents.encode("\(id)", forKey: .guid) try contents.encode(name, forKey: .name) try contents.encode(path, forKey: .path) try contents.encode(projects.map(\.signature), forKey: .projects) @@ -158,11 +158,12 @@ public enum PIF { } } + // FIXME: Delete this (https://github.com/swiftlang/swift-package-manager/issues/8552). public required init(from decoder: Decoder) throws { let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) let contents = try superContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .contents) - self.guid = try contents.decode(GUID.self, forKey: .guid) + self.id = try contents.decode(GUID.self, forKey: .guid) self.name = try contents.decode(String.self, forKey: .name) self.path = try contents.decode(AbsolutePath.self, forKey: .path) self.projects = try contents.decode([Project].self, forKey: .projects) @@ -205,6 +206,7 @@ public enum PIF { } } + // FIXME: Delete this (https://github.com/swiftlang/swift-package-manager/issues/8552). public required init(from decoder: Decoder) throws { let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) self.underlying = try superContainer.decode(ProjectModel.Project.self, forKey: .contents) @@ -242,7 +244,8 @@ public enum PIF { } public required init(from decoder: Decoder) throws { - // FIXME: Remove all support for decoding PIF objects in SwiftBuildSupport? rdar://149003797 + // FIXME: Remove all support for decoding PIF objects in SwiftBuildSupport? + // (https://github.com/swiftlang/swift-package-manager/issues/8552) fatalError("Decoding not implemented") /* let superContainer = try decoder.container(keyedBy: HighLevelObject.CodingKeys.self) diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 56f91274912..cb6866018ae 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -22,6 +22,7 @@ import SPMBuildCore import func TSCBasic.memoize import func TSCBasic.topologicalSort +import var TSCBasic.stdoutStream #if canImport(SwiftBuild) import enum SwiftBuild.ProjectModel @@ -98,7 +99,8 @@ public final class PIFBuilder { /// - Returns: The package graph in the JSON PIF format. func generatePIF( prettyPrint: Bool = true, - preservePIFModelStructure: Bool = false + preservePIFModelStructure: Bool = false, + printPIFManifestGraphviz: Bool = false ) throws -> String { #if canImport(SwiftBuild) let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder() @@ -114,7 +116,17 @@ public final class PIFBuilder { let pifData = try encoder.encode(topLevelObject) let pifString = String(decoding: pifData, as: UTF8.self) - + + if printPIFManifestGraphviz { + // Print dot graph to stdout. + writePIF(topLevelObject.workspace, toDOT: stdoutStream) + stdoutStream.flush() + + // Abort the build process, ensuring we don't add + // further noise to stdout (and break `dot` graph parsing). + throw PIFGenerationError.printedPIFManifestGraphviz + } + return pifString #else fatalError("Swift Build support is not linked in.") @@ -167,7 +179,7 @@ public final class PIFBuilder { ) let workspace = PIF.Workspace( - guid: "Workspace:\(rootPackage.path.pathString)", + id: "Workspace:\(rootPackage.path.pathString)", name: rootPackage.manifest.displayName, // TODO: use identity instead? path: rootPackage.path, projects: projects @@ -405,6 +417,9 @@ public enum PIFGenerationError: Error { versions: [SwiftLanguageVersion], supportedVersions: [SwiftLanguageVersion] ) + + /// Early build termination when using `--print-pif-manifest-graph`. + case printedPIFManifestGraphviz } extension PIFGenerationError: CustomStringConvertible { @@ -423,6 +438,9 @@ extension PIFGenerationError: CustomStringConvertible { ): "None of the Swift language versions used in target '\(target)' settings are supported." + " (given: \(given), supported: \(supported))" + + case .printedPIFManifestGraphviz: + "Printed PIF manifest as graphviz" } } } diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index 8e4fdfc3e7a..e1689c1b086 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -969,7 +969,6 @@ extension ProjectModel.BuildSettings.Platform { case .windows: .windows case .wasi: .wasi case .openbsd: .openbsd - case .freebsd: .freebsd default: preconditionFailure("Unexpected platform: \(platform.name)") } } diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 374a02cf517..38d3272beaf 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -238,7 +238,9 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } let pifBuilder = try await getPIFBuilder() - let pif = try pifBuilder.generatePIF() + let pif = try pifBuilder.generatePIF( + printPIFManifestGraphviz: buildParameters.printPIFManifestGraphviz + ) try self.fileSystem.writeIfChanged(path: buildParameters.pifManifest, string: pif) From 5da13be6bd815e5c7323288e00c8b203542bc48d Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:56:16 -0400 Subject: [PATCH 41/99] Enable more plugin related tests for non-macOS platforms (#8477) There are tests that were formerly failing due to missing support for plugins in the old PIF builder. Enable these test for better coverage with the new builder. * Fix spelling in one of the APIs * Remove macOS-specific linker flag in the PIF builder and use a setting that is target platform neutral * Update skip comments for tests based on new analysis of the failures --- .../SwiftBuildSupport/PackagePIFBuilder.swift | 6 +++--- .../PackagePIFProjectBuilder+Modules.swift | 10 ++-------- Tests/CommandsTests/BuildCommandTests.swift | 9 --------- Tests/CommandsTests/PackageCommandTests.swift | 8 +------- Tests/CommandsTests/TestCommandTests.swift | 16 ++++++++-------- 5 files changed, 14 insertions(+), 35 deletions(-) diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 88a2bee948f..ba854b1f461 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -651,9 +651,9 @@ extension PackagePIFBuilder.LinkedPackageBinary { init?(dependency: ResolvedModule.Dependency, package: ResolvedPackage) { switch dependency { - case .product(let producutDependency, _): - guard producutDependency.hasSourceTargets else { return nil } - self.init(name: producutDependency.name, packageName: package.name, type: .product) + case .product(let productDependency, _): + guard productDependency.hasSourceTargets else { return nil } + self.init(name: productDependency.name, packageName: package.name, type: .product) case .module(let moduleDependency, _): self.init(module: moduleDependency, package: package) diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index ca71fa6f7f3..028c9e537ea 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -504,20 +504,14 @@ extension PackagePIFProjectBuilder { } // Additional settings for the linker. - let baselineOTHER_LDFLAGS: [String] let enableDuplicateLinkageCulling = UserDefaults.standard.bool( forKey: "IDESwiftPackagesEnableDuplicateLinkageCulling", defaultValue: true ) if enableDuplicateLinkageCulling { - baselineOTHER_LDFLAGS = [ - "-Wl,-no_warn_duplicate_libraries", - "$(inherited)" - ] - } else { - baselineOTHER_LDFLAGS = ["$(inherited)"] + impartedSettings[.LD_WARN_DUPLICATE_LIBRARIES] = "NO" } - impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + baselineOTHER_LDFLAGS + impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + ["$(inherited)"] impartedSettings[.OTHER_LDRFLAGS] = [] log( .debug, diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index ca39fc6bbce..2c700c1e7e7 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -956,13 +956,4 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { try await super.testBuildSystemDefaultSettings() } - - override func testBuildCompleteMessage() async throws { - #if os(Linux) - throw XCTSkip("SWBINTTODO: Need to properly set LD_LIBRARY_PATH on linux") - #else - try await super.testBuildCompleteMessage() - #endif - } - } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index a28f411b731..ec2d28943e2 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -3898,15 +3898,9 @@ class PackageCommandSwiftBuildTests: PackageCommandTestCase { } override func testCommandPluginBuildingCallbacks() async throws { - throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") + throw XCTSkip("SWBINTTODO: Test fails because plugin is not producing expected output to stdout.") } override func testCommandPluginBuildTestability() async throws { throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") } - -#if !os(macOS) - override func testCommandPluginTestingCallbacks() async throws { - throw XCTSkip("SWBINTTODO: Test fails on inability to find libclang on Linux. Also, plugins are not currently supported") - } -#endif } diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 436839d9fef..f4fece83f0c 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -701,35 +701,35 @@ class TestCommandSwiftBuildTests: TestCommandTestCase { #if !os(macOS) override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestSkip() async throws { - throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputWhenEmpty() async throws { - throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestFilter() async throws { - throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestParallel() async throws { - throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } #endif } From 2b659da51edc00b7254c358e9ea6abd25346aea6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 25 Apr 2025 12:03:58 +0100 Subject: [PATCH 42/99] Revert "Enable more plugin related tests for non-macOS platforms" (#8557) Reverts swiftlang/swift-package-manager#8477 due to Amazon Linux 2 failures https://ci.swift.org/job/oss-swift-package-amazon-linux-2/4177/console --- .../SwiftBuildSupport/PackagePIFBuilder.swift | 6 +++--- .../PackagePIFProjectBuilder+Modules.swift | 10 ++++++++-- Tests/CommandsTests/BuildCommandTests.swift | 9 +++++++++ Tests/CommandsTests/PackageCommandTests.swift | 8 +++++++- Tests/CommandsTests/TestCommandTests.swift | 16 ++++++++-------- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index ba854b1f461..88a2bee948f 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -651,9 +651,9 @@ extension PackagePIFBuilder.LinkedPackageBinary { init?(dependency: ResolvedModule.Dependency, package: ResolvedPackage) { switch dependency { - case .product(let productDependency, _): - guard productDependency.hasSourceTargets else { return nil } - self.init(name: productDependency.name, packageName: package.name, type: .product) + case .product(let producutDependency, _): + guard producutDependency.hasSourceTargets else { return nil } + self.init(name: producutDependency.name, packageName: package.name, type: .product) case .module(let moduleDependency, _): self.init(module: moduleDependency, package: package) diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index 028c9e537ea..ca71fa6f7f3 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -504,14 +504,20 @@ extension PackagePIFProjectBuilder { } // Additional settings for the linker. + let baselineOTHER_LDFLAGS: [String] let enableDuplicateLinkageCulling = UserDefaults.standard.bool( forKey: "IDESwiftPackagesEnableDuplicateLinkageCulling", defaultValue: true ) if enableDuplicateLinkageCulling { - impartedSettings[.LD_WARN_DUPLICATE_LIBRARIES] = "NO" + baselineOTHER_LDFLAGS = [ + "-Wl,-no_warn_duplicate_libraries", + "$(inherited)" + ] + } else { + baselineOTHER_LDFLAGS = ["$(inherited)"] } - impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + ["$(inherited)"] + impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + baselineOTHER_LDFLAGS impartedSettings[.OTHER_LDRFLAGS] = [] log( .debug, diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 2c700c1e7e7..ca39fc6bbce 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -956,4 +956,13 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { try await super.testBuildSystemDefaultSettings() } + + override func testBuildCompleteMessage() async throws { + #if os(Linux) + throw XCTSkip("SWBINTTODO: Need to properly set LD_LIBRARY_PATH on linux") + #else + try await super.testBuildCompleteMessage() + #endif + } + } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index ec2d28943e2..a28f411b731 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -3898,9 +3898,15 @@ class PackageCommandSwiftBuildTests: PackageCommandTestCase { } override func testCommandPluginBuildingCallbacks() async throws { - throw XCTSkip("SWBINTTODO: Test fails because plugin is not producing expected output to stdout.") + throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") } override func testCommandPluginBuildTestability() async throws { throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") } + +#if !os(macOS) + override func testCommandPluginTestingCallbacks() async throws { + throw XCTSkip("SWBINTTODO: Test fails on inability to find libclang on Linux. Also, plugins are not currently supported") + } +#endif } diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index f4fece83f0c..436839d9fef 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -701,35 +701,35 @@ class TestCommandSwiftBuildTests: TestCommandTestCase { #if !os(macOS) override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") } override func testSwiftTestSkip() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } override func testSwiftTestXMLOutputWhenEmpty() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } override func testSwiftTestFilter() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } override func testSwiftTestParallel() async throws { - throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") + throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") } #endif } From 5b2118b950f8f3691b5ad4ce34b9b0173031979c Mon Sep 17 00:00:00 2001 From: johnbute Date: Fri, 25 Apr 2025 09:50:24 -0400 Subject: [PATCH 43/99] Prevent non-targets from depending on test targets (#8513) Prevent non-targets from depending on test targets ### Motivation: Fix for rdar://149007214 Currently, only test targets are allowed to have dependencies on other test targets. ### Modifications: Simply checked for each non-test target, if their dependency is of type test. If it is, throw an error. ### Result: Swift package manager will give an error explaining that only testTargets can depend on other testTargets Fixes #8478 --------- Co-authored-by: John Bute --- .../ManifestLoader+Validation.swift | 6 + Sources/PackageLoading/PackageBuilder.swift | 13 ++ .../PackageGraphTests/ModulesGraphTests.swift | 175 ++++++++++++++++++ 3 files changed, 194 insertions(+) diff --git a/Sources/PackageLoading/ManifestLoader+Validation.swift b/Sources/PackageLoading/ManifestLoader+Validation.swift index a0213af9239..972cf4648e2 100644 --- a/Sources/PackageLoading/ManifestLoader+Validation.swift +++ b/Sources/PackageLoading/ManifestLoader+Validation.swift @@ -311,6 +311,12 @@ extension Basics.Diagnostic { return .error("\(messagePrefix) in dependencies of target '\(targetName)'; valid packages are: \(validPackages.map{ "\($0.descriptionForValidation)" }.joined(separator: ", "))") } + static func invalidDependencyOnTestTarget(dependency: Module.Dependency, targetName: String) -> Self { + .error( + "Invalid dependency: '\(targetName)' cannot depend on test target dependency '\(dependency.name)'. Only test targets can depend on other test targets" + ) + } + static func invalidBinaryLocation(targetName: String) -> Self { .error("invalid location for binary target '\(targetName)'") } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 8524e8ff707..df8641cf00c 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -951,6 +951,19 @@ public final class PackageBuilder { } } + // Ensure non-test targets do not depend on test targets. + // Only test targets are allowed to have dependencies on other test targets. + if !potentialModule.isTest { + for dependency in dependencies { + if let depTarget = dependency.module, depTarget.type == .test { + self.observabilityScope.emit(.invalidDependencyOnTestTarget( + dependency: dependency, + targetName: potentialModule.name + )) + } + } + } + // Create the build setting assignment table for this target. let buildSettings = try self.buildSettings( for: manifestTarget, diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index d840f8e189a..02b3a907834 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -412,6 +412,181 @@ final class ModulesGraphTests: XCTestCase { } } + func testLibraryInvalidDependencyOnTestTarget() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/Foo/Foo.swift", + "/Foo/Tests/FooTest/FooTest.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v6_0, + products: [ + ProductDescription(name: "Foo", type: .library(.automatic), targets: ["FooTest"]), + ], + targets: [ + TargetDescription(name: "Foo", dependencies: ["FooTest"]), + TargetDescription(name: "FooTest", type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "Invalid dependency: 'Foo' cannot depend on test target dependency 'FooTest'. Only test targets can depend on other test targets", + severity: .error + ) + } + } + + func testExecutableInvalidDependencyOnTestTarget() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/Foo/main.swift", + "/Foo/Tests/FooTest/FooTest.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v6_0, + targets: [ + TargetDescription(name: "Foo", dependencies: ["FooTest"], type: .executable), + TargetDescription(name: "FooTest", type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "Invalid dependency: 'Foo' cannot depend on test target dependency 'FooTest'. Only test targets can depend on other test targets", + severity: .error + ) + } + } + + func testPluginInvalidDependencyOnTestTarget() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Plugins/Foo/main.swift", + "/Foo/Tests/FooTest/FooTest.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v6_0, + targets: [ + TargetDescription( + name: "Foo", + dependencies: ["FooTest"], + type: .plugin, + pluginCapability: .buildTool + ), + TargetDescription(name: "FooTest", type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "Invalid dependency: 'Foo' cannot depend on test target dependency 'FooTest'. Only test targets can depend on other test targets", + severity: .error + ) + } + } + + func testMacroInvalidDependencyOnTestTarget() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/Foo/main.swift", + "/Foo/Tests/FooTest/FooTest.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v6_0, + targets: [ + TargetDescription( + name: "Foo", + dependencies: ["FooTest"], + type: .macro + ), + TargetDescription(name: "FooTest", type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "Invalid dependency: 'Foo' cannot depend on test target dependency 'FooTest'. Only test targets can depend on other test targets", + severity: .error + ) + } + } + + + func testValidDependencyOnTestTarget() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Tests/Foo/Foo.swift", + "/Foo/Tests/FooTest/FooTest.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v6_0, + products: [ + ], + targets: [ + TargetDescription(name: "Foo", dependencies: ["FooTest"], type: .test), + TargetDescription(name: "FooTest", type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + } + // Make sure there is no error when we reference Test targets in a package and then // use it as a dependency to another package. SR-2353 func testTestTargetDeclInExternalPackage() throws { From ed3046151ceea7d51726f595b64a6345a8fc7cf6 Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Fri, 25 Apr 2025 07:53:20 -0700 Subject: [PATCH 44/99] Adds DocC catalog to host SwiftPM documentation (#8487) Adds a `Documentation.docc` directory (a bare DocC catalog) as a stub for migrating existing content in `Documentation/` from general markdown into DocC, as well as providing a base for expanding that documentation. Follow up PRs will bring over and convert content from the `Documentation` directory more piecemeal --- Package.swift | 8 ++++++++ .../Documentation.docc/SwiftPackageManager.md | 14 ++++++++++++++ Sources/PackageManagerDocs/EmptyFile.swift | 0 3 files changed, 22 insertions(+) create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md create mode 100644 Sources/PackageManagerDocs/EmptyFile.swift diff --git a/Package.swift b/Package.swift index d55eba42c2b..644c862769b 100644 --- a/Package.swift +++ b/Package.swift @@ -429,6 +429,12 @@ let package = Package( ] ), + // MARK: Documentation + + .target( + name: "PackageManagerDocs" + ), + // MARK: Package Manager Functionality .target( @@ -1043,6 +1049,8 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/apple/swift-collections.git", "1.0.1" ..< "1.2.0"), .package(url: "https://github.com/apple/swift-certificates.git", "1.0.1" ..< "1.6.0"), .package(url: "https://github.com/swiftlang/swift-toolchain-sqlite.git", from: "1.0.0"), + // For use in previewing documentation + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), ] } else { package.dependencies += [ diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md new file mode 100644 index 00000000000..be1804726ae --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md @@ -0,0 +1,14 @@ +# ``PackageManagerDocs`` + +@Metadata { + @DisplayName("Swift Package Manager") +} + +Organize, manage, and edit Swift packages. + +## Overview + +The Swift Package Manager is a tool for managing distribution of source code, aimed at making it easy to share your code and reuse others’ code. The tool directly addresses the challenges of compiling and linking Swift packages, managing dependencies, versioning, and supporting flexible distribution and collaboration models. + +## Topics + diff --git a/Sources/PackageManagerDocs/EmptyFile.swift b/Sources/PackageManagerDocs/EmptyFile.swift new file mode 100644 index 00000000000..e69de29bb2d From d026b3fe0a301b43358ba473b40e0ddc3517a629 Mon Sep 17 00:00:00 2001 From: kcieplak Date: Fri, 25 Apr 2025 14:39:48 -0400 Subject: [PATCH 45/99] Closes #8520 - Fix up test execution for IntegrationTests (#8538) SPM has functionality to skip a set of tests using an undocumented environment variable, '_SWIFTPM_SKIP_TESTS_LIST'. The presence of this variable causes test output to change around starting/ending of test suites. Test Suite 'All tests' started vs. Test Suite 'Selected tests' started * Remove checking for the 'All tests' string. * Check the return code of the test execution. * Set up environment variables to match bootstrap in the IntegrationTests execution. * Change bootstrap to use a call() instead of call_output() as call_output will block until the process has finished. A swift-test may take a long time. Certain CI environments expects output to stream out, and will cancel the build if none is detected for 30 mins. * Update tests with using the 'checker', to output stdout/stderr for easier debugging of failed expects. --- .../IntegrationTestSupport/Helpers.swift | 12 +-- .../Tests/IntegrationTests/BasicTests.swift | 90 ++++++++++--------- .../Tests/IntegrationTests/SwiftPMTests.swift | 49 +++++----- .../Tests/IntegrationTests/XCBuildTests.swift | 20 ++--- Utilities/bootstrap | 2 +- 5 files changed, 91 insertions(+), 82 deletions(-) diff --git a/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift b/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift index eb59b855742..8206b055b23 100644 --- a/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift +++ b/IntegrationTests/Sources/IntegrationTestSupport/Helpers.swift @@ -14,6 +14,8 @@ import TSCBasic import TSCTestSupport import enum TSCUtility.Git +public typealias ShReturnType = (stdout: String, stderr: String, returnCode: ProcessResult.ExitStatus) + public let sdkRoot: AbsolutePath? = { if let environmentPath = ProcessInfo.processInfo.environment["SDK_ROOT"] { return try! AbsolutePath(validating: environmentPath) @@ -131,7 +133,7 @@ public func sh( env: [String: String] = [:], file: StaticString = #file, line: UInt = #line -) throws -> (stdout: String, stderr: String) { +) throws -> ShReturnType { let result = try _sh(arguments, env: env, file: file, line: line) let stdout = try result.utf8Output() let stderr = try result.utf8stderrOutput() @@ -145,7 +147,7 @@ public func sh( ) } - return (stdout, stderr) + return (stdout, stderr, result.exitStatus) } @discardableResult @@ -154,7 +156,7 @@ public func shFails( env: [String: String] = [:], file: StaticString = #file, line: UInt = #line -) throws -> (stdout: String, stderr: String) { +) throws -> ShReturnType { let result = try _sh(arguments, env: env, file: file, line: line) let stdout = try result.utf8Output() let stderr = try result.utf8stderrOutput() @@ -168,7 +170,7 @@ public func shFails( ) } - return (stdout, stderr) + return (stdout, stderr, result.exitStatus) } @discardableResult @@ -385,4 +387,4 @@ extension ProcessResult { \((try? utf8stderrOutput()) ?? "") """ } -} +} \ No newline at end of file diff --git a/IntegrationTests/Tests/IntegrationTests/BasicTests.swift b/IntegrationTests/Tests/IntegrationTests/BasicTests.swift index 91d6abd483a..68704313afb 100644 --- a/IntegrationTests/Tests/IntegrationTests/BasicTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/BasicTests.swift @@ -124,13 +124,13 @@ private struct BasicTests { let packagePath = tempDir.appending(component: "Project") try localFileSystem.createDirectory(packagePath) try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "executable") - let buildOutput = try sh(swiftBuild, "--package-path", packagePath).stdout + let packageOutput = try sh(swiftBuild, "--package-path", packagePath) // Check the build log. - let checker = StringChecker(string: buildOutput) - #expect(checker.check(.regex("Compiling .*Project.*"))) - #expect(checker.check(.regex("Linking .*Project"))) - #expect(checker.check(.contains("Build complete"))) + let checker = StringChecker(string: packageOutput.stdout) + #expect(checker.check(.regex("Compiling .*Project.*")), "stdout: '\(packageOutput.stdout)'\n stderr:'\(packageOutput.stderr)'") + #expect(checker.check(.regex("Linking .*Project")), "stdout: '\(packageOutput.stdout)'\n stderr:'\(packageOutput.stderr)'") + #expect(checker.check(.contains("Build complete")), "stdout: '\(packageOutput.stdout)'\n stderr:'\(packageOutput.stderr)'") // Verify that the tool was built and works. let toolOutput = try sh(packagePath.appending(components: ".build", "debug", "Project")) @@ -138,8 +138,8 @@ private struct BasicTests { #expect(toolOutput.lowercased().contains("hello, world!")) // Check there were no compile errors or warnings. - #expect(buildOutput.contains("error") == false) - #expect(buildOutput.contains("warning") == false) + #expect(packageOutput.stdout.contains("error") == false) + #expect(packageOutput.stdout.contains("warning") == false) } } @@ -151,17 +151,19 @@ private struct BasicTests { try localFileSystem.createDirectory(packagePath) withKnownIssue("error: no tests found; create a target in the 'Tests' directory") { try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "executable") - let testOutput = try sh(swiftTest, "--package-path", packagePath).stdout + let packageOutput = try sh(swiftTest, "--package-path", packagePath, "--vv") // Check the test log. - let checker = StringChecker(string: testOutput) - #expect(checker.check(.regex("Compiling .*ProjectTests.*"))) - #expect(checker.check("Test Suite 'All tests' passed")) - #expect(checker.checkNext("Executed 1 test")) + let checker = StringChecker(string: packageOutput.stdout) + #expect(checker.check(.regex("Compiling .*ProjectTests.*")), "stdout: '\(packageOutput.stdout)'\n stderr:'\(packageOutput.stderr)'") + #expect(checker.checkNext("Executed 1 test"), "stdout: '\(packageOutput.stdout)'\n stderr:'\(packageOutput.stderr)'") + + // Check the return code + #expect(packageOutput.returnCode == .terminated(code: 0)) // Check there were no compile errors or warnings. - #expect(testOutput.contains("error") == false) - #expect(testOutput.contains("warning") == false) + #expect(packageOutput.stdout.contains("error") == false) + #expect(packageOutput.stdout.contains("warning") == false) } } } @@ -192,17 +194,14 @@ private struct BasicTests { let packagePath = tempDir.appending(component: "Project") try localFileSystem.createDirectory(packagePath) try sh(swiftPackage, "--package-path", packagePath, "init", "--type", "library") - let testOutput = try sh(swiftTest, "--package-path", packagePath).stdout + let shOutput = try sh(swiftTest, "--package-path", packagePath) - // Check the test log. - let checker = StringChecker(string: testOutput) - #expect(checker.check(.contains("Test Suite 'All tests' started"))) - #expect(checker.check(.contains("Test example() passed after"))) - #expect(checker.checkNext(.contains("Test run with 1 test passed after"))) + // Check the return code + #expect(shOutput.returnCode == .terminated(code: 0)) // Check there were no compile errors or warnings. - #expect(testOutput.contains("error") == false) - #expect(testOutput.contains("warning") == false) + #expect(shOutput.stdout.contains("error") == false) + #expect(shOutput.stdout.contains("warning") == false) } } @@ -248,11 +247,11 @@ private struct BasicTests { #expect(buildOutput.contains("Build complete")) // Verify that the tool exists and works. - let toolOutput = try sh( + let shOutput = try sh( packagePath.appending(components: ".build", "debug", "special tool") ).stdout - #expect(toolOutput == "HI\(ProcessInfo.EOL)") + #expect(shOutput == "HI\(ProcessInfo.EOL)") } } @@ -281,17 +280,20 @@ private struct BasicTests { """ ) ) - let (runOutput, runError) = try sh( + let shOutput = try sh( swiftRun, "--package-path", packagePath, "secho", "1", #""two""# ) // Check the run log. - let checker = StringChecker(string: runError) - #expect(checker.check(.regex("Compiling .*secho.*"))) - #expect(checker.check(.regex("Linking .*secho"))) - #expect(checker.check(.contains("Build of product 'secho' complete"))) + let checker = StringChecker(string: shOutput.stderr) + #expect(checker.check(.regex("Compiling .*secho.*")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + #expect(checker.check(.regex("Linking .*secho")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + #expect(checker.check(.contains("Build of product 'secho' complete")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + + #expect(shOutput.stdout == "1 \"two\"\(ProcessInfo.EOL)") - #expect(runOutput == "1 \"two\"\(ProcessInfo.EOL)") + // Check the return code + #expect(shOutput.returnCode == .terminated(code: 0)) } } @@ -318,16 +320,16 @@ private struct BasicTests { """ ) ) - let testOutput = try sh( + let shOutput = try sh( swiftTest, "--package-path", packagePath, "--filter", "MyTests.*", "--skip", - "testBaz" - ).stderr + "testBaz", "--vv" + ) // Check the test log. - let checker = StringChecker(string: testOutput) - #expect(checker.check(.contains("Test Suite 'MyTests' started"))) - #expect(checker.check(.contains("Test Suite 'MyTests' passed"))) - #expect(checker.check(.contains("Executed 2 tests, with 0 failures"))) + let checker = StringChecker(string: shOutput.stderr) + #expect(checker.check(.contains("Test Suite 'MyTests' started")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + #expect(checker.check(.contains("Test Suite 'MyTests' passed")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + #expect(checker.check(.contains("Executed 2 tests, with 0 failures")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") } } @@ -410,15 +412,15 @@ private struct BasicTests { ) ) - let testOutput = try sh( - swiftTest, "--package-path", packagePath, "--filter", "MyTests.*" - ).stdout + let shOutput = try sh( + swiftTest, "--package-path", packagePath, "--filter", "MyTests.*", "--vv" + ) // Check the test log. - let checker = StringChecker(string: testOutput) - #expect(checker.check(.contains("Test Suite 'MyTests' started"))) - #expect(checker.check(.contains("Test Suite 'MyTests' passed"))) - #expect(checker.check(.contains("Executed 2 tests, with 0 failures"))) + let checker = StringChecker(string: shOutput.stdout) + #expect(checker.check(.contains("Test Suite 'MyTests' started")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + #expect(checker.check(.contains("Test Suite 'MyTests' passed")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") + #expect(checker.check(.contains("Executed 2 tests, with 0 failures")), "stdout: '\(shOutput.stdout)'\n stderr:'\(shOutput.stderr)'") } } } diff --git a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift index 3f673e5509c..b69d6077426 100644 --- a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift @@ -28,10 +28,10 @@ private struct SwiftPMTests { try binaryTargetsFixture { fixturePath in do { withKnownIssue("error: local binary target ... does not contain a binary artifact") { - let (stdout, stderr) = try sh(swiftRun, "--package-path", fixturePath, "exe") - #expect(!stderr.contains("error:")) + let runOutput = try sh(swiftRun, "--package-path", fixturePath, "exe") + #expect(!runOutput.stderr.contains("error:")) #expect( - stdout == """ + runOutput.stdout == """ SwiftFramework() Library(framework: SwiftFramework.SwiftFramework()) @@ -42,15 +42,15 @@ private struct SwiftPMTests { do { withKnownIssue("error: local binary target ... does not contain a binary artifact") { - let (stdout, stderr) = try sh(swiftRun, "--package-path", fixturePath, "cexe") - #expect(!stderr.contains("error:")) - #expect(stdout.contains(" Date: Fri, 25 Apr 2025 18:37:55 -0400 Subject: [PATCH 46/99] Re-enable more plugin related tests for non-macOS platforms (#8558) There are tests that were formerly failing due to missing support for plugins in the old PIF builder. Enable these test for better coverage with the new builder. * Fix spelling in one of the APIs * Remove macOS-specific linker flag in the PIF builder and use a setting that is target platform neutral * Update skip comments for tests based on new analysis of the failures * Skip test that's failing on Amazon Linux 2 --- .../SwiftBuildSupport/PackagePIFBuilder.swift | 6 +++--- .../PackagePIFProjectBuilder+Modules.swift | 10 ++-------- Tests/CommandsTests/BuildCommandTests.swift | 8 +++++--- Tests/CommandsTests/PackageCommandTests.swift | 10 ++++++++-- Tests/CommandsTests/TestCommandTests.swift | 16 ++++++++-------- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 88a2bee948f..ba854b1f461 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -651,9 +651,9 @@ extension PackagePIFBuilder.LinkedPackageBinary { init?(dependency: ResolvedModule.Dependency, package: ResolvedPackage) { switch dependency { - case .product(let producutDependency, _): - guard producutDependency.hasSourceTargets else { return nil } - self.init(name: producutDependency.name, packageName: package.name, type: .product) + case .product(let productDependency, _): + guard productDependency.hasSourceTargets else { return nil } + self.init(name: productDependency.name, packageName: package.name, type: .product) case .module(let moduleDependency, _): self.init(module: moduleDependency, package: package) diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index ca71fa6f7f3..028c9e537ea 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -504,20 +504,14 @@ extension PackagePIFProjectBuilder { } // Additional settings for the linker. - let baselineOTHER_LDFLAGS: [String] let enableDuplicateLinkageCulling = UserDefaults.standard.bool( forKey: "IDESwiftPackagesEnableDuplicateLinkageCulling", defaultValue: true ) if enableDuplicateLinkageCulling { - baselineOTHER_LDFLAGS = [ - "-Wl,-no_warn_duplicate_libraries", - "$(inherited)" - ] - } else { - baselineOTHER_LDFLAGS = ["$(inherited)"] + impartedSettings[.LD_WARN_DUPLICATE_LIBRARIES] = "NO" } - impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + baselineOTHER_LDFLAGS + impartedSettings[.OTHER_LDFLAGS] = (sourceModule.isCxx ? ["-lc++"] : []) + ["$(inherited)"] impartedSettings[.OTHER_LDRFLAGS] = [] log( .debug, diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index ca39fc6bbce..16e7d45f501 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -959,10 +959,12 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { override func testBuildCompleteMessage() async throws { #if os(Linux) - throw XCTSkip("SWBINTTODO: Need to properly set LD_LIBRARY_PATH on linux") - #else - try await super.testBuildCompleteMessage() + if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { + throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") + } #endif + + try await super.testBuildCompleteMessage() } } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index a28f411b731..876dfa4c9e4 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -3898,7 +3898,7 @@ class PackageCommandSwiftBuildTests: PackageCommandTestCase { } override func testCommandPluginBuildingCallbacks() async throws { - throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") + throw XCTSkip("SWBINTTODO: Test fails because plugin is not producing expected output to stdout.") } override func testCommandPluginBuildTestability() async throws { throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") @@ -3906,7 +3906,13 @@ class PackageCommandSwiftBuildTests: PackageCommandTestCase { #if !os(macOS) override func testCommandPluginTestingCallbacks() async throws { - throw XCTSkip("SWBINTTODO: Test fails on inability to find libclang on Linux. Also, plugins are not currently supported") +#if os(Linux) + if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { + throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") + } +#endif + try await super.testCommandPluginTestingCallbacks() } #endif + } diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 436839d9fef..f4fece83f0c 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -701,35 +701,35 @@ class TestCommandSwiftBuildTests: TestCommandTestCase { #if !os(macOS) override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputVerifySingleTestFailureMessageWithFlagEnabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputVerifyMultipleTestFailureMessageWithFlagDisabledXCTest() async throws { - throw XCTSkip("Result XML could not be found. The build fails due to an LD_LIBRARY_PATH issue finding swift core libraries. https://github.com/swiftlang/swift-package-manager/issues/8416") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestSkip() async throws { - throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestXMLOutputWhenEmpty() async throws { - throw XCTSkip("This fails due to a linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestFilter() async throws { - throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } override func testSwiftTestParallel() async throws { - throw XCTSkip("This fails due to an unknown linker error on Linux. https://github.com/swiftlang/swift-package-manager/issues/8439") + throw XCTSkip("Result XML could not be found. The build fails because of missing test helper generation logic for non-macOS platforms: https://github.com/swiftlang/swift-package-manager/issues/8479") } #endif } From 2d4884803e617d71eddfb41119711e9c940004c2 Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Sun, 27 Apr 2025 23:36:21 -0400 Subject: [PATCH 47/99] Re-enable tests in Windows CI (#8562) The investigation is pointing to running git commands in parallel that is causing the hang with the Windows CI. Turn them off until we can figure out how to serialize them across xctest processes. Once they completed, fixed the BuildPlanTests which were matching with backslashes. Also had backslash issues when launching the integration tests. --------- --- .../XCTAssertHelpers.swift | 9 +++++++ Tests/BuildTests/BuildPlanTests.swift | 3 ++- .../GitRepositoryProviderTests.swift | 7 +++++ .../GitRepositoryTests.swift | 22 +++++++++++++++ Utilities/build-using-self | 27 +++++++++---------- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Sources/_InternalTestSupport/XCTAssertHelpers.swift b/Sources/_InternalTestSupport/XCTAssertHelpers.swift index c99154e2f48..9bdbd3e475e 100644 --- a/Sources/_InternalTestSupport/XCTAssertHelpers.swift +++ b/Sources/_InternalTestSupport/XCTAssertHelpers.swift @@ -45,11 +45,20 @@ public func XCTAssertEqual (_ lhs:(T,U), _ rhs:(T,U), } public func XCTSkipIfCI(file: StaticString = #filePath, line: UInt = #line) throws { + // TODO: is this actually the right variable now? if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil { throw XCTSkip("Skipping because the test is being run on CI", file: file, line: line) } } +public func XCTSkipIfWindowsCI(file: StaticString = #filePath, line: UInt = #line) throws { + #if os(Windows) + if ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil { + throw XCTSkip("Skipping because the test is being run on CI", file: file, line: line) + } + #endif +} + /// An `async`-friendly replacement for `XCTAssertThrowsError`. public func XCTAssertAsyncThrowsError( _ expression: @autoclosure () async throws -> T, diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 914b46130a4..f3d9a2a74de 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -7033,7 +7033,8 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { let fileMap = try String(bytes: fs.readFileContents(file.path).contents, encoding: .utf8) for diagnosticFile in buildDescription.diagnosticFiles { - XCTAssertMatch(fileMap, .contains(diagnosticFile.pathString)) + let fileName = diagnosticFile.pathString.replacingOccurrences(of: "\\", with: "\\\\") + XCTAssertMatch(fileMap, .contains(fileName)) } } } diff --git a/Tests/SourceControlTests/GitRepositoryProviderTests.swift b/Tests/SourceControlTests/GitRepositoryProviderTests.swift index 8f8db05555a..0c6be7e9200 100644 --- a/Tests/SourceControlTests/GitRepositoryProviderTests.swift +++ b/Tests/SourceControlTests/GitRepositoryProviderTests.swift @@ -17,6 +17,9 @@ import XCTest class GitRepositoryProviderTests: XCTestCase { func testIsValidDirectory() throws { + // Skipping all tests that call git on Windows. + // We have a hang in CI when running in parallel. + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { sandbox in let provider = GitRepositoryProvider() @@ -42,6 +45,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testIsValidDirectoryThrowsPrintableError() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { temp in let provider = GitRepositoryProvider() let expectedErrorMessage = "not a git repository" @@ -56,6 +60,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testGitShellErrorIsPrintable() throws { + try XCTSkipIfWindowsCI() let stdOut = "An error from Git - stdout" let stdErr = "An error from Git - stderr" let arguments = ["git", "error"] @@ -84,6 +89,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testGitShellErrorEmptyStdOut() throws { + try XCTSkipIfWindowsCI() let stdErr = "An error from Git - stderr" let result = AsyncProcessResult( arguments: ["git", "error"], @@ -101,6 +107,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testGitShellErrorEmptyStdErr() throws { + try XCTSkipIfWindowsCI() let stdOut = "An error from Git - stdout" let result = AsyncProcessResult( arguments: ["git", "error"], diff --git a/Tests/SourceControlTests/GitRepositoryTests.swift b/Tests/SourceControlTests/GitRepositoryTests.swift index daadddc3fd5..44cdc50abbe 100644 --- a/Tests/SourceControlTests/GitRepositoryTests.swift +++ b/Tests/SourceControlTests/GitRepositoryTests.swift @@ -62,6 +62,9 @@ class GitRepositoryTests: XCTestCase { /// Test the basic provider functions. func testProvider() throws { + // Skipping all tests that call git on Windows. + // We have a hang in CI when running in parallel. + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try! makeDirectories(testRepoPath) @@ -127,6 +130,7 @@ class GitRepositoryTests: XCTestCase { /// contained within it for more information. func testRawRepository() throws { try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8385: test repository has non-portable file names") + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Unarchive the static test repository. @@ -186,6 +190,7 @@ class GitRepositoryTests: XCTestCase { } func testSubmoduleRead() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try makeDirectories(testRepoPath) @@ -209,6 +214,7 @@ class GitRepositoryTests: XCTestCase { /// Test the Git file system view. func testGitFileView() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try makeDirectories(testRepoPath) @@ -297,6 +303,7 @@ class GitRepositoryTests: XCTestCase { /// Test the handling of local checkouts. func testCheckouts() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a test repository. let testRepoPath = path.appending("test-repo") @@ -343,6 +350,7 @@ class GitRepositoryTests: XCTestCase { } func testFetch() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -382,6 +390,7 @@ class GitRepositoryTests: XCTestCase { } func testHasUnpushedCommits() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -418,6 +427,7 @@ class GitRepositoryTests: XCTestCase { } func testSetRemote() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -448,6 +458,7 @@ class GitRepositoryTests: XCTestCase { } func testUncommittedChanges() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -475,6 +486,7 @@ class GitRepositoryTests: XCTestCase { } func testBranchOperations() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -505,6 +517,7 @@ class GitRepositoryTests: XCTestCase { } func testRevisionOperations() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let repositoryPath = path.appending("test-repo") @@ -530,6 +543,7 @@ class GitRepositoryTests: XCTestCase { } func testCheckoutRevision() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -573,6 +587,7 @@ class GitRepositoryTests: XCTestCase { } func testSubmodules() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in let provider = GitRepositoryProvider() @@ -662,6 +677,7 @@ class GitRepositoryTests: XCTestCase { } func testAlternativeObjectStoreValidation() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -695,6 +711,7 @@ class GitRepositoryTests: XCTestCase { } func testAreIgnored() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test_repo") @@ -716,6 +733,7 @@ class GitRepositoryTests: XCTestCase { } func testAreIgnoredWithSpaceInRepoPath() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test repo") @@ -732,6 +750,7 @@ class GitRepositoryTests: XCTestCase { } func testMissingDefaultBranch() throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { path in // Create a repository. let testRepoPath = path.appending("test-repo") @@ -769,6 +788,7 @@ class GitRepositoryTests: XCTestCase { } func testValidDirectoryLocalRelativeOrigin() async throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") @@ -815,6 +835,7 @@ class GitRepositoryTests: XCTestCase { } func testValidDirectoryLocalAbsoluteOrigin() async throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") @@ -865,6 +886,7 @@ class GitRepositoryTests: XCTestCase { } func testValidDirectoryRemoteOrigin() async throws { + try XCTSkipIfWindowsCI() try testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") diff --git a/Utilities/build-using-self b/Utilities/build-using-self index 3b59152b9ba..68bfa6433a8 100755 --- a/Utilities/build-using-self +++ b/Utilities/build-using-self @@ -152,20 +152,19 @@ def main() -> None: shlex.split(f"swift build --configuration {args.config} {ignore}"), ) - if os.name != "nt": # turn off for Windows until we get the hang resolved - swift_testing_arg= "--enable-swift-testing" if args.enable_swift_testing else "" - xctest_arg= "--enable-xctest" if args.enable_swift_testing else "" - call( - shlex.split(f"swift run swift-test --configuration {args.config} --parallel {swift_testing_arg} {xctest_arg} --scratch-path .test {ignore}"), - ) - - integration_test_dir = REPO_ROOT_PATH / "IntegrationTests" - call( - shlex.split(f"swift package --package-path {integration_test_dir} update"), - ) - call( - shlex.split(f"swift run swift-test --package-path {integration_test_dir} --parallel {ignore}"), - ) + swift_testing_arg= "--enable-swift-testing" if args.enable_swift_testing else "" + xctest_arg= "--enable-xctest" if args.enable_swift_testing else "" + call( + shlex.split(f"swift run swift-test --configuration {args.config} --parallel {swift_testing_arg} {xctest_arg} --scratch-path .test {ignore}"), + ) + + integration_test_dir = (REPO_ROOT_PATH / "IntegrationTests").as_posix() + call( + shlex.split(f"swift package --package-path {integration_test_dir} update"), + ) + call( + shlex.split(f"swift run swift-test --package-path {integration_test_dir} --parallel {ignore}"), + ) if is_on_darwin(): run_bootstrap(swiftpm_bin_dir=swiftpm_bin_dir) From 553f1358b4171d98f04b692c1a8dd23e17c1c94d Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Mon, 28 Apr 2025 14:08:45 -0400 Subject: [PATCH 48/99] Tests: enable a test and update skip reason (#8542) Enable a single test on Windows and include a GitHub issue with some tests that are skipped on Windows. --- .../Serialization/SerializedJSONTests.swift | 2 +- Tests/BuildTests/BuildSystemDelegateTests.swift | 2 +- Tests/PackageGraphTests/ModulesGraphTests.swift | 2 +- .../PackageBuilderTests.swift | 2 +- Tests/PackageModelTests/PackageModelTests.swift | 16 +++++++++------- Tests/QueryEngineTests/QueryEngineTests.swift | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift index b499a4eb1dc..db3ab32e95d 100644 --- a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift +++ b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift @@ -34,7 +34,7 @@ final class SerializedJSONTests: XCTestCase { } func testPathInterpolationFailsOnWindows() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "Expectations are not met") + try skipOnWindowsAsTestCurrentlyFails(because: "Expectations are not met. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") #if os(Windows) var path = try AbsolutePath(validating: #"\\?\C:\Users"#) diff --git a/Tests/BuildTests/BuildSystemDelegateTests.swift b/Tests/BuildTests/BuildSystemDelegateTests.swift index 8e07b6a3d1a..ea844efc800 100644 --- a/Tests/BuildTests/BuildSystemDelegateTests.swift +++ b/Tests/BuildTests/BuildSystemDelegateTests.swift @@ -30,7 +30,7 @@ final class BuildSystemDelegateTests: XCTestCase { } func testFilterNonFatalCodesignMessages() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: "Package fails to build when the test is being executed") + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8540: Package fails to build when the test is being executed") try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") // Note: we can re-use the `TestableExe` fixture here since we just need an executable. diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index 02b3a907834..ac2f5e68469 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -25,7 +25,7 @@ import struct TSCBasic.ByteString final class ModulesGraphTests: XCTestCase { func testBasic() throws { - try skipOnWindowsAsTestCurrentlyFails() + try skipOnWindowsAsTestCurrentlyFails(because: "Possibly related to: https://github.com/swiftlang/swift-package-manager/issues/8511") let fs = InMemoryFileSystem( emptyFiles: "/Foo/Sources/Foo/source.swift", diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index c57b2da08cb..6a71f526a8c 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -564,7 +564,7 @@ final class PackageBuilderTests: XCTestCase { } func testTestManifestSearch() throws { - try skipOnWindowsAsTestCurrentlyFails() + try skipOnWindowsAsTestCurrentlyFails(because: "possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") let fs = InMemoryFileSystem(emptyFiles: "/pkg/foo.swift", diff --git a/Tests/PackageModelTests/PackageModelTests.swift b/Tests/PackageModelTests/PackageModelTests.swift index 8c7f5f3b184..9d230b286d5 100644 --- a/Tests/PackageModelTests/PackageModelTests.swift +++ b/Tests/PackageModelTests/PackageModelTests.swift @@ -17,7 +17,6 @@ import Basics import func TSCBasic.withTemporaryFile import XCTest -import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails import struct TSCBasic.ByteString @@ -160,10 +159,7 @@ final class PackageModelTests: XCTestCase { } func testDetermineSwiftCompilers() throws { - try skipOnWindowsAsTestCurrentlyFails() - let fs = localFileSystem - try withTemporaryFile { _ in try withTemporaryDirectory(removeTreeOnDeinit: true) { tmp in // When swiftc is not in the toolchain bin directory, UserToolchain // should find it in the system PATH search paths in the order they @@ -175,10 +171,17 @@ final class PackageModelTests: XCTestCase { // Create a directory with two swiftc binaries in it. let binDirs = ["bin1", "bin2"].map { tmp.appending($0) } + #if os(Windows) + let exeSuffix = ".exe" + #else + let exeSuffix = "" + #endif + let expectedExecuable = "swiftc\(exeSuffix)" // Files that end with .exe are considered executable on Windows. for binDir in binDirs { try fs.createDirectory(binDir) - let binFile = binDir.appending("swiftc") + let binFile = binDir.appending(expectedExecuable) try fs.writeFileContents(binFile, bytes: ByteString(Self.tinyPEBytes)) + XCTAssertTrue(fs.exists(binFile), "File '\(binFile)' does not exist when it should") #if !os(Windows) try fs.chmod(.executable, path: binFile, options: []) #endif @@ -193,8 +196,7 @@ final class PackageModelTests: XCTestCase { ) // The first swiftc in the search paths should be chosen. - XCTAssertEqual(compilers.compile, binDirs.first?.appending("swiftc")) + XCTAssertEqual(compilers.compile, binDirs.first?.appending(expectedExecuable)) } - } } } diff --git a/Tests/QueryEngineTests/QueryEngineTests.swift b/Tests/QueryEngineTests/QueryEngineTests.swift index 840bff4d4e4..c87f59820ed 100644 --- a/Tests/QueryEngineTests/QueryEngineTests.swift +++ b/Tests/QueryEngineTests/QueryEngineTests.swift @@ -100,7 +100,7 @@ private struct Expression: CachingQuery { final class QueryEngineTests: XCTestCase { func testFilePathHashing() throws { - try skipOnWindowsAsTestCurrentlyFails() + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8541") let path = "/root" From f161b02e598579f43ffc663a43c9b533bb8cb495 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Mon, 28 Apr 2025 14:29:56 -0400 Subject: [PATCH 49/99] Test: Enable most AsyncProcessTests (#8500) Enable all but 3 tests. Some AsyncProcessTests call to `cat` and `echo` commands. Update the executable to be called correctly on Windows via the `cmd.exe /c` command, where the `cat` equivalent is `type`, which displays the file contents. Also, many tests call a script. We created a batch file, which simply calls the associated script invoked by python. Only 3 tests remain skipped. - one failed assertion on Windows - two otheers appear to have caused `swift test` tp hang. Related to: #8433 rdar://148248105 --- Package.swift | 6 + Sources/Basics/Concurrency/AsyncProcess.swift | 14 + Sources/_InternalTestSupport/Process.swift | 49 ++++ Tests/BasicsTests/AsyncProcessTests.swift | 251 +++++++++--------- .../processInputs/deadlock-if-blocking-io.bat | 2 + Tests/BasicsTests/processInputs/echo.bat | 2 + Tests/BasicsTests/processInputs/exit4.bat | 1 + Tests/BasicsTests/processInputs/in-to-out.bat | 2 + .../processInputs/long-stdout-stderr.bat | 2 + .../processInputs/simple-stdout-stderr.bat | 2 + 10 files changed, 202 insertions(+), 129 deletions(-) create mode 100644 Sources/_InternalTestSupport/Process.swift create mode 100644 Tests/BasicsTests/processInputs/deadlock-if-blocking-io.bat create mode 100644 Tests/BasicsTests/processInputs/echo.bat create mode 100644 Tests/BasicsTests/processInputs/exit4.bat create mode 100644 Tests/BasicsTests/processInputs/in-to-out.bat create mode 100644 Tests/BasicsTests/processInputs/long-stdout-stderr.bat create mode 100644 Tests/BasicsTests/processInputs/simple-stdout-stderr.bat diff --git a/Package.swift b/Package.swift index 644c862769b..fb1307104d0 100644 --- a/Package.swift +++ b/Package.swift @@ -815,11 +815,17 @@ let package = Package( "Archiver/Inputs/invalid_archive.tar.gz", "Archiver/Inputs/invalid_archive.zip", "processInputs/long-stdout-stderr", + "processInputs/long-stdout-stderr.bat", "processInputs/exit4", + "processInputs/exit4.bat", "processInputs/simple-stdout-stderr", + "processInputs/simple-stdout-stderr.bat", "processInputs/deadlock-if-blocking-io", + "processInputs/deadlock-if-blocking-io.bat", "processInputs/echo", + "processInputs/echo.bat", "processInputs/in-to-out", + "processInputs/in-to-out.bat", ] ), .testTarget( diff --git a/Sources/Basics/Concurrency/AsyncProcess.swift b/Sources/Basics/Concurrency/AsyncProcess.swift index 9868b5a1324..8b6c86b42dc 100644 --- a/Sources/Basics/Concurrency/AsyncProcess.swift +++ b/Sources/Basics/Concurrency/AsyncProcess.swift @@ -387,6 +387,20 @@ package final class AsyncProcess { self.loggingHandler = loggingHandler ?? AsyncProcess.loggingHandler } + package convenience init( + args: [String], + environment: Environment = .current, + outputRedirection: OutputRedirection = .collect, + loggingHandler: LoggingHandler? = .none + ) { + self.init( + arguments: args, + environment: environment, + outputRedirection: outputRedirection, + loggingHandler: loggingHandler + ) + } + package convenience init( args: String..., environment: Environment = .current, diff --git a/Sources/_InternalTestSupport/Process.swift b/Sources/_InternalTestSupport/Process.swift new file mode 100644 index 00000000000..72c501aedc4 --- /dev/null +++ b/Sources/_InternalTestSupport/Process.swift @@ -0,0 +1,49 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +public import Foundation + +public enum OperatingSystem: Hashable, Sendable { + case macOS + case windows + case linux + case android + case unknown +} + +extension ProcessInfo { + #if os(macOS) + public static let hostOperatingSystem = OperatingSystem.macOS + #elseif os(Linux) + public static let hostOperatingSystem = OperatingSystem.linux + #elseif os(Windows) + public static let hostOperatingSystem = OperatingSystem.windows + #else + public static let hostOperatingSystem = OperatingSystem.unknown + #endif + + #if os(Windows) + public static let EOL = "\r\n" + #else + public static let EOL = "\n" + #endif + + #if os(Windows) + public static let exeSuffix = ".exe" + #else + public static let exeSuffix = "" + #endif + + #if os(Windows) + public static let batSuffix = ".bat" + #else + public static let batSuffix = "" + #endif +} diff --git a/Tests/BasicsTests/AsyncProcessTests.swift b/Tests/BasicsTests/AsyncProcessTests.swift index 8ffb8bc7f7d..6f27f3ca7ba 100644 --- a/Tests/BasicsTests/AsyncProcessTests.swift +++ b/Tests/BasicsTests/AsyncProcessTests.swift @@ -22,56 +22,48 @@ import class TSCBasic.Thread import func TSCBasic.withTemporaryFile import func TSCTestSupport.withCustomEnv -final class AsyncProcessTests: XCTestCase { - #if os(Windows) - let executableExt = ".exe" - #else - let executableExt = "" - #endif +#if os(Windows) +let catExecutable = "type" +#else +let catExecutable = "cat" +#endif - func testBasics() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "echo.exe")" - """) +final class AsyncProcessTests: XCTestCase { + let echoExecutableArgs = getAsyncProcessArgs(executable: "echo") + let catExecutableArgs = getAsyncProcessArgs(executable: catExecutable) - do { - let process = AsyncProcess(args: "echo\(executableExt)", "hello") + func testBasicsProcess() throws { + let process = AsyncProcess(arguments: echoExecutableArgs + ["hello"]) try process.launch() let result = try process.waitUntilExit() - XCTAssertEqual(try result.utf8Output(), "hello\n") + XCTAssertEqual(try result.utf8Output(), "hello\(ProcessInfo.EOL)") XCTAssertEqual(result.exitStatus, .terminated(code: 0)) XCTAssertEqual(result.arguments, process.arguments) - } + } - do { - let process = AsyncProcess(scriptName: "exit4") + func testBasicsScript() throws { + let process = AsyncProcess(scriptName: "exit4\(ProcessInfo.batSuffix)") try process.launch() let result = try process.waitUntilExit() XCTAssertEqual(result.exitStatus, .terminated(code: 4)) - } } func testPopenBasic() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "echo.exe")" - """) - // Test basic echo. - XCTAssertEqual(try AsyncProcess.popen(arguments: ["echo\(executableExt)", "hello"]).utf8Output(), "hello\n") + XCTAssertEqual(try AsyncProcess.popen(arguments: echoExecutableArgs + ["hello"]).utf8Output(), "hello\(ProcessInfo.EOL)") } func testPopenWithBufferLargerThanAllocated() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "cat.exe")" - """) // Test buffer larger than that allocated. try withTemporaryFile { file in let count = 10000 let stream = BufferedOutputByteStream() stream.send(Format.asRepeating(string: "a", count: count)) try localFileSystem.writeFileContents(file.path, bytes: stream.bytes) - let outputCount = try AsyncProcess.popen(args: "cat\(executableExt)", file.path.pathString).utf8Output().count - XCTAssert(outputCount == count) + let actualStreamCount = stream.bytes.count + XCTAssertTrue(actualStreamCount == count, "Actual stream count (\(actualStreamCount)) is not as exxpected (\(count))") + let outputCount = try AsyncProcess.popen(arguments: catExecutableArgs + [file.path.pathString]).utf8Output().count + XCTAssert(outputCount == count, "Actual count (\(outputCount)) is not as expected (\(count))") } } @@ -122,18 +114,14 @@ final class AsyncProcessTests: XCTestCase { XCTAssertTrue(output.hasPrefix(answer)) } - func testCheckNonZeroExit() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "echo.exe")" - """) - + func testCheckNonZeroExit() async throws { do { - let output = try AsyncProcess.checkNonZeroExit(args: "echo\(executableExt)", "hello") - XCTAssertEqual(output, "hello\n") + let output = try await AsyncProcess.checkNonZeroExit(args: echoExecutableArgs + ["hello"]) + XCTAssertEqual(output, "hello\(ProcessInfo.EOL)") } do { - let output = try AsyncProcess.checkNonZeroExit(scriptName: "exit4") + let output = try await AsyncProcess.checkNonZeroExit(scriptName: "exit4\(ProcessInfo.batSuffix)") XCTFail("Unexpected success \(output)") } catch AsyncProcessResult.Error.nonZeroExit(let result) { XCTAssertEqual(result.exitStatus, .terminated(code: 4)) @@ -142,17 +130,13 @@ final class AsyncProcessTests: XCTestCase { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testCheckNonZeroExitAsync() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "echo.exe")" - """) - do { - let output = try await AsyncProcess.checkNonZeroExit(args: "echo\(executableExt)", "hello") - XCTAssertEqual(output, "hello\n") + let output = try await AsyncProcess.checkNonZeroExit(args: echoExecutableArgs + ["hello"]) + XCTAssertEqual(output, "hello\(ProcessInfo.EOL)") } do { - let output = try await AsyncProcess.checkNonZeroExit(scriptName: "exit4") + let output = try await AsyncProcess.checkNonZeroExit(scriptName: "exit4\(ProcessInfo.batSuffix)") XCTFail("Unexpected success \(output)") } catch AsyncProcessResult.Error.nonZeroExit(let result) { XCTAssertEqual(result.exitStatus, .terminated(code: 4)) @@ -160,7 +144,7 @@ final class AsyncProcessTests: XCTestCase { } func testFindExecutable() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "Assertion failure when trying to find ls executable") + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: Assertion failure when trying to find ls executable") try testWithTemporaryDirectory { tmpdir in // This process should always work. @@ -171,11 +155,16 @@ final class AsyncProcessTests: XCTestCase { // Create a local nonexecutable file to test. let tempExecutable = tmpdir.appending(component: "nonExecutableProgram") - try localFileSystem.writeFileContents(tempExecutable, bytes: """ + #if os(Windows) + let exitScriptContent = ByteString("EXIT /B") + #else + let exitScriptContent = ByteString(""" #!/bin/sh exit """) + #endif + try localFileSystem.writeFileContents(tempExecutable, bytes: exitScriptContent) try withCustomEnv(["PATH": tmpdir.pathString]) { XCTAssertEqual(AsyncProcess.findExecutable("nonExecutableProgram"), nil) @@ -206,11 +195,7 @@ final class AsyncProcessTests: XCTestCase { } func testThreadSafetyOnWaitUntilExit() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "echo.exe")" - """) - - let process = AsyncProcess(args: "echo\(executableExt)", "hello") + let process = AsyncProcess(args: echoExecutableArgs + ["hello"]) try process.launch() var result1 = "" @@ -229,17 +214,13 @@ final class AsyncProcessTests: XCTestCase { t1.join() t2.join() - XCTAssertEqual(result1, "hello\n") - XCTAssertEqual(result2, "hello\n") + XCTAssertEqual(result1, "hello\(ProcessInfo.EOL)") + XCTAssertEqual(result2, "hello\(ProcessInfo.EOL)") } @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) func testThreadSafetyOnWaitUntilExitAsync() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "echo.exe")" - """) - - let process = AsyncProcess(args: "echo\(executableExt)", "hello") + let process = AsyncProcess(args: echoExecutableArgs + ["hello"]) try process.launch() let t1 = Task { @@ -253,46 +234,38 @@ final class AsyncProcessTests: XCTestCase { let result1 = try await t1.value let result2 = try await t2.value - XCTAssertEqual(result1, "hello\n") - XCTAssertEqual(result2, "hello\n") + XCTAssertEqual(result1, "hello\(ProcessInfo.EOL)") + XCTAssertEqual(result2, "hello\(ProcessInfo.EOL)") } func testStdin() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) - var stdout = [UInt8]() - let process = AsyncProcess(scriptName: "in-to-out", outputRedirection: .stream(stdout: { stdoutBytes in + let process = AsyncProcess(scriptName: "in-to-out\(ProcessInfo.batSuffix)", outputRedirection: .stream(stdout: { stdoutBytes in stdout += stdoutBytes }, stderr: { _ in })) let stdinStream = try process.launch() - stdinStream.write("hello\n") + stdinStream.write("hello\(ProcessInfo.EOL)") stdinStream.flush() try stdinStream.close() try process.waitUntilExit() - XCTAssertEqual(String(decoding: stdout, as: UTF8.self), "hello\n") + XCTAssertEqual(String(decoding: stdout, as: UTF8.self), "hello\(ProcessInfo.EOL)") } func testStdoutStdErr() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) - // A simple script to check that stdout and stderr are captured separatly. do { - let result = try AsyncProcess.popen(scriptName: "simple-stdout-stderr") - XCTAssertEqual(try result.utf8Output(), "simple output\n") - XCTAssertEqual(try result.utf8stderrOutput(), "simple error\n") + let result = try AsyncProcess.popen(scriptName: "simple-stdout-stderr\(ProcessInfo.batSuffix)") + XCTAssertEqual(try result.utf8Output(), "simple output\(ProcessInfo.EOL)") + XCTAssertEqual(try result.utf8stderrOutput(), "simple error\(ProcessInfo.EOL)") } // A long stdout and stderr output. do { - let result = try AsyncProcess.popen(scriptName: "long-stdout-stderr") + let result = try AsyncProcess.popen(scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)") let count = 16 * 1024 XCTAssertEqual(try result.utf8Output(), String(repeating: "1", count: count)) XCTAssertEqual(try result.utf8stderrOutput(), String(repeating: "2", count: count)) @@ -300,7 +273,7 @@ final class AsyncProcessTests: XCTestCase { // This script will block if the streams are not read. do { - let result = try AsyncProcess.popen(scriptName: "deadlock-if-blocking-io") + let result = try AsyncProcess.popen(scriptName: "deadlock-if-blocking-io\(ProcessInfo.batSuffix)") let count = 16 * 1024 XCTAssertEqual(try result.utf8Output(), String(repeating: "1", count: count)) XCTAssertEqual(try result.utf8stderrOutput(), String(repeating: "2", count: count)) @@ -309,20 +282,16 @@ final class AsyncProcessTests: XCTestCase { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testStdoutStdErrAsync() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) - // A simple script to check that stdout and stderr are captured separatly. do { - let result = try await AsyncProcess.popen(scriptName: "simple-stdout-stderr") - XCTAssertEqual(try result.utf8Output(), "simple output\n") - XCTAssertEqual(try result.utf8stderrOutput(), "simple error\n") + let result = try await AsyncProcess.popen(scriptName: "simple-stdout-stderr\(ProcessInfo.batSuffix)") + XCTAssertEqual(try result.utf8Output(), "simple output\(ProcessInfo.EOL)") + XCTAssertEqual(try result.utf8stderrOutput(), "simple error\(ProcessInfo.EOL)") } // A long stdout and stderr output. do { - let result = try await AsyncProcess.popen(scriptName: "long-stdout-stderr") + let result = try await AsyncProcess.popen(scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)") let count = 16 * 1024 XCTAssertEqual(try result.utf8Output(), String(repeating: "1", count: count)) XCTAssertEqual(try result.utf8stderrOutput(), String(repeating: "2", count: count)) @@ -330,7 +299,7 @@ final class AsyncProcessTests: XCTestCase { // This script will block if the streams are not read. do { - let result = try await AsyncProcess.popen(scriptName: "deadlock-if-blocking-io") + let result = try await AsyncProcess.popen(scriptName: "deadlock-if-blocking-io\(ProcessInfo.batSuffix)") let count = 16 * 1024 XCTAssertEqual(try result.utf8Output(), String(repeating: "1", count: count)) XCTAssertEqual(try result.utf8stderrOutput(), String(repeating: "2", count: count)) @@ -338,45 +307,51 @@ final class AsyncProcessTests: XCTestCase { } func testStdoutStdErrRedirected() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) - // A simple script to check that stdout and stderr are captured in the same location. do { let process = AsyncProcess( - scriptName: "simple-stdout-stderr", + scriptName: "simple-stdout-stderr\(ProcessInfo.batSuffix)", outputRedirection: .collect(redirectStderr: true) ) try process.launch() let result = try process.waitUntilExit() - XCTAssertEqual(try result.utf8Output(), "simple error\nsimple output\n") - XCTAssertEqual(try result.utf8stderrOutput(), "") + #if os(Windows) + let expectedStdout = "simple output\(ProcessInfo.EOL)" + let expectedStderr = "simple error\(ProcessInfo.EOL)" + #else + let expectedStdout = "simple error\(ProcessInfo.EOL)simple output\(ProcessInfo.EOL)" + let expectedStderr = "" + #endif + XCTAssertEqual(try result.utf8Output(), expectedStdout) + XCTAssertEqual(try result.utf8stderrOutput(), expectedStderr) } // A long stdout and stderr output. do { let process = AsyncProcess( - scriptName: "long-stdout-stderr", + scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)", outputRedirection: .collect(redirectStderr: true) ) try process.launch() let result = try process.waitUntilExit() let count = 16 * 1024 - XCTAssertEqual(try result.utf8Output(), String(repeating: "12", count: count)) - XCTAssertEqual(try result.utf8stderrOutput(), "") + #if os(Windows) + let expectedStdout = String(repeating: "1", count: count) + let expectedStderr = String(repeating: "2", count: count) + #else + let expectedStdout = String(repeating: "12", count: count) + let expectedStderr = "" + #endif + XCTAssertEqual(try result.utf8Output(), expectedStdout) + XCTAssertEqual(try result.utf8stderrOutput(), expectedStderr) } } func testStdoutStdErrStreaming() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) - var stdout = [UInt8]() var stderr = [UInt8]() - let process = AsyncProcess(scriptName: "long-stdout-stderr", outputRedirection: .stream(stdout: { stdoutBytes in + let process = AsyncProcess(scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)", outputRedirection: .stream(stdout: { stdoutBytes in stdout += stdoutBytes }, stderr: { stderrBytes in stderr += stderrBytes @@ -390,13 +365,9 @@ final class AsyncProcessTests: XCTestCase { } func testStdoutStdErrStreamingRedirected() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) - var stdout = [UInt8]() var stderr = [UInt8]() - let process = AsyncProcess(scriptName: "long-stdout-stderr", outputRedirection: .stream(stdout: { stdoutBytes in + let process = AsyncProcess(scriptName: "long-stdout-stderr\(ProcessInfo.batSuffix)", outputRedirection: .stream(stdout: { stdoutBytes in stdout += stdoutBytes }, stderr: { stderrBytes in stderr += stderrBytes @@ -405,15 +376,18 @@ final class AsyncProcessTests: XCTestCase { try process.waitUntilExit() let count = 16 * 1024 - XCTAssertEqual(String(bytes: stdout, encoding: .utf8), String(repeating: "12", count: count)) - XCTAssertEqual(stderr, []) + #if os(Windows) + let expectedStdout = String(repeating: "1", count: count) + let expectedStderr = String(repeating: "2", count: count) + #else + let expectedStdout = String(repeating: "12", count: count) + let expectedStderr = "" + #endif + XCTAssertEqual(String(bytes: stdout, encoding: .utf8), expectedStdout) + XCTAssertEqual(String(bytes: stderr, encoding: .utf8), expectedStderr) } func testWorkingDirectory() throws { - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "missingExecutableProgram(program: "cat.exe")" - """) - guard #available(macOS 10.15, *) else { // Skip this test since it's not supported in this OS. return @@ -435,14 +409,14 @@ final class AsyncProcessTests: XCTestCase { try localFileSystem.writeFileContents(childPath, bytes: ByteString("child")) do { - let process = AsyncProcess(arguments: ["cat\(executableExt)", "file"], workingDirectory: tempDirPath) + let process = AsyncProcess(arguments: catExecutableArgs + ["file"], workingDirectory: tempDirPath) try process.launch() let result = try process.waitUntilExit() XCTAssertEqual(try result.utf8Output(), "parent") } do { - let process = AsyncProcess(arguments: ["cat", "file"], workingDirectory: childPath.parentDirectory) + let process = AsyncProcess(arguments: catExecutableArgs + ["file"], workingDirectory: childPath.parentDirectory) try process.launch() let result = try process.waitUntilExit() XCTAssertEqual(try result.utf8Output(), "child") @@ -453,15 +427,13 @@ final class AsyncProcessTests: XCTestCase { func testAsyncStream() async throws { // rdar://133548796 try XCTSkipIfCI() - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: 'swift test' was hanging.") let (stdoutStream, stdoutContinuation) = AsyncProcess.ReadableStream.makeStream() let (stderrStream, stderrContinuation) = AsyncProcess.ReadableStream.makeStream() let process = AsyncProcess( - scriptName: "echo\(executableExt)", + scriptName: "echo\(ProcessInfo.batSuffix)", outputRedirection: .stream { stdoutContinuation.yield($0) } stderr: { @@ -474,14 +446,14 @@ final class AsyncProcessTests: XCTestCase { group.addTask { var counter = 0 - stdin.write("Hello \(counter)\n") + stdin.write("Hello \(counter)\(ProcessInfo.EOL)") stdin.flush() for await output in stdoutStream { - XCTAssertEqual(output, .init("Hello \(counter)\n".utf8)) + XCTAssertEqual(output, .init("Hello \(counter)\(ProcessInfo.EOL)".utf8)) counter += 1 - stdin.write(.init("Hello \(counter)\n".utf8)) + stdin.write(.init("Hello \(counter)\(ProcessInfo.EOL)".utf8)) stdin.flush() } @@ -513,22 +485,21 @@ final class AsyncProcessTests: XCTestCase { func testAsyncStreamHighLevelAPI() async throws { // rdar://133548796 try XCTSkipIfCI() - try skipOnWindowsAsTestCurrentlyFails(because: """ - threw error "Error Domain=NSCocoaErrorDomain Code=3584 "(null)"UserInfo={NSUnderlyingError=Error Domain=org.swift.Foundation.WindowsError Code=193 "(null)"}" - """) + try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: 'swift test' was hanging.") let result = try await AsyncProcess.popen( - scriptName: "echo\(executableExt)", + scriptName: "echo\(ProcessInfo.batSuffix)", // maps to 'processInputs/echo' script stdout: { stdin, stdout in var counter = 0 - stdin.write("Hello \(counter)\n") + stdin.write("Hello \(counter)\(ProcessInfo.EOL)") stdin.flush() for await output in stdout { - XCTAssertEqual(output, .init("Hello \(counter)\n".utf8)) + + XCTAssertEqual(output, .init("Hello \(counter)\(ProcessInfo.EOL)".utf8)) counter += 1 - stdin.write(.init("Hello \(counter)\n".utf8)) + stdin.write(.init("Hello \(counter)\(ProcessInfo.EOL)".utf8)) stdin.flush() } @@ -561,12 +532,25 @@ extension AsyncProcess { outputRedirection: OutputRedirection = .collect ) { self.init( - arguments: [Self.script(scriptName)] + arguments, + arguments: getAsyncProcessArgs(executable: AsyncProcess.script(scriptName)) + arguments, environment: .current, outputRedirection: outputRedirection ) } + @discardableResult + fileprivate static func checkNonZeroExit( + args: [String], + environment: Environment = .current, + loggingHandler: LoggingHandler? = .none + ) async throws -> String { + try await self.checkNonZeroExit( + arguments: args, + environment: environment, + loggingHandler: loggingHandler + ) + } + @available(*, noasync) fileprivate static func checkNonZeroExit( scriptName: String, @@ -600,7 +584,7 @@ extension AsyncProcess { environment: Environment = .current, loggingHandler: LoggingHandler? = .none ) throws -> AsyncProcessResult { - try self.popen(arguments: [self.script(scriptName)], environment: .current, loggingHandler: loggingHandler) + try self.popen(arguments: getAsyncProcessArgs(executable: self.script(scriptName)), environment: .current, loggingHandler: loggingHandler) } @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @@ -610,7 +594,7 @@ extension AsyncProcess { environment: Environment = .current, loggingHandler: LoggingHandler? = .none ) async throws -> AsyncProcessResult { - try await self.popen(arguments: [self.script(scriptName)], environment: .current, loggingHandler: loggingHandler) + try await self.popen(arguments: getAsyncProcessArgs(executable: self.script(scriptName)), environment: .current, loggingHandler: loggingHandler) } fileprivate static func popen( @@ -618,6 +602,15 @@ extension AsyncProcess { stdout: @escaping AsyncProcess.DuplexStreamHandler, stderr: AsyncProcess.ReadableStreamHandler? = nil ) async throws -> AsyncProcessResult { - try await self.popen(arguments: [self.script(scriptName)], stdoutHandler: stdout, stderrHandler: stderr) + try await self.popen(arguments: getAsyncProcessArgs(executable: self.script(scriptName)), stdoutHandler: stdout, stderrHandler: stderr) } } + +fileprivate func getAsyncProcessArgs(executable: String) -> [String] { + #if os(Windows) + let args = ["cmd.exe", "/c", executable] + #else + let args = [executable] + #endif + return args +} diff --git a/Tests/BasicsTests/processInputs/deadlock-if-blocking-io.bat b/Tests/BasicsTests/processInputs/deadlock-if-blocking-io.bat new file mode 100644 index 00000000000..28438667ac8 --- /dev/null +++ b/Tests/BasicsTests/processInputs/deadlock-if-blocking-io.bat @@ -0,0 +1,2 @@ +@echo off +python %~dp0deadlock-if-blocking-io \ No newline at end of file diff --git a/Tests/BasicsTests/processInputs/echo.bat b/Tests/BasicsTests/processInputs/echo.bat new file mode 100644 index 00000000000..e954bbcdd16 --- /dev/null +++ b/Tests/BasicsTests/processInputs/echo.bat @@ -0,0 +1,2 @@ +@echo off +python %~dp0echo \ No newline at end of file diff --git a/Tests/BasicsTests/processInputs/exit4.bat b/Tests/BasicsTests/processInputs/exit4.bat new file mode 100644 index 00000000000..9f3929bdf9d --- /dev/null +++ b/Tests/BasicsTests/processInputs/exit4.bat @@ -0,0 +1 @@ +EXIT /B 4 \ No newline at end of file diff --git a/Tests/BasicsTests/processInputs/in-to-out.bat b/Tests/BasicsTests/processInputs/in-to-out.bat new file mode 100644 index 00000000000..dbf639c3fe7 --- /dev/null +++ b/Tests/BasicsTests/processInputs/in-to-out.bat @@ -0,0 +1,2 @@ +@echo off +python %~dp0in-to-out \ No newline at end of file diff --git a/Tests/BasicsTests/processInputs/long-stdout-stderr.bat b/Tests/BasicsTests/processInputs/long-stdout-stderr.bat new file mode 100644 index 00000000000..57aaf420f21 --- /dev/null +++ b/Tests/BasicsTests/processInputs/long-stdout-stderr.bat @@ -0,0 +1,2 @@ +@echo off +python %~dp0long-stdout-stderr \ No newline at end of file diff --git a/Tests/BasicsTests/processInputs/simple-stdout-stderr.bat b/Tests/BasicsTests/processInputs/simple-stdout-stderr.bat new file mode 100644 index 00000000000..4e1d389ee12 --- /dev/null +++ b/Tests/BasicsTests/processInputs/simple-stdout-stderr.bat @@ -0,0 +1,2 @@ +@echo off +python %~dp0simple-stdout-stderr \ No newline at end of file From 68d55b1d69c79670e6b1191068581c2a5cbd4560 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Fri, 25 Apr 2025 15:37:55 -0700 Subject: [PATCH 50/99] Fix a number of minor curated documentation issues Fixing wrong parameter names, adding missing APIs to the curation, removing a couple non-existent things. --- .../PackageDependency.swift | 5 --- .../Curation/BuildSettingCondition.md | 3 +- .../Curation/Dependency.md | 34 ++++++++++++++++++- .../Extensions/CLanguageStandard-hash.md | 2 +- .../Extensions/CXXLanguageStandard-hash.md | 2 +- .../Curation/Extensions/LanguageTag-hash.md | 2 +- .../Extensions/Library-LibraryType-hash.md | 2 +- .../Extensions/Resource-Localization-hash.md | 2 +- .../Extensions/Target-TargetType-hash.md | 2 +- .../Curation/LanguageTag.md | 2 -- .../Curation/Package.md | 13 +++++++ .../Curation/Platform.md | 2 ++ .../Curation/Resource.md | 1 + .../Curation/SupportedPlatforms.md | 7 +--- .../Curation/SwiftVersion.md | 11 ------ .../Curation/Target-Dependency.md | 12 ++----- .../Target-TargetDependencyCondition.md | 2 ++ .../Curation/Target.md | 21 +++++++++++- .../PackageDescription.swift | 2 ++ .../Version+StringLiteralConvertible.swift | 8 ++--- Sources/PackageDescription/Version.swift | 4 +-- 21 files changed, 90 insertions(+), 49 deletions(-) delete mode 100644 Sources/PackageDescription/PackageDescription.docc/Curation/SwiftVersion.md diff --git a/Sources/PackageDescription/PackageDependency.swift b/Sources/PackageDescription/PackageDependency.swift index 594736e558c..dd654c5b37a 100644 --- a/Sources/PackageDescription/PackageDependency.swift +++ b/Sources/PackageDescription/PackageDependency.swift @@ -511,7 +511,6 @@ extension Package.Dependency { /// ``` /// /// - Parameters: - /// - name: The name of the package, or nil to deduce it from the URL. /// - url: The valid Git URL of the package. /// - range: The custom version range requirement. /// @@ -534,7 +533,6 @@ extension Package.Dependency { /// ``` /// /// - Parameters: - /// - name: The name of the package, or nil to deduce it from the URL. /// - url: The valid Git URL of the package. /// - range: The custom version range requirement. /// - traits: The trait configuration of this dependency. Defaults to enabling the default traits. @@ -585,7 +583,6 @@ extension Package.Dependency { /// ``` /// /// - Parameters: - /// - name: The name of the package, or `nil` to deduce it from the URL. /// - url: The valid Git URL of the package. /// - range: The closed version range requirement. /// @@ -608,7 +605,6 @@ extension Package.Dependency { /// ``` /// /// - Parameters: - /// - name: The name of the package, or `nil` to deduce it from the URL. /// - url: The valid Git URL of the package. /// - range: The closed version range requirement. /// - traits: The trait configuration of this dependency. Defaults to enabling the default traits. @@ -788,7 +784,6 @@ extension Package.Dependency { /// Adds a remote package dependency given a version requirement. /// /// - Parameters: - /// - name: The name of the package, or nil to deduce it from the URL. /// - url: The valid Git URL of the package. /// - requirement: A dependency requirement. See static methods on `Package.Dependency.Requirement` for available options. /// diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/BuildSettingCondition.md b/Sources/PackageDescription/PackageDescription.docc/Curation/BuildSettingCondition.md index 6b0ad810888..ffc550fe605 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/BuildSettingCondition.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/BuildSettingCondition.md @@ -6,5 +6,6 @@ - ``when(platforms:)`` - ``when(configuration:)`` -- ``when(platforms:configuration:)-2991l`` - ``when(platforms:configuration:)-475co`` +- ``when(platforms:configuration:traits:)`` +- ``when(platforms:configuration:)-2991l`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Dependency.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Dependency.md index 95c3faa2ab4..f3f06defc8a 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Dependency.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Dependency.md @@ -5,20 +5,52 @@ ### Creating a Package Dependency - ``package(name:path:)`` +- ``package(name:path:traits:)`` +- ``package(path:)`` +- ``package(path:traits:)`` - ``package(url:from:)`` - ``package(url:_:)-2ys47`` - ``package(url:_:)-1r6rc`` - ``package(url:branch:)`` - ``package(url:revision:)`` - ``package(url:exact:)`` -- ``package(path:)`` +- ``package(url:exact:traits:)`` +- ``package(url:_:traits:)-(_,Range,_)`` +- ``package(url:_:traits:)-(_,ClosedRange,_)`` +- ``package(url:branch:traits:)`` +- ``package(url:from:traits:)`` +- ``package(url:revision:traits:)`` +- ``package(id:_:)-(_,Range)`` +- ``package(id:_:)-(_,ClosedRange)`` +- ``package(id:_:traits:)-(_,Range,_)`` +- ``package(id:_:traits:)-(_,ClosedRange,_)`` +- ``package(id:exact:)`` +- ``package(id:exact:traits:)`` +- ``package(id:from:)`` +- ``package(id:from:traits:)`` +- ``package(name:url:_:)-(String?,_,_)`` +- ``package(name:url:_:)-(_,_,Range)`` +- ``package(name:url:_:)-(_,_,ClosedRange)`` +- ``package(name:url:branch:)`` +- ``package(name:url:from:)`` +- ``package(name:url:revision:)`` +- ``package(url:_:)-(_,Package.Dependency.Requirement)`` +- ``name`` +- ``url`` ### Declaring Requirements - ``requirement-swift.property`` - ``Requirement-swift.enum`` +- ``traits`` +- ``Trait`` +- ``RegistryRequirement`` +- ``SourceControlRequirement`` ### Describing a Package Dependency +- ``name`` +- ``url`` - ``kind-swift.property`` +- ``Kind`` - ``Version`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CLanguageStandard-hash.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CLanguageStandard-hash.md index a789334c7b2..389328ca47f 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CLanguageStandard-hash.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CLanguageStandard-hash.md @@ -6,4 +6,4 @@ Hashes the C language standard by feeding the item into the given hasher. -- Parameter into: The hasher. +- Parameter hasher: The hasher. diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CXXLanguageStandard-hash.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CXXLanguageStandard-hash.md index 04bfff2d8c2..69a2a9c22df 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CXXLanguageStandard-hash.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/CXXLanguageStandard-hash.md @@ -6,4 +6,4 @@ Hashes the C++ language standard by feeding the item into the given hasher. -- Parameter into: The hasher. +- Parameter hasher: The hasher. diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/LanguageTag-hash.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/LanguageTag-hash.md index caabf6fa08a..e1d3b4ad1aa 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/LanguageTag-hash.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/LanguageTag-hash.md @@ -6,4 +6,4 @@ Hashes the language tag by feeding the item into the given hasher. -- Parameter into: The hasher. +- Parameter hasher: The hasher. diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Library-LibraryType-hash.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Library-LibraryType-hash.md index ffb2b9f70a3..942f7c31a25 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Library-LibraryType-hash.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Library-LibraryType-hash.md @@ -11,4 +11,4 @@ Implement this method to conform to the Hashable protocol. The components used f > Important: > Never call finalize() on hasher. Doing so may become a compile-time error in the future. -- Parameter into: The hasher to use when combining the components of this instance. +- Parameter hasher: The hasher to use when combining the components of this instance. diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Resource-Localization-hash.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Resource-Localization-hash.md index aa34c1b2923..ee48715c910 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Resource-Localization-hash.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Resource-Localization-hash.md @@ -6,4 +6,4 @@ Hashes the localization by feeding the item into the given hasher. -- Parameter into: The hasher. +- Parameter hasher: The hasher. diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Target-TargetType-hash.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Target-TargetType-hash.md index cd8203cc945..c08d320b785 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Target-TargetType-hash.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Extensions/Target-TargetType-hash.md @@ -6,6 +6,6 @@ Hashes the target type by feeding the item into the given hasher. -- Parameter into: The hasher. +- Parameter hasher: The hasher. diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/LanguageTag.md b/Sources/PackageDescription/PackageDescription.docc/Curation/LanguageTag.md index bb00e985638..d8b1f564b50 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/LanguageTag.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/LanguageTag.md @@ -4,7 +4,6 @@ ### Creating a Language Tag -- ``init(_:)`` - - ``init(stringLiteral:)`` - @@ -13,7 +12,6 @@ ### Describing a Language Tag -- ``tag`` - ``description`` ### Hashing diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Package.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Package.md index f85d1e97751..284f38751a2 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Package.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Package.md @@ -4,7 +4,13 @@ ### Creating a Package +- ``Package/init(name:defaultLocalization:platforms:pkgConfig:providers:products:dependencies:targets:swiftLanguageModes:cLanguageStandard:cxxLanguageStandard:)`` +- ``Package/init(name:defaultLocalization:platforms:pkgConfig:providers:products:traits:dependencies:targets:swiftLanguageModes:cLanguageStandard:cxxLanguageStandard:)`` - ``Package/init(name:defaultLocalization:platforms:pkgConfig:providers:products:dependencies:targets:swiftLanguageVersions:cLanguageStandard:cxxLanguageStandard:)`` +- ``Package/init(name:platforms:pkgConfig:providers:products:dependencies:targets:swiftLanguageVersions:cLanguageStandard:cxxLanguageStandard:)`` +- ``Package/init(name:pkgConfig:providers:products:dependencies:targets:swiftLanguageVersions:cLanguageStandard:cxxLanguageStandard:)-(_,_,_,_,_,_,[Int]?,_,_)`` +- ``Package/init(name:pkgConfig:providers:products:dependencies:targets:swiftLanguageVersions:cLanguageStandard:cxxLanguageStandard:)-(_,_,_,_,_,_,[SwiftVersion]?,_,_)`` + ### Naming the Package @@ -37,6 +43,11 @@ - ``Package/pkgConfig`` - ``Package/providers`` +### Configuring Traits + +- ``Package/traits`` +- ``Trait`` + ### Declaring Package Dependencies - ``Package/dependencies`` @@ -44,9 +55,11 @@ ### Declaring Supported Languages +- ``SwiftLanguageMode`` - ``SwiftVersion`` - ``CLanguageStandard`` - ``CXXLanguageStandard`` +- ``Package/swiftLanguageModes`` - ``Package/swiftLanguageVersions`` - ``Package/cLanguageStandard`` - ``Package/cxxLanguageStandard`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Platform.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Platform.md index f694781bc66..25466890bc5 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Platform.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Platform.md @@ -9,10 +9,12 @@ - ``macOS`` - ``tvOS`` - ``watchOS`` +- ``visionOS`` - ``macCatalyst`` - ``driverKit`` - ``android`` - ``linux`` +- ``freebsd`` - ``openbsd`` - ``wasi`` - ``windows`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Resource.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Resource.md index 1535ff8d5b3..8279f56e422 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Resource.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Resource.md @@ -7,3 +7,4 @@ - ``process(_:localization:)`` - ``Localization`` - ``copy(_:)`` +- ``embedInCode(_:)`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/SupportedPlatforms.md b/Sources/PackageDescription/PackageDescription.docc/Curation/SupportedPlatforms.md index e30d3c1b50b..b4062a595ab 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/SupportedPlatforms.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/SupportedPlatforms.md @@ -51,15 +51,10 @@ - - ``DriverKitVersion`` -### Supporting Linux - -- - -### Type methods +### Supporting Custom Platforms - ``custom(_:versionString:)`` ### Operator Functions - ``!=(_:_:)`` -- ``==(_:_:)`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/SwiftVersion.md b/Sources/PackageDescription/PackageDescription.docc/Curation/SwiftVersion.md deleted file mode 100644 index 760c6c7b481..00000000000 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/SwiftVersion.md +++ /dev/null @@ -1,11 +0,0 @@ -# ``PackageDescription/SwiftVersion`` - -## Topics - -### Enumeration Cases - -- ``v3`` -- ``v4`` -- ``v4_2`` -- ``v5`` -- ``version(_:)`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Target-Dependency.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Target-Dependency.md index e8616f0e280..27b3e78d2e4 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Target-Dependency.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Target-Dependency.md @@ -4,22 +4,14 @@ ### Creating a Target Dependency +- ``product(name:package:moduleAliases:condition:)`` - ``product(name:package:condition:)`` - ``product(name:package:)-fp0j`` - ``product(name:package:)-2nako`` -- ``product(name:package:moduleAliases:)`` -- ``product(name:package:moduleAliases:condition:)`` +- ``productItem(name:package:condition:)`` - ``target(name:condition:)`` - ``target(name:)`` - ``byName(name:condition:)`` - ``byName(name:)`` - ``TargetDependencyCondition`` - ``init(stringLiteral:)`` -- ``init(extendedGraphemeClusterLiteral:)`` -- ``init(unicodeScalarLiteral:)`` - -### Identifying related types - -- ``ExtendedGraphemeClusterLiteralType`` -- ``StringLiteralType`` -- ``UnicodeScalarLiteralType`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Target-TargetDependencyCondition.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Target-TargetDependencyCondition.md index 36c8b139722..2a39614a69e 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Target-TargetDependencyCondition.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Target-TargetDependencyCondition.md @@ -5,4 +5,6 @@ ### Creating a Dependency Condition - ``when(platforms:)-5bxhc`` +- ``when(traits:)`` +- ``when(platforms:traits:)`` - ``when(platforms:)-4djh6`` diff --git a/Sources/PackageDescription/PackageDescription.docc/Curation/Target.md b/Sources/PackageDescription/PackageDescription.docc/Curation/Target.md index 33507e4f3b4..a68bcc6c27b 100644 --- a/Sources/PackageDescription/PackageDescription.docc/Curation/Target.md +++ b/Sources/PackageDescription/PackageDescription.docc/Curation/Target.md @@ -30,11 +30,29 @@ ### Creating an Executable Target -- ``executableTarget(name:dependencies:path:exclude:sources:resources:publicHeadersPath:cSettings:cxxSettings:swiftSettings:linkerSettings:)`` +- ``executableTarget(name:dependencies:path:exclude:sources:resources:publicHeadersPath:packageAccess:cSettings:cxxSettings:swiftSettings:linkerSettings:plugins:)`` - ``executableTarget(name:dependencies:path:exclude:sources:resources:publicHeadersPath:cSettings:cxxSettings:swiftSettings:linkerSettings:plugins:)`` +- ``executableTarget(name:dependencies:path:exclude:sources:resources:publicHeadersPath:cSettings:cxxSettings:swiftSettings:linkerSettings:)`` + +### Creating a Regular Target + +- ``target(name:dependencies:path:exclude:sources:resources:publicHeadersPath:packageAccess:cSettings:cxxSettings:swiftSettings:linkerSettings:plugins:)`` +- ``target(name:dependencies:path:exclude:sources:resources:publicHeadersPath:cSettings:cxxSettings:swiftSettings:linkerSettings:plugins:)`` +- ``target(name:dependencies:path:exclude:sources:resources:publicHeadersPath:cSettings:cxxSettings:swiftSettings:linkerSettings:)`` +- ``target(name:dependencies:path:exclude:sources:publicHeadersPath:cSettings:cxxSettings:swiftSettings:linkerSettings:)`` +- ``target(name:dependencies:path:exclude:sources:publicHeadersPath:)`` + +### Creating a Test Target + +- ``testTarget(name:dependencies:path:exclude:sources:resources:packageAccess:cSettings:cxxSettings:swiftSettings:linkerSettings:plugins:)`` +- ``testTarget(name:dependencies:path:exclude:sources:resources:cSettings:cxxSettings:swiftSettings:linkerSettings:plugins:)`` +- ``testTarget(name:dependencies:path:exclude:sources:resources:cSettings:cxxSettings:swiftSettings:linkerSettings:)`` +- ``testTarget(name:dependencies:path:exclude:sources:cSettings:cxxSettings:swiftSettings:linkerSettings:)`` +- ``testTarget(name:dependencies:path:exclude:sources:)`` ### Creating a Plugin Target +- ``plugin(name:capability:dependencies:path:exclude:sources:packageAccess:)`` - ``plugin(name:capability:dependencies:path:exclude:sources:)`` - ``pluginCapability-swift.property`` - ``PluginCapability-swift.enum`` @@ -61,6 +79,7 @@ - ``SwiftSetting`` - ``LinkerSetting`` - ``PluginUsage`` +- ``packageAccess`` ### Describing the Target Type diff --git a/Sources/PackageDescription/PackageDescription.swift b/Sources/PackageDescription/PackageDescription.swift index 261fca0cc81..edc7b18cdf4 100644 --- a/Sources/PackageDescription/PackageDescription.swift +++ b/Sources/PackageDescription/PackageDescription.swift @@ -171,6 +171,7 @@ public final class Package { /// - pkgConfig: The name to use for C modules. If present, the Swift /// Package Manager searches for a `.pc` file to get the /// additional flags required for a system target. + /// - providers: The system package providers that this package uses. /// - products: The list of products that this package makes available for clients to use. /// - dependencies: The list of package dependencies. /// - targets: The list of targets that are part of this package. @@ -211,6 +212,7 @@ public final class Package { /// - pkgConfig: The name to use for C modules. If present, the Swift /// Package Manager searches for a `.pc` file to get the /// additional flags required for a system target. + /// - providers: The system package providers that this package uses. /// - products: The list of products that this package makes available for clients to use. /// - dependencies: The list of package dependencies. /// - targets: The list of targets that are part of this package. diff --git a/Sources/PackageDescription/Version+StringLiteralConvertible.swift b/Sources/PackageDescription/Version+StringLiteralConvertible.swift index 58dbc187e39..c3d01faf20c 100644 --- a/Sources/PackageDescription/Version+StringLiteralConvertible.swift +++ b/Sources/PackageDescription/Version+StringLiteralConvertible.swift @@ -13,7 +13,7 @@ extension Version: ExpressibleByStringLiteral { /// Initializes a version struct with the provided string literal. /// - /// - Parameter version: A string literal to use for creating a new version struct. + /// - Parameter value: A string literal to use for creating a new version struct. public init(stringLiteral value: String) { if let version = Version(value) { self = version @@ -29,7 +29,7 @@ extension Version: ExpressibleByStringLiteral { /// Initializes a version struct with the provided extended grapheme cluster. /// - /// - Parameter version: An extended grapheme cluster to use for creating a new + /// - Parameter value: An extended grapheme cluster to use for creating a new /// version struct. public init(extendedGraphemeClusterLiteral value: String) { self.init(stringLiteral: value) @@ -37,7 +37,7 @@ extension Version: ExpressibleByStringLiteral { /// Initializes a version struct with the provided Unicode string. /// - /// - Parameter version: A Unicode string to use for creating a new version struct. + /// - Parameter value: A Unicode string to use for creating a new version struct. public init(unicodeScalarLiteral value: String) { self.init(stringLiteral: value) } @@ -45,7 +45,7 @@ extension Version: ExpressibleByStringLiteral { extension Version: LosslessStringConvertible { /// Initializes a version struct with the provided version string. - /// - Parameter version: A version string to use for creating a new version struct. + /// - Parameter versionString: A version string to use for creating a new version struct. public init?(_ versionString: String) { // SemVer 2.0.0 allows only ASCII alphanumerical characters and "-" in the version string, except for "." and "+" as delimiters. ("-" is used as a delimiter between the version core and pre-release identifiers, but it's allowed within pre-release and metadata identifiers as well.) // Alphanumerics check will come later, after each identifier is split out (i.e. after the delimiters are removed). diff --git a/Sources/PackageDescription/Version.swift b/Sources/PackageDescription/Version.swift index 09b9b214da7..b777a241919 100644 --- a/Sources/PackageDescription/Version.swift +++ b/Sources/PackageDescription/Version.swift @@ -59,11 +59,11 @@ public struct Version: Sendable { /// - minor: The minor version number. /// - patch: The patch version number. /// - prereleaseIdentifiers: The pre-release identifier. - /// - buildMetaDataIdentifiers: Build metadata that identifies a build. + /// - buildMetadataIdentifiers: Build metadata that identifies a build. /// /// - Precondition: `major >= 0 && minor >= 0 && patch >= 0`. /// - Precondition: `prereleaseIdentifiers` can contain only ASCII alpha-numeric characters and "-". - /// - Precondition: `buildMetaDataIdentifiers` can contain only ASCII alpha-numeric characters and "-". + /// - Precondition: `buildMetadataIdentifiers` can contain only ASCII alpha-numeric characters and "-". public init( _ major: Int, _ minor: Int, From f808b9f87c427a0f84c317c45b432a7e5a927ce5 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 29 Apr 2025 13:42:19 +0100 Subject: [PATCH 51/99] Use `GENERATE_PRELINK_OBJECT_FILE` in `PackagePIFProjectBuilder` (#8570) Fixing build failure in https://ci.swift.org/job/oss-swift-pr-test-macoss/4978/console introduced by https://github.com/swiftlang/swift-build/pull/452. --- .../SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index 028c9e537ea..44d05cbcde9 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -423,7 +423,7 @@ extension PackagePIFProjectBuilder { .spm_mangledToBundleIdentifier() settings[.EXECUTABLE_NAME] = executableName settings[.CLANG_ENABLE_MODULES] = "YES" - settings[.GENERATE_MASTER_OBJECT_FILE] = "NO" + settings[.GENERATE_PRELINK_OBJECT_FILE] = "NO" settings[.STRIP_INSTALLED_PRODUCT] = "NO" // Macros build as executables, so they need slightly different From 6965245d1fd3035b116235e967dc639eab2f11f5 Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Tue, 29 Apr 2025 09:52:02 -0400 Subject: [PATCH 52/99] Avoid building targets that are conditional on platform when building all (#8568) When using the swiftbuild build system, and building all targets with a project that contains a target that is depended on only when on a specific platform that target should be skipped. Make the All(Excluding|Including)Tests generated targets depend only on targets that are reachable from the roots through dependencies that are either unconditional or are conditioned on the current platform. --- Sources/SwiftBuildSupport/PIFBuilder.swift | 26 ++++++++++++++----- .../SwiftBuildSupport/SwiftBuildSystem.swift | 3 ++- .../FunctionalTests/MiscellaneousTests.swift | 11 ++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index cb6866018ae..6ed5ae1ab32 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -100,7 +100,8 @@ public final class PIFBuilder { func generatePIF( prettyPrint: Bool = true, preservePIFModelStructure: Bool = false, - printPIFManifestGraphviz: Bool = false + printPIFManifestGraphviz: Bool = false, + buildParameters: BuildParameters ) throws -> String { #if canImport(SwiftBuild) let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder() @@ -109,7 +110,7 @@ public final class PIFBuilder { encoder.userInfo[.encodeForSwiftBuild] = true } - let topLevelObject = try self.construct() + let topLevelObject = try self.construct(buildParameters: buildParameters) // Sign the PIF objects before encoding it for Swift Build. try PIF.sign(workspace: topLevelObject.workspace) @@ -138,7 +139,7 @@ public final class PIFBuilder { private var cachedPIF: PIF.TopLevelObject? /// Constructs a `PIF.TopLevelObject` representing the package graph. - private func construct() throws -> PIF.TopLevelObject { + private func construct(buildParameters: BuildParameters) throws -> PIF.TopLevelObject { try memoize(to: &self.cachedPIF) { guard let rootPackage = self.graph.rootPackages.only else { if self.graph.rootPackages.isEmpty { @@ -174,7 +175,9 @@ public final class PIFBuilder { projects.append( try buildAggregateProject( packagesAndProjects: packagesAndProjects, - observabilityScope: observabilityScope + observabilityScope: observabilityScope, + modulesGraph: graph, + buildParameters: buildParameters ) ) @@ -197,7 +200,7 @@ public final class PIFBuilder { packageGraph: ModulesGraph, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - preservePIFModelStructure: Bool + preservePIFModelStructure: Bool, ) throws -> String { let parameters = PIFBuilderParameters(buildParameters, supportedSwiftVersions: []) let builder = Self( @@ -206,7 +209,7 @@ public final class PIFBuilder { fileSystem: fileSystem, observabilityScope: observabilityScope ) - return try builder.generatePIF(preservePIFModelStructure: preservePIFModelStructure) + return try builder.generatePIF(preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters) } } @@ -306,7 +309,9 @@ fileprivate final class PackagePIFBuilderDelegate: PackagePIFBuilder.BuildDelega fileprivate func buildAggregateProject( packagesAndProjects: [(package: ResolvedPackage, project: ProjectModel.Project)], - observabilityScope: ObservabilityScope + observabilityScope: ObservabilityScope, + modulesGraph: ModulesGraph, + buildParameters: BuildParameters ) throws -> ProjectModel.Project { precondition(!packagesAndProjects.isEmpty) @@ -367,6 +372,13 @@ fileprivate func buildAggregateProject( // conflicts with those from "PACKAGE-TARGET:Foo-dynamic". continue } + + if let resolvedModule = modulesGraph.module(for: target.name) { + guard modulesGraph.isInRootPackages(resolvedModule, satisfying: buildParameters.buildEnvironment) else { + // Disconnected target, possibly due to platform when condition that isn't satisfied + continue + } + } aggregateProject[keyPath: allIncludingTestsTargetKeyPath].common.addDependency( on: target.id, diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 38d3272beaf..79e8b6567ea 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -239,7 +239,8 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { let pifBuilder = try await getPIFBuilder() let pif = try pifBuilder.generatePIF( - printPIFManifestGraphviz: buildParameters.printPIFManifestGraphviz + printPIFManifestGraphviz: buildParameters.printPIFManifestGraphviz, + buildParameters: buildParameters ) try self.fileSystem.writeIfChanged(path: buildParameters.pifManifest, string: pif) diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index dd39ab65f0c..c06e2f76dc2 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -665,4 +665,15 @@ final class MiscellaneousTestCase: XCTestCase { XCTAssertEqual(errors, [], "unexpected errors: \(errors)") } } + + func testRootPackageWithConditionalsSwiftBuild() async throws { +#if os(Linux) + if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { + throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") + } +#endif + try await fixture(name: "Miscellaneous/RootPackageWithConditionals") { path in + _ = try await SwiftPM.Build.execute(["--build-system=swiftbuild"], packagePath: path, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) + } + } } From e0afabb43ea2081ab276e5cef80d43872e62e8b7 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Tue, 29 Apr 2025 09:53:51 -0700 Subject: [PATCH 53/99] Factor the working directory skip into a common function (#8561) Also skips on OpenBSD, which has the same issue. --- .../XCTAssertHelpers.swift | 18 ++++++++++++++++++ Tests/BuildTests/BuildPlanTests.swift | 5 +---- Tests/CommandsTests/BuildCommandTests.swift | 19 +++---------------- Tests/CommandsTests/PackageCommandTests.swift | 7 ++----- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/Sources/_InternalTestSupport/XCTAssertHelpers.swift b/Sources/_InternalTestSupport/XCTAssertHelpers.swift index 9bdbd3e475e..f63aa2b1dc8 100644 --- a/Sources/_InternalTestSupport/XCTAssertHelpers.swift +++ b/Sources/_InternalTestSupport/XCTAssertHelpers.swift @@ -268,3 +268,21 @@ public struct CommandExecutionError: Error { public let stdout: String public let stderr: String } + +/// Skips the test if running on a platform which lacks the ability for build tasks to set a working directory due to lack of requisite system API. +/// +/// Presently, relevant platforms include Amazon Linux 2 and OpenBSD. +/// +/// - seealso: https://github.com/swiftlang/swift-package-manager/issues/8560 +public func XCTSkipIfWorkingDirectoryUnsupported() throws { + func unavailable() throws { + throw XCTSkip("Thread-safe process working directory support is unavailable on this platform.") + } + #if os(Linux) + if FileManager.default.contents(atPath: "/etc/system-release").map({ String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" }) ?? false { + try unavailable() + } + #elseif os(OpenBSD) + try unavailable() + #endif +} diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index f3d9a2a74de..5c9ac3c9ce9 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -7069,14 +7069,11 @@ class BuildPlanSwiftBuildTests: BuildPlanTestCase { } override func testPackageNameFlag() async throws { + try XCTSkipIfWorkingDirectoryUnsupported() #if os(Windows) throw XCTSkip("Skip until there is a resolution to the partial linking with Windows that results in a 'subsystem must be defined' error.") #endif - #if os(Linux) - if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { - throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") - } // Linking error: "/usr/bin/ld.gold: fatal error: -pie and -static are incompatible". // Tracked by GitHub issue: https://github.com/swiftlang/swift-package-manager/issues/8499 throw XCTSkip("Skipping Swift Build testing on Linux because of linking issues.") diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 16e7d45f501..a91357a4147 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -859,12 +859,7 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { } override func testParseableInterfaces() async throws { - #if os(Linux) - if FileManager.default.contents(atPath: "/etc/system-release") - .map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { - throw XCTSkip("https://github.com/swiftlang/swift-package-manager/issues/8545: Test currently fails on Amazon Linux 2") - } - #endif + try XCTSkipIfWorkingDirectoryUnsupported() try await fixture(name: "Miscellaneous/ParseableInterfaces") { fixturePath in do { let result = try await build(["--enable-parseable-module-interfaces"], packagePath: fixturePath) @@ -944,11 +939,7 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { #endif override func testBuildSystemDefaultSettings() async throws { - #if os(Linux) - if FileManager.default.contents(atPath: "/etc/system-release").map( { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ) ?? false { - throw XCTSkip("Skipping SwiftBuild testing on Amazon Linux because of platform issues.") - } - #endif + try XCTSkipIfWorkingDirectoryUnsupported() if ProcessInfo.processInfo.environment["SWIFTPM_NO_SWBUILD_DEPENDENCY"] != nil { throw XCTSkip("SWIFTPM_NO_SWBUILD_DEPENDENCY is set so skipping because SwiftPM doesn't have the swift-build capability built inside.") @@ -958,11 +949,7 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { } override func testBuildCompleteMessage() async throws { - #if os(Linux) - if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { - throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") - } - #endif + try XCTSkipIfWorkingDirectoryUnsupported() try await super.testBuildCompleteMessage() } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 876dfa4c9e4..cb2a1cef77a 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -3906,11 +3906,8 @@ class PackageCommandSwiftBuildTests: PackageCommandTestCase { #if !os(macOS) override func testCommandPluginTestingCallbacks() async throws { -#if os(Linux) - if FileManager.default.contents(atPath: "/etc/system-release").map { String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" } ?? false { - throw XCTSkip("Skipping Swift Build testing on Amazon Linux because of platform issues.") - } -#endif + try XCTSkipIfWorkingDirectoryUnsupported() + try await super.testCommandPluginTestingCallbacks() } #endif From ed31e6e9559377f95066c64f16fe8cf6d8141dbf Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 29 Apr 2025 16:35:06 -0400 Subject: [PATCH 54/99] Tests: Enable more WorkspaceTests on Windows (#8457) Many WorkspaceTests instantiate a MockWorkspace, which takes a path as an input. The tests were defining a POSIX-like path as the sandbox directory, however, some assertions were incorrect as, on these *nix like filesystem, the canonical path name and the path were identical. Update the WorkspaceTests expectation accordingly, and add some automated tests to cover the InMemoryFileSystem (which is commented out as a crash occurs in the Linux Platform CI build with Swift Testing tests). Relates: #8433 rdar://148248105 --- .../_InternalTestSupport/MockWorkspace.swift | 8 +- .../XCTAssertHelpers.swift | 2 +- Sources/_InternalTestSupport/misc.swift | 4 +- .../FileSystem/InMemoryFilesSystemTests.swift | 236 +++++++ Tests/WorkspaceTests/WorkspaceTests.swift | 594 +++++------------- 5 files changed, 390 insertions(+), 454 deletions(-) create mode 100644 Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index 4b1d516f9f0..3349eb60f91 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -185,12 +185,14 @@ public final class MockWorkspace { private func create() async throws { // Remove the sandbox if present. - try self.fileSystem.removeFileTree(self.sandbox) + if self.fileSystem.exists(self.sandbox) { + try self.fileSystem.removeFileTree(self.sandbox) + } // Create directories. try self.fileSystem.createDirectory(self.sandbox, recursive: true) - try self.fileSystem.createDirectory(self.rootsDir) - try self.fileSystem.createDirectory(self.packagesDir) + try self.fileSystem.createDirectory(self.rootsDir, recursive: true) + try self.fileSystem.createDirectory(self.packagesDir, recursive: true) var manifests: [MockManifestLoader.Key: Manifest] = [:] diff --git a/Sources/_InternalTestSupport/XCTAssertHelpers.swift b/Sources/_InternalTestSupport/XCTAssertHelpers.swift index f63aa2b1dc8..1eb619ee1c5 100644 --- a/Sources/_InternalTestSupport/XCTAssertHelpers.swift +++ b/Sources/_InternalTestSupport/XCTAssertHelpers.swift @@ -46,7 +46,7 @@ public func XCTAssertEqual (_ lhs:(T,U), _ rhs:(T,U), public func XCTSkipIfCI(file: StaticString = #filePath, line: UInt = #line) throws { // TODO: is this actually the right variable now? - if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil { + if isInCiEnvironment { throw XCTSkip("Skipping because the test is being run on CI", file: file, line: line) } } diff --git a/Sources/_InternalTestSupport/misc.swift b/Sources/_InternalTestSupport/misc.swift index 0cc73af222d..baf6a186abf 100644 --- a/Sources/_InternalTestSupport/misc.swift +++ b/Sources/_InternalTestSupport/misc.swift @@ -39,6 +39,8 @@ import enum TSCUtility.Git @_exported import func TSCTestSupport.systemQuietly @_exported import enum TSCTestSupport.StringPattern +public let isInCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil + /// Test helper utility for executing a block with a temporary directory. public func testWithTemporaryDirectory( function: StaticString = #function, @@ -294,7 +296,7 @@ public func skipOnWindowsAsTestCurrentlyFails(because reason: String? = nil) thr } else { failureCause = "" } - throw XCTSkip("Test fails on windows\(failureCause)") + throw XCTSkip("Skipping tests on windows\(failureCause)") #endif } diff --git a/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift b/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift new file mode 100644 index 00000000000..b34f869896f --- /dev/null +++ b/Tests/BasicsTests/FileSystem/InMemoryFilesSystemTests.swift @@ -0,0 +1,236 @@ +/* + This source file is part of the Swift.org open source project + + Copyright (c) 2025 Apple Inc. and the Swift project authors + Licensed under Apache License v2.0 with Runtime Library Exception + + See http://swift.org/LICENSE.txt for license information + See http://swift.org/CONTRIBUTORS.txt for Swift project authors + */ + +import Basics +import struct TSCBasic.ByteString +import struct TSCBasic.FileSystemError + +import Testing +import _InternalTestSupport + +#if os(Linux) +let isLinux = true +#else +let isLinux = false +#endif + +// Comment out the Swift Testing test until we figure out why the Linux Smoke Test +// is crashing +// struct InMemoryFileSystemTests { +// @Test( +// arguments: [ +// ( +// path: "/", +// recurvise: true, +// expectedFiles: [ +// (p: "/", shouldExist: true), +// ], +// expectError: false +// ), +// ( +// path: "/tmp", +// recurvise: true, +// expectedFiles: [ +// (p: "/", shouldExist: true), +// (p: "/tmp", shouldExist: true), +// ], +// expectError: false +// ), +// ( +// path: "/tmp/ws", +// recurvise: true, +// expectedFiles: [ +// (p: "/", shouldExist: true), +// (p: "/tmp", shouldExist: true), +// (p: "/tmp/ws", shouldExist: true), +// ], +// expectError: false +// ), +// ( +// path: "/tmp/ws", +// recurvise: false, +// expectedFiles: [ +// (p: "/", shouldExist: true), +// (p: "/tmp", shouldExist: true), +// (p: "/tmp/ws", shouldExist: true), +// ], +// expectError: true +// ), +// ] +// ) +// func creatingDirectoryCreatesInternalFiles( +// path: String, +// recursive: Bool, +// expectedFiles: [(String, Bool) ], +// expectError: Bool +// ) async throws { +// let fs = InMemoryFileSystem() +// let pathUnderTest = AbsolutePath(path) + +// func errorMessage(_ pa: AbsolutePath, _ exists: Bool) -> String { +// return "Path '\(pa) \(exists ? "should exists, but doesn't" : "should not exist, but does.")" +// } + +// try withKnownIssue { +// try fs.createDirectory(pathUnderTest, recursive: recursive) + +// for (p, shouldExist) in expectedFiles { +// let expectedPath = AbsolutePath(p) +// #expect(fs.exists(expectedPath) == shouldExist, "\(errorMessage(expectedPath, shouldExist))") +// } +// } when: { +// expectError +// } +// } + + +// @Test( +// arguments: [ +// "/", +// "/tmp", +// "/tmp/", +// "/something/ws", +// "/something/ws/", +// "/what/is/this", +// "/what/is/this/", +// ] +// ) +// func callingCreateDirectoryOnAnExistingDirectoryIsSuccessful(path: String) async throws { +// let root = AbsolutePath(path) +// let fs = InMemoryFileSystem() + +// #expect(throws: Never.self) { +// try fs.createDirectory(root, recursive: true) +// } + +// #expect(throws: Never.self) { +// try fs.createDirectory(root.appending("more"), recursive: true) +// } +// } + +// struct writeFileContentsTests { + +// @Test +// func testWriteFileContentsSuccessful() async throws { +// // GIVEN we have a filesytstem +// let fs = InMemoryFileSystem() +// // and a path +// let pathUnderTest = AbsolutePath("/myFile.zip") +// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + +// // WHEN we write contents to the file +// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + +// // THEN we expect the file to exist +// #expect(fs.exists(pathUnderTest), "Path \(pathUnderTest.pathString) does not exists when it should") +// } + +// @Test +// func testWritingAFileWithANonExistingParentDirectoryFails() async throws { +// // GIVEN we have a filesytstem +// let fs = InMemoryFileSystem() +// // and a path +// let pathUnderTest = AbsolutePath("/tmp/myFile.zip") +// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + +// // WHEN we write contents to the file +// // THEn we expect an error to occus +// try withKnownIssue { +// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) +// } + +// // AND we expect the file to not exist +// #expect(!fs.exists(pathUnderTest), "Path \(pathUnderTest.pathString) does exists when it should not") +// } + +// @Test +// func errorOccursWhenWritingToRootDirectory() async throws { +// // GIVEN we have a filesytstem +// let fs = InMemoryFileSystem() +// // and a path +// let pathUnderTest = AbsolutePath("/") +// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + +// // WHEN we write contents to the file +// // THEN we expect an error to occur +// try withKnownIssue { +// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) +// } + +// } + +// @Test +// func testErrorOccursIfParentIsNotADirectory() async throws { +// // GIVEN we have a filesytstem +// let fs = InMemoryFileSystem() +// // AND an existing file +// let aFile = AbsolutePath("/foo") +// try fs.writeFileContents(aFile, bytes: "") + +// // AND a the path under test that has an existing file as a parent +// let pathUnderTest = aFile.appending("myFile") +// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + +// // WHEN we write contents to the file +// // THEN we expect an error to occur +// withKnownIssue { +// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) +// } + +// } +// } + + +// struct testReadFileContentsTests { +// @Test +// func readingAFileThatDoesNotExistsRaisesAnError()async throws { +// // GIVEN we have a filesystem +// let fs = InMemoryFileSystem() + +// // WHEN we read a non-existing file +// // THEN an error occurs +// try withKnownIssue { +// let _ = try fs.readFileContents("/file/does/not/exists") +// } +// } + +// @Test +// func readingExistingFileReturnsExpectedContents() async throws { +// // GIVEN we have a filesytstem +// let fs = InMemoryFileSystem() +// // AND a file a path +// let pathUnderTest = AbsolutePath("/myFile.zip") +// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) +// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + +// // WHEN we read contents if the file +// let actualContents = try fs.readFileContents(pathUnderTest) + +// // THEN the actual contents should match the expected to match the +// #expect(actualContents == expectedContents, "Actual is not as expected") +// } + +// @Test +// func readingADirectoryFailsWithAnError() async throws { +// // GIVEN we have a filesytstem +// let fs = InMemoryFileSystem() +// // AND a file a path +// let pathUnderTest = AbsolutePath("/myFile.zip") +// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) +// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + +// // WHEN we read the contents of a directory +// // THEN we expect a failure to occur +// withKnownIssue { +// let _ = try fs.readFileContents(pathUnderTest.parentDirectory) +// } +// } +// } +// } diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index bb479d83893..a2f06d441d1 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -43,8 +43,6 @@ final class WorkspaceTests: XCTestCase { // } func testBasics() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -339,8 +337,6 @@ final class WorkspaceTests: XCTestCase { } func testMultipleRootPackages() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -398,8 +394,6 @@ final class WorkspaceTests: XCTestCase { } func testRootPackagesOverride() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -462,8 +456,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateRootPackages() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -501,8 +493,6 @@ final class WorkspaceTests: XCTestCase { /// Test that the explicit name given to a package is not used as its identity. func testExplicitPackageNameIsNotUsedAsPackageIdentity() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -560,7 +550,7 @@ final class WorkspaceTests: XCTestCase { roots: ["foo-package", "bar-package"], dependencies: [ .localSourceControl( - path: "/tmp/ws/pkgs/bar-package", + path: "\(sandbox)/pkgs/bar-package", requirement: .upToNextMajor(from: "1.0.0") ), ] @@ -576,8 +566,6 @@ final class WorkspaceTests: XCTestCase { /// Test that the remote repository is not resolved when a root package with same name is already present. func testRootAsDependency1() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -633,14 +621,12 @@ final class WorkspaceTests: XCTestCase { await workspace.checkManagedDependencies { result in result.check(notPresent: "baz") } - XCTAssertNoMatch(workspace.delegate.events, [.equal("fetching package: /tmp/ws/pkgs/Baz")]) + XCTAssertNoMatch(workspace.delegate.events, [.equal("fetching package: \(sandbox)/pkgs/Baz")]) XCTAssertNoMatch(workspace.delegate.events, [.equal("will resolve dependencies")]) } /// Test that a root package can be used as a dependency when the remote version was resolved previously. func testRootAsDependency2() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -728,8 +714,6 @@ final class WorkspaceTests: XCTestCase { } func testGraphRootDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -791,9 +775,8 @@ final class WorkspaceTests: XCTestCase { } } - func testCanResolveWithIncompatiblePackages() async throws { - try skipOnWindowsAsTestCurrentlyFails() + func testCanResolveWithIncompatiblePackages() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -896,8 +879,6 @@ final class WorkspaceTests: XCTestCase { } func testResolverCanHaveError() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -959,8 +940,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_empty() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1006,8 +985,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_newPackages() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1069,8 +1046,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_requirementChange_versionToBranch() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1139,8 +1114,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_requirementChange_versionToRevision() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let cPath = RelativePath("C") @@ -1192,8 +1165,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_requirementChange_localToBranch() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1261,8 +1232,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_requirementChange_versionToLocal() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1330,8 +1299,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_requirementChange_branchToLocal() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1400,8 +1367,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_other() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1471,8 +1436,6 @@ final class WorkspaceTests: XCTestCase { } func testPrecomputeResolution_notRequired() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let bPath = RelativePath("B") @@ -1538,8 +1501,6 @@ final class WorkspaceTests: XCTestCase { } func testLoadingRootManifests() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -1564,8 +1525,6 @@ final class WorkspaceTests: XCTestCase { } func testUpdate() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -1667,8 +1626,6 @@ final class WorkspaceTests: XCTestCase { } func testUpdateDryRun() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -1740,7 +1697,7 @@ final class WorkspaceTests: XCTestCase { .updated(.init(requirement: .version(Version("1.5.0")), products: .everything)) #endif - let path = AbsolutePath("/tmp/ws/pkgs/Foo") + let path = sandbox.appending(components: ["pkgs","Foo"]) let expectedChange = ( PackageReference.localSourceControl(identity: PackageIdentity(path: path), path: path), stateChange @@ -1764,8 +1721,6 @@ final class WorkspaceTests: XCTestCase { } func testPartialUpdate() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -1870,8 +1825,6 @@ final class WorkspaceTests: XCTestCase { } func testCleanAndReset() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -1951,10 +1904,8 @@ final class WorkspaceTests: XCTestCase { } func testDependencyManifestLoading() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") - let fs = InMemoryFileSystem() + let fs: InMemoryFileSystem = InMemoryFileSystem() let workspace = try await MockWorkspace( sandbox: sandbox, @@ -2032,8 +1983,6 @@ final class WorkspaceTests: XCTestCase { } func testDependencyManifestsOrder() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2103,8 +2052,6 @@ final class WorkspaceTests: XCTestCase { } func testBranchAndRevision() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2148,7 +2095,7 @@ final class WorkspaceTests: XCTestCase { ) // Get some revision identifier of Bar. - let bar = RepositorySpecifier(path: "/tmp/ws/pkgs/Bar") + let bar = RepositorySpecifier(path: "\(sandbox)/pkgs/Bar") let barRevision = workspace.repositoryProvider.specifierMap[bar]!.revisions[0] // We request Bar via revision. @@ -2169,8 +2116,6 @@ final class WorkspaceTests: XCTestCase { } func testResolve() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2244,8 +2189,6 @@ final class WorkspaceTests: XCTestCase { } func testDeletedCheckoutDirectory() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2291,9 +2234,8 @@ final class WorkspaceTests: XCTestCase { } } - func testMinimumRequiredToolsVersionInDependencyResolution() async throws { - try skipOnWindowsAsTestCurrentlyFails() + func testMinimumRequiredToolsVersionInDependencyResolution() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2335,8 +2277,6 @@ final class WorkspaceTests: XCTestCase { } func testToolsVersionRootPackages() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2415,9 +2355,8 @@ final class WorkspaceTests: XCTestCase { } } - func testEditDependency() async throws { - try skipOnWindowsAsTestCurrentlyFails() + func testEditDependency() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2504,7 +2443,7 @@ final class WorkspaceTests: XCTestCase { } // Edit bar at a custom path and branch (ToT). - let barPath = AbsolutePath("/tmp/ws/custom/bar") + let barPath = sandbox.appending(components: ["custom", "bar"]) await workspace.checkEdit(packageIdentity: "bar", path: barPath, checkoutBranch: "dev") { diagnostics in XCTAssertNoDiagnostics(diagnostics) } @@ -2526,8 +2465,6 @@ final class WorkspaceTests: XCTestCase { } func testUnsafeFlagsInEditedPackage() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2580,7 +2517,7 @@ final class WorkspaceTests: XCTestCase { XCTAssertNoDiagnostics(diagnostics) } - let editedFooPath = AbsolutePath("/tmp/ws/Foo") + let editedFooPath = sandbox.appending("Foo") await workspace.checkEdit(packageIdentity: "Foo", path: editedFooPath) { diagnostics in XCTAssertNoDiagnostics(diagnostics) } @@ -2591,8 +2528,6 @@ final class WorkspaceTests: XCTestCase { } func testMissingEditCanRestoreOriginalCheckout() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2656,8 +2591,6 @@ final class WorkspaceTests: XCTestCase { } func testCanUneditRemovedDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2732,8 +2665,6 @@ final class WorkspaceTests: XCTestCase { } func testDependencyResolutionWithEdit() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2855,8 +2786,6 @@ final class WorkspaceTests: XCTestCase { } func testPrefetchingWithOverridenPackage() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2942,8 +2871,6 @@ final class WorkspaceTests: XCTestCase { // Test that changing a particular dependency re-resolves the graph. func testChangeOneDependency() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3030,8 +2957,6 @@ final class WorkspaceTests: XCTestCase { } func testResolutionFailureWithEditedDependency() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3100,6 +3025,11 @@ final class WorkspaceTests: XCTestCase { workspace.manifestLoader.manifests[editedFooKey] = manifest } + } + func testResolutionFailureWithEditedDependencyWithABadGraph() async throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + // Try resolving a bad graph. let deps: [MockDependency] = [ .sourceControl(path: "./Bar", requirement: .exact("1.1.0"), products: .specific(["Bar"])), @@ -3152,8 +3082,6 @@ final class WorkspaceTests: XCTestCase { } func testStateModified() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3245,8 +3173,6 @@ final class WorkspaceTests: XCTestCase { } func testSkipUpdate() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3296,8 +3222,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalDependencyBasics() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3375,9 +3299,8 @@ final class WorkspaceTests: XCTestCase { } } - func testLocalDependencyTransitive() async throws { - try skipOnWindowsAsTestCurrentlyFails() + func testLocalDependencyTransitive() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3440,8 +3363,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalDependencyWithPackageUpdate() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3506,8 +3427,6 @@ final class WorkspaceTests: XCTestCase { } func testMissingLocalDependencyDiagnostic() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3548,8 +3467,6 @@ final class WorkspaceTests: XCTestCase { } func testRevisionVersionSwitch() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3619,8 +3536,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalVersionSwitch() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3690,8 +3605,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalLocalSwitch() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3764,8 +3677,6 @@ final class WorkspaceTests: XCTestCase { // Test that switching between two same local packages placed at // different locations works correctly. func testDependencySwitchLocalWithSameIdentity() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3843,8 +3754,6 @@ final class WorkspaceTests: XCTestCase { // Test that switching between two remote packages at // different locations works correctly. func testDependencySwitchRemoteWithSameIdentity() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3923,8 +3832,6 @@ final class WorkspaceTests: XCTestCase { } func testResolvedFileUpdate() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3980,15 +3887,12 @@ final class WorkspaceTests: XCTestCase { } func testResolvedFileSchemeToolsVersion() async throws { - try skipOnWindowsAsTestCurrentlyFails() - - let fs = InMemoryFileSystem() - for pair in [ (ToolsVersion.v5_2, ToolsVersion.v5_2), (ToolsVersion.v5_6, ToolsVersion.v5_6), (ToolsVersion.v5_2, ToolsVersion.v5_6), ] { + let fs = InMemoryFileSystem() let sandbox = AbsolutePath("/tmp/ws/") let workspace = try await MockWorkspace( sandbox: sandbox, @@ -4045,16 +3949,16 @@ final class WorkspaceTests: XCTestCase { let minToolsVersion = [pair.0, pair.1].min()! let expectedSchemeVersion = minToolsVersion >= .v5_6 ? 2 : 1 + let actualSchemeVersion = try workspace.getOrCreateWorkspace().resolvedPackagesStore.load().schemeVersion() XCTAssertEqual( - try workspace.getOrCreateWorkspace().resolvedPackagesStore.load().schemeVersion(), - expectedSchemeVersion + actualSchemeVersion, + expectedSchemeVersion, + "Actual scheme version (\(actualSchemeVersion)) is not as expected (\(expectedSchemeVersion)). Pair 0 (\(pair.0)) pair 1 (\(pair.1))" ) } } func testResolvedFileStableCanonicalLocation() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -4297,8 +4201,6 @@ final class WorkspaceTests: XCTestCase { } func testPreferResolvedFileWhenExists() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -4554,21 +4456,19 @@ final class WorkspaceTests: XCTestCase { } } } + } - // util - func checkPinnedVersion(pin: ResolvedPackagesStore.ResolvedPackage, version: Version) { - switch pin.state { - case .version(let pinnedVersion, _): - XCTAssertEqual(pinnedVersion, version) - default: - XCTFail("non-version pin \(pin.state)") - } + // util + func checkPinnedVersion(pin: ResolvedPackagesStore.ResolvedPackage, version: Version) { + switch pin.state { + case .version(let pinnedVersion, _): + XCTAssertEqual(pinnedVersion, version) + default: + XCTFail("non-version pin \(pin.state)") } } func testPackageSimpleMirrorPath() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -4661,8 +4561,6 @@ final class WorkspaceTests: XCTestCase { } func testPackageMirrorPath() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -4766,8 +4664,6 @@ final class WorkspaceTests: XCTestCase { } func testPackageSimpleMirrorURL() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -4857,8 +4753,6 @@ final class WorkspaceTests: XCTestCase { } func testPackageMirrorURL() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -4964,8 +4858,6 @@ final class WorkspaceTests: XCTestCase { } func testPackageMirrorURLToRegistry() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5036,8 +4928,6 @@ final class WorkspaceTests: XCTestCase { } func testPackageMirrorRegistryToURL() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5111,8 +5001,6 @@ final class WorkspaceTests: XCTestCase { // file for a transitive dependency whose URL is later changed to // something else, while keeping the same package identity. func testTransitiveDependencySwitchWithSameIdentity() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5266,8 +5154,6 @@ final class WorkspaceTests: XCTestCase { } func testForceResolveToResolvedVersions() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5394,8 +5280,6 @@ final class WorkspaceTests: XCTestCase { } func testForceResolveToResolvedVersionsDuplicateLocalDependency() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5447,8 +5331,6 @@ final class WorkspaceTests: XCTestCase { } func testForceResolveWithNoResolvedFile() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5506,8 +5388,6 @@ final class WorkspaceTests: XCTestCase { } func testForceResolveToResolvedVersionsLocalPackage() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5549,8 +5429,6 @@ final class WorkspaceTests: XCTestCase { } func testForceResolveToResolvedVersionsLocalPackageInAdditionalDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5652,8 +5530,6 @@ final class WorkspaceTests: XCTestCase { } func testRevisionDepOnLocal() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5712,8 +5588,6 @@ final class WorkspaceTests: XCTestCase { } func testRootPackagesOverrideBasenameMismatch() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5764,8 +5638,6 @@ final class WorkspaceTests: XCTestCase { } func testManagedDependenciesNotCaseSensitive() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5860,8 +5732,6 @@ final class WorkspaceTests: XCTestCase { } func testUnsafeFlags() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5942,8 +5812,6 @@ final class WorkspaceTests: XCTestCase { } func testUnsafeFlagsInFoundation() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -5993,8 +5861,6 @@ final class WorkspaceTests: XCTestCase { } func testEditDependencyHadOverridableConstraints() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6096,8 +5962,6 @@ final class WorkspaceTests: XCTestCase { } func testTargetBasedDependency() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6206,8 +6070,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalArchivedArtifactExtractionHappyPath() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6312,17 +6174,17 @@ final class WorkspaceTests: XCTestCase { try fs.writeFileContents(bFrameworkArchivePath, bytes: ByteString([0xB0])) // Ensure that the artifacts do not exist yet - XCTAssertFalse(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/A/A1.xcframework"))) - XCTAssertFalse(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/A/A2.artifactbundle"))) - XCTAssertFalse(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/B/B.xcframework"))) + XCTAssertFalse(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "A", "A1.xcframework"]))) + XCTAssertFalse(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "A", "A2", "artifactbundle"]))) + XCTAssertFalse(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "B", "B", "xcframework"]))) try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) // Ensure that the artifacts have been properly extracted - XCTAssertTrue(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/a/A1/A1.xcframework"))) - XCTAssertTrue(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/a/A2/A2.artifactbundle"))) - XCTAssertTrue(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/b/B/B.xcframework"))) + XCTAssertTrue(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "a", "A1", "A1.xcframework"]))) + XCTAssertTrue(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "a", "A2", "A2.artifactbundle"]))) + XCTAssertTrue(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "b", "B", "B.xcframework"]))) // Ensure that the original archives have been untouched XCTAssertTrue(fs.exists(a1FrameworkArchivePath)) @@ -6331,15 +6193,15 @@ final class WorkspaceTests: XCTestCase { // Ensure that the temporary folders have been properly created XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A1"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A2"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/b/B"), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A1"]), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A2"]), + sandbox.appending(components: [".build", "artifacts", "extract", "b", "B"]), ]) // Ensure that the temporary directories have been removed - XCTAssertTrue(try! fs.getDirectoryContents(AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A1")).isEmpty) - XCTAssertTrue(try! fs.getDirectoryContents(AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A2")).isEmpty) - XCTAssertTrue(try! fs.getDirectoryContents(AbsolutePath("/tmp/ws/.build/artifacts/extract/b/B")).isEmpty) + XCTAssertTrue(try! fs.getDirectoryContents(sandbox.appending(components: [".build", "artifacts", "extract", "a", "A1"])).isEmpty) + XCTAssertTrue(try! fs.getDirectoryContents(sandbox.appending(components: [".build", "artifacts", "extract", "a", "A2"])).isEmpty) + XCTAssertTrue(try! fs.getDirectoryContents(sandbox.appending(components: [".build", "artifacts", "extract", "b", "B"])).isEmpty) } await workspace.checkManagedArtifacts { result in @@ -6378,8 +6240,6 @@ final class WorkspaceTests: XCTestCase { // It ensures that all the appropriate clean-up operations are executed, and the workspace // contains the correct set of managed artifacts after the transition. func testLocalArchivedArtifactSourceTransitionPermutations() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6595,31 +6455,31 @@ final class WorkspaceTests: XCTestCase { XCTAssertTrue(fs.exists(a4FrameworkArchivePath)) // Ensure that the new artifacts have been properly extracted - XCTAssertTrue(try fs.exists(AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A1/\(a1FrameworkName)"))) + XCTAssertTrue(try fs.exists(AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A1/\(a1FrameworkName)"))) XCTAssertTrue( try fs .exists( - AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A3/\(a3FrameworkName)/local-archived") + AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A3/\(a3FrameworkName)/local-archived") ) ) XCTAssertTrue( try fs - .exists(AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A4/\(a4FrameworkName)/remote")) + .exists(AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A4/\(a4FrameworkName)/remote")) ) // Ensure that the old artifacts have been removed - XCTAssertFalse(try fs.exists(AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A2/\(a2FrameworkName)"))) + XCTAssertFalse(try fs.exists(AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A2/\(a2FrameworkName)"))) XCTAssertFalse( try fs - .exists(AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A3/\(a3FrameworkName)/remote")) + .exists(AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A3/\(a3FrameworkName)/remote")) ) XCTAssertFalse( try fs .exists( - AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A4/\(a4FrameworkName)/local-archived") + AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A4/\(a4FrameworkName)/local-archived") ) ) - XCTAssertFalse(try fs.exists(AbsolutePath(validating: "/tmp/ws/.build/artifacts/a/A5/\(a5FrameworkName)"))) + XCTAssertFalse(try fs.exists(AbsolutePath(validating: "\(sandbox)/.build/artifacts/a/A5/\(a5FrameworkName)"))) } await workspace.checkManagedArtifacts { result in @@ -6651,10 +6511,8 @@ final class WorkspaceTests: XCTestCase { } func testLocalArchivedArtifactNameDoesNotMatchTargetName() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") - let fs = InMemoryFileSystem() + let fs: InMemoryFileSystem = InMemoryFileSystem() // create a dummy xcframework directory from the request archive let archiver = MockArchiver(handler: { archiver, archivePath, destinationPath, completion in @@ -6712,8 +6570,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalArchivedArtifactExtractionError() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6765,8 +6621,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalArchiveDoesNotMatchTargetName() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6843,8 +6697,6 @@ final class WorkspaceTests: XCTestCase { ////// STAET ATDIN func testLocalArchivedArtifactChecksumChange() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -6928,7 +6780,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, _ in // Ensure that only the artifact archive with the changed checksum has been extracted XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A1"), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "A1"]), ]) } @@ -6949,8 +6801,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalArchivedArtifactStripFirstComponent() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -7024,17 +6874,17 @@ final class WorkspaceTests: XCTestCase { try fs.writeFileContents(archivesPath.appending("nested2.zip"), bytes: ByteString([0x3])) // ensure that the artifacts do not exist yet - XCTAssertFalse(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root/flat/flat.xcframework"))) - XCTAssertFalse(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root/nested/nested.artifactbundle"))) - XCTAssertFalse(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root/nested2/nested2.xcframework"))) + XCTAssertFalse(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root", "flat", "flat.xcframework"]))) + XCTAssertFalse(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root", "nested", "nested.artifactbundle"]))) + XCTAssertFalse(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root", "nested2", "nested2.xcframework"]))) try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root"]))) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/flat"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/nested"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/nested2"), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "flat"]), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "nested"]), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "nested2"]), ]) } @@ -7061,9 +6911,7 @@ final class WorkspaceTests: XCTestCase { } func testLocalArtifactHappyPath() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - - let sandbox = AbsolutePath("/tmp/ws/") + let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() let workspace = try await MockWorkspace( @@ -7115,8 +6963,6 @@ final class WorkspaceTests: XCTestCase { } func testLocalArtifactDoesNotExist() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -7146,13 +6992,13 @@ final class WorkspaceTests: XCTestCase { testDiagnostics(diagnostics) { result in result.checkUnordered( diagnostic: .contains( - "local binary target 'A1' at '\(AbsolutePath("/tmp/ws/roots/Root/XCFrameworks/incorrect.xcframework"))' does not contain a binary artifact." + "local binary target 'A1' at '\(sandbox.appending(components: ["roots", "Root", "XCFrameworks", "incorrect.xcframework"]))' does not contain a binary artifact." ), severity: .error ) result.checkUnordered( diagnostic: .contains( - "local binary target 'A2' at '\(AbsolutePath("/tmp/ws/roots/Root/ArtifactBundles/incorrect.artifactbundle"))' does not contain a binary artifact." + "local binary target 'A2' at '\(sandbox.appending(components: ["roots", "Root", "ArtifactBundles", "incorrect.artifactbundle"]))' does not contain a binary artifact." ), severity: .error ) @@ -7161,8 +7007,6 @@ final class WorkspaceTests: XCTestCase { } func testArtifactDownloadHappyPath() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeKeyValueStore() @@ -7284,8 +7128,8 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/a"))) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/b"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "a"]))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "b"]))) XCTAssertEqual(downloads.map(\.key.absoluteString).sorted(), [ "https://a.com/a1.zip", "https://a.com/a2.zip", @@ -7297,9 +7141,9 @@ final class WorkspaceTests: XCTestCase { ByteString([0xB0]).hexadecimalRepresentation, ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A1"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A2"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/b/B"), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A1"]), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A2"]), + sandbox.appending(components: [".build", "artifacts", "extract", "b", "B"]), ]) XCTAssertEqual( downloads.map(\.value).sorted(), @@ -7352,8 +7196,6 @@ final class WorkspaceTests: XCTestCase { } func testArtifactDownloadWithPreviousState() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeKeyValueStore() @@ -7581,14 +7423,14 @@ final class WorkspaceTests: XCTestCase { severity: .error ) } - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/b"))) - XCTAssert(fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/a/A1/A1.xcframework"))) - XCTAssert(fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/a/A2/A2.xcframework"))) - XCTAssert(!fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/a/A3/A3.xcframework"))) - XCTAssert(!fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/a/A4/A4.xcframework"))) - XCTAssert(!fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/a/A5/A5.xcframework"))) - XCTAssert(fs.exists(AbsolutePath("/tmp/ws/pkgs/a/XCFrameworks/A7.xcframework"))) - XCTAssert(!fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/Foo"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "b"]))) + XCTAssert(fs.exists(sandbox.appending(components: [".build", "artifacts", "a", "A1", "A1.xcframework"]))) + XCTAssert(fs.exists(sandbox.appending(components: [".build", "artifacts", "a", "A2", "A2.xcframework"]))) + XCTAssert(!fs.exists(sandbox.appending(components: [".build", "artifacts", "a", "A3", "A3.xcframework"]))) + XCTAssert(!fs.exists(sandbox.appending(components: [".build", "artifacts", "a", "A4", "A4.xcframework"]))) + XCTAssert(!fs.exists(sandbox.appending(components: [".build", "artifacts", "a", "A5", "A5.xcframework"]))) + XCTAssert(fs.exists(sandbox.appending(components: ["pkgs", "a", "XCFrameworks", "A7.xcframework"]))) + XCTAssert(!fs.exists(sandbox.appending(components: [".build", "artifacts", "Foo"]))) XCTAssertEqual(downloads.map(\.key.absoluteString).sorted(), [ "https://a.com/a2.zip", "https://a.com/a3.zip", @@ -7602,10 +7444,10 @@ final class WorkspaceTests: XCTestCase { ByteString([0xB0]).hexadecimalRepresentation, ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A2"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A3"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A7"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/b/B"), + sandbox.appending(components: [".build", "artifacts", "extract","a", "A2"]), + sandbox.appending(components: [".build", "artifacts", "extract","a", "A3"]), + sandbox.appending(components: [".build", "artifacts", "extract","a", "A7"]), + sandbox.appending(components: [".build", "artifacts", "extract","b", "B"]), ]) XCTAssertEqual( downloads.map(\.value).sorted(), @@ -7662,8 +7504,6 @@ final class WorkspaceTests: XCTestCase { } func testArtifactDownloadTwice() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeArrayStore<(URL, AbsolutePath)>() @@ -7740,7 +7580,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root"]))) XCTAssertEqual(workspace.checksumAlgorithm.hashes.map(\.hexadecimalRepresentation).sorted(), [ ByteString([0xA1]).hexadecimalRepresentation, ]) @@ -7750,7 +7590,7 @@ final class WorkspaceTests: XCTestCase { "https://a.com/a1.zip", ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A1"), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "A1"]), ]) XCTAssertEqual( downloads.map(\.1).sorted(), @@ -7765,7 +7605,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root"]))) XCTAssertEqual(workspace.checksumAlgorithm.hashes.map(\.hexadecimalRepresentation).sorted(), [ ByteString([0xA1]).hexadecimalRepresentation, ByteString([0xA1]).hexadecimalRepresentation, @@ -7776,8 +7616,8 @@ final class WorkspaceTests: XCTestCase { "https://a.com/a1.zip", "https://a.com/a1.zip", ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A1"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A1"), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "A1"]), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "A1"]), ]) XCTAssertEqual( downloads.map(\.1).sorted(), @@ -7786,8 +7626,6 @@ final class WorkspaceTests: XCTestCase { } func testArtifactDownloadServerError() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let fs = InMemoryFileSystem() let sandbox = AbsolutePath("/tmp/ws/") try fs.createDirectory(sandbox, recursive: true) @@ -7841,8 +7679,8 @@ final class WorkspaceTests: XCTestCase { } // make sure artifact downloaded is deleted - XCTAssertTrue(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root"))) - XCTAssertFalse(fs.exists(AbsolutePath("/tmp/ws/.build/artifacts/root/a.zip"))) + XCTAssertTrue(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root"]))) + XCTAssertFalse(fs.exists(sandbox.appending(components: [".build", "artifacts", "root", "a.zip"]))) // make sure the cached artifact is also deleted let artifactCacheKey = artifactUrl.spm_mangledToC99ExtendedIdentifier() @@ -7858,8 +7696,6 @@ final class WorkspaceTests: XCTestCase { } func testArtifactDownloaderOrArchiverError() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -7884,7 +7720,7 @@ final class WorkspaceTests: XCTestCase { } let archiver = MockArchiver(handler: { _, _, destinationPath, completion in - XCTAssertEqual(destinationPath.parentDirectory, AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A2")) + XCTAssertEqual(destinationPath.parentDirectory, sandbox.appending(components: [".build", "artifacts", "extract", "root", "A2"])) completion(.failure(DummyError())) }) @@ -7946,9 +7782,8 @@ final class WorkspaceTests: XCTestCase { } } - func testDownloadedArtifactNotAnArchiveError() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) + func testDownloadedArtifactNotAnArchiveError() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8057,8 +7892,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactInvalid() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8130,8 +7963,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactDoesNotMatchTargetName() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8207,11 +8038,14 @@ final class WorkspaceTests: XCTestCase { } func testArtifactChecksum() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) + try skipOnWindowsAsTestCurrentlyFails(because: #""" + threw error "\tmp\ws doesn't exist in file system" because there is an issue with InMemoryFileSystem readFileContents(...) on Windows + """#) let fs = InMemoryFileSystem() try fs.createMockToolchain() let sandbox = AbsolutePath("/tmp/ws/") + try fs.createDirectory(sandbox, recursive: true) let checksumAlgorithm = MockHashAlgorithm() @@ -8281,8 +8115,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactChecksumChange() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8335,8 +8167,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactChecksumChangeURLChange() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8428,8 +8258,6 @@ final class WorkspaceTests: XCTestCase { } func testArtifactDownloadAddsAcceptHeader() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let acceptHeaders = ThreadSafeBox([String]()) @@ -8506,8 +8334,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactNoCache() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeBox(0) @@ -8597,8 +8423,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactCache() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeBox(0) @@ -8703,8 +8527,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactTransitive() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeKeyValueStore() @@ -8845,7 +8667,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/a"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "a"]))) XCTAssertEqual(downloads.map(\.key.absoluteString).sorted(), [ "https://a.com/a.zip", ]) @@ -8853,7 +8675,7 @@ final class WorkspaceTests: XCTestCase { ByteString([0xA]).hexadecimalRepresentation, ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A"), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A"]), ]) XCTAssertEqual( downloads.map(\.value).sorted(), @@ -8875,8 +8697,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactArchiveExists() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() // this relies on internal knowledge of the destination path construction @@ -8989,8 +8809,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactConcurrency() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -9103,8 +8921,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadedArtifactStripFirstComponent() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() let downloads = ThreadSafeKeyValueStore() @@ -9211,7 +9027,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/root"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "root"]))) XCTAssertEqual(downloads.map(\.key.absoluteString).sorted(), [ "https://a.com/flat.zip", "https://a.com/nested.zip", @@ -9223,9 +9039,9 @@ final class WorkspaceTests: XCTestCase { ByteString([0x03]).hexadecimalRepresentation, ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/flat"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/nested"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/nested2"), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "flat"]), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "nested"]), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "nested2"]), ]) XCTAssertEqual( downloads.map(\.value).sorted(), @@ -9265,10 +9081,8 @@ final class WorkspaceTests: XCTestCase { } func testArtifactMultipleExtensions() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") - let fs = InMemoryFileSystem() + let fs: InMemoryFileSystem = InMemoryFileSystem() let downloads = ThreadSafeKeyValueStore() // returns a dummy zipfile for the requested artifact @@ -9359,8 +9173,8 @@ final class WorkspaceTests: XCTestCase { ByteString([0xA2]).hexadecimalRepresentation, ]) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A1"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/root/A2"), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "A1"]), + sandbox.appending(components: [".build", "artifacts", "extract", "root", "A2"]), ]) XCTAssertEqual( downloads.map(\.value).sorted(), @@ -9391,8 +9205,6 @@ final class WorkspaceTests: XCTestCase { } func testLoadRootPackageWithBinaryDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -9437,8 +9249,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexFilesHappyPath() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -9606,8 +9416,8 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in XCTAssertNoDiagnostics(diagnostics) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/a"))) - XCTAssert(fs.isDirectory(AbsolutePath("/tmp/ws/.build/artifacts/b"))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "a"]))) + XCTAssert(fs.isDirectory(sandbox.appending(components: [".build", "artifacts", "b"]))) XCTAssertEqual(downloads.map(\.key.absoluteString).sorted(), [ "https://a.com/a1.zip", "https://a.com/a2/a2.zip", @@ -9626,9 +9436,9 @@ final class WorkspaceTests: XCTestCase { ).map(\.hexadecimalRepresentation).sorted() ) XCTAssertEqual(archiver.extractions.map(\.destinationPath.parentDirectory).sorted(), [ - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A1"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/a/A2"), - AbsolutePath("/tmp/ws/.build/artifacts/extract/b/B"), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A1"]), + sandbox.appending(components: [".build", "artifacts", "extract", "a", "A2"]), + sandbox.appending(components: [".build", "artifacts", "extract", "b", "B"]), ]) XCTAssertEqual( downloads.map(\.value).sorted(), @@ -9681,8 +9491,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexServerError() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -9725,8 +9533,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexFileBadChecksum() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -9793,8 +9599,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexFileChecksumChanges() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -9845,8 +9649,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexFileBadArchivesChecksum() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -9954,8 +9756,6 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexFileArchiveNotFound() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -10028,10 +9828,9 @@ final class WorkspaceTests: XCTestCase { } func testDownloadArchiveIndexTripleNotFound() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() + try fs.createMockToolchain() let hostToolchain = try UserToolchain.mockHostToolchain(fs) @@ -10100,8 +9899,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateDependencyIdentityWithNameAtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10163,7 +9960,7 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in result.check( - diagnostic: "Conflicting identity for utility: dependency '/tmp/ws/pkgs/bar/utility' and dependency '/tmp/ws/pkgs/foo/utility' both point to the same package identity 'utility'.", + diagnostic: "Conflicting identity for utility: dependency '\(CanonicalPackageLocation(sandbox.pathString))/pkgs/bar/utility' and dependency '\(CanonicalPackageLocation(sandbox.pathString))/pkgs/foo/utility' both point to the same package identity 'utility'.", severity: .error ) } @@ -10171,8 +9968,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateDependencyIdentityWithoutNameAtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10225,8 +10020,9 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( - diagnostic: "Conflicting identity for utility: dependency '/tmp/ws/pkgs/bar/utility' and dependency '/tmp/ws/pkgs/foo/utility' both point to the same package identity 'utility'.", + diagnostic: "Conflicting identity for utility: dependency '\(tmpDirCanonicalPackageLocation)/pkgs/bar/utility' and dependency '\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility' both point to the same package identity 'utility'.", severity: .error ) } @@ -10234,8 +10030,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateExplicitDependencyName_AtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10305,8 +10099,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateManifestNameAtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10362,8 +10154,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateManifestName_ExplicitProductPackage_AtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10419,8 +10209,6 @@ final class WorkspaceTests: XCTestCase { } func testManifestNameAndIdentityConflict_AtRoot_Pre52() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10476,8 +10264,6 @@ final class WorkspaceTests: XCTestCase { } func testManifestNameAndIdentityConflict_AtRoot_Post52_Incorrect() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10542,8 +10328,6 @@ final class WorkspaceTests: XCTestCase { } func testManifestNameAndIdentityConflict_AtRoot_Post52_Correct() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10599,8 +10383,6 @@ final class WorkspaceTests: XCTestCase { } func testManifestNameAndIdentityConflict_ExplicitDependencyNames_AtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10665,8 +10447,6 @@ final class WorkspaceTests: XCTestCase { } func testManifestNameAndIdentityConflict_ExplicitDependencyNames_ExplicitProductPackage_AtRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10731,8 +10511,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityWithNames() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10813,8 +10591,9 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( - diagnostic: "Conflicting identity for utility: dependency '/tmp/ws/pkgs/other/utility' and dependency '/tmp/ws/pkgs/foo/utility' both point to the same package identity 'utility'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->/tmp/ws/pkgs/bar->/tmp/ws/pkgs/other/utility (B) /tmp/ws/roots/root->/tmp/ws/pkgs/foo/utility. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency.", + diagnostic: "Conflicting identity for utility: dependency '\(tmpDirCanonicalPackageLocation)/pkgs/other/utility' and dependency '\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility' both point to the same package identity 'utility'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/bar->\(tmpDirCanonicalPackageLocation)/pkgs/other/utility (B) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency.", severity: .error ) } @@ -10822,9 +10601,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityMultiplePossibleChains() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #""" - threw error "\tmp\ws doesn't exist in file system" - """#) let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -10969,22 +10745,26 @@ final class WorkspaceTests: XCTestCase { ] ) + let sandboxCanonicalPackageLocation: CanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in result.check( - diagnostic: "Conflicting identity for glass: dependency '/tmp/ws/pkgs/tempered/glass' and dependency '/tmp/ws/pkgs/standard/glass' both point to the same package identity 'glass'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->/tmp/ws/pkgs/house->/tmp/ws/pkgs/premium_window->/tmp/ws/pkgs/tempered/glass (B) /tmp/ws/roots/root->/tmp/ws/pkgs/shack->/tmp/ws/pkgs/standard/glass. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency.", + diagnostic: "Conflicting identity for glass: dependency '\(sandboxCanonicalPackageLocation)/pkgs/tempered/glass' and dependency '\(sandboxCanonicalPackageLocation)/pkgs/standard/glass' both point to the same package identity 'glass'. The dependencies are introduced through the following chains: (A) \(sandboxCanonicalPackageLocation)/roots/root->\(sandboxCanonicalPackageLocation)/pkgs/house->\(sandboxCanonicalPackageLocation)/pkgs/premium_window->\(sandboxCanonicalPackageLocation)/pkgs/tempered/glass (B) \(sandboxCanonicalPackageLocation)/roots/root->\(sandboxCanonicalPackageLocation)/pkgs/shack->\(sandboxCanonicalPackageLocation)/pkgs/standard/glass. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency.", severity: .error ) } } try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in + let prefix1 = sandbox.appending(components: ["pkgs", "tempered", "glass"]) result.checkUnordered( - diagnostic: .contains("Conflicting identity for glass: chains of dependencies for /tmp/ws/pkgs/tempered/glass: [[/tmp/ws/roots/root, /tmp/ws/pkgs/house, /tmp/ws/pkgs/premium_window, /tmp/ws/pkgs/tempered/glass]]"), + diagnostic: .contains("Conflicting identity for glass: chains of dependencies for \(prefix1): [[\(sandboxCanonicalPackageLocation)/roots/root, \(sandboxCanonicalPackageLocation)/pkgs/house, \(sandboxCanonicalPackageLocation)/pkgs/premium_window, \(sandboxCanonicalPackageLocation)/pkgs/tempered/glass]]"), severity: .debug ) + + let prefix2 = sandbox.appending(components: ["pkgs", "standard", "glass"]) result.checkUnordered( - diagnostic: .contains("Conflicting identity for glass: chains of dependencies for /tmp/ws/pkgs/standard/glass: [[/tmp/ws/roots/root, /tmp/ws/pkgs/shack, /tmp/ws/pkgs/standard/glass], [/tmp/ws/roots/root, /tmp/ws/pkgs/house, /tmp/ws/pkgs/budget_window, /tmp/ws/pkgs/standard/glass]]"), + diagnostic: .contains("Conflicting identity for glass: chains of dependencies for \(prefix2): [[\(sandboxCanonicalPackageLocation)/roots/root, \(sandboxCanonicalPackageLocation)/pkgs/shack, \(sandboxCanonicalPackageLocation)/pkgs/standard/glass], [\(sandboxCanonicalPackageLocation)/roots/root, \(sandboxCanonicalPackageLocation)/pkgs/house, \(sandboxCanonicalPackageLocation)/pkgs/budget_window, \(sandboxCanonicalPackageLocation)/pkgs/standard/glass]]"), severity: .debug ) } @@ -10992,10 +10772,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateIdentityDependenciesMultipleRoots() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #""" - threw error "\tmp\ws doesn't exist in file system" - """#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11068,12 +10844,16 @@ final class WorkspaceTests: XCTestCase { testPartialDiagnostics(diagnostics, minSeverity: .debug) { result in // Order of roots processing is not deterministic. To make the test less brittle, we check debug // output of individual conflicts instead of a summarized error message. + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) + let prefix = sandbox.appending(components: ["pkgs", "standing", "water"]) result.checkUnordered( - diagnostic: .contains("Conflicting identity for water: chains of dependencies for /tmp/ws/pkgs/standing/water: [[/tmp/ws/roots/lake, /tmp/ws/pkgs/standing/water]]"), + diagnostic: .contains("Conflicting identity for water: chains of dependencies for \(prefix): [[\(tmpDirCanonicalPackageLocation)/roots/lake, \(tmpDirCanonicalPackageLocation)/pkgs/standing/water]]"), severity: .debug ) + + let prefix2 = sandbox.appending(components: ["pkgs", "flowing", "water"]) result.checkUnordered( - diagnostic: .contains("Conflicting identity for water: chains of dependencies for /tmp/ws/pkgs/flowing/water: [[/tmp/ws/roots/river, /tmp/ws/pkgs/flowing/water]]"), + diagnostic: .contains("Conflicting identity for water: chains of dependencies for \(prefix2): [[\(tmpDirCanonicalPackageLocation)/roots/river, \(tmpDirCanonicalPackageLocation)/pkgs/flowing/water]]"), severity: .debug ) } @@ -11081,8 +10861,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityWithoutNames() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11153,8 +10931,9 @@ final class WorkspaceTests: XCTestCase { // we will escalate this to an error in a few versions to tighten up the validation try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( - diagnostic: "Conflicting identity for utility: dependency '/tmp/ws/pkgs/other-foo/utility' and dependency '/tmp/ws/pkgs/foo/utility' both point to the same package identity 'utility'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->/tmp/ws/pkgs/bar->/tmp/ws/pkgs/other-foo/utility (B) /tmp/ws/roots/root->/tmp/ws/pkgs/foo/utility. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency. This will be escalated to an error in future versions of SwiftPM.", + diagnostic: "Conflicting identity for utility: dependency '\(tmpDirCanonicalPackageLocation)/pkgs/other-foo/utility' and dependency '\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility' both point to the same package identity 'utility'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/bar->\(tmpDirCanonicalPackageLocation)/pkgs/other-foo/utility (B) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency. This will be escalated to an error in future versions of SwiftPM.", severity: .warning ) // FIXME: rdar://72940946 @@ -11168,8 +10947,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentitySimilarURLs1() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11245,8 +11022,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentitySimilarURLs2() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11322,8 +11097,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityGitHubURLs1() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11399,8 +11172,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityGitHubURLs2() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11479,8 +11250,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityUnfamiliarURLs() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11557,8 +11326,9 @@ final class WorkspaceTests: XCTestCase { // we will escalate this to an error in a few versions to tighten up the validation try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( - diagnostic: "Conflicting identity for foo: dependency 'github.com/foo-moved/foo' and dependency 'github.com/foo/foo' both point to the same package identity 'foo'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->/tmp/ws/pkgs/bar->github.com/foo-moved/foo (B) /tmp/ws/roots/root->github.com/foo/foo. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency. This will be escalated to an error in future versions of SwiftPM.", + diagnostic: "Conflicting identity for foo: dependency 'github.com/foo-moved/foo' and dependency 'github.com/foo/foo' both point to the same package identity 'foo'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/bar->github.com/foo-moved/foo (B) \(tmpDirCanonicalPackageLocation)/roots/root->github.com/foo/foo. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency. This will be escalated to an error in future versions of SwiftPM.", severity: .warning ) } @@ -11566,8 +11336,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateTransitiveIdentityWithSimilarURLs() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11690,8 +11458,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateNestedTransitiveIdentityWithNames() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11775,8 +11541,9 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["Root"]) { _, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( - diagnostic: "Conflicting identity for utility: dependency '/tmp/ws/pkgs/other/utility' and dependency '/tmp/ws/pkgs/foo/utility' both point to the same package identity 'utility'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->/tmp/ws/pkgs/foo/utility->/tmp/ws/pkgs/bar->/tmp/ws/pkgs/other/utility (B) /tmp/ws/roots/root->/tmp/ws/pkgs/foo/utility. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency.", + diagnostic: "Conflicting identity for utility: dependency '\(tmpDirCanonicalPackageLocation)/pkgs/other/utility' and dependency '\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility' both point to the same package identity 'utility'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility->\(tmpDirCanonicalPackageLocation)/pkgs/bar->\(tmpDirCanonicalPackageLocation)/pkgs/other/utility (B) \(tmpDirCanonicalPackageLocation)/roots/root->\(tmpDirCanonicalPackageLocation)/pkgs/foo/utility. If there are multiple chains that lead to the same dependency, only the first chain is shown here. To see all chains use debug output option. To resolve the conflict, coordinate with the maintainer of the package that introduces the conflicting dependency.", severity: .error ) } @@ -11784,8 +11551,6 @@ final class WorkspaceTests: XCTestCase { } func testDuplicateNestedTransitiveIdentityWithoutNames() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11870,8 +11635,6 @@ final class WorkspaceTests: XCTestCase { } func testRootPathConflictsWithTransitiveIdentity() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11947,8 +11710,6 @@ final class WorkspaceTests: XCTestCase { } func testDeterministicURLPreference() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -12110,8 +11871,6 @@ final class WorkspaceTests: XCTestCase { } func testDeterministicURLPreferenceWithRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -12251,8 +12010,6 @@ final class WorkspaceTests: XCTestCase { } func testCanonicalURLWithPreviousManagedState() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -12415,8 +12172,6 @@ final class WorkspaceTests: XCTestCase { } func testCanonicalURLChanges() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -12530,8 +12285,6 @@ final class WorkspaceTests: XCTestCase { } func testCanonicalURLChangesWithTransitiveDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -12698,8 +12451,6 @@ final class WorkspaceTests: XCTestCase { } func testCycleRoot() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -12816,8 +12567,6 @@ final class WorkspaceTests: XCTestCase { } func testResolutionBranchAndVersion() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13030,8 +12779,6 @@ final class WorkspaceTests: XCTestCase { } func testBasicResolutionFromSourceControl() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13138,8 +12885,6 @@ final class WorkspaceTests: XCTestCase { } func testBasicTransitiveResolutionFromSourceControl() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13284,8 +13029,6 @@ final class WorkspaceTests: XCTestCase { } func testBasicResolutionFromRegistry() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13392,8 +13135,6 @@ final class WorkspaceTests: XCTestCase { } func testBasicTransitiveResolutionFromRegistry() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13538,8 +13279,6 @@ final class WorkspaceTests: XCTestCase { } func testTransitiveResolutionFromRegistryWithByNameDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13620,8 +13359,6 @@ final class WorkspaceTests: XCTestCase { // no dups func testResolutionMixedRegistryAndSourceControl1() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13752,8 +13489,6 @@ final class WorkspaceTests: XCTestCase { } func testTransitiveResolutionFromRegistryWithDifferentPackageNameCasing() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13819,8 +13554,6 @@ final class WorkspaceTests: XCTestCase { // duplicate package at root level func testResolutionMixedRegistryAndSourceControl2() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -13920,8 +13653,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 scm // --> dep2 scm --> dep1 registry func testResolutionMixedRegistryAndSourceControl3() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14011,9 +13742,10 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["root"]) { graph, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( diagnostic: .contains(""" - dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->git/org/bar->org.foo (B) /tmp/ws/roots/root->git/org/foo. + dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->git/org/bar->org.foo (B) \(tmpDirCanonicalPackageLocation)/roots/root->git/org/foo. """), severity: .warning ) @@ -14085,8 +13817,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 scm // --> dep2 registry --> dep1 registry func testResolutionMixedRegistryAndSourceControl4() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14176,9 +13906,10 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["root"]) { graph, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( diagnostic: .contains(""" - dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->org.bar->org.foo (B) /tmp/ws/roots/root->git/org/foo. + dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->org.bar->org.foo (B) \(tmpDirCanonicalPackageLocation)/roots/root->git/org/foo. """), severity: .warning ) @@ -14225,8 +13956,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 scm // --> dep2 scm --> dep1 registry incompatible version func testResolutionMixedRegistryAndSourceControl5() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14352,8 +14081,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 registry // --> dep2 registry --> dep1 scm func testResolutionMixedRegistryAndSourceControl6() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14442,10 +14169,11 @@ final class WorkspaceTests: XCTestCase { workspace.sourceControlToRegistryDependencyTransformation = .identity try await workspace.checkPackageGraph(roots: ["root"]) { graph, diagnostics in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) testDiagnostics(diagnostics) { result in result.check( diagnostic: .contains(""" - dependency 'git/org/foo' and dependency 'org.foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->org.bar->git/org/foo (B) /tmp/ws/roots/root->org.foo. + dependency 'git/org/foo' and dependency 'org.foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->org.bar->git/org/foo (B) \(tmpDirCanonicalPackageLocation)/roots/root->org.foo. """), severity: .warning ) @@ -14500,8 +14228,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 registry // --> dep2 registry --> dep1 scm incompatible version func testResolutionMixedRegistryAndSourceControl7() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14627,8 +14353,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 registry --> dep3 scm // --> dep2 registry --> dep3 registry func testResolutionMixedRegistryAndSourceControl8() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14734,9 +14458,10 @@ final class WorkspaceTests: XCTestCase { try await workspace.checkPackageGraph(roots: ["root"]) { graph, diagnostics in testDiagnostics(diagnostics) { result in + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) result.check( diagnostic: .contains(""" - dependency 'git/org/baz' and dependency 'org.baz' both point to the same package identity 'org.baz'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->org.foo->git/org/baz (B) /tmp/ws/roots/root->org.bar->org.baz. + dependency 'git/org/baz' and dependency 'org.baz' both point to the same package identity 'org.baz'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->org.foo->git/org/baz (B) \(tmpDirCanonicalPackageLocation)/roots/root->org.bar->org.baz. """), severity: .warning ) @@ -14799,8 +14524,6 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 registry --> dep3 scm // --> dep2 registry --> dep3 registry incompatible version func testResolutionMixedRegistryAndSourceControl9() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -14942,9 +14665,8 @@ final class WorkspaceTests: XCTestCase { // mixed graph root --> dep1 scm branch // --> dep2 registry --> dep1 registry func testResolutionMixedRegistryAndSourceControl10() async throws { - try skipOnWindowsAsTestCurrentlyFails() - - let sandbox = AbsolutePath("/tmp/ws/") + let sandbox: AbsolutePath = AbsolutePath("/tmp/ws/") + let tmpDirCanonicalPackageLocation = CanonicalPackageLocation(sandbox.pathString) let fs = InMemoryFileSystem() let workspace = try await MockWorkspace( @@ -15035,7 +14757,7 @@ final class WorkspaceTests: XCTestCase { testDiagnostics(diagnostics) { result in result.check( diagnostic: .contains(""" - dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->org.bar->org.foo (B) /tmp/ws/roots/root->git/org/foo. + dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->org.bar->org.foo (B) \(tmpDirCanonicalPackageLocation)/roots/root->git/org/foo. """), severity: .warning ) @@ -15065,7 +14787,7 @@ final class WorkspaceTests: XCTestCase { testDiagnostics(diagnostics) { result in result.check( diagnostic: .contains(""" - dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) /tmp/ws/roots/root->org.bar->org.foo (B) /tmp/ws/roots/root->git/org/foo. + dependency 'org.foo' and dependency 'git/org/foo' both point to the same package identity 'org.foo'. The dependencies are introduced through the following chains: (A) \(tmpDirCanonicalPackageLocation)/roots/root->org.bar->org.foo (B) \(tmpDirCanonicalPackageLocation)/roots/root->git/org/foo. """), severity: .warning ) @@ -15091,8 +14813,6 @@ final class WorkspaceTests: XCTestCase { } func testCustomPackageContainerProvider() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15122,7 +14842,7 @@ final class WorkspaceTests: XCTestCase { customRetrievalPath: .root ) - let fooPath = AbsolutePath("/tmp/ws/Foo") + let fooPath = sandbox.appending("Foo") let fooPackageReference = PackageReference(identity: PackageIdentity(path: fooPath), kind: .root(fooPath)) let fooContainer = MockPackageContainer(package: fooPackageReference) @@ -15182,8 +14902,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryMissingConfigurationErrors() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15233,12 +14951,10 @@ final class WorkspaceTests: XCTestCase { testDiagnostics(diagnostics) { result in result.check(diagnostic: .equal("no registry configured for 'org' scope"), severity: .error) } - } + } } func testRegistryReleasesServerErrors() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15324,8 +15040,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryReleaseChecksumServerErrors() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15413,8 +15127,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryManifestServerErrors() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15502,8 +15214,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryDownloadServerErrors() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15591,8 +15301,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryArchiveErrors() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15655,8 +15363,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryMetadata() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15739,8 +15445,6 @@ final class WorkspaceTests: XCTestCase { } func testRegistryDefaultRegistryConfiguration() async throws { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -15807,9 +15511,9 @@ final class WorkspaceTests: XCTestCase { metadata: [String: RegistryReleaseMetadata], mirrors: DependencyMirrors? = nil ) async throws -> MockWorkspace { - try skipOnWindowsAsTestCurrentlyFails() - let sandbox = AbsolutePath("/tmp/ws/") + // let sandbox = AbsolutePath.root.appending("swiftpm-tests-can-be-deleted/tmp/ws") + let sandbox = AbsolutePath.root.appending(components: ["swiftpm-tests-can-be-deleted", "tmp", "ws"]) let fs = InMemoryFileSystem() return try await MockWorkspace( @@ -16105,8 +15809,6 @@ final class WorkspaceTests: XCTestCase { } func testTraitConfigurationExists_NoDefaultTraits() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -16195,8 +15897,6 @@ final class WorkspaceTests: XCTestCase { } func testTraitConfigurationExists_WithDefaultTraits() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -16285,8 +15985,6 @@ final class WorkspaceTests: XCTestCase { } func testTraitConfiguration_WithPrunedDependencies() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -16365,8 +16063,6 @@ final class WorkspaceTests: XCTestCase { } func testNoTraitConfiguration_WithDefaultTraits() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() From 02436eab0980491548f34d38a70e09ceb9b1068d Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 30 Apr 2025 09:04:28 -0400 Subject: [PATCH 55/99] Refactor slow to type check expression (#8565) ### Motivation: An expression in `SwiftRunCommand` was taking almost 2 seconds to type check on my M4 MacBook. ### Modifications: Break it out in to multiple, faster to type check expressions. ### Result: This brings each individual expression down to under 80ms to check. This was diagnosed with: ``` swift build \ -Xswiftc -Xfrontend -Xswiftc -warn-long-function-bodies=80 \ -Xswiftc -Xfrontend -Xswiftc -warn-long-expression-type-checking=80 ``` --- Sources/Commands/SwiftRunCommand.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index 21c455edd55..ff8db0d3a94 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -274,7 +274,8 @@ public struct SwiftRunCommand: AsyncSwiftCommand { // If the executable is implicit, search through root products. let rootExecutables = graph.rootPackages .flatMap { $0.products } - .filter { $0.type == .executable || $0.type == .snippet } + // The type checker slows down significantly when ProductTypes arent explicitly typed. + .filter { $0.type == ProductType.executable || $0.type == ProductType.snippet } .map { $0.name } // Error out if the package contains no executables. From 244f99cc6171e29a71ed4528540bf88c6d80fa1a Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Wed, 30 Apr 2025 09:23:18 -0700 Subject: [PATCH 56/99] Use correct FileSystem in the PIF builder for Swift Build (#8574) ### Motivation: Ensure the PIF builder follows the same FileSystem as the remaining code in the SwiftBuildSupport target. ### Modifications: Keep around a file system reference in PackagePIFBuilder and use that across the PIF builder. --- Sources/SwiftBuildSupport/PIFBuilder.swift | 5 +++-- .../PackagePIFBuilder+Helpers.swift | 13 +++++++------ .../PackagePIFBuilder+Plugins.swift | 9 +++++---- .../SwiftBuildSupport/PackagePIFBuilder.swift | 17 ++++++++++++----- .../PackagePIFProjectBuilder+Modules.swift | 3 ++- .../PackagePIFProjectBuilder.swift | 2 +- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 6ed5ae1ab32..667e943b0ba 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -110,7 +110,7 @@ public final class PIFBuilder { encoder.userInfo[.encodeForSwiftBuild] = true } - let topLevelObject = try self.construct(buildParameters: buildParameters) + let topLevelObject = try self.constructPIF(buildParameters: buildParameters) // Sign the PIF objects before encoding it for Swift Build. try PIF.sign(workspace: topLevelObject.workspace) @@ -139,7 +139,7 @@ public final class PIFBuilder { private var cachedPIF: PIF.TopLevelObject? /// Constructs a `PIF.TopLevelObject` representing the package graph. - private func construct(buildParameters: BuildParameters) throws -> PIF.TopLevelObject { + private func constructPIF(buildParameters: BuildParameters) throws -> PIF.TopLevelObject { try memoize(to: &self.cachedPIF) { guard let rootPackage = self.graph.rootPackages.only else { if self.graph.rootPackages.isEmpty { @@ -164,6 +164,7 @@ public final class PIFBuilder { buildToolPluginResultsByTargetName: [:], createDylibForDynamicProducts: self.parameters.shouldCreateDylibForDynamicProducts, packageDisplayVersion: package.manifest.displayName, + fileSystem: self.fileSystem, observabilityScope: self.observabilityScope ) diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index e1689c1b086..b0273deb5a3 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -12,16 +12,16 @@ import Foundation +import protocol TSCBasic.FileSystem import struct TSCUtility.Version import struct Basics.AbsolutePath import struct Basics.Diagnostic -import let Basics.localFileSystem import struct Basics.ObservabilityMetadata -import class Basics.ObservabilityScope -import class Basics.ObservabilitySystem import struct Basics.RelativePath import struct Basics.SourceControlURL +import class Basics.ObservabilityScope +import class Basics.ObservabilitySystem import class Basics.ThreadSafeArrayStore import enum PackageModel.BuildConfiguration @@ -440,13 +440,13 @@ extension PackageGraph.ResolvedModule { } /// Relative path of the module-map file, if any (*only* applies to C-language modules). - var moduleMapFileRelativePath: RelativePath? { + func moduleMapFileRelativePath(fileSystem: FileSystem) -> RelativePath? { guard let clangModule = self.underlying as? ClangModule else { return nil } let moduleMapFileAbsolutePath = clangModule.moduleMapPath // Check whether there is actually a modulemap at the specified path. // FIXME: Feels wrong to do file system access at this level —— instead, libSwiftPM's TargetBuilder should do that? - guard localFileSystem.isFile(moduleMapFileAbsolutePath) else { return nil } + guard fileSystem.isFile(moduleMapFileAbsolutePath) else { return nil } let moduleMapFileRelativePath = moduleMapFileAbsolutePath.relative(to: clangModule.sources.root) return try! RelativePath(validating: moduleMapFileRelativePath.pathString) @@ -612,6 +612,7 @@ extension SystemLibraryModule { /// Returns pkgConfig result for a system library target. func pkgConfig( package: PackageGraph.ResolvedPackage, + fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> (cFlags: [String], libs: [String]) { let diagnostics = ThreadSafeArrayStore() @@ -651,7 +652,7 @@ extension SystemLibraryModule { for: self, pkgConfigDirectories: [], brewPrefix: brewPrefix, - fileSystem: localFileSystem, + fileSystem: fileSystem, observabilityScope: pkgConfigParsingScope ) guard let pkgConfigResult else { return emptyPkgConfig } diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift index 3934f30984e..2f89010bfc9 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift @@ -12,9 +12,10 @@ import Foundation -import struct Basics.AbsolutePath -import let Basics.localFileSystem +import protocol TSCBasic.FileSystem + import enum Basics.Sandbox +import struct Basics.AbsolutePath import struct Basics.SourceControlURL #if canImport(SwiftBuild) @@ -123,10 +124,10 @@ extension PackagePIFBuilder { } /// Applies the sandbox profile to the given command line, and return the modified command line. - public func apply(to command: [String]) throws -> [String] { + public func apply(to command: [String], fileSystem: FileSystem) throws -> [String] { try Sandbox.apply( command: command, - fileSystem: localFileSystem, + fileSystem: fileSystem, strictness: self.strictness, writableDirectories: self.writableDirectories, readOnlyDirectories: self.readOnlyDirectories diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index ba854b1f461..001ff5c5437 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -12,20 +12,22 @@ import Foundation +import protocol TSCBasic.FileSystem + import struct Basics.AbsolutePath import struct Basics.SourceControlURL +import struct Basics.Diagnostic +import struct Basics.ObservabilityMetadata +import class Basics.ObservabilityScope import class PackageModel.Manifest import class PackageModel.Package +import class PackageModel.Product import struct PackageModel.Platform import struct PackageModel.PlatformVersion -import class PackageModel.Product -import enum PackageModel.ProductType import struct PackageModel.Resource +import enum PackageModel.ProductType -import struct Basics.Diagnostic -import struct Basics.ObservabilityMetadata -import class Basics.ObservabilityScope import struct PackageGraph.ModulesGraph import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage @@ -169,6 +171,9 @@ public final class PackagePIFBuilder { /// Package display version, if any (i.e., it can be a version, branch or a git ref). let packageDisplayVersion: String? + /// The file system to read from. + let fileSystem: FileSystem + /// Whether to suppress warnings from compilers, linkers, and other build tools for package dependencies. private var suppressWarningsForPackageDependencies: Bool { UserDefaults.standard.bool(forKey: "SuppressWarningsForPackageDependencies", defaultValue: true) @@ -191,6 +196,7 @@ public final class PackagePIFBuilder { buildToolPluginResultsByTargetName: [String: BuildToolPluginInvocationResult], createDylibForDynamicProducts: Bool = false, packageDisplayVersion: String?, + fileSystem: FileSystem, observabilityScope: ObservabilityScope ) { self.package = resolvedPackage @@ -200,6 +206,7 @@ public final class PackagePIFBuilder { self.buildToolPluginResultsByTargetName = buildToolPluginResultsByTargetName self.createDylibForDynamicProducts = createDylibForDynamicProducts self.packageDisplayVersion = packageDisplayVersion + self.fileSystem = fileSystem self.observabilityScope = observabilityScope } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index 44d05cbcde9..e766c3f4725 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -377,7 +377,7 @@ extension PackagePIFProjectBuilder { // We only need to impart this to C clients. impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] - } else if sourceModule.moduleMapFileRelativePath == nil { + } else if sourceModule.moduleMapFileRelativePath(fileSystem: self.pifBuilder.fileSystem) == nil { // Otherwise, this is a C library module and we generate a modulemap if one is already not provided. if case .umbrellaHeader(let path) = sourceModule.moduleMapType { log(.debug, "\(package.name).\(sourceModule.name) generated umbrella header") @@ -824,6 +824,7 @@ extension PackagePIFProjectBuilder { let settings: ProjectModel.BuildSettings = self.package.underlying.packageBaseBuildSettings let pkgConfig = try systemLibrary.pkgConfig( package: self.package, + fileSystem: self.pifBuilder.fileSystem, observabilityScope: pifBuilder.observabilityScope ) diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index 1b8648f1622..c37a6e6f3b9 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -460,7 +460,7 @@ struct PackagePIFProjectBuilder { ) { var commandLine = [command.executable] + command.arguments if let sandbox = command.sandboxProfile, !pifBuilder.delegate.isPluginExecutionSandboxingDisabled { - commandLine = try! sandbox.apply(to: commandLine) + commandLine = try! sandbox.apply(to: commandLine, fileSystem: self.pifBuilder.fileSystem) } self.project[keyPath: targetKeyPath].customTasks.append( From 174cdc29d04ae660c885afc5c6d5ccab8e7e01ba Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 30 Apr 2025 13:32:41 -0400 Subject: [PATCH 57/99] Make add-dependency idempotent (#8534) ### Motivation: If the same dependency is added twice with different version specifiers the manifest is no longer valid. If the same dependency is added twice with the same version specifier it remains valid, but less maintainable. ### Modifications: If a manifest already contains the exact dependency and version that is being added via a `add-dependency` call, don't add it twice. Instead, do nothing. If a manifest contains a dependency with the supplied URL but a different version qualifier then throw an error explaining that the supplied dependency has already been added. ### Result: A manifest that continues to parse correctly. Issue: #8519 --- .../AddPackageDependency.swift | 64 +++ .../ManifestEditError.swift | 3 + Tests/CommandsTests/PackageCommandTests.swift | 467 +++++++++++------- 3 files changed, 351 insertions(+), 183 deletions(-) diff --git a/Sources/PackageModelSyntax/AddPackageDependency.swift b/Sources/PackageModelSyntax/AddPackageDependency.swift index 5061195b5db..af017889d33 100644 --- a/Sources/PackageModelSyntax/AddPackageDependency.swift +++ b/Sources/PackageModelSyntax/AddPackageDependency.swift @@ -44,6 +44,13 @@ public enum AddPackageDependency { throw ManifestEditError.cannotFindPackage } + guard try !dependencyAlreadyAdded( + dependency, + in: packageCall + ) else { + return PackageEditResult(manifestEdits: []) + } + let newPackageCall = try addPackageDependencyLocal( dependency, to: packageCall ) @@ -55,6 +62,50 @@ public enum AddPackageDependency { ) } + /// Return `true` if the dependency already exists in the manifest, otherwise return `false`. + /// Throws an error if a dependency already exists with the same id or url, but different arguments. + private static func dependencyAlreadyAdded( + _ dependency: MappablePackageDependency.Kind, + in packageCall: FunctionCallExprSyntax + ) throws -> Bool { + let dependencySyntax = dependency.asSyntax() + guard let dependenctFnSyntax = dependencySyntax.as(FunctionCallExprSyntax.self) else { + throw ManifestEditError.cannotFindPackage + } + + guard let id = dependenctFnSyntax.arguments.first(where: { + $0.label?.text == "url" || $0.label?.text == "id" || $0.label?.text == "path" + }) else { + throw InternalError("Missing id or url argument in dependency syntax") + } + + if let existingDependencies = packageCall.findArgument(labeled: "dependencies") { + // If we have an existing dependencies array, we need to check if + if let expr = existingDependencies.expression.as(ArrayExprSyntax.self) { + // Iterate through existing dependencies and look for an argument that matches + // either the `id` or `url` argument of the new dependency. + let existingArgument = expr.elements.first { elem in + if let funcExpr = elem.expression.as(FunctionCallExprSyntax.self) { + return funcExpr.arguments.contains { + $0.trimmedDescription == id.trimmedDescription + } + } + return true + } + + if let existingArgument { + let normalizedExistingArgument = existingArgument.detached.with(\.trailingComma, nil) + // This exact dependency already exists, return false to indicate we should do nothing. + if normalizedExistingArgument.trimmedDescription == dependencySyntax.trimmedDescription { + return true + } + throw ManifestEditError.existingDependency(dependencyName: dependency.identifier) + } + } + } + return false + } + /// Implementation of adding a package dependency to an existing call. static func addPackageDependencyLocal( _ dependency: MappablePackageDependency.Kind, @@ -67,3 +118,16 @@ public enum AddPackageDependency { ) } } + +fileprivate extension MappablePackageDependency.Kind { + var identifier: String { + switch self { + case .sourceControl(let name, let path, _): + return name ?? path + case .fileSystem(let name, let location): + return name ?? location + case .registry(let id, _): + return id + } + } +} \ No newline at end of file diff --git a/Sources/PackageModelSyntax/ManifestEditError.swift b/Sources/PackageModelSyntax/ManifestEditError.swift index 7f02e0b2683..6c4cdde0806 100644 --- a/Sources/PackageModelSyntax/ManifestEditError.swift +++ b/Sources/PackageModelSyntax/ManifestEditError.swift @@ -23,6 +23,7 @@ package enum ManifestEditError: Error { case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax) case oldManifest(ToolsVersion, expected: ToolsVersion) case cannotAddSettingsToPluginTarget + case existingDependency(dependencyName: String) } extension ToolsVersion { @@ -46,6 +47,8 @@ extension ManifestEditError: CustomStringConvertible { "package manifest version \(version) is too old: please update to manifest version \(expectedVersion) or newer" case .cannotAddSettingsToPluginTarget: "plugin targets do not support settings" + case .existingDependency(let name): + "unable to add dependency '\(name)' because it already exists in the list of dependencies" } } } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index cb2a1cef77a..3b9504eb777 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -40,9 +40,14 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { private func execute( _ args: [String] = [], packagePath: AbsolutePath? = nil, + manifest: String? = nil, env: Environment? = nil ) async throws -> (stdout: String, stderr: String) { var environment = env ?? [:] + if let manifest, let packagePath { + try localFileSystem.writeFileContents(packagePath.appending("Package.swift"), string: manifest) + } + // don't ignore local packages when caching environment["SWIFTPM_TESTS_PACKAGECACHE"] = "1" return try await executeSwiftPackage( @@ -875,155 +880,282 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { } } - func testPackageAddURLDependency() async throws { + // Helper function to arbitrarily assert on manifest content + func assertManifest(_ packagePath: AbsolutePath, _ callback: (String) throws -> Void) throws { + let manifestPath = packagePath.appending("Package.swift") + XCTAssertFileExists(manifestPath) + let contents: String = try localFileSystem.readFileContents(manifestPath) + try callback(contents) + } + + // Helper function to assert content exists in the manifest + func assertManifestContains(_ packagePath: AbsolutePath, _ expected: String) throws { + try assertManifest(packagePath) { manifestContents in + XCTAssertMatch(manifestContents, .contains(expected)) + } + } + + // Helper function to test adding a URL dependency and asserting the result + func executeAddURLDependencyAndAssert( + packagePath: AbsolutePath, + initialManifest: String? = nil, + url: String, + requirementArgs: [String], + expectedManifestString: String, + ) async throws { + _ = try await execute( + ["add-dependency", url] + requirementArgs, + packagePath: packagePath, + manifest: initialManifest + ) + try assertManifestContains(packagePath, expectedManifestString) + } + + func testPackageAddDifferentDependencyWithSameURLTwiceFails() async throws { try await testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("PackageB") try fs.createDirectory(path) - try fs.writeFileContents(path.appending("Package.swift"), string: - """ + let url = "https://github.com/swiftlang/swift-syntax.git" + let manifest = """ // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "client", + dependencies: [ + .package(url: "\(url)", exact: "601.0.1") + ], targets: [ .target(name: "client", dependencies: [ "library" ]) ] ) - """ + """ + + try localFileSystem.writeFileContents(path.appending("Package.swift"), string: manifest) + + await XCTAssertThrowsCommandExecutionError( + try await execute(["add-dependency", url, "--revision", "58e9de4e7b79e67c72a46e164158e3542e570ab6"], packagePath: path) + ) { error in + XCTAssertMatch(error.stderr, .contains("error: unable to add dependency 'https://github.com/swiftlang/swift-syntax.git' because it already exists in the list of dependencies")) + } + } + } + + func testPackageAddSameDependencyURLTwiceHasNoEffect() async throws { + try await testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + + let url = "https://github.com/swiftlang/swift-syntax.git" + let manifest = """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client", + dependencies: [ + .package(url: "\(url)", exact: "601.0.1"), + ], + targets: [ .target(name: "client", dependencies: [ "library" ]) ] + ) + """ + let expected = #".package(url: "https://github.com/swiftlang/swift-syntax.git", exact: "601.0.1"),"# + + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: url, + requirementArgs: ["--exact", "601.0.1"], + expectedManifestString: expected ) - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--exact", - "1.0.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--branch", - "main", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--revision", - "58e9de4e7b79e67c72a46e164158e3542e570ab6", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--from", - "1.0.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--from", - "2.0.0", - "--to", - "2.2.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--up-to-next-minor-from", - "1.0.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "https://github.com/swiftlang/swift-syntax.git", - "--up-to-next-minor-from", - "3.0.0", - "--to", - "3.3.0", - ], - packagePath: path + try assertManifest(path) { + let components = $0.components(separatedBy: expected) + XCTAssertEqual(components.count, 2, "Expected the dependency to be added exactly once.") + } + } + } + + func testPackageAddSameDependencyPathTwiceHasNoEffect() async throws { + try await testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + + let depPath = "../foo" + let manifest = """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client", + dependencies: [ + .package(path: "\(depPath)") + ], + targets: [ .target(name: "client", dependencies: [ "library" ]) ] + ) + """ + + let expected = #".package(path: "../foo")"# + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: depPath, + requirementArgs: ["--type", "path"], + expectedManifestString: expected ) + try assertManifest(path) { + let components = $0.components(separatedBy: expected) + XCTAssertEqual(components.count, 2, "Expected the dependency to be added exactly once.") + } + } + } - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - let contents: String = try fs.readFileContents(manifest) + func testPackageAddSameDependencyRegistryTwiceHasNoEffect() async throws { + try await testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + + let registryId = "foo" + let manifest = """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client", + dependencies: [ + .package(id: "\(registryId)") + ], + targets: [ .target(name: "client", dependencies: [ "library" ]) ] + ) + """ + + let expected = #".package(id: "foo", exact: "1.0.0")"# + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: registryId, + requirementArgs: ["--type", "registry", "--exact", "1.0.0"], + expectedManifestString: expected + ) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", exact: "1.0.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"),"#)) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", revision: "58e9de4e7b79e67c72a46e164158e3542e570ab6"),"#)) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", from: "1.0.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", "2.0.0" ..< "2.2.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", "1.0.0" ..< "1.1.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/swiftlang/swift-syntax.git", "3.0.0" ..< "3.3.0"),"#)) + try assertManifest(path) { + let components = $0.components(separatedBy: expected) + XCTAssertEqual(components.count, 2, "Expected the dependency to be added exactly once.") + } } } - func testPackageAddPathDependency() async throws { + func testPackageAddURLDependency() async throws { try await testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("PackageB") try fs.createDirectory(path) - try fs.writeFileContents(path.appending("Package.swift"), string: - """ + let manifest = """ // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "client", targets: [ .target(name: "client", dependencies: [ "library" ]) ] ) - """ + """ + + // Test adding with --exact using the new helper + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--exact", "1.0.0"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", exact: "1.0.0"),"#, ) - _ = try await execute( - [ - "add-dependency", - "/absolute", - "--type", - "path" - ], - packagePath: path + // Test adding with --branch + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--branch", "main"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", branch: "main"),"# ) - _ = try await execute( - [ - "add-dependency", - "../relative", - "--type", - "path" - ], - packagePath: path + // Test adding with --revision + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--revision", "58e9de4e7b79e67c72a46e164158e3542e570ab6"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", revision: "58e9de4e7b79e67c72a46e164158e3542e570ab6"),"# ) - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - let contents: String = try fs.readFileContents(manifest) + // Test adding with --from + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--from", "1.0.0"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", from: "1.0.0"),"# + ) + + // Test adding with --from and --to + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--from", "2.0.0", "--to", "2.2.0"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", "2.0.0" ..< "2.2.0"),"# + ) + + // Test adding with --up-to-next-minor-from + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--up-to-next-minor-from", "1.0.0"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", "1.0.0" ..< "1.1.0"),"# + ) - XCTAssertMatch(contents, .contains(#".package(path: "/absolute"),"#)) - XCTAssertMatch(contents, .contains(#".package(path: "../relative"),"#)) + // Test adding with --up-to-next-minor-from and --to + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "https://github.com/swiftlang/swift-syntax.git", + requirementArgs: ["--up-to-next-minor-from", "3.0.0", "--to", "3.3.0"], + expectedManifestString: #".package(url: "https://github.com/swiftlang/swift-syntax.git", "3.0.0" ..< "3.3.0"),"# + ) + } + } + + func testPackageAddPathDependency() async throws { + try await testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + let manifest = """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client", + targets: [ .target(name: "client", dependencies: [ "library" ]) ] + ) + """ + + // Add absolute path dependency + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "/absolute", + requirementArgs: ["--type", "path"], + expectedManifestString: #".package(path: "/absolute"),"# + ) + + // Add relative path dependency (operates on the modified manifest) + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "../relative", + requirementArgs: ["--type", "path"], + expectedManifestString: #".package(path: "../relative"),"# + ) } } @@ -1033,8 +1165,7 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { let path = tmpPath.appending("PackageB") try fs.createDirectory(path) - try fs.writeFileContents(path.appending("Package.swift"), string: - """ + let manifest = """ // swift-tools-version: 5.9 import PackageDescription let package = Package( @@ -1042,81 +1173,51 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { targets: [ .target(name: "client", dependencies: [ "library" ]) ] ) """ + + // Test adding with --exact + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "scope.name", + requirementArgs: ["--type", "registry", "--exact", "1.0.0"], + expectedManifestString: #".package(id: "scope.name", exact: "1.0.0"),"# ) - _ = try await execute( - [ - "add-dependency", - "scope.name", - "--type", - "registry", - "--exact", - "1.0.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "scope.name", - "--type", - "registry", - "--from", - "1.0.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "scope.name", - "--type", - "registry", - "--from", - "2.0.0", - "--to", - "2.2.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "scope.name", - "--type", - "registry", - "--up-to-next-minor-from", - "1.0.0", - ], - packagePath: path - ) - - _ = try await execute( - [ - "add-dependency", - "scope.name", - "--type", - "registry", - "--up-to-next-minor-from", - "3.0.0", - "--to", - "3.3.0", - ], - packagePath: path + // Test adding with --from + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "scope.name", + requirementArgs: ["--type", "registry", "--from", "1.0.0"], + expectedManifestString: #".package(id: "scope.name", from: "1.0.0"),"# ) - let manifest = path.appending("Package.swift") - XCTAssertFileExists(manifest) - let contents: String = try fs.readFileContents(manifest) + // Test adding with --from and --to + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "scope.name", + requirementArgs: ["--type", "registry", "--from", "2.0.0", "--to", "2.2.0"], + expectedManifestString: #".package(id: "scope.name", "2.0.0" ..< "2.2.0"),"# + ) - XCTAssertMatch(contents, .contains(#".package(id: "scope.name", exact: "1.0.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(id: "scope.name", from: "1.0.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(id: "scope.name", "2.0.0" ..< "2.2.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(id: "scope.name", "1.0.0" ..< "1.1.0"),"#)) - XCTAssertMatch(contents, .contains(#".package(id: "scope.name", "3.0.0" ..< "3.3.0"),"#)) + // Test adding with --up-to-next-minor-from + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "scope.name", + requirementArgs: ["--type", "registry", "--up-to-next-minor-from", "1.0.0"], + expectedManifestString: #".package(id: "scope.name", "1.0.0" ..< "1.1.0"),"# + ) + + // Test adding with --up-to-next-minor-from and --to + try await executeAddURLDependencyAndAssert( + packagePath: path, + initialManifest: manifest, + url: "scope.name", + requirementArgs: ["--type", "registry", "--up-to-next-minor-from", "3.0.0", "--to", "3.3.0"], + expectedManifestString: #".package(id: "scope.name", "3.0.0" ..< "3.3.0"),"# + ) } } From d57bd3633bdee23ab83b99427f1c8072b08baa76 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Wed, 30 Apr 2025 14:23:47 -0400 Subject: [PATCH 58/99] pipeline: support additional args in build-using-self (#8559) Update the build-using-self to support accepting a triple and a build system, which some self hosted CI environments might want to set. --- Utilities/build-using-self | 121 +++++++++++++++++++++++++++++++++---- Utilities/helpers.py | 8 +-- 2 files changed, 112 insertions(+), 17 deletions(-) diff --git a/Utilities/build-using-self b/Utilities/build-using-self index 68bfa6433a8..7bb2b9048ee 100755 --- a/Utilities/build-using-self +++ b/Utilities/build-using-self @@ -12,6 +12,8 @@ # ===----------------------------------------------------------------------===## import argparse +import dataclasses +import itertools import logging import os import pathlib @@ -64,10 +66,22 @@ def get_arguments() -> argparse.Namespace: "--configuration", type=Configuration, dest="config", - default="debug", - choices=[e.value for e in Configuration], + default=Configuration.DEBUG, + choices=[e for e in Configuration], help="The configuraiton to use.", ) + parser.add_argument( + "-t", + "--triple", + type=str, + dest="triple", + ) + parser.add_argument( + "-b", + "--build-system", + type=str, + dest="build_system", + ) parser.add_argument( "--enable-swift-testing", action="store_true", @@ -99,7 +113,10 @@ def is_on_darwin() -> bool: return platform.uname().system == "Darwin" -def set_environment(*, swiftpm_bin_dir: pathlib.Path,) -> None: +def set_environment( + *, + swiftpm_bin_dir: pathlib.Path, +) -> None: os.environ["SWIFTCI_IS_SELF_HOSTED"] = "1" # Set the SWIFTPM_CUSTOM_BIN_DIR path @@ -131,39 +148,117 @@ def run_bootstrap(swiftpm_bin_dir: pathlib.Path) -> None: ) +GlobalArgsValueType = str + + +@dataclasses.dataclass +class GlobalArgs: + global_argument: str + value: t.Optional[GlobalArgsValueType] + + +def filterNone(items: t.Iterable) -> t.Iterable: + return list(filter(lambda x: x is not None, items)) + + def main() -> None: args = get_arguments() - ignore = "-Xlinker /ignore:4217" if os.name == "nt" else "" logging.getLogger().setLevel(logging.DEBUG if args.is_verbose else logging.INFO) logging.debug("Args: %r", args) - + ignore_args = ["-Xlinker", "/ignore:4217"] if os.name == "nt" else [] + globalArgsData = [ + GlobalArgs(global_argument="--triple", value=args.triple), + GlobalArgs(global_argument="--build-system", value=args.build_system), + ] + global_args: t.Iterator[GlobalArgsValueType] = list( + itertools.chain.from_iterable( + [[arg.global_argument, arg.value] for arg in globalArgsData if arg.value] + ) + ) + logging.debug("Global Args: %r", global_args) with change_directory(REPO_ROOT_PATH): swiftpm_bin_dir = get_swiftpm_bin_dir(config=args.config) set_environment(swiftpm_bin_dir=swiftpm_bin_dir) call( - shlex.split("swift --version"), + filterNone( + [ + "swift", + "--version", + ] + ) ) call( - shlex.split("swift package update"), + filterNone( + [ + "swift", + "package", + "update", + ] + ) ) call( - shlex.split(f"swift build --configuration {args.config} {ignore}"), + filterNone( + [ + "swift", + "build", + *global_args, + "--configuration", + args.config, + *ignore_args, + ] + ) ) - swift_testing_arg= "--enable-swift-testing" if args.enable_swift_testing else "" - xctest_arg= "--enable-xctest" if args.enable_swift_testing else "" + swift_testing_arg = ( + "--enable-swift-testing" if args.enable_swift_testing else None + ) + xctest_arg = "--enable-xctest" if args.enable_swift_testing else None call( - shlex.split(f"swift run swift-test --configuration {args.config} --parallel {swift_testing_arg} {xctest_arg} --scratch-path .test {ignore}"), + filterNone( + [ + "swift", + "run", + "swift-test", + *global_args, + "--configuration", + args.config, + "--parallel", + swift_testing_arg, + xctest_arg, + "--scratch-path", + ".test", + *ignore_args, + ] + ) ) integration_test_dir = (REPO_ROOT_PATH / "IntegrationTests").as_posix() call( - shlex.split(f"swift package --package-path {integration_test_dir} update"), + filterNone( + [ + "swift", + "package", + "--package-path", + integration_test_dir, + "update", + ] + ) ) call( - shlex.split(f"swift run swift-test --package-path {integration_test_dir} --parallel {ignore}"), + filterNone( + [ + "swift", + "run", + "swift-test", + *global_args, + "--package-path", + integration_test_dir, + "--parallel", + *ignore_args, + ] + ) ) if is_on_darwin(): diff --git a/Utilities/helpers.py b/Utilities/helpers.py index b5a4a9df9a9..8f469e8ecca 100644 --- a/Utilities/helpers.py +++ b/Utilities/helpers.py @@ -65,11 +65,11 @@ def mkdir_p(path): def call(cmd, cwd=None, verbose=False): """Calls a subprocess.""" cwd = cwd or pathlib.Path.cwd() - logging.info("executing command >>> %r with cwd %s", " ".join([str(c) for c in cmd]), cwd) try: + logging.info("executing command >>> %r with cwd %s", cmd, cwd) subprocess.check_call(cmd, cwd=cwd) except subprocess.CalledProcessError as cpe: - logging.debug("executing command >>> %r with cwd %s", " ".join([str(c) for c in cmd]), cwd) + logging.debug("executing command >>> %r with cwd %s", cmd, cwd) logging.error( "\n".join([ "Process failure with return code %d: %s", @@ -96,7 +96,7 @@ def call_output(cmd, cwd=None, stderr=False, verbose=False): """Calls a subprocess for its return data.""" stderr = subprocess.STDOUT if stderr else False cwd = cwd or pathlib.Path.cwd() - logging.info("executing command >>> %r with cwd %s", " ".join([str(c) for c in cmd]), cwd) + logging.info("executing command >>> %r with cwd %s", cmd, cwd) try: return subprocess.check_output( cmd, @@ -105,7 +105,7 @@ def call_output(cmd, cwd=None, stderr=False, verbose=False): universal_newlines=True, ).strip() except subprocess.CalledProcessError as cpe: - logging.debug("executing command >>> %r with cwd %s", " ".join([str(c) for c in cmd]), cwd) + logging.debug("executing command >>> %r with cwd %s", cmd, cwd) logging.error( "\n".join([ "Process failure with return code %d: %s", From 4a36f559d7a315c8ce9d8088ec1fa3dace6b445d Mon Sep 17 00:00:00 2001 From: Bri Peticca Date: Wed, 30 Apr 2025 14:25:02 -0400 Subject: [PATCH 59/99] [Traits] Change `TraitConfiguration` to be an enum instead of struct (#8370) Fixes #8355. Since the way a `TraitConfiguration` is set up can affect how traits are calculated and whether default traits are overridden or not, it's more ergonomic to change this to an `enum` that lists the various possible cases of configuration: - No trait configuration, which defaults to using default traits if applicable - A configuration with an empty set of traits that overrides the default traits - A configuration that enables all traits - A configuration that specifies an exact set of traits that are enabled This way we can avoid doing multiple checks on the properties of the `struct` and instead rely on the exact case that describes how to continue calculating enabled traits. The refactoring done as a part of this change also fixes #8356. --- Sources/Build/LLBuildDescription.swift | 2 + .../PackageCommands/EditCommands.swift | 4 +- .../Commands/PackageCommands/Resolve.swift | 2 +- Sources/CoreCommands/BuildSystemSupport.swift | 1 + Sources/CoreCommands/Options.swift | 2 +- Sources/CoreCommands/SwiftCommandState.swift | 21 +- Sources/PackageGraph/CMakeLists.txt | 1 - .../PackageGraph/ModulesGraph+Loading.swift | 2 +- Sources/PackageGraph/ModulesGraph.swift | 4 +- Sources/PackageGraph/PackageContainer.swift | 4 +- Sources/PackageGraph/PackageGraphRoot.swift | 55 +- .../Resolution/DependencyResolutionNode.swift | 15 +- .../PubGrub/PubGrubDependencyResolver.swift | 2 +- .../PubGrub/PubGrubPackageContainer.swift | 6 +- Sources/PackageGraph/TraitConfiguration.swift | 28 - Sources/PackageLoading/ManifestLoader.swift | 1 + Sources/PackageModel/CMakeLists.txt | 2 + .../Manifest/Manifest+Traits.swift | 509 ++++++++++++++++++ Sources/PackageModel/Manifest/Manifest.swift | 378 +------------ .../PackageDependencyDescription.swift | 2 +- .../Manifest/TraitConfiguration.swift | 59 ++ .../BuildSystem/BuildSystem.swift | 2 + .../SourceKitLSPAPI/BuildDescription.swift | 2 +- .../PluginTargetBuildDescription.swift | 2 +- .../FileSystemPackageContainer.swift | 7 +- .../RegistryPackageContainer.swift | 4 +- .../SourceControlPackageContainer.swift | 4 +- .../ResolverPrecomputationProvider.swift | 11 +- .../Workspace/Workspace+Configuration.swift | 6 +- .../Workspace/Workspace+Dependencies.swift | 8 +- Sources/Workspace/Workspace+Manifests.swift | 92 +--- Sources/Workspace/Workspace+Registry.swift | 1 + Sources/Workspace/Workspace.swift | 4 +- .../ManifestExtensions.swift | 14 +- .../_InternalTestSupport/MockDependency.swift | 46 +- .../_InternalTestSupport/MockPackage.swift | 6 +- .../MockPackageGraphs.swift | 10 +- .../_InternalTestSupport/MockWorkspace.swift | 9 +- Sources/_InternalTestSupport/misc.swift | 2 +- Sources/swift-bootstrap/main.swift | 2 +- Tests/BuildTests/PluginInvocationTests.swift | 3 +- Tests/FunctionalTests/TraitTests.swift | 31 +- .../PackageGraphPerfTests.swift | 1 + .../PackageGraphTests/ModulesGraphTests.swift | 2 + Tests/PackageGraphTests/PubGrubTests.swift | 7 +- Tests/PackageModelTests/ManifestTests.swift | 211 +++++++- .../SourceKitLSPAPITests.swift | 9 +- .../ManifestSourceGenerationTests.swift | 1 + .../RegistryPackageContainerTests.swift | 1 + Tests/WorkspaceTests/WorkspaceTests.swift | 128 +++++ .../XCBuildSupportTests/PIFBuilderTests.swift | 3 + 51 files changed, 1117 insertions(+), 612 deletions(-) delete mode 100644 Sources/PackageGraph/TraitConfiguration.swift create mode 100644 Sources/PackageModel/Manifest/Manifest+Traits.swift create mode 100644 Sources/PackageModel/Manifest/TraitConfiguration.swift diff --git a/Sources/Build/LLBuildDescription.swift b/Sources/Build/LLBuildDescription.swift index 8149a687129..6dfa5fca2fa 100644 --- a/Sources/Build/LLBuildDescription.swift +++ b/Sources/Build/LLBuildDescription.swift @@ -17,6 +17,8 @@ import SPMBuildCore import PackageGraph import PackageModel +import enum PackageModel.TraitConfiguration + import struct TSCBasic.ByteString /// Contains the description of the build that is needed during the execution. diff --git a/Sources/Commands/PackageCommands/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift index f0966e2378d..2071e9c5329 100644 --- a/Sources/Commands/PackageCommands/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -37,7 +37,7 @@ extension SwiftPackageCommand { var packageIdentity: String func run(_ swiftCommandState: SwiftCommandState) async throws { - try await swiftCommandState.resolve(nil) + try await swiftCommandState.resolve() let workspace = try swiftCommandState.getActiveWorkspace() // Put the dependency in edit mode. @@ -66,7 +66,7 @@ extension SwiftPackageCommand { var packageIdentity: String func run(_ swiftCommandState: SwiftCommandState) async throws { - try await swiftCommandState.resolve(nil) + try await swiftCommandState.resolve() let workspace = try swiftCommandState.getActiveWorkspace() try await workspace.unedit( diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index baf18eb9e7b..7ff3f61453c 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -16,7 +16,7 @@ import CoreCommands import TSCUtility import Workspace -import struct PackageGraph.TraitConfiguration +import enum PackageModel.TraitConfiguration extension SwiftPackageCommand { struct ResolveOptions: ParsableArguments { diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index 28fa15d1407..84ba4c01f18 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -22,6 +22,7 @@ import class Basics.ObservabilityScope import struct PackageGraph.ModulesGraph import struct PackageLoading.FileRuleDescription import protocol TSCBasic.OutputByteStream +import enum PackageModel.TraitConfiguration private struct NativeBuildSystemFactory: BuildSystemFactory { let swiftCommandState: SwiftCommandState diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index 0b45c58651d..29336f002f6 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -27,7 +27,7 @@ import struct PackageModel.PackageIdentity import enum PackageModel.Sanitizer @_spi(SwiftPMInternal) import struct PackageModel.SwiftSDK -import struct PackageGraph.TraitConfiguration +import enum PackageModel.TraitConfiguration import struct SPMBuildCore.BuildParameters import struct SPMBuildCore.BuildSystemProvider diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 3549d6bd99c..12e193d838f 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -214,14 +214,14 @@ public final class SwiftCommandState { } /// Get the current workspace root object. - public func getWorkspaceRoot(traitConfiguration: TraitConfiguration? = nil) throws -> PackageGraphRootInput { + public func getWorkspaceRoot(traitConfiguration: TraitConfiguration = .default) throws -> PackageGraphRootInput { let packages: [AbsolutePath] if let workspace = options.locations.multirootPackageDataFile { packages = try self.workspaceLoaderProvider(self.fileSystem, self.observabilityScope) .load(workspace: workspace) } else { - packages = [try getPackageRoot()] + packages = try [self.getPackageRoot()] } return PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration) @@ -457,10 +457,7 @@ public final class SwiftCommandState { } /// Returns the currently active workspace. - public func getActiveWorkspace( - emitDeprecatedConfigurationWarning: Bool = false, - traitConfiguration: TraitConfiguration? = nil - ) throws -> Workspace { + public func getActiveWorkspace(emitDeprecatedConfigurationWarning: Bool = false, traitConfiguration: TraitConfiguration = .default) throws -> Workspace { if let workspace = _workspace { return workspace } @@ -525,9 +522,7 @@ public final class SwiftCommandState { return workspace } - public func getRootPackageInformation(traitConfiguration: TraitConfiguration? = nil) async throws - -> (dependencies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) - { + public func getRootPackageInformation(traitConfiguration: TraitConfiguration = .default) async throws -> (dependencies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) { let workspace = try self.getActiveWorkspace(traitConfiguration: traitConfiguration) let root = try self.getWorkspaceRoot(traitConfiguration: traitConfiguration) let rootManifests = try await workspace.loadRootManifests( @@ -652,7 +647,7 @@ public final class SwiftCommandState { } /// Resolve the dependencies. - public func resolve(_ traitConfiguration: TraitConfiguration?) async throws { + public func resolve(_ traitConfiguration: TraitConfiguration = .default) async throws { let workspace = try getActiveWorkspace(traitConfiguration: traitConfiguration) let root = try getWorkspaceRoot(traitConfiguration: traitConfiguration) @@ -682,7 +677,7 @@ public final class SwiftCommandState { ) async throws -> ModulesGraph { try await self.loadPackageGraph( explicitProduct: explicitProduct, - traitConfiguration: nil, + traitConfiguration: .default, testEntryPointPath: testEntryPointPath ) } @@ -695,7 +690,7 @@ public final class SwiftCommandState { @discardableResult package func loadPackageGraph( explicitProduct: String? = nil, - traitConfiguration: TraitConfiguration? = nil, + traitConfiguration: TraitConfiguration = .default, testEntryPointPath: AbsolutePath? = nil ) async throws -> ModulesGraph { do { @@ -750,7 +745,7 @@ public final class SwiftCommandState { try self._manifestLoader.get() } - public func canUseCachedBuildManifest(_ traitConfiguration: TraitConfiguration? = nil) async throws -> Bool { + public func canUseCachedBuildManifest(_ traitConfiguration: TraitConfiguration = .default) async throws -> Bool { if !self.options.caching.cacheBuildManifest { return false } diff --git a/Sources/PackageGraph/CMakeLists.txt b/Sources/PackageGraph/CMakeLists.txt index c6f6b7fba40..46bd6580f5c 100644 --- a/Sources/PackageGraph/CMakeLists.txt +++ b/Sources/PackageGraph/CMakeLists.txt @@ -19,7 +19,6 @@ add_library(PackageGraph PackageModel+Extensions.swift PackageRequirement.swift ResolvedPackagesStore.swift - TraitConfiguration.swift Resolution/PubGrub/Assignment.swift Resolution/PubGrub/ContainerProvider.swift Resolution/PubGrub/DiagnosticReportBuilder.swift diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 03b8ebd1e23..50020c2f602 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -1035,7 +1035,7 @@ private func calculateEnabledTraits( } } - if let parentPackage, !(explictlyEnabledTraits == nil || areDefaultsEnabled) && manifest.traits.isEmpty { + if let parentPackage, !(explictlyEnabledTraits == nil || areDefaultsEnabled) && !manifest.supportsTraits { // We throw an error when default traits are disabled for a package without any traits // This allows packages to initially move new API behind traits once. throw ModuleError.disablingDefaultTraitsOnEmptyTraits( diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 928f3c9d405..7c7e9800be5 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -458,7 +458,7 @@ public func loadModulesGraph( useXCBuildFileRules: Bool = false, customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, observabilityScope: ObservabilityScope, - traitConfiguration: TraitConfiguration? = nil + traitConfiguration: TraitConfiguration = .default ) throws -> ModulesGraph { let rootManifests = manifests.filter(\.packageKind.isRoot).spm_createDictionary { ($0.path, $0) } let externalManifests = try manifests.filter { !$0.packageKind.isRoot } @@ -471,7 +471,7 @@ public func loadModulesGraph( let packages = Array(rootManifests.keys) let input = PackageGraphRootInput(packages: packages, traitConfiguration: traitConfiguration) - let graphRoot = PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: input, manifests: rootManifests, explicitProduct: explicitProduct, diff --git a/Sources/PackageGraph/PackageContainer.swift b/Sources/PackageGraph/PackageContainer.swift index d7e9e4a34b5..58c751f0c8e 100644 --- a/Sources/PackageGraph/PackageContainer.swift +++ b/Sources/PackageGraph/PackageContainer.swift @@ -102,7 +102,7 @@ public protocol PackageContainer { /// Fetch the enabled traits of a package container. /// /// NOTE: This method should only be called on root packages. - func getEnabledTraits(traitConfiguration: TraitConfiguration?, version: Version?) async throws -> Set + func getEnabledTraits(traitConfiguration: TraitConfiguration, version: Version?) async throws -> Set } extension PackageContainer { @@ -118,7 +118,7 @@ extension PackageContainer { return true } - public func getEnabledTraits(traitConfiguration: TraitConfiguration?, version: Version? = nil) async throws -> Set { + public func getEnabledTraits(traitConfiguration: TraitConfiguration, version: Version? = nil) async throws -> Set { return [] } } diff --git a/Sources/PackageGraph/PackageGraphRoot.swift b/Sources/PackageGraph/PackageGraphRoot.swift index 68fca677e18..8091ba65656 100644 --- a/Sources/PackageGraph/PackageGraphRoot.swift +++ b/Sources/PackageGraph/PackageGraphRoot.swift @@ -24,13 +24,13 @@ public struct PackageGraphRootInput { public let dependencies: [PackageDependency] /// The trait configuration for the root packages. - public let traitConfiguration: TraitConfiguration? + public let traitConfiguration: TraitConfiguration /// Create a package graph root. public init( packages: [AbsolutePath], dependencies: [PackageDependency] = [], - traitConfiguration: TraitConfiguration? = nil + traitConfiguration: TraitConfiguration = .default ) { self.packages = packages self.dependencies = dependencies @@ -49,6 +49,7 @@ public struct PackageGraphRoot { return self.packages.compactMapValues { $0.manifest } } + /// The root manifest(s)'s enabled traits (and their transitively enabled traits). public var enabledTraits: [PackageIdentity: Set] /// The root package references. @@ -94,7 +95,7 @@ public struct PackageGraphRoot { explicitProduct: String? = nil, dependencyMapper: DependencyMapper? = nil, observabilityScope: ObservabilityScope - ) { + ) throws { self.packages = input.packages.reduce(into: .init(), { partial, inputPath in if let manifest = manifests[inputPath] { let packagePath = manifest.path.parentDirectory @@ -103,20 +104,27 @@ public struct PackageGraphRoot { } }) - do { - // Calculate the enabled traits for root. - self.enabledTraits = try packages.reduce(into: [PackageIdentity: Set]()) { traitsMap, package in - let manifest = package.value.manifest - let traitConfiguration = input.traitConfiguration - - let enabledTraits = try manifest.enabledTraits(using: traitConfiguration?.enabledTraits, enableAllTraits: traitConfiguration?.enableAllTraits ?? false) - - traitsMap[package.key] = enabledTraits + // Calculate the enabled traits for root. + var enableTraitsMap: [PackageIdentity: Set] = [:] + enableTraitsMap = try packages.reduce(into: [PackageIdentity: Set]()) { traitsMap, package in + let manifest = package.value.manifest + let traitConfiguration = input.traitConfiguration + + // Should only ever have to use trait configuration here for roots. + let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) + traitsMap[package.key] = enabledTraits + + // Calculate the enabled traits for each dependency of this root: + manifest.dependencies.forEach { dependency in + if let traits = dependency.traits { + let traitNames = traits.map(\.name) + traitsMap[dependency.identity, default: []].formUnion(Set(traitNames)) + } } - } catch { - self.enabledTraits = [:] } + self.enabledTraits = enableTraitsMap + // FIXME: Deprecate special casing once the manifest supports declaring used executable products. // Special casing explicit products like this is necessary to pass the test suite and satisfy backwards compatibility. // However, changing the dependencies based on the command line arguments may force `Package.resolved` to temporarily change, @@ -129,18 +137,26 @@ public struct PackageGraphRoot { // Check that the dependency is used in at least one of the manifests. // If not, then we can omit this dependency if pruning unused dependencies // is enabled. - return manifests.values.reduce(false) { - guard $1.pruneDependencies else { return $0 || true } - if let isUsed = try? $1.isPackageDependencyUsed(dep, enabledTraits: input.traitConfiguration?.enabledTraits, enableAllTraits: input.traitConfiguration?.enableAllTraits ?? false) { - return $0 || isUsed + return manifests.values.reduce(false) { result, manifest in + guard manifest.pruneDependencies else { return true } + let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] + if let isUsed = try? manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) { + return result || isUsed } + return true } }) if let explicitProduct { // FIXME: `dependenciesRequired` modifies manifests and prevents conversion of `Manifest` to a value type - let deps = try? manifests.values.lazy.map({ try $0.dependenciesRequired(for: .everything, input.traitConfiguration?.enabledTraits, enableAllTraits: input.traitConfiguration?.enableAllTraits ?? false) }).flatMap({ $0 }) + let deps = try? manifests.values.lazy + .map({ manifest -> [PackageDependency] in + let enabledTraits: Set? = enableTraitsMap[manifest.packageIdentity] + return try manifest.dependenciesRequired(for: .everything, enabledTraits) + }) + .flatMap({ $0 }) + for dependency in deps ?? [] { adjustedDependencies.append(dependency.filtered(by: .specific([explicitProduct]))) } @@ -224,3 +240,4 @@ extension PackageDependency.Registry.Requirement { } } } + diff --git a/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift index d7f343f0bf7..8865707b651 100644 --- a/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift +++ b/Sources/PackageGraph/Resolution/DependencyResolutionNode.swift @@ -58,7 +58,7 @@ public enum DependencyResolutionNode { /// It is a warning condition, and builds do not actually need these dependencies. /// However, forcing the graph to resolve and fetch them anyway allows the diagnostics passes access /// to the information needed in order to provide actionable suggestions to help the user stitch up the dependency declarations properly. - case root(package: PackageReference, traitConfiguration: TraitConfiguration? = nil) + case root(package: PackageReference, traitConfiguration: TraitConfiguration = .default) /// The package. public var package: PackageReference { @@ -94,7 +94,7 @@ public enum DependencyResolutionNode { public var traits: Set? { switch self { case .root(_, let config): - return config?.enabledTraits + return config.enabledTraits case .product(_, _, let enabledTraits): return enabledTraits default: @@ -102,6 +102,17 @@ public enum DependencyResolutionNode { } } + public var traitConfiguration: TraitConfiguration { + switch self { + case .root(_, let config): + return config + case .product(_, _, let enabledTraits): + return .init(enabledTraits: enabledTraits) + case .empty: + return .default + } + } + /// Returns the dependency that a product has on its own package, if relevant. /// /// This is the constraint that requires all products from a package resolve to the same version. diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift index a41560d5f76..71e39debc1e 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift @@ -165,7 +165,7 @@ public struct PubGrubDependencyResolver { } /// Execute the resolution algorithm to find a valid assignment of versions. - public func solve(constraints: [Constraint], traitConfiguration: TraitConfiguration? = nil) async -> Result<[DependencyResolverBinding], Error> { + public func solve(constraints: [Constraint], traitConfiguration: TraitConfiguration = .default) async -> Result<[DependencyResolverBinding], Error> { // the graph resolution root let root: DependencyResolutionNode if constraints.count == 1, let constraint = constraints.first, constraint.package.kind.isRoot { diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift index 04603d23a68..37893363a04 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubPackageContainer.swift @@ -154,8 +154,7 @@ final class PubGrubPackageContainer { at version: Version, node: DependencyResolutionNode, overriddenPackages: [PackageReference: (version: BoundVersion, products: ProductFilter)], - root: DependencyResolutionNode, - traitConfiguration: TraitConfiguration? = nil + root: DependencyResolutionNode ) async throws -> [Incompatibility] { // FIXME: It would be nice to compute bounds for this as well. if await !self.underlying.isToolsVersionCompatible(at: version) { @@ -168,10 +167,11 @@ final class PubGrubPackageContainer { )] } + let enabledTraits = node.package.kind.isRoot ? try await self.underlying.getEnabledTraits(traitConfiguration: node.traitConfiguration) : node.traits var unprocessedDependencies = try await self.underlying.getDependencies( at: version, productFilter: node.productFilter, - node.traits + enabledTraits ) if let sharedVersion = node.versionLock(version: version) { unprocessedDependencies.append(sharedVersion) diff --git a/Sources/PackageGraph/TraitConfiguration.swift b/Sources/PackageGraph/TraitConfiguration.swift deleted file mode 100644 index db68fcb6941..00000000000 --- a/Sources/PackageGraph/TraitConfiguration.swift +++ /dev/null @@ -1,28 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -/// The trait configuration. -public struct TraitConfiguration: Codable, Hashable { - /// The traits to enable for the package. - package var enabledTraits: Set? - - /// Enables all traits of the package. - package var enableAllTraits: Bool - - public init( - enabledTraits: Set? = nil, - enableAllTraits: Bool = false - ) { - self.enabledTraits = enabledTraits - self.enableAllTraits = enableAllTraits - } -} diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index 9aedfa8454b..3330f9b147e 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -440,6 +440,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { let manifest = Manifest( displayName: parsedManifest.name, + packageIdentity: packageIdentity, path: manifestPath, packageKind: packageKind, packageLocation: packageLocation, diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index aa978e3f181..8e42f906f12 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -17,6 +17,8 @@ add_library(PackageModel IdentityResolver.swift InstalledSwiftPMConfiguration.swift Manifest/Manifest.swift + Manifest/Manifest+Traits.swift + Manifest/TraitConfiguration.swift Manifest/PackageConditionDescription.swift Manifest/PackageDependencyDescription.swift Manifest/PlatformDescription.swift diff --git a/Sources/PackageModel/Manifest/Manifest+Traits.swift b/Sources/PackageModel/Manifest/Manifest+Traits.swift new file mode 100644 index 00000000000..5f9260cc858 --- /dev/null +++ b/Sources/PackageModel/Manifest/Manifest+Traits.swift @@ -0,0 +1,509 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import Foundation + +// MARK: - Traits Validation + +/// Validator methods that check the correctness of traits and their support as defined in the manifest. +extension Manifest { + /// Determines whether traits are supported for this Manifest. + public var supportsTraits: Bool { + !self.traits.isEmpty + } + + /// Validates a trait by checking that it is defined in the manifest; if not, an error is thrown. + private func validateTrait(_ trait: TraitDescription) throws { + guard !trait.isDefault else { + if !supportsTraits { + throw TraitError.invalidTrait( + package: self.displayName, + trait: trait.name, + availableTraits: traits.map({ $0.name }) + ) + } + + return + } + + try self.validateTrait(trait.name) + } + + /// Validates a trait by checking that it is defined in the manifest; if not, an error is thrown. + private func validateTrait(_ trait: String, parentPackage: String? = nil) throws { + guard trait != "default" else { + if !supportsTraits { + throw TraitError.invalidTrait( + package: self.displayName, + trait: trait, + availableTraits: traits.map({ $0.name }) + ) + } + + return + } + + // Check if the passed trait is a valid trait. + if self.traits.first(where: { $0.name == trait }) == nil { + throw TraitError.invalidTrait( + package: self.displayName, + trait: trait, + availableTraits: self.traits.map({ $0.name }), + parentPackage: parentPackage + ) + } + } + + /// Validates a set of traits that is intended to be enabled for the manifest; if there are any discrepencies in the + /// set of enabled traits and whether the manifest defines these traits (or if it defines any traits at all), then an + /// error indicating the issue will be thrown. + private func validateEnabledTraits( + _ explicitlyEnabledTraits: Set?, + _ parentPackage: String? = nil + ) throws { + guard supportsTraits else { + if let explicitlyEnabledTraits, !explicitlyEnabledTraits.contains("default") { + throw TraitError.traitsNotSupported( + parentPackage: parentPackage, + package: self.displayName, + explicitlyEnabledTraits: explicitlyEnabledTraits.map({ $0 }) + ) + } + + return + } + + let enabledTraits = explicitlyEnabledTraits ?? [] + + // Validate each trait to assure it's defined in the current package. + for trait in enabledTraits { + try validateTrait(trait, parentPackage: parentPackage) + } + + let areDefaultsEnabled = enabledTraits.contains("default") + + // Ensure that disabling default traits is disallowed for packages that don't define any traits. + if !(explicitlyEnabledTraits == nil || areDefaultsEnabled) && !self.supportsTraits { + // We throw an error when default traits are disabled for a package without any traits + // This allows packages to initially move new API behind traits once. + throw TraitError.traitsNotSupported( + parentPackage: parentPackage, + package: displayName, + explicitlyEnabledTraits: enabledTraits.map({ $0 }) + ) + } + } + + private func validateTraitConfiguration(_ traitConfiguration: TraitConfiguration) throws { + guard supportsTraits else { + switch traitConfiguration { + case .disableAllTraits: + throw TraitError.traitsNotSupported( + parentPackage: nil, + package: displayName, + explicitlyEnabledTraits: [] + ) + case .enabledTraits(let traits): + throw TraitError.traitsNotSupported( + parentPackage: nil, + package: displayName, + explicitlyEnabledTraits: traits.map({ $0 }) + ) + case .enableAllTraits, .default: + return + } + } + + // Get the enabled traits; if the trait configuration's `.enabledTraits` returns nil, + // we know that it's the `.enableAllTraits` case, since the config does not store + // all the defined traits of the manifest itself. + let enabledTraits = traitConfiguration.enabledTraits ?? Set(self.traits.map({ $0.name })) + + try validateEnabledTraits(enabledTraits) + } +} + + +// MARK: - Traits + +/// Helper methods to calculate states of the manifest and its dependencies when given a set of enabled traits. +extension Manifest { + /// The default traits as defined in this package as the root. + public var defaultTraits: Set? { + // First, guard against whether this package actually has traits. + guard self.supportsTraits else { return nil } + return self.traits.filter(\.isDefault) + } + + /// A map of trait names to the trait description. + public var traitsMap: [String: TraitDescription] { + self.traits.reduce(into: [String: TraitDescription]()) { traitsMap, trait in + traitsMap[trait.name] = trait + } + } + + /// Calculates the set of all transitive traits that are enabled for this manifest using the passed trait configuration. + /// Since a trait configuration is only used for root packages, this method is intended for use with root packages only. + public func enabledTraits(using traitConfiguration: TraitConfiguration) throws -> Set? { + // If this manifest does not support traits, but the passed configuration either + // disables default traits or enables non-default traits (i.e. traits that would + // not exist for this manifest) then we must throw an error. + try validateTraitConfiguration(traitConfiguration) + guard supportsTraits, packageKind.isRoot else { + return nil + } + + var enabledTraits: Set = [] + + switch traitConfiguration { + case .enableAllTraits: + enabledTraits = Set(traits.map(\.name)) + case .default: + if let defaultTraits = defaultTraits?.map(\.name) { + enabledTraits = Set(defaultTraits) + } + case .disableAllTraits: + return [] + case .enabledTraits(let explicitlyEnabledTraits): + enabledTraits = explicitlyEnabledTraits + } + + if let allEnabledTraits = try? self.enabledTraits(using: enabledTraits, nil) { + enabledTraits = allEnabledTraits + } + + return enabledTraits + } + + /// Calculates the set of all transitive traits that are enabled for this manifest using the passed set of + /// explicitly enabled traits, and the parent package that defines the enabled traits for this package. + /// This method is intended for use with non-root packages. + public func enabledTraits(using explicitlyEnabledTraits: Set?, _ parentPackage: String?) throws -> Set? { + // If this manifest does not support traits, but the passed configuration either + // disables default traits or enables non-default traits (i.e. traits that would + // not exist for this manifest) then we must throw an error. + try validateEnabledTraits(explicitlyEnabledTraits, parentPackage) + guard supportsTraits else { + return nil + } + + var enabledTraits: Set = [] + + if let allEnabledTraits = try? calculateAllEnabledTraits(explictlyEnabledTraits: explicitlyEnabledTraits, parentPackage) { + enabledTraits = allEnabledTraits + } + + return enabledTraits + + } + + /// Determines if a trait is enabled with a given set of enabled traits. + public func isTraitEnabled(_ trait: TraitDescription, _ enabledTraits: Set?) throws -> Bool { + // First, check that the queried trait is valid. + try validateTrait(trait) + // Then, check that the list of enabled traits is valid. + try validateEnabledTraits(enabledTraits) + + // Special case for dealing with whether a default trait is enabled. + guard !trait.isDefault else { + // Check that the manifest defines default traits. + if self.traits.contains(where: \.isDefault) { + // If the trait is a default trait, then we must do the following checks: + // - If there exists a list of enabled traits, ensure that the default trait + // is declared in the set. + // - If there is no existing list of enabled traits (nil), and we know that the + // manifest has defined default traits, then just return true. + // - If none of these conditions are met, then defaults aren't enabled and we return false. + if let enabledTraits, enabledTraits.contains(trait.name) { + return true + } else if enabledTraits == nil { + return true + } else { + return false + } + } + + // If manifest does not define default traits, then throw an invalid trait error. + throw TraitError.invalidTrait( + package: self.displayName, + trait: trait.name, + availableTraits: self.traits.map(\.name) + ) + } + + guard supportsTraits else { + // If the above checks pass without throwing an error, then we simply return false + // if the manifest does not support traits. + return false + } + + // Special case for dealing with whether a default trait is enabled. + guard !trait.isDefault else { + // Check that the manifest defines default traits. + if self.traits.contains(where: \.isDefault) { + // If the trait is a default trait, then we must do the following checks: + // - If there exists a list of enabled traits, ensure that the default trait + // is declared in the set. + // - If there is no existing list of enabled traits (nil), and we know that the + // manifest has defined default traits, then just return true. + // - If none of these conditions are met, then defaults aren't enabled and we return false. + if let enabledTraits, enabledTraits.contains(trait.name) { + return true + } else if enabledTraits == nil { + return true + } else { + return false + } + } + + // If manifest does not define default traits, then throw an invalid trait error. + throw TraitError.invalidTrait( + package: self.displayName, + trait: trait.name, + availableTraits: self.traits.map(\.name) + ) + } + + // Compute all transitively enabled traits. + let allEnabledTraits = try calculateAllEnabledTraits(explictlyEnabledTraits: enabledTraits) + + return allEnabledTraits.contains(trait.name) + } + + /// Calculates and returns a set of all enabled traits, beginning with a set of explicitly enabled traits (which can either be the default traits of a manifest, or a configuration of enabled traits determined from a user-generated trait configuration) and determines which traits are transitively enabled. + private func calculateAllEnabledTraits( + explictlyEnabledTraits: Set?, + _ parentPackage: String? = nil + ) throws -> Set { + try validateEnabledTraits(explictlyEnabledTraits, parentPackage) + // This the point where we flatten the enabled traits and resolve the recursive traits + var enabledTraits = explictlyEnabledTraits ?? [] + let areDefaultsEnabled = enabledTraits.remove("default") != nil + + // We have to enable all default traits if no traits are enabled or the defaults are explicitly enabled + if explictlyEnabledTraits == nil || areDefaultsEnabled { + if let defaultTraits { + enabledTraits.formUnion(defaultTraits.flatMap(\.enabledTraits)) + } + } + + // Iteratively flatten transitively enabled traits; stop when all transitive traits have been found. + while true { + let transitivelyEnabledTraits = try Set( + // We are going to calculate which traits are actually enabled for a node here. To do this + // we have to check if default traits should be used and then flatten all the enabled traits. + enabledTraits + .flatMap { trait in + guard let traitDescription = traitsMap[trait] else { + throw TraitError.invalidTrait( + package: self.displayName, + trait: trait, + parentPackage: parentPackage + ) + } + return traitDescription.enabledTraits + } + ) + + let appendedList = enabledTraits.union(transitivelyEnabledTraits) + if appendedList.count == enabledTraits.count { + break + } else { + enabledTraits = appendedList + } + } + + return enabledTraits + } + + /// Computes the dependencies that are in use per target in this manifest. + public func usedTargetDependencies(withTraits enabledTraits: Set?) throws -> [String: Set] { + try self.targets.reduce(into: [String: Set]()) { depMap, target in + let nonTraitDeps = target.dependencies.filter { + $0.condition?.traits?.isEmpty ?? true + } + + let traitGuardedDeps = try target.dependencies.filter { dep in + let traits = dep.condition?.traits ?? [] + + // For each trait that is a condition on this target dependency, assure that + // each one is enabled in the manifest. + return try traits.allSatisfy({ try isTraitEnabled(.init(stringLiteral: $0), enabledTraits) }) + } + + let deps = nonTraitDeps + traitGuardedDeps + depMap[target.name] = Set(deps) + } + } + + /// Computes the set of package dependencies that are used by targets of this manifest. + public func usedDependencies(withTraits enabledTraits: Set?) throws -> (knownPackage: Set, unknownPackage: Set) { + let deps = try self.usedTargetDependencies(withTraits: enabledTraits) + .values + .flatMap { $0 } + .compactMap(\.package) + + var known: Set = [] + var unknown: Set = [] + + for item in deps { + if let dep = self.packageDependency(referencedBy: item) { + known.insert(dep.identity.description) + } else if self.targetMap[item] == nil { + // Marking this dependency as tentatively used, given that we cannot find the package ref at this stage. + unknown.insert(item) + } + } + + return (knownPackage: known, unknownPackage: unknown) + } + + /// Computes the list of target dependencies per target that are guarded by traits. + /// A target dependency is considered potentially trait-guarded if it defines a condition wherein there exists a + /// list of traits. + /// - Parameters: + /// - lowercasedKeys: A flag that determines whether the keys in the resulting dictionary are lowercased. + /// - Returns: A dictionary that maps the name of a `TargetDescription` to a list of its dependencies that are + /// guarded by traits. + public func traitGuardedTargetDependencies( + lowercasedKeys: Bool = false + ) -> [String: [TargetDescription.Dependency]] { + self.targets.reduce(into: [String: [TargetDescription.Dependency]]()) { depMap, target in + let traitGuardedTargetDependencies = traitGuardedTargetDependencies( + for: target + ) + + traitGuardedTargetDependencies.forEach { + guard let package = lowercasedKeys ? $0.key.package?.lowercased() : $0.key.package else { return } + depMap[package, default: []].append($0.key) + } + } + } + + /// Computes the list of target dependencies that are guarded by traits for given target. + /// A target dependency is considered potentially trait-guarded if it defines a condition wherein there exists a + /// list of traits. + /// - Parameters: + /// - target: A `TargetDescription` for which the trait-guarded target dependencies are calculated. + /// - Returns: A dictionary that maps each trait-guarded `TargetDescription.Dependency` of the given + /// `TargetDescription` to the list of traits that guard it. + public func traitGuardedTargetDependencies(for target: TargetDescription) + -> [TargetDescription.Dependency: Set] + { + target.dependencies.filter { + !($0.condition?.traits?.isEmpty ?? true) + }.reduce(into: [TargetDescription.Dependency: Set]()) { depMap, dep in + depMap[dep, default: []].formUnion(dep.condition?.traits ?? []) + } + } + + /// Determines whether a target dependency is enabled given a set of enabled traits for this manifest. + public func isTargetDependencyEnabled( + target: String, + _ dependency: TargetDescription.Dependency, + enabledTraits: Set?, + enableAllTraits: Bool = false + ) throws -> Bool { + guard self.supportsTraits, !enableAllTraits else { return true } + guard let target = self.targetMap[target] else { return false } + guard target.dependencies.contains(where: { $0 == dependency }) else { + throw InternalError( + "target dependency \(dependency.name) not found for target \(target.name) in package \(self.displayName)" + ) + } + + let traitsToEnable = self.traitGuardedTargetDependencies(for: target)[dependency] ?? [] + + let isEnabled = try traitsToEnable.allSatisfy { try self.isTraitEnabled( + .init(stringLiteral: $0), + enabledTraits, + ) } + + return traitsToEnable.isEmpty || isEnabled + } + /// Determines whether a given package dependency is used by this manifest given a set of enabled traits. + public func isPackageDependencyUsed(_ dependency: PackageDependency, enabledTraits: Set?) throws -> Bool { + let usedDependencies = try self.usedDependencies(withTraits: enabledTraits) + let foundKnownPackage = usedDependencies.knownPackage.contains(where: { + $0.caseInsensitiveCompare(dependency.identity.description) == .orderedSame + }) + + // if there is a target dependency referenced by name and the package it originates from is unknown, default to + // tentatively marking the package dependency as used. to be resolved later on. + return foundKnownPackage || (!foundKnownPackage && !usedDependencies.unknownPackage.isEmpty) + } +} + +// MARK: - Trait Error + +public enum TraitError: Swift.Error { + /// Indicates that an invalid trait was enabled. + case invalidTrait( + package: String, + trait: String, + availableTraits: [String] = [], + parentPackage: String? = nil + ) + + /// Indicates that the manifest does not support traits, yet a method was called with a configuration of enabled + /// traits. + case traitsNotSupported( + parentPackage: String?, + package: String, + explicitlyEnabledTraits: [String] + ) +} + +extension TraitError: CustomStringConvertible { + public var description: String { + switch self { + case .invalidTrait(let package, let trait, var availableTraits, let parentPackage): + availableTraits = availableTraits.sorted() + var errorMsg = "Trait '\(trait)'" + if let parentPackage { + errorMsg += " enabled by parent package '\(parentPackage)'" + } + errorMsg += " is not declared by package '\(package)'." + if availableTraits.isEmpty { + errorMsg += " There are no available traits declared by this package." + } else { + errorMsg += + " The available traits declared by this package are: \(availableTraits.joined(separator: ", "))." + } + return errorMsg + case .traitsNotSupported(let parentPackage, let package, var explicitlyEnabledTraits): + explicitlyEnabledTraits = explicitlyEnabledTraits.sorted() + if explicitlyEnabledTraits.isEmpty { + if let parentPackage { + return """ + Disabled default traits by package '\(parentPackage)' on package '\(package)' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + """ + } else { + return """ + Disabled default traits on package '\(package)' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + """ + } + } else { + if let parentPackage { + return """ + Package '\(parentPackage)' enables traits [\(explicitlyEnabledTraits.joined(separator: ", "))] on package '\(package)' that declares no traits. + """ + } else { + return """ + Traits [\(explicitlyEnabledTraits.joined(separator: ", "))] have been enabled on package '\(package)' that declares no traits. + """ + } + } + } + } +} diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 1f8a937d6e7..798a4f6543b 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -30,6 +30,9 @@ public final class Manifest: Sendable { /// FIXME: deprecate this, there is no value in this once we have real package identifiers public let displayName: String + /// The package identity. + public let packageIdentity: PackageIdentity + // FIXME: deprecate this, this is not part of the manifest information, we just use it as a container for this data // FIXME: This doesn't belong here, we want the Manifest to be purely tied // to the repository state, it shouldn't matter where it is. @@ -108,6 +111,7 @@ public final class Manifest: Sendable { public init( displayName: String, + packageIdentity: PackageIdentity, path: AbsolutePath, packageKind: PackageReference.Kind, packageLocation: String, @@ -128,6 +132,7 @@ public final class Manifest: Sendable { pruneDependencies: Bool = false ) { self.displayName = displayName + self.packageIdentity = packageIdentity self.path = path self.packageKind = packageKind self.packageLocation = packageLocation @@ -194,16 +199,14 @@ public final class Manifest: Sendable { /// /// If we set the `enabledTraits` to be `["Trait1"]`, then the list of dependencies guarded by traits would be `[]`. /// Otherwise, if `enabledTraits` were `nil`, then the dependencies guarded by traits would be `["Bar"]`. - public func dependenciesTraitGuarded( - withEnabledTraits enabledTraits: Set?, - enableAllTraits: Bool = false - ) -> [PackageDependency] { + public func dependenciesTraitGuarded(withEnabledTraits enabledTraits: Set?) -> [PackageDependency] { guard supportsTraits else { return [] } let traitGuardedDeps = self.traitGuardedTargetDependencies(lowercasedKeys: true) - let explicitlyEnabledTraits = try? self.enabledTraits(using: enabledTraits, enableAllTraits: enableAllTraits) + let explicitlyEnabledTraits = try? self.enabledTraits(using: enabledTraits, nil) + guard self.toolsVersion >= .v5_2 && !self.packageKind.isRoot else { let deps = self.dependencies.filter { var result = false @@ -246,12 +249,14 @@ public final class Manifest: Sendable { continue } - if let explicitlyEnabledTraits, - guardingTraits.intersection(explicitlyEnabledTraits) != guardingTraits + if let enabledTraits, + guardingTraits.intersection(enabledTraits) != guardingTraits { guardedDependencies.insert(dependency.identity) } } + + // Since plugins cannot specify traits as a guarding condition, we can skip them. } let dependencies = self.dependencies.filter { guardedDependencies.contains($0.identity) } @@ -262,8 +267,7 @@ public final class Manifest: Sendable { /// Returns the package dependencies required for a particular products filter and trait configuration. public func dependenciesRequired( for productFilter: ProductFilter, - _ enabledTraits: Set?, - enableAllTraits: Bool = false + _ enabledTraits: Set? ) throws -> [PackageDependency] { #if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION // If we have already calculated it, returned the cached value. @@ -280,17 +284,13 @@ public final class Manifest: Sendable { return dependencies } #else - let explicitlyEnabledTraits: Set? = try self.enabledTraits( - using: enabledTraits, - enableAllTraits: enableAllTraits - ) guard self.toolsVersion >= .v5_2 && !self.packageKind.isRoot else { var dependencies = self.dependencies - if self.pruneDependencies { - dependencies = try dependencies.filter { - try self.isPackageDependencyUsed($0, enabledTraits: explicitlyEnabledTraits) - } + if pruneDependencies { + dependencies = try dependencies.filter({ + return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) + }) } return dependencies } @@ -298,9 +298,9 @@ public final class Manifest: Sendable { // using .nothing as cache key while ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION is false if var dependencies = self._requiredDependencies[.nothing] { if self.pruneDependencies { - dependencies = try dependencies.filter { - try self.isPackageDependencyUsed($0, enabledTraits: explicitlyEnabledTraits) - } + dependencies = try dependencies.filter({ + return try self.isPackageDependencyUsed($0, enabledTraits: enabledTraits) + }) } return dependencies } else { @@ -310,7 +310,7 @@ public final class Manifest: Sendable { guard try self.isTargetDependencyEnabled( target: target.name, targetDependency, - enabledTraits: explicitlyEnabledTraits + enabledTraits: enabledTraits ) else { continue } if let dependency = self.packageDependency(referencedBy: targetDependency) { requiredDependencies.insert(dependency.identity) @@ -458,7 +458,7 @@ public final class Manifest: Sendable { } } - private func packageDependency( + internal func packageDependency( referencedBy packageName: String ) -> PackageDependency? { self.dependencies.first(where: { @@ -672,337 +672,3 @@ extension Manifest: Encodable { try container.encode(self.packageKind, forKey: .packageKind) } } - -// MARK: - Traits - -/// Helper methods that enable data collection through traits configurations in manifests. -extension Manifest { - /// Determines whether traits are supported for this Manifest. - public var supportsTraits: Bool { - !self.traits.isEmpty - } - - /// The default traits as defined in this package as the root. - public var defaultTraits: Set? { - // First, guard against whether this package actually has traits. - guard self.supportsTraits else { return nil } - return self.traits.filter(\.isDefault) - } - - /// A map of trait names to the trait description. - public var traitsMap: [String: TraitDescription] { - self.traits.reduce(into: [String: TraitDescription]()) { traitsMap, trait in - traitsMap[trait.name] = trait - } - } - - /// Calculates the set of all transitive traits that are enabled for this manifest using the passed set of - /// explicitly enabled traits and a flag that - /// determines whether all traits are enabled. - public func enabledTraits( - using explicitTraits: Set?, - enableAllTraits: Bool = false - ) throws -> Set? { - guard self.supportsTraits else { - if var explicitTraits { - explicitTraits.remove("default") - if !explicitTraits.isEmpty { - throw TraitError.traitsNotSupported( - package: self.displayName, - explicitlyEnabledTraits: self.traits.map(\.name) - ) - } - } - - return nil - } - - var enabledTraits = explicitTraits - - if enableAllTraits { - enabledTraits = (enabledTraits ?? []).union(Set(self.traits.map(\.name))) - } - - if let allEnabledTraits = try? calculateAllEnabledTraits(explictlyEnabledTraits: enabledTraits) { - enabledTraits = allEnabledTraits - } - - return enabledTraits - } - - /// Given a trait, determine if the trait is enabled given the current set of enabled traits. - public func isTraitEnabled( - _ trait: TraitDescription, - _ explicitTraits: Set?, - _ enableAllTraits: Bool = false - ) throws -> Bool { - guard self.supportsTraits else { - if var explicitTraits { - explicitTraits.remove("default") - if !explicitTraits.isEmpty { - throw TraitError.invalidTrait( - package: self.displayName, - trait: trait.name, - availableTraits: self.traits.map(\.name) - ) - } - } - - return false - } - guard !trait.isDefault else { - if self.traits.contains(where: \.isDefault) { - return true - } - throw TraitError.invalidTrait( - package: self.displayName, - trait: trait.name, - availableTraits: self.traits.map(\.name) - ) - } - - let allEnabledTraits = try enabledTraits(using: explicitTraits, enableAllTraits: enableAllTraits) ?? [] - - return allEnabledTraits.contains(trait.name) - } - - /// Calculates and returns a set of all enabled traits, beginning with a set of explicitly enabled traits (either - /// defined by default traits of - /// this manifest, or by a user-generated traits configuration) and determines which traits are transitively - /// enabled. - private func calculateAllEnabledTraits(explictlyEnabledTraits: Set?) throws -> Set { - // This the point where we flatten the enabled traits and resolve the recursive traits - var enabledTraits = explictlyEnabledTraits ?? [] - let areDefaultsEnabled = enabledTraits.remove("default") != nil - - for trait in enabledTraits { - // Check if the enabled trait is a valid trait - if self.traits.first(where: { $0.name == trait }) == nil { - throw TraitError.invalidTrait(package: self.displayName, trait: trait) - } - } - - // We have to enable all default traits if no traits are enabled or the defaults are explicitly enabled - if explictlyEnabledTraits == nil || areDefaultsEnabled { - if let defaultTraits { - enabledTraits.formUnion(defaultTraits.flatMap(\.enabledTraits)) - } - } - - // Iteratively flatten transitively enabled traits; stop when all transitive traits have been found. - while true { - let transitivelyEnabledTraits = try Set( - // We are going to calculate which traits are actually enabled for a node here. To do this - // we have to check if default traits should be used and then flatten all the enabled traits. - enabledTraits - .flatMap { trait in - guard let traitDescription = traitsMap[trait] else { - throw TraitError.invalidTrait(package: self.displayName, trait: trait) - } - return traitDescription.enabledTraits - } - ) - - let appendedList = enabledTraits.union(transitivelyEnabledTraits) - if appendedList.count == enabledTraits.count { - break - } else { - enabledTraits = appendedList - } - } - - return enabledTraits - } - - /// Computes the dependencies that are in use per target in this manifest. - public func usedTargetDependencies( - withTraits enabledTraits: Set?, - enableAllTraits: Bool = false - ) throws -> [String: Set] { - try self.targets.reduce(into: [String: Set]()) { depMap, target in - let nonTraitDeps = target.dependencies.filter { - $0.condition?.traits?.isEmpty ?? true - } - - let traitGuardedDeps = try target.dependencies.filter { dep in - let traits = dep.condition?.traits ?? [] - - // For each trait that is a condition on this target dependency, assure that - // each one is enabled in the manifest. - return try traits.allSatisfy { try self.isTraitEnabled( - .init(stringLiteral: $0), - enabledTraits, - enableAllTraits - ) } - } - - let deps = nonTraitDeps + traitGuardedDeps - depMap[target.name] = Set(deps) - } - } - - /// Computes the set of package dependencies that are used by targets of this manifest. - public func usedDependencies( - withTraits enabledTraits: Set?, - enableAllTraits: Bool = false - ) throws -> (knownPackage: Set, unknownPackage: Set) { - let deps = try self.usedTargetDependencies( - withTraits: enabledTraits, - enableAllTraits: enableAllTraits - ) - .values - .flatMap { $0 } - .compactMap(\.package) - - var known: Set = [] - var unknown: Set = [] - - for item in deps { - if let dep = self.packageDependency(referencedBy: item) { - known.insert(dep.identity.description) - } else if self.targetMap[item] == nil { - // Marking this dependency as tentatively used, given that we cannot find the package ref at this stage. - unknown.insert(item) - } - } - - return (knownPackage: known, unknownPackage: unknown) - } - - /// Computes the list of target dependencies per target that are guarded by traits. - /// A target dependency is considered potentially trait-guarded if it defines a condition wherein there exists a - /// list of traits. - /// - Parameters: - /// - lowercasedKeys: A flag that determines whether the keys in the resulting dictionary are lowercased. - /// - Returns: A dictionary that maps the name of a `TargetDescription` to a list of its dependencies that are - /// guarded by traits. - public func traitGuardedTargetDependencies( - lowercasedKeys: Bool = false - ) -> [String: [TargetDescription.Dependency]] { - self.targets.reduce(into: [String: [TargetDescription.Dependency]]()) { depMap, target in - let traitGuardedTargetDependencies = traitGuardedTargetDependencies( - for: target - ) - - traitGuardedTargetDependencies.forEach { - guard let package = lowercasedKeys ? $0.key.package?.lowercased() : $0.key.package else { return } - depMap[package, default: []].append($0.key) - } - } - } - - /// Computes the list of target dependencies that are guarded by traits for given target. - /// A target dependency is considered potentially trait-guarded if it defines a condition wherein there exists a - /// list of traits. - /// - Parameters: - /// - target: A `TargetDescription` for which the trait-guarded target dependencies are calculated. - /// - Returns: A dictionary that maps each trait-guarded `TargetDescription.Dependency` of the given - /// `TargetDescription` to the list of traits that guard it. - public func traitGuardedTargetDependencies(for target: TargetDescription) - -> [TargetDescription.Dependency: Set] - { - target.dependencies.filter { - !($0.condition?.traits?.isEmpty ?? true) - }.reduce(into: [TargetDescription.Dependency: Set]()) { depMap, dep in - depMap[dep, default: []].formUnion(dep.condition?.traits ?? []) - } - } - - /// Computes the enabled traits for a given target dependency - public func enabledTraits(forDependency dependency: TargetDescription.Dependency) -> Set? { - guard let package = self.packageDependency(referencedBy: dependency), - let traits = package.traits?.compactMap(\.name) - else { - return nil - } - - return Set(traits) - } - - /// Determines whether a target dependency is enabled given a set of enabled traits for this manifest. - public func isTargetDependencyEnabled( - target: String, - _ dependency: TargetDescription.Dependency, - enabledTraits: Set?, - enableAllTraits: Bool = false - ) throws -> Bool { - guard self.supportsTraits, !enableAllTraits else { return true } - guard let target = self.targetMap[target] else { return false } - guard target.dependencies.contains(where: { $0 == dependency }) else { - throw InternalError( - "target dependency \(dependency.name) not found for target \(target.name) in package \(self.displayName)" - ) - } - - let traitsToEnable = self.traitGuardedTargetDependencies(for: target)[dependency] ?? [] - - let isEnabled = try traitsToEnable.allSatisfy { try self.isTraitEnabled( - .init(stringLiteral: $0), - enabledTraits, - enableAllTraits - ) } - - return traitsToEnable.isEmpty || isEnabled - } - - /// Determines whether a given package dependency is used by this manifest given a set of enabled traits. - public func isPackageDependencyUsed( - _ dependency: PackageDependency, - enabledTraits: Set?, - enableAllTraits: Bool = false - ) throws -> Bool { - let usedDependencies = try self.usedDependencies(withTraits: enabledTraits, enableAllTraits: enableAllTraits) - let foundKnownPackage = usedDependencies.knownPackage.contains(where: { - $0.caseInsensitiveCompare(dependency.identity.description) == .orderedSame - }) - - // if there is a target dependency referenced by name and the package it originates from is unknown, default to - // tentatively marking the package dependency as used. to be resolved later on. - return foundKnownPackage || (!foundKnownPackage && !usedDependencies.unknownPackage.isEmpty) - } -} - -// MARK: - Trait Error - -public indirect enum TraitError: Swift.Error { - /// Indicates that an invalid trait was enabled. - case invalidTrait( - package: String, - trait: String, - availableTraits: [String] = [] - ) - - /// Indicates that the manifest does not support traits, yet a method was called with a configuration of enabled - /// traits. - case traitsNotSupported( - package: String, - explicitlyEnabledTraits: [String] - ) -} - -extension TraitError: CustomStringConvertible { - public var description: String { - switch self { - case .invalidTrait(let package, let trait, let availableTraits): - var errorMsg = """ - Trait '"\(trait)"' is not declared by package '\(package)'. - """ - if availableTraits.isEmpty { - errorMsg += " There are no available traits defined by this package." - } else { - errorMsg += - " The available traits defined for this package are: \(availableTraits.joined(separator: ", "))." - } - return errorMsg - case .traitsNotSupported(let package, let explicitlyEnabledTraits): - return """ - Package \( - package - ) does not have any available traits defined, yet an explicit configuration of enabled traits were provided: \( - explicitlyEnabledTraits - .joined(separator: ", ") - ). - """ - } - } -} diff --git a/Sources/PackageModel/Manifest/PackageDependencyDescription.swift b/Sources/PackageModel/Manifest/PackageDependencyDescription.swift index 647fe161a0d..8cc316b3e81 100644 --- a/Sources/PackageModel/Manifest/PackageDependencyDescription.swift +++ b/Sources/PackageModel/Manifest/PackageDependencyDescription.swift @@ -20,7 +20,7 @@ import struct TSCUtility.Version /// Represents a package dependency. public enum PackageDependency: Equatable, Hashable, Sendable { /// A struct representing an enabled trait of a dependency. - package struct Trait: Hashable, Sendable, Codable, ExpressibleByStringLiteral { + public struct Trait: Hashable, Sendable, Codable, ExpressibleByStringLiteral { /// A condition that limits the application of a dependencies trait. package struct Condition: Hashable, Sendable, Codable { /// The set of traits of this package that enable the dependency's trait. diff --git a/Sources/PackageModel/Manifest/TraitConfiguration.swift b/Sources/PackageModel/Manifest/TraitConfiguration.swift new file mode 100644 index 00000000000..65ebe5acc51 --- /dev/null +++ b/Sources/PackageModel/Manifest/TraitConfiguration.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// The trait configuration. +public enum TraitConfiguration: Codable, Hashable { + case enableAllTraits + case disableAllTraits + case enabledTraits(Set) + case `default` + + public init( + enabledTraits: Set? = nil, + enableAllTraits: Bool = false + ) { + // If all traits are enabled, then no other checks are necessary. + guard !enableAllTraits else { + self = .enableAllTraits + return + } + + // There can be two possible cases here: + // - The set of enabled traits is empty, which means that no traits are enabled. + // - The set of enabled traits is not empty and specifies which traits are enabled. + if let enabledTraits { + if enabledTraits.isEmpty { + self = .disableAllTraits + } else { + self = .enabledTraits(enabledTraits) + } + } else { + // Since enableAllTraits isn't enabled and there isn't a set of enabled traits, + // there is no configuration passed by the user. + self = .default + } + } + + /// The set of enabled traits, if available. + public var enabledTraits: Set? { + switch self { + case .default: + ["default"] + case .enabledTraits(let traits): + traits + case .disableAllTraits: + [] + case .enableAllTraits: + nil + } + } +} diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index c874ef1a9d5..658c70f7c92 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -13,6 +13,8 @@ import Basics import PackageGraph +import enum PackageModel.TraitConfiguration + import protocol TSCBasic.OutputByteStream /// An enum representing what subset of the package to build. diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index c8b486400e2..8873b3bf500 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -19,7 +19,7 @@ import Basics import Build import PackageGraph internal import PackageLoading -internal import PackageModel +import PackageModel import SPMBuildCore public enum BuildDestination { diff --git a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift index 5b7f512e59e..b55aeb86067 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -15,7 +15,7 @@ import Foundation import Basics import PackageGraph internal import PackageLoading -internal import PackageModel +import PackageModel struct PluginTargetBuildDescription: BuildTarget { private let target: ResolvedModule diff --git a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift index d3d9e5c7003..c1892c25aee 100644 --- a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift @@ -136,15 +136,12 @@ public struct FileSystemPackageContainer: PackageContainer { fatalError("This should never be called") } - public func getEnabledTraits(traitConfiguration: TraitConfiguration?, at version: Version? = nil) async throws -> Set { + public func getEnabledTraits(traitConfiguration: TraitConfiguration, at version: Version? = nil) async throws -> Set { guard version == nil else { throw InternalError("File system package container does not support versioning.") } let manifest = try await loadManifest() - guard manifest.packageKind.isRoot else { - return [] - } - let enabledTraits = try manifest.enabledTraits(using: traitConfiguration?.enabledTraits, enableAllTraits: traitConfiguration?.enableAllTraits ?? false) + let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) return enabledTraits ?? [] } } diff --git a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 208b05dd2e0..4b23d79fa1b 100644 --- a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -212,7 +212,7 @@ public class RegistryPackageContainer: PackageContainer { return (manifests: manifests, fileSystem: fileSystem) } - public func getEnabledTraits(traitConfiguration: TraitConfiguration?, at version: Version?) async throws -> Set { + public func getEnabledTraits(traitConfiguration: TraitConfiguration, at version: Version?) async throws -> Set { guard let version else { throw InternalError("Version needed to compute enabled traits for registry package \(self.package.identity.description)") } @@ -220,7 +220,7 @@ public class RegistryPackageContainer: PackageContainer { guard manifest.packageKind.isRoot else { return [] } - let enabledTraits = try manifest.enabledTraits(using: traitConfiguration?.enabledTraits, enableAllTraits: traitConfiguration?.enableAllTraits ?? false) + let enabledTraits = try manifest.enabledTraits(using: traitConfiguration) return enabledTraits ?? [] } } diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index 4bc7be1a26c..da1f56f959f 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -419,12 +419,12 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri } } - public func getEnabledTraits(traitConfiguration: TraitConfiguration?, at revision: String?, version: Version?) async throws -> Set { + public func getEnabledTraits(traitConfiguration: TraitConfiguration, at revision: String?, version: Version?) async throws -> Set { guard let version, let tag = getTag(for: version) else { return [] } let manifest = try await self.loadManifest(tag: tag, version: version) - return try manifest.enabledTraits(using: traitConfiguration?.enabledTraits, enableAllTraits: traitConfiguration?.enableAllTraits ?? false) ?? [] + return try manifest.enabledTraits(using: traitConfiguration) ?? [] } public var isRemoteContainer: Bool? { diff --git a/Sources/Workspace/ResolverPrecomputationProvider.swift b/Sources/Workspace/ResolverPrecomputationProvider.swift index 1c3ea8ade29..5319db97c10 100644 --- a/Sources/Workspace/ResolverPrecomputationProvider.swift +++ b/Sources/Workspace/ResolverPrecomputationProvider.swift @@ -177,25 +177,22 @@ private struct LocalPackageContainer: PackageContainer { } } - func getEnabledTraits(traitConfiguration: TraitConfiguration?, at version: Version? = nil) async throws -> Set { + func getEnabledTraits(traitConfiguration: TraitConfiguration, at version: Version? = nil) async throws -> Set { guard manifest.packageKind.isRoot else { return [] } - let configurationEnabledTraits = traitConfiguration?.enabledTraits - let enableAllTraits = traitConfiguration?.enableAllTraits ?? false - if let version { switch dependency?.state { case .sourceControlCheckout(.version(version, revision: _)): - return try manifest.enabledTraits(using: configurationEnabledTraits, enableAllTraits: enableAllTraits) ?? [] + return try manifest.enabledTraits(using: traitConfiguration) ?? [] case .registryDownload(version: version): - return try manifest.enabledTraits(using: configurationEnabledTraits, enableAllTraits: enableAllTraits) ?? [] + return try manifest.enabledTraits(using: traitConfiguration) ?? [] default: throw InternalError("expected version based state, but state was \(String(describing: dependency?.state))") } } else { - return try manifest.enabledTraits(using: configurationEnabledTraits, enableAllTraits: enableAllTraits) ?? [] + return try manifest.enabledTraits(using: traitConfiguration) ?? [] } } } diff --git a/Sources/Workspace/Workspace+Configuration.swift b/Sources/Workspace/Workspace+Configuration.swift index 1bfc7d62636..671ffba09f4 100644 --- a/Sources/Workspace/Workspace+Configuration.swift +++ b/Sources/Workspace/Workspace+Configuration.swift @@ -796,7 +796,7 @@ public struct WorkspaceConfiguration { public var pruneDependencies: Bool /// The trait configuration for the root. - public var traitConfiguration: TraitConfiguration? + public var traitConfiguration: TraitConfiguration public init( skipDependenciesUpdates: Bool, @@ -813,7 +813,7 @@ public struct WorkspaceConfiguration { manifestImportRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])?, usePrebuilts: Bool, pruneDependencies: Bool, - traitConfiguration: TraitConfiguration? + traitConfiguration: TraitConfiguration ) { self.skipDependenciesUpdates = skipDependenciesUpdates self.prefetchBasedOnResolvedFile = prefetchBasedOnResolvedFile @@ -849,7 +849,7 @@ public struct WorkspaceConfiguration { manifestImportRestrictions: .none, usePrebuilts: false, pruneDependencies: false, - traitConfiguration: nil + traitConfiguration: .default ) } diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index f6af11802d9..c5e080404cf 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -47,7 +47,7 @@ import struct SourceControl.Revision import struct TSCUtility.Version import struct PackageModel.TargetDescription import struct PackageModel.TraitDescription -import struct PackageGraph.TraitConfiguration +import enum PackageModel.TraitConfiguration import class PackageModel.Manifest extension Workspace { @@ -82,7 +82,7 @@ extension Workspace { let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) // Load the current manifests. - let graphRoot = PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, dependencyMapper: self.dependencyMapper, @@ -350,7 +350,7 @@ extension Workspace { packages: root.packages, observabilityScope: observabilityScope ) - let graphRoot = PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, explicitProduct: explicitProduct, @@ -517,7 +517,7 @@ extension Workspace { let resolvedFileOriginHash = try self.computeResolvedFileOriginHash(root: root) // Load the current manifests. - let graphRoot = PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: root, manifests: rootManifests, explicitProduct: explicitProduct, diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 5b1935b9665..1eaaccba606 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -27,7 +27,6 @@ import protocol PackageGraph.CustomPackageContainer import struct PackageGraph.GraphLoadingNode import struct PackageGraph.PackageContainerConstraint import struct PackageGraph.PackageGraphRoot -import struct PackageGraph.TraitConfiguration import class PackageLoading.ManifestLoader import struct PackageLoading.ManifestValidator import struct PackageLoading.ToolsVersionParser @@ -36,6 +35,7 @@ import struct PackageModel.PackageIdentity import struct PackageModel.PackageReference import enum PackageModel.ProductFilter import struct PackageModel.ToolsVersion +import enum PackageModel.TraitConfiguration import protocol TSCBasic.FileSystem import func TSCBasic.findCycle import struct TSCBasic.KeyedPair @@ -226,10 +226,8 @@ extension Workspace { let inputNodes: [GraphLoadingNode] = try root.packages.map { identity, package in inputIdentities.append(package.reference) - var traits: Set? = [] - if let enabledTraits = rootEnabledTraitsMap[package.reference.identity] { - traits = try package.manifest.enabledTraits(using: enabledTraits) - } + var traits: Set? = rootEnabledTraitsMap[package.reference.identity] ?? [] + let node = try GraphLoadingNode( identity: identity, manifest: package.manifest, @@ -241,12 +239,8 @@ extension Workspace { let package = dependency.packageRef inputIdentities.append(package) return try manifestsMap[dependency.identity].map { manifest in - var traits: Set? = [] + var traits: Set? = rootDependenciesEnabledTraitsMap[dependency.identity] ?? [] - if let enabledTraits = rootDependenciesEnabledTraitsMap[dependency.identity] { - // Recursively calculate the enabled traits of this package. - traits = try manifest.enabledTraits(using: enabledTraits) - } return try GraphLoadingNode( identity: dependency.identity, manifest: manifest, @@ -346,10 +340,9 @@ extension Workspace { // Calculate all transitively enabled traits for this manifest. var allEnabledTraits: Set = [] - if let explicitlyEnabledTraits, - let calculatedTraits = try manifest.enabledTraits(using: Set(explicitlyEnabledTraits)) + if let explicitlyEnabledTraits { - allEnabledTraits = calculatedTraits + allEnabledTraits = Set(explicitlyEnabledTraits) } return try GraphLoadingNode( @@ -431,7 +424,7 @@ extension Workspace { return true } return !conditionTraits - .intersection(workspace.configuration.traitConfiguration?.enabledTraits ?? []).isEmpty + .intersection(workspace.configuration.traitConfiguration.enabledTraits ?? []).isEmpty }.map(\.name) ?? [] traitMap[dependency.identity] = Set(explicitlyEnabledTraits) @@ -599,16 +592,14 @@ extension Workspace { let rootManifests = try root.manifests.mapValues { manifest in let deps = try manifest.dependencies.filter { dep in guard configuration.pruneDependencies else { return true } - let explicitlyEnabledTraits = try manifest.enabledTraits( - using: configuration.traitConfiguration?.enabledTraits, - enableAllTraits: configuration.traitConfiguration?.enableAllTraits ?? false - ) - let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: explicitlyEnabledTraits) + let enabledTraits = root.enabledTraits[manifest.packageIdentity] + let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) return isDepUsed } return Manifest( displayName: manifest.displayName, + packageIdentity: manifest.packageIdentity, path: manifest.path, packageKind: manifest.packageKind, packageLocation: manifest.packageLocation, @@ -638,42 +629,8 @@ extension Workspace { let firstLevelDependencies = try topLevelManifests.values.map { manifest in try manifest.dependencies.filter { dep in guard configuration.pruneDependencies else { return true } - var config = configuration.traitConfiguration - if manifest.packageKind.isRoot { - let manifestEnabledTraits = try manifest.enabledTraits( - using: configuration.traitConfiguration?.enabledTraits, - enableAllTraits: configuration.traitConfiguration?.enableAllTraits ?? false - ) - config = .init(enabledTraits: manifestEnabledTraits) - } else { - let rootManifests = root.manifests.values.filter(\.packageKind.isRoot) - - // find the package dependency in each of the root manifests - let packageDependencyInRoots = rootManifests - .compactMap { - $0.dependencies - .first(where: { $0.identity.description == manifest.displayName.lowercased() }) - } - - // pluck out the enabled traits defined by the package dependency struct - let enabledTraitsPerPackageDep = packageDependencyInRoots.map(\.traits) - - // create a union of the sets; if all are nil, then there is no config - var manifestEnabledTraits: Set? - for enabledTraits in enabledTraitsPerPackageDep { - if let enabledTraits = enabledTraits?.map(\.name) { - if let resultSet = manifestEnabledTraits { - manifestEnabledTraits = resultSet.union(Set(enabledTraits)) - } else { - manifestEnabledTraits = Set(enabledTraits) - } - } - } - - manifestEnabledTraits = try manifest.enabledTraits(using: manifestEnabledTraits) - config = .init(enabledTraits: manifestEnabledTraits) - } - let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: config?.enabledTraits) + var enabledTraits: Set? = root.enabledTraits[manifest.packageIdentity] + let isDepUsed = try manifest.isPackageDependencyUsed(dep, enabledTraits: enabledTraits) return isDepUsed }.map(\.packageRef) }.flatMap(\.self) @@ -706,7 +663,7 @@ extension Workspace { ) dependenciesManifests.forEach { loadedManifests[$0.key] = $0.value } return try (dependenciesRequired + dependenciesGuarded).compactMap { dependency in - try loadedManifests[dependency.identity].flatMap { + try loadedManifests[dependency.identity].flatMap { manifest in // we also compare the location as this function may attempt to load // dependencies that have the same identity but from a different location // which is an error case we diagnose an report about in the GraphLoading part which @@ -718,20 +675,18 @@ extension Workspace { return !conditionTraits.intersection(node.item.enabledTraits).isEmpty }.map(\.name) - var allEnabledTraits: Set = [] - if let explicitlyEnabledTraits, - let calculatedTraits = try $0.enabledTraits(using: Set(explicitlyEnabledTraits)) - { - allEnabledTraits = calculatedTraits - } + let calculatedTraits = try manifest.enabledTraits( + using: explicitlyEnabledTraits.flatMap { Set($0) }, + node.item.identity.description + ) - return $0.canonicalPackageLocation == dependency.packageRef.canonicalLocation ? + return manifest.canonicalPackageLocation == dependency.packageRef.canonicalLocation ? try KeyedPair( GraphLoadingNode( identity: dependency.identity, - manifest: $0, + manifest: manifest, productFilter: dependency.productFilter, - enabledTraits: allEnabledTraits + enabledTraits: calculatedTraits ?? [] ), key: dependency.identity ) : @@ -744,13 +699,8 @@ extension Workspace { do { let manifestGraphRoots = try topLevelManifests.map { identity, manifest in - // Since these represent the root manifests, can pass in the enabled traits from the trait configuration - // here, as that is what it represents. let isRoot = manifest.packageKind.isRoot - let enabledTraits = isRoot ? try manifest.enabledTraits( - using: configuration.traitConfiguration?.enabledTraits, - enableAllTraits: configuration.traitConfiguration?.enableAllTraits ?? false - ) : [] + let enabledTraits = isRoot ? root.enabledTraits[identity] : [] return try KeyedPair( GraphLoadingNode( identity: identity, diff --git a/Sources/Workspace/Workspace+Registry.swift b/Sources/Workspace/Workspace+Registry.swift index e304b4697a1..17687d99ffc 100644 --- a/Sources/Workspace/Workspace+Registry.swift +++ b/Sources/Workspace/Workspace+Registry.swift @@ -318,6 +318,7 @@ extension Workspace { let modifiedManifest = Manifest( displayName: manifest.displayName, + packageIdentity: manifest.packageIdentity, path: manifest.path, packageKind: manifest.packageKind, packageLocation: manifest.packageLocation, diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index b4e849ae908..dccf7d41843 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -1001,7 +1001,7 @@ extension Workspace { try await self.loadPackageGraph( rootPath: rootPath, explicitProduct: explicitProduct, - traitConfiguration: nil, + traitConfiguration: .default, observabilityScope: observabilityScope ) } @@ -1010,7 +1010,7 @@ extension Workspace { package func loadPackageGraph( rootPath: AbsolutePath, explicitProduct: String? = nil, - traitConfiguration: TraitConfiguration? = nil, + traitConfiguration: TraitConfiguration = .default, observabilityScope: ObservabilityScope ) async throws -> ModulesGraph { try await self.loadPackageGraph( diff --git a/Sources/_InternalTestSupport/ManifestExtensions.swift b/Sources/_InternalTestSupport/ManifestExtensions.swift index 90034880eaa..7f65ad5cf2d 100644 --- a/Sources/_InternalTestSupport/ManifestExtensions.swift +++ b/Sources/_InternalTestSupport/ManifestExtensions.swift @@ -32,13 +32,14 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [.init(name: "defaults")], + traits: Set = [.init(name: "default")], pruneDependencies: Bool = false ) -> Manifest { Self.createManifest( displayName: displayName, path: path, packageKind: .root(path), + packageIdentity: .plain(displayName), packageLocation: path.pathString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -72,13 +73,14 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [.init(name: "defaults")], + traits: Set = [.init(name: "default")], pruneDependencies: Bool = false ) -> Manifest { Self.createManifest( displayName: displayName, path: path, packageKind: .fileSystem(path), + packageIdentity: .plain(displayName), packageLocation: path.pathString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -118,6 +120,7 @@ extension Manifest { displayName: displayName, path: path, packageKind: .localSourceControl(path), + packageIdentity: .plain(displayName), packageLocation: path.pathString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -158,6 +161,7 @@ extension Manifest { displayName: displayName, path: path, packageKind: .remoteSourceControl(url), + packageIdentity: .plain(displayName), packageLocation: url.absoluteString, defaultLocalization: defaultLocalization, platforms: platforms, @@ -197,6 +201,7 @@ extension Manifest { displayName: displayName, path: path, packageKind: .registry(identity), + packageIdentity: .plain(displayName), packageLocation: identity.description, defaultLocalization: defaultLocalization, platforms: platforms, @@ -218,6 +223,7 @@ extension Manifest { displayName: String, path: AbsolutePath = .root, packageKind: PackageReference.Kind, + packageIdentity: PackageIdentity, packageLocation: String? = nil, defaultLocalization: String? = nil, platforms: [PlatformDescription] = [], @@ -231,11 +237,12 @@ extension Manifest { dependencies: [PackageDependency] = [], products: [ProductDescription] = [], targets: [TargetDescription] = [], - traits: Set = [.init(name: "defaults")], + traits: Set = [.init(name: "default")], pruneDependencies: Bool = false ) -> Manifest { return Manifest( displayName: displayName, + packageIdentity: packageIdentity, path: path.basename == Manifest.filename ? path : path.appending(component: Manifest.filename), packageKind: packageKind, packageLocation: packageLocation ?? path.pathString, @@ -260,6 +267,7 @@ extension Manifest { public func with(location: String) -> Manifest { Manifest( displayName: self.displayName, + packageIdentity: self.packageIdentity, path: self.path, packageKind: self.packageKind, packageLocation: location, diff --git a/Sources/_InternalTestSupport/MockDependency.swift b/Sources/_InternalTestSupport/MockDependency.swift index f0e59a78070..1553decf02b 100644 --- a/Sources/_InternalTestSupport/MockDependency.swift +++ b/Sources/_InternalTestSupport/MockDependency.swift @@ -22,7 +22,7 @@ public struct MockDependency { public let deprecatedName: String? public let location: Location public let products: ProductFilter - package let traits: Set + public let traits: Set init( deprecatedName: String? = nil, @@ -49,7 +49,8 @@ public struct MockDependency { identity: identity, deprecatedName: self.deprecatedName, path: mappedPath, - productFilter: self.products + productFilter: self.products, + traits: self.traits ) case .localSourceControl(let path, let requirement): let absolutePath = baseURL.appending(path) @@ -63,7 +64,8 @@ public struct MockDependency { deprecatedName: self.deprecatedName, path: mappedPath, requirement: requirement, - productFilter: self.products + productFilter: self.products, + traits: self.traits ) case .remoteSourceControl(let url, let _requirement): let mappedLocation = identityResolver.mappedLocation(for: url.absoluteString) @@ -93,7 +95,8 @@ public struct MockDependency { deprecatedName: self.deprecatedName, url: mappedURL, requirement: _requirement, - productFilter: self.products + productFilter: self.products, + traits: self.traits ) } case .registry(let identity, let _requirement): @@ -121,43 +124,44 @@ public struct MockDependency { deprecatedName: self.deprecatedName, url: mappedURL, requirement: requirement, - productFilter: self.products + productFilter: self.products, + traits: self.traits ) } } } - public static func fileSystem(path: String, products: ProductFilter = .everything) -> MockDependency { - try! MockDependency(location: .fileSystem(path: RelativePath(validating: path)), products: products) + public static func fileSystem(path: String, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + try! MockDependency(location: .fileSystem(path: RelativePath(validating: path)), products: products, traits: traits) } - public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { - try! .sourceControl(path: RelativePath(validating: path), requirement: requirement, products: products) + public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + try! .sourceControl(path: RelativePath(validating: path), requirement: requirement, products: products, traits: traits) } - public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { - MockDependency(location: .localSourceControl(path: path, requirement: requirement), products: products) + public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + MockDependency(location: .localSourceControl(path: path, requirement: requirement), products: products, traits: traits) } - public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { - try! MockDependency(deprecatedName: name, location: .localSourceControl(path: RelativePath(validating: path), requirement: requirement), products: products) + public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + try! MockDependency(deprecatedName: name, location: .localSourceControl(path: RelativePath(validating: path), requirement: requirement), products: products, traits: traits) } - public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { - .sourceControl(url: SourceControlURL(url), requirement: requirement, products: products) + public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + .sourceControl(url: SourceControlURL(url), requirement: requirement, products: products, traits: traits) } - public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { - MockDependency(location: .remoteSourceControl(url: url, requirement: requirement), products: products) + public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + MockDependency(location: .remoteSourceControl(url: url, requirement: requirement), products: products, traits: traits) } - public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything) -> MockDependency { - .registry(identity: .plain(identity), requirement: requirement) + public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + .registry(identity: .plain(identity), requirement: requirement, traits: traits) } - public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything) -> MockDependency { - MockDependency(location: .registry(identity: identity, requirement: requirement), products: products) + public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything, traits: Set = []) -> MockDependency { + MockDependency(location: .registry(identity: identity, requirement: requirement), products: products, traits: traits) } public enum Location { diff --git a/Sources/_InternalTestSupport/MockPackage.swift b/Sources/_InternalTestSupport/MockPackage.swift index 06018c20b96..0185514e486 100644 --- a/Sources/_InternalTestSupport/MockPackage.swift +++ b/Sources/_InternalTestSupport/MockPackage.swift @@ -35,7 +35,7 @@ public struct MockPackage { targets: [MockTarget], products: [MockProduct] = [], dependencies: [MockDependency] = [], - traits: Set = [.init(name: "defaults")], + traits: Set = [.init(name: "default")], versions: [String?] = [], revisionProvider: ((String) -> String)? = nil, toolsVersion: ToolsVersion? = nil @@ -60,7 +60,7 @@ public struct MockPackage { targets: [MockTarget], products: [MockProduct], dependencies: [MockDependency] = [], - traits: Set = [.init(name: "defaults")], + traits: Set = [.init(name: "default")], versions: [String?] = [], revisionProvider: ((String) -> String)? = nil, toolsVersion: ToolsVersion? = nil @@ -86,7 +86,7 @@ public struct MockPackage { targets: [MockTarget], products: [MockProduct], dependencies: [MockDependency] = [], - traits: Set = [.init(name: "defaults")], + traits: Set = [.init(name: "default")], versions: [String?] = [], revisionProvider: ((String) -> String)? = nil, toolsVersion: ToolsVersion? = nil diff --git a/Sources/_InternalTestSupport/MockPackageGraphs.swift b/Sources/_InternalTestSupport/MockPackageGraphs.swift index 259e7ef2231..7e60fa7c41d 100644 --- a/Sources/_InternalTestSupport/MockPackageGraphs.swift +++ b/Sources/_InternalTestSupport/MockPackageGraphs.swift @@ -126,7 +126,7 @@ package func macrosPackageGraph() throws -> MockPackageGraph { ), ], observabilityScope: observability.topScope, - traitConfiguration: nil + traitConfiguration: .default ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -284,7 +284,7 @@ package func macrosTestsPackageGraph() throws -> MockPackageGraph { ), ], observabilityScope: observability.topScope, - traitConfiguration: nil + traitConfiguration: .default ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -317,7 +317,7 @@ package func trivialPackageGraph() throws -> MockPackageGraph { ), ], observabilityScope: observability.topScope, - traitConfiguration: nil + traitConfiguration: .default ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -361,7 +361,7 @@ package func embeddedCxxInteropPackageGraph() throws -> MockPackageGraph { ), ], observabilityScope: observability.topScope, - traitConfiguration: nil + traitConfiguration: .default ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -426,7 +426,7 @@ package func toolsExplicitLibrariesGraph(linkage: ProductType.LibraryType) throw ), ], observabilityScope: observability.topScope, - traitConfiguration: nil + traitConfiguration: .default ) XCTAssertNoDiagnostics(observability.diagnostics) diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index 3349eb60f91..b7ce26427a2 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -94,7 +94,7 @@ public final class MockWorkspace { public var sourceControlToRegistryDependencyTransformation: WorkspaceConfiguration .SourceControlToRegistryDependencyTransformation var defaultRegistry: Registry? - public let traitConfiguration: TraitConfiguration? + public let traitConfiguration: TraitConfiguration public let pruneDependencies: Bool public init( @@ -116,7 +116,7 @@ public final class MockWorkspace { .SourceControlToRegistryDependencyTransformation = .disabled, defaultRegistry: Registry? = .none, customHostTriple: Triple = hostTriple, - traitConfiguration: TraitConfiguration? = nil, + traitConfiguration: TraitConfiguration = .default, pruneDependencies: Bool = false ) async throws { try fileSystem.createMockToolchain() @@ -321,6 +321,7 @@ public final class MockWorkspace { displayName: package.name, path: packagePath, packageKind: packageKind, + packageIdentity: .plain(package.name), packageLocation: packageLocation, platforms: package.platforms, version: v, @@ -680,7 +681,7 @@ public final class MockWorkspace { packages: rootInput.packages, observabilityScope: observability.topScope ) - let root = PackageGraphRoot( + let root = try PackageGraphRoot( input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope @@ -947,7 +948,7 @@ public final class MockWorkspace { packages: rootInput.packages, observabilityScope: observability.topScope ) - let graphRoot = PackageGraphRoot( + let graphRoot = try PackageGraphRoot( input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope diff --git a/Sources/_InternalTestSupport/misc.swift b/Sources/_InternalTestSupport/misc.swift index baf6a186abf..45a3f4937e6 100644 --- a/Sources/_InternalTestSupport/misc.swift +++ b/Sources/_InternalTestSupport/misc.swift @@ -433,7 +433,7 @@ public func loadPackageGraph( useXCBuildFileRules: Bool = false, customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, observabilityScope: ObservabilityScope, - traitConfiguration: TraitConfiguration? + traitConfiguration: TraitConfiguration = .default ) throws -> ModulesGraph { try loadModulesGraph( identityResolver: identityResolver, diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index ce22ee20825..f1727ca4e28 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -405,7 +405,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { } } - let packageGraphRoot = PackageGraphRoot( + let packageGraphRoot = try PackageGraphRoot( input: .init(packages: [packagePath]), manifests: [packagePath: rootPackageManifest], observabilityScope: observabilityScope diff --git a/Tests/BuildTests/PluginInvocationTests.swift b/Tests/BuildTests/PluginInvocationTests.swift index cd864d057a1..84c4ac524c1 100644 --- a/Tests/BuildTests/PluginInvocationTests.swift +++ b/Tests/BuildTests/PluginInvocationTests.swift @@ -81,8 +81,7 @@ final class PluginInvocationTests: XCTestCase { ] ) ], - observabilityScope: observability.topScope, - traitConfiguration: nil + observabilityScope: observability.topScope ) // Check the basic integrity before running plugins. diff --git a/Tests/FunctionalTests/TraitTests.swift b/Tests/FunctionalTests/TraitTests.swift index b9bef2aaac5..51f890721c3 100644 --- a/Tests/FunctionalTests/TraitTests.swift +++ b/Tests/FunctionalTests/TraitTests.swift @@ -313,24 +313,23 @@ final class TraitTests: XCTestCase { } } - func testPackageDisablinDefaultsTrait_whenNoTraits() async throws { + func testPackageDisablingDefaultsTrait_whenNoTraits() async throws { try await fixture(name: "Traits") { fixturePath in - do { - let _ = try await executeSwiftRun( - fixturePath.appending("DisablingEmptyDefaultsExample"), - "DisablingEmptyDefaultsExample" - ) - } catch let error as SwiftPMError { - switch error { - case .packagePathNotFound: - throw error - case .executionFailure(_, _, let stderr): - let expectedErr = """ - error: Disabled default traits by package 'disablingemptydefaultsexample' on package 'Package11' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. - - """ - XCTAssertTrue(stderr.contains(expectedErr)) + await XCTAssertAsyncThrowsError(try await executeSwiftRun( + fixturePath.appending("DisablingEmptyDefaultsExample"), + "DisablingEmptyDefaultsExample" + )) { error in + guard case SwiftPMError.executionFailure(_, _, let stderr) = error else { + XCTFail() + return } + + let expectedErr = """ + error: Disabled default traits by package 'disablingemptydefaultsexample' on package 'Package11' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + + """ + XCTAssertTrue(stderr.contains(expectedErr)) + } } } diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index df39c33958d..060169aacb2 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -66,6 +66,7 @@ final class PackageGraphPerfTests: XCTestCasePerf { displayName: name, path: try AbsolutePath(validating: location).appending(component: Manifest.filename), packageKind: isRoot ? .root(try .init(validating: location)) : .localSourceControl(try .init(validating: location)), + packageIdentity: .plain(name), packageLocation: location, platforms: [], version: "1.0.0", diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index ac2f5e68469..e3b39afafa6 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -4463,6 +4463,7 @@ extension Manifest { displayName: self.displayName, path: self.path.parentDirectory, packageKind: self.packageKind, + packageIdentity: self.packageIdentity, packageLocation: self.packageLocation, toolsVersion: self.toolsVersion, dependencies: self.dependencies, @@ -4475,6 +4476,7 @@ extension Manifest { displayName: self.displayName, path: self.path.parentDirectory, packageKind: self.packageKind, + packageIdentity: self.packageIdentity, packageLocation: self.packageLocation, toolsVersion: self.toolsVersion, dependencies: dependencies, diff --git a/Tests/PackageGraphTests/PubGrubTests.swift b/Tests/PackageGraphTests/PubGrubTests.swift index eea4c038ee3..974db2432ac 100644 --- a/Tests/PackageGraphTests/PubGrubTests.swift +++ b/Tests/PackageGraphTests/PubGrubTests.swift @@ -316,7 +316,7 @@ final class PubGrubTests: XCTestCase { let deps = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])) ]) - let result = await resolver.solve(constraints: deps, traitConfiguration: nil) + let result = await resolver.solve(constraints: deps) switch result { case .failure(let error): @@ -1615,7 +1615,7 @@ final class PubGrubTests: XCTestCase { "foo": (.versionSet(v1Range), .specific(["foo"])), "bar": (.versionSet(v2Range), .specific(["bar"])), ]) - let result = await resolver.solve(constraints: dependencies, traitConfiguration: nil) + let result = await resolver.solve(constraints: dependencies) AssertResult(result, [ ("foo", .version("1.1.0")), @@ -1660,8 +1660,7 @@ final class PubGrubTests: XCTestCase { package: other ), overriddenPackages: [:], - root: .root(package: root), - traitConfiguration: nil + root: .root(package: root) ) XCTAssertEqual( result, diff --git a/Tests/PackageModelTests/ManifestTests.swift b/Tests/PackageModelTests/ManifestTests.swift index 818db0d1252..34a902cc470 100644 --- a/Tests/PackageModelTests/ManifestTests.swift +++ b/Tests/PackageModelTests/ManifestTests.swift @@ -165,7 +165,7 @@ class ManifestTests: XCTestCase { } } - func testEnabledTraits_WhenNoTraitsInManifest() throws { + func testIsTraitEnabled_WhenNoTraitsInManifest() throws { let products = try [ ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]), @@ -205,17 +205,154 @@ class ManifestTests: XCTestCase { for trait in traits.sorted(by: { $0.name < $1.name }) { XCTAssertThrowsError(try manifest.isTraitEnabled(trait, Set(traits.map(\.name)))) { error in XCTAssertEqual("\(error)", """ - Trait '"\( + Trait '\( trait .name - )"' is not declared by package 'Foo'. There are no available traits defined by this package. + )' is not declared by package 'Foo'. There are no available traits declared by this package. """) } } } } - func testEnabledTraits_WhenNoDefaultTraitsAndNoConfig() throws { + func testIsTraitEnabled_WhenInvalidTraitQueried() throws { + let products = try [ + ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), + ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]), + ] + + let targets = try [ + TargetDescription(name: "Foo", dependencies: ["Bar"]), + TargetDescription(name: "Bar", dependencies: ["Baz"]), + TargetDescription(name: "Baz", dependencies: ["MyPlugin"]), + TargetDescription(name: "FooBar", dependencies: []), + TargetDescription(name: "MyPlugin", type: .plugin, pluginCapability: .buildTool), + ] + + let traits: Set = [ + TraitDescription(name: "Trait1", enabledTraits: ["Trait2"]), + TraitDescription(name: "Trait2"), + ] + + let dependencies: [PackageDependency] = [ + .localSourceControl(path: "/Bar", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/Baz", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/MyPlugin", requirement: .upToNextMajor(from: "1.0.0")), + ] + + do { + let manifest = Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v5_2, + dependencies: dependencies, + products: products, + targets: targets, + traits: traits, + pruneDependencies: true // Since all dependencies are used, this shouldn't affect the outcome. + ) + + // Test `isTraitEnabled` when the trait we're querying for does not exist. + XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExist"), nil)) { error in + XCTAssertEqual("\(error)", """ + Trait 'IDontExist' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + """) + } + + // Test `isTraitEnabled` when the set of enabled traits contains a trait that isn't defined in the package. + XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "Trait1"), ["IDontExist"])) { error in + XCTAssertEqual("\(error)", """ + Trait 'IDontExist' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + """) + } + + // Test `isTraitEnabled` when the set of enabled traits contains a trait that isn't defined in the package, and the queried trait is the same non-existant trait. + XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExist"), ["IDontExist"])) { error in + XCTAssertEqual("\(error)", """ + Trait 'IDontExist' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + """) + } + + // Test `isTraitEnabled` when the set of enabled traits contains a trait that isn't defined in the package, and the queried trait is another non-existant trait. + XCTAssertThrowsError(try manifest.isTraitEnabled(.init(stringLiteral: "IDontExistPart2"), ["IDontExist"])) { error in + XCTAssertEqual("\(error)", """ + Trait 'IDontExistPart2' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2. + """) + } + + } + } + + func testEnabledTraits_WhenTraitsNotSupported() throws { + let products = try [ + ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), + ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]), + ] + + let targets = try [ + TargetDescription(name: "Foo", dependencies: ["Bar"]), + TargetDescription(name: "Bar", dependencies: ["Baz"]), + TargetDescription(name: "Baz", dependencies: ["MyPlugin"]), + TargetDescription(name: "FooBar", dependencies: []), + TargetDescription(name: "MyPlugin", type: .plugin, pluginCapability: .buildTool), + ] + + let dependencies: [PackageDependency] = [ + .localSourceControl(path: "/Bar", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/Baz", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/MyPlugin", requirement: .upToNextMajor(from: "1.0.0")), + ] + + do { + let manifest = Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v5_2, + dependencies: dependencies, + products: products, + targets: targets, + traits: [], + pruneDependencies: true // Since all dependencies are used, this shouldn't affect the outcome. + ) + + // Enabled Traits when passed a TraitConfiguration: + + // When passed .disableAllTraits configuration + XCTAssertThrowsError(try manifest.enabledTraits(using: .disableAllTraits)) { error in + XCTAssertEqual("\(error)", """ + Disabled default traits on package 'Foo' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + """) + } + + // When passed .enableAllTraits configuration + XCTAssertThrowsError(try manifest.enabledTraits(using: .enabledTraits(["Trait1"]))) { error in + XCTAssertEqual("\(error)", """ + Traits [Trait1] have been enabled on package 'Foo' that declares no traits. + """) + } + + XCTAssertNoThrow(try manifest.enabledTraits(using: .enableAllTraits)) + XCTAssertNoThrow(try manifest.enabledTraits(using: .default)) + + // Enabled Traits when passed explicitly enabled traits list: + + // If given a parent package, and the enabled traits being passed don't exist: + XCTAssertThrowsError(try manifest.enabledTraits(using: ["Trait1"], "Qux")) { error in + XCTAssertEqual("\(error)", """ + Package 'Qux' enables traits [Trait1] on package 'Foo' that declares no traits. + """) + } + + // If given a parent package, and the default traits are disabled: + XCTAssertThrowsError(try manifest.enabledTraits(using: [], "Qux")) { error in + XCTAssertEqual("\(error)", """ + Disabled default traits by package 'Qux' on package 'Foo' that declares no traits. This is prohibited to allow packages to adopt traits initially without causing an API break. + """) + } + } + } + + func testIsTraitEnabled_WhenNoDefaultTraitsAndNoConfig() throws { let dependencies: [PackageDependency] = [ .localSourceControl(path: "/Baz", requirement: .upToNextMajor(from: "1.0.0")), .localSourceControl(path: "/Buzz", requirement: .upToNextMajor(from: "1.0.0")), @@ -292,7 +429,7 @@ class ManifestTests: XCTestCase { } } - func testEnabledTraits_WhenDefaultTraitsAndNoTraitConfig() throws { + func testIsTraitEnabled_WhenDefaultTraitsAndNoTraitConfig() throws { let products = try [ ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]), @@ -328,7 +465,52 @@ class ManifestTests: XCTestCase { } } - func testCalculateAllEnabledTraits_WithOnlyDefaultTraitsEnabled() throws { + func testEnabledTraits_WithAllTraitsDisabled() throws { + let products = try [ + ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), + ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]), + ] + + let targets = try [ + TargetDescription(name: "Foo", dependencies: ["Bar"]), + TargetDescription(name: "Bar", dependencies: ["Baz"]), + TargetDescription(name: "Baz", dependencies: ["MyPlugin"]), + TargetDescription(name: "FooBar", dependencies: []), + TargetDescription(name: "MyPlugin", type: .plugin, pluginCapability: .buildTool), + ] + + let dependencies: [PackageDependency] = [ + .localSourceControl(path: "/Bar", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/Baz", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/MyPlugin", requirement: .upToNextMajor(from: "1.0.0")), + ] + + let traits: Set = [ + TraitDescription(name: "Trait1", enabledTraits: ["Trait2"]), + TraitDescription(name: "Trait2"), + TraitDescription(name: "Trait3") + ] + + do { + let manifest = Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v5_2, + dependencies: dependencies, + products: products, + targets: targets, + traits: traits, + pruneDependencies: true // Since all dependencies are used, this shouldn't affect the outcome. + ) + + XCTAssertNoThrow(try { + let enabledTraits = try XCTUnwrap(manifest.enabledTraits(using: .disableAllTraits)) + XCTAssertEqual(enabledTraits, []) + }()) + } + } + + func testEnabledTraits_WithOnlyDefaultTraitsEnabled() throws { let products = try [ ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), ] @@ -358,12 +540,12 @@ class ManifestTests: XCTestCase { // Calculate the enabled traits without an explicitly declared set of enabled traits. // This should default to fetching the default traits, if they exist (which in this test case // they do), and then will calculate the transitive set of traits that are enabled. - let allEnabledTraits = try manifest.enabledTraits(using: nil)?.sorted() + let allEnabledTraits = try XCTUnwrap(manifest.enabledTraits(using: .default)).sorted() XCTAssertEqual(allEnabledTraits, ["Trait1", "Trait2"]) } } - func testCalculateAllEnabledTraits_WithExplicitTraitsEnabled() throws { + func testEnabledTraits_WithExplicitTraitsEnabled() throws { let products = try [ ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), ] @@ -392,18 +574,18 @@ class ManifestTests: XCTestCase { // Calculate the enabled traits with an explicitly declared set of enabled traits. // This should override the default traits (since it isn't explicitly passed in here). - let allEnabledTraitsWithoutDefaults = try manifest.enabledTraits(using: ["Trait3"])?.sorted() + let allEnabledTraitsWithoutDefaults = try manifest.enabledTraits(using: .enabledTraits(["Trait3"]))?.sorted() XCTAssertEqual(allEnabledTraitsWithoutDefaults, ["Trait3"]) // Calculate the enabled traits with an explicitly declared set of enabled traits, // including the default traits. Since default traits are explicitly enabled in the // passed set of traits, this will be factored into the calculation. - let allEnabledTraitsWithDefaults = try manifest.enabledTraits(using: ["default", "Trait3"])?.sorted() + let allEnabledTraitsWithDefaults = try manifest.enabledTraits(using: .enabledTraits(["default", "Trait3"]))?.sorted() XCTAssertEqual(allEnabledTraitsWithDefaults, ["Trait1", "Trait2", "Trait3"]) } } - func testCalculateAllEnabledTraits_WithAllTraitsEnabled() throws { + func testEnabledTraits_WithAllTraitsEnabled() throws { let products = try [ ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]), ] @@ -431,7 +613,7 @@ class ManifestTests: XCTestCase { ) // Calculate the enabled traits with all traits enabled flag. - let allEnabledTraits = try manifest.enabledTraits(using: [], enableAllTraits: true)?.sorted() + let allEnabledTraits = try manifest.enabledTraits(using: .enableAllTraits)?.sorted() XCTAssertEqual(allEnabledTraits, ["Trait1", "Trait2", "Trait3"]) } } @@ -761,7 +943,7 @@ class ManifestTests: XCTestCase { // The list of required dependencies should remain the same, since all depenencies are being // used in the current manifest. - let calculatedDependencies = try manifest.dependenciesRequired(for: .everything, nil, enableAllTraits: true) + let calculatedDependencies = try manifest.dependenciesRequired(for: .everything, nil) XCTAssertEqual(calculatedDependencies.map(\.identity).sorted(), dependencies.map(\.identity).sorted()) } } @@ -846,8 +1028,7 @@ class ManifestTests: XCTestCase { let calculatedDependenciesWithAllTraitsEnabled = try manifest.dependenciesRequired( for: .everything, - [], - enableAllTraits: true + ["Trait1", "Trait2"] ) XCTAssertEqual( calculatedDependenciesWithAllTraitsEnabled.map(\.identity).sorted(), diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index a920599bdf3..cb99502be38 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -59,8 +59,7 @@ final class SourceKitLSPAPITests: XCTestCase { TargetDescription(name: "plugin", type: .plugin, pluginCapability: .buildTool) ]), ], - observabilityScope: observability.topScope, - traitConfiguration: nil + observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -147,8 +146,7 @@ final class SourceKitLSPAPITests: XCTestCase { ] ), ], - observabilityScope: observability.topScope, - traitConfiguration: nil + observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -219,8 +217,7 @@ final class SourceKitLSPAPITests: XCTestCase { ] ), ], - observabilityScope: observability.topScope, - traitConfiguration: nil + observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index 2d82cd0fc33..6126c040399 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -519,6 +519,7 @@ final class ManifestSourceGenerationTests: XCTestCase { displayName: "MyLibrary", path: packageDir.appending("Package.swift"), packageKind: .root("/tmp/MyLibrary"), + packageIdentity: .plain("MyLibrary"), packageLocation: packageDir.pathString, platforms: [], toolsVersion: .v5_5, diff --git a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift index cd6135c4964..4dce3f48fe4 100644 --- a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift +++ b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift @@ -280,6 +280,7 @@ final class RegistryPackageContainerTests: XCTestCase { displayName: packageIdentity.description, path: manifestPath, packageKind: packageKind, + packageIdentity: packageIdentity, packageLocation: packageLocation, platforms: [], toolsVersion: manifestToolsVersion diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index a2f06d441d1..4e41de29e86 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -2937,6 +2937,7 @@ final class WorkspaceTests: XCTestCase { displayName: manifest.displayName, path: manifest.path, packageKind: manifest.packageKind, + packageIdentity: manifest.packageIdentity, packageLocation: manifest.packageLocation, platforms: [], version: manifest.version, @@ -12710,6 +12711,7 @@ final class WorkspaceTests: XCTestCase { displayName: packageIdentity.description, path: manifestPath, packageKind: packageKind, + packageIdentity: packageIdentity, packageLocation: packageLocation, platforms: [], toolsVersion: manifestToolsVersion @@ -15415,6 +15417,7 @@ final class WorkspaceTests: XCTestCase { displayName: "Foo", path: packagePath.appending(component: Manifest.filename), packageKind: .registry("org.foo"), + packageIdentity: .plain("Foo"), packageLocation: "org.foo", toolsVersion: .current, products: [ @@ -16143,6 +16146,131 @@ final class WorkspaceTests: XCTestCase { } } + func testInvalidTrait_WhenParentPackageEnablesTraits() async throws { + try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) + + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Foo", + targets: [ + MockTarget( + name: "Foo", + dependencies: [ + .product( + name: "Baz", + package: "Baz", + condition: .init(traits: ["Trait1"]) + ), + ] + ), + MockTarget(name: "Bar", dependencies: ["Baz"]), + ], + products: [ + MockProduct(name: "Foo", modules: ["Foo", "Bar"]), + ], + dependencies: [ + .sourceControl(path: "./Baz", requirement: .upToNextMajor(from: "1.0.0"), traits: ["TraitNotFound"]), + ], + traits: [.init(name: "default", enabledTraits: ["Trait2"]), "Trait1", "Trait2"] + ), + ], + packages: [ + MockPackage( + name: "Baz", + targets: [ + MockTarget(name: "Baz"), + ], + products: [ + MockProduct(name: "Baz", modules: ["Baz"]), + ], + traits: ["TraitFound"], + versions: ["1.0.0", "1.5.0"] + ), + ] + ) + + let deps: [MockDependency] = [ + .sourceControl(path: "./Baz", requirement: .exact("1.0.0"), products: .specific(["Baz"]), traits: ["TraitFound"]), + ] + + try await workspace.checkPackageGraphFailure(roots: ["Foo"], deps: deps) { diagnostics in + testDiagnostics(diagnostics) { result in + result.check(diagnostic: .equal("Trait 'TraitNotFound' enabled by parent package 'foo' is not declared by package 'Baz'. The available traits declared by this package are: TraitFound."), severity: .error) + } + } + await workspace.checkManagedDependencies { result in + result.check(dependency: "baz", at: .checkout(.version("1.0.0"))) + } + } + + func testInvalidTraitConfiguration_ForRootPackage() async throws { + try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) + + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Foo", + targets: [ + MockTarget( + name: "Foo", + dependencies: [ + .product( + name: "Baz", + package: "Baz", + condition: .init(traits: ["Trait1"]) + ), + ] + ), + MockTarget(name: "Bar", dependencies: ["Baz"]), + ], + products: [ + MockProduct(name: "Foo", modules: ["Foo", "Bar"]), + ], + dependencies: [ + .sourceControl(path: "./Baz", requirement: .upToNextMajor(from: "1.0.0"), traits: ["TraitFound"]), + ], + traits: [.init(name: "default", enabledTraits: ["Trait2"]), "Trait1", "Trait2"] + ), + ], + packages: [ + MockPackage( + name: "Baz", + targets: [ + MockTarget(name: "Baz"), + ], + products: [ + MockProduct(name: "Baz", modules: ["Baz"]), + ], + traits: ["TraitFound"], + versions: ["1.0.0", "1.5.0"] + ), + ], + // Trait configuration containing trait that isn't defined in the root package. + traitConfiguration: .enabledTraits(["TraitNotFound"]), + ) + + let deps: [MockDependency] = [ + .sourceControl(path: "./Baz", requirement: .exact("1.0.0"), products: .specific(["Baz"]), traits: ["TraitFound"]), + ] + + try await workspace.checkPackageGraphFailure(roots: ["Foo"], deps: deps) { diagnostics in + testDiagnostics(diagnostics) { result in + result.check(diagnostic: .equal("Trait 'TraitNotFound' is not declared by package 'Foo'. The available traits declared by this package are: Trait1, Trait2, default."), severity: .error) + } + } + } + func makeRegistryClient( packageIdentity: PackageIdentity, packageVersion: Version, diff --git a/Tests/XCBuildSupportTests/PIFBuilderTests.swift b/Tests/XCBuildSupportTests/PIFBuilderTests.swift index 172d59f255b..cb31a9bc627 100644 --- a/Tests/XCBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/XCBuildSupportTests/PIFBuilderTests.swift @@ -133,6 +133,7 @@ final class PIFBuilderTests: XCTestCase { displayName: "Foo", path: "/Foo", packageKind: .root("/Foo"), + packageIdentity: .plain("Foo"), defaultLocalization: "fr", toolsVersion: .v5_2, dependencies: [ @@ -1926,6 +1927,7 @@ final class PIFBuilderTests: XCTestCase { displayName: "Bar", path: "/Bar", packageKind: .root("/Bar"), + packageIdentity: .plain("Bar"), toolsVersion: .v4_2, cLanguageStandard: "c11", swiftLanguageVersions: [.v4_2], @@ -2688,6 +2690,7 @@ final class PIFBuilderTests: XCTestCase { displayName: "Foo", path: "/Foo", packageKind: .root("/Foo"), + packageIdentity: .plain("Foo"), toolsVersion: .v5_3, targets: [ .init(name: "foo", dependencies: [ From bc72d824feab284279656a408303cc29b9604315 Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:55:22 -0400 Subject: [PATCH 60/99] Fix dup modmaps taking into account macro test targets. (#8524) In my previous attempt to fix the duplicate modulemaps from C library dependencies of macros/plugins and destination targets, I didn't take into account test targets depending on macros, i.e. macro tests. In these cases the modulemap flags do need to pass through to the tests. This was discovered in swift-foundation which couldn't find the _SwiftSyntaxCShims module map. Fixed this my moving the traversing link dependencies a layer lower since we need access to the parent node during traversal and this is available in the successor methods. Cleaned up my previous attempt. --- Sources/Basics/Graph/GraphAlgorithms.swift | 47 ---------- .../ModuleBuildDescription.swift | 14 +-- Sources/Build/BuildPlan/BuildPlan+Swift.swift | 2 +- Sources/Build/BuildPlan/BuildPlan.swift | 88 +++++++++++++++++-- Tests/BuildTests/BuildPlanTests.swift | 25 +++++- .../BuildTests/BuildPlanTraversalTests.swift | 2 - 6 files changed, 105 insertions(+), 73 deletions(-) diff --git a/Sources/Basics/Graph/GraphAlgorithms.swift b/Sources/Basics/Graph/GraphAlgorithms.swift index deee4941985..8ccc6038cc0 100644 --- a/Sources/Basics/Graph/GraphAlgorithms.swift +++ b/Sources/Basics/Graph/GraphAlgorithms.swift @@ -131,50 +131,3 @@ public func depthFirstSearch( } } } - -/// Implements a pre-order depth-first search that traverses the whole graph and -/// doesn't distinguish between unique and duplicate nodes. The visitor can abort -/// a path as needed to prune the tree. -/// The method expects the graph to be acyclic but doesn't check that. -/// -/// - Parameters: -/// - nodes: The list of input nodes to sort. -/// - successors: A closure for fetching the successors of a particular node. -/// - onNext: A callback to indicate the node currently being processed -/// including its parent (if any) and its depth. Returns whether to -/// continue down the current path. -/// -/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges -/// reachable from the input nodes via the relation. -public enum DepthFirstContinue { - case `continue` - case abort -} - -public func depthFirstSearch( - _ nodes: [T], - successors: (T) throws -> [T], - visitNext: (T, _ parent: T?) throws -> DepthFirstContinue -) rethrows { - var stack = OrderedSet>() - - for node in nodes { - precondition(stack.isEmpty) - stack.append(TraversalNode(parent: nil, curr: node)) - - while !stack.isEmpty { - let node = stack.removeLast() - - if try visitNext(node.curr, node.parent) == .continue { - for succ in try successors(node.curr) { - stack.append( - TraversalNode( - parent: node.curr, - curr: succ - ) - ) - } - } - } - } -} diff --git a/Sources/Build/BuildDescription/ModuleBuildDescription.swift b/Sources/Build/BuildDescription/ModuleBuildDescription.swift index 06c52486045..95f657f9b46 100644 --- a/Sources/Build/BuildDescription/ModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ModuleBuildDescription.swift @@ -187,30 +187,18 @@ extension ModuleBuildDescription { var dependencies: [Dependency] = [] plan.traverseDependencies(of: self) { product, _, description in dependencies.append(.product(product, description)) - return .continue } onModule: { module, _, description in dependencies.append(.module(module, description)) - return .continue } return dependencies } package func recursiveLinkDependencies(using plan: BuildPlan) -> [Dependency] { var dependencies: [Dependency] = [] - plan.traverseDependencies(of: self) { product, _, description in - guard product.type != .macro && product.type != .plugin else { - return .abort - } - + plan.traverseLinkDependencies(of: self) { product, _, description in dependencies.append(.product(product, description)) - return .continue } onModule: { module, _, description in - guard module.type != .macro && module.type != .plugin else { - return .abort - } - dependencies.append(.module(module, description)) - return .continue } return dependencies } diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index d04c86e6033..476b3e2543b 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -24,7 +24,7 @@ extension BuildPlan { func plan(swiftTarget: SwiftModuleBuildDescription) throws { // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target // builds against - for case .module(let dependency, let description) in swiftTarget.recursiveDependencies(using: self) { + for case .module(let dependency, let description) in swiftTarget.recursiveLinkDependencies(using: self) { switch dependency.underlying { case let underlyingTarget as ClangModule where underlyingTarget.type == .library: guard case let .clang(target)? = description else { diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index bf45b9b5263..7f41e6cccf5 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -1175,8 +1175,8 @@ extension BuildPlan { package func traverseDependencies( of description: ModuleBuildDescription, - onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> DepthFirstContinue, - onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> DepthFirstContinue + onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void, + onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void ) { var visited = Set() func successors( @@ -1217,16 +1217,92 @@ extension BuildPlan { case .package: [] } - } visitNext: { module, _ in + } onNext: { module, _ in switch module { case .package: - return .continue + break + + case .product(let product, let destination): + onProduct(product, destination, self.description(for: product, context: destination)) + + case .module(let module, let destination): + onModule(module, destination, self.description(for: module, context: destination)) + } + } + } + + // Only follow link time dependencies, i.e. skip dependencies on macros and plugins + // except for testTargets that depend on macros. + package func traverseLinkDependencies( + of description: ModuleBuildDescription, + onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void, + onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void + ) { + var visited = Set() + func successors( + for product: ResolvedProduct, + destination: Destination + ) -> [TraversalNode] { + product.modules.map { module in + TraversalNode(module: module, context: destination) + }.filter { + visited.insert($0).inserted + } + } + + func successors( + for parentModule: ResolvedModule, + destination: Destination + ) -> [TraversalNode] { + parentModule + .dependencies(satisfying: description.buildParameters.buildEnvironment) + .reduce(into: [TraversalNode]()) { partial, dependency in + switch dependency { + case .product(let product, _): + guard product.type != .plugin else { + return + } + + guard product.type != .macro || parentModule.type == .test else { + return + } + + partial.append(.init(product: product, context: destination)) + case .module(let childModule, _): + guard childModule.type != .plugin else { + return + } + + guard childModule.type != .macro || parentModule.type == .test else { + return + } + + partial.append(.init(module: childModule, context: destination)) + } + }.filter { + visited.insert($0).inserted + } + } + + depthFirstSearch(successors(for: description.module, destination: description.destination)) { + switch $0 { + case .module(let module, let destination): + successors(for: module, destination: destination) + case .product(let product, let destination): + successors(for: product, destination: destination) + case .package: + [] + } + } onNext: { module, _ in + switch module { + case .package: + break case .product(let product, let destination): - return onProduct(product, destination, self.description(for: product, context: destination)) + onProduct(product, destination, self.description(for: product, context: destination)) case .module(let module, let destination): - return onModule(module, destination, self.description(for: module, context: destination)) + onModule(module, destination, self.description(for: module, context: destination)) } } } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 5c9ac3c9ce9..ffd57a8cd5a 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -6928,14 +6928,16 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { "/LeakTest/Sources/CLib/Clib.c", "/LeakTest/Sources/MyMacro/MyMacro.swift", "/LeakTest/Sources/MyPluginTool/MyPluginTool.swift", - "/LeakTest/Plugins/MyPlugin/MyPlugin.swift", "/LeakTest/Sources/MyLib/MyLib.swift", + "/LeakTest/Plugins/MyPlugin/MyPlugin.swift", + "/LeakTest/Tests/MyMacroTests/MyMacroTests.swift", + "/LeakTest/Tests/MyMacro2Tests/MyMacro2Tests.swift", "/LeakLib/Sources/CLib2/include/Clib.h", "/LeakLib/Sources/CLib2/Clib.c", "/LeakLib/Sources/MyMacro2/MyMacro.swift", "/LeakLib/Sources/MyPluginTool2/MyPluginTool.swift", + "/LeakLib/Sources/MyLib2/MyLib.swift", "/LeakLib/Plugins/MyPlugin2/MyPlugin.swift", - "/LeakLib/Sources/MyLib2/MyLib.swift" ]) let graph = try loadModulesGraph(fileSystem: fs, manifests: [ @@ -6944,6 +6946,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { path: "/LeakLib", products: [ ProductDescription(name: "MyLib2", type: .library(.automatic), targets: ["MyLib2"]), + ProductDescription(name: "MyMacros2", type: .macro, targets: ["MyMacro2"]) ], targets: [ TargetDescription(name: "CLib2"), @@ -6969,6 +6972,11 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { dependencies: ["CLib", "MyMacro", .product(name: "MyLib2", package: "LeakLib")], pluginUsages: [.plugin(name: "MyPlugin", package: nil)] ), + TargetDescription(name: "MyMacroTests", dependencies: ["MyMacro"], type: .test), + TargetDescription( + name: "MyMacro2Tests", + dependencies: [.product(name: "MyMacros2", package: "LeakLib")], + type: .test), ] ) ], observabilityScope: observability.topScope) @@ -6982,8 +6990,17 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let myLib = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyLib" })).swift() - print(myLib.additionalFlags) - XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool/include")}), "flags shouldn't contain tools items") + XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool")}), "flags shouldn't contain tools items") + + // Make sure the tests do have the include path and the module map from the lib + let myMacroTests = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyMacroTests" })).swift() + let flags = myMacroTests.additionalFlags.joined(separator: " ") + XCTAssertMatch(flags, .regex("CLib[/\\\\]include")) + XCTAssertMatch(flags, .regex("CLib-tool.build[/\\\\]module.modulemap")) + let myMacro2Tests = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyMacro2Tests" })).swift() + let flags2 = myMacro2Tests.additionalFlags.joined(separator: " ") + XCTAssertMatch(flags2, .regex("CLib2[/\\\\]include")) + XCTAssertMatch(flags2, .regex("CLib2-tool.build[/\\\\]module.modulemap")) } func testDiagnosticsAreMentionedInOutputsFileMap() async throws { diff --git a/Tests/BuildTests/BuildPlanTraversalTests.swift b/Tests/BuildTests/BuildPlanTraversalTests.swift index ed469887d60..2b898cabd76 100644 --- a/Tests/BuildTests/BuildPlanTraversalTests.swift +++ b/Tests/BuildTests/BuildPlanTraversalTests.swift @@ -146,10 +146,8 @@ final class BuildPlanTraversalTests: XCTestCase { XCTAssertEqual(product.name, "SwiftSyntax") XCTAssertEqual(destination, .host) XCTAssertNil(description) - return .continue } onModule: { module, destination, description in moduleDependencies.append((module, destination, description)) - return .continue } XCTAssertEqual(moduleDependencies.count, 2) From b092abe87ce57c31a3113a83b41ec7d341c9c694 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Wed, 30 Apr 2025 15:08:23 -0400 Subject: [PATCH 61/99] Tests: Merge Windows helper function and update GH issues (#8569) Update the GitHub issue reference on some skipped tests, and also merge the skipIfOnWindowsBecause..() and XCTSkipOnWindows() helper functions to make aid with discoverability. Also, update the Git Tests to be skipped only in the self hosted pipelines, as that is the environment where the tests are hanging. --- .../XCTAssertHelpers.swift | 29 ++++++++++--- Sources/_InternalTestSupport/misc.swift | 13 +----- Tests/BasicsTests/AsyncProcessTests.swift | 10 ++--- Tests/BasicsTests/FileSystem/PathTests.swift | 34 +++++++-------- Tests/BasicsTests/FileSystem/VFSTests.swift | 4 +- Tests/BasicsTests/HTTPClientTests.swift | 2 +- Tests/BasicsTests/LegacyHTTPClientTests.swift | 2 +- .../Serialization/SerializedJSONTests.swift | 4 +- .../URLSessionHTTPClientTests.swift | 2 +- Tests/BuildTests/BuildPlanTests.swift | 10 ++--- .../BuildTests/BuildSystemDelegateTests.swift | 2 +- Tests/BuildTests/PluginsBuildPlanTests.swift | 2 +- Tests/BuildTests/PrepareForIndexTests.swift | 6 +-- Tests/CommandsTests/BuildCommandTests.swift | 1 + Tests/CommandsTests/RunCommandTests.swift | 2 +- Tests/CommandsTests/TestCommandTests.swift | 2 +- .../PackageGraphPerfTests.swift | 2 +- .../PackageGraphTests/ModulesGraphTests.swift | 2 +- .../PD_4_2_LoadingTests.swift | 2 +- .../PackageBuilderTests.swift | 2 +- .../PkgConfigParserTests.swift | 20 ++++----- .../PackageLoadingTests/PkgConfigTests.swift | 6 +-- Tests/QueryEngineTests/QueryEngineTests.swift | 2 +- .../GitRepositoryProviderTests.swift | 10 ++--- .../GitRepositoryTests.swift | 42 +++++++++---------- .../ManifestSourceGenerationTests.swift | 6 +-- .../RegistryPackageContainerTests.swift | 2 +- .../SourceControlPackageContainerTests.swift | 8 ++-- Tests/WorkspaceTests/WorkspaceTests.swift | 10 ++--- Utilities/README.md | 2 +- 30 files changed, 123 insertions(+), 118 deletions(-) diff --git a/Sources/_InternalTestSupport/XCTAssertHelpers.swift b/Sources/_InternalTestSupport/XCTAssertHelpers.swift index 1eb619ee1c5..83506d0463b 100644 --- a/Sources/_InternalTestSupport/XCTAssertHelpers.swift +++ b/Sources/_InternalTestSupport/XCTAssertHelpers.swift @@ -44,17 +44,34 @@ public func XCTAssertEqual (_ lhs:(T,U), _ rhs:(T,U), TSCTestSupport.XCTAssertEqual(lhs, rhs, file: file, line: line) } -public func XCTSkipIfCI(file: StaticString = #filePath, line: UInt = #line) throws { +public func XCTSkipIfPlatformCI(because reason: String? = nil, file: StaticString = #filePath, line: UInt = #line) throws { // TODO: is this actually the right variable now? if isInCiEnvironment { - throw XCTSkip("Skipping because the test is being run on CI", file: file, line: line) + let failureCause = reason ?? "Skipping because the test is being run on CI" + throw XCTSkip(failureCause, file: file, line: line) } } -public func XCTSkipIfWindowsCI(file: StaticString = #filePath, line: UInt = #line) throws { +public func XCTSkipIfselfHostedCI(because reason: String, file: StaticString = #filePath, line: UInt = #line) throws { + // TODO: is this actually the right variable now? + if isSelfHostedCiEnvironment { + throw XCTSkip(reason, file: file, line: line) + } +} + +public func XCTSkipOnWindows(because reason: String? = nil, skipPlatformCi: Bool = false, skipSelfHostedCI: Bool = false , file: StaticString = #filePath, line: UInt = #line) throws { #if os(Windows) - if ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil { - throw XCTSkip("Skipping because the test is being run on CI", file: file, line: line) + let failureCause: String + if let reason { + failureCause = " because \(reason.description)" + } else { + failureCause = "" + } + if (skipPlatformCi || skipSelfHostedCI) { + try XCTSkipIfPlatformCI(because: "Test is run in Platform CI. Skipping\(failureCause)", file: file, line: line) + try XCTSkipIfselfHostedCI(because: "Test is run in Self hosted CI. Skipping\(failureCause)", file: file, line: line) + } else { + throw XCTSkip("Skipping test\(failureCause)", file: file, line: line) } #endif } @@ -276,7 +293,7 @@ public struct CommandExecutionError: Error { /// - seealso: https://github.com/swiftlang/swift-package-manager/issues/8560 public func XCTSkipIfWorkingDirectoryUnsupported() throws { func unavailable() throws { - throw XCTSkip("Thread-safe process working directory support is unavailable on this platform.") + throw XCTSkip("https://github.com/swiftlang/swift-package-manager/issues/8560: Thread-safe process working directory support is unavailable on this platform.") } #if os(Linux) if FileManager.default.contents(atPath: "/etc/system-release").map({ String(decoding: $0, as: UTF8.self) == "Amazon Linux release 2 (Karoo)\n" }) ?? false { diff --git a/Sources/_InternalTestSupport/misc.swift b/Sources/_InternalTestSupport/misc.swift index 45a3f4937e6..73f5834d6f9 100644 --- a/Sources/_InternalTestSupport/misc.swift +++ b/Sources/_InternalTestSupport/misc.swift @@ -40,6 +40,7 @@ import enum TSCUtility.Git @_exported import enum TSCTestSupport.StringPattern public let isInCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil +public let isSelfHostedCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil /// Test helper utility for executing a block with a temporary directory. public func testWithTemporaryDirectory( @@ -288,18 +289,6 @@ public func executeSwiftBuild( return try await SwiftPM.Build.execute(args, packagePath: packagePath, env: env) } -public func skipOnWindowsAsTestCurrentlyFails(because reason: String? = nil) throws { - #if os(Windows) - let failureCause: String - if let reason { - failureCause = " because \(reason.description)" - } else { - failureCause = "" - } - throw XCTSkip("Skipping tests on windows\(failureCause)") - #endif -} - @discardableResult public func executeSwiftRun( _ packagePath: AbsolutePath?, diff --git a/Tests/BasicsTests/AsyncProcessTests.swift b/Tests/BasicsTests/AsyncProcessTests.swift index 6f27f3ca7ba..fd34a520c1f 100644 --- a/Tests/BasicsTests/AsyncProcessTests.swift +++ b/Tests/BasicsTests/AsyncProcessTests.swift @@ -144,7 +144,7 @@ final class AsyncProcessTests: XCTestCase { } func testFindExecutable() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: Assertion failure when trying to find ls executable") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: Assertion failure when trying to find ls executable") try testWithTemporaryDirectory { tmpdir in // This process should always work. @@ -426,8 +426,8 @@ final class AsyncProcessTests: XCTestCase { func testAsyncStream() async throws { // rdar://133548796 - try XCTSkipIfCI() - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: 'swift test' was hanging.") + try XCTSkipIfPlatformCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: 'swift test' was hanging.") let (stdoutStream, stdoutContinuation) = AsyncProcess.ReadableStream.makeStream() let (stderrStream, stderrContinuation) = AsyncProcess.ReadableStream.makeStream() @@ -484,8 +484,8 @@ final class AsyncProcessTests: XCTestCase { func testAsyncStreamHighLevelAPI() async throws { // rdar://133548796 - try XCTSkipIfCI() - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: 'swift test' was hanging.") + try XCTSkipIfPlatformCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8547: 'swift test' was hanging.") let result = try await AsyncProcess.popen( scriptName: "echo\(ProcessInfo.batSuffix)", // maps to 'processInputs/echo' script diff --git a/Tests/BasicsTests/FileSystem/PathTests.swift b/Tests/BasicsTests/FileSystem/PathTests.swift index 720697106a6..85679ebd3e1 100644 --- a/Tests/BasicsTests/FileSystem/PathTests.swift +++ b/Tests/BasicsTests/FileSystem/PathTests.swift @@ -12,7 +12,7 @@ import Basics import Foundation import XCTest -import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails() +import _InternalTestSupport // for XCTSkipOnWindows() #if os(Windows) private var windows: Bool { true } @@ -57,7 +57,7 @@ class PathTests: XCTestCase { } func testRepeatedPathSeparators() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "all assertions fail") + try XCTSkipOnWindows(because: "all assertions fail") XCTAssertEqual(AbsolutePath("/ab//cd//ef").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") XCTAssertEqual(AbsolutePath("/ab///cd//ef").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") @@ -66,7 +66,7 @@ class PathTests: XCTestCase { } func testTrailingPathSeparators() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "trailing path seperator is not removed from pathString") + try XCTSkipOnWindows(because: "trailing path seperator is not removed from pathString") XCTAssertEqual(AbsolutePath("/ab/cd/ef/").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") XCTAssertEqual(AbsolutePath("/ab/cd/ef//").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") @@ -75,7 +75,7 @@ class PathTests: XCTestCase { } func testDotPathComponents() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/ab/././cd//ef").pathString, "/ab/cd/ef") XCTAssertEqual(AbsolutePath("/ab/./cd//ef/.").pathString, "/ab/cd/ef") @@ -84,7 +84,7 @@ class PathTests: XCTestCase { } func testDotDotPathComponents() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/..").pathString, windows ? #"\"# : "/") XCTAssertEqual(AbsolutePath("/../../../../..").pathString, windows ? #"\"# : "/") @@ -102,7 +102,7 @@ class PathTests: XCTestCase { } func testCombinationsAndEdgeCases() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("///").pathString, windows ? #"\"# : "/") XCTAssertEqual(AbsolutePath("/./").pathString, windows ? #"\"# : "/") @@ -133,7 +133,7 @@ class PathTests: XCTestCase { } func testDirectoryNameExtraction() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/").dirname, windows ? #"\"# : "/") XCTAssertEqual(AbsolutePath("/a").dirname, windows ? #"\"# : "/") @@ -152,7 +152,7 @@ class PathTests: XCTestCase { } func testBaseNameExtraction() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/").basename, windows ? #"\"# : "/") XCTAssertEqual(AbsolutePath("/a").basename, "a") @@ -170,7 +170,7 @@ class PathTests: XCTestCase { } func testBaseNameWithoutExt() throws{ - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, windows ? #"\"# : "/") XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a") @@ -195,7 +195,7 @@ class PathTests: XCTestCase { } func testSuffixExtraction() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "expected nil is not the actual") + try XCTSkipOnWindows(because: "expected nil is not the actual") XCTAssertEqual(RelativePath("a").suffix, nil) XCTAssertEqual(RelativePath("a").extension, nil) @@ -222,7 +222,7 @@ class PathTests: XCTestCase { } func testParentDirectory() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/").parentDirectory, AbsolutePath("/")) XCTAssertEqual(AbsolutePath("/").parentDirectory.parentDirectory, AbsolutePath("/")) @@ -233,7 +233,7 @@ class PathTests: XCTestCase { @available(*, deprecated) func testConcatenation() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("")).pathString, windows ? #"\"# : "/") XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath(".")).pathString, windows ? #"\"# : "/") @@ -272,7 +272,7 @@ class PathTests: XCTestCase { } func testPathComponents() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/").components, ["/"]) XCTAssertEqual(AbsolutePath("/.").components, ["/"]) @@ -302,7 +302,7 @@ class PathTests: XCTestCase { } func testRelativePathFromAbsolutePaths() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/")), RelativePath(".")); XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")), RelativePath("a/b/c/d")); @@ -345,7 +345,7 @@ class PathTests: XCTestCase { } func testAbsolutePathValidation() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertNoThrow(try AbsolutePath(validating: "/a/b/c/d")) @@ -359,7 +359,7 @@ class PathTests: XCTestCase { } func testRelativePathValidation() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() XCTAssertNoThrow(try RelativePath(validating: "a/b/c/d")) @@ -374,7 +374,7 @@ class PathTests: XCTestCase { } func testCodable() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() struct Foo: Codable, Equatable { var path: AbsolutePath diff --git a/Tests/BasicsTests/FileSystem/VFSTests.swift b/Tests/BasicsTests/FileSystem/VFSTests.swift index 4cb98615ef4..867c7078ae2 100644 --- a/Tests/BasicsTests/FileSystem/VFSTests.swift +++ b/Tests/BasicsTests/FileSystem/VFSTests.swift @@ -16,7 +16,7 @@ import XCTest import struct TSCBasic.ByteString -import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails +import _InternalTestSupport // for XCTSkipOnWindows func testWithTemporaryDirectory( function: StaticString = #function, @@ -38,7 +38,7 @@ func testWithTemporaryDirectory( class VFSTests: XCTestCase { func testLocalBasics() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() // tiny PE binary from: https://archive.is/w01DO let contents: [UInt8] = [ diff --git a/Tests/BasicsTests/HTTPClientTests.swift b/Tests/BasicsTests/HTTPClientTests.swift index de4434e2190..33fbacee638 100644 --- a/Tests/BasicsTests/HTTPClientTests.swift +++ b/Tests/BasicsTests/HTTPClientTests.swift @@ -226,7 +226,7 @@ final class HTTPClientTests: XCTestCase { } func testExponentialBackoff() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") let counter = SendableBox(0) let lastCall = SendableBox() diff --git a/Tests/BasicsTests/LegacyHTTPClientTests.swift b/Tests/BasicsTests/LegacyHTTPClientTests.swift index b4de3965733..b074c6ec9c8 100644 --- a/Tests/BasicsTests/LegacyHTTPClientTests.swift +++ b/Tests/BasicsTests/LegacyHTTPClientTests.swift @@ -350,7 +350,7 @@ final class LegacyHTTPClientTests: XCTestCase { } func testExponentialBackoff() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") let count = ThreadSafeBox(0) let lastCall = ThreadSafeBox() diff --git a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift index db3ab32e95d..858abce2cad 100644 --- a/Tests/BasicsTests/Serialization/SerializedJSONTests.swift +++ b/Tests/BasicsTests/Serialization/SerializedJSONTests.swift @@ -12,7 +12,7 @@ @testable import Basics import XCTest -import _InternalTestSupport // for skipOnWindowsAsTestCurrentlyFails +import _InternalTestSupport // for XCTSkipOnWindows final class SerializedJSONTests: XCTestCase { func testPathInterpolation() throws { @@ -34,7 +34,7 @@ final class SerializedJSONTests: XCTestCase { } func testPathInterpolationFailsOnWindows() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "Expectations are not met. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") + try XCTSkipOnWindows(because: "Expectations are not met. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") #if os(Windows) var path = try AbsolutePath(validating: #"\\?\C:\Users"#) diff --git a/Tests/BasicsTests/URLSessionHTTPClientTests.swift b/Tests/BasicsTests/URLSessionHTTPClientTests.swift index 799f0ed637a..6115952a569 100644 --- a/Tests/BasicsTests/URLSessionHTTPClientTests.swift +++ b/Tests/BasicsTests/URLSessionHTTPClientTests.swift @@ -356,7 +356,7 @@ final class URLSessionHTTPClientTest: XCTestCase { // https://github.com/apple/swift-corelibs-foundation/pull/2593 tries to address the latter part try XCTSkipIf(true, "test is only supported on macOS") #endif - try XCTSkipIfCI() + try XCTSkipIfPlatformCI() let netrcContent = "default login default password default" let netrc = try NetrcAuthorizationWrapper(underlying: NetrcParser.parse(netrcContent)) let authData = Data("default:default".utf8) diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index ffd57a8cd5a..e428bbff7e9 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -620,7 +620,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testPackageNameFlag() async throws { - try XCTSkipIfCI() // test is disabled because it isn't stable, see rdar://118239206 + try XCTSkipIfPlatformCI() // test is disabled because it isn't stable, see rdar://118239206 let isFlagSupportedInDriver = try DriverSupport.checkToolchainDriverFlags( flags: ["package-name"], toolchain: UserToolchain.default, @@ -2040,7 +2040,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func test_symbolGraphExtract_arguments() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() // ModuleGraph: // . @@ -4701,7 +4701,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testUserToolchainCompileFlags() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fs = InMemoryFileSystem( emptyFiles: @@ -4955,7 +4955,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testUserToolchainWithToolsetCompileFlags() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: "Path delimiters donw's work well on Windows") + try XCTSkipOnWindows(because: "Path delimiters donw's work well on Windows") let fileSystem = InMemoryFileSystem( emptyFiles: @@ -5125,7 +5125,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testUserToolchainWithSDKSearchPaths() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fileSystem = InMemoryFileSystem( emptyFiles: diff --git a/Tests/BuildTests/BuildSystemDelegateTests.swift b/Tests/BuildTests/BuildSystemDelegateTests.swift index ea844efc800..10a5d495add 100644 --- a/Tests/BuildTests/BuildSystemDelegateTests.swift +++ b/Tests/BuildTests/BuildSystemDelegateTests.swift @@ -30,7 +30,7 @@ final class BuildSystemDelegateTests: XCTestCase { } func testFilterNonFatalCodesignMessages() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8540: Package fails to build when the test is being executed") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8540: Package fails to build when the test is being executed") try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") // Note: we can re-use the `TestableExe` fixture here since we just need an executable. diff --git a/Tests/BuildTests/PluginsBuildPlanTests.swift b/Tests/BuildTests/PluginsBuildPlanTests.swift index a7e422e7271..93f0a69b5e2 100644 --- a/Tests/BuildTests/PluginsBuildPlanTests.swift +++ b/Tests/BuildTests/PluginsBuildPlanTests.swift @@ -18,7 +18,7 @@ import PackageModel final class PluginsBuildPlanTests: XCTestCase { func testBuildToolsDatabasePath() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try await fixture(name: "Miscellaneous/Plugins/MySourceGenPlugin") { fixturePath in let (stdout, _) = try await executeSwiftBuild(fixturePath) diff --git a/Tests/BuildTests/PrepareForIndexTests.swift b/Tests/BuildTests/PrepareForIndexTests.swift index 8c712e69b10..c986aa2b3f8 100644 --- a/Tests/BuildTests/PrepareForIndexTests.swift +++ b/Tests/BuildTests/PrepareForIndexTests.swift @@ -26,7 +26,7 @@ import struct PackageModel.TargetDescription class PrepareForIndexTests: XCTestCase { func testPrepare() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let (graph, fs, scope) = try macrosPackageGraph() @@ -96,7 +96,7 @@ class PrepareForIndexTests: XCTestCase { // enable-testing requires the non-exportable-decls, make sure they aren't skipped. func testEnableTesting() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fs = InMemoryFileSystem( emptyFiles: @@ -167,7 +167,7 @@ class PrepareForIndexTests: XCTestCase { } func testPrepareNoLazy() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let (graph, fs, scope) = try macrosPackageGraph() diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index a91357a4147..08356ea7b2f 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -860,6 +860,7 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { override func testParseableInterfaces() async throws { try XCTSkipIfWorkingDirectoryUnsupported() + try await fixture(name: "Miscellaneous/ParseableInterfaces") { fixturePath in do { let result = try await build(["--enable-parseable-module-interfaces"], packagePath: fixturePath) diff --git a/Tests/CommandsTests/RunCommandTests.swift b/Tests/CommandsTests/RunCommandTests.swift index 7516cc41308..7152038a16d 100644 --- a/Tests/CommandsTests/RunCommandTests.swift +++ b/Tests/CommandsTests/RunCommandTests.swift @@ -139,7 +139,7 @@ class RunCommandTestCase: CommandsBuildProviderTestCase { } func testSwiftRunSIGINT() throws { - try XCTSkipIfCI() + try XCTSkipIfPlatformCI() try fixture(name: "Miscellaneous/SwiftRun") { fixturePath in let mainFilePath = fixturePath.appending("main.swift") try localFileSystem.removeFileTree(mainFilePath) diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index f4fece83f0c..134b3f2d900 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -577,7 +577,7 @@ class TestCommandTestCase: CommandsBuildProviderTestCase { } func testFatalErrorDisplayedCorrectNumberOfTimesWhenSingleXCTestHasFatalErrorInBuildCompilation() async throws { - try XCTSkipIfCI() + try XCTSkipIfPlatformCI() // Test for GitHub Issue #6605 // GIVEN we have a Swift Package that has a fatalError building the tests let expected = 1 diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index 060169aacb2..7858462a026 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -166,7 +166,7 @@ final class PackageGraphPerfTests: XCTestCasePerf { } func testRecursiveDependencies() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() var resolvedTarget = ResolvedModule.mock(packageIdentity: "pkg", name: "t0") for i in 1..<1000 { diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index e3b39afafa6..384ae953c36 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -25,7 +25,7 @@ import struct TSCBasic.ByteString final class ModulesGraphTests: XCTestCase { func testBasic() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "Possibly related to: https://github.com/swiftlang/swift-package-manager/issues/8511") + try XCTSkipOnWindows(because: "Possibly related to: https://github.com/swiftlang/swift-package-manager/issues/8511") let fs = InMemoryFileSystem( emptyFiles: "/Foo/Sources/Foo/source.swift", diff --git a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift index 801a92bf543..f3dda8f8fac 100644 --- a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift @@ -680,7 +680,7 @@ final class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { // platforms just getting lucky? I'm feeling lucky. throw XCTSkip("Foundation Process.terminationStatus race condition (apple/swift-corelibs-foundation#4589") #else - try XCTSkipIfCI() + try XCTSkipIfPlatformCI() try await testWithTemporaryDirectory { path in let total = 100 diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index 6a71f526a8c..8a50b80c3ec 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -564,7 +564,7 @@ final class PackageBuilderTests: XCTestCase { } func testTestManifestSearch() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") + try XCTSkipOnWindows(because: "possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") let fs = InMemoryFileSystem(emptyFiles: "/pkg/foo.swift", diff --git a/Tests/PackageLoadingTests/PkgConfigParserTests.swift b/Tests/PackageLoadingTests/PkgConfigParserTests.swift index 295f5d95483..e62bc3ca0c8 100644 --- a/Tests/PackageLoadingTests/PkgConfigParserTests.swift +++ b/Tests/PackageLoadingTests/PkgConfigParserTests.swift @@ -19,7 +19,7 @@ import struct TSCBasic.ByteString final class PkgConfigParserTests: XCTestCase { func testCircularPCFile() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let observability = ObservabilitySystem.makeForTesting() @@ -36,7 +36,7 @@ final class PkgConfigParserTests: XCTestCase { } func testGTK3PCFile() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try! loadPCFile("gtk+-3.0.pc") { parser in XCTAssertEqual(parser.variables, [ @@ -58,7 +58,7 @@ final class PkgConfigParserTests: XCTestCase { } func testEmptyCFlags() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try! loadPCFile("empty_cflags.pc") { parser in XCTAssertEqual(parser.variables, [ @@ -74,7 +74,7 @@ final class PkgConfigParserTests: XCTestCase { } func testCFlagsCaseInsensitveKeys() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try! loadPCFile("case_insensitive.pc") { parser in XCTAssertEqual(parser.cFlags, ["-I/usr/local/include"]) @@ -82,7 +82,7 @@ final class PkgConfigParserTests: XCTestCase { } func testVariableinDependency() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try! loadPCFile("deps_variable.pc") { parser in XCTAssertEqual(parser.variables, [ @@ -108,7 +108,7 @@ final class PkgConfigParserTests: XCTestCase { } func testEscapedSpaces() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try! loadPCFile("escaped_spaces.pc") { parser in XCTAssertEqual(parser.variables, [ @@ -125,7 +125,7 @@ final class PkgConfigParserTests: XCTestCase { } func testDummyDependency() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() try loadPCFile("dummy_dependency.pc") { parser in XCTAssertEqual(parser.variables, [ @@ -213,7 +213,7 @@ final class PkgConfigParserTests: XCTestCase { } func testAbsolutePathDependency() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let libffiPath = "/usr/local/opt/libffi/lib/pkgconfig/libffi.pc" @@ -248,7 +248,7 @@ final class PkgConfigParserTests: XCTestCase { } func testUnevenQuotes() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() do { try loadPCFile("quotes_failure.pc") @@ -259,7 +259,7 @@ final class PkgConfigParserTests: XCTestCase { } func testSysrootDir() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() // sysroot should be prepended to all path variables, and should therefore appear in cflags and libs. try loadPCFile("gtk+-3.0.pc", sysrootDir: "/opt/sysroot/somewhere") { parser in diff --git a/Tests/PackageLoadingTests/PkgConfigTests.swift b/Tests/PackageLoadingTests/PkgConfigTests.swift index 466f507c2e3..4cc34895f87 100644 --- a/Tests/PackageLoadingTests/PkgConfigTests.swift +++ b/Tests/PackageLoadingTests/PkgConfigTests.swift @@ -87,7 +87,7 @@ class PkgConfigTests: XCTestCase { } func testEnvVar() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() // Pc file. try Environment.makeCustom(["PKG_CONFIG_PATH": inputsDir.pathString]) { @@ -152,7 +152,7 @@ class PkgConfigTests: XCTestCase { } func testExplicitPkgConfigDirectories() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() // Pc file. for result in try pkgConfigArgs( @@ -212,7 +212,7 @@ class PkgConfigTests: XCTestCase { } func testDependencies() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() // Use additionalSearchPaths instead of pkgConfigArgs to test handling // of search paths when loading dependencies. diff --git a/Tests/QueryEngineTests/QueryEngineTests.swift b/Tests/QueryEngineTests/QueryEngineTests.swift index c87f59820ed..def80727b93 100644 --- a/Tests/QueryEngineTests/QueryEngineTests.swift +++ b/Tests/QueryEngineTests/QueryEngineTests.swift @@ -100,7 +100,7 @@ private struct Expression: CachingQuery { final class QueryEngineTests: XCTestCase { func testFilePathHashing() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8541") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8541") let path = "/root" diff --git a/Tests/SourceControlTests/GitRepositoryProviderTests.swift b/Tests/SourceControlTests/GitRepositoryProviderTests.swift index 0c6be7e9200..d1cd1bfac99 100644 --- a/Tests/SourceControlTests/GitRepositoryProviderTests.swift +++ b/Tests/SourceControlTests/GitRepositoryProviderTests.swift @@ -19,7 +19,7 @@ class GitRepositoryProviderTests: XCTestCase { func testIsValidDirectory() throws { // Skipping all tests that call git on Windows. // We have a hang in CI when running in parallel. - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { sandbox in let provider = GitRepositoryProvider() @@ -45,7 +45,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testIsValidDirectoryThrowsPrintableError() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { temp in let provider = GitRepositoryProvider() let expectedErrorMessage = "not a git repository" @@ -60,7 +60,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testGitShellErrorIsPrintable() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) let stdOut = "An error from Git - stdout" let stdErr = "An error from Git - stderr" let arguments = ["git", "error"] @@ -89,7 +89,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testGitShellErrorEmptyStdOut() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) let stdErr = "An error from Git - stderr" let result = AsyncProcessResult( arguments: ["git", "error"], @@ -107,7 +107,7 @@ class GitRepositoryProviderTests: XCTestCase { } func testGitShellErrorEmptyStdErr() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) let stdOut = "An error from Git - stdout" let result = AsyncProcessResult( arguments: ["git", "error"], diff --git a/Tests/SourceControlTests/GitRepositoryTests.swift b/Tests/SourceControlTests/GitRepositoryTests.swift index 44cdc50abbe..511e2ff0194 100644 --- a/Tests/SourceControlTests/GitRepositoryTests.swift +++ b/Tests/SourceControlTests/GitRepositoryTests.swift @@ -64,7 +64,7 @@ class GitRepositoryTests: XCTestCase { func testProvider() throws { // Skipping all tests that call git on Windows. // We have a hang in CI when running in parallel. - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try! makeDirectories(testRepoPath) @@ -129,8 +129,8 @@ class GitRepositoryTests: XCTestCase { /// `Inputs`, which has known commit hashes. See the `construct.sh` script /// contained within it for more information. func testRawRepository() throws { - try skipOnWindowsAsTestCurrentlyFails(because: "https://github.com/swiftlang/swift-package-manager/issues/8385: test repository has non-portable file names") - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8385: test repository has non-portable file names") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Unarchive the static test repository. @@ -190,7 +190,7 @@ class GitRepositoryTests: XCTestCase { } func testSubmoduleRead() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try makeDirectories(testRepoPath) @@ -214,7 +214,7 @@ class GitRepositoryTests: XCTestCase { /// Test the Git file system view. func testGitFileView() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in let testRepoPath = path.appending("test-repo") try makeDirectories(testRepoPath) @@ -303,7 +303,7 @@ class GitRepositoryTests: XCTestCase { /// Test the handling of local checkouts. func testCheckouts() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a test repository. let testRepoPath = path.appending("test-repo") @@ -350,7 +350,7 @@ class GitRepositoryTests: XCTestCase { } func testFetch() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -390,7 +390,7 @@ class GitRepositoryTests: XCTestCase { } func testHasUnpushedCommits() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -427,7 +427,7 @@ class GitRepositoryTests: XCTestCase { } func testSetRemote() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -458,7 +458,7 @@ class GitRepositoryTests: XCTestCase { } func testUncommittedChanges() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -486,7 +486,7 @@ class GitRepositoryTests: XCTestCase { } func testBranchOperations() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -517,7 +517,7 @@ class GitRepositoryTests: XCTestCase { } func testRevisionOperations() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let repositoryPath = path.appending("test-repo") @@ -543,7 +543,7 @@ class GitRepositoryTests: XCTestCase { } func testCheckoutRevision() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -587,7 +587,7 @@ class GitRepositoryTests: XCTestCase { } func testSubmodules() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in let provider = GitRepositoryProvider() @@ -677,7 +677,7 @@ class GitRepositoryTests: XCTestCase { } func testAlternativeObjectStoreValidation() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test-repo") @@ -711,7 +711,7 @@ class GitRepositoryTests: XCTestCase { } func testAreIgnored() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test_repo") @@ -733,7 +733,7 @@ class GitRepositoryTests: XCTestCase { } func testAreIgnoredWithSpaceInRepoPath() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repo. let testRepoPath = path.appending("test repo") @@ -750,7 +750,7 @@ class GitRepositoryTests: XCTestCase { } func testMissingDefaultBranch() throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { path in // Create a repository. let testRepoPath = path.appending("test-repo") @@ -788,7 +788,7 @@ class GitRepositoryTests: XCTestCase { } func testValidDirectoryLocalRelativeOrigin() async throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") @@ -835,7 +835,7 @@ class GitRepositoryTests: XCTestCase { } func testValidDirectoryLocalAbsoluteOrigin() async throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") @@ -886,7 +886,7 @@ class GitRepositoryTests: XCTestCase { } func testValidDirectoryRemoteOrigin() async throws { - try XCTSkipIfWindowsCI() + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) try testWithTemporaryDirectory { tmpDir in // Create a repository. let packageDir = tmpDir.appending("SomePackage") diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index 6126c040399..a327711979d 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -233,7 +233,7 @@ final class ManifestSourceGenerationTests: XCTestCase { } func testAdvancedFeatures() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let manifestContents = """ // swift-tools-version:5.3 @@ -796,7 +796,7 @@ final class ManifestSourceGenerationTests: XCTestCase { } func testStrictMemorySafety() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: "compilation error: type 'SwiftSetting' has no member 'strictMemorySafety'") + try XCTSkipOnWindows(because: "compilation error: type 'SwiftSetting' has no member 'strictMemorySafety'") let manifestContents = """ // swift-tools-version:6.2 @@ -865,7 +865,7 @@ final class ManifestSourceGenerationTests: XCTestCase { } func testDefaultIsolation() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: "there are compilation errors") + try XCTSkipOnWindows(because: "there are compilation errors") let manifest = Manifest.createRootManifest( displayName: "pkg", diff --git a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift index 4dce3f48fe4..fcd6809d83b 100644 --- a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift +++ b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift @@ -26,7 +26,7 @@ import struct TSCUtility.Version final class RegistryPackageContainerTests: XCTestCase { override func setUpWithError() throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() } func testToolsVersionCompatibleVersions() async throws { diff --git a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift index 2f63a5976fd..6eb4dcd830a 100644 --- a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift +++ b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift @@ -125,7 +125,7 @@ private let v1Range: VersionSetSpecifier = .range("1.0.0" ..< "2.0.0") final class SourceControlPackageContainerTests: XCTestCase { func testVprefixVersions() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -172,7 +172,7 @@ final class SourceControlPackageContainerTests: XCTestCase { } func testVersions() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -270,7 +270,7 @@ final class SourceControlPackageContainerTests: XCTestCase { } func testPreReleaseVersions() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -319,7 +319,7 @@ final class SourceControlPackageContainerTests: XCTestCase { } func testSimultaneousVersions() async throws { - try skipOnWindowsAsTestCurrentlyFails() + try XCTSkipOnWindows() let fs = InMemoryFileSystem() try fs.createMockToolchain() diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 4e41de29e86..56ca4a5ff1f 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -38,7 +38,7 @@ final class WorkspaceTests: XCTestCase { // ] // let matches = windowsPassingTests.filter { $0 == self.invocation?.selector} // if matches.count == 0 { - // try skipOnWindowsAsTestCurrentlyFails() + // try XCTSkipOnWindows() // } // } @@ -775,7 +775,6 @@ final class WorkspaceTests: XCTestCase { } } - func testCanResolveWithIncompatiblePackages() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -2234,7 +2233,6 @@ final class WorkspaceTests: XCTestCase { } } - func testMinimumRequiredToolsVersionInDependencyResolution() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -3888,6 +3886,8 @@ final class WorkspaceTests: XCTestCase { } func testResolvedFileSchemeToolsVersion() async throws { + let fs = InMemoryFileSystem() + for pair in [ (ToolsVersion.v5_2, ToolsVersion.v5_2), (ToolsVersion.v5_6, ToolsVersion.v5_6), @@ -7783,7 +7783,6 @@ final class WorkspaceTests: XCTestCase { } } - func testDownloadedArtifactNotAnArchiveError() async throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -8039,7 +8038,7 @@ final class WorkspaceTests: XCTestCase { } func testArtifactChecksum() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #""" + try XCTSkipOnWindows(because: #""" threw error "\tmp\ws doesn't exist in file system" because there is an issue with InMemoryFileSystem readFileContents(...) on Windows """#) @@ -15514,7 +15513,6 @@ final class WorkspaceTests: XCTestCase { metadata: [String: RegistryReleaseMetadata], mirrors: DependencyMirrors? = nil ) async throws -> MockWorkspace { - // let sandbox = AbsolutePath.root.appending("swiftpm-tests-can-be-deleted/tmp/ws") let sandbox = AbsolutePath.root.appending(components: ["swiftpm-tests-can-be-deleted", "tmp", "ws"]) let fs = InMemoryFileSystem() diff --git a/Utilities/README.md b/Utilities/README.md index 4c43408998f..460864a5513 100644 --- a/Utilities/README.md +++ b/Utilities/README.md @@ -6,7 +6,7 @@ Here are some steps the worked running on Windows. 2. Launch a `Powershell.exe` session 3. Run the following in power shell to start a container running the nightly toolchain ``` - docker run --pull always --rm --interactive --tty swiftlang/swift:nightly-main-windowsservercore-1809 powershell.exe + docker run --pull always --rm --interactive --tty swiftlang/swift:nightly-6.1-windowsservercore-1809 powershell.exe ``` 4. When the container start, clone the "merged" PR to `C:\source` ``` From e84c0baa156ba29abf94715e8f61220985cf417e Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Wed, 30 Apr 2025 13:09:54 -0700 Subject: [PATCH 62/99] Some PIF delegate fixes for Swift Build (#8573) ### Motivation: Fixes a few issues introduced by #8441, where the PIF model transitioned from reference types to value types (i.e., the new SwiftBuild.ProjectModel API.) ### Modifications: Just a couple of changes in protocol PackagePIFBuilder.BuildDelegate, ensuring we can actually mutate the passed-in ProjectModel.Project value. --- Sources/SwiftBuildSupport/PIFBuilder.swift | 3 ++- Sources/SwiftBuildSupport/PackagePIFBuilder.swift | 5 +++-- .../PackagePIFProjectBuilder+Products.swift | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 667e943b0ba..c65e7ce2454 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -279,7 +279,7 @@ fileprivate final class PackagePIFBuilderDelegate: PackagePIFBuilder.BuildDelega [] } - func addCustomTargets(pifProject: SwiftBuild.ProjectModel.Project) throws -> [PackagePIFBuilder.ModuleOrProduct] { + func addCustomTargets(pifProject: inout SwiftBuild.ProjectModel.Project) throws -> [PackagePIFBuilder.ModuleOrProduct] { return [] } @@ -293,6 +293,7 @@ fileprivate final class PackagePIFBuilderDelegate: PackagePIFBuilder.BuildDelega func configureLibraryProduct( product: PackageModel.Product, + project: inout ProjectModel.Project, target: WritableKeyPath, additionalFiles: WritableKeyPath ) { diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index 001ff5c5437..b786a209001 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -124,7 +124,7 @@ public final class PackagePIFBuilder { func customSDKOptions(forPlatform: PackageModel.Platform) -> [String] /// Create additional custom PIF targets after all targets have been built. - func addCustomTargets(pifProject: ProjectModel.Project) throws -> [PackagePIFBuilder.ModuleOrProduct] + func addCustomTargets(pifProject: inout ProjectModel.Project) throws -> [PackagePIFBuilder.ModuleOrProduct] /// Should we suppresses the specific product dependency, updating the provided build settings if necessary? /// The specified product may be in the same package or a different one. @@ -141,6 +141,7 @@ public final class PackagePIFBuilder { /// Provides additional configuration and files for the specified library product. func configureLibraryProduct( product: PackageModel.Product, + project: inout ProjectModel.Project, target: WritableKeyPath, additionalFiles: WritableKeyPath ) @@ -474,7 +475,7 @@ public final class PackagePIFBuilder { } } - let customModulesAndProducts = try delegate.addCustomTargets(pifProject: projectBuilder.project) + let customModulesAndProducts = try delegate.addCustomTargets(pifProject: &projectBuilder.project) projectBuilder.builtModulesAndProducts.append(contentsOf: customModulesAndProducts) self._pifProject = projectBuilder.project diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index d9cb5d42b7f..a085a041439 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -703,6 +703,7 @@ extension PackagePIFProjectBuilder { // Additional configuration and files for this library product. pifBuilder.delegate.configureLibraryProduct( product: product.underlying, + project: &self.project, target: librayUmbrellaTargetKeyPath, additionalFiles: additionalFilesGroupKeyPath ) From 1e1fecba29966d1692197d3f02a8cafac49f9c5b Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Wed, 30 Apr 2025 13:54:19 -0700 Subject: [PATCH 63/99] stubs for initial DocC structure (#8572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Placeholder structure for DocC content to lay out an initial area to build into. ### Motivation: @bripeticca and I wanted to have something in place to merge into and a general plan of attack for shifting current content from the `/documentation` directory into DocC format. This provides a bit more structure over the bare docc catalog, and puts in placeholders for the CLI documentation as well. We'll be creating issues for each of the existing markdown files in `/documentation`, and one for each cluster of CLI commands (grouped by top-level subcommand) to track progress. ### Modifications: This drops in place some light DocC content, with no real depth, to set up an initial structure to merge content into. This isn't merging any existing documentation ### Result: The PackageManagerDocs target has more concrete content layout structure to it, with placeholders for CLI commands and where to expand to build out article sets based on existing documentation. Use `swift package --disable-sandbox preview-documentation --target PackageManagerDocs` with this PR to get a sense of the layout. Screenshot 2025-04-29 at 2 19 10 PM --- .../Assets/command-icon.svg | 4 ++ .../Assets/command-icon~dark.svg | 4 ++ .../Documentation.docc/GettingStarted.md | 11 ++++ .../Documentation.docc/IntroducingPackages.md | 11 ++++ .../Package/PackageAddDependency.md | 15 +++++ .../Package/PackageAddProduct.md | 15 +++++ .../Package/PackageAddTarget.md | 15 +++++ .../Package/PackageAddTargetDependency.md | 15 +++++ .../Package/PackageArchiveSource.md | 15 +++++ .../Package/PackageClean.md | 15 +++++ .../Package/PackageCompletionTool.md | 15 +++++ .../Package/PackageComputeChecksum.md | 15 +++++ .../Package/PackageConfigGetMirror.md | 15 +++++ .../Package/PackageConfigSetMirror.md | 15 +++++ .../Package/PackageConfigUnsetMirror.md | 15 +++++ .../Package/PackageDescribe.md | 15 +++++ .../PackageDiagnoseAPIBreakingChange.md | 15 +++++ .../Package/PackageDumpPackage.md | 15 +++++ .../Package/PackageDumpSymbolGraph.md | 15 +++++ .../Documentation.docc/Package/PackageEdit.md | 15 +++++ .../Documentation.docc/Package/PackageInit.md | 15 +++++ .../Package/PackagePlugin.md | 15 +++++ .../Package/PackagePurgeCache.md | 15 +++++ .../Package/PackageReset.md | 15 +++++ .../Package/PackageResolve.md | 15 +++++ .../Package/PackageShowDependencies.md | 15 +++++ .../Package/PackageShowExecutables.md | 17 +++++ .../Package/PackageToolsVersion.md | 15 +++++ .../Package/PackageUnedit.md | 15 +++++ .../Package/PackageUpdate.md | 15 +++++ .../PackageCollectionAdd.md | 15 +++++ .../PackageCollectionDescribe.md | 15 +++++ .../PackageCollectionList.md | 15 +++++ .../PackageCollectionRefresh.md | 15 +++++ .../PackageCollectionRemove.md | 15 +++++ .../PackageCollectionSearch.md | 15 +++++ .../PackageRegistry/PackageRegistryLogin.md | 15 +++++ .../PackageRegistry/PackageRegistryLogout.md | 15 +++++ .../PackageRegistry/PackageRegistryPublish.md | 15 +++++ .../PackageRegistry/PackageRegistrySet.md | 15 +++++ .../PackageRegistry/PackageRegistryUnset.md | 15 +++++ .../SDK/SDKConfigurationReset.md | 17 +++++ .../SDK/SDKConfigurationSet.md | 15 +++++ .../SDK/SDKConfigurationShow.md | 15 +++++ .../Documentation.docc/SDK/SDKConfigure.md | 15 +++++ .../Documentation.docc/SDK/SDKInstall.md | 17 +++++ .../Documentation.docc/SDK/SDKList.md | 15 +++++ .../Documentation.docc/SDK/SDKRemove.md | 15 +++++ .../Documentation.docc/SwiftBuild.md | 15 +++++ .../SwiftPackageCollectionCommands.md | 32 ++++++++++ .../SwiftPackageCommands.md | 63 +++++++++++++++++++ .../Documentation.docc/SwiftPackageManager.md | 31 ++++++++- .../SwiftPackageRegistryCommands.md | 24 +++++++ .../Documentation.docc/SwiftRepl.md | 15 +++++ .../Documentation.docc/SwiftRun.md | 15 +++++ .../Documentation.docc/SwiftSDKCommands.md | 33 ++++++++++ .../Documentation.docc/SwiftTest.md | 15 +++++ Sources/PackageManagerDocs/EmptyFile.swift | 4 ++ 58 files changed, 942 insertions(+), 1 deletion(-) create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon.svg create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon~dark.svg create mode 100644 Sources/PackageManagerDocs/Documentation.docc/GettingStarted.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/IntroducingPackages.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddDependency.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddProduct.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTarget.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTargetDependency.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageArchiveSource.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageClean.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageCompletionTool.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageComputeChecksum.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigGetMirror.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigSetMirror.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigUnsetMirror.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageDescribe.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageDiagnoseAPIBreakingChange.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpPackage.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpSymbolGraph.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageEdit.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageInit.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackagePlugin.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackagePurgeCache.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageReset.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageResolve.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowDependencies.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowExecutables.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageToolsVersion.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageUnedit.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/Package/PackageUpdate.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionAdd.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionDescribe.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionList.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRefresh.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRemove.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionSearch.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogin.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogout.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryPublish.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistrySet.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryUnset.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationReset.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationSet.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationShow.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKInstall.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKList.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SDK/SDKRemove.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftBuild.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCollectionCommands.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCommands.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftPackageRegistryCommands.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftRepl.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftRun.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftSDKCommands.md create mode 100644 Sources/PackageManagerDocs/Documentation.docc/SwiftTest.md diff --git a/Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon.svg b/Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon.svg new file mode 100644 index 00000000000..e41b7fa62d2 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon.svg @@ -0,0 +1,4 @@ + diff --git a/Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon~dark.svg b/Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon~dark.svg new file mode 100644 index 00000000000..1d4593095cd --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Assets/command-icon~dark.svg @@ -0,0 +1,4 @@ + diff --git a/Sources/PackageManagerDocs/Documentation.docc/GettingStarted.md b/Sources/PackageManagerDocs/Documentation.docc/GettingStarted.md new file mode 100644 index 00000000000..371e4b90be4 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/GettingStarted.md @@ -0,0 +1,11 @@ +# Getting Started + +Learn to create and use a Swift package. + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/IntroducingPackages.md b/Sources/PackageManagerDocs/Documentation.docc/IntroducingPackages.md new file mode 100644 index 00000000000..42c0319a18c --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/IntroducingPackages.md @@ -0,0 +1,11 @@ +# Introducing Packages + +Learn to create and use a Swift package. + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddDependency.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddDependency.md new file mode 100644 index 00000000000..03b69eab64d --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddDependency.md @@ -0,0 +1,15 @@ +# swift package add-dependency + +Add a package dependency to the manifest. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddProduct.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddProduct.md new file mode 100644 index 00000000000..4453e9c1317 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddProduct.md @@ -0,0 +1,15 @@ +# swift package add-product + +Add a new product to the manifest. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTarget.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTarget.md new file mode 100644 index 00000000000..f5b0fcf66d3 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTarget.md @@ -0,0 +1,15 @@ +# swift package add-target + +Add a new target to the manifest. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTargetDependency.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTargetDependency.md new file mode 100644 index 00000000000..e3a877f2fd8 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageAddTargetDependency.md @@ -0,0 +1,15 @@ +# swift package add-target-dependency + +Add a new target dependency to the manifest. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageArchiveSource.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageArchiveSource.md new file mode 100644 index 00000000000..8c505bd33c9 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageArchiveSource.md @@ -0,0 +1,15 @@ +# swift package archive-source + +Create a source archive for the package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageClean.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageClean.md new file mode 100644 index 00000000000..95767e3bc53 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageClean.md @@ -0,0 +1,15 @@ +# swift package clean + +Delete build artifacts. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageCompletionTool.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageCompletionTool.md new file mode 100644 index 00000000000..e1c7997d2fe --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageCompletionTool.md @@ -0,0 +1,15 @@ +# swift package completion-tool + +Completion command (for shell completions). + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageComputeChecksum.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageComputeChecksum.md new file mode 100644 index 00000000000..e34e7697f3e --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageComputeChecksum.md @@ -0,0 +1,15 @@ +# swift package compute-checksum + +Compute the checksum for a binary artifact. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigGetMirror.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigGetMirror.md new file mode 100644 index 00000000000..548f7b26819 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigGetMirror.md @@ -0,0 +1,15 @@ +# swift package config get-mirror + +Print mirror configuration for the given package dependency. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigSetMirror.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigSetMirror.md new file mode 100644 index 00000000000..eaa3133b217 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigSetMirror.md @@ -0,0 +1,15 @@ +# swift package config set-mirror + +Set a mirror for a dependency. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigUnsetMirror.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigUnsetMirror.md new file mode 100644 index 00000000000..feaa99f4100 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageConfigUnsetMirror.md @@ -0,0 +1,15 @@ +# swift package config unset-mirror + +Remove an existing mirror. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDescribe.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDescribe.md new file mode 100644 index 00000000000..6f2f9c0e1f8 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDescribe.md @@ -0,0 +1,15 @@ +# swift package describe + +Describe the current package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDiagnoseAPIBreakingChange.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDiagnoseAPIBreakingChange.md new file mode 100644 index 00000000000..02d25a98881 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDiagnoseAPIBreakingChange.md @@ -0,0 +1,15 @@ +# swift package diagnose-api-breaking-changes + +Diagnose API-breaking changes to Swift modules in a package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpPackage.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpPackage.md new file mode 100644 index 00000000000..1645afd68b1 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpPackage.md @@ -0,0 +1,15 @@ +# swift package dump-package + +Print parsed Package.swift as JSON. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpSymbolGraph.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpSymbolGraph.md new file mode 100644 index 00000000000..c0a99cf44ab --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageDumpSymbolGraph.md @@ -0,0 +1,15 @@ +# swift package dump-symbol-graph + +Dump Symbol Graph. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageEdit.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageEdit.md new file mode 100644 index 00000000000..1bec7f13ece --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageEdit.md @@ -0,0 +1,15 @@ +# swift package edit + +Put a package in editable mode. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageInit.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageInit.md new file mode 100644 index 00000000000..da4a4996d48 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageInit.md @@ -0,0 +1,15 @@ +# swift package init + +Initialize a new package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackagePlugin.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackagePlugin.md new file mode 100644 index 00000000000..ae6e91ef2b3 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackagePlugin.md @@ -0,0 +1,15 @@ +# swift package plugin + +Invoke a command plugin or perform other actions on command plugins. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackagePurgeCache.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackagePurgeCache.md new file mode 100644 index 00000000000..eecc665db22 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackagePurgeCache.md @@ -0,0 +1,15 @@ +# swift package purge-cache + +Purge the global repository cache. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageReset.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageReset.md new file mode 100644 index 00000000000..ebd68f9a894 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageReset.md @@ -0,0 +1,15 @@ +# swift package reset + +Reset the complete cache/build directory. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageResolve.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageResolve.md new file mode 100644 index 00000000000..02ebacd2def --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageResolve.md @@ -0,0 +1,15 @@ +# swift package resolve + +Resolve package dependencies. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowDependencies.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowDependencies.md new file mode 100644 index 00000000000..dcdd087de5d --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowDependencies.md @@ -0,0 +1,15 @@ +# swift package show-dependencies + +Print the resolved dependency graph. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowExecutables.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowExecutables.md new file mode 100644 index 00000000000..e9de1ba77d2 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageShowExecutables.md @@ -0,0 +1,17 @@ +# swift package show-executables + +List the available executables from this package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + + + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageToolsVersion.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageToolsVersion.md new file mode 100644 index 00000000000..8a6ad3fdff0 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageToolsVersion.md @@ -0,0 +1,15 @@ +# swift package tools-version + +Manipulate tools version of the current package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageUnedit.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageUnedit.md new file mode 100644 index 00000000000..fbdf0ef96f8 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageUnedit.md @@ -0,0 +1,15 @@ +# swift package unedit + +Remove a package from editable mode. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/Package/PackageUpdate.md b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageUpdate.md new file mode 100644 index 00000000000..63cd88f5aad --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/Package/PackageUpdate.md @@ -0,0 +1,15 @@ +# swift package update + +Update package dependencies. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionAdd.md b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionAdd.md new file mode 100644 index 00000000000..2f5bea6fd3d --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionAdd.md @@ -0,0 +1,15 @@ +# swift package-collection add + +Add a new collection. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionDescribe.md b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionDescribe.md new file mode 100644 index 00000000000..b35540c8c33 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionDescribe.md @@ -0,0 +1,15 @@ +# swift package-collection describe + +Get metadata for a collection or a package included in an imported collection. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionList.md b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionList.md new file mode 100644 index 00000000000..b066e7bdd0b --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionList.md @@ -0,0 +1,15 @@ +# swift package-collection list + +List configured collections. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRefresh.md b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRefresh.md new file mode 100644 index 00000000000..585b99e3e63 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRefresh.md @@ -0,0 +1,15 @@ +# swift package-collection refresh + +Refresh configured collections. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRemove.md b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRemove.md new file mode 100644 index 00000000000..a2a4a0876a7 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionRemove.md @@ -0,0 +1,15 @@ +# swift package-collection remove + +Remove a configured collection. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionSearch.md b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionSearch.md new file mode 100644 index 00000000000..fb8fa716709 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageCollections/PackageCollectionSearch.md @@ -0,0 +1,15 @@ +# swift package-collection search + +Search for packages by keywords or module names. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogin.md b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogin.md new file mode 100644 index 00000000000..a1661eb542d --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogin.md @@ -0,0 +1,15 @@ +# swift package-registry login + +Log in to a registry. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogout.md b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogout.md new file mode 100644 index 00000000000..01c2fdcbd2a --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryLogout.md @@ -0,0 +1,15 @@ +# swift package-registry logout + +Log out from a registry. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryPublish.md b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryPublish.md new file mode 100644 index 00000000000..b2c024d9c31 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryPublish.md @@ -0,0 +1,15 @@ +# swift package-registry publish + +Publish to a registry. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistrySet.md b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistrySet.md new file mode 100644 index 00000000000..4567a93b657 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistrySet.md @@ -0,0 +1,15 @@ +# swift package-registry set + +Set a custom registry. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryUnset.md b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryUnset.md new file mode 100644 index 00000000000..f9140a02077 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/PackageRegistry/PackageRegistryUnset.md @@ -0,0 +1,15 @@ +# swift package-registry unset + +Remove a configured registry. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationReset.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationReset.md new file mode 100644 index 00000000000..b15d6f522d9 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationReset.md @@ -0,0 +1,17 @@ +# swift sdk configuration reset + +Resets configuration properties currently applied to a given Swift SDK and target triple. + +If no specific property is specified, all of them are reset for the Swift SDK. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationSet.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationSet.md new file mode 100644 index 00000000000..5fefb8afcd6 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationSet.md @@ -0,0 +1,15 @@ +# swift sdk configuration reset + +Sets configuration options for installed Swift SDKs. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationShow.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationShow.md new file mode 100644 index 00000000000..9497d57d4f1 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigurationShow.md @@ -0,0 +1,15 @@ +# swift sdk configuration show + +Prints all configuration properties currently applied to a given Swift SDK and target triple. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md new file mode 100644 index 00000000000..8f863b4a74c --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKConfigure.md @@ -0,0 +1,15 @@ +# swift sdk configure + +Manages configuration options for installed Swift SDKs. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKInstall.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKInstall.md new file mode 100644 index 00000000000..0c3b353d241 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKInstall.md @@ -0,0 +1,17 @@ +# swift sdk install + +Installs a given Swift SDK bundle to a location discoverable by SwiftPM. + +If the artifact bundle is at a remote location, it's downloaded to local filesystem first. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKList.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKList.md new file mode 100644 index 00000000000..8097e653c20 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKList.md @@ -0,0 +1,15 @@ +# swift sdk list + +Print a list of IDs of available Swift SDKs available on the filesystem. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKRemove.md b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKRemove.md new file mode 100644 index 00000000000..d7b1b136ad0 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SDK/SDKRemove.md @@ -0,0 +1,15 @@ +# swift sdk remove + +Removes a previously installed Swift SDK bundle from the filesystem. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftBuild.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftBuild.md new file mode 100644 index 00000000000..e515631f42f --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftBuild.md @@ -0,0 +1,15 @@ +# swift build + +Compile the contents of your package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCollectionCommands.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCollectionCommands.md new file mode 100644 index 00000000000..ebcb37c7beb --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCollectionCommands.md @@ -0,0 +1,32 @@ +# swift package-collection + +Interact with package collections. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +Overview of package manager commands here... + + + +## Topics + +### Adding a package colleciton +- + +### Finding package collections +- + +### Updating package collection +- + +### Inspecting package collections +- +- + +### Removing a package collection +- + diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCommands.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCommands.md new file mode 100644 index 00000000000..584ac5250df --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageCommands.md @@ -0,0 +1,63 @@ +# swift package + +Subcommands to update and inspect your Swift package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +Overview of package manager commands here... + + + +## Topics + +### Creating packages +- + +### Updating and resolving dependencies +- +- + +### Editing packages +- +- +- +- +- +- + +### Using package manager plugins +- +- + + + +### Inspecting packages +- +- +- +- +- +- + +### Cleaning builds and caches +- +- +- + +### Archiving packages +- +- + +### Integrating Package Manager into your shell +- + +### Configuring Mirrors +- +- +- + + diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md index be1804726ae..2804a491c22 100644 --- a/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageManager.md @@ -8,7 +8,36 @@ Organize, manage, and edit Swift packages. ## Overview -The Swift Package Manager is a tool for managing distribution of source code, aimed at making it easy to share your code and reuse others’ code. The tool directly addresses the challenges of compiling and linking Swift packages, managing dependencies, versioning, and supporting flexible distribution and collaboration models. +The Swift Package Manager leets you share your code as a package, depend on and use other share packages, as well as build, test, document, and run your code. ## Topics +### Essentials + +- +- + + + + + + + + + + + + + + + +### Swift Commands + +- +- +- +- +- +- +- +- diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageRegistryCommands.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageRegistryCommands.md new file mode 100644 index 00000000000..886507efce4 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftPackageRegistryCommands.md @@ -0,0 +1,24 @@ +# swift package-registry + +Interact with package registry and manage related configuration. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +Overview of package manager commands here... + + + +## Topics + +### Adding and Removing Registries +- +- + +### Accessing Registries +- +- +- diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftRepl.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftRepl.md new file mode 100644 index 00000000000..63819a8cf13 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftRepl.md @@ -0,0 +1,15 @@ +# swift repl + +Run Swift code interactively with LLDB. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftRun.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftRun.md new file mode 100644 index 00000000000..bc9194f0f67 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftRun.md @@ -0,0 +1,15 @@ +# swift run + +Run an executable product in your package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftSDKCommands.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftSDKCommands.md new file mode 100644 index 00000000000..d37416b5f7e --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftSDKCommands.md @@ -0,0 +1,33 @@ +# swift sdk + +Perform operations on Swift SDKs. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +Overview of package manager commands here... + + + +## Topics + +### Installing an SDK +- + +### Listing SDKs +- + +### Removing an SDK +- + +### Configuring an SDK +- + +### Deprecated Commands +- +- +- + diff --git a/Sources/PackageManagerDocs/Documentation.docc/SwiftTest.md b/Sources/PackageManagerDocs/Documentation.docc/SwiftTest.md new file mode 100644 index 00000000000..9c98ae72ea0 --- /dev/null +++ b/Sources/PackageManagerDocs/Documentation.docc/SwiftTest.md @@ -0,0 +1,15 @@ +# swift test + +Build and run tests in your package. + +@Metadata { + @PageImage(purpose: icon, source: command-icon) +} + +## Overview + +overview content here.... + +### First Section + +First section content diff --git a/Sources/PackageManagerDocs/EmptyFile.swift b/Sources/PackageManagerDocs/EmptyFile.swift index e69de29bb2d..5a717f150a4 100644 --- a/Sources/PackageManagerDocs/EmptyFile.swift +++ b/Sources/PackageManagerDocs/EmptyFile.swift @@ -0,0 +1,4 @@ +// This is an empty placeholder swift file for the documentation target. + +// You can preview the documentation here by running the following command: +// swift package --disable-sandbox preview-documentation --target PackageManagerDocs From 0dd66e8bc5ccb7723c013f60180a11b203709031 Mon Sep 17 00:00:00 2001 From: Matt Seaman Date: Wed, 30 Apr 2025 14:28:09 -0700 Subject: [PATCH 64/99] Introduce new defines to enable #bundle in Swift Packages (#8556) Introduce Swift compilation conditions to support `#bundle` in Foundation. ### Motivation: This is the SwiftPM companion to `#bundle`. Pitch: https://forums.swift.org/t/pitch-introduce-bundle-macro/79288 Proposal: https://github.com/swiftlang/swift-foundation/pull/1251 Implementation: https://github.com/swiftlang/swift-foundation/pull/1274 ### Modifications: Define `SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE` when we'd like `#bundle` to call our generated `Bundle.module`. Define `SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE` when there are no resources and `#bundle` should complain about that. Leave them both undefined when we'd like `#bundle` to keep its default behavior of using the same bundle where the source code lives. ### Result: `#bundle` will point to the correct resource bundle when called from Swift Packages. --- .../SwiftModuleBuildDescription.swift | 6 ++ .../PackagePIFProjectBuilder+Modules.swift | 14 ++++ .../PackagePIFProjectBuilder+Products.swift | 12 ++++ Sources/XCBuildSupport/PIFBuilder.swift | 15 +++++ Tests/BuildTests/BuildPlanTests.swift | 16 ++++- .../CrossCompilationBuildPlanTests.swift | 2 +- .../XCBuildSupportTests/PIFBuilderTests.swift | 64 +++++++++++++++++-- 7 files changed, 123 insertions(+), 6 deletions(-) diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 89f2637ff0b..721153fb251 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -969,6 +969,12 @@ public final class SwiftModuleBuildDescription { break } + if bundlePath != nil { + compilationConditions += ["-DSWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + } else { + compilationConditions += ["-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + } + return compilationConditions } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index e766c3f4725..ae3de279e84 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -596,6 +596,20 @@ extension PackagePIFProjectBuilder { settings[.COREML_COMPILER_CONTAINER] = "swift-package" } + if sourceModule.usesSwift { + // Leave an explicit indicator regarding whether we are generating a Bundle.module accessor. + // This will be read by the #bundle macro defined in Foundation. + if !shouldGenerateBundleAccessor { + // No resources, so explicitly indicate that. + // #bundle will then produce an error about there being no resources. + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS].lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE") } + } else if !(resourceBundleName?.isEmpty ?? true) { + // We have an explicit resource bundle via Bundle.module. + // #bundle should call into that. + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS].lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE") } + } // else we won't set either of those and just let #bundle point to the same bundle as the source code. + } + if desiredModuleType == .macro { settings[.SWIFT_IMPLEMENTS_MACROS_FOR_MODULE_NAMES] = [sourceModule.c99name] } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index a085a041439..65a209fb8d2 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -245,6 +245,9 @@ extension PackagePIFProjectBuilder { if result.shouldGenerateBundleAccessor { settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" + + // Do not set `SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE` here since it is just going to point to the same bundle as code. + // #bundle can use its default implementation for that. } if result.shouldGenerateEmbedInCodeAccessor { settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "YES" @@ -264,6 +267,12 @@ extension PackagePIFProjectBuilder { if result.shouldGenerateBundleAccessor { settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" + + if mainModule.usesSwift { + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS].lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE") } + } + } else if mainModule.usesSwift { + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS].lazilyInitializeAndMutate(initialValue: ["$(inherited)"]) { $0.append("SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE") } } if result.shouldGenerateEmbedInCodeAccessor { settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "YES" @@ -305,6 +314,9 @@ extension PackagePIFProjectBuilder { settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" settings[.GENERATE_EMBED_IN_CODE_ACCESSORS] = "NO" + // Do not set `SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE` here since it is just going to point to the same bundle as code. + // #bundle can use its default implementation for that. + // If we did not create a resource bundle target, // we still need to add build tool commands for any generated files. addBuildToolCommands( diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 3d020614783..283379fedd1 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -488,12 +488,14 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.GENERATE_INFOPLIST_FILE] = "YES" } + var isSwiftModule = false if let clangTarget = mainTarget.underlying as? ClangModule { // Let the target itself find its own headers. settings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) settings[.GCC_C_LANGUAGE_STANDARD] = clangTarget.cLanguageStandard settings[.CLANG_CXX_LANGUAGE_STANDARD] = clangTarget.cxxLanguageStandard } else if let swiftTarget = mainTarget.underlying as? SwiftModule { + isSwiftModule = true try settings.addSwiftVersionSettings(target: swiftTarget, parameters: self.parameters) settings.addCommonSwiftSettings(package: self.package, target: mainTarget, parameters: self.parameters) } @@ -501,6 +503,12 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { if let resourceBundle = addResourceBundle(for: mainTarget, in: pifTarget) { settings[.PACKAGE_RESOURCE_BUNDLE_NAME] = resourceBundle settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" + + if isSwiftModule { + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS, default: ["$(inherited)"]].append("SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE") + } + } else if isSwiftModule { + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS, default: ["$(inherited)"]].append("SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE") } // For targets, we use the common build settings for both the "Debug" and the "Release" configurations (all @@ -654,6 +662,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { let moduleMapFileContents: String? let shouldImpartModuleMap: Bool + var isSwiftModule = false if let clangTarget = target.underlying as? ClangModule { // Let the target itself find its own headers. settings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) @@ -680,6 +689,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { shouldImpartModuleMap = false } } else if let swiftTarget = target.underlying as? SwiftModule { + isSwiftModule = true try settings.addSwiftVersionSettings(target: swiftTarget, parameters: self.parameters) // Generate ObjC compatibility header for Swift library targets. @@ -729,7 +739,12 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { if let resourceBundle = addResourceBundle(for: target, in: pifTarget) { settings[.PACKAGE_RESOURCE_BUNDLE_NAME] = resourceBundle settings[.GENERATE_RESOURCE_ACCESSORS] = "YES" + if isSwiftModule { + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS, default: ["$(inherited)"]].append("SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE") + } impartedSettings[.EMBED_PACKAGE_RESOURCE_BUNDLE_NAMES, default: ["$(inherited)"]].append(resourceBundle) + } else if isSwiftModule { + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS, default: ["$(inherited)"]].append("SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE") } // For targets, we use the common build settings for both the "Debug" and the "Release" configurations (all diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index e428bbff7e9..37163045e39 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -792,6 +792,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -811,6 +812,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -1224,6 +1226,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { "-O", .equal(self.j), "-DSWIFT_PACKAGE", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -1318,6 +1321,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { "-O", .equal(self.j), "-DSWIFT_PACKAGE", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -1863,6 +1867,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", @@ -2365,6 +2370,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -2387,6 +2393,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -2499,6 +2506,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { "-O", .equal(self.j), "-DSWIFT_PACKAGE", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -2864,6 +2872,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-Xcc", "-fmodule-map-file=\(Clibgit.appending(components: "module.modulemap"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, @@ -3166,6 +3175,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -3185,6 +3195,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { .equal(self.j), "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, "-swift-version", "4", @@ -3824,7 +3835,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { "-Onone", "-enable-testing", .equal(self.j), - "-DSWIFT_PACKAGE", "-DDEBUG", + "-DSWIFT_PACKAGE", "-DDEBUG", "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(Pkg.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", @@ -5888,6 +5899,9 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { XCTAssertEqual(try barTarget.objects.map(\.pathString), [ buildPath.appending(components: "Bar.build", "Bar.swift.o").pathString, ]) + + XCTAssertTrue(try fooTarget.compileArguments().contains(["-DSWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"])) + XCTAssertTrue(try barTarget.compileArguments().contains(["-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"])) } func testSwiftWASIBundleAccessor() async throws { diff --git a/Tests/BuildTests/CrossCompilationBuildPlanTests.swift b/Tests/BuildTests/CrossCompilationBuildPlanTests.swift index 698ad2c8183..27a5412487b 100644 --- a/Tests/BuildTests/CrossCompilationBuildPlanTests.swift +++ b/Tests/BuildTests/CrossCompilationBuildPlanTests.swift @@ -179,7 +179,7 @@ final class CrossCompilationBuildPlanTests: XCTestCase { exe, [ "-enable-batch-mode", "-serialize-diagnostics", "-Onone", "-enable-testing", - "-j3", "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-j3", "-DSWIFT_PACKAGE", "-DDEBUG", "-DSWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE", "-Xcc", "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", "-Xcc", "-I", "-Xcc", "\(pkgPath.appending(components: "Sources", "lib", "include"))", "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, diff --git a/Tests/XCBuildSupportTests/PIFBuilderTests.swift b/Tests/XCBuildSupportTests/PIFBuilderTests.swift index cb31a9bc627..36f280978be 100644 --- a/Tests/XCBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/XCBuildSupportTests/PIFBuilderTests.swift @@ -543,6 +543,10 @@ final class PIFBuilderTests: XCTestCase { settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"] ) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) } } @@ -568,6 +572,10 @@ final class PIFBuilderTests: XCTestCase { settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"] ) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) } } @@ -672,6 +680,10 @@ final class PIFBuilderTests: XCTestCase { settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"] ) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) } } @@ -691,6 +703,10 @@ final class PIFBuilderTests: XCTestCase { settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"] ) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) } } @@ -878,6 +894,10 @@ final class PIFBuilderTests: XCTestCase { "$(inherited)", "/toolchain/lib/swift/macosx", ]) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooTests") @@ -925,6 +945,10 @@ final class PIFBuilderTests: XCTestCase { "$(inherited)", "/toolchain/lib/swift/macosx", ]) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooTests") @@ -1396,6 +1420,10 @@ final class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "FooLib1-Swift.h") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooLib1_Module") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) } } @@ -1429,6 +1457,10 @@ final class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "FooLib1-Swift.h") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooLib1_Module") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE"] + ) } } @@ -2226,12 +2258,20 @@ final class PIFBuilderTests: XCTestCase { target.checkBuildConfiguration("Debug") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], "Foo_foo") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + ) } } - target.checkBuildConfiguration("Debug") { configuration in + target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], "Foo_foo") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + ) } } @@ -2289,7 +2329,7 @@ final class PIFBuilderTests: XCTestCase { } } - target.checkBuildConfiguration("Debug") { configuration in + target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], nil) } @@ -2307,12 +2347,20 @@ final class PIFBuilderTests: XCTestCase { target.checkBuildConfiguration("Debug") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], "Foo_FooLib") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + ) } } - target.checkBuildConfiguration("Debug") { configuration in + target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], "Foo_FooLib") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + ) } } @@ -2328,12 +2376,20 @@ final class PIFBuilderTests: XCTestCase { target.checkBuildConfiguration("Debug") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], "Foo_FooTests") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + ) } } - target.checkBuildConfiguration("Debug") { configuration in + target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in XCTAssertEqual(settings[.PACKAGE_RESOURCE_BUNDLE_NAME], "Foo_FooTests") + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_MODULE_RESOURCE_BUNDLE_AVAILABLE"] + ) } } From 072bf1aade0104c4dc32978e337e28e843daf7d8 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Wed, 30 Apr 2025 15:32:21 -0700 Subject: [PATCH 65/99] Add missing file to SwiftBuildSupport/CMakeLists.txt (#8581) ### Motivation: Fix a minor cmake build issue I introduced in pull request #8539. ### Modifications: Add missing Swift file to `Sources/SwiftBuildSupport/CMakeLists.txt`. --- Sources/SwiftBuildSupport/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftBuildSupport/CMakeLists.txt b/Sources/SwiftBuildSupport/CMakeLists.txt index 1a7b9eca3ad..ed516095346 100644 --- a/Sources/SwiftBuildSupport/CMakeLists.txt +++ b/Sources/SwiftBuildSupport/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(SwiftBuildSupport STATIC BuildSystem.swift + DotPIFSerializer.swift PackagePIFBuilder.swift PackagePIFBuilder+Helpers.swift PackagePIFBuilder+Plugins.swift From bdfd75415d3c0207afb109ad36dd46c948be8f47 Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Wed, 30 Apr 2025 16:50:08 -0700 Subject: [PATCH 66/99] Rename remaining occurrences of the old helper. (#8583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename remaining occurrences of the old helper. ### Motivation: My local test failed to build after #8569 with ``` …/Tests/WorkspaceTests/WorkspaceTests.swift:16148:13: error: cannot find 'skipOnWindowsAsTestCurrentlyFails' in scope 16146 | 16147 | func testInvalidTrait_WhenParentPackageEnablesTraits() async throws { 16148 | try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) | `- error: cannot find 'skipOnWindowsAsTestCurrentlyFails' in scope 16149 | 16150 | let sandbox = AbsolutePath("/tmp/ws/") ``` I checked, and it appears to be leftovers after #8569: ``` % rg skipOnWindowsAsTestCurrentlyFails Tests/WorkspaceTests/WorkspaceTests.swift 16148: try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) 16211: try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) ``` ### Modifications: Renamed `skipOnWindowsAsTestCurrentlyFails` to `XCTSkipOnWindows`. ### Result: Tests build and pass. --- Tests/WorkspaceTests/WorkspaceTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 56ca4a5ff1f..c79f5dee0f8 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -16145,7 +16145,7 @@ final class WorkspaceTests: XCTestCase { } func testInvalidTrait_WhenParentPackageEnablesTraits() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) + try XCTSkipOnWindows(because: #"\tmp\ws doesn't exist in file system"#) let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -16208,7 +16208,7 @@ final class WorkspaceTests: XCTestCase { } func testInvalidTraitConfiguration_ForRootPackage() async throws { - try skipOnWindowsAsTestCurrentlyFails(because: #"\tmp\ws doesn't exist in file system"#) + try XCTSkipOnWindows(because: #"\tmp\ws doesn't exist in file system"#) let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() From 8dbd83eee45368ee58d2e69890086ea83cb96865 Mon Sep 17 00:00:00 2001 From: Chris McGee <87777443+cmcgee1024@users.noreply.github.com> Date: Thu, 1 May 2025 17:27:01 -0400 Subject: [PATCH 67/99] Add swift version file to record the Swift toolchain to use (#8411) When working with SwiftPM, there is a particular version that development is expected to use so that all of the compilers, standard libraries, test frameworks, etc. are expected to be working. The `.swift-version` file establishes a standard way to record this information. Also, there is tooling available through `swiftly` to use that information to automatically use the correct toolchain in any git commit to set the developer's toolchain when they run and test SwiftPM functions at desk. Create the `.swift-version` file and set it to the current expected toolchain version for development: 6.0.3. Add documentation regarding this file in the contributor guide. Link to swiftly so that developers can install the tool, and enhance their development workflows. --- .swift-version | 1 + CONTRIBUTING.md | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 00000000000..358e78e6074 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +6.1.0 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a49877c8e8e..918664bee02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,7 +88,13 @@ $> swift --version Apple Swift version 5.3 ``` -Note: Alternatively use tools like [swiftly](https://www.swift.org/swiftly/documentation/swiftlydocs/) that help manage toolchains versions. +Alternatively, there are tools like [swiftly](https://github.com/swiftlang/swiftly) that can install and manage toolchains automatically. This repository has a file called `.swift-version` that will keep swiftly at the current recommended version of the toolchain for best results. The `swiftly install` command ensures that SwiftPM's in-use toolchain is installed on your system and ready for you to do your development work with the usual swift commands. + +```bash +swiftly install +swift build +swift test +``` ## Local Development @@ -322,14 +328,15 @@ Note there are several Linux and Swift versions options to choose from, e.g.: 2. Clone a working copy of your fork 3. Create a new branch 4. Make your code changes -5. Try to keep your changes (when possible) below 200 lines of code. -6. We use [SwiftFormat](https://www.github.com/nicklockwood/SwiftFormat) to enforce code style. Please install and run SwiftFormat before submitting your PR, ideally isolating formatting changes only to code changed for the original goal of the PR. This will keep the PR diff smaller. -7. Commit (include the Radar link or GitHub issue id in the commit message if possible and a description your changes). Try to have only 1 commit in your PR (but, of course, if you add changes that can be helpful to be kept aside from the previous commit, make a new commit for them). -8. Push the commit / branch to your fork -9. Make a PR from your fork / branch to `apple: main` -10. While creating your PR, make sure to follow the PR Template providing information about the motivation and highlighting the changes. -11. Reviewers are going to be automatically added to your PR -12. Pull requests will be merged by the maintainers after it passes CI testing and receives approval from one or more reviewers. Merge timing may be impacted by release schedule considerations. +5. If a particular version of the Swift toolchain is needed then update the `.swift-version` file to that version (or use `swiftly use` to update it). +6. Try to keep your changes (when possible) below 200 lines of code. +7. We use [SwiftFormat](https://www.github.com/nicklockwood/SwiftFormat) to enforce code style. Please install and run SwiftFormat before submitting your PR, ideally isolating formatting changes only to code changed for the original goal of the PR. This will keep the PR diff smaller. +8. Commit (include the Radar link or GitHub issue id in the commit message if possible and a description your changes). Try to have only 1 commit in your PR (but, of course, if you add changes that can be helpful to be kept aside from the previous commit, make a new commit for them). +9. Push the commit / branch to your fork +10. Make a PR from your fork / branch to `apple: main` +11. While creating your PR, make sure to follow the PR Template providing information about the motivation and highlighting the changes. +12. Reviewers are going to be automatically added to your PR +13. Pull requests will be merged by the maintainers after it passes CI testing and receives approval from one or more reviewers. Merge timing may be impacted by release schedule considerations. By submitting a pull request, you represent that you have the right to license your contribution to Apple and the community, and agree by submitting the patch From c5582fb96cf6e63e48f2b265e35e66908e13a3ff Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Thu, 1 May 2025 18:23:21 -0400 Subject: [PATCH 68/99] Tests: Conditionally skip the Archiver tests (#8601) The Archiver tests has a dependency on a system binary being available on the host. Update the archiver test to skip the test is the required executable is not available on the system. Fixes: #8586 Issue: rdar://150414402 Required for: https://github.com/swiftlang/swift/pull/80405 --- Sources/Basics/Archiver/TarArchiver.swift | 2 +- Sources/Basics/Archiver/ZipArchiver.swift | 22 ++++++++--- .../XCTAssertHelpers.swift | 25 ++++++++++++ .../Archiver/TarArchiverTests.swift | 6 +++ .../Archiver/UniversalArchiverTests.swift | 18 +++++++++ .../Archiver/ZipArchiverTests.swift | 14 +++++++ Tests/_InternalTestSupportTests/Misc.swift | 11 ++++++ .../XCTAssertHelpersTests.swift | 38 +++++++++++++++++++ 8 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 Tests/_InternalTestSupportTests/XCTAssertHelpersTests.swift diff --git a/Sources/Basics/Archiver/TarArchiver.swift b/Sources/Basics/Archiver/TarArchiver.swift index c0bf27cb788..2d1c7fce5b2 100644 --- a/Sources/Basics/Archiver/TarArchiver.swift +++ b/Sources/Basics/Archiver/TarArchiver.swift @@ -25,7 +25,7 @@ public struct TarArchiver: Archiver { private let cancellator: Cancellator /// The underlying command - private let tarCommand: String + internal let tarCommand: String /// Creates a `TarArchiver`. /// diff --git a/Sources/Basics/Archiver/ZipArchiver.swift b/Sources/Basics/Archiver/ZipArchiver.swift index cf1a8b7f38f..d4f4f1bb7d2 100644 --- a/Sources/Basics/Archiver/ZipArchiver.swift +++ b/Sources/Basics/Archiver/ZipArchiver.swift @@ -29,7 +29,14 @@ public struct ZipArchiver: Archiver, Cancellable { /// Absolute path to the Windows tar in the system folder #if os(Windows) - private let windowsTar: String + internal let windowsTar: String + #else + internal let unzip = "unzip" + internal let zip = "zip" + #endif + + #if os(FreeBSD) + internal let tar = "tar" #endif /// Creates a `ZipArchiver`. @@ -74,7 +81,9 @@ public struct ZipArchiver: Archiver, Cancellable { // It's part of system32 anyway so use the absolute path. let process = AsyncProcess(arguments: [windowsTar, "xf", archivePath.pathString, "-C", destinationPath.pathString]) #else - let process = AsyncProcess(arguments: ["unzip", archivePath.pathString, "-d", destinationPath.pathString]) + let process = AsyncProcess(arguments: [ + self.unzip, archivePath.pathString, "-d", destinationPath.pathString, + ]) #endif guard let registrationKey = self.cancellator.register(process) else { throw CancellationError.failedToRegisterProcess(process) @@ -113,7 +122,10 @@ public struct ZipArchiver: Archiver, Cancellable { // On FreeBSD, the unzip command is available in base but not the zip command. // Therefore; we use libarchive(bsdtar) to produce the ZIP archive instead. let process = AsyncProcess( - arguments: ["tar", "-c", "--format", "zip", "-f", destinationPath.pathString, directory.basename], + arguments: [ + self.tar, "-c", "--format", "zip", "-f", destinationPath.pathString, + directory.basename, + ], workingDirectory: directory.parentDirectory ) #else @@ -127,7 +139,7 @@ public struct ZipArchiver: Archiver, Cancellable { arguments: [ "/bin/sh", "-c", - "cd \(directory.parentDirectory.underlying.pathString) && zip -ry \(destinationPath.pathString) \(directory.basename)", + "cd \(directory.parentDirectory.underlying.pathString) && \(self.zip) -ry \(destinationPath.pathString) \(directory.basename)" ] ) #endif @@ -154,7 +166,7 @@ public struct ZipArchiver: Archiver, Cancellable { #if os(Windows) let process = AsyncProcess(arguments: [windowsTar, "tf", path.pathString]) #else - let process = AsyncProcess(arguments: ["unzip", "-t", path.pathString]) + let process = AsyncProcess(arguments: [self.unzip, "-t", path.pathString]) #endif guard let registrationKey = self.cancellator.register(process) else { throw CancellationError.failedToRegisterProcess(process) diff --git a/Sources/_InternalTestSupport/XCTAssertHelpers.swift b/Sources/_InternalTestSupport/XCTAssertHelpers.swift index 83506d0463b..33ad7eac963 100644 --- a/Sources/_InternalTestSupport/XCTAssertHelpers.swift +++ b/Sources/_InternalTestSupport/XCTAssertHelpers.swift @@ -76,6 +76,31 @@ public func XCTSkipOnWindows(because reason: String? = nil, skipPlatformCi: Bool #endif } +public func _requiresTools(_ executable: String) throws { + func getAsyncProcessArgs(_ executable: String) -> [String] { + #if os(Windows) + let args = ["cmd.exe", "/c", "where.exe", executable] + #else + let args = ["which", executable] + #endif + return args + } + try AsyncProcess.checkNonZeroExit(arguments: getAsyncProcessArgs(executable)) +} +public func XCTRequires( + executable: String, + file: StaticString = #filePath, + line: UInt = #line +) throws { + + do { + try _requiresTools(executable) + } catch (let AsyncProcessResult.Error.nonZeroExit(result)) { + throw XCTSkip( + "Skipping as tool \(executable) is not found in the path. (\(result.description))") + } +} + /// An `async`-friendly replacement for `XCTAssertThrowsError`. public func XCTAssertAsyncThrowsError( _ expression: @autoclosure () async throws -> T, diff --git a/Tests/BasicsTests/Archiver/TarArchiverTests.swift b/Tests/BasicsTests/Archiver/TarArchiverTests.swift index b330e4599c9..b4ab64025df 100644 --- a/Tests/BasicsTests/Archiver/TarArchiverTests.swift +++ b/Tests/BasicsTests/Archiver/TarArchiverTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +@testable import struct Basics.TarArchiver import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported import _InternalTestSupport import XCTest @@ -18,6 +19,11 @@ import XCTest import struct TSCBasic.FileSystemError final class TarArchiverTests: XCTestCase { + override func setUp() async throws { + let archiver = TarArchiver(fileSystem: localFileSystem) + try XCTRequires(executable: archiver.tarCommand) + } + func testSuccess() async throws { try await testWithTemporaryDirectory { tmpdir in let archiver = TarArchiver(fileSystem: localFileSystem) diff --git a/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift b/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift index 69e6bb76911..451dd35a651 100644 --- a/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift +++ b/Tests/BasicsTests/Archiver/UniversalArchiverTests.swift @@ -11,6 +11,8 @@ //===----------------------------------------------------------------------===// import Basics +@testable import struct Basics.TarArchiver +@testable import struct Basics.ZipArchiver import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported import _InternalTestSupport import XCTest @@ -18,6 +20,22 @@ import XCTest import struct TSCBasic.FileSystemError final class UniversalArchiverTests: XCTestCase { + override func setUp() async throws { + let zipAchiver = ZipArchiver(fileSystem: localFileSystem) + #if os(Windows) + try XCTRequires(executable: zipAchiver.windowsTar) + #else + try XCTRequires(executable: zipAchiver.unzip) + try XCTRequires(executable: zipAchiver.zip) + #endif + #if os(FreeBSD) + try XCTRequires(executable: zipAchiver.tar) + #endif + + let tarAchiver = TarArchiver(fileSystem: localFileSystem) + try XCTRequires(executable: tarAchiver.tarCommand) + } + func testSuccess() async throws { try await testWithTemporaryDirectory { tmpdir in let archiver = UniversalArchiver(localFileSystem) diff --git a/Tests/BasicsTests/Archiver/ZipArchiverTests.swift b/Tests/BasicsTests/Archiver/ZipArchiverTests.swift index 7b036813376..a9666ab17cc 100644 --- a/Tests/BasicsTests/Archiver/ZipArchiverTests.swift +++ b/Tests/BasicsTests/Archiver/ZipArchiverTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +@testable import struct Basics.ZipArchiver import _InternalTestSupport import XCTest import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported @@ -18,6 +19,19 @@ import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported import struct TSCBasic.FileSystemError final class ZipArchiverTests: XCTestCase { + override func setUp() async throws { + let archiver = ZipArchiver(fileSystem: localFileSystem) + #if os(Windows) + try XCTRequires(executable: archiver.windowsTar) + #else + try XCTRequires(executable: archiver.unzip) + try XCTRequires(executable: archiver.zip) + #endif + #if os(FreeBSD) + try XCTRequires(executable: archiver.tar) + #endif + } + func testZipArchiverSuccess() async throws { try await testWithTemporaryDirectory { tmpdir in let archiver = ZipArchiver(fileSystem: localFileSystem) diff --git a/Tests/_InternalTestSupportTests/Misc.swift b/Tests/_InternalTestSupportTests/Misc.swift index a88d8241128..1b44eaabe37 100644 --- a/Tests/_InternalTestSupportTests/Misc.swift +++ b/Tests/_InternalTestSupportTests/Misc.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024-2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// import SPMBuildCore import _InternalTestSupport import XCTest diff --git a/Tests/_InternalTestSupportTests/XCTAssertHelpersTests.swift b/Tests/_InternalTestSupportTests/XCTAssertHelpersTests.swift new file mode 100644 index 00000000000..0755356a898 --- /dev/null +++ b/Tests/_InternalTestSupportTests/XCTAssertHelpersTests.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import XCTest +import func _InternalTestSupport.XCTAssertThrows +import func _InternalTestSupport._requiresTools + +final class TestRequiresTool: XCTestCase { + func testErrorIsThrownIfExecutableIsNotFoundOnThePath() throws { + XCTAssertThrows( + try _requiresTools("doesNotExists") + ) { (error: AsyncProcessResult.Error) in + return true + } + } + + func testErrorIsNotThrownIfExecutableIsOnThePath() throws { + // Essentially call either "which which" or "where.exe where.exe" + #if os(Window) + let executable = "where.exe" + #else + let executable = "which" + #endif + XCTAssertNoThrow( + try _requiresTools(executable) + ) + } +} \ No newline at end of file From 620cbb9398455301d9692e1554b80dbffb1e3adf Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Thu, 1 May 2025 22:10:59 -0400 Subject: [PATCH 69/99] Tests: Enable some BuildTests/**/*.swift tests on Windows (#8512) - Enable the BuildPlanTests on Windows - Split a PrepareForIndexTests tests into two withthe passing, and failing on Windows - update/add a message on some skipped windows tests, with a reference to GitHub Issue Related to: #8433 Depends on: #8569 rdar://148248105 --- .../info.json | 4 +++ .../mytool-windows/mytool.bat | 21 +++++++++++ Tests/BuildTests/BuildPlanTests.swift | 36 +++++++++---------- Tests/BuildTests/PluginsBuildPlanTests.swift | 2 +- Tests/BuildTests/PrepareForIndexTests.swift | 32 ++++++++++++----- 5 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/mytool-windows/mytool.bat diff --git a/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/info.json b/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/info.json index 322cdaa8f4a..d64be1aaa42 100644 --- a/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/info.json +++ b/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/info.json @@ -12,6 +12,10 @@ { "path": "mytool-linux/mytool", "supportedTriples": ["x86_64-unknown-linux-gnu"] + }, + { + "path": "mytool-windows/mytool.bat", + "supportedTriples": ["x86_64-unknown-windows-msvc", "aarch64-unknown-windows-msvc"] } ] } diff --git a/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/mytool-windows/mytool.bat b/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/mytool-windows/mytool.bat new file mode 100644 index 00000000000..50f66dd2c73 --- /dev/null +++ b/Fixtures/Miscellaneous/Plugins/MyBinaryToolPlugin/Binaries/MyVendedSourceGenBuildTool.artifactbundle/mytool-windows/mytool.bat @@ -0,0 +1,21 @@ +@echo off + +set verbose=false +IF NOT "%1"=="" ( + IF "%1"=="--verbose" ( + SET verbose=true + SHIFT + ) +) + +set input=%1 +set output=%2 +shift +shift + + +if "%verbose%" == "true" ( + echo "[mytool-windows] '%input%' '%output%'" +) +@echo on +echo f | xcopy.exe /f "%input%" "%output%" \ No newline at end of file diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 37163045e39..a222ee9e8ad 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -2045,8 +2045,6 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func test_symbolGraphExtract_arguments() async throws { - try XCTSkipOnWindows() - // ModuleGraph: // . // ├── A (Swift) @@ -2115,33 +2113,38 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { // A do { + let expectedModuleMap = AbsolutePath("/path/to/build/\(triple)/debug/C.build/module.modulemap").pathString try XCTAssertMatchesSubSequences( result.moduleBuildDescription(for: "A").symbolGraphExtractArguments(), // Swift Module dependencies - ["-I", "/path/to/build/\(triple)/debug/Modules"], + ["-I", .equal(AbsolutePath("/path/to/build/\(triple)/debug/Modules").pathString)], // C Module dependencies - ["-Xcc", "-I", "-Xcc", "/Pkg/Sources/C/include"], - ["-Xcc", "-fmodule-map-file=/path/to/build/\(triple)/debug/C.build/module.modulemap"] + ["-Xcc", "-I", "-Xcc", .equal(AbsolutePath("/Pkg/Sources/C/include").pathString)], + ["-Xcc", "-fmodule-map-file=\(expectedModuleMap)"] ) } // D do { + let expectedBModuleMap = AbsolutePath("/path/to/build/\(triple)/debug/B.build/module.modulemap").pathString + let expectedCModuleMap = AbsolutePath("/path/to/build/\(triple)/debug/C.build/module.modulemap").pathString + let expectedDModuleMap = AbsolutePath("/path/to/build/\(triple)/debug/D.build/module.modulemap").pathString + let expectedModuleCache = AbsolutePath("/path/to/build/\(triple)/debug/ModuleCache").pathString try XCTAssertMatchesSubSequences( result.moduleBuildDescription(for: "D").symbolGraphExtractArguments(), // Self Module - ["-I", "/Pkg/Sources/D/include"], - ["-Xcc", "-fmodule-map-file=/path/to/build/\(triple)/debug/D.build/module.modulemap"], + ["-I", .equal(AbsolutePath("/Pkg/Sources/D/include").pathString)], + ["-Xcc", "-fmodule-map-file=\(expectedDModuleMap)"], // C Module dependencies - ["-Xcc", "-I", "-Xcc", "/Pkg/Sources/C/include"], - ["-Xcc", "-fmodule-map-file=/path/to/build/\(triple)/debug/C.build/module.modulemap"], + ["-Xcc", "-I", "-Xcc", .equal(AbsolutePath("/Pkg/Sources/C/include").pathString)], + ["-Xcc", "-fmodule-map-file=\(expectedCModuleMap)"], // General Args [ "-Xcc", "-fmodules", "-Xcc", "-fmodule-name=D", - "-Xcc", "-fmodules-cache-path=/path/to/build/\(triple)/debug/ModuleCache", + "-Xcc", "-fmodules-cache-path=\(expectedModuleCache)", ] ) @@ -2149,7 +2152,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { try XCTAssertMatchesSubSequences( result.moduleBuildDescription(for: "D").symbolGraphExtractArguments(), // Swift Module dependencies - ["-Xcc", "-fmodule-map-file=/path/to/build/\(triple)/debug/B.build/module.modulemap"] + ["-Xcc", "-fmodule-map-file=\(expectedBModuleMap)"] ) #endif } @@ -4712,8 +4715,6 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testUserToolchainCompileFlags() async throws { - try XCTSkipOnWindows() - let fs = InMemoryFileSystem( emptyFiles: "/Pkg/Sources/exe/main.swift", @@ -4966,8 +4967,6 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testUserToolchainWithToolsetCompileFlags() async throws { - try XCTSkipOnWindows(because: "Path delimiters donw's work well on Windows") - let fileSystem = InMemoryFileSystem( emptyFiles: "/Pkg/Sources/exe/main.swift", @@ -5094,7 +5093,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { let exeCompileArguments = try result.moduleBuildDescription(for: "exe").swift().compileArguments() let exeCompileArgumentsPattern: [StringPattern] = [ jsonFlag(tool: .swiftCompiler), - "-ld-path=/fake/toolchain/usr/bin/linker", + "-ld-path=\(AbsolutePath("/fake/toolchain/usr/bin/linker").pathString)", "-g", cliFlag(tool: .swiftCompiler), .anySequence, "-Xcc", jsonFlag(tool: .cCompiler), "-Xcc", "-g", "-Xcc", cliFlag(tool: .cCompiler), @@ -5119,7 +5118,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { let exeLinkArguments = try result.buildProduct(for: "exe").linkArguments() let exeLinkArgumentsPattern: [StringPattern] = [ jsonFlag(tool: .swiftCompiler), - "-ld-path=/fake/toolchain/usr/bin/linker", + "-ld-path=\(AbsolutePath("/fake/toolchain/usr/bin/linker").pathString)", "-g", cliFlag(tool: .swiftCompiler), .anySequence, "-Xlinker", jsonFlag(tool: .linker), "-Xlinker", cliFlag(tool: .linker), @@ -5136,8 +5135,6 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } func testUserToolchainWithSDKSearchPaths() async throws { - try XCTSkipOnWindows() - let fileSystem = InMemoryFileSystem( emptyFiles: "/Pkg/Sources/exe/main.swift", @@ -7080,6 +7077,7 @@ class BuildPlanNativeTests: BuildPlanTestCase { override func testDuplicateProductNamesWithNonDefaultLibsThrowError() async throws { try await super.testDuplicateProductNamesWithNonDefaultLibsThrowError() } + } class BuildPlanSwiftBuildTests: BuildPlanTestCase { diff --git a/Tests/BuildTests/PluginsBuildPlanTests.swift b/Tests/BuildTests/PluginsBuildPlanTests.swift index 93f0a69b5e2..64e8fc609ec 100644 --- a/Tests/BuildTests/PluginsBuildPlanTests.swift +++ b/Tests/BuildTests/PluginsBuildPlanTests.swift @@ -18,7 +18,7 @@ import PackageModel final class PluginsBuildPlanTests: XCTestCase { func testBuildToolsDatabasePath() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: "Fails to build the project to due to incorrect Path handling. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") try await fixture(name: "Miscellaneous/Plugins/MySourceGenPlugin") { fixturePath in let (stdout, _) = try await executeSwiftBuild(fixturePath) diff --git a/Tests/BuildTests/PrepareForIndexTests.swift b/Tests/BuildTests/PrepareForIndexTests.swift index c986aa2b3f8..f49e09cc851 100644 --- a/Tests/BuildTests/PrepareForIndexTests.swift +++ b/Tests/BuildTests/PrepareForIndexTests.swift @@ -22,11 +22,12 @@ import class Basics.ObservabilitySystem @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import func PackageGraph.loadModulesGraph import class PackageModel.Manifest +import struct PackageGraph.ModulesGraph import struct PackageModel.TargetDescription class PrepareForIndexTests: XCTestCase { func testPrepare() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: "coreCommands.count = 0 instead of 1. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") let (graph, fs, scope) = try macrosPackageGraph() @@ -94,10 +95,7 @@ class PrepareForIndexTests: XCTestCase { XCTAssertTrue(manifest.targets.keys.contains(name)) } - // enable-testing requires the non-exportable-decls, make sure they aren't skipped. - func testEnableTesting() async throws { - try XCTSkipOnWindows() - + func testEnableTestingSetup() throws-> (fs: InMemoryFileSystem, observability: TestingObservability, graph: ModulesGraph) { let fs = InMemoryFileSystem( emptyFiles: "/Pkg/Sources/lib/lib.swift", @@ -105,7 +103,6 @@ class PrepareForIndexTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let scope = observability.topScope let graph = try loadModulesGraph( fileSystem: fs, @@ -119,8 +116,16 @@ class PrepareForIndexTests: XCTestCase { ] ), ], - observabilityScope: scope + observabilityScope: observability.topScope ) + return (fs, observability, graph) + } + + func testEnableTestingDebugConfiguration() async throws { + // enable-testing requires the non-exportable-decls, make sure they aren't skipped. + let (fs, observability, graph) = try self.testEnableTestingSetup() + let scope = observability.topScope + XCTAssertNoDiagnostics(observability.diagnostics) // Under debug, enable-testing is turned on by default. Make sure the flag is not added. @@ -143,6 +148,17 @@ class PrepareForIndexTests: XCTestCase { return swiftCommand.otherArguments.contains("-experimental-skip-non-exportable-decls") && !swiftCommand.otherArguments.contains("-enable-testing") })) + } + + func testEnableTestingReleaseConfiguration() async throws { + try XCTSkipOnWindows(because: """ + Assertion failure. ("0") is not equal to ("1"). Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511 + """) + + let (fs, observability, graph) = try self.testEnableTestingSetup() + let scope = observability.topScope + + XCTAssertNoDiagnostics(observability.diagnostics) // Under release, enable-testing is turned off by default so we should see our flag let releasePlan = try await BuildPlan( @@ -167,7 +183,7 @@ class PrepareForIndexTests: XCTestCase { } func testPrepareNoLazy() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: "coreCommands.count = 0 instead of 1. Possibly related to https://github.com/swiftlang/swift-package-manager/issues/8511") let (graph, fs, scope) = try macrosPackageGraph() From 96d86b03579ee4b68bad0e5e6e49f6ef6ad6288b Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Thu, 1 May 2025 22:45:14 -0400 Subject: [PATCH 70/99] Tests: Reference issue in skipped SourceControlPackageContainerTests (#8579) A few SourceControlPackageContainerTests fail for the same reason. Keep skipping them but add a GitHub issue and the error that occurs to the skip reason Relates to: #8433 Issue: rdar://148248105 Depends on: #8569 --- .../SourceControlPackageContainerTests.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift index 6eb4dcd830a..45adb1a22f6 100644 --- a/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift +++ b/Tests/WorkspaceTests/SourceControlPackageContainerTests.swift @@ -125,7 +125,9 @@ private let v1Range: VersionSetSpecifier = .range("1.0.0" ..< "2.0.0") final class SourceControlPackageContainerTests: XCTestCase { func testVprefixVersions() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: """ + https://github.com/swiftlang/swift-package-manager/issues/8578 + """) let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -172,7 +174,9 @@ final class SourceControlPackageContainerTests: XCTestCase { } func testVersions() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: """ + https://github.com/swiftlang/swift-package-manager/issues/8578 + """) let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -270,7 +274,9 @@ final class SourceControlPackageContainerTests: XCTestCase { } func testPreReleaseVersions() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: """ + https://github.com/swiftlang/swift-package-manager/issues/8578 + """) let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -319,7 +325,9 @@ final class SourceControlPackageContainerTests: XCTestCase { } func testSimultaneousVersions() async throws { - try XCTSkipOnWindows() + try XCTSkipOnWindows(because: """ + https://github.com/swiftlang/swift-package-manager/issues/8578 + """) let fs = InMemoryFileSystem() try fs.createMockToolchain() @@ -374,7 +382,7 @@ final class SourceControlPackageContainerTests: XCTestCase { func testDependencyConstraints() throws { #if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION #else - try XCTSkipIf(true) + try XCTSkipIf(true, "Target based dependency resolution is disabled") #endif let dependencies: [PackageDependency] = [ From 879236b71dcbdac9bb98b6b69e803779125f4042 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Fri, 2 May 2025 07:43:17 -0400 Subject: [PATCH 71/99] Tests: Enable BasicsTests/FileSystem/* Tests on Windows (Swift Testing) (#8450) Convert posted test from XCTest to Swift Testing, while updating to ensuring we mark the tests that currently fail on windows. Tests/BasicsTests/FileSystem/FileSystemTests.swift Tests/BasicsTests/FileSystem/PathShimTests.swift Tests/BasicsTests/FileSystem/PathTests.swift Tests/BasicsTests/FileSystem/TemporaryFileTests.swift Tests/BasicsTests/FileSystem/VFSTests.swift Depends on https://github.com/swiftlang/swift/pull/81217 Partially Addresses: #8433 Issue: rdar://148248105 --- .../FileSystem/FileSystemTests.swift | 58 +- .../FileSystem/InMemoryFilesSystemTests.swift | 431 +++---- .../FileSystem/PathShimTests.swift | 45 +- Tests/BasicsTests/FileSystem/PathTests.swift | 1009 +++++++++++------ .../FileSystem/TemporaryFileTests.swift | 69 +- Tests/BasicsTests/FileSystem/VFSTests.swift | 206 ++-- 6 files changed, 1089 insertions(+), 729 deletions(-) diff --git a/Tests/BasicsTests/FileSystem/FileSystemTests.swift b/Tests/BasicsTests/FileSystem/FileSystemTests.swift index 32c6ddb121d..270fcc968f8 100644 --- a/Tests/BasicsTests/FileSystem/FileSystemTests.swift +++ b/Tests/BasicsTests/FileSystem/FileSystemTests.swift @@ -9,77 +9,84 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation +import TSCTestSupport +import Testing @testable import Basics -import TSCTestSupport -import XCTest -final class FileSystemTests: XCTestCase { - func testStripFirstLevelComponent() throws { +struct FileSystemTests { + @Test + func stripFirstLevelComponent() throws { let fileSystem = InMemoryFileSystem() let rootPath = AbsolutePath("/root") try fileSystem.createDirectory(rootPath) - let totalDirectories = Int.random(in: 0 ..< 100) - for index in 0 ..< totalDirectories { + let totalDirectories = Int.random(in: 0..<100) + for index in 0.. String { -// return "Path '\(pa) \(exists ? "should exists, but doesn't" : "should not exist, but does.")" -// } - -// try withKnownIssue { -// try fs.createDirectory(pathUnderTest, recursive: recursive) - -// for (p, shouldExist) in expectedFiles { -// let expectedPath = AbsolutePath(p) -// #expect(fs.exists(expectedPath) == shouldExist, "\(errorMessage(expectedPath, shouldExist))") -// } -// } when: { -// expectError -// } -// } - - -// @Test( -// arguments: [ -// "/", -// "/tmp", -// "/tmp/", -// "/something/ws", -// "/something/ws/", -// "/what/is/this", -// "/what/is/this/", -// ] -// ) -// func callingCreateDirectoryOnAnExistingDirectoryIsSuccessful(path: String) async throws { -// let root = AbsolutePath(path) -// let fs = InMemoryFileSystem() - -// #expect(throws: Never.self) { -// try fs.createDirectory(root, recursive: true) -// } - -// #expect(throws: Never.self) { -// try fs.createDirectory(root.appending("more"), recursive: true) -// } -// } - -// struct writeFileContentsTests { - -// @Test -// func testWriteFileContentsSuccessful() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // and a path -// let pathUnderTest = AbsolutePath("/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) - -// // THEN we expect the file to exist -// #expect(fs.exists(pathUnderTest), "Path \(pathUnderTest.pathString) does not exists when it should") -// } - -// @Test -// func testWritingAFileWithANonExistingParentDirectoryFails() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // and a path -// let pathUnderTest = AbsolutePath("/tmp/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// // THEn we expect an error to occus -// try withKnownIssue { -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) -// } - -// // AND we expect the file to not exist -// #expect(!fs.exists(pathUnderTest), "Path \(pathUnderTest.pathString) does exists when it should not") -// } - -// @Test -// func errorOccursWhenWritingToRootDirectory() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // and a path -// let pathUnderTest = AbsolutePath("/") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// // THEN we expect an error to occur -// try withKnownIssue { -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) -// } - -// } - -// @Test -// func testErrorOccursIfParentIsNotADirectory() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // AND an existing file -// let aFile = AbsolutePath("/foo") -// try fs.writeFileContents(aFile, bytes: "") - -// // AND a the path under test that has an existing file as a parent -// let pathUnderTest = aFile.appending("myFile") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) - -// // WHEN we write contents to the file -// // THEN we expect an error to occur -// withKnownIssue { -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) -// } - -// } -// } - - -// struct testReadFileContentsTests { -// @Test -// func readingAFileThatDoesNotExistsRaisesAnError()async throws { -// // GIVEN we have a filesystem -// let fs = InMemoryFileSystem() - -// // WHEN we read a non-existing file -// // THEN an error occurs -// try withKnownIssue { -// let _ = try fs.readFileContents("/file/does/not/exists") -// } -// } - -// @Test -// func readingExistingFileReturnsExpectedContents() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // AND a file a path -// let pathUnderTest = AbsolutePath("/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) - -// // WHEN we read contents if the file -// let actualContents = try fs.readFileContents(pathUnderTest) - -// // THEN the actual contents should match the expected to match the -// #expect(actualContents == expectedContents, "Actual is not as expected") -// } - -// @Test -// func readingADirectoryFailsWithAnError() async throws { -// // GIVEN we have a filesytstem -// let fs = InMemoryFileSystem() -// // AND a file a path -// let pathUnderTest = AbsolutePath("/myFile.zip") -// let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) -// try fs.writeFileContents(pathUnderTest, bytes: expectedContents) - -// // WHEN we read the contents of a directory -// // THEN we expect a failure to occur -// withKnownIssue { -// let _ = try fs.readFileContents(pathUnderTest.parentDirectory) -// } -// } -// } -// } +struct InMemoryFileSystemTests { + @Test( + arguments: [ + ( + path: "/", + recurvise: true, + expectedFiles: [ + (p: "/", shouldExist: true) + ], + expectError: false + ), + ( + path: "/tmp", + recurvise: true, + expectedFiles: [ + (p: "/", shouldExist: true), + (p: "/tmp", shouldExist: true), + ], + expectError: false + ), + ( + path: "/tmp/ws", + recurvise: true, + expectedFiles: [ + (p: "/", shouldExist: true), + (p: "/tmp", shouldExist: true), + (p: "/tmp/ws", shouldExist: true), + ], + expectError: false + ), + ( + path: "/tmp/ws", + recurvise: false, + expectedFiles: [ + (p: "/", shouldExist: true), + (p: "/tmp", shouldExist: true), + (p: "/tmp/ws", shouldExist: true), + ], + expectError: true + ), + ] + ) + func creatingDirectoryCreatesInternalFiles( + path: String, + recursive: Bool, + expectedFiles: [(String, Bool)], + expectError: Bool + ) async throws { + let fs = InMemoryFileSystem() + let pathUnderTest = AbsolutePath(path) + + func errorMessage(_ pa: AbsolutePath, _ exists: Bool) -> String { + return + "Path '\(pa) \(exists ? "should exists, but doesn't" : "should not exist, but does.")" + } + + try withKnownIssue { + try fs.createDirectory(pathUnderTest, recursive: recursive) + + for (p, shouldExist) in expectedFiles { + let expectedPath = AbsolutePath(p) + #expect( + fs.exists(expectedPath) == shouldExist, + "\(errorMessage(expectedPath, shouldExist))") + } + } when: { + expectError + } + } + + + @Test( + arguments: [ + "/", + "/tmp", + "/tmp/", + "/something/ws", + "/something/ws/", + "/what/is/this", + "/what/is/this/", + ] + ) + func callingCreateDirectoryOnAnExistingDirectoryIsSuccessful(path: String) async throws { + let root = AbsolutePath(path) + let fs = InMemoryFileSystem() + + #expect(throws: Never.self) { + try fs.createDirectory(root, recursive: true) + } + + #expect(throws: Never.self) { + try fs.createDirectory(root.appending("more"), recursive: true) + } + } + + struct writeFileContentsTests { + + @Test + func testWriteFileContentsSuccessful() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // and a path + let pathUnderTest = AbsolutePath("/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + + // THEN we expect the file to exist + #expect( + fs.exists(pathUnderTest), + "Path \(pathUnderTest.pathString) does not exists when it should") + } + + @Test + func testWritingAFileWithANonExistingParentDirectoryFails() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // and a path + let pathUnderTest = AbsolutePath("/tmp/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + // THEn we expect an error to occus + try withKnownIssue { + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + } + + // AND we expect the file to not exist + #expect( + !fs.exists(pathUnderTest), + "Path \(pathUnderTest.pathString) does exists when it should not") + } + + @Test + func errorOccursWhenWritingToRootDirectory() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // and a path + let pathUnderTest = AbsolutePath("/") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + // THEN we expect an error to occur + try withKnownIssue { + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + } + + } + + @Test + func testErrorOccursIfParentIsNotADirectory() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // AND an existing file + let aFile = AbsolutePath("/foo") + try fs.writeFileContents(aFile, bytes: "") + + // AND a the path under test that has an existing file as a parent + let pathUnderTest = aFile.appending("myFile") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + + // WHEN we write contents to the file + // THEN we expect an error to occur + withKnownIssue { + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + } + + } + } + + + struct testReadFileContentsTests { + @Test + func readingAFileThatDoesNotExistsRaisesAnError() async throws { + // GIVEN we have a filesystem + let fs = InMemoryFileSystem() + + // WHEN we read a non-existing file + // THEN an error occurs + try withKnownIssue { + let _ = try fs.readFileContents("/file/does/not/exists") + } + } + + @Test + func readingExistingFileReturnsExpectedContents() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // AND a file a path + let pathUnderTest = AbsolutePath("/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + + // WHEN we read contents if the file + let actualContents = try fs.readFileContents(pathUnderTest) + + // THEN the actual contents should match the expected to match the + #expect(actualContents == expectedContents, "Actual is not as expected") + } + + @Test + func readingADirectoryFailsWithAnError() async throws { + // GIVEN we have a filesytstem + let fs = InMemoryFileSystem() + // AND a file a path + let pathUnderTest = AbsolutePath("/myFile.zip") + let expectedContents = ByteString([0xAA, 0xBB, 0xCC]) + try fs.writeFileContents(pathUnderTest, bytes: expectedContents) + + // WHEN we read the contents of a directory + // THEN we expect a failure to occur + withKnownIssue { + let _ = try fs.readFileContents(pathUnderTest.parentDirectory) + } + } + } +} diff --git a/Tests/BasicsTests/FileSystem/PathShimTests.swift b/Tests/BasicsTests/FileSystem/PathShimTests.swift index 6f0af62a76f..3dc752c2837 100644 --- a/Tests/BasicsTests/FileSystem/PathShimTests.swift +++ b/Tests/BasicsTests/FileSystem/PathShimTests.swift @@ -12,64 +12,65 @@ import Basics import Foundation -import XCTest +import Testing -class PathShimTests: XCTestCase { - func testRescursiveDirectoryCreation() { - // For the tests we'll need a temporary directory. +struct PathShimTests { + @Test + func rescursiveDirectoryCreation() { try! withTemporaryDirectory(removeTreeOnDeinit: true) { path in // Create a directory under several ancestor directories. let dirPath = path.appending(components: "abc", "def", "ghi", "mno", "pqr") try! makeDirectories(dirPath) // Check that we were able to actually create the directory. - XCTAssertTrue(localFileSystem.isDirectory(dirPath)) + #expect(localFileSystem.isDirectory(dirPath)) // Check that there's no error if we try to create the directory again. - try! makeDirectories(dirPath) + #expect(throws: Never.self) { + try! makeDirectories(dirPath) + } } } } -class WalkTests: XCTestCase { - func testNonRecursive() throws { - #if os(Android) +struct WalkTests { + @Test + func nonRecursive() throws { +#if os(Android) let root = "/system" var expected: [AbsolutePath] = [ "\(root)/usr", "\(root)/bin", "\(root)/etc", ] - #elseif os(Windows) + let expectedCount = 3 +#elseif os(Windows) let root = ProcessInfo.processInfo.environment["SystemRoot"]! var expected: [AbsolutePath] = [ "\(root)/System32", "\(root)/SysWOW64", ] - #else + let expectedCount = (root as NSString).pathComponents.count + 2 +#else let root = "" var expected: [AbsolutePath] = [ "/usr", "/bin", "/sbin", ] - #endif + let expectedCount = 2 +#endif for x in try walk(AbsolutePath(validating: "\(root)/"), recursively: false) { if let i = expected.firstIndex(of: x) { expected.remove(at: i) } - #if os(Android) - XCTAssertEqual(3, x.components.count) - #elseif os(Windows) - XCTAssertEqual((root as NSString).pathComponents.count + 2, x.components.count) - #else - XCTAssertEqual(2, x.components.count) - #endif + #expect(x.components.count == expectedCount, "Actual is not as expected") } - XCTAssertEqual(expected.count, 0) + #expect(expected.count == 0) } - func testRecursive() { + @Test + func recursive() { let root = AbsolutePath(#file).parentDirectory.parentDirectory.parentDirectory.parentDirectory .appending(component: "Sources") var expected = [ @@ -82,6 +83,6 @@ class WalkTests: XCTestCase { expected.remove(at: i) } } - XCTAssertEqual(expected, []) + #expect(expected == []) } } diff --git a/Tests/BasicsTests/FileSystem/PathTests.swift b/Tests/BasicsTests/FileSystem/PathTests.swift index 85679ebd3e1..5cf741f1a34 100644 --- a/Tests/BasicsTests/FileSystem/PathTests.swift +++ b/Tests/BasicsTests/FileSystem/PathTests.swift @@ -6,13 +6,11 @@ See http://swift.org/LICENSE.txt for license information See http://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ + */ import Basics import Foundation -import XCTest - -import _InternalTestSupport // for XCTSkipOnWindows() +import Testing #if os(Windows) private var windows: Bool { true } @@ -20,362 +18,687 @@ private var windows: Bool { true } private var windows: Bool { false } #endif -class PathTests: XCTestCase { - func testBasics() { - XCTAssertEqual(AbsolutePath("/").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").pathString, windows ? #"\a"# : "/a") - XCTAssertEqual(AbsolutePath("/a/b/c").pathString, windows ? #"\a\b\c"# : "/a/b/c") - XCTAssertEqual(RelativePath(".").pathString, ".") - XCTAssertEqual(RelativePath("a").pathString, "a") - XCTAssertEqual(RelativePath("a/b/c").pathString, windows ? #"a\b\c"# : "a/b/c") - XCTAssertEqual(RelativePath("~").pathString, "~") // `~` is not special - } - func testStringInitialization() throws { - let abs1 = AbsolutePath("/") - let abs2 = AbsolutePath(abs1, ".") - XCTAssertEqual(abs1, abs2) - let rel3 = "." - let abs3 = try AbsolutePath(abs2, validating: rel3) - XCTAssertEqual(abs2, abs3) - let base = AbsolutePath("/base/path") - let abs4 = AbsolutePath("/a/b/c", relativeTo: base) - XCTAssertEqual(abs4, AbsolutePath("/a/b/c")) - let abs5 = AbsolutePath("./a/b/c", relativeTo: base) - XCTAssertEqual(abs5, AbsolutePath("/base/path/a/b/c")) - let abs6 = AbsolutePath("~/bla", relativeTo: base) // `~` isn't special - XCTAssertEqual(abs6, AbsolutePath("/base/path/~/bla")) - } +struct PathTests { + struct AbsolutePathTests { + private func pathStringIsSetCorrectlyTestImplementation(path: String, expected: String, label: String) { + let actual = AbsolutePath(path).pathString - func testStringLiteralInitialization() { - let abs = AbsolutePath("/") - XCTAssertEqual(abs.pathString, windows ? #"\"# : "/") - let rel1 = RelativePath(".") - XCTAssertEqual(rel1.pathString, ".") - let rel2 = RelativePath("~") - XCTAssertEqual(rel2.pathString, "~") // `~` is not special - } + #expect(actual == expected, "\(label): Actual is not as expected. Path is: \(path)") + } - func testRepeatedPathSeparators() throws { - try XCTSkipOnWindows(because: "all assertions fail") + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/"), label: "Basics"), + (path: "/a", expected: (windows ? #"\a"# : "/a"), label: "Basics"), + (path: "/a/b/c", expected: (windows ? #"\a\b\c"# : "/a/b/c"), label: "Basics"), + ] + ) + func pathStringIsSetCorrectly(path: String, expected: String, label: String) { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } - XCTAssertEqual(AbsolutePath("/ab//cd//ef").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab///cd//ef").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(RelativePath("ab//cd//ef").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - XCTAssertEqual(RelativePath("ab//cd///ef").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - } + @Test( + arguments: [ + (path: "/ab/cd/ef/", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "Trailing path seperator"), + (path: "/ab/cd/ef//", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "Trailing path seperator"), + (path: "/ab/cd/ef///", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "Trailing path seperator"), + (path: "/ab//cd//ef", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "repeated path seperators"), + (path: "/ab///cd//ef", expected: (windows ? #"\ab\cd\ef"# : "/ab/cd/ef"), label: "repeated path seperators"), + (path: "/ab/././cd//ef", expected: "/ab/cd/ef", label: "dot path component"), + (path: "/ab/./cd//ef/.", expected: "/ab/cd/ef", label: "dot path component"), + (path: "/..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/../../../../..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/abc/..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/abc/../..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/../abc", expected: (windows ? #"\abc"# : "/abc"), label: "dot dot path component"), + (path: "/../abc/..", expected: (windows ? #"\"# : "/"), label: "dot dot path component"), + (path: "/../abc/../def", expected: (windows ? #"\def"# : "/def"), label: "dot dot path component"), + (path: "///", expected: (windows ? #"\"# : "/"), label: "combinations and edge cases"), + (path: "/./", expected: (windows ? #"\"# : "/"), label: "combinations and edge cases"), + ] + ) + func pathStringIsSetCorrectlySkipOnWindows(path: String, expected: String, label: String) { + withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } when :{ + ProcessInfo.hostOperatingSystem == .windows + } + } - func testTrailingPathSeparators() throws { - try XCTSkipOnWindows(because: "trailing path seperator is not removed from pathString") + @Test + func stringInitialization() throws { + let abs1 = AbsolutePath("/") + let abs2 = AbsolutePath(abs1, ".") + #expect(abs1 == abs2) + let rel3 = "." + let abs3 = try AbsolutePath(abs2, validating: rel3) + #expect(abs2 == abs3) + let base = AbsolutePath("/base/path") + let abs4 = AbsolutePath("/a/b/c", relativeTo: base) + #expect(abs4 == AbsolutePath("/a/b/c")) + let abs5 = AbsolutePath("./a/b/c", relativeTo: base) + #expect(abs5 == AbsolutePath("/base/path/a/b/c")) + let abs6 = AbsolutePath("~/bla", relativeTo: base) // `~` isn't special + #expect(abs6 == AbsolutePath("/base/path/~/bla")) + } - XCTAssertEqual(AbsolutePath("/ab/cd/ef/").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab/cd/ef//").pathString, windows ? #"\ab\cd\ef"# : "/ab/cd/ef") - XCTAssertEqual(RelativePath("ab/cd/ef/").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - XCTAssertEqual(RelativePath("ab/cd/ef//").pathString, windows ? #"ab\cd\ef"# : "ab/cd/ef") - } + struct AbsolutePathDirectoryNameAtributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = AbsolutePath(path).dirname + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/")), + (path: "/a", expected: (windows ? #"\"# : "/")), + (path: "/ab/c//d/", expected: (windows ? #"\ab\c"# : "/ab/c")), + ] + ) + func absolutePathDirectoryNameAttributeAllPlatforms(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "/./a", expected: (windows ? #"\"# : "/")), + (path: "/../..", expected: (windows ? #"\"# : "/")), + ] + ) + func absolutePathDirectoryNameAttributeFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } - func testDotPathComponents() throws { - try XCTSkipOnWindows() + struct AbsolutePathBaseNameAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = AbsolutePath(path).basename + + #expect(actual == expected, "Actual is not as expected: \(path)") + } + + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/")), + (path: "/a", expected: "a"), + (path: "/./a", expected: "a"), + ] + ) + func absolutePathBaseNameExtractionAllPlatforms(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "/../..", expected: "/"), + ] + ) + func absolutePathBaseNameExtractionFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } - XCTAssertEqual(AbsolutePath("/ab/././cd//ef").pathString, "/ab/cd/ef") - XCTAssertEqual(AbsolutePath("/ab/./cd//ef/.").pathString, "/ab/cd/ef") - XCTAssertEqual(RelativePath("ab/./cd/././ef").pathString, "ab/cd/ef") - XCTAssertEqual(RelativePath("ab/./cd/ef/.").pathString, "ab/cd/ef") - } + struct AbsolutePathBasenameWithoutExtAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = AbsolutePath(path).basenameWithoutExt + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + + } + + @Test( + arguments: [ + (path: "/", expected: (windows ? #"\"# : "/")), + (path: "/a", expected: "a"), + (path: "/./a", expected: "a"), + (path: "/a.txt", expected: "a"), + (path: "/./a.txt", expected: "a"), + ] + ) + func absolutePathBaseNameWithoutExt(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "/../..", expected: "/"), + ] + ) + func absolutePathBaseNameWithoutExtFailedOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } - func testDotDotPathComponents() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../../../../..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/abc/..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/abc/../..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../abc").pathString, windows ? #"\abc"# : "/abc") - XCTAssertEqual(AbsolutePath("/../abc/..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../abc/../def").pathString, windows ? #"\def"# : "/def") - XCTAssertEqual(RelativePath("..").pathString, "..") - XCTAssertEqual(RelativePath("../..").pathString, "../..") - XCTAssertEqual(RelativePath(".././..").pathString, "../..") - XCTAssertEqual(RelativePath("../abc/..").pathString, "..") - XCTAssertEqual(RelativePath("../abc/.././").pathString, "..") - XCTAssertEqual(RelativePath("abc/..").pathString, ".") - } + struct AbsolutePathParentDirectoryAttributeReturnsExpectedValue { + private func testImplementation(path: String, numParentDirectoryCalls: Int, expected: String) throws { + let pathUnderTest = AbsolutePath(path) + let expectedPath = AbsolutePath(expected) + try #require(numParentDirectoryCalls >= 1, "Test configuration Error.") + + var actual = pathUnderTest + for _ in 0 ..< numParentDirectoryCalls { + actual = actual.parentDirectory + } + #expect(actual == expectedPath) + } + @Test( + arguments: [ + (path: "/", numParentDirectoryCalls: 1, expected: "/"), + (path: "/", numParentDirectoryCalls: 2, expected: "/"), + (path: "/bar", numParentDirectoryCalls: 1, expected: "/"), + ] + ) + func absolutePathParentDirectoryAttributeReturnsAsExpected(path: String, numParentDirectoryCalls: Int, expected: String) throws { + try testImplementation(path: path, numParentDirectoryCalls: numParentDirectoryCalls, expected: expected) + } + + @Test( + arguments: [ + (path: "/bar/../foo/..//", numParentDirectoryCalls: 2, expected: "/"), + (path: "/bar/../foo/..//yabba/a/b", numParentDirectoryCalls: 2, expected: "/yabba") + ] + ) + func absolutePathParentDirectoryAttributeReturnsAsExpectedFailsOnWindows(path: String, numParentDirectoryCalls: Int, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, numParentDirectoryCalls: numParentDirectoryCalls, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testCombinationsAndEdgeCases() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("///").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/./").pathString, windows ? #"\"# : "/") - XCTAssertEqual(RelativePath("").pathString, ".") - XCTAssertEqual(RelativePath(".").pathString, ".") - XCTAssertEqual(RelativePath("./abc").pathString, "abc") - XCTAssertEqual(RelativePath("./abc/").pathString, "abc") - XCTAssertEqual(RelativePath("./abc/../bar").pathString, "bar") - XCTAssertEqual(RelativePath("foo/../bar").pathString, "bar") - XCTAssertEqual(RelativePath("foo///..///bar///baz").pathString, "bar/baz") - XCTAssertEqual(RelativePath("foo/../bar/./").pathString, "bar") - XCTAssertEqual(RelativePath("../abc/def/").pathString, "../abc/def") - XCTAssertEqual(RelativePath("././././.").pathString, ".") - XCTAssertEqual(RelativePath("./././../.").pathString, "..") - XCTAssertEqual(RelativePath("./").pathString, ".") - XCTAssertEqual(RelativePath(".//").pathString, ".") - XCTAssertEqual(RelativePath("./.").pathString, ".") - XCTAssertEqual(RelativePath("././").pathString, ".") - XCTAssertEqual(RelativePath("../").pathString, "..") - XCTAssertEqual(RelativePath("../.").pathString, "..") - XCTAssertEqual(RelativePath("./..").pathString, "..") - XCTAssertEqual(RelativePath("./../.").pathString, "..") - XCTAssertEqual(RelativePath("./////../////./////").pathString, "..") - XCTAssertEqual(RelativePath("../a").pathString, windows ? #"..\a"# : "../a") - XCTAssertEqual(RelativePath("../a/..").pathString, "..") - XCTAssertEqual(RelativePath("a/..").pathString, ".") - XCTAssertEqual(RelativePath("a/../////../////./////").pathString, "..") - } + } - func testDirectoryNameExtraction() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/./a").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/../..").dirname, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/ab/c//d/").dirname, windows ? #"\ab\c"# : "/ab/c") - XCTAssertEqual(RelativePath("ab/c//d/").dirname, windows ? #"ab\c"# : "ab/c") - XCTAssertEqual(RelativePath("../a").dirname, "..") - XCTAssertEqual(RelativePath("../a/..").dirname, ".") - XCTAssertEqual(RelativePath("a/..").dirname, ".") - XCTAssertEqual(RelativePath("./..").dirname, ".") - XCTAssertEqual(RelativePath("a/../////../////./////").dirname, ".") - XCTAssertEqual(RelativePath("abc").dirname, ".") - XCTAssertEqual(RelativePath("").dirname, ".") - XCTAssertEqual(RelativePath(".").dirname, ".") - } + @Test( + arguments: [ + (path: "/", expected: ["/"]), + (path: "/.", expected: ["/"]), + (path: "/..", expected: ["/"]), + (path: "/bar", expected: ["/", "bar"]), + (path: "/foo/bar/..", expected: ["/", "foo"]), + (path: "/bar/../foo", expected: ["/", "foo"]), + (path: "/bar/../foo/..//", expected: ["/"]), + (path: "/bar/../foo/..//yabba/a/b/", expected: ["/", "yabba", "a", "b"]), + ] + ) + func componentsAttributeReturnsExpectedValue(path: String, expected: [String]) throws { + withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + let actual = AbsolutePath(path).components + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testBaseNameExtraction() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").basename, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").basename, "a") - XCTAssertEqual(AbsolutePath("/./a").basename, "a") - XCTAssertEqual(AbsolutePath("/../..").basename, "/") - XCTAssertEqual(RelativePath("../..").basename, "..") - XCTAssertEqual(RelativePath("../a").basename, "a") - XCTAssertEqual(RelativePath("../a/..").basename, "..") - XCTAssertEqual(RelativePath("a/..").basename, ".") - XCTAssertEqual(RelativePath("./..").basename, "..") - XCTAssertEqual(RelativePath("a/../////../////./////").basename, "..") - XCTAssertEqual(RelativePath("abc").basename, "abc") - XCTAssertEqual(RelativePath("").basename, ".") - XCTAssertEqual(RelativePath(".").basename, ".") - } + struct AncestryTest { + @Test( + arguments: [ + (path: "/a/b/c/d/e/f", descendentOfOrEqualTo: "/a/b/c/d", expected: true), + (path: "/a/b/c/d/e/f.swift", descendentOfOrEqualTo: "/a/b/c", expected: true), + (path: "/", descendentOfOrEqualTo: "/", expected: true), + (path: "/foo/bar", descendentOfOrEqualTo: "/", expected: true), + (path: "/foo/bar", descendentOfOrEqualTo: "/foo/bar/baz", expected: false), + (path: "/foo/bar", descendentOfOrEqualTo: "/bar", expected: false) + ] + ) + func isDescendantOfOrEqual(path: String, descendentOfOrEqualTo: String, expected: Bool) { + let actual = AbsolutePath(path).isDescendantOfOrEqual(to: AbsolutePath(descendentOfOrEqualTo)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/foo/bar", descendentOf: "/foo/bar", expected: false), + (path: "/foo/bar", descendentOf: "/foo", expected: true) + ] + ) + func isDescendant(path: String, ancesterOf: String, expected: Bool) { + let actual = AbsolutePath(path).isDescendant(of: AbsolutePath(ancesterOf)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/a/b/c/d", ancestorOfOrEqualTo: "/a/b/c/d/e/f", expected: true), + (path: "/a/b/c", ancestorOfOrEqualTo: "/a/b/c/d/e/f.swift", expected: true), + (path: "/", ancestorOfOrEqualTo: "/", expected: true), + (path: "/", ancestorOfOrEqualTo: "/foo/bar", expected: true), + (path: "/foo/bar/baz", ancestorOfOrEqualTo: "/foo/bar", expected: false), + (path: "/bar", ancestorOfOrEqualTo: "/foo/bar", expected: false), + ] + ) + func isAncestorOfOrEqual(path: String, ancestorOfOrEqualTo: String, expected: Bool) { + let actual = AbsolutePath(path).isAncestorOfOrEqual(to: AbsolutePath(ancestorOfOrEqualTo)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "/foo/bar", ancesterOf: "/foo/bar", expected: false), + (path: "/foo", ancesterOf: "/foo/bar", expected: true), + ] + ) + func isAncestor(path: String, ancesterOf: String, expected: Bool) { + let actual = AbsolutePath(path).isAncestor(of: AbsolutePath(ancesterOf)) + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + } - func testBaseNameWithoutExt() throws{ - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/./a").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, "/") - XCTAssertEqual(RelativePath("../..").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("../a").basenameWithoutExt, "a") - XCTAssertEqual(RelativePath("../a/..").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("a/..").basenameWithoutExt, ".") - XCTAssertEqual(RelativePath("./..").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("a/../////../////./////").basenameWithoutExt, "..") - XCTAssertEqual(RelativePath("abc").basenameWithoutExt, "abc") - XCTAssertEqual(RelativePath("").basenameWithoutExt, ".") - XCTAssertEqual(RelativePath(".").basenameWithoutExt, ".") - - XCTAssertEqual(AbsolutePath("/a.txt").basenameWithoutExt, "a") - XCTAssertEqual(AbsolutePath("/./a.txt").basenameWithoutExt, "a") - XCTAssertEqual(RelativePath("../a.bc").basenameWithoutExt, "a") - XCTAssertEqual(RelativePath("abc.swift").basenameWithoutExt, "abc") - XCTAssertEqual(RelativePath("../a.b.c").basenameWithoutExt, "a.b") - XCTAssertEqual(RelativePath("abc.xyz.123").basenameWithoutExt, "abc.xyz") - } + @Test + func absolutePathValidation() throws { + #expect(throws: Never.self) { + try AbsolutePath(validating: "/a/b/c/d") + } + + withKnownIssue { + #expect {try AbsolutePath(validating: "~/a/b/d")} throws: { error in + ("\(error)" == "invalid absolute path '~/a/b/d'; absolute path must begin with '/'") + } + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + + #expect {try AbsolutePath(validating: "a/b/d") } throws: { error in + ("\(error)" == "invalid absolute path 'a/b/d'") + } + } + + @Test + func comparison() { + #expect(AbsolutePath("/") <= AbsolutePath("/")); + #expect(AbsolutePath("/abc") < AbsolutePath("/def")); + #expect(AbsolutePath("/2") <= AbsolutePath("/2.1")); + #expect(AbsolutePath("/3.1") > AbsolutePath("/2")); + #expect(AbsolutePath("/2") >= AbsolutePath("/2")); + #expect(AbsolutePath("/2.1") >= AbsolutePath("/2")); + } - func testSuffixExtraction() throws { - try XCTSkipOnWindows(because: "expected nil is not the actual") - - XCTAssertEqual(RelativePath("a").suffix, nil) - XCTAssertEqual(RelativePath("a").extension, nil) - XCTAssertEqual(RelativePath("a.").suffix, nil) - XCTAssertEqual(RelativePath("a.").extension, nil) - XCTAssertEqual(RelativePath(".a").suffix, nil) - XCTAssertEqual(RelativePath(".a").extension, nil) - XCTAssertEqual(RelativePath("").suffix, nil) - XCTAssertEqual(RelativePath("").extension, nil) - XCTAssertEqual(RelativePath(".").suffix, nil) - XCTAssertEqual(RelativePath(".").extension, nil) - XCTAssertEqual(RelativePath("..").suffix, nil) - XCTAssertEqual(RelativePath("..").extension, nil) - XCTAssertEqual(RelativePath("a.foo").suffix, ".foo") - XCTAssertEqual(RelativePath("a.foo").extension, "foo") - XCTAssertEqual(RelativePath(".a.foo").suffix, ".foo") - XCTAssertEqual(RelativePath(".a.foo").extension, "foo") - XCTAssertEqual(RelativePath(".a.foo.bar").suffix, ".bar") - XCTAssertEqual(RelativePath(".a.foo.bar").extension, "bar") - XCTAssertEqual(RelativePath("a.foo.bar").suffix, ".bar") - XCTAssertEqual(RelativePath("a.foo.bar").extension, "bar") - XCTAssertEqual(RelativePath(".a.foo.bar.baz").suffix, ".baz") - XCTAssertEqual(RelativePath(".a.foo.bar.baz").extension, "baz") } + struct RelativePathTests { + private func pathStringIsSetCorrectlyTestImplementation(path: String, expected: String, label: String) { + let actual = RelativePath(path).pathString - func testParentDirectory() throws { - try XCTSkipOnWindows() + #expect(actual == expected, "\(label): Actual is not as expected. Path is: \(path)") + } - XCTAssertEqual(AbsolutePath("/").parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/").parentDirectory.parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar").parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").parentDirectory.parentDirectory, AbsolutePath("/")) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b").parentDirectory.parentDirectory, AbsolutePath("/yabba")) - } + @Test( + arguments: [ + (path: ".", expected: ".", label: "Basics"), + (path: "a", expected: "a", label: "Basics"), + (path: "a/b/c", expected: (windows ? #"a\b\c"# : "a/b/c"), label: "Basics"), + (path: "~", expected: "~", label: "Basics"), + (path: "..", expected: "..", label: "dot dot path component"), + (path: "", expected: ".", label: "combinations and edge cases"), + (path: ".", expected: ".", label: "combinations and edge cases"), + (path: "../a", expected: (windows ? #"..\a"# : "../a"), label: "combinations and edge cases"), + ] + ) + func pathStringIsSetCorrectly(path: String, expected: String, label: String) { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } - @available(*, deprecated) - func testConcatenation() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath(".")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("..")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/"), RelativePath("bar")).pathString, windows ? #"\bar"# : "/bar") - XCTAssertEqual(AbsolutePath(AbsolutePath("/foo/bar"), RelativePath("..")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo/..//")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath(AbsolutePath("/bar/../foo/..//yabba/"), RelativePath("a/b")).pathString, windows ? #"\yabba\a\b"# : "/yabba/a/b") - - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath(".")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("..")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(RelativePath("bar")).pathString, windows ? #"\bar"# : "/bar") - XCTAssertEqual(AbsolutePath("/foo/bar").appending(RelativePath("..")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath("/bar").appending(RelativePath("../foo")).pathString, windows ? #"\foo"# : "/foo") - XCTAssertEqual(AbsolutePath("/bar").appending(RelativePath("../foo/..//")).pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/").appending(RelativePath("a/b")).pathString, windows ? #"\yabba\a\b"# : "/yabba/a/b") - - XCTAssertEqual(AbsolutePath("/").appending(component: "a").pathString, windows ? #"\a"# : "/a") - XCTAssertEqual(AbsolutePath("/a").appending(component: "b").pathString, windows ? #"\a\b"# : "/a/b") - XCTAssertEqual(AbsolutePath("/").appending(components: "a", "b").pathString, windows ? #"\a\b"# : "/a/b") - XCTAssertEqual(AbsolutePath("/a").appending(components: "b", "c").pathString, windows ? #"\a\b\c"# : "/a/b/c") - - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "", "c").pathString, windows ? #"\a\b\c\c"# : "/a/b/c/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "").pathString, windows ? #"\a\b\c"# : "/a/b/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: ".").pathString, windows ? #"\a\b\c"# : "/a/b/c") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..").pathString, windows ? #"\a\b"# : "/a/b") - XCTAssertEqual(AbsolutePath("/a/b/c").appending(components: "..", "d").pathString, windows ? #"\a\b\d"# : "/a/b/d") - XCTAssertEqual(AbsolutePath("/").appending(components: "..").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(components: ".").pathString, windows ? #"\"# : "/") - XCTAssertEqual(AbsolutePath("/").appending(components: "..", "a").pathString, windows ? #"\a"# : "/a") - - XCTAssertEqual(RelativePath("hello").appending(components: "a", "b", "c", "..").pathString, windows ? #"hello\a\b"# : "hello/a/b") - XCTAssertEqual(RelativePath("hello").appending(RelativePath("a/b/../c/d")).pathString, windows ? #"hello\a\c\d"# : "hello/a/c/d") - } + @Test( + arguments: [ + (path: "ab//cd//ef", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "repeated path seperators"), + (path: "ab//cd///ef", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "repeated path seperators"), + + (path: "ab/cd/ef/", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "Trailing path seperator"), + (path: "ab/cd/ef//", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "Trailing path seperator"), + (path: "ab/cd/ef///", expected: (windows ? #"ab\cd\ef"# : "ab/cd/ef"), label: "Trailing path seperator"), + + (path: "ab/./cd/././ef", expected: "ab/cd/ef", label: "dot path component"), + (path: "ab/./cd/ef/.", expected: "ab/cd/ef", label: "dot path component"), + + (path: "../..", expected: "../..", label: "dot dot path component"), + (path: ".././..", expected: "../..", label: "dot dot path component"), + (path: "../abc/..", expected: "..", label: "dot dot path component"), + (path: "../abc/.././", expected: "..", label: "dot dot path component"), + (path: "abc/..", expected: ".", label: "dot dot path component"), + + (path: "../", expected: "..", label: "combinations and edge cases"), + (path: "./abc", expected: "abc", label: "combinations and edge cases"), + (path: "./abc/", expected: "abc", label: "combinations and edge cases"), + (path: "./abc/../bar", expected: "bar", label: "combinations and edge cases"), + (path: "foo/../bar", expected: "bar", label: "combinations and edge cases"), + (path: "foo///..///bar///baz", expected: "bar/baz", label: "combinations and edge cases"), + (path: "foo/../bar/./", expected: "bar", label: "combinations and edge cases"), + (path: "../abc/def/", expected: "../abc/def", label: "combinations and edge cases"), + (path: "././././.", expected: ".", label: "combinations and edge cases"), + (path: "./././../.", expected: "..", label: "combinations and edge cases"), + (path: "./", expected: ".", label: "combinations and edge cases"), + (path: ".//", expected: ".", label: "combinations and edge cases"), + (path: "./.", expected: ".", label: "combinations and edge cases"), + (path: "././", expected: ".", label: "combinations and edge cases"), + (path: "../.", expected: "..", label: "combinations and edge cases"), + (path: "./..", expected: "..", label: "combinations and edge cases"), + (path: "./../.", expected: "..", label: "combinations and edge cases"), + (path: "./////../////./////", expected: "..", label: "combinations and edge cases"), + (path: "../a/..", expected: "..", label: "combinations and edge cases"), + (path: "a/..", expected: ".", label: "combinations and edge cases"), + (path: "a/../////../////./////", expected: "..", label: "combinations and edge cases"), + + ] + ) + func pathStringIsSetCorrectlyFailsOnWindows(path: String, expected: String, label: String) { + withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) does not resolve properly") { + pathStringIsSetCorrectlyTestImplementation( + path: path, + expected: expected, + label: label + ) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testPathComponents() throws { - try XCTSkipOnWindows() - - XCTAssertEqual(AbsolutePath("/").components, ["/"]) - XCTAssertEqual(AbsolutePath("/.").components, ["/"]) - XCTAssertEqual(AbsolutePath("/..").components, ["/"]) - XCTAssertEqual(AbsolutePath("/bar").components, ["/", "bar"]) - XCTAssertEqual(AbsolutePath("/foo/bar/..").components, ["/", "foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo").components, ["/", "foo"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//").components, ["/"]) - XCTAssertEqual(AbsolutePath("/bar/../foo/..//yabba/a/b/").components, ["/", "yabba", "a", "b"]) - - XCTAssertEqual(RelativePath("").components, ["."]) - XCTAssertEqual(RelativePath(".").components, ["."]) - XCTAssertEqual(RelativePath("..").components, [".."]) - XCTAssertEqual(RelativePath("bar").components, ["bar"]) - XCTAssertEqual(RelativePath("foo/bar/..").components, ["foo"]) - XCTAssertEqual(RelativePath("bar/../foo").components, ["foo"]) - XCTAssertEqual(RelativePath("bar/../foo/..//").components, ["."]) - XCTAssertEqual(RelativePath("bar/../foo/..//yabba/a/b/").components, ["yabba", "a", "b"]) - XCTAssertEqual(RelativePath("../..").components, ["..", ".."]) - XCTAssertEqual(RelativePath(".././/..").components, ["..", ".."]) - XCTAssertEqual(RelativePath("../a").components, ["..", "a"]) - XCTAssertEqual(RelativePath("../a/..").components, [".."]) - XCTAssertEqual(RelativePath("a/..").components, ["."]) - XCTAssertEqual(RelativePath("./..").components, [".."]) - XCTAssertEqual(RelativePath("a/../////../////./////").components, [".."]) - XCTAssertEqual(RelativePath("abc").components, ["abc"]) - } + struct relateivePathDirectoryNameAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = RelativePath(path).dirname + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "ab/c//d/", expected: (windows ? #"ab\c"# : "ab/c")), + (path: "../a", expected: ".."), + (path: "./..", expected: "."), + ] + ) + func relativePathDirectoryNameExtraction(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "../a/..", expected: "."), + (path: "a/..", expected: "."), + (path: "a/../////../////./////", expected: "."), + (path: "abc", expected: "."), + (path: "", expected: "."), + (path: ".", expected: "."), + ] + ) + func relativePathDirectoryNameExtractionFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testRelativePathFromAbsolutePaths() throws { - try XCTSkipOnWindows() + } - XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/")), RelativePath(".")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")), RelativePath("a/b/c/d")); - XCTAssertEqual(AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")), RelativePath("../../..")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")), RelativePath("c/d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")), RelativePath("d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")), RelativePath("../../b/c/d")); - XCTAssertEqual(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")), RelativePath("../../../a/b/c/d")); - } + struct relativePathBaseNameAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual = RelativePath(path).basename + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + @Test( + arguments: [ + (path: "../..", expected: ".."), + (path: "../a", expected: "a"), + (path: "../a/..", expected: ".."), + (path: "./..", expected: ".."), + (path: "abc", expected: "abc"), + (path: "", expected: "."), + (path: ".", expected: "."), + ] + ) + func relativePathBaseNameExtraction(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "a/..", expected: "."), + (path: "a/../////../////./////", expected: ".."), + ] + ) + func relativePathBaseNameExtractionFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testComparison() { - XCTAssertTrue(AbsolutePath("/") <= AbsolutePath("/")); - XCTAssertTrue(AbsolutePath("/abc") < AbsolutePath("/def")); - XCTAssertTrue(AbsolutePath("/2") <= AbsolutePath("/2.1")); - XCTAssertTrue(AbsolutePath("/3.1") > AbsolutePath("/2")); - XCTAssertTrue(AbsolutePath("/2") >= AbsolutePath("/2")); - XCTAssertTrue(AbsolutePath("/2.1") >= AbsolutePath("/2")); - } + } - func testAncestry() { - XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f").isDescendantOfOrEqual(to: AbsolutePath("/a/b/c/d"))) - XCTAssertTrue(AbsolutePath("/a/b/c/d/e/f.swift").isDescendantOfOrEqual(to: AbsolutePath("/a/b/c"))) - XCTAssertTrue(AbsolutePath("/").isDescendantOfOrEqual(to: AbsolutePath("/"))) - XCTAssertTrue(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/"))) - XCTAssertFalse(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/foo/bar/baz"))) - XCTAssertFalse(AbsolutePath("/foo/bar").isDescendantOfOrEqual(to: AbsolutePath("/bar"))) - - XCTAssertFalse(AbsolutePath("/foo/bar").isDescendant(of: AbsolutePath("/foo/bar"))) - XCTAssertTrue(AbsolutePath("/foo/bar").isDescendant(of: AbsolutePath("/foo"))) - - XCTAssertTrue(AbsolutePath("/a/b/c/d").isAncestorOfOrEqual(to: AbsolutePath("/a/b/c/d/e/f"))) - XCTAssertTrue(AbsolutePath("/a/b/c").isAncestorOfOrEqual(to: AbsolutePath("/a/b/c/d/e/f.swift"))) - XCTAssertTrue(AbsolutePath("/").isAncestorOfOrEqual(to: AbsolutePath("/"))) - XCTAssertTrue(AbsolutePath("/").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar"))) - XCTAssertFalse(AbsolutePath("/foo/bar/baz").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar"))) - XCTAssertFalse(AbsolutePath("/bar").isAncestorOfOrEqual(to: AbsolutePath("/foo/bar"))) - - XCTAssertFalse(AbsolutePath("/foo/bar").isAncestor(of: AbsolutePath("/foo/bar"))) - XCTAssertTrue(AbsolutePath("/foo").isAncestor(of: AbsolutePath("/foo/bar"))) - } + struct RelativePathBasenameWithoutExtAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: String) throws { + let actual: String = RelativePath(path).basenameWithoutExt + + #expect(actual == expected, "Actual is not as expected. Path is: \(path)") + } + + @Test( + arguments: [ + (path: "../a", expected: "a"), + (path: "a/..", expected: "."), + (path: "abc", expected: "abc"), + (path: "../a.bc", expected: "a"), + (path: "abc.swift", expected: "abc"), + (path: "../a.b.c", expected: "a.b"), + (path: "abc.xyz.123", expected: "abc.xyz"), + ] + ) + func relativePathBaseNameWithoutExt(path: String, expected: String) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "../..", expected: ".."), + (path: "../a/..", expected: ".."), + (path: "./..", expected: ".."), + (path: "a/../////../////./////", expected: ".."), + (path: "", expected: "."), + (path: ".", expected: "."), + ] + ) + func relativePathBaseNameWithoutExtFailsOnWindows(path: String, expected: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - func testAbsolutePathValidation() throws { - try XCTSkipOnWindows() + } - XCTAssertNoThrow(try AbsolutePath(validating: "/a/b/c/d")) + struct relativePathSuffixAndExtensionAttributeReturnsExpectedValue { + private func testImplementation(path: String, expectedSuffix: String?, expectedExtension: String?) throws { + let pathUnderTest = RelativePath(path) + + #expect(pathUnderTest.suffix == expectedSuffix, "Actual suffix is not as expected. Path is: \(path)") + #expect(pathUnderTest.extension == expectedExtension, "Actual extension is not as expected. Path is: \(path)") + } + @Test( + arguments: [ + (path: "a", expectedSuffix: nil, expectedExtension: nil), + (path: "a.foo", expectedSuffix: ".foo", expectedExtension: "foo"), + (path: ".a.foo", expectedSuffix: ".foo", expectedExtension: "foo"), + (path: "a.foo.bar", expectedSuffix: ".bar", expectedExtension: "bar"), + (path: ".a.foo.bar", expectedSuffix: ".bar", expectedExtension: "bar"), + (path: ".a.foo.bar.baz", expectedSuffix: ".baz", expectedExtension: "baz"), + ] + ) + func suffixExtraction(path: String, expectedSuffix: String?, expectedExtension: String?) throws { + try testImplementation(path: path, expectedSuffix: expectedSuffix, expectedExtension: expectedExtension) + } + + @Test( + arguments:[ + "a.", + ".a", + "", + ".", + "..", + ] + ) + func suffixExtractionFailsOnWindows(path: String) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not handled properly") { + try testImplementation(path: path, expectedSuffix: nil, expectedExtension: nil) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } - XCTAssertThrowsError(try AbsolutePath(validating: "~/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid absolute path '~/a/b/d'; absolute path must begin with '/'") } - XCTAssertThrowsError(try AbsolutePath(validating: "a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid absolute path 'a/b/d'") + struct componentsAttributeReturnsExpectedValue { + private func testImplementation(path: String, expected: [String]) throws { + let actual = RelativePath(path).components + + #expect(actual == expected, "Actual is not as expected: \(path)") + } + + @Test( + arguments: [ + (path: "", expected: ["."]), + (path: ".", expected: ["."]), + (path: "..", expected: [".."]), + (path: "bar", expected: ["bar"]), + (path: "../..", expected: ["..", ".."]), + (path: "../a", expected: ["..", "a"]), + (path: "abc", expected: ["abc"]), + ] as [(String, [String])] + ) + func relativePathComponentsAttributeAllPlatform(path: String, expected: [String]) throws { + try testImplementation(path: path, expected: expected) + } + + @Test( + arguments: [ + (path: "foo/bar/..", expected: ["foo"]), + (path: "bar/../foo", expected: ["foo"]), + (path: "bar/../foo/..//", expected: ["."]), + (path: "bar/../foo/..//yabba/a/b/", expected: ["yabba", "a", "b"]), + (path: ".././/..", expected: ["..", ".."]), + (path: "../a/..", expected: [".."]), + (path: "a/..", expected: ["."]), + (path: "./..", expected: [".."]), + (path: "a/../////../////./////", expected: [".."]), + ] as [(String, [String])] + ) + func relativePathComponentsAttributeFailsOnWindows(path: String, expected: [String]) throws { + try withKnownIssue("https://github.com/swiftlang/swift-package-manager/issues/8511: Path \(path) is not properly") { + try testImplementation(path: path, expected: expected) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } + } + } + + @Test + func relativePathValidation() throws { + #expect(throws: Never.self) { + try RelativePath(validating: "a/b/c/d") + } + + withKnownIssue { + #expect {try RelativePath(validating: "/a/b/d")} throws: { error in + ("\(error)" == "invalid relative path '/a/b/d'; relative path should not begin with '/'") + } + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } - } - func testRelativePathValidation() throws { - try XCTSkipOnWindows() + } - XCTAssertNoThrow(try RelativePath(validating: "a/b/c/d")) + @Test + @available(*, deprecated) + func concatenation() throws { + #expect(AbsolutePath(AbsolutePath("/"), RelativePath("")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/"), RelativePath(".")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/"), RelativePath("..")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/"), RelativePath("bar")).pathString == (windows ? #"\bar"# : "/bar")) + #expect(AbsolutePath(AbsolutePath("/foo/bar"), RelativePath("..")).pathString == (windows ? #"\foo"# : "/foo")) + #expect(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo")).pathString == (windows ? #"\foo"# : "/foo")) + withKnownIssue { + #expect(AbsolutePath(AbsolutePath("/bar"), RelativePath("../foo/..//")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath(AbsolutePath("/bar/../foo/..//yabba/"), RelativePath("a/b")).pathString == (windows ? #"\yabba\a\b"# : "/yabba/a/b")) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } - XCTAssertThrowsError(try RelativePath(validating: "/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid relative path '/a/b/d'; relative path should not begin with '/'") - //XCTAssertEqual("\(error)", "invalid relative path '/a/b/d'; relative path should not begin with '/' or '~'") + #expect(AbsolutePath("/").appending(RelativePath("")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(RelativePath(".")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(RelativePath("..")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(RelativePath("bar")).pathString == (windows ? #"\bar"# : "/bar")) + #expect(AbsolutePath("/foo/bar").appending(RelativePath("..")).pathString == (windows ? #"\foo"# : "/foo")) + #expect(AbsolutePath("/bar").appending(RelativePath("../foo")).pathString == (windows ? #"\foo"# : "/foo")) + withKnownIssue { + #expect(AbsolutePath("/bar").appending(RelativePath("../foo/..//")).pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/bar/../foo/..//yabba/").appending(RelativePath("a/b")).pathString == (windows ? #"\yabba\a\b"# : "/yabba/a/b")) + } when: { + ProcessInfo.hostOperatingSystem == .windows } - /*XCTAssertThrowsError(try RelativePath(validating: "~/a/b/d")) { error in - XCTAssertEqual("\(error)", "invalid relative path '~/a/b/d'; relative path should not begin with '/' or '~'") - }*/ + #expect(AbsolutePath("/").appending(component: "a").pathString == (windows ? #"\a"# : "/a")) + #expect(AbsolutePath("/a").appending(component: "b").pathString == (windows ? #"\a\b"# : "/a/b")) + #expect(AbsolutePath("/").appending(components: "a", "b").pathString == (windows ? #"\a\b"# : "/a/b")) + #expect(AbsolutePath("/a").appending(components: "b", "c").pathString == (windows ? #"\a\b\c"# : "/a/b/c")) + + #expect(AbsolutePath("/a/b/c").appending(components: "", "c").pathString == (windows ? #"\a\b\c\c"# : "/a/b/c/c")) + #expect(AbsolutePath("/a/b/c").appending(components: "").pathString == (windows ? #"\a\b\c"# : "/a/b/c")) + #expect(AbsolutePath("/a/b/c").appending(components: ".").pathString == (windows ? #"\a\b\c"# : "/a/b/c")) + #expect(AbsolutePath("/a/b/c").appending(components: "..").pathString == (windows ? #"\a\b"# : "/a/b")) + #expect(AbsolutePath("/a/b/c").appending(components: "..", "d").pathString == (windows ? #"\a\b\d"# : "/a/b/d")) + #expect(AbsolutePath("/").appending(components: "..").pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(components: ".").pathString == (windows ? #"\"# : "/")) + #expect(AbsolutePath("/").appending(components: "..", "a").pathString == (windows ? #"\a"# : "/a")) + + #expect(RelativePath("hello").appending(components: "a", "b", "c", "..").pathString == (windows ? #"hello\a\b"# : "hello/a/b")) + #expect(RelativePath("hello").appending(RelativePath("a/b/../c/d")).pathString == (windows ? #"hello\a\c\d"# : "hello/a/c/d")) } - func testCodable() throws { - try XCTSkipOnWindows() + @Test + func relativePathFromAbsolutePaths() throws { + #expect(AbsolutePath("/").relative(to: AbsolutePath("/")) == RelativePath(".")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/")) == RelativePath("a/b/c/d")); + #expect(AbsolutePath("/").relative(to: AbsolutePath("/a/b/c")) == RelativePath("../../..")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b")) == RelativePath("c/d")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/b/c")) == RelativePath("d")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/a/c/d")) == RelativePath("../../b/c/d")); + #expect(AbsolutePath("/a/b/c/d").relative(to: AbsolutePath("/b/c/d")) == RelativePath("../../../a/b/c/d")); + } + @Test + func codable() throws { struct Foo: Codable, Equatable { var path: AbsolutePath } @@ -392,48 +715,64 @@ class PathTests: XCTestCase { let foo = Foo(path: "/path/to/foo") let data = try JSONEncoder().encode(foo) let decodedFoo = try JSONDecoder().decode(Foo.self, from: data) - XCTAssertEqual(foo, decodedFoo) + #expect(foo == decodedFoo) } do { let foo = Foo(path: "/path/to/../to/foo") let data = try JSONEncoder().encode(foo) let decodedFoo = try JSONDecoder().decode(Foo.self, from: data) - XCTAssertEqual(foo, decodedFoo) - XCTAssertEqual(foo.path.pathString, windows ? #"\path\to\foo"# : "/path/to/foo") - XCTAssertEqual(decodedFoo.path.pathString, windows ? #"\path\to\foo"# : "/path/to/foo") + #expect(foo == decodedFoo) + withKnownIssue { + #expect(foo.path.pathString == (windows ? #"\path\to\foo"# : "/path/to/foo")) + #expect(decodedFoo.path.pathString == (windows ? #"\path\to\foo"# : "/path/to/foo")) + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } do { let bar = Bar(path: "path/to/bar") let data = try JSONEncoder().encode(bar) let decodedBar = try JSONDecoder().decode(Bar.self, from: data) - XCTAssertEqual(bar, decodedBar) + #expect(bar == decodedBar) } do { let bar = Bar(path: "path/to/../to/bar") let data = try JSONEncoder().encode(bar) let decodedBar = try JSONDecoder().decode(Bar.self, from: data) - XCTAssertEqual(bar, decodedBar) - XCTAssertEqual(bar.path.pathString, "path/to/bar") - XCTAssertEqual(decodedBar.path.pathString, "path/to/bar") + #expect(bar == decodedBar) + withKnownIssue { + #expect(bar.path.pathString == "path/to/bar") + #expect(decodedBar.path.pathString == "path/to/bar") + } when: { + ProcessInfo.hostOperatingSystem == .windows + } } do { let data = try JSONEncoder().encode(Baz(path: "")) - XCTAssertThrowsError(try JSONDecoder().decode(Foo.self, from: data)) - XCTAssertNoThrow(try JSONDecoder().decode(Bar.self, from: data)) // empty string is a valid relative path + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Foo.self, from: data) + } + #expect(throws: Never.self) { + try JSONDecoder().decode(Bar.self, from: data) + } // empty string is a valid relative path } do { let data = try JSONEncoder().encode(Baz(path: "foo")) - XCTAssertThrowsError(try JSONDecoder().decode(Foo.self, from: data)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Foo.self, from: data) + } } do { let data = try JSONEncoder().encode(Baz(path: "/foo")) - XCTAssertThrowsError(try JSONDecoder().decode(Bar.self, from: data)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Bar.self, from: data) + } } } diff --git a/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift b/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift index 5460faf901d..cb5b80206f8 100644 --- a/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift +++ b/Tests/BasicsTests/FileSystem/TemporaryFileTests.swift @@ -9,83 +9,84 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation -import XCTest +import Testing import Basics -class TemporaryAsyncFileTests: XCTestCase { - func testBasicTemporaryDirectory() async throws { - // Test can create and remove temp directory. +struct TemporaryAsyncFileTests { + @Test + func basicTemporaryDirectory() async throws { let path1: AbsolutePath = try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in // Do some async task try await Task.sleep(nanoseconds: 1_000) - - XCTAssertTrue(localFileSystem.isDirectory(tempDirPath)) + + #expect(localFileSystem.isDirectory(tempDirPath)) return tempDirPath }.value - XCTAssertFalse(localFileSystem.isDirectory(path1)) - + #expect(!localFileSystem.isDirectory(path1)) + // Test temp directory is not removed when its not empty. let path2: AbsolutePath = try await withTemporaryDirectory { tempDirPath in - XCTAssertTrue(localFileSystem.isDirectory(tempDirPath)) + #expect(localFileSystem.isDirectory(tempDirPath)) // Create a file inside the temp directory. let filePath = tempDirPath.appending("somefile") // Do some async task try await Task.sleep(nanoseconds: 1_000) - + try localFileSystem.writeFileContents(filePath, bytes: []) return tempDirPath }.value - XCTAssertTrue(localFileSystem.isDirectory(path2)) + #expect(localFileSystem.isDirectory(path2)) // Cleanup. try localFileSystem.removeFileTree(path2) - XCTAssertFalse(localFileSystem.isDirectory(path2)) - + #expect(!localFileSystem.isDirectory(path2)) + // Test temp directory is removed when its not empty and removeTreeOnDeinit is enabled. let path3: AbsolutePath = try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in - XCTAssertTrue(localFileSystem.isDirectory(tempDirPath)) + #expect(localFileSystem.isDirectory(tempDirPath)) let filePath = tempDirPath.appending("somefile") // Do some async task try await Task.sleep(nanoseconds: 1_000) - + try localFileSystem.writeFileContents(filePath, bytes: []) return tempDirPath }.value - XCTAssertFalse(localFileSystem.isDirectory(path3)) + #expect(!localFileSystem.isDirectory(path3)) } - - func testCanCreateUniqueTempDirectories() async throws { + + @Test + func canCreateUniqueTempDirectories() async throws { let (pathOne, pathTwo): (AbsolutePath, AbsolutePath) = try await withTemporaryDirectory(removeTreeOnDeinit: true) { pathOne in let pathTwo: AbsolutePath = try await withTemporaryDirectory(removeTreeOnDeinit: true) { pathTwo in // Do some async task try await Task.sleep(nanoseconds: 1_000) - - XCTAssertTrue(localFileSystem.isDirectory(pathOne)) - XCTAssertTrue(localFileSystem.isDirectory(pathTwo)) + + #expect(localFileSystem.isDirectory(pathOne)) + #expect(localFileSystem.isDirectory(pathTwo)) // Their paths should be different. - XCTAssertTrue(pathOne != pathTwo) + #expect(pathOne != pathTwo) return pathTwo }.value return (pathOne, pathTwo) }.value - XCTAssertFalse(localFileSystem.isDirectory(pathOne)) - XCTAssertFalse(localFileSystem.isDirectory(pathTwo)) + #expect(!localFileSystem.isDirectory(pathOne)) + #expect(!localFileSystem.isDirectory(pathTwo)) } - - func testCancelOfTask() async throws { + + @Test + func cancelOfTask() async throws { let task: Task = try withTemporaryDirectory { path in - + try await Task.sleep(nanoseconds: 1_000_000_000) - XCTAssertTrue(Task.isCancelled) - XCTAssertFalse(localFileSystem.isDirectory(path)) + #expect(Task.isCancelled) + #expect(!localFileSystem.isDirectory(path)) return path } task.cancel() - do { - // The correct path is to throw an error here - let _ = try await task.value - XCTFail("The correct path here is to throw an error") - } catch {} + await #expect(throws: (any Error).self, "Error did not error when accessing `task.value`") { + try await task.value + } } } diff --git a/Tests/BasicsTests/FileSystem/VFSTests.swift b/Tests/BasicsTests/FileSystem/VFSTests.swift index 867c7078ae2..fc251b3b650 100644 --- a/Tests/BasicsTests/FileSystem/VFSTests.swift +++ b/Tests/BasicsTests/FileSystem/VFSTests.swift @@ -9,10 +9,11 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics import func TSCBasic.withTemporaryFile -import XCTest +import Testing import struct TSCBasic.ByteString @@ -36,107 +37,112 @@ func testWithTemporaryDirectory( }.value } -class VFSTests: XCTestCase { - func testLocalBasics() throws { - try XCTSkipOnWindows() - - // tiny PE binary from: https://archive.is/w01DO - let contents: [UInt8] = [ - 0x4d, 0x5a, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x01, 0x00, - 0x6a, 0x2a, 0x58, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x03, 0x01, 0x0b, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02 - ] - - let fs = localFileSystem - try withTemporaryFile { [contents] vfsPath in - try withTemporaryDirectory(removeTreeOnDeinit: true) { [contents] tempDirPath in - let file = tempDirPath.appending("best") - try fs.writeFileContents(file, string: "best") - - let sym = tempDirPath.appending("hello") - try fs.createSymbolicLink(sym, pointingAt: file, relative: false) - - let executable = tempDirPath.appending("exec-foo") - try fs.writeFileContents(executable, bytes: ByteString(contents)) -#if !os(Windows) - try fs.chmod(.executable, path: executable, options: []) -#endif - - let executableSym = tempDirPath.appending("exec-sym") - try fs.createSymbolicLink(executableSym, pointingAt: executable, relative: false) - - try fs.createDirectory(tempDirPath.appending("dir")) - try fs.writeFileContents(tempDirPath.appending(components: ["dir", "file"]), bytes: []) - - try VirtualFileSystem.serializeDirectoryTree(tempDirPath, into: AbsolutePath(vfsPath.path), fs: fs, includeContents: [executable]) - } - let vfs = try VirtualFileSystem(path: vfsPath.path, fs: fs) - - // exists() - XCTAssertTrue(vfs.exists(AbsolutePath("/"))) - XCTAssertFalse(vfs.exists(AbsolutePath("/does-not-exist"))) - - // isFile() - let filePath = AbsolutePath("/best") - XCTAssertTrue(vfs.exists(filePath)) - XCTAssertTrue(vfs.isFile(filePath)) - XCTAssertEqual(try vfs.getFileInfo(filePath).fileType, .typeRegular) - XCTAssertFalse(vfs.isDirectory(filePath)) - XCTAssertFalse(vfs.isFile(AbsolutePath("/does-not-exist"))) - XCTAssertFalse(vfs.isSymlink(AbsolutePath("/does-not-exist"))) - XCTAssertThrowsError(try vfs.getFileInfo(AbsolutePath("/does-not-exist"))) - - // isSymlink() - let symPath = AbsolutePath("/hello") - XCTAssertTrue(vfs.isSymlink(symPath)) - XCTAssertTrue(vfs.isFile(symPath)) - XCTAssertEqual(try vfs.getFileInfo(symPath).fileType, .typeSymbolicLink) - XCTAssertFalse(vfs.isDirectory(symPath)) - - // isExecutableFile - let executablePath = AbsolutePath("/exec-foo") - let executableSymPath = AbsolutePath("/exec-sym") - XCTAssertTrue(vfs.isExecutableFile(executablePath)) - XCTAssertTrue(vfs.isExecutableFile(executableSymPath)) - XCTAssertTrue(vfs.isSymlink(executableSymPath)) - XCTAssertFalse(vfs.isExecutableFile(symPath)) - XCTAssertFalse(vfs.isExecutableFile(filePath)) - XCTAssertFalse(vfs.isExecutableFile(AbsolutePath("/does-not-exist"))) - XCTAssertFalse(vfs.isExecutableFile(AbsolutePath("/"))) - - // readFileContents - let execFileContents = try vfs.readFileContents(executablePath) - XCTAssertEqual(execFileContents, ByteString(contents)) - - // isDirectory() - XCTAssertTrue(vfs.isDirectory(AbsolutePath("/"))) - XCTAssertFalse(vfs.isDirectory(AbsolutePath("/does-not-exist"))) - - // getDirectoryContents() - do { - _ = try vfs.getDirectoryContents(AbsolutePath("/does-not-exist")) - XCTFail("Unexpected success") - } catch { - XCTAssertEqual(error.localizedDescription, "no such file or directory: \(AbsolutePath("/does-not-exist"))") +struct VFSTests { + @Test + func localBasics() throws { + try withKnownIssue("Permission issues on Windows") { + // tiny PE binary from: https://archive.is/w01DO + let contents: [UInt8] = [ + 0x4d, 0x5a, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4c, 0x01, 0x01, 0x00, + 0x6a, 0x2a, 0x58, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x03, 0x01, 0x0b, 0x01, 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02 + ] + + let fs = localFileSystem + try withTemporaryFile { [contents] vfsPath in + try withTemporaryDirectory(removeTreeOnDeinit: true) { [contents] tempDirPath in + let file = tempDirPath.appending("best") + try fs.writeFileContents(file, string: "best") + + let sym = tempDirPath.appending("hello") + try fs.createSymbolicLink(sym, pointingAt: file, relative: false) + + let executable = tempDirPath.appending("exec-foo") + try fs.writeFileContents(executable, bytes: ByteString(contents)) + #if !os(Windows) + try fs.chmod(.executable, path: executable, options: []) + #endif + + let executableSym = tempDirPath.appending("exec-sym") + try fs.createSymbolicLink(executableSym, pointingAt: executable, relative: false) + + try fs.createDirectory(tempDirPath.appending("dir")) + try fs.writeFileContents(tempDirPath.appending(components: ["dir", "file"]), bytes: []) + + try VirtualFileSystem.serializeDirectoryTree(tempDirPath, into: AbsolutePath(vfsPath.path), fs: fs, includeContents: [executable]) + } + + let vfs = try VirtualFileSystem(path: vfsPath.path, fs: fs) + + // exists() + #expect(vfs.exists(AbsolutePath("/"))) + #expect(!vfs.exists(AbsolutePath("/does-not-exist"))) + + // isFile() + let filePath = AbsolutePath("/best") + #expect(vfs.exists(filePath)) + #expect(vfs.isFile(filePath)) + #expect(try vfs.getFileInfo(filePath).fileType == .typeRegular) + #expect(!vfs.isDirectory(filePath)) + #expect(!vfs.isFile(AbsolutePath("/does-not-exist"))) + #expect(!vfs.isSymlink(AbsolutePath("/does-not-exist"))) + #expect(throws: (any Error).self) { + try vfs.getFileInfo(AbsolutePath("/does-not-exist")) + } + + // isSymlink() + let symPath = AbsolutePath("/hello") + #expect(vfs.isSymlink(symPath)) + #expect(vfs.isFile(symPath)) + #expect(try vfs.getFileInfo(symPath).fileType == .typeSymbolicLink) + #expect(!vfs.isDirectory(symPath)) + + // isExecutableFile + let executablePath = AbsolutePath("/exec-foo") + let executableSymPath = AbsolutePath("/exec-sym") + #expect(vfs.isExecutableFile(executablePath)) + #expect(vfs.isExecutableFile(executableSymPath)) + #expect(vfs.isSymlink(executableSymPath)) + #expect(!vfs.isExecutableFile(symPath)) + #expect(!vfs.isExecutableFile(filePath)) + #expect(!vfs.isExecutableFile(AbsolutePath("/does-not-exist"))) + #expect(!vfs.isExecutableFile(AbsolutePath("/"))) + + // readFileContents + let execFileContents = try vfs.readFileContents(executablePath) + #expect(execFileContents == ByteString(contents)) + + // isDirectory() + #expect(vfs.isDirectory(AbsolutePath("/"))) + #expect(!vfs.isDirectory(AbsolutePath("/does-not-exist"))) + + // getDirectoryContents() + let dirContents = try vfs.getDirectoryContents(AbsolutePath("/")) + #expect(dirContents.sorted() == ["best", "dir", "exec-foo", "exec-sym", "hello"]) + #expect {try vfs.getDirectoryContents(AbsolutePath("/does-not-exist"))} throws: { error in + (error.localizedDescription == "no such file or directory: \(AbsolutePath("/does-not-exist"))") + } + + let thisDirectoryContents = try vfs.getDirectoryContents(AbsolutePath("/")) + #expect(!thisDirectoryContents.contains(where: { $0 == "." })) + #expect(!thisDirectoryContents.contains(where: { $0 == ".." })) + #expect(thisDirectoryContents.sorted() == ["best", "dir", "exec-foo", "exec-sym", "hello"]) + + let contents = try vfs.getDirectoryContents(AbsolutePath("/dir")) + #expect(contents == ["file"]) + + let fileContents = try vfs.readFileContents(AbsolutePath("/dir/file")) + #expect(fileContents == "") } - - let thisDirectoryContents = try vfs.getDirectoryContents(AbsolutePath("/")) - XCTAssertFalse(thisDirectoryContents.contains(where: { $0 == "." })) - XCTAssertFalse(thisDirectoryContents.contains(where: { $0 == ".." })) - XCTAssertEqual(thisDirectoryContents.sorted(), ["best", "dir", "exec-foo", "exec-sym", "hello"]) - - let contents = try vfs.getDirectoryContents(AbsolutePath("/dir")) - XCTAssertEqual(contents, ["file"]) - - let fileContents = try vfs.readFileContents(AbsolutePath("/dir/file")) - XCTAssertEqual(fileContents, "") + } when: { + ProcessInfo.hostOperatingSystem == .windows } } } From 366e5458f822b1d2a4ad19c207e47f6e41d2ce1b Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Fri, 2 May 2025 17:02:45 +0100 Subject: [PATCH 72/99] =?UTF-8?q?Implement=20SwiftFixIt=20=E2=80=94=20a=20?= =?UTF-8?q?library=20for=20deserializing=20diagnostics=20and=20applying=20?= =?UTF-8?q?fix-its=20(#8585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Motivation: This is part of the [migration tooling](https://github.com/swiftlang/swift-evolution/pull/2673) initiative for Swift features. ### Modifications: For now, this API accepts a collection of `.dia` files, deserializes them, and applies fix-its in place. Eventually, it is expected to serve the upcoming `swift fixit` and `swift migrate` subcommands and support various filtering options, as well as a stateful, interactive workflow similar to `git add --patch`. --- Package.swift | 19 + Sources/SwiftFixIt/CMakeLists.txt | 24 + Sources/SwiftFixIt/SwiftFixit.swift | 409 +++++++++++++ Tests/SwiftFixItTests/SwiftFixItTests.swift | 609 ++++++++++++++++++++ 4 files changed, 1061 insertions(+) create mode 100644 Sources/SwiftFixIt/CMakeLists.txt create mode 100644 Sources/SwiftFixIt/SwiftFixit.swift create mode 100644 Tests/SwiftFixItTests/SwiftFixItTests.swift diff --git a/Package.swift b/Package.swift index fb1307104d0..597653249d7 100644 --- a/Package.swift +++ b/Package.swift @@ -304,6 +304,21 @@ let package = Package( ] ), + .target( + /** API for deserializing diagnostics and applying fix-its */ + name: "SwiftFixIt", + dependencies: [ + "Basics", + .product(name: "TSCBasic", package: "swift-tools-support-core"), + ] + swiftSyntaxDependencies( + ["SwiftDiagnostics", "SwiftIDEUtils", "SwiftParser", "SwiftSyntax"] + ), + exclude: ["CMakeLists.txt"], + swiftSettings: commonExperimentalFeatures + [ + .unsafeFlags(["-static"]), + ] + ), + // MARK: Project Model .target( @@ -916,6 +931,10 @@ let package = Package( dependencies: ["SourceControl", "_InternalTestSupport"], exclude: ["Inputs/TestRepo.tgz"] ), + .testTarget( + name: "SwiftFixItTests", + dependencies: ["SwiftFixIt", "_InternalTestSupport"] + ), .testTarget( name: "XCBuildSupportTests", dependencies: ["XCBuildSupport", "_InternalTestSupport", "_InternalBuildTestSupport"], diff --git a/Sources/SwiftFixIt/CMakeLists.txt b/Sources/SwiftFixIt/CMakeLists.txt new file mode 100644 index 00000000000..f0b192ca6f8 --- /dev/null +++ b/Sources/SwiftFixIt/CMakeLists.txt @@ -0,0 +1,24 @@ +# This source file is part of the Swift open source project +# +# Copyright (c) 2025 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for Swift project authors + +add_library(SwiftFixIt STATIC + SwiftFixIt.swift) +target_link_libraries(SwiftFixIt PUBLIC + Basics + + SwiftSyntax::SwiftDiagnostics + SwiftSyntax::SwiftIDEUtils + SwiftSyntax::SwiftParser + SwiftSyntax::SwiftSyntax + + TSCBasic + TSCUtility +) + +set_target_properties(SwiftFixIt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) diff --git a/Sources/SwiftFixIt/SwiftFixit.swift b/Sources/SwiftFixIt/SwiftFixit.swift new file mode 100644 index 00000000000..74b3ea0f214 --- /dev/null +++ b/Sources/SwiftFixIt/SwiftFixit.swift @@ -0,0 +1,409 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath +import protocol Basics.FileSystem +import var Basics.localFileSystem +import struct Basics.SwiftVersion + +import struct SwiftDiagnostics.Diagnostic +import struct SwiftDiagnostics.DiagnosticCategory +import protocol SwiftDiagnostics.DiagnosticMessage +import enum SwiftDiagnostics.DiagnosticSeverity +import struct SwiftDiagnostics.DiagnosticsFormatter +import struct SwiftDiagnostics.FixIt +import protocol SwiftDiagnostics.FixItMessage +import struct SwiftDiagnostics.GroupedDiagnostics +import struct SwiftDiagnostics.MessageID + +@_spi(FixItApplier) +import enum SwiftIDEUtils.FixItApplier + +import struct SwiftParser.Parser + +import struct SwiftSyntax.AbsolutePosition +import struct SwiftSyntax.SourceFileSyntax +import class SwiftSyntax.SourceLocationConverter +import struct SwiftSyntax.Syntax + +import struct TSCBasic.ByteString +import struct TSCUtility.SerializedDiagnostics + +private enum Error: Swift.Error { + case unexpectedDiagnosticSeverity + case failedToResolveSourceLocation +} + +// FIXME: An abstraction for tests to work around missing memberwise initializers in `TSCUtility.SerializedDiagnostics`. +protocol AnySourceLocation { + var filename: String { get } + var line: UInt64 { get } + var column: UInt64 { get } + var offset: UInt64 { get } +} + +// FIXME: An abstraction for tests to work around missing memberwise initializers in `TSCUtility.SerializedDiagnostics`. +protocol AnyFixIt { + associatedtype SourceLocation: AnySourceLocation + + var start: SourceLocation { get } + var end: SourceLocation { get } + var text: String { get } +} + +// FIXME: An abstraction for tests to work around missing memberwise initializers in `TSCUtility.SerializedDiagnostics`. +protocol AnyDiagnostic { + associatedtype SourceLocation: AnySourceLocation + associatedtype FixIt: AnyFixIt where FixIt.SourceLocation == SourceLocation + + var text: String { get } + var level: SerializedDiagnostics.Diagnostic.Level { get } + var location: SourceLocation? { get } + var category: String? { get } + var categoryURL: String? { get } + var flag: String? { get } + var ranges: [(SourceLocation, SourceLocation)] { get } + var fixIts: [FixIt] { get } +} + +extension SerializedDiagnostics.Diagnostic: AnyDiagnostic {} +extension SerializedDiagnostics.SourceLocation: AnySourceLocation {} +extension SerializedDiagnostics.FixIt: AnyFixIt {} + +/// The backing API for `SwiftFixitCommand`. +package struct SwiftFixIt /*: ~Copyable */ { + private typealias DiagnosticsPerFile = [SourceFile: [SwiftDiagnostics.Diagnostic]] + + private let fileSystem: any FileSystem + + private let diagnosticsPerFile: DiagnosticsPerFile + + package init( + diagnosticFiles: [AbsolutePath], + fileSystem: any FileSystem + ) throws { + // Deserialize the diagnostics. + let diagnostics = try diagnosticFiles.map { path in + let fileContents = try fileSystem.readFileContents(path) + return try TSCUtility.SerializedDiagnostics(bytes: fileContents).diagnostics + }.lazy.joined() + + self = try SwiftFixIt(diagnostics: diagnostics, fileSystem: fileSystem) + } + + init( + diagnostics: some Collection, + fileSystem: any FileSystem + ) throws { + self.fileSystem = fileSystem + + // Build a map from source files to `SwiftDiagnostics` diagnostics. + var diagnosticsPerFile: DiagnosticsPerFile = [:] + + var diagnosticConverter = DiagnosticConverter(fileSystem: fileSystem) + var currentPrimaryDiagnosticHasNoteWithFixIt = false + + for diagnostic in diagnostics { + let hasFixits = !diagnostic.fixIts.isEmpty + + if diagnostic.level == .note { + if hasFixits { + // The Swift compiler produces parallel fix-its by attaching + // them to notes, which in turn associate to a single + // error/warning. Prefer the first fix-it in this case: if + // the last error/warning we saw has a note with a fix-it + // and so is this one, skip it. + if currentPrimaryDiagnosticHasNoteWithFixIt { + continue + } + + currentPrimaryDiagnosticHasNoteWithFixIt = true + } + } else { + currentPrimaryDiagnosticHasNoteWithFixIt = false + } + + // We are only interested in diagnostics with fix-its. + guard hasFixits else { + continue + } + + guard let (sourceFile, convertedDiagnostic) = + try diagnosticConverter.diagnostic(from: diagnostic) + else { + continue + } + + diagnosticsPerFile[consume sourceFile, default: []].append(consume convertedDiagnostic) + } + + self.diagnosticsPerFile = consume diagnosticsPerFile + } + + package func applyFixIts() throws { + // Bulk-apply fix-its to each file and write the results back. + for (sourceFile, diagnostics) in self.diagnosticsPerFile { + let result = SwiftIDEUtils.FixItApplier.applyFixes( + from: diagnostics, + filterByMessages: nil, + to: sourceFile.syntax + ) + + try self.fileSystem.writeFileContents(sourceFile.path, string: consume result) + } + } +} + +extension SwiftDiagnostics.DiagnosticSeverity { + fileprivate init?(from level: TSCUtility.SerializedDiagnostics.Diagnostic.Level) { + switch level { + case .ignored: + return nil + case .note: + self = .note + case .warning: + self = .warning + case .error, .fatal: + self = .error + case .remark: + self = .remark + } + } +} + +private struct DeserializedDiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { + let message: String + let severity: SwiftDiagnostics.DiagnosticSeverity + let category: SwiftDiagnostics.DiagnosticCategory? + + var diagnosticID: SwiftDiagnostics.MessageID { + .init(domain: "swift-fixit", id: "\(Self.self)") + } +} + +private struct DeserializedFixItMessage: SwiftDiagnostics.FixItMessage { + var message: String { "" } + + var fixItID: SwiftDiagnostics.MessageID { + .init(domain: "swift-fixit", id: "\(Self.self)") + } +} + +private struct SourceFile { + let path: AbsolutePath + let syntax: SwiftSyntax.SourceFileSyntax + + let sourceLocationConverter: SwiftSyntax.SourceLocationConverter + + init(path: AbsolutePath, in fileSystem: borrowing some FileSystem) throws { + self.path = path + + let bytes = try fileSystem.readFileContents(path) + + self.syntax = bytes.contents.withUnsafeBufferPointer { pointer in + SwiftParser.Parser.parse(source: pointer) + } + + self.sourceLocationConverter = SwiftSyntax.SourceLocationConverter( + fileName: path.pathString, + tree: self.syntax + ) + } + + func position(of location: borrowing some AnySourceLocation) throws -> AbsolutePosition { + guard try AbsolutePath(validating: location.filename) == self.path else { + // Wrong source file. + throw Error.failedToResolveSourceLocation + } + + guard location.offset == 0 else { + return AbsolutePosition(utf8Offset: Int(location.offset)) + } + + return self.sourceLocationConverter.position( + ofLine: Int(location.line), + column: Int(location.column) + ) + } + + func node(at location: some AnySourceLocation) throws -> Syntax { + let position = try position(of: location) + + if let token = syntax.token(at: position) { + return SwiftSyntax.Syntax(token) + } + + if position == self.syntax.endPosition { + // FIXME: EOF token is not included in '.token(at: position)' + // We might want to include it, but want to avoid special handling. + if let token = syntax.lastToken(viewMode: .all) { + return SwiftSyntax.Syntax(token) + } + + return Syntax(self.syntax) + } + + // position out of range. + throw Error.failedToResolveSourceLocation + } +} + +extension SourceFile: Hashable { + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.syntax == rhs.syntax + } + + func hash(into hasher: inout Hasher) { + hasher.combine(self.syntax) + } +} + +private struct DiagnosticConverter /*: ~Copyable */ { + private struct SourceFileCache /*: ~Copyable */ { + private let fileSystem: any FileSystem + + private var sourceFiles: [AbsolutePath: SourceFile] + + init(fileSystem: any FileSystem) { + self.fileSystem = fileSystem + self.sourceFiles = [:] + } + + subscript(location: some AnySourceLocation) -> SourceFile { + mutating get throws { + let path = try AbsolutePath(validating: location.filename) + + if let cached = sourceFiles[path] { + return cached + } + + let sourceFile = try SourceFile(path: path, in: fileSystem) + sourceFiles[path] = sourceFile + + return sourceFile + } + } + } + + private var sourceFileCache: SourceFileCache + + init(fileSystem: any FileSystem) { + self.sourceFileCache = SourceFileCache(fileSystem: fileSystem) + } +} + +extension DiagnosticConverter { + // We expect a fix-it to be in the same source file as the diagnostic it is + // attached to. The opposite can hurt clarity and is more difficult and + // less efficient to model and process in general. The compiler may want to + // actually guard against this pattern and establish a convention to instead + // emit notes with those fix-its. + private static func fixIt( + from diagnostic: borrowing some AnyDiagnostic, + in sourceFile: borrowing SourceFile + ) throws -> SwiftDiagnostics.FixIt { + let changes = try diagnostic.fixIts.map { fixIt in + let startPosition = try sourceFile.position(of: fixIt.start) + let endPosition = try sourceFile.position(of: fixIt.end) + + return SwiftDiagnostics.FixIt.Change.replaceText( + range: startPosition ..< endPosition, + with: fixIt.text, + in: Syntax(sourceFile.syntax) + ) + } + + return SwiftDiagnostics.FixIt(message: DeserializedFixItMessage(), changes: changes) + } + + private static func highlights( + from diagnostic: borrowing some AnyDiagnostic, + in sourceFile: borrowing SourceFile + ) throws -> [Syntax] { + try diagnostic.ranges.map { startLocation, endLocation in + let startPosition = try sourceFile.position(of: startLocation) + let endPosition = try sourceFile.position(of: endLocation) + + var highlightedNode = try sourceFile.node(at: startLocation) + + // Walk up from the start token until we find a syntax node that matches + // the highlight range. + while true { + // If this syntax matches our starting/ending positions, add the + // highlight and we're done. + if highlightedNode.positionAfterSkippingLeadingTrivia == startPosition + && highlightedNode.endPositionBeforeTrailingTrivia == endPosition + { + break + } + + // Go up to the parent. + guard let parent = highlightedNode.parent else { + break + } + + highlightedNode = parent + } + + return highlightedNode + } + } + + typealias Diagnostic = (sourceFile: SourceFile, diagnostic: SwiftDiagnostics.Diagnostic) + + mutating func diagnostic( + from diagnostic: borrowing some AnyDiagnostic + ) throws -> Diagnostic? { + if diagnostic.fixIts.isEmpty { + preconditionFailure("Expected diagnostic with fix-its") + } + + guard let location = diagnostic.location else { + return nil + } + + let message: DeserializedDiagnosticMessage + do { + guard let severity = SwiftDiagnostics.DiagnosticSeverity(from: diagnostic.level) else { + return nil + } + + let category: SwiftDiagnostics.DiagnosticCategory? = + if let category = diagnostic.category { + .init(name: category, documentationURL: diagnostic.categoryURL) + } else { + nil + } + + message = .init( + message: diagnostic.text, + severity: severity, + category: category + ) + } + + let sourceFile = try sourceFileCache[location] + + return try Diagnostic( + sourceFile: sourceFile, + diagnostic: SwiftDiagnostics.Diagnostic( + node: sourceFile.node(at: location), + position: sourceFile.position(of: location), + message: message, + highlights: Self.highlights(from: diagnostic, in: sourceFile), + fixIts: [ + Self.fixIt(from: diagnostic, in: sourceFile), + ] + ) + ) + } +} diff --git a/Tests/SwiftFixItTests/SwiftFixItTests.swift b/Tests/SwiftFixItTests/SwiftFixItTests.swift new file mode 100644 index 00000000000..4b628a395ad --- /dev/null +++ b/Tests/SwiftFixItTests/SwiftFixItTests.swift @@ -0,0 +1,609 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import _InternalTestSupport +import struct Basics.AbsolutePath +import var Basics.localFileSystem +@testable +import SwiftFixIt +import struct TSCUtility.SerializedDiagnostics +import XCTest + +final class SwiftFixItTests: XCTestCase { + private struct TestDiagnostic: AnyDiagnostic { + struct SourceLocation: AnySourceLocation { + let filename: String + let line: UInt64 + let column: UInt64 + let offset: UInt64 + } + + struct FixIt: AnyFixIt { + let start: TestDiagnostic.SourceLocation + let end: TestDiagnostic.SourceLocation + let text: String + } + + var text: String + var level: SerializedDiagnostics.Diagnostic.Level + var location: SourceLocation? + var category: String? + var categoryURL: String? + var flag: String? + var ranges: [(SourceLocation, SourceLocation)] = [] + var fixIts: [FixIt] = [] + } + + private struct SourceFileEdit { + let input: String + let result: String + } + + private struct TestCase { + let edits: T + let diagnostics: [TestDiagnostic] + } + + private func _testAPI( + _ sourceFilePathsAndEdits: [(AbsolutePath, SourceFileEdit)], + _ diagnostics: [TestDiagnostic] + ) throws { + for (path, edit) in sourceFilePathsAndEdits { + try localFileSystem.writeFileContents(path, string: edit.input) + } + + let swiftFixIt = try SwiftFixIt(diagnostics: diagnostics, fileSystem: localFileSystem) + try swiftFixIt.applyFixIts() + + for (path, edit) in sourceFilePathsAndEdits { + try XCTAssertEqual(localFileSystem.readFileContents(path), edit.result) + } + } + + private func uniqueSwiftFileName() -> String { + "\(UUID().uuidString).swift" + } + + // Cannot use variadic generics: crashes. + private func testAPI1File( + _ getTestCase: (String) -> TestCase + ) throws { + try testWithTemporaryDirectory { fixturePath in + let sourceFilePath = fixturePath.appending(self.uniqueSwiftFileName()) + + let testCase = getTestCase(sourceFilePath.pathString) + + try self._testAPI( + [(sourceFilePath, testCase.edits)], + testCase.diagnostics + ) + } + } + + private func testAPI2Files( + _ getTestCase: (String, String) -> TestCase<(SourceFileEdit, SourceFileEdit)> + ) throws { + try testWithTemporaryDirectory { fixturePath in + let sourceFilePath1 = fixturePath.appending(self.uniqueSwiftFileName()) + let sourceFilePath2 = fixturePath.appending(self.uniqueSwiftFileName()) + + let testCase = getTestCase(sourceFilePath1.pathString, sourceFilePath2.pathString) + + try self._testAPI( + [(sourceFilePath1, testCase.edits.0), (sourceFilePath2, testCase.edits.1)], + testCase.diagnostics + ) + } + } +} + +extension SwiftFixItTests { + func testFixIt() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "let x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testFixItIgnoredDiagnostic() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "var x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .ignored, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Ignore, diagnostic is ignored. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testFixItNoLocation() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "var x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: nil, + fixIts: [ + // Ignore, diagnostic without location. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testNoteFixIt() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "let x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0) + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testNonParallelNoteFixIts() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "let x = 22"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0) + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0) + ), + // Make sure we apply this fix-it. + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 9, offset: 0), + end: .init(filename: filename, line: 1, column: 10, offset: 0), + text: "22" + ), + ] + ), + ] + ) + } + } + + func testFixItsOnDifferentLines() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init( + input: """ + var x = 1 + var y = 2 + var z = 3 + """, + result: """ + let x = 1 + var y = 244 + z = 3 + """ + ), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Replacement. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + // Addition. + .init( + start: .init(filename: filename, line: 2, column: 10, offset: 0), + end: .init(filename: filename, line: 2, column: 10, offset: 0), + text: "44" + ), + // Deletion. + .init( + start: .init(filename: filename, line: 3, column: 1, offset: 0), + end: .init(filename: filename, line: 3, column: 5, offset: 0), + text: "" + ), + ] + ), + ] + ) + } + } + + func testNonOverlappingFixItsOnSameLine() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = foo(1, 2)", result: "x = fooo(1, 233)"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Replacement. + .init( + start: .init(filename: filename, line: 1, column: 9, offset: 0), + end: .init(filename: filename, line: 1, column: 12, offset: 0), + text: "fooo" + ), + // Addition. + .init( + start: .init(filename: filename, line: 1, column: 17, offset: 0), + end: .init(filename: filename, line: 1, column: 17, offset: 0), + text: "33" + ), + // Deletion. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 5, offset: 0), + text: "" + ), + ] + ), + ] + ) + } + } + + func testOverlappingFixItsSingleDiagnostic() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "_ = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Applied. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 6, offset: 0), + text: "_" + ), + // Ignored, overlaps with previous fix-it. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testOverlappingFixItsDifferentDiagnostics() throws { + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "_ = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Applied. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 6, offset: 0), + text: "_" + ), + ] + ), + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Ignored, overlaps with previous fix-it. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testParallelFixIts1() throws { + // First parallel fix-it is applied per emission order. + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "let x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0) + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Applied. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Ignored, parallel to previous fix-it. + .init( + start: .init(filename: filename, line: 1, column: 9, offset: 0), + end: .init(filename: filename, line: 1, column: 10, offset: 0), + text: "22" + ), + ] + ), + ] + ) + } + } + + func testParallelFixIts2() throws { + // First parallel fix-it is applied per emission order. + try self.testAPI1File { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "let x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0) + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Applied. + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0) + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + // Ignored, parallel to previous fix-it. + .init( + start: .init(filename: filename, line: 1, column: 9, offset: 0), + end: .init(filename: filename, line: 1, column: 10, offset: 0), + text: "22" + ), + ] + ), + ] + ) + } + } + + func testFixItsMultipleFiles() throws { + try self.testAPI2Files { (filename1: String, filename2: String) in + .init( + edits: ( + .init(input: "var x = 1", result: "let x = 1"), + .init(input: "var x = 1", result: "let x = 1") + ), + diagnostics: [ + // filename1 + TestDiagnostic( + text: "warning", + level: .warning, + location: .init(filename: filename1, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename1, line: 1, column: 1, offset: 0), + end: .init(filename: filename1, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename1, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename1, line: 1, column: 1, offset: 0), + end: .init(filename: filename1, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + // filename2 + TestDiagnostic( + text: "warning", + level: .warning, + location: .init(filename: filename2, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename2, line: 1, column: 1, offset: 0), + end: .init(filename: filename2, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename2, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename2, line: 1, column: 1, offset: 0), + end: .init(filename: filename2, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testFixItNoteInDifferentFile() throws { + try self.testAPI2Files { (filename1: String, filename2: String) in + .init( + edits: ( + .init(input: "var x = 1", result: "let x = 1"), + .init(input: "var x = 1", result: "var x = 1") + ), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename2, line: 1, column: 1, offset: 0) + ), + TestDiagnostic( + text: "note", + level: .note, + location: .init(filename: filename1, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename1, line: 1, column: 1, offset: 0), + end: .init(filename: filename1, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } + + func testFixItInDifferentFile() { + do { + try self.testAPI2Files { (filename1: String, filename2: String) in + .init( + edits: ( + .init(input: "var x = 1", result: "let x = 1"), + .init(input: "", result: "") + ), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename2, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename1, line: 1, column: 1, offset: 0), + end: .init(filename: filename1, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + } catch { + // Expected to throw an error. + return + } + + XCTFail() + } +} From e8aff4bbb28661a414e8371f1b4ddef510d6d9f4 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Fri, 2 May 2025 12:06:33 -0400 Subject: [PATCH 73/99] Test: Guard a ManifestSourceGenerationTest and annotate skip message (#8582) Guard a test in ManifestSourceGenerationTest based on the compiler version, and annotate skipped tests with a GitHub issue. Also, enable the two Workspace Traits tests that were added after #8569 was merged. Relates to: #8433 Issue: rdar://148248105 --- Sources/_InternalTestSupport/XCTAssertHelpers.swift | 7 +++++++ Tests/WorkspaceTests/ManifestSourceGenerationTests.swift | 4 ++-- Tests/WorkspaceTests/WorkspaceTests.swift | 4 ---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/_InternalTestSupport/XCTAssertHelpers.swift b/Sources/_InternalTestSupport/XCTAssertHelpers.swift index 33ad7eac963..b558a0692bf 100644 --- a/Sources/_InternalTestSupport/XCTAssertHelpers.swift +++ b/Sources/_InternalTestSupport/XCTAssertHelpers.swift @@ -101,6 +101,13 @@ public func XCTRequires( } } +public func XCTSkipIfCompilerLessThan6_2() throws { + #if compiler(>=6.2) + #else + throw XCTSkip("Skipping as compiler version is less thann 6.2") + #endif +} + /// An `async`-friendly replacement for `XCTAssertThrowsError`. public func XCTAssertAsyncThrowsError( _ expression: @autoclosure () async throws -> T, diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index a327711979d..0a550e24fe0 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -796,7 +796,7 @@ final class ManifestSourceGenerationTests: XCTestCase { } func testStrictMemorySafety() async throws { - try XCTSkipOnWindows(because: "compilation error: type 'SwiftSetting' has no member 'strictMemorySafety'") + try XCTSkipIfCompilerLessThan6_2() let manifestContents = """ // swift-tools-version:6.2 @@ -865,7 +865,7 @@ final class ManifestSourceGenerationTests: XCTestCase { } func testDefaultIsolation() async throws { - try XCTSkipOnWindows(because: "there are compilation errors") + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8543: there are compilation errors") let manifest = Manifest.createRootManifest( displayName: "pkg", diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index c79f5dee0f8..15180376160 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -16145,8 +16145,6 @@ final class WorkspaceTests: XCTestCase { } func testInvalidTrait_WhenParentPackageEnablesTraits() async throws { - try XCTSkipOnWindows(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -16208,8 +16206,6 @@ final class WorkspaceTests: XCTestCase { } func testInvalidTraitConfiguration_ForRootPackage() async throws { - try XCTSkipOnWindows(because: #"\tmp\ws doesn't exist in file system"#) - let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() From a9e09ef1b32434b3560c15c54deeeb5c68602768 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Fri, 2 May 2025 20:18:13 -0400 Subject: [PATCH 74/99] Test: Convert more tests to Swift Testing (#8100) --- Sources/_InternalTestSupport/misc.swift | 11 + ...FilePackageSigningEntityStorageTests.swift | 204 +++++----- .../SigningEntityTests.swift | 53 ++- .../SigningIdentityTests.swift | 29 +- Tests/PackageSigningTests/SigningTests.swift | 350 ++++++++++-------- Tests/QueryEngineTests/QueryEngineTests.swift | 208 ++++++----- .../GitRepositoryProviderTests.swift | 86 +++-- .../InMemoryGitRepositoryTests.swift | 79 ++-- 8 files changed, 559 insertions(+), 461 deletions(-) diff --git a/Sources/_InternalTestSupport/misc.swift b/Sources/_InternalTestSupport/misc.swift index 73f5834d6f9..afcc670cc23 100644 --- a/Sources/_InternalTestSupport/misc.swift +++ b/Sources/_InternalTestSupport/misc.swift @@ -42,6 +42,17 @@ import enum TSCUtility.Git public let isInCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil public let isSelfHostedCiEnvironment = ProcessInfo.processInfo.environment["SWIFTCI_IS_SELF_HOSTED"] != nil +public let isRealSigningIdentyEcLabelEnvVarSet = + ProcessInfo.processInfo.environment["REAL_SIGNING_IDENTITY_EC_LABEL"] != nil + +public let isRealSigningIdentitTestDefined = { + #if ENABLE_REAL_SIGNING_IDENTITY_TEST + return true + #else + return false + #endif +}() + /// Test helper utility for executing a block with a temporary directory. public func testWithTemporaryDirectory( function: StaticString = #function, diff --git a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift index fb9620b09dc..8f8db5838d1 100644 --- a/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift +++ b/Tests/PackageSigningTests/FilePackageSigningEntityStorageTests.swift @@ -16,12 +16,13 @@ import Basics import PackageModel @testable import PackageSigning import _InternalTestSupport -import XCTest +import Testing import struct TSCUtility.Version -final class FilePackageSigningEntityStorageTests: XCTestCase { - func testHappyCase() async throws { +struct FilePackageSigningEntityStorageTests { + @Test + func happyCase() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -68,36 +69,32 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // A data file should have been created for each package - XCTAssertTrue(mockFileSystem.exists(storage.directoryPath.appending(component: package.signedVersionsFilename))) - XCTAssertTrue( - mockFileSystem - .exists(storage.directoryPath.appending(component: otherPackage.signedVersionsFilename)) - ) + #expect(mockFileSystem.exists(storage.directoryPath.appending(component: package.signedVersionsFilename))) + #expect(mockFileSystem + .exists(storage.directoryPath.appending(component: otherPackage.signedVersionsFilename))) // Signed versions should be saved do { let packageSigners = try storage.get(package: package) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 2) - XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0"), Version("1.1.0")]) - XCTAssertEqual( - packageSigners.signers[davinci]?.origins, - [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))] - ) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("2.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://foo.com"))]) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 2) + #expect(packageSigners.signers[davinci]?.versions == [Version("1.0.0"), Version("1.1.0")]) + #expect(packageSigners.signers[davinci]?.origins == [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))]) + #expect(packageSigners.signers[appleseed]?.versions == [Version("2.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://foo.com"))]) } do { let packageSigners = try storage.get(package: otherPackage) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 1) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://foo.com"))]) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 1) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://foo.com"))]) } } - func testPutDifferentSigningEntityShouldConflict() async throws { + @Test + func putDifferentSigningEntityShouldConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -124,19 +121,24 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) // Writing different signing entities for the same version should fail - await XCTAssertAsyncThrowsError(try storage.put( - package: package, - version: version, - signingEntity: appleseed, - origin: .registry(URL("http://foo.com")) - )) { error in + #expect { + try storage.put( + package: package, + version: version, + signingEntity: appleseed, + origin: .registry(URL("http://foo.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.conflict = error else { - return XCTFail("Expected PackageSigningEntityStorageError.conflict, got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.conflict, got \(error)") + return false } + return true } } - func testPutSameSigningEntityShouldNotConflict() async throws { + @Test + func putSameSigningEntityShouldNotConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -165,16 +167,14 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 1) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) - XCTAssertEqual( - packageSigners.signers[appleseed]?.origins, - [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))] - ) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 1) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://foo.com")), .registry(URL("http://bar.com"))]) } - func testPutUnrecognizedSigningEntityShouldError() async throws { + @Test + func putUnrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -183,19 +183,24 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { let appleseed = SigningEntity.unrecognized(name: "J. Appleseed", organizationalUnit: nil, organization: nil) let version = Version("1.0.0") - await XCTAssertAsyncThrowsError(try storage.put( - package: package, - version: version, - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) // origin is different and should be added - )) { error in + #expect { + try storage.put( + package: package, + version: version, + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) // origin is different and should be added + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } } - func testAddDifferentSigningEntityShouldNotConflict() async throws { + @Test + func addDifferentSigningEntityShouldNotConflict() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -230,16 +235,17 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertNil(packageSigners.expectedSigner) - XCTAssertEqual(packageSigners.signers.count, 2) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) - XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[davinci]?.origins, [.registry(URL("http://foo.com"))]) - XCTAssertEqual(packageSigners.signingEntities(of: Version("1.0.0")), [appleseed, davinci]) + #expect(packageSigners.expectedSigner == nil) + #expect(packageSigners.signers.count == 2) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://bar.com"))]) + #expect(packageSigners.signers[davinci]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[davinci]?.origins == [.registry(URL("http://foo.com"))]) + #expect(packageSigners.signingEntities(of: Version("1.0.0")) == [appleseed, davinci]) } - func testAddUnrecognizedSigningEntityShouldError() async throws { + @Test + func addUnrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -260,19 +266,24 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { origin: .registry(URL("http://foo.com")) ) - await XCTAssertAsyncThrowsError(try storage.add( - package: package, - version: version, - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) - )) { error in + #expect { + try storage.add( + package: package, + version: version, + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } } - func testChangeSigningEntityFromVersion() async throws { + @Test + func changeSigningEntityFromVersion() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -306,16 +317,17 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertEqual(packageSigners.expectedSigner?.signingEntity, appleseed) - XCTAssertEqual(packageSigners.expectedSigner?.fromVersion, Version("1.5.0")) - XCTAssertEqual(packageSigners.signers.count, 2) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.5.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) - XCTAssertEqual(packageSigners.signers[davinci]?.versions, [Version("1.0.0")]) - XCTAssertEqual(packageSigners.signers[davinci]?.origins, [.registry(URL("http://foo.com"))]) + #expect(packageSigners.expectedSigner?.signingEntity == appleseed) + #expect(packageSigners.expectedSigner?.fromVersion == Version("1.5.0")) + #expect(packageSigners.signers.count == 2) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.5.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://bar.com"))]) + #expect(packageSigners.signers[davinci]?.versions == [Version("1.0.0")]) + #expect(packageSigners.signers[davinci]?.origins == [.registry(URL("http://foo.com"))]) } - func testChangeSigningEntityFromVersion_unrecognizedSigningEntityShouldError() async throws { + @Test + func changeSigningEntityFromVersion_unrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -335,19 +347,24 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { origin: .registry(URL("http://foo.com")) ) - await XCTAssertAsyncThrowsError(try storage.changeSigningEntityFromVersion( - package: package, - version: Version("1.5.0"), - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) - )) { error in + #expect { + try storage.changeSigningEntityFromVersion( + package: package, + version: Version("1.5.0"), + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } } - func testChangeSigningEntityForAllVersions() async throws { + @Test + func changeSigningEntityForAllVersions() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -387,14 +404,15 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { ) let packageSigners = try storage.get(package: package) - XCTAssertEqual(packageSigners.expectedSigner?.signingEntity, appleseed) - XCTAssertEqual(packageSigners.expectedSigner?.fromVersion, Version("1.5.0")) - XCTAssertEqual(packageSigners.signers.count, 1) - XCTAssertEqual(packageSigners.signers[appleseed]?.versions, [Version("1.5.0"), Version("2.0.0")]) - XCTAssertEqual(packageSigners.signers[appleseed]?.origins, [.registry(URL("http://bar.com"))]) + #expect(packageSigners.expectedSigner?.signingEntity == appleseed) + #expect(packageSigners.expectedSigner?.fromVersion == Version("1.5.0")) + #expect(packageSigners.signers.count == 1) + #expect(packageSigners.signers[appleseed]?.versions == [Version("1.5.0"), Version("2.0.0")]) + #expect(packageSigners.signers[appleseed]?.origins == [.registry(URL("http://bar.com"))]) } - func testChangeSigningEntityForAllVersions_unrecognizedSigningEntityShouldError() async throws { + @Test + func changeSigningEntityForAllVersions_unrecognizedSigningEntityShouldError() async throws { let mockFileSystem = InMemoryFileSystem() let directoryPath = AbsolutePath("/signing") let storage = FilePackageSigningEntityStorage(fileSystem: mockFileSystem, directoryPath: directoryPath) @@ -414,15 +432,19 @@ final class FilePackageSigningEntityStorageTests: XCTestCase { origin: .registry(URL("http://foo.com")) ) - await XCTAssertAsyncThrowsError(try storage.changeSigningEntityForAllVersions( - package: package, - version: Version("1.5.0"), - signingEntity: appleseed, - origin: .registry(URL("http://bar.com")) - )) { error in + #expect { + try storage.changeSigningEntityForAllVersions( + package: package, + version: Version("1.5.0"), + signingEntity: appleseed, + origin: .registry(URL("http://bar.com")) + ) + } throws: { error in guard case PackageSigningEntityStorageError.unrecognizedSigningEntity = error else { - return XCTFail("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + Issue.record("Expected PackageSigningEntityStorageError.unrecognizedSigningEntity but got \(error)") + return false } + return true } } } diff --git a/Tests/PackageSigningTests/SigningEntityTests.swift b/Tests/PackageSigningTests/SigningEntityTests.swift index e53aa99975b..46ae6a98701 100644 --- a/Tests/PackageSigningTests/SigningEntityTests.swift +++ b/Tests/PackageSigningTests/SigningEntityTests.swift @@ -9,7 +9,8 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// - +import Foundation +import Testing import XCTest import Basics @@ -17,8 +18,9 @@ import Basics import _InternalTestSupport import X509 -final class SigningEntityTests: XCTestCase { - func testTwoADPSigningEntitiesAreEqualIfTeamIDEqual() { +struct SigningEntityTests { + @Test + func twoADPSigningEntitiesAreEqualIfTeamIDEqual() { let adp1 = SigningEntity.recognized( type: .adp, name: "A. Appleseed", @@ -37,48 +39,39 @@ final class SigningEntityTests: XCTestCase { organizationalUnit: "SwiftPM Test Unit Y", organization: "C" ) - XCTAssertEqual(adp1, adp2) // Only team ID (org unit) needs to match - XCTAssertNotEqual(adp1, adp3) + #expect(adp1 == adp2) // Only team ID (org unit) needs to match + #expect(adp1 != adp3) } - func testFromECKeyCertificate() throws { + @Test( + "From certificate key", + arguments: [ + (certificateFilename: "Test_ec.cer", id: "EC Key"), + (certificateFilename: "Test_rsa.cer", id: "RSA Key") + ] + ) + func fromCertificate(certificateFilename: String, id: String) throws { try fixture(name: "Signing", createGitRepo: false) { fixturePath in let certificateBytes = try readFileContents( in: fixturePath, pathComponents: "Certificates", - "Test_ec.cer" + certificateFilename ) let certificate = try Certificate(certificateBytes) let signingEntity = SigningEntity.from(certificate: certificate) guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual(name, certificate.subject.commonName) - XCTAssertEqual(organizationalUnit, certificate.subject.organizationalUnitName) - XCTAssertEqual(organization, certificate.subject.organizationName) - } - } - - func testFromRSAKeyCertificate() throws { - try fixture(name: "Signing", createGitRepo: false) { fixturePath in - let certificateBytes = try readFileContents( - in: fixturePath, - pathComponents: "Certificates", - "Test_rsa.cer" - ) - let certificate = try Certificate(certificateBytes) - - let signingEntity = SigningEntity.from(certificate: certificate) - guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") - } - XCTAssertEqual(name, certificate.subject.commonName) - XCTAssertEqual(organizationalUnit, certificate.subject.organizationalUnitName) - XCTAssertEqual(organization, certificate.subject.organizationName) + #expect(name == certificate.subject.commonName) + #expect(organizationalUnit == certificate.subject.organizationalUnitName) + #expect(organization == certificate.subject.organizationName) } } +} +final class SigningEntityXCTests: XCTestCase { #if os(macOS) func testFromKeychainCertificate() async throws { #if ENABLE_REAL_SIGNING_IDENTITY_TEST diff --git a/Tests/PackageSigningTests/SigningIdentityTests.swift b/Tests/PackageSigningTests/SigningIdentityTests.swift index 0f32499ba8d..b426cce6b5f 100644 --- a/Tests/PackageSigningTests/SigningIdentityTests.swift +++ b/Tests/PackageSigningTests/SigningIdentityTests.swift @@ -9,7 +9,9 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation +import Testing import XCTest import _CryptoExtras // For RSA @@ -19,8 +21,9 @@ import Crypto import _InternalTestSupport import X509 -final class SigningIdentityTests: XCTestCase { - func testSwiftSigningIdentityWithECKey() throws { +struct SigningIdentityTests { + @Test + func swiftSigningIdentityWithECKey() throws { try fixture(name: "Signing", createGitRepo: false) { fixturePath in let certificateBytes = try readFileContents( in: fixturePath, @@ -30,9 +33,9 @@ final class SigningIdentityTests: XCTestCase { let certificate = try Certificate(certificateBytes) let subject = certificate.subject - XCTAssertEqual("Test (EC) leaf", subject.commonName) - XCTAssertEqual("Test (EC) org unit", subject.organizationalUnitName) - XCTAssertEqual("Test (EC) org", subject.organizationName) + #expect("Test (EC) leaf" == subject.commonName) + #expect("Test (EC) org unit" == subject.organizationalUnitName) + #expect("Test (EC) org" == subject.organizationName) let privateKeyBytes = try readFileContents( in: fixturePath, @@ -43,17 +46,19 @@ final class SigningIdentityTests: XCTestCase { _ = SwiftSigningIdentity(certificate: certificate, privateKey: Certificate.PrivateKey(privateKey)) // Test public API - XCTAssertNoThrow( + #expect(throws: Never.self) { + try SwiftSigningIdentity( derEncodedCertificate: certificateBytes, derEncodedPrivateKey: privateKeyBytes, privateKeyType: .p256 ) - ) + } } } - func testSwiftSigningIdentityWithRSAKey() throws { + @Test + func swiftSigningIdentityWithRSAKey() throws { try fixture(name: "Signing", createGitRepo: false) { fixturePath in let certificateBytes = try readFileContents( in: fixturePath, @@ -63,9 +68,9 @@ final class SigningIdentityTests: XCTestCase { let certificate = try Certificate(certificateBytes) let subject = certificate.subject - XCTAssertEqual("Test (RSA) leaf", subject.commonName) - XCTAssertEqual("Test (RSA) org unit", subject.organizationalUnitName) - XCTAssertEqual("Test (RSA) org", subject.organizationName) + #expect("Test (RSA) leaf" == subject.commonName) + #expect("Test (RSA) org unit" == subject.organizationalUnitName) + #expect("Test (RSA) org" == subject.organizationName) let privateKeyBytes = try readFileContents( in: fixturePath, @@ -76,6 +81,8 @@ final class SigningIdentityTests: XCTestCase { _ = SwiftSigningIdentity(certificate: certificate, privateKey: Certificate.PrivateKey(privateKey)) } } +} +final class SigningIdentityXCTests: XCTestCase { #if os(macOS) func testSigningIdentityFromKeychain() async throws { diff --git a/Tests/PackageSigningTests/SigningTests.swift b/Tests/PackageSigningTests/SigningTests.swift index 67d7706c620..4fd1fd6db61 100644 --- a/Tests/PackageSigningTests/SigningTests.swift +++ b/Tests/PackageSigningTests/SigningTests.swift @@ -18,10 +18,11 @@ import Foundation import _InternalTestSupport import SwiftASN1 @testable import X509 // need internal APIs for OCSP testing -import XCTest +import Testing -final class SigningTests: XCTestCase { - func testCMS1_0_0EndToEnd() async throws { +struct SigningTests { + @Test + func CMS1_0_0EndToEnd() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -55,17 +56,20 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC) leaf", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC) leaf" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMSEndToEndWithECSigningIdentity() async throws { + @Test + func CMSEndToEndWithECSigningIdentity() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -97,17 +101,20 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC) leaf", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC) leaf" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMSEndToEndWithRSASigningIdentity() async throws { + @Test + func CMSEndToEndWithRSASigningIdentity() async throws { let keyAndCertChain = try self.rsaTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -139,17 +146,20 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (RSA) leaf", name) - XCTAssertEqual("Test (RSA) org unit", organizationalUnit) - XCTAssertEqual("Test (RSA) org", organization) + #expect("Test (RSA) leaf" == name) + #expect("Test (RSA) org unit" == organizationalUnit) + #expect("Test (RSA) org" == organization) } - func testCMSWrongKeyTypeForSignatureAlgorithm() async throws { + @Test + func CMSWrongKeyTypeForSignatureAlgorithm() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -168,15 +178,18 @@ final class SigningTests: XCTestCase { intermediateCertificates: keyAndCertChain.intermediateCertificates, observabilityScope: ObservabilitySystem.NOOP ) - XCTFail("Expected error") + Issue.record("Expected error") } catch { guard case SigningError.keyDoesNotSupportSignatureAlgorithm = error else { - return XCTFail("Expected SigningError.keyDoesNotSupportSignatureAlgorithm but got \(error)") + Issue.record( + "Expected SigningError.keyDoesNotSupportSignatureAlgorithm but got \(error)") + return } } } - func testCMS1_0_0EndToEndWithSelfSignedCertificate() async throws { + @Test + func CMS1_0_0EndToEndWithSelfSignedCertificate() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -210,17 +223,20 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC)", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC)" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMSEndToEndWithSelfSignedECSigningIdentity() async throws { + @Test + func CMSEndToEndWithSelfSignedECSigningIdentity() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -252,17 +268,20 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC)", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC)" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMSEndToEndWithSelfSignedRSASigningIdentity() async throws { + @Test + func CMSEndToEndWithSelfSignedRSASigningIdentity() async throws { let keyAndCertChain = try self.rsaSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -294,17 +313,20 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (RSA)", name) - XCTAssertEqual("Test (RSA) org unit", organizationalUnit) - XCTAssertEqual("Test (RSA) org", organization) + #expect("Test (RSA)" == name) + #expect("Test (RSA) org unit" == organizationalUnit) + #expect("Test (RSA) org" == organization) } - func testCMSBadSignature() async throws { + @Test + func CMSBadSignature() async throws { let content = Array("per aspera ad astra".utf8) let signature = Array("bad signature".utf8) @@ -317,11 +339,13 @@ final class SigningTests: XCTestCase { ) guard case .invalid = status else { - return XCTFail("Expected signature status to be .invalid but got \(status)") + Issue.record("Expected signature status to be .invalid but got \(status)") + return } } - func testCMSInvalidSignature() async throws { + @Test + func CMSInvalidSignature() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -354,11 +378,13 @@ final class SigningTests: XCTestCase { ) guard case .invalid = status else { - return XCTFail("Expected signature status to be .invalid but got \(status)") + Issue.record("Expected signature status to be .invalid but got \(status)") + return } } - func testCMSUntrustedCertificate() async throws { + @Test + func CMSUntrustedCertificate() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -390,11 +416,14 @@ final class SigningTests: XCTestCase { ) guard case .certificateNotTrusted = status else { - return XCTFail("Expected signature status to be .certificateNotTrusted but got \(status)") + Issue.record( + "Expected signature status to be .certificateNotTrusted but got \(status)") + return } } - func testCMSCheckCertificateValidityPeriod() async throws { + @Test + func CMSCheckCertificateValidityPeriod() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -429,9 +458,11 @@ final class SigningTests: XCTestCase { ) guard case .certificateInvalid(let reason) = status else { - return XCTFail("Expected signature status to be .certificateInvalid but got \(status)") + Issue.record( + "Expected signature status to be .certificateInvalid but got \(status)") + return } - XCTAssertTrue(reason.contains("not yet valid")) + #expect(reason.contains("not yet valid")) } do { @@ -452,13 +483,16 @@ final class SigningTests: XCTestCase { ) guard case .certificateInvalid(let reason) = status else { - return XCTFail("Expected signature status to be .certificateInvalid but got \(status)") + Issue.record( + "Expected signature status to be .certificateInvalid but got \(status)") + return } - XCTAssertTrue(reason.contains("has expired")) + #expect(reason.contains("has expired")) } } - func testCMSCheckCertificateRevocationStatus() async throws { + @Test + func CMSCheckCertificateRevocationStatus() async throws { let leafName = try OCSPTestHelper.distinguishedName(commonName: "localhost") let intermediateName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test Intermediate CA") let caName = try OCSPTestHelper.distinguishedName(commonName: "SwiftPM Test CA") @@ -511,19 +545,23 @@ final class SigningTests: XCTestCase { throw StringError("Missing OCSP request") } - let ocspResponse = try OCSPResponse.successful(.signed( - responderID: ResponderID.byName(intermediateName), - producedAt: GeneralizedTime(validationTime), - responses: [OCSPSingleResponse( - certID: singleRequest.certID, - certStatus: .unknown, - thisUpdate: GeneralizedTime(validationTime - .days(1)), - nextUpdate: GeneralizedTime(validationTime + .days(1)) - )], - privateKey: intermediatePrivateKey, - responseExtensions: { nonce } - )) - return HTTPClientResponse(statusCode: 200, body: try Data(ocspResponse.derEncodedBytes())) + let ocspResponse = try OCSPResponse.successful( + .signed( + responderID: ResponderID.byName(intermediateName), + producedAt: GeneralizedTime(validationTime), + responses: [ + OCSPSingleResponse( + certID: singleRequest.certID, + certStatus: .unknown, + thisUpdate: GeneralizedTime(validationTime - .days(1)), + nextUpdate: GeneralizedTime(validationTime + .days(1)) + ) + ], + privateKey: intermediatePrivateKey, + responseExtensions: { nonce } + )) + return HTTPClientResponse( + statusCode: 200, body: try Data(ocspResponse.derEncodedBytes())) default: throw StringError("method and url should match") } @@ -557,9 +595,11 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) guard case .certificateInvalid(let reason) = status else { - return XCTFail("Expected signature status to be .certificateInvalid but got \(status)") + Issue.record( + "Expected signature status to be .certificateInvalid but got \(status)") + return } - XCTAssertTrue(reason.contains("status unknown")) + #expect(reason.contains("status unknown")) } // certificateRevocation = .allowSoftFail allows status 'unknown' @@ -578,17 +618,16 @@ final class SigningTests: XCTestCase { observabilityScope: ObservabilitySystem.NOOP ) guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } } } - func testCMSEndToEndWithRSAKeyADPCertificate() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - + @Test( + .enabled(if: isRealSigningIdentitTestDefined) + ) + func CMSEndToEndWithRSAKeyADPCertificate() async throws { let keyAndCertChain = try rsaADPKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -620,7 +659,8 @@ final class SigningTests: XCTestCase { ) guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } func rsaADPKeyAndCertChain() throws -> KeyAndCertChain { @@ -642,12 +682,10 @@ final class SigningTests: XCTestCase { } } - func testCMSEndToEndWithECKeyADPCertificate() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - + @Test( + .enabled(if: isRealSigningIdentitTestDefined) + ) + func CMSEndToEndWithECKeyADPCertificate() async throws { let keyAndCertChain = try ecADPKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -679,7 +717,8 @@ final class SigningTests: XCTestCase { ) guard case .valid = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } func ecADPKeyAndCertChain() throws -> KeyAndCertChain { @@ -701,19 +740,18 @@ final class SigningTests: XCTestCase { } } - #if os(macOS) - func testCMS1_0_0EndToEndWithADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif + // #if os(macOS) + @Test( + .enabled(if: ProcessInfo.hostOperatingSystem == .windows), + .enabled(if: isRealSigningIdentitTestDefined), + .enabled(if: isRealSigningIdentyEcLabelEnvVarSet), + ) + func CMS1_0_0EndToEndWithADPSigningIdentityFromKeychain() async throws { + let label = try #require(Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"]) - guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") - } let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) + #expect(!matches.isEmpty) let signingIdentity = matches[0] let content = Array("per aspera ad astra".utf8) @@ -744,34 +782,33 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } switch signingEntity { case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) } } - #endif - - #if os(macOS) - func testCMSEndToEndWithECKeyADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - guard let label = Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_EC_LABEL' env var is not set") - } + // #endif + + // #if os(macOS) + @Test( + .enabled(if: ProcessInfo.hostOperatingSystem == .windows), + .enabled(if: isRealSigningIdentitTestDefined), + .enabled(if: isRealSigningIdentyEcLabelEnvVarSet), + ) + func CMSEndToEndWithECKeyADPSigningIdentityFromKeychain() async throws { + let label = try #require(Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"]) let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) + #expect(!matches.isEmpty) let signingIdentity = matches[0] let content = Array("per aspera ad astra".utf8) @@ -800,34 +837,33 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } switch signingEntity { case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) } } - #endif - - #if os(macOS) + // #endif + + // #if os(macOS) + @Test( + .enabled(if: ProcessInfo.hostOperatingSystem == .windows), + .enabled(if: isRealSigningIdentitTestDefined), + .enabled(if: isRealSigningIdentyEcLabelEnvVarSet), + ) func testCMSEndToEndWithRSAKeyADPSigningIdentityFromKeychain() async throws { - #if ENABLE_REAL_SIGNING_IDENTITY_TEST - #else - try XCTSkipIf(true) - #endif - - guard let label = Environment.current["REAL_SIGNING_IDENTITY_RSA_LABEL"] else { - throw XCTSkip("Skipping because 'REAL_SIGNING_IDENTITY_RSA_LABEL' env var is not set") - } + let label = try #require(Environment.current["REAL_SIGNING_IDENTITY_EC_LABEL"]) let identityStore = SigningIdentityStore(observabilityScope: ObservabilitySystem.NOOP) let matches = identityStore.find(by: label) - XCTAssertTrue(!matches.isEmpty) + #expect(!matches.isEmpty) let signingIdentity = matches[0] let content = Array("per aspera ad astra".utf8) @@ -856,22 +892,24 @@ final class SigningTests: XCTestCase { ) guard case .valid(let signingEntity) = status else { - return XCTFail("Expected signature status to be .valid but got \(status)") + Issue.record("Expected signature status to be .valid but got \(status)") + return } switch signingEntity { case .recognized(_, let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) case .unrecognized(let name, let organizationalUnit, let organization): - XCTAssertNotNil(name) - XCTAssertNotNil(organizationalUnit) - XCTAssertNotNil(organization) + #expect(name != nil) + #expect(organizationalUnit != nil) + #expect(organization != nil) } } - #endif + // #endif - func testCMS1_0_0ExtractSigningEntity() async throws { + @Test + func CMS1_0_0ExtractSigningEntity() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -903,14 +941,16 @@ final class SigningTests: XCTestCase { ) guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC) leaf", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC) leaf" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { + @Test + func CMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws { let keyAndCertChain = try self.ecSelfSignedTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -942,14 +982,16 @@ final class SigningTests: XCTestCase { ) guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else { - return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)") + Issue.record("Expected SigningEntity.unrecognized but got \(signingEntity)") + return } - XCTAssertEqual("Test (EC)", name) - XCTAssertEqual("Test (EC) org unit", organizationalUnit) - XCTAssertEqual("Test (EC) org", organization) + #expect("Test (EC)" == name) + #expect("Test (EC) org unit" == organizationalUnit) + #expect("Test (EC) org" == organization) } - func testCMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { + @Test + func CMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws { let keyAndCertChain = try self.ecTestKeyAndCertChain() let signingIdentity = SwiftSigningIdentity( certificate: try Certificate(keyAndCertChain.leafCertificate), @@ -980,10 +1022,12 @@ final class SigningTests: XCTestCase { format: signatureFormat, verifierConfiguration: verifierConfiguration ) - XCTFail("expected error") + Issue.record("expected error") } catch { guard case SigningError.certificateNotTrusted = error else { - return XCTFail("Expected error to be SigningError.certificateNotTrusted but got \(error)") + Issue.record( + "Expected error to be SigningError.certificateNotTrusted but got \(error)") + return } } } diff --git a/Tests/QueryEngineTests/QueryEngineTests.swift b/Tests/QueryEngineTests/QueryEngineTests.swift index def80727b93..01c258e8168 100644 --- a/Tests/QueryEngineTests/QueryEngineTests.swift +++ b/Tests/QueryEngineTests/QueryEngineTests.swift @@ -9,6 +9,7 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import _AsyncFileSystem import Basics @@ -17,154 +18,157 @@ import struct Foundation.Data @testable import QueryEngine import struct SystemPackage.FilePath import _InternalTestSupport -import XCTest +import Testing private let encoder = JSONEncoder() private let decoder = JSONDecoder() private extension AsyncFileSystem { - func read(_ path: FilePath, bufferLimit: Int = 10 * 1024 * 1024, as: V.Type) async throws -> V { - let data = try await self.withOpenReadableFile(path) { - var data = Data() - for try await chunk in try await $0.read() { - data.append(contentsOf: chunk) - - assert(data.count < bufferLimit) - } - return data + func read(_ path: FilePath, bufferLimit: Int = 10 * 1024 * 1024, as: V.Type) async throws -> V { + let data = try await self.withOpenReadableFile(path) { + var data = Data() + for try await chunk in try await $0.read() { + data.append(contentsOf: chunk) + + assert(data.count < bufferLimit) + } + return data + } + + return try decoder.decode(V.self, from: data) } - return try decoder.decode(V.self, from: data) - } - - func write(_ path: FilePath, _ value: some Encodable) async throws { - let data = try encoder.encode(value) - try await self.withOpenWritableFile(path) { fileHandle in - try await fileHandle.write(data) + func write(_ path: FilePath, _ value: some Encodable) async throws { + let data = try encoder.encode(value) + try await self.withOpenWritableFile(path) { fileHandle in + try await fileHandle.write(data) + } } - } } private struct Const: CachingQuery { - let x: Int + let x: Int - func run(engine: QueryEngine) async throws -> FilePath { - let resultPath = FilePath("/Const-\(x)") - try await engine.fileSystem.write(resultPath, self.x) - return resultPath - } + func run(engine: QueryEngine) async throws -> FilePath { + let resultPath = FilePath("/Const-\(x)") + try await engine.fileSystem.write(resultPath, self.x) + return resultPath + } } private struct MultiplyByTwo: CachingQuery { - let x: Int + let x: Int - func run(engine: QueryEngine) async throws -> FilePath { - let constPath = try await engine[Const(x: self.x)].path - let constResult = try await engine.fileSystem.read(constPath, as: Int.self) + func run(engine: QueryEngine) async throws -> FilePath { + let constPath = try await engine[Const(x: self.x)].path + let constResult = try await engine.fileSystem.read(constPath, as: Int.self) - let resultPath = FilePath("/MultiplyByTwo-\(constResult)") - try await engine.fileSystem.write(resultPath, constResult * 2) - return resultPath - } + let resultPath = FilePath("/MultiplyByTwo-\(constResult)") + try await engine.fileSystem.write(resultPath, constResult * 2) + return resultPath + } } private struct AddThirty: CachingQuery { - let x: Int + let x: Int - func run(engine: QueryEngine) async throws -> FilePath { - let constPath = try await engine[Const(x: self.x)].path - let constResult = try await engine.fileSystem.read(constPath, as: Int.self) + func run(engine: QueryEngine) async throws -> FilePath { + let constPath = try await engine[Const(x: self.x)].path + let constResult = try await engine.fileSystem.read(constPath, as: Int.self) - let resultPath = FilePath("/AddThirty-\(constResult)") - try await engine.fileSystem.write(resultPath, constResult + 30) - return resultPath - } + let resultPath = FilePath("/AddThirty-\(constResult)") + try await engine.fileSystem.write(resultPath, constResult + 30) + return resultPath + } } private struct Expression: CachingQuery { - let x: Int - let y: Int + let x: Int + let y: Int - func run(engine: QueryEngine) async throws -> FilePath { - let multiplyPath = try await engine[MultiplyByTwo(x: self.x)].path - let addThirtyPath = try await engine[AddThirty(x: self.y)].path + func run(engine: QueryEngine) async throws -> FilePath { + let multiplyPath = try await engine[MultiplyByTwo(x: self.x)].path + let addThirtyPath = try await engine[AddThirty(x: self.y)].path - let multiplyResult = try await engine.fileSystem.read(multiplyPath, as: Int.self) - let addThirtyResult = try await engine.fileSystem.read(addThirtyPath, as: Int.self) + let multiplyResult = try await engine.fileSystem.read(multiplyPath, as: Int.self) + let addThirtyResult = try await engine.fileSystem.read(addThirtyPath, as: Int.self) - let resultPath = FilePath("/Expression-\(multiplyResult)-\(addThirtyResult)") - try await engine.fileSystem.write(resultPath, multiplyResult + addThirtyResult) - return resultPath - } + let resultPath = FilePath("/Expression-\(multiplyResult)-\(addThirtyResult)") + try await engine.fileSystem.write(resultPath, multiplyResult + addThirtyResult) + return resultPath + } } -final class QueryEngineTests: XCTestCase { - func testFilePathHashing() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8541") +struct QueryEngineTests { + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8541"), + .disabled(if: ProcessInfo.hostOperatingSystem == .windows), + ) + func filePathHashing() throws { + let path = "/root" - let path = "/root" + let hashEncoder1 = HashEncoder() + try hashEncoder1.encode(FilePath(path)) + let digest1 = hashEncoder1.finalize() - let hashEncoder1 = HashEncoder() - try hashEncoder1.encode(FilePath(path)) - let digest1 = hashEncoder1.finalize() + let hashEncoder2 = HashEncoder() + try hashEncoder2.encode(String(reflecting: FilePath.self)) + try hashEncoder2.encode(path) + let digest2 = hashEncoder2.finalize() - let hashEncoder2 = HashEncoder() - try hashEncoder2.encode(String(reflecting: FilePath.self)) - try hashEncoder2.encode(path) - let digest2 = hashEncoder2.finalize() + #expect(digest1 == digest2) + } - XCTAssertEqual(digest1, digest2) - } + @Test + func simpleCaching() async throws { + let observabilitySystem = ObservabilitySystem.makeForTesting() + let engine = QueryEngine( + MockFileSystem(), + observabilitySystem.topScope, + cacheLocation: .memory + ) - func testSimpleCaching() async throws { - let observabilitySystem = ObservabilitySystem.makeForTesting() - let engine = QueryEngine( - MockFileSystem(), - observabilitySystem.topScope, - cacheLocation: .memory - ) + var resultPath = try await engine[Expression(x: 1, y: 2)].path + var result = try await engine.fileSystem.read(resultPath, as: Int.self) - var resultPath = try await engine[Expression(x: 1, y: 2)].path - var result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 34) - XCTAssertEqual(result, 34) + var cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 5) - var cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 5) + var cacheHits = await engine.cacheHits + #expect(cacheHits == 0) - var cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 0) + resultPath = try await engine[Expression(x: 1, y: 2)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 34) - resultPath = try await engine[Expression(x: 1, y: 2)].path - result = try await engine.fileSystem.read(resultPath, as: Int.self) - XCTAssertEqual(result, 34) + cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 5) - cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 5) + cacheHits = await engine.cacheHits + #expect(cacheHits == 1) - cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 1) + resultPath = try await engine[Expression(x: 2, y: 1)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 35) - resultPath = try await engine[Expression(x: 2, y: 1)].path - result = try await engine.fileSystem.read(resultPath, as: Int.self) - XCTAssertEqual(result, 35) + cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 8) - cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 8) + cacheHits = await engine.cacheHits + #expect(cacheHits == 3) - cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 3) + resultPath = try await engine[Expression(x: 2, y: 1)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + #expect(result == 35) - resultPath = try await engine[Expression(x: 2, y: 1)].path - result = try await engine.fileSystem.read(resultPath, as: Int.self) - XCTAssertEqual(result, 35) + cacheMisses = await engine.cacheMisses + #expect(cacheMisses == 8) - cacheMisses = await engine.cacheMisses - XCTAssertEqual(cacheMisses, 8) + cacheHits = await engine.cacheHits + #expect(cacheHits == 4) - cacheHits = await engine.cacheHits - XCTAssertEqual(cacheHits, 4) - - try await engine.shutDown() - } + try await engine.shutDown() + } } diff --git a/Tests/SourceControlTests/GitRepositoryProviderTests.swift b/Tests/SourceControlTests/GitRepositoryProviderTests.swift index d1cd1bfac99..cbc86a38f7f 100644 --- a/Tests/SourceControlTests/GitRepositoryProviderTests.swift +++ b/Tests/SourceControlTests/GitRepositoryProviderTests.swift @@ -9,17 +9,19 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics import _InternalTestSupport @testable import SourceControl -import XCTest +import Testing -class GitRepositoryProviderTests: XCTestCase { - func testIsValidDirectory() throws { - // Skipping all tests that call git on Windows. - // We have a hang in CI when running in parallel. - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) +struct GitRepositoryProviderTests { + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func isValidDirectory() throws { try testWithTemporaryDirectory { sandbox in let provider = GitRepositoryProvider() @@ -27,40 +29,50 @@ class GitRepositoryProviderTests: XCTestCase { let repositoryPath = sandbox.appending("test") try localFileSystem.createDirectory(repositoryPath) initGitRepo(repositoryPath) - XCTAssertTrue(try provider.isValidDirectory(repositoryPath)) + #expect(try provider.isValidDirectory(repositoryPath)) // no-checkout bare repository let noCheckoutRepositoryPath = sandbox.appending("test-no-checkout") try localFileSystem.copy(from: repositoryPath.appending(".git"), to: noCheckoutRepositoryPath) - XCTAssertTrue(try provider.isValidDirectory(noCheckoutRepositoryPath)) + #expect(try provider.isValidDirectory(noCheckoutRepositoryPath)) // non-git directory let notGitPath = sandbox.appending("test-not-git") - XCTAssertThrowsError(try provider.isValidDirectory(notGitPath)) + #expect(throws: (any Error).self) { + try provider.isValidDirectory(notGitPath) + } // non-git child directory of a git directory let notGitChildPath = repositoryPath.appending("test-not-git") - XCTAssertThrowsError(try provider.isValidDirectory(notGitChildPath)) + #expect(throws: (any Error).self) { + try provider.isValidDirectory(notGitChildPath) + } } } - func testIsValidDirectoryThrowsPrintableError() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func isValidDirectoryThrowsPrintableError() throws { try testWithTemporaryDirectory { temp in let provider = GitRepositoryProvider() let expectedErrorMessage = "not a git repository" - XCTAssertThrowsError(try provider.isValidDirectory(temp)) { error in + #expect { + try provider.isValidDirectory(temp) + } throws: { error in let errorString = String(describing: error) - XCTAssertTrue( - errorString.contains(expectedErrorMessage), - "Error string '\(errorString)' should contain '\(expectedErrorMessage)'" - ) + let matched = errorString.contains(expectedErrorMessage) + return matched } } } - func testGitShellErrorIsPrintable() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func gitShellErrorIsPrintable() throws { let stdOut = "An error from Git - stdout" let stdErr = "An error from Git - stderr" let arguments = ["git", "error"] @@ -74,22 +86,22 @@ class GitRepositoryProviderTests: XCTestCase { ) let error = GitShellError(result: result) let errorString = "\(error)" - XCTAssertTrue( + #expect( errorString.contains(stdOut), - "Error string '\(errorString)' should contain '\(stdOut)'" - ) - XCTAssertTrue( + "Error string '\(errorString)' should contain '\(stdOut)'") + #expect( errorString.contains(stdErr), - "Error string '\(errorString)' should contain '\(stdErr)'" - ) - XCTAssertTrue( + "Error string '\(errorString)' should contain '\(stdErr)'") + #expect( errorString.contains(command), - "Error string '\(errorString)' should contain '\(command)'" - ) + "Error string '\(errorString)' should contain '\(command)'") } - func testGitShellErrorEmptyStdOut() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func gitShellErrorEmptyStdOut() throws { let stdErr = "An error from Git - stderr" let result = AsyncProcessResult( arguments: ["git", "error"], @@ -100,14 +112,16 @@ class GitRepositoryProviderTests: XCTestCase { ) let error = GitShellError(result: result) let errorString = "\(error)" - XCTAssertTrue( + #expect( errorString.contains(stdErr), - "Error string '\(errorString)' should contain '\(stdErr)'" - ) + "Error string '\(errorString)' should contain '\(stdErr)'") } - func testGitShellErrorEmptyStdErr() throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8564", skipSelfHostedCI: true) + @Test( + .bug("https://github.com/swiftlang/swift-package-manager/issues/8564"), + .disabled(if: isSelfHostedCiEnvironment && ProcessInfo.hostOperatingSystem == .windows), + ) + func gitShellErrorEmptyStdErr() throws { let stdOut = "An error from Git - stdout" let result = AsyncProcessResult( arguments: ["git", "error"], @@ -118,7 +132,7 @@ class GitRepositoryProviderTests: XCTestCase { ) let error = GitShellError(result: result) let errorString = "\(error)" - XCTAssertTrue( + #expect( errorString.contains(stdOut), "Error string '\(errorString)' should contain '\(stdOut)'" ) diff --git a/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift b/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift index d2ab60b6b80..416fac6164f 100644 --- a/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift +++ b/Tests/SourceControlTests/InMemoryGitRepositoryTests.swift @@ -9,69 +9,72 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics import SourceControl import _InternalTestSupport -import XCTest +import Testing -final class InMemoryGitRepositoryTests: XCTestCase { - func testBasics() throws { +struct InMemoryGitRepositoryTests { + @Test + func basics() throws { let fs = InMemoryFileSystem() let repo = InMemoryGitRepository(path: .root, fs: fs) try repo.createDirectory("/new-dir/subdir", recursive: true) - XCTAssertTrue(!repo.hasUncommittedChanges()) + #expect(!repo.hasUncommittedChanges()) let filePath = AbsolutePath("/new-dir/subdir").appending("new-file.txt") try repo.writeFileContents(filePath, bytes: "one") - XCTAssertEqual(try repo.readFileContents(filePath), "one") - XCTAssertTrue(repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "one") + #expect(repo.hasUncommittedChanges()) let firstCommit = try repo.commit() - XCTAssertTrue(!repo.hasUncommittedChanges()) + #expect(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "one") - XCTAssertEqual(try fs.readFileContents(filePath), "one") + #expect(try repo.readFileContents(filePath) == "one") + #expect(try fs.readFileContents(filePath) == "one") try repo.writeFileContents(filePath, bytes: "two") - XCTAssertEqual(try repo.readFileContents(filePath), "two") - XCTAssertTrue(repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") + #expect(repo.hasUncommittedChanges()) let secondCommit = try repo.commit() - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") try repo.writeFileContents(filePath, bytes: "three") - XCTAssertTrue(repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "three") + #expect(repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "three") try repo.checkout(revision: firstCommit) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "one") - XCTAssertEqual(try fs.readFileContents(filePath), "one") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "one") + #expect(try fs.readFileContents(filePath) == "one") try repo.checkout(revision: secondCommit) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") - XCTAssert(try repo.getTags().isEmpty) + #expect(try repo.getTags().isEmpty) try repo.tag(name: "2.0.0") - XCTAssertEqual(try repo.getTags(), ["2.0.0"]) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") - XCTAssertEqual(try fs.readFileContents(filePath), "two") + #expect(try repo.getTags() == ["2.0.0"]) + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") + #expect(try fs.readFileContents(filePath) == "two") try repo.checkout(revision: firstCommit) - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "one") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "one") try repo.checkout(tag: "2.0.0") - XCTAssertTrue(!repo.hasUncommittedChanges()) - XCTAssertEqual(try repo.readFileContents(filePath), "two") + #expect(!repo.hasUncommittedChanges()) + #expect(try repo.readFileContents(filePath) == "two") } - func testProvider() throws { + @Test + func provider() throws { let v1 = "1.0.0" let v2 = "2.0.0" let repo = InMemoryGitRepository(path: .root, fs: InMemoryFileSystem()) @@ -95,23 +98,23 @@ final class InMemoryGitRepositoryTests: XCTestCase { // Adding a new tag in original repo shouldn't show up in fetched repo. try repo.tag(name: "random") - XCTAssertEqual(try fooRepo.getTags().sorted(), [v1, v2]) - XCTAssert(fooRepo.exists(revision: try fooRepo.resolveRevision(tag: v1))) + #expect(try fooRepo.getTags().sorted() == [v1, v2]) + #expect(fooRepo.exists(revision: try fooRepo.resolveRevision(tag: v1))) let fooCheckoutPath = AbsolutePath("/fooCheckout") - XCTAssertFalse(try provider.workingCopyExists(at: fooCheckoutPath)) + #expect(!(try provider.workingCopyExists(at: fooCheckoutPath))) _ = try provider.createWorkingCopy(repository: specifier, sourcePath: fooRepoPath, at: fooCheckoutPath, editable: false) - XCTAssertTrue(try provider.workingCopyExists(at: fooCheckoutPath)) + #expect(try provider.workingCopyExists(at: fooCheckoutPath)) let fooCheckout = try provider.openWorkingCopy(at: fooCheckoutPath) - XCTAssertEqual(try fooCheckout.getTags().sorted(), [v1, v2]) - XCTAssert(fooCheckout.exists(revision: try fooCheckout.getCurrentRevision())) + #expect(try fooCheckout.getTags().sorted() == [v1, v2]) + #expect(fooCheckout.exists(revision: try fooCheckout.getCurrentRevision())) let checkoutRepo = try provider.openRepo(at: fooCheckoutPath) try fooCheckout.checkout(tag: v1) - XCTAssertEqual(try checkoutRepo.readFileContents(filePath), "one") + #expect(try checkoutRepo.readFileContents(filePath) == "one") try fooCheckout.checkout(tag: v2) - XCTAssertEqual(try checkoutRepo.readFileContents(filePath), "two") + #expect(try checkoutRepo.readFileContents(filePath) == "two") } } From 98726b9d1512fad41c20ee8e91b432aaf8060725 Mon Sep 17 00:00:00 2001 From: Jake Petroules Date: Fri, 2 May 2025 09:06:33 -0700 Subject: [PATCH 75/99] Bump iOS deployment target to 17 to match dependencies --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 597653249d7..e989267b4dd 100644 --- a/Package.swift +++ b/Package.swift @@ -104,7 +104,7 @@ let package = Package( name: "SwiftPM", platforms: [ .macOS(.v13), - .iOS(.v16), + .iOS(.v17), .macCatalyst(.v17), ], products: From 8694d3c5a11914eafa0837f5a236cef2a653af07 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Sun, 4 May 2025 13:43:34 -0400 Subject: [PATCH 76/99] Tests: Convert some WorkspaceTests module tests to Swift Testing (#8092) --- .../AuthorizationProviderTests.swift | 84 +++--- .../MirrorsConfigurationTests.swift | 58 ++-- ...sVersionSpecificationGenerationTests.swift | 43 +-- ...olsVersionSpecificationRewriterTests.swift | 277 +++++++++--------- .../WorkspaceTests/WorkspaceStateTests.swift | 60 ++-- 5 files changed, 270 insertions(+), 252 deletions(-) diff --git a/Tests/WorkspaceTests/AuthorizationProviderTests.swift b/Tests/WorkspaceTests/AuthorizationProviderTests.swift index 4e8bebabed2..a49d0e44705 100644 --- a/Tests/WorkspaceTests/AuthorizationProviderTests.swift +++ b/Tests/WorkspaceTests/AuthorizationProviderTests.swift @@ -9,14 +9,16 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation @testable import Basics import _InternalTestSupport import Workspace -import XCTest +import Testing -final class AuthorizationProviderTests: XCTestCase { - func testNetrcAuthorizationProviders() throws { +fileprivate struct AuthorizationProviderTests { + @Test + func netrcAuthorizationProviders() throws { let observability = ObservabilitySystem.makeForTesting() // custom .netrc file @@ -32,19 +34,20 @@ final class AuthorizationProviderTests: XCTestCase { let configuration = Workspace.Configuration.Authorization(netrc: .custom(customPath), keychain: .disabled) let authorizationProvider = try configuration.makeAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) as? CompositeAuthorizationProvider - let netrcProviders = authorizationProvider?.providers.compactMap { $0 as? NetrcAuthorizationProvider } + let netrcProviders = try #require(authorizationProvider?.providers.compactMap { $0 as? NetrcAuthorizationProvider }) - XCTAssertEqual(netrcProviders?.count, 1) - XCTAssertEqual(try netrcProviders?.first.map { try resolveSymlinks($0.path) }, try resolveSymlinks(customPath)) + let expectedNetrcProvider = try resolveSymlinks(customPath) + #expect(netrcProviders.count == 1) + #expect(try netrcProviders.first.map { try resolveSymlinks($0.path) } == expectedNetrcProvider) - let auth = authorizationProvider?.authentication(for: "https://mymachine.labkey.org") - XCTAssertEqual(auth?.user, "custom@labkey.org") - XCTAssertEqual(auth?.password, "custom") + let auth = try #require(authorizationProvider?.authentication(for: "https://mymachine.labkey.org")) + #expect(auth.user == "custom@labkey.org") + #expect(auth.password == "custom") // delete it try fileSystem.removeFileTree(customPath) - XCTAssertThrowsError(try configuration.makeAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope), "error expected") { error in - XCTAssertEqual(error as? StringError, StringError("Did not find netrc file at \(customPath).")) + #expect(throws: StringError("Did not find netrc file at \(customPath).")) { + try configuration.makeAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) } } @@ -61,25 +64,27 @@ final class AuthorizationProviderTests: XCTestCase { let configuration = Workspace.Configuration.Authorization(netrc: .user, keychain: .disabled) let authorizationProvider = try configuration.makeAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) as? CompositeAuthorizationProvider - let netrcProviders = authorizationProvider?.providers.compactMap { $0 as? NetrcAuthorizationProvider } + let netrcProviders = try #require(authorizationProvider?.providers.compactMap { $0 as? NetrcAuthorizationProvider }) - XCTAssertEqual(netrcProviders?.count, 1) - XCTAssertEqual(try netrcProviders?.first.map { try resolveSymlinks($0.path) }, try resolveSymlinks(userPath)) + let expectedNetrcProvider = try resolveSymlinks(userPath) + #expect(netrcProviders.count == 1) + #expect(try netrcProviders.first.map { try resolveSymlinks($0.path) } == expectedNetrcProvider) - let auth = authorizationProvider?.authentication(for: "https://mymachine.labkey.org") - XCTAssertEqual(auth?.user, "user@labkey.org") - XCTAssertEqual(auth?.password, "user") + let auth = try #require(authorizationProvider?.authentication(for: "https://mymachine.labkey.org")) + #expect(auth.user == "user@labkey.org") + #expect(auth.password == "user") // delete it do { try fileSystem.removeFileTree(userPath) let authorizationProvider = try configuration.makeAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) as? CompositeAuthorizationProvider - XCTAssertNil(authorizationProvider) + #expect(authorizationProvider == nil) } } } - func testRegistryNetrcAuthorizationProviders() throws { + @Test + func registryNetrcAuthorizationProviders() throws { let observability = ObservabilitySystem.makeForTesting() // custom .netrc file @@ -97,17 +102,18 @@ final class AuthorizationProviderTests: XCTestCase { let configuration = Workspace.Configuration.Authorization(netrc: .custom(customPath), keychain: .disabled) let netrcProvider = try configuration.makeRegistryAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) as? NetrcAuthorizationProvider - XCTAssertNotNil(netrcProvider) - XCTAssertEqual(try netrcProvider.map { try resolveSymlinks($0.path) }, try resolveSymlinks(customPath)) + let expectedNetrcProvider = try resolveSymlinks(customPath) + #expect(netrcProvider != nil) + #expect(try netrcProvider.map { try resolveSymlinks($0.path) } == expectedNetrcProvider) - let auth = netrcProvider?.authentication(for: "https://mymachine.labkey.org") - XCTAssertEqual(auth?.user, "custom@labkey.org") - XCTAssertEqual(auth?.password, "custom") + let auth = try #require(netrcProvider?.authentication(for: "https://mymachine.labkey.org")) + #expect(auth.user == "custom@labkey.org") + #expect(auth.password == "custom") // delete it try fileSystem.removeFileTree(customPath) - XCTAssertThrowsError(try configuration.makeRegistryAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope), "error expected") { error in - XCTAssertEqual(error as? StringError, StringError("did not find netrc file at \(customPath)")) + #expect(throws: StringError("did not find netrc file at \(customPath)")) { + try configuration.makeRegistryAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) } } @@ -126,22 +132,28 @@ final class AuthorizationProviderTests: XCTestCase { let configuration = Workspace.Configuration.Authorization(netrc: .user, keychain: .disabled) let netrcProvider = try configuration.makeRegistryAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) as? NetrcAuthorizationProvider - XCTAssertNotNil(netrcProvider) - XCTAssertEqual(try netrcProvider.map { try resolveSymlinks($0.path) }, try resolveSymlinks(userPath)) + let expectedNetrcProvider = try resolveSymlinks(userPath) + #expect(netrcProvider != nil) + #expect(try netrcProvider.map { try resolveSymlinks($0.path) } == expectedNetrcProvider) - let auth = netrcProvider?.authentication(for: "https://mymachine.labkey.org") - XCTAssertEqual(auth?.user, "user@labkey.org") - XCTAssertEqual(auth?.password, "user") + let auth = try #require(netrcProvider?.authentication(for: "https://mymachine.labkey.org")) + #expect(auth.user == "user@labkey.org") + #expect(auth.password == "user") // delete it do { try fileSystem.removeFileTree(userPath) - let authorizationProvider = try configuration.makeRegistryAuthorizationProvider(fileSystem: fileSystem, observabilityScope: observability.topScope) as? NetrcAuthorizationProvider + let authorizationProviderOpt = + try configuration.makeRegistryAuthorizationProvider( + fileSystem: fileSystem, + observabilityScope: observability.topScope, + ) as? NetrcAuthorizationProvider // Even if user .netrc file doesn't exist, the provider will be non-nil but contain no data. - XCTAssertNotNil(authorizationProvider) - XCTAssertEqual(try authorizationProvider.map { try resolveSymlinks($0.path) }, try resolveSymlinks(userPath)) - - XCTAssertTrue(authorizationProvider!.machines.isEmpty) + let expectedAuthorizationProvider = try resolveSymlinks(userPath) + let authorizationProvider: NetrcAuthorizationProvider = try #require( + authorizationProviderOpt) + #expect(authorizationProvider.path == expectedAuthorizationProvider) + #expect(authorizationProvider.machines.isEmpty) } } } diff --git a/Tests/WorkspaceTests/MirrorsConfigurationTests.swift b/Tests/WorkspaceTests/MirrorsConfigurationTests.swift index d91861303c1..7d584bcab17 100644 --- a/Tests/WorkspaceTests/MirrorsConfigurationTests.swift +++ b/Tests/WorkspaceTests/MirrorsConfigurationTests.swift @@ -13,10 +13,11 @@ import Basics import _InternalTestSupport import Workspace -import XCTest +import Testing -final class MirrorsConfigurationTests: XCTestCase { - func testLoadingSchema1() throws { +fileprivate struct MirrorsConfigurationTests { + @Test + func loadingSchema1() throws { let fs = InMemoryFileSystem() let configFile = AbsolutePath("/config/mirrors.json") @@ -42,30 +43,33 @@ final class MirrorsConfigurationTests: XCTestCase { let config = Workspace.Configuration.MirrorsStorage(path: configFile, fileSystem: fs, deleteWhenEmpty: true) let mirrors = try config.get() - XCTAssertEqual(mirrors.mirror(for: originalURL),mirrorURL) - XCTAssertEqual(mirrors.original(for: mirrorURL), originalURL) + #expect(mirrors.mirror(for: originalURL) == mirrorURL) + #expect(mirrors.original(for: mirrorURL) == originalURL) } - func testThrowsWhenNotFound() throws { + @Test + func throwsWhenNotFound() throws { + let gitUrl = "https://github.com/apple/swift-argument-parser.git" let fs = InMemoryFileSystem() let configFile = AbsolutePath("/config/mirrors.json") let config = Workspace.Configuration.MirrorsStorage(path: configFile, fileSystem: fs, deleteWhenEmpty: true) let mirrors = try config.get() - XCTAssertThrows(StringError("Mirror not found for 'https://github.com/apple/swift-argument-parser.git'")) { - try mirrors.unset(originalOrMirror: "https://github.com/apple/swift-argument-parser.git") + #expect(throws: StringError("Mirror not found for '\(gitUrl)'")) { + try mirrors.unset(originalOrMirror: gitUrl) } } - func testDeleteWhenEmpty() throws { + @Test + func deleteWhenEmpty() throws { let fs = InMemoryFileSystem() let configFile = AbsolutePath("/config/mirrors.json") let config = Workspace.Configuration.MirrorsStorage(path: configFile, fileSystem: fs, deleteWhenEmpty: true) try config.apply{ _ in } - XCTAssertFalse(fs.exists(configFile)) + #expect(!fs.exists(configFile)) let originalURL = "https://github.com/apple/swift-argument-parser.git" let mirrorURL = "https://github.com/mona/swift-argument-parser.git" @@ -73,22 +77,23 @@ final class MirrorsConfigurationTests: XCTestCase { try config.apply{ mirrors in try mirrors.set(mirror: mirrorURL, for: originalURL) } - XCTAssertTrue(fs.exists(configFile)) + #expect(fs.exists(configFile)) try config.apply{ mirrors in try mirrors.unset(originalOrMirror: originalURL) } - XCTAssertFalse(fs.exists(configFile)) + #expect(!fs.exists(configFile)) } - func testDontDeleteWhenEmpty() throws { + @Test + func dontDeleteWhenEmpty() throws { let fs = InMemoryFileSystem() let configFile = AbsolutePath("/config/mirrors.json") let config = Workspace.Configuration.MirrorsStorage(path: configFile, fileSystem: fs, deleteWhenEmpty: false) try config.apply{ _ in } - XCTAssertFalse(fs.exists(configFile)) + #expect(!fs.exists(configFile)) let originalURL = "https://github.com/apple/swift-argument-parser.git" let mirrorURL = "https://github.com/mona/swift-argument-parser.git" @@ -96,16 +101,17 @@ final class MirrorsConfigurationTests: XCTestCase { try config.apply{ mirrors in try mirrors.set(mirror: mirrorURL, for: originalURL) } - XCTAssertTrue(fs.exists(configFile)) + #expect(fs.exists(configFile)) try config.apply{ mirrors in try mirrors.unset(originalOrMirror: originalURL) } - XCTAssertTrue(fs.exists(configFile)) - XCTAssertTrue(try config.get().isEmpty) + #expect(fs.exists(configFile)) + #expect(try config.get().isEmpty) } - func testLocalAndShared() throws { + @Test + func localAndShared() throws { let fs = InMemoryFileSystem() let localConfigFile = AbsolutePath("/config/local-mirrors.json") let sharedConfigFile = AbsolutePath("/config/shared-mirrors.json") @@ -125,9 +131,9 @@ final class MirrorsConfigurationTests: XCTestCase { try mirrors.set(mirror: mirror1URL, for: original1URL) } - XCTAssertEqual(config.mirrors.count, 1) - XCTAssertEqual(config.mirrors.mirror(for: original1URL), mirror1URL) - XCTAssertEqual(config.mirrors.original(for: mirror1URL), original1URL) + #expect(config.mirrors.count == 1) + #expect(config.mirrors.mirror(for: original1URL) == mirror1URL) + #expect(config.mirrors.original(for: mirror1URL) == original1URL) // now write to local location @@ -138,12 +144,12 @@ final class MirrorsConfigurationTests: XCTestCase { try mirrors.set(mirror: mirror2URL, for: original2URL) } - XCTAssertEqual(config.mirrors.count, 1) - XCTAssertEqual(config.mirrors.mirror(for: original2URL), mirror2URL) - XCTAssertEqual(config.mirrors.original(for: mirror2URL), original2URL) + #expect(config.mirrors.count == 1) + #expect(config.mirrors.mirror(for: original2URL) == mirror2URL) + #expect(config.mirrors.original(for: mirror2URL) == original2URL) // should not see the shared any longer - XCTAssertEqual(config.mirrors.mirror(for: original1URL), nil) - XCTAssertEqual(config.mirrors.original(for: mirror1URL), nil) + #expect(config.mirrors.mirror(for: original1URL) == nil) + #expect(config.mirrors.original(for: mirror1URL) == nil) } } diff --git a/Tests/WorkspaceTests/ToolsVersionSpecificationGenerationTests.swift b/Tests/WorkspaceTests/ToolsVersionSpecificationGenerationTests.swift index 0b5bed54c69..8e84be7d014 100644 --- a/Tests/WorkspaceTests/ToolsVersionSpecificationGenerationTests.swift +++ b/Tests/WorkspaceTests/ToolsVersionSpecificationGenerationTests.swift @@ -9,36 +9,45 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation /// /// This file tests the generation of a Swift tools version specification from a known version. /// -import XCTest +import Testing import PackageModel import struct TSCUtility.Version /// Test cases for the generation of Swift tools version specifications. -class ToolsVersionSpecificationGenerationTests: XCTestCase { +fileprivate struct ToolsVersionSpecificationGenerationTests { /// Tests the generation of Swift tools version specifications. - func testToolsVersionSpecificationGeneration() throws { + @Test + func toolsVersionSpecificationGeneration() throws { let versionWithNonZeroPatch = ToolsVersion(version: Version(4, 3, 2)) - XCTAssertEqual(versionWithNonZeroPatch.specification(), "// swift-tools-version:4.3.2") - XCTAssertEqual(versionWithNonZeroPatch.specification(roundedTo: .automatic), "// swift-tools-version:4.3.2") - XCTAssertEqual(versionWithNonZeroPatch.specification(roundedTo: .minor), "// swift-tools-version:4.3") - XCTAssertEqual(versionWithNonZeroPatch.specification(roundedTo: .patch), "// swift-tools-version:4.3.2") - + #expect(versionWithNonZeroPatch.specification() == "// swift-tools-version:4.3.2") + #expect(versionWithNonZeroPatch.specification(roundedTo: .automatic) == "// swift-tools-version:4.3.2") + #expect(versionWithNonZeroPatch.specification(roundedTo: .minor) == "// swift-tools-version:4.3") + #expect(versionWithNonZeroPatch.specification(roundedTo: .patch) == "// swift-tools-version:4.3.2") + let versionWithZeroPatch = ToolsVersion.v5_3 // 5.3.0 - XCTAssertEqual(versionWithZeroPatch.specification(), "// swift-tools-version:5.3") - XCTAssertEqual(versionWithZeroPatch.specification(roundedTo: .automatic), "// swift-tools-version:5.3") - XCTAssertEqual(versionWithZeroPatch.specification(roundedTo: .minor), "// swift-tools-version:5.3") - XCTAssertEqual(versionWithZeroPatch.specification(roundedTo: .patch), "// swift-tools-version:5.3.0") - + #expect(versionWithZeroPatch.specification() == "// swift-tools-version:5.3") + #expect(versionWithZeroPatch.specification(roundedTo: .automatic) == "// swift-tools-version:5.3") + #expect(versionWithZeroPatch.specification(roundedTo: .minor) == "// swift-tools-version:5.3") + #expect(versionWithZeroPatch.specification(roundedTo: .patch) == "// swift-tools-version:5.3.0") + let newMajorVersion = ToolsVersion.v5 // 5.0.0 - XCTAssertEqual(newMajorVersion.specification(), "// swift-tools-version:5.0") - XCTAssertEqual(newMajorVersion.specification(roundedTo: .automatic), "// swift-tools-version:5.0") - XCTAssertEqual(newMajorVersion.specification(roundedTo: .minor), "// swift-tools-version:5.0") - XCTAssertEqual(newMajorVersion.specification(roundedTo: .patch), "// swift-tools-version:5.0.0") + #expect(newMajorVersion.specification() == "// swift-tools-version:5.0") + #expect(newMajorVersion.specification(roundedTo: .automatic) == "// swift-tools-version:5.0") + #expect(newMajorVersion.specification(roundedTo: .minor) == "// swift-tools-version:5.0") + #expect(newMajorVersion.specification(roundedTo: .patch) == "// swift-tools-version:5.0.0") + + let allZeroVersion = ToolsVersion(version: Version(0, 0, 0)) + #expect(allZeroVersion.specification() == "// swift-tools-version:0.0") + #expect(allZeroVersion.specification(roundedTo: .automatic) == "// swift-tools-version:0.0") + #expect(allZeroVersion.specification(roundedTo: .minor) == "// swift-tools-version:0.0") + #expect(allZeroVersion.specification(roundedTo: .patch) == "// swift-tools-version:0.0.0") } + } diff --git a/Tests/WorkspaceTests/ToolsVersionSpecificationRewriterTests.swift b/Tests/WorkspaceTests/ToolsVersionSpecificationRewriterTests.swift index 486951cbb26..9a67dd5ad5a 100644 --- a/Tests/WorkspaceTests/ToolsVersionSpecificationRewriterTests.swift +++ b/Tests/WorkspaceTests/ToolsVersionSpecificationRewriterTests.swift @@ -17,185 +17,170 @@ import Basics import PackageModel @testable import Workspace -import XCTest +import Testing /// Test cases for `rewriteToolsVersionSpecification(toDefaultManifestIn:specifying:fileSystem:)` -final class ToolsVersionSpecificationRewriterTests: XCTestCase { - - /// Tests `rewriteToolsVersionSpecification(toDefaultManifestIn:specifying:fileSystem:)`. - func testNonVersionSpecificManifests() throws { - // Empty file. - rewriteToolsVersionSpecificationToDefaultManifest(content: "") { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\n") - } - - // File with just a new line. - rewriteToolsVersionSpecificationToDefaultManifest(content: "\n") { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\n\n") - } - - // File with some contents. - rewriteToolsVersionSpecificationToDefaultManifest(content: "let package = ... \n") { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\nlet package = ... \n") - } +fileprivate struct ToolsVersionSpecificationRewriterTests { - // File already having a valid version specifier. - let content = """ - // swift-tools-version:3.1.2 - ... - """ - - rewriteToolsVersionSpecificationToDefaultManifest(content: content) { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\n...") - } + struct NonVersionSpecificManifestTestData: Identifiable { + let id: String + let content: String + let version: ToolsVersion + let expected: String + } + @Test( + arguments:[ + NonVersionSpecificManifestTestData( + id: "Empty file.", + content: "", + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\n" + ), + NonVersionSpecificManifestTestData( + id: "File with just a new line.", + content: "\n", + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\n\n" + ), + NonVersionSpecificManifestTestData( + id: "File with some contents.", + content: "let package = ... \n", + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\nlet package = ... \n" + ), + NonVersionSpecificManifestTestData( + id: "File already having a valid version specifier.", + content: """ + // swift-tools-version:3.1.2 + ... + """, + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\n..." + ), + NonVersionSpecificManifestTestData( + id: "File already having a valid version specifier.", + content: """ + // swift-tools-version:3.1.2 + ... + """, + version: ToolsVersion(version: "2.1.0"), + expected: "// swift-tools-version:2.1\n..." + ), + NonVersionSpecificManifestTestData( + id: "Contents with invalid tools version specification (ignoring the validity of the version specifier).", + content: """ + // swift-tool-version:3.1.2 + ... + """, + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\n// swift-tool-version:3.1.2\n..." + ), + NonVersionSpecificManifestTestData( + id: "Contents with invalid version specifier.", + content: """ + // swift-tools-version:3.1.2 + ... + """, + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\n..." + ), + NonVersionSpecificManifestTestData( + id: "Contents with invalid version specifier and some meta data.", + content: """ + // swift-tools-version:3.1.2 + ... + """, + version: ToolsVersion(version: "4.1.2"), + expected: "// swift-tools-version:4.1.2\n..." + ), + NonVersionSpecificManifestTestData( + id: "Try to write a version with prerelease and build meta data.", + content: "let package = ... \n", + version: ToolsVersion(version: "4.1.2-alpha.beta+sha.1234"), + expected: "// swift-tools-version:4.1.2\nlet package = ... \n" + ) + ] + ) + func nonVersionSpecificManifests(_ data: NonVersionSpecificManifestTestData) throws { + let content = data.content + let version = data.version + let expected = data.expected - // Write a version with zero in patch number. - rewriteToolsVersionSpecificationToDefaultManifest( - content: """ - // swift-tools-version:3.1.2 - ... - """, - version: ToolsVersion(version: "2.1.0") - ) { result in - XCTAssertEqual(result, "// swift-tools-version:2.1\n...") - } + let inMemoryFileSystem = InMemoryFileSystem() - // Contents with invalid tools version specification (ignoring the validity of the version specifier). - rewriteToolsVersionSpecificationToDefaultManifest( - content: """ - // swift-tool-version:3.1.2 - ... - """ - ) { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\n// swift-tool-version:3.1.2\n...") - } + let manifestFilePath = AbsolutePath("/pkg/Package.swift") - // Contents with invalid version specifier. - rewriteToolsVersionSpecificationToDefaultManifest( - content: """ - // swift-tools-version:3.1.2 - ... - """ - ) { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\n...") - } + try inMemoryFileSystem.createDirectory(manifestFilePath.parentDirectory, recursive: true) + try inMemoryFileSystem.writeFileContents(manifestFilePath, string: content) - // Contents with invalid version specifier and some meta data. - rewriteToolsVersionSpecificationToDefaultManifest( - content: """ - // swift-tools-version:3.1.2 - ... - """ - ) { result in - // Note: Right now we lose the metadata but if we ever start using it, we should preserve it. - XCTAssertEqual(result, "// swift-tools-version:4.1.2\n...") - } + try ToolsVersionSpecificationWriter.rewriteSpecification( + manifestDirectory: manifestFilePath.parentDirectory, + toolsVersion: version, + fileSystem: inMemoryFileSystem + ) - // Try to write a version with prerelease and build meta data. - let toolsVersion = ToolsVersion(version: "4.1.2-alpha.beta+sha.1234") - rewriteToolsVersionSpecificationToDefaultManifest( - content: "let package = ... \n", - version: toolsVersion - ) { result in - XCTAssertEqual(result, "// swift-tools-version:4.1.2\nlet package = ... \n") - } + // resultHandler(try inMemoryFileSystem.readFileContents(manifestFilePath)) + let actual = try #require(try inMemoryFileSystem.readFileContents(manifestFilePath)) + #expect(actual.validDescription == expected, "Actual is not expected") } - - func testManifestAccessFailures() throws { + + @Test + func manifestAccessFailures() throws { let toolsVersion = ToolsVersion.v5_3 - + let inMemoryFileSystem = InMemoryFileSystem() let manifestFilePath = AbsolutePath("/pkg/Package.swift/Package.swift") try inMemoryFileSystem.createDirectory(manifestFilePath.parentDirectory, recursive: true) // /pkg/Package.swift/ - + // Test `ManifestAccessError.Kind.isADirectory` - XCTAssertThrowsError( + + #expect{ try ToolsVersionSpecificationWriter.rewriteSpecification( manifestDirectory: manifestFilePath.parentDirectory.parentDirectory, // /pkg/ toolsVersion: toolsVersion, fileSystem: inMemoryFileSystem - ), - "'/pkg/Package.swift' is a directory, and an error should've been thrown" - ) { error in - guard let error = error as? ToolsVersionSpecificationWriter.ManifestAccessError else { - XCTFail("a ManifestAccessError should've been thrown") - return - } - XCTAssertEqual( - error.kind, - .isADirectory ) - XCTAssertEqual( - error.description, - "no accessible Swift Package Manager manifest file found at '\(manifestFilePath.parentDirectory)'; the path is a directory; a file is expected" // /pkg/Package.swift/ + } throws: { error in + let error = try #require( + error as? ToolsVersionSpecificationWriter.ManifestAccessError, + "a ManifestAccessError should've been thrown" ) + let isExpectedKind = (error.kind == .isADirectory) + let isExpectedDescription = (error.description == "no accessible Swift Package Manager manifest file found at '\(manifestFilePath.parentDirectory)'; the path is a directory; a file is expected") + + return isExpectedKind && isExpectedDescription } - + // Test `ManifestAccessError.Kind.noSuchFileOrDirectory` - XCTAssertThrowsError( + #expect { try ToolsVersionSpecificationWriter.rewriteSpecification( manifestDirectory: manifestFilePath.parentDirectory, // /pkg/Package.swift/ toolsVersion: toolsVersion, fileSystem: inMemoryFileSystem - ), - "'/pkg/Package.swift' is a directory, and an error should've been thrown" - ) { error in - guard let error = error as? ToolsVersionSpecificationWriter.ManifestAccessError else { - XCTFail("a ManifestAccessError should've been thrown") - return - } - XCTAssertEqual( - error.kind, - .noSuchFileOrDirectory ) - XCTAssertEqual( - error.description, - "no accessible Swift Package Manager manifest file found at '\(manifestFilePath)'; a component of the path does not exist, or the path is an empty string" // /pkg/Package.swift/Package.swift + } throws: { error in + let error = try #require( + error as? ToolsVersionSpecificationWriter.ManifestAccessError, + "a ManifestAccessError should've been thrown" ) + let isExpectedKind = (error.kind == .noSuchFileOrDirectory) + let isExpectedDescription = (error.description == "no accessible Swift Package Manager manifest file found at '\(manifestFilePath)'; a component of the path does not exist, or the path is an empty string") + + return isExpectedKind && isExpectedDescription } - - // TODO: Test `ManifestAccessError.Kind.unknown` } - + // Private functions are not run in tests. - private func testVersionSpecificManifests() throws { - // TODO: Add the functionality and tests for version-specific manifests too. - } + @Test + func versionSpecificManifests() throws { - func testZeroedPatchVersion() { - XCTAssertEqual(ToolsVersion(version: "4.2.1").zeroedPatch.description, "4.2.0") - XCTAssertEqual(ToolsVersion(version: "4.2.0").zeroedPatch.description, "4.2.0") - XCTAssertEqual(ToolsVersion(version: "6.0.129").zeroedPatch.description, "6.0.0") } - - /// Does the boilerplate filesystem preparations, then calls `rewriteToolsVersionSpecification(toDefaultManifestIn:specifying:fileSystem:)`, for `testNonVersionSpecificManifests()`. - /// - Parameters: - /// - stream: The stream to read from and write to the filesystem. - /// - version: The Swift tools version to specify. - /// - resultHandler: The result handler. - func rewriteToolsVersionSpecificationToDefaultManifest( - content: String, - version: ToolsVersion = ToolsVersion(version: "4.1.2"), - resultHandler: (String) -> Void - ) { - do { - let inMemoryFileSystem = InMemoryFileSystem() - - let manifestFilePath = AbsolutePath("/pkg/Package.swift") - - try inMemoryFileSystem.createDirectory(manifestFilePath.parentDirectory, recursive: true) - try inMemoryFileSystem.writeFileContents(manifestFilePath, string: content) - - try ToolsVersionSpecificationWriter.rewriteSpecification( - manifestDirectory: manifestFilePath.parentDirectory, - toolsVersion: version, - fileSystem: inMemoryFileSystem - ) - resultHandler(try inMemoryFileSystem.readFileContents(manifestFilePath)) - } catch { - XCTFail("Failed with error \(error)") - } + @Test + func zeroedPatchVersion() { + #expect(ToolsVersion(version: "4.2.1").zeroedPatch.description == "4.2.0") + #expect(ToolsVersion(version: "4.2.0").zeroedPatch.description == "4.2.0") + #expect(ToolsVersion(version: "6.0.129").zeroedPatch.description == "6.0.0") } - + } diff --git a/Tests/WorkspaceTests/WorkspaceStateTests.swift b/Tests/WorkspaceTests/WorkspaceStateTests.swift index fda8eac54d2..3785a12e2e3 100644 --- a/Tests/WorkspaceTests/WorkspaceStateTests.swift +++ b/Tests/WorkspaceTests/WorkspaceStateTests.swift @@ -9,13 +9,13 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// - import Basics @testable import Workspace -import XCTest +import Testing -final class WorkspaceStateTests: XCTestCase { - func testV4Format() async throws { +fileprivate struct WorkspaceStateTests { + @Test + func v4Format() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -83,12 +83,13 @@ final class WorkspaceStateTests: XCTestCase { ) let dependencies = await WorkspaceState(fileSystem: fs, storageDirectory: buildDir).dependencies - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("yams") })) - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-tools-support-core") })) - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-argument-parser") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("yams") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-tools-support-core") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-argument-parser") })) } - func testV4FormatWithPath() async throws { + @Test + func v4FormatWithPath() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -156,12 +157,13 @@ final class WorkspaceStateTests: XCTestCase { ) let dependencies = await WorkspaceState(fileSystem: fs, storageDirectory: buildDir).dependencies - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("yams") })) - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-tools-support-core") })) - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-argument-parser") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("yams") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-tools-support-core") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-argument-parser") })) } - func testV5Format() async throws { + @Test + func v5Format() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -229,12 +231,13 @@ final class WorkspaceStateTests: XCTestCase { ) let dependencies = await WorkspaceState(fileSystem: fs, storageDirectory: buildDir).dependencies - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("yams") })) - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-tools-support-core") })) - XCTAssertTrue(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-argument-parser") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("yams") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-tools-support-core") })) + #expect(dependencies.contains(where: { $0.packageRef.identity == .plain("swift-argument-parser") })) } - func testSavedDependenciesAreSorted() async throws { + @Test + func savedDependenciesAreSorted() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -292,13 +295,14 @@ final class WorkspaceStateTests: XCTestCase { let serialized: String = try fs.readFileContents(statePath) - let argpRange = try XCTUnwrap(serialized.range(of: "swift-argument-parser")) - let yamsRange = try XCTUnwrap(serialized.range(of: "yams")) + let argpRange = try #require(serialized.range(of: "swift-argument-parser")) + let yamsRange = try #require(serialized.range(of: "yams")) - XCTAssertTrue(argpRange.lowerBound < yamsRange.lowerBound) + #expect(argpRange.lowerBound < yamsRange.lowerBound) } - func testArtifacts() async throws { + @Test + func artifacts() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -363,13 +367,14 @@ final class WorkspaceStateTests: XCTestCase { ) let artifacts = await WorkspaceState(fileSystem: fs, storageDirectory: buildDir).artifacts - XCTAssertTrue(artifacts.contains(where: { $0.packageRef.identity == .plain("foo") && $0.targetName == "foo" })) - XCTAssertTrue(artifacts.contains(where: { $0.packageRef.identity == .plain("foo") && $0.targetName == "bar" })) - XCTAssertTrue(artifacts.contains(where: { $0.packageRef.identity == .plain("bar") && $0.targetName == "bar" })) + #expect(artifacts.contains(where: { $0.packageRef.identity == .plain("foo") && $0.targetName == "foo" })) + #expect(artifacts.contains(where: { $0.packageRef.identity == .plain("foo") && $0.targetName == "bar" })) + #expect(artifacts.contains(where: { $0.packageRef.identity == .plain("bar") && $0.targetName == "bar" })) } // rdar://86857825 - func testDuplicateDependenciesDoNotCrash() async throws { + @Test + func duplicateDependenciesDoNotCrash() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -425,11 +430,12 @@ final class WorkspaceStateTests: XCTestCase { let dependencies = await WorkspaceState(fileSystem: fs, storageDirectory: buildDir).dependencies // empty since we have dups so we warn and fail the loading // TODO: test for diagnostics when we can get them from the WorkspaceState initializer - XCTAssertTrue(dependencies.isEmpty) + #expect(dependencies.isEmpty) } // rdar://86857825 - func testDuplicateArtifactsDoNotCrash() async throws { + @Test + func duplicateArtifactsDoNotCrash() async throws { let fs = InMemoryFileSystem() let buildDir = AbsolutePath("/.build") @@ -481,7 +487,7 @@ final class WorkspaceStateTests: XCTestCase { let artifacts = await WorkspaceState(fileSystem: fs, storageDirectory: buildDir).artifacts // empty since we have dups so we warn and fail the loading // TODO: test for diagnostics when we can get them from the WorkspaceState initializer - XCTAssertTrue(artifacts.isEmpty) + #expect(artifacts.isEmpty) } } From 968810513bbb54cc663208c7b59c7aba88578257 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Mon, 5 May 2025 18:06:47 -0400 Subject: [PATCH 77/99] Tests: Convert more BasicTests to Swift Testing (#8617) Convert the following test suites to Swift Testing - Tests/BasicsTests/ByteStringExtensionsTests.swift - Tests/BasicsTests/ConcurrencyHelpersTests.swift - Tests/BasicsTests/DictionaryTest.swift - Tests/BasicsTests/DispatchTimeTests.swift - Tests/BasicsTests/NetrcTests.swift - Tests/BasicsTests/TripleTests.swift Also, remove the phony and sample Swift Testing tests, which were used to ensure we don't regress. --- .../ByteStringExtensionsTests.swift | 13 +- .../BasicsTests/ConcurrencyHelpersTests.swift | 46 +- Tests/BasicsTests/DictionaryTest.swift | 21 +- Tests/BasicsTests/DispatchTimeTests.swift | 21 +- Tests/BasicsTests/NetrcTests.swift | 400 +++++++------- Tests/BasicsTests/PhonyTest.swift | 7 - Tests/BasicsTests/SampleTests.swift | 9 - Tests/BasicsTests/TripleTests.swift | 509 +++++++++++------- 8 files changed, 574 insertions(+), 452 deletions(-) delete mode 100644 Tests/BasicsTests/PhonyTest.swift delete mode 100644 Tests/BasicsTests/SampleTests.swift diff --git a/Tests/BasicsTests/ByteStringExtensionsTests.swift b/Tests/BasicsTests/ByteStringExtensionsTests.swift index 7659695c0e5..b26c4cce315 100644 --- a/Tests/BasicsTests/ByteStringExtensionsTests.swift +++ b/Tests/BasicsTests/ByteStringExtensionsTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,16 +11,17 @@ //===----------------------------------------------------------------------===// import Basics -import XCTest +import Testing import struct TSCBasic.ByteString -final class ByteStringExtensionsTests: XCTestCase { - func testSHA256Checksum() { +struct ByteStringExtensionsTests { + @Test + func sHA256Checksum() { let byteString = ByteString(encodingAsUTF8: "abc") - XCTAssertEqual(byteString.contents, [0x61, 0x62, 0x63]) + #expect(byteString.contents == [0x61, 0x62, 0x63]) // See https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf - XCTAssertEqual(byteString.sha256Checksum, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") + #expect(byteString.sha256Checksum == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") } } diff --git a/Tests/BasicsTests/ConcurrencyHelpersTests.swift b/Tests/BasicsTests/ConcurrencyHelpersTests.swift index 2efa891fded..0b589c5f4b1 100644 --- a/Tests/BasicsTests/ConcurrencyHelpersTests.swift +++ b/Tests/BasicsTests/ConcurrencyHelpersTests.swift @@ -2,22 +2,24 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation @testable import Basics import TSCTestSupport -import XCTest +import Testing -final class ConcurrencyHelpersTest: XCTestCase { +struct ConcurrencyHelpersTest { let queue = DispatchQueue(label: "ConcurrencyHelpersTest", attributes: .concurrent) - func testThreadSafeKeyValueStore() { + @Test + func threadSafeKeyValueStore() throws { for _ in 0 ..< 100 { let sync = DispatchGroup() @@ -41,18 +43,15 @@ final class ConcurrencyHelpersTest: XCTestCase { } } - switch sync.wait(timeout: .now() + .seconds(2)) { - case .timedOut: - XCTFail("timeout") - case .success: - expected.forEach { key, value in - XCTAssertEqual(cache[key], value) - } + try #require(sync.wait(timeout: .now() + .seconds(2)) == .success) + expected.forEach { key, value in + #expect(cache[key] == value) } } } - func testThreadSafeArrayStore() { + @Test + func threadSafeArrayStore() throws { for _ in 0 ..< 100 { let sync = DispatchGroup() @@ -71,18 +70,15 @@ final class ConcurrencyHelpersTest: XCTestCase { } } - switch sync.wait(timeout: .now() + .seconds(2)) { - case .timedOut: - XCTFail("timeout") - case .success: - let expectedSorted = expected.sorted() - let resultsSorted = cache.get().sorted() - XCTAssertEqual(expectedSorted, resultsSorted) - } + try #require(sync.wait(timeout: .now() + .seconds(2)) == .success) + let expectedSorted = expected.sorted() + let resultsSorted = cache.get().sorted() + #expect(expectedSorted == resultsSorted) } } - func testThreadSafeBox() { + @Test + func threadSafeBox() throws { for _ in 0 ..< 100 { let sync = DispatchGroup() @@ -108,12 +104,8 @@ final class ConcurrencyHelpersTest: XCTestCase { } } - switch sync.wait(timeout: .now() + .seconds(2)) { - case .timedOut: - XCTFail("timeout") - case .success: - XCTAssertEqual(cache.get(), winner) - } + try #require(sync.wait(timeout: .now() + .seconds(2)) == .success) + #expect(cache.get() == winner) } } } diff --git a/Tests/BasicsTests/DictionaryTest.swift b/Tests/BasicsTests/DictionaryTest.swift index 88e4cbbd913..70f00db7d18 100644 --- a/Tests/BasicsTests/DictionaryTest.swift +++ b/Tests/BasicsTests/DictionaryTest.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,26 +11,27 @@ //===----------------------------------------------------------------------===// @testable import Basics -import XCTest +import Testing -final class DictionaryTests: XCTestCase { - func testThrowingUniqueKeysWithValues() throws { +struct DictionaryTests { + @Test + func throwingUniqueKeysWithValues() throws { do { let keysWithValues = [("key1", "value1"), ("key2", "value2")] let dictionary = try Dictionary(throwingUniqueKeysWithValues: keysWithValues) - XCTAssertEqual(dictionary["key1"], "value1") - XCTAssertEqual(dictionary["key2"], "value2") + #expect(dictionary["key1"] == "value1") + #expect(dictionary["key2"] == "value2") } do { let keysWithValues = [("key1", "value"), ("key2", "value")] let dictionary = try Dictionary(throwingUniqueKeysWithValues: keysWithValues) - XCTAssertEqual(dictionary["key1"], "value") - XCTAssertEqual(dictionary["key2"], "value") + #expect(dictionary["key1"] == "value") + #expect(dictionary["key2"] == "value") } do { let keysWithValues = [("key", "value1"), ("key", "value2")] - XCTAssertThrowsError(try Dictionary(throwingUniqueKeysWithValues: keysWithValues)) { error in - XCTAssertEqual(error as? StringError, StringError("duplicate key found: 'key'")) + #expect(throws: StringError("duplicate key found: 'key'")) { + try Dictionary(throwingUniqueKeysWithValues: keysWithValues) } } } diff --git a/Tests/BasicsTests/DispatchTimeTests.swift b/Tests/BasicsTests/DispatchTimeTests.swift index 12286be62e4..b9fa2175b0e 100644 --- a/Tests/BasicsTests/DispatchTimeTests.swift +++ b/Tests/BasicsTests/DispatchTimeTests.swift @@ -2,37 +2,40 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics -import XCTest +import Testing -final class DispatchTimeTests: XCTestCase { - func testDifferencePositive() { +struct DispatchTimeTests { + @Test + func differencePositive() { let point: DispatchTime = .now() let future: DispatchTime = point + .seconds(10) let diff1: DispatchTimeInterval = point.distance(to: future) - XCTAssertEqual(diff1.seconds(), 10) + #expect(diff1.seconds() == 10) let diff2: DispatchTimeInterval = future.distance(to: point) - XCTAssertEqual(diff2.seconds(), -10) + #expect(diff2.seconds() == -10) } - func testDifferenceNegative() { + @Test + func differenceNegative() { let point: DispatchTime = .now() let past: DispatchTime = point - .seconds(10) let diff1: DispatchTimeInterval = point.distance(to: past) - XCTAssertEqual(diff1.seconds(), -10) + #expect(diff1.seconds() == -10) let diff2: DispatchTimeInterval = past.distance(to: point) - XCTAssertEqual(diff2.seconds(), 10) + #expect(diff2.seconds() == 10) } } diff --git a/Tests/BasicsTests/NetrcTests.swift b/Tests/BasicsTests/NetrcTests.swift index 72e69bcbd29..d4ac3cca3b3 100644 --- a/Tests/BasicsTests/NetrcTests.swift +++ b/Tests/BasicsTests/NetrcTests.swift @@ -9,32 +9,35 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics -import XCTest +import Testing -class NetrcTests: XCTestCase { +struct NetrcTests { /// should load machines for a given inline format - func testLoadMachinesInline() throws { + @Test + func loadMachinesInline() throws { let content = "machine example.com login anonymous password qwerty" let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 1) + #expect(netrc.machines.count == 1) - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qwerty") + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") let authorization = netrc.authorization(for: "http://example.com/resource.zip") - XCTAssertEqual(authorization, Netrc.Authorization(login: "anonymous", password: "qwerty")) + #expect(authorization == Netrc.Authorization(login: "anonymous", password: "qwerty")) - XCTAssertNil(netrc.authorization(for: "http://example2.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "http://www.example2.com/resource.zip")) + #expect(netrc.authorization(for: "http://example2.com/resource.zip") == nil) + #expect(netrc.authorization(for: "http://www.example2.com/resource.zip") == nil) } /// should load machines for a given multi-line format - func testLoadMachinesMultiLine() throws { + @Test + func loadMachinesMultiLine() throws { let content = """ machine example.com login anonymous @@ -42,22 +45,23 @@ class NetrcTests: XCTestCase { """ let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 1) + #expect(netrc.machines.count == 1) - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qwerty") + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") let authorization = netrc.authorization(for: "http://example.com/resource.zip") - XCTAssertEqual(authorization, Netrc.Authorization(login: "anonymous", password: "qwerty")) + #expect(authorization == Netrc.Authorization(login: "anonymous", password: "qwerty")) - XCTAssertNil(netrc.authorization(for: "http://example2.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "http://www.example2.com/resource.zip")) + #expect(netrc.authorization(for: "http://example2.com/resource.zip") == nil) + #expect(netrc.authorization(for: "http://www.example2.com/resource.zip") == nil) } /// Should fall back to default machine when not matching host - func testLoadDefaultMachine() throws { + @Test + func loadDefaultMachine() throws { let content = """ machine example.com login anonymous @@ -69,23 +73,24 @@ class NetrcTests: XCTestCase { """ let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 2) + #expect(netrc.machines.count == 2) - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qwerty") + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") - let machine2 = netrc.machines.last - XCTAssertEqual(machine2?.name, "default") - XCTAssertEqual(machine2?.login, "id") - XCTAssertEqual(machine2?.password, "secret") + let machine2 = try #require(netrc.machines.last) + #expect(machine2.name == "default") + #expect(machine2.login == "id") + #expect(machine2.password == "secret") let authorization = netrc.authorization(for: "http://example2.com/resource.zip") - XCTAssertEqual(authorization, Netrc.Authorization(login: "id", password: "secret")) + #expect(authorization == Netrc.Authorization(login: "id", password: "secret")) } - func testRegexParsing() throws { + @Test + func regexParsing() throws { let content = """ machine machine login login @@ -109,25 +114,26 @@ class NetrcTests: XCTestCase { """ let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 3) + #expect(netrc.machines.count == 3) - XCTAssertEqual(netrc.machines[0].name, "machine") - XCTAssertEqual(netrc.machines[0].login, "login") - XCTAssertEqual(netrc.machines[0].password, "password") + #expect(netrc.machines[0].name == "machine") + #expect(netrc.machines[0].login == "login") + #expect(netrc.machines[0].password == "password") - XCTAssertEqual(netrc.machines[1].name, "login") - XCTAssertEqual(netrc.machines[1].login, "password") - XCTAssertEqual(netrc.machines[1].password, "machine") + #expect(netrc.machines[1].name == "login") + #expect(netrc.machines[1].login == "password") + #expect(netrc.machines[1].password == "machine") - XCTAssertEqual(netrc.machines[2].name, "default") - XCTAssertEqual(netrc.machines[2].login, "id") - XCTAssertEqual(netrc.machines[2].password, "secret") + #expect(netrc.machines[2].name == "default") + #expect(netrc.machines[2].login == "id") + #expect(netrc.machines[2].password == "secret") let authorization = netrc.authorization(for: "http://example2.com/resource.zip") - XCTAssertEqual(authorization, Netrc.Authorization(login: "id", password: "secret")) + #expect(authorization == Netrc.Authorization(login: "id", password: "secret")) } - func testOutOfOrderDefault() { + @Test + func outOfOrderDefault() { let content = """ machine machine login login @@ -146,12 +152,13 @@ class NetrcTests: XCTestCase { password secret """ - XCTAssertThrowsError(try NetrcParser.parse(content)) { error in - XCTAssertEqual(error as? NetrcError, .invalidDefaultMachinePosition) + #expect(throws: NetrcError.invalidDefaultMachinePosition) { + try NetrcParser.parse(content) } } - func testErrorOnMultipleDefault() { + @Test + func errorOnMultipleDefault() { let content = """ machine machine login login @@ -174,13 +181,14 @@ class NetrcTests: XCTestCase { password terces """ - XCTAssertThrowsError(try NetrcParser.parse(content)) { error in - XCTAssertEqual(error as? NetrcError, .invalidDefaultMachinePosition) + #expect(throws: NetrcError.invalidDefaultMachinePosition) { + try NetrcParser.parse(content) } } /// should load machines for a given multi-line format with comments - func testLoadMachinesMultilineComments() throws { + @Test + func loadMachinesMultilineComments() throws { let content = """ ## This is a comment # This is another comment @@ -190,48 +198,51 @@ class NetrcTests: XCTestCase { """ let machines = try NetrcParser.parse(content).machines - XCTAssertEqual(machines.count, 1) + #expect(machines.count == 1) - let machine = machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qwerty") + let machine = try #require(machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") } /// should load machines for a given multi-line + whitespaces format - func testLoadMachinesMultilineWhitespaces() throws { + @Test + func loadMachinesMultilineWhitespaces() throws { let content = """ machine example.com login anonymous password qwerty """ let machines = try NetrcParser.parse(content).machines - XCTAssertEqual(machines.count, 1) + #expect(machines.count == 1) - let machine = machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qwerty") + let machine = try #require(machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") } /// should load multiple machines for a given inline format - func testLoadMultipleMachinesInline() throws { + @Test + func loadMultipleMachinesInline() throws { let content = "machine example.com login anonymous password qwerty machine example2.com login anonymous2 password qwerty2" let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 2) + #expect(netrc.machines.count == 2) - XCTAssertEqual(netrc.machines[0].name, "example.com") - XCTAssertEqual(netrc.machines[0].login, "anonymous") - XCTAssertEqual(netrc.machines[0].password, "qwerty") + #expect(netrc.machines[0].name == "example.com") + #expect(netrc.machines[0].login == "anonymous") + #expect(netrc.machines[0].password == "qwerty") - XCTAssertEqual(netrc.machines[1].name, "example2.com") - XCTAssertEqual(netrc.machines[1].login, "anonymous2") - XCTAssertEqual(netrc.machines[1].password, "qwerty2") + #expect(netrc.machines[1].name == "example2.com") + #expect(netrc.machines[1].login == "anonymous2") + #expect(netrc.machines[1].password == "qwerty2") } /// should load multiple machines for a given multi-line format - func testLoadMultipleMachinesMultiline() throws { + @Test + func loadMultipleMachinesMultiline() throws { let content = """ machine example.com login anonymous password qwerty @@ -241,90 +252,98 @@ class NetrcTests: XCTestCase { """ let machines = try NetrcParser.parse(content).machines - XCTAssertEqual(machines.count, 2) + #expect(machines.count == 2) var machine = machines[0] - XCTAssertEqual(machine.name, "example.com") - XCTAssertEqual(machine.login, "anonymous") - XCTAssertEqual(machine.password, "qwerty") + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") machine = machines[1] - XCTAssertEqual(machine.name, "example2.com") - XCTAssertEqual(machine.login, "anonymous2") - XCTAssertEqual(machine.password, "qwerty2") + #expect(machine.name == "example2.com") + #expect(machine.login == "anonymous2") + #expect(machine.password == "qwerty2") } /// should throw error when machine parameter is missing - func testErrorMachineParameterMissing() throws { + @Test + func errorMachineParameterMissing() throws { let content = "login anonymous password qwerty" - XCTAssertThrowsError(try NetrcParser.parse(content)) { error in - XCTAssertEqual(error as? NetrcError, .machineNotFound) + #expect(throws: NetrcError.machineNotFound) { + try NetrcParser.parse(content) } } /// should throw error for an empty machine values - func testErrorEmptyMachineValue() throws { + @Test + func errorEmptyMachineValue() throws { let content = "machine" - XCTAssertThrowsError(try NetrcParser.parse(content)) { error in - XCTAssertEqual(error as? NetrcError, .machineNotFound) + #expect(throws: NetrcError.machineNotFound) { + try NetrcParser.parse(content) } } /// should throw error for an empty machine values - func testEmptyMachineValueFollowedByDefaultNoError() throws { + @Test + func emptyMachineValueFollowedByDefaultNoError() throws { let content = "machine default login id password secret" let netrc = try NetrcParser.parse(content) let authorization = netrc.authorization(for: "http://example.com/resource.zip") - XCTAssertEqual(authorization, Netrc.Authorization(login: "id", password: "secret")) + #expect(authorization == Netrc.Authorization(login: "id", password: "secret")) } /// should return authorization when config contains a given machine - func testReturnAuthorizationForMachineMatch() throws { + @Test + func returnAuthorizationForMachineMatch() throws { let content = "machine example.com login anonymous password qwerty" let netrc = try NetrcParser.parse(content) let authorization = netrc.authorization(for: "http://example.com/resource.zip") - XCTAssertEqual(authorization, Netrc.Authorization(login: "anonymous", password: "qwerty")) + #expect(authorization == Netrc.Authorization(login: "anonymous", password: "qwerty")) } - func testReturnNoAuthorizationForUnmatched() throws { + @Test + func returnNoAuthorizationForUnmatched() throws { let content = "machine example.com login anonymous password qwerty" let netrc = try NetrcParser.parse(content) - XCTAssertNil(netrc.authorization(for: "http://www.example.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "ftp.example.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "http://example2.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "http://www.example2.com/resource.zip")) + #expect(netrc.authorization(for: "http://www.example.com/resource.zip") == nil) + #expect(netrc.authorization(for: "ftp.example.com/resource.zip") == nil) + #expect(netrc.authorization(for: "http://example2.com/resource.zip") == nil) + #expect(netrc.authorization(for: "http://www.example2.com/resource.zip") == nil) } /// should not return authorization when config does not contain a given machine - func testNoReturnAuthorizationForNoMachineMatch() throws { + @Test + func noReturnAuthorizationForNoMachineMatch() throws { let content = "machine example.com login anonymous password qwerty" let netrc = try NetrcParser.parse(content) - XCTAssertNil(netrc.authorization(for: "https://example99.com")) - XCTAssertNil(netrc.authorization(for: "http://www.example.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "ftp.example.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "http://example2.com/resource.zip")) - XCTAssertNil(netrc.authorization(for: "http://www.example2.com/resource.zip")) + #expect(netrc.authorization(for: "https://example99.com") == nil) + #expect(netrc.authorization(for: "http://www.example.com/resource.zip") == nil) + #expect(netrc.authorization(for: "ftp.example.com/resource.zip") == nil) + #expect(netrc.authorization(for: "http://example2.com/resource.zip") == nil) + #expect(netrc.authorization(for: "http://www.example2.com/resource.zip") == nil) } /// Test case: https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/filesreference/netrc.html - func testIBMDocumentation() throws { + @Test + func iBMDocumentation() throws { let content = "machine host1.austin.century.com login fred password bluebonnet" let netrc = try NetrcParser.parse(content) - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "host1.austin.century.com") - XCTAssertEqual(machine?.login, "fred") - XCTAssertEqual(machine?.password, "bluebonnet") + let machine = try #require(netrc.machines.first) + #expect(machine.name == "host1.austin.century.com") + #expect(machine.login == "fred") + #expect(machine.password == "bluebonnet") } /// Should not fail on presence of `account`, `macdef`, `default` /// test case: https://gist.github.com/tpope/4247721 - func testNoErrorTrailingAccountMacdefDefault() throws { + @Test + func noErrorTrailingAccountMacdefDefault() throws { let content = """ machine api.heroku.com login my@email.com @@ -341,28 +360,29 @@ class NetrcTests: XCTestCase { let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 4) + #expect(netrc.machines.count == 4) - XCTAssertEqual(netrc.machines[0].name, "api.heroku.com") - XCTAssertEqual(netrc.machines[0].login, "my@email.com") - XCTAssertEqual(netrc.machines[0].password, "01230123012301230123012301230123") + #expect(netrc.machines[0].name == "api.heroku.com") + #expect(netrc.machines[0].login == "my@email.com") + #expect(netrc.machines[0].password == "01230123012301230123012301230123") - XCTAssertEqual(netrc.machines[1].name, "api.github.com") - XCTAssertEqual(netrc.machines[1].login, "somebody") - XCTAssertEqual(netrc.machines[1].password, "something") + #expect(netrc.machines[1].name == "api.github.com") + #expect(netrc.machines[1].login == "somebody") + #expect(netrc.machines[1].password == "something") - XCTAssertEqual(netrc.machines[2].name, "ftp.server") - XCTAssertEqual(netrc.machines[2].login, "abc") - XCTAssertEqual(netrc.machines[2].password, "def") + #expect(netrc.machines[2].name == "ftp.server") + #expect(netrc.machines[2].login == "abc") + #expect(netrc.machines[2].password == "def") - XCTAssertEqual(netrc.machines[3].name, "default") - XCTAssertEqual(netrc.machines[3].login, "anonymous") - XCTAssertEqual(netrc.machines[3].password, "my@email.com") + #expect(netrc.machines[3].name == "default") + #expect(netrc.machines[3].login == "anonymous") + #expect(netrc.machines[3].password == "my@email.com") } /// Should not fail on presence of `account`, `macdef`, `default` /// test case: https://gist.github.com/tpope/4247721 - func testNoErrorMixedAccount() throws { + @Test + func noErrorMixedAccount() throws { let content = """ machine api.heroku.com login my@email.com @@ -379,28 +399,29 @@ class NetrcTests: XCTestCase { let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 4) + #expect(netrc.machines.count == 4) - XCTAssertEqual(netrc.machines[0].name, "api.heroku.com") - XCTAssertEqual(netrc.machines[0].login, "my@email.com") - XCTAssertEqual(netrc.machines[0].password, "01230123012301230123012301230123") + #expect(netrc.machines[0].name == "api.heroku.com") + #expect(netrc.machines[0].login == "my@email.com") + #expect(netrc.machines[0].password == "01230123012301230123012301230123") - XCTAssertEqual(netrc.machines[1].name, "api.github.com") - XCTAssertEqual(netrc.machines[1].login, "somebody") - XCTAssertEqual(netrc.machines[1].password, "something") + #expect(netrc.machines[1].name == "api.github.com") + #expect(netrc.machines[1].login == "somebody") + #expect(netrc.machines[1].password == "something") - XCTAssertEqual(netrc.machines[2].name, "ftp.server") - XCTAssertEqual(netrc.machines[2].login, "abc") - XCTAssertEqual(netrc.machines[2].password, "def") + #expect(netrc.machines[2].name == "ftp.server") + #expect(netrc.machines[2].login == "abc") + #expect(netrc.machines[2].password == "def") - XCTAssertEqual(netrc.machines[3].name, "default") - XCTAssertEqual(netrc.machines[3].login, "anonymous") - XCTAssertEqual(netrc.machines[3].password, "my@email.com") + #expect(netrc.machines[3].name == "default") + #expect(netrc.machines[3].login == "anonymous") + #expect(netrc.machines[3].password == "my@email.com") } /// Should not fail on presence of `account`, `macdef`, `default` /// test case: https://renenyffenegger.ch/notes/Linux/fhs/home/username/_netrc - func testNoErrorMultipleMacdefAndComments() throws { + @Test + func noErrorMultipleMacdefAndComments() throws { let content = """ machine ftp.foobar.baz login john @@ -421,18 +442,19 @@ class NetrcTests: XCTestCase { let netrc = try NetrcParser.parse(content) - XCTAssertEqual(netrc.machines.count, 2) + #expect(netrc.machines.count == 2) - XCTAssertEqual(netrc.machines[0].name, "ftp.foobar.baz") - XCTAssertEqual(netrc.machines[0].login, "john") - XCTAssertEqual(netrc.machines[0].password, "5ecr3t") + #expect(netrc.machines[0].name == "ftp.foobar.baz") + #expect(netrc.machines[0].login == "john") + #expect(netrc.machines[0].password == "5ecr3t") - XCTAssertEqual(netrc.machines[1].name, "other.server.org") - XCTAssertEqual(netrc.machines[1].login, "fred") - XCTAssertEqual(netrc.machines[1].password, "sunshine4ever") + #expect(netrc.machines[1].name == "other.server.org") + #expect(netrc.machines[1].login == "fred") + #expect(netrc.machines[1].password == "sunshine4ever") } - func testComments() throws { + @Test + func comments() throws { let content = """ # A comment at the beginning of the line machine example.com # Another comment @@ -442,62 +464,60 @@ class NetrcTests: XCTestCase { let netrc = try NetrcParser.parse(content) - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qw#erty") + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qw#erty") } - // TODO: These permutation tests would be excellent swift-testing parameterized tests. - func testAllHashQuotingPermutations() throws { - let cases = [ - ("qwerty", "qwerty"), - ("qwe#rty", "qwe#rty"), - ("\"qwe#rty\"", "qwe#rty"), - ("\"qwe #rty\"", "qwe #rty"), - ("\"qwe# rty\"", "qwe# rty"), + @Test( + arguments: [ + (testCase: "qwerty", expected: "qwerty"), + (testCase: "qwe#rty", expected: "qwe#rty"), + (testCase: "\"qwe#rty\"", expected: "qwe#rty"), + (testCase: "\"qwe #rty\"", expected: "qwe #rty"), + (testCase: "\"qwe# rty\"", expected: "qwe# rty") ] + ) + func allHashQuotingPermutations(testCase: String, expected: String) throws { + let content = """ + machine example.com + login \(testCase) + password \(testCase) + """ + let netrc = try NetrcParser.parse(content) - for (testCase, expected) in cases { - let content = """ - machine example.com - login \(testCase) - password \(testCase) - """ - let netrc = try NetrcParser.parse(content) - - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, expected, "Expected login \(testCase) to parse as \(expected)") - XCTAssertEqual(machine?.password, expected, "Expected \(testCase) to parse as \(expected)") - } + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == expected) + #expect(machine.password == expected) } - func testAllCommentPermutations() throws { - let cases = [ - ("qwerty # a comment", "qwerty"), - ("qwe#rty # a comment", "qwe#rty"), - ("\"qwe#rty\" # a comment", "qwe#rty"), - ("\"qwe #rty\" # a comment", "qwe #rty"), - ("\"qwe# rty\" # a comment", "qwe# rty"), + @Test( + arguments: [ + (testCase: "qwerty # a comment", expected: "qwerty"), + (testCase: "qwe#rty # a comment", expected: "qwe#rty"), + (testCase: "\"qwe#rty\" # a comment", expected: "qwe#rty"), + (testCase: "\"qwe #rty\" # a comment", expected: "qwe #rty"), + (testCase: "\"qwe# rty\" # a comment", expected: "qwe# rty") ] + ) + func allCommentPermutations(testCase: String, expected: String) throws { + let content = """ + machine example.com + login \(testCase) + password \(testCase) + """ + let netrc = try NetrcParser.parse(content) - for (testCase, expected) in cases { - let content = """ - machine example.com - login \(testCase) - password \(testCase) - """ - let netrc = try NetrcParser.parse(content) - - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, expected, "Expected login \(testCase) to parse as \(expected)") - XCTAssertEqual(machine?.password, expected, "Expected password \(testCase) to parse as \(expected)") - } + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == expected) + #expect(machine.password == expected) } - func testQuotedMachine() throws { + @Test + func quotedMachine() throws { let content = """ machine "example.com" login anonymous @@ -506,9 +526,9 @@ class NetrcTests: XCTestCase { let netrc = try NetrcParser.parse(content) - let machine = netrc.machines.first - XCTAssertEqual(machine?.name, "example.com") - XCTAssertEqual(machine?.login, "anonymous") - XCTAssertEqual(machine?.password, "qwerty") + let machine = try #require(netrc.machines.first) + #expect(machine.name == "example.com") + #expect(machine.login == "anonymous") + #expect(machine.password == "qwerty") } } diff --git a/Tests/BasicsTests/PhonyTest.swift b/Tests/BasicsTests/PhonyTest.swift deleted file mode 100644 index 5d7a0467bef..00000000000 --- a/Tests/BasicsTests/PhonyTest.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Testing - -// This test exists to force xunit to report at least one test pass -// for systems that require the output to report something. -struct PhonyTest { - @Test func phonyPass() {} -} diff --git a/Tests/BasicsTests/SampleTests.swift b/Tests/BasicsTests/SampleTests.swift deleted file mode 100644 index 19dd021c352..00000000000 --- a/Tests/BasicsTests/SampleTests.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Testing - -struct Foo { - - @Test - func myTest() { - #expect(Bool(true)) - } -} \ No newline at end of file diff --git a/Tests/BasicsTests/TripleTests.swift b/Tests/BasicsTests/TripleTests.swift index d9ec228e79e..81e12026545 100644 --- a/Tests/BasicsTests/TripleTests.swift +++ b/Tests/BasicsTests/TripleTests.swift @@ -2,244 +2,365 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics -import XCTest - -final class TripleTests: XCTestCase { - func testIsAppleIsDarwin() { - func XCTAssertTriple( - _ triple: String, - isApple: Bool, - isDarwin: Bool, - file: StaticString = #filePath, - line: UInt = #line - ) { - guard let triple = try? Triple(triple) else { - XCTFail( - "Unknown triple '\(triple)'.", - file: file, - line: line) - return - } - XCTAssert( - isApple == triple.isApple(), - """ - Expected triple '\(triple.tripleString)' \ - \(isApple ? "" : " not") to be an Apple triple. - """, - file: file, - line: line) - XCTAssert( - isDarwin == triple.isDarwin(), - """ - Expected triple '\(triple.tripleString)' \ - \(isDarwin ? "" : " not") to be a Darwin triple. - """, - file: file, - line: line) - } - - XCTAssertTriple("x86_64-pc-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("x86_64-pc-linux-musl", isApple: false, isDarwin: false) - XCTAssertTriple("powerpc-bgp-linux", isApple: false, isDarwin: false) - XCTAssertTriple("arm-none-none-eabi", isApple: false, isDarwin: false) - XCTAssertTriple("arm-none-linux-musleabi", isApple: false, isDarwin: false) - XCTAssertTriple("wasm32-unknown-wasi", isApple: false, isDarwin: false) - XCTAssertTriple("riscv64-unknown-linux", isApple: false, isDarwin: false) - XCTAssertTriple("mips-mti-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("mipsel-img-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("mips64-mti-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("mips64el-img-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("mips64el-img-linux-gnuabin32", isApple: false, isDarwin: false) - XCTAssertTriple("mips64-unknown-linux-gnuabi64", isApple: false, isDarwin: false) - XCTAssertTriple("mips64-unknown-linux-gnuabin32", isApple: false, isDarwin: false) - XCTAssertTriple("mipsel-unknown-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("mips-unknown-linux-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("arm-oe-linux-gnueabi", isApple: false, isDarwin: false) - XCTAssertTriple("aarch64-oe-linux", isApple: false, isDarwin: false) - XCTAssertTriple("armv7em-unknown-none-macho", isApple: false, isDarwin: false) - XCTAssertTriple("armv7em-apple-none-macho", isApple: true, isDarwin: false) - XCTAssertTriple("armv7em-apple-none", isApple: true, isDarwin: false) - XCTAssertTriple("aarch64-apple-macosx", isApple: true, isDarwin: true) - XCTAssertTriple("x86_64-apple-macosx", isApple: true, isDarwin: true) - XCTAssertTriple("x86_64-apple-macosx10.15", isApple: true, isDarwin: true) - XCTAssertTriple("x86_64h-apple-darwin", isApple: true, isDarwin: true) - XCTAssertTriple("i686-pc-windows-msvc", isApple: false, isDarwin: false) - XCTAssertTriple("i686-pc-windows-gnu", isApple: false, isDarwin: false) - XCTAssertTriple("i686-pc-windows-cygnus", isApple: false, isDarwin: false) +import Testing + +struct TripleTests { + @Test( + "Triple is Apple and is Darwin", + arguments: [ + (tripleName: "x86_64-pc-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "x86_64-pc-linux-musl", isApple: false, isDarwin: false), + (tripleName: "powerpc-bgp-linux", isApple: false, isDarwin: false), + (tripleName: "arm-none-none-eabi", isApple: false, isDarwin: false), + (tripleName: "arm-none-linux-musleabi", isApple: false, isDarwin: false), + (tripleName: "wasm32-unknown-wasi", isApple: false, isDarwin: false), + (tripleName: "riscv64-unknown-linux", isApple: false, isDarwin: false), + (tripleName: "mips-mti-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "mipsel-img-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "mips64-mti-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "mips64el-img-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "mips64el-img-linux-gnuabin32", isApple: false, isDarwin: false), + (tripleName: "mips64-unknown-linux-gnuabi64", isApple: false, isDarwin: false), + (tripleName: "mips64-unknown-linux-gnuabin32", isApple: false, isDarwin: false), + (tripleName: "mipsel-unknown-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "mips-unknown-linux-gnu", isApple: false, isDarwin: false), + (tripleName: "arm-oe-linux-gnueabi", isApple: false, isDarwin: false), + (tripleName: "aarch64-oe-linux", isApple: false, isDarwin: false), + (tripleName: "armv7em-unknown-none-macho", isApple: false, isDarwin: false), + (tripleName: "armv7em-apple-none-macho", isApple: true, isDarwin: false), + (tripleName: "armv7em-apple-none", isApple: true, isDarwin: false), + (tripleName: "aarch64-apple-macosx", isApple: true, isDarwin: true), + (tripleName: "x86_64-apple-macosx", isApple: true, isDarwin: true), + (tripleName: "x86_64-apple-macosx10.15", isApple: true, isDarwin: true), + (tripleName: "x86_64h-apple-darwin", isApple: true, isDarwin: true), + (tripleName: "i686-pc-windows-msvc", isApple: false, isDarwin: false), + (tripleName: "i686-pc-windows-gnu", isApple: false, isDarwin: false), + (tripleName: "i686-pc-windows-cygnus", isApple: false, isDarwin: false), + ], + ) + func isAppleIsDarwin(_ tripleName: String, _ isApple: Bool, _ isDarwin: Bool) throws { + let triple = try #require(try Triple(tripleName), "Unknown triple '\(tripleName)'.") + #expect( + isApple == triple.isApple(), + """ + Expected triple '\(triple.tripleString)' \ + \(isApple ? "" : " not") to be an Apple triple. + """, + ) + #expect( + isDarwin == triple.isDarwin(), + """ + Expected triple '\(triple.tripleString)' \ + \(isDarwin ? "" : " not") to be a Darwin triple. + """, + ) } - func testDescription() throws { + @Test + func description() throws { let triple = try Triple("x86_64-pc-linux-gnu") - XCTAssertEqual("foo \(triple) bar", "foo x86_64-pc-linux-gnu bar") + #expect("foo \(triple) bar" == "foo x86_64-pc-linux-gnu bar") } - func testTripleStringForPlatformVersion() throws { - func XCTAssertTriple( - _ triple: String, - forPlatformVersion version: String, - is expectedTriple: String, - file: StaticString = #filePath, - line: UInt = #line - ) { - guard let triple = try? Triple(triple) else { - XCTFail("Unknown triple '\(triple)'.", file: file, line: line) - return - } - let actualTriple = triple.tripleString(forPlatformVersion: version) - XCTAssert( - actualTriple == expectedTriple, - """ - Actual triple '\(actualTriple)' did not match expected triple \ - '\(expectedTriple)' for platform version '\(version)'. - """, - file: file, - line: line) - } - - XCTAssertTriple("x86_64-apple-macosx", forPlatformVersion: "", is: "x86_64-apple-macosx") - XCTAssertTriple("x86_64-apple-macosx", forPlatformVersion: "13.0", is: "x86_64-apple-macosx13.0") - - XCTAssertTriple("armv7em-apple-macosx10.12", forPlatformVersion: "", is: "armv7em-apple-macosx") - XCTAssertTriple("armv7em-apple-macosx10.12", forPlatformVersion: "13.0", is: "armv7em-apple-macosx13.0") - - XCTAssertTriple("powerpc-apple-macos", forPlatformVersion: "", is: "powerpc-apple-macos") - XCTAssertTriple("powerpc-apple-macos", forPlatformVersion: "13.0", is: "powerpc-apple-macos13.0") - - XCTAssertTriple("i686-apple-macos10.12.0", forPlatformVersion: "", is: "i686-apple-macos") - XCTAssertTriple("i686-apple-macos10.12.0", forPlatformVersion: "13.0", is: "i686-apple-macos13.0") - - XCTAssertTriple("riscv64-apple-darwin", forPlatformVersion: "", is: "riscv64-apple-darwin") - XCTAssertTriple("riscv64-apple-darwin", forPlatformVersion: "22", is: "riscv64-apple-darwin22") - - XCTAssertTriple("mips-apple-darwin19", forPlatformVersion: "", is: "mips-apple-darwin") - XCTAssertTriple("mips-apple-darwin19", forPlatformVersion: "22", is: "mips-apple-darwin22") - - XCTAssertTriple("arm64-apple-ios-simulator", forPlatformVersion: "", is: "arm64-apple-ios-simulator") - XCTAssertTriple("arm64-apple-ios-simulator", forPlatformVersion: "13.0", is: "arm64-apple-ios13.0-simulator") - - XCTAssertTriple("arm64-apple-ios12-simulator", forPlatformVersion: "", is: "arm64-apple-ios-simulator") - XCTAssertTriple("arm64-apple-ios12-simulator", forPlatformVersion: "13.0", is: "arm64-apple-ios13.0-simulator") + @Test( + "Triple String for Platform Version", + arguments: [ + ( + tripleName: "x86_64-apple-macosx", + version: "", + expectedTriple: "x86_64-apple-macosx", + ), + ( + tripleName: "x86_64-apple-macosx", + version: "13.0", + expectedTriple: "x86_64-apple-macosx13.0", + ), + ( + tripleName: "armv7em-apple-macosx10.12", + version: "", + expectedTriple: "armv7em-apple-macosx", + ), + ( + tripleName: "armv7em-apple-macosx10.12", + version: "13.0", + expectedTriple: "armv7em-apple-macosx13.0", + ), + ( + tripleName: "powerpc-apple-macos", + version: "", + expectedTriple: "powerpc-apple-macos", + ), + ( + tripleName: "powerpc-apple-macos", + version: "13.0", + expectedTriple: "powerpc-apple-macos13.0", + ), + ( + tripleName: "i686-apple-macos10.12.0", + version: "", + expectedTriple: "i686-apple-macos", + ), + ( + tripleName: "i686-apple-macos10.12.0", + version: "13.0", + expectedTriple: "i686-apple-macos13.0", + ), + ( + tripleName: "riscv64-apple-darwin", + version: "", + expectedTriple: "riscv64-apple-darwin", + ), + ( + tripleName: "riscv64-apple-darwin", + version: "22", + expectedTriple: "riscv64-apple-darwin22", + ), + ( + tripleName: "mips-apple-darwin19", + version: "", + expectedTriple: "mips-apple-darwin", + ), + ( + tripleName: "mips-apple-darwin19", + version: "22", + expectedTriple: "mips-apple-darwin22", + ), + ( + tripleName: "arm64-apple-ios-simulator", + version: "", + expectedTriple: "arm64-apple-ios-simulator", + ), + ( + tripleName: "arm64-apple-ios-simulator", + version: "13.0", + expectedTriple: "arm64-apple-ios13.0-simulator", + ), + ( + tripleName: "arm64-apple-ios12-simulator", + version: "", + expectedTriple: "arm64-apple-ios-simulator", + ), + ( + tripleName: "arm64-apple-ios12-simulator", + version: "13.0", + expectedTriple: "arm64-apple-ios13.0-simulator", + ), + ] + ) + func tripleStringForPlatformVersion( + tripleName: String, version: String, expectedTriple: String + ) throws { + let triple = try #require(try Triple(tripleName), "Unknown triple '\(tripleName)'.") + let actualTriple = triple.tripleString(forPlatformVersion: version) + #expect( + actualTriple == expectedTriple, + """ + Actual triple '\(actualTriple)' did not match expected triple \ + '\(expectedTriple)' for platform version '\(version)'. + """, + ) + } - func testKnownTripleParsing() { - func XCTAssertTriple( - _ triple: String, - matches components: ( - arch: Triple.Arch?, - subArch: Triple.SubArch?, - vendor: Triple.Vendor?, - os: Triple.OS?, - environment: Triple.Environment?, - objectFormat: Triple.ObjectFormat?), - file: StaticString = #filePath, - line: UInt = #line - ) { - guard let triple = try? Triple(triple) else { - XCTFail( - "Unknown triple '\(triple)'.", - file: file, - line: line) - return - } - XCTAssertEqual(triple.arch, components.arch, file: file, line: line) - XCTAssertEqual(triple.subArch, components.subArch, file: file, line: line) - XCTAssertEqual(triple.vendor, components.vendor, file: file, line: line) - XCTAssertEqual(triple.os, components.os, file: file, line: line) - XCTAssertEqual(triple.environment, components.environment, file: file, line: line) - XCTAssertEqual(triple.objectFormat, components.objectFormat, file: file, line: line) - } - XCTAssertTriple("armv7em-apple-none-eabihf-macho", matches: (.arm, .arm(.v7em), .apple, .noneOS, .eabihf, .macho)) - XCTAssertTriple("x86_64-apple-macosx", matches: (.x86_64, nil, .apple, .macosx, nil, .macho)) - XCTAssertTriple("x86_64-unknown-linux-gnu", matches: (.x86_64, nil, nil, .linux, .gnu, .elf)) - XCTAssertTriple("aarch64-unknown-linux-gnu", matches: (.aarch64, nil, nil, .linux, .gnu, .elf)) - XCTAssertTriple("aarch64-unknown-linux-android", matches: (.aarch64, nil, nil, .linux, .android, .elf)) - XCTAssertTriple("x86_64-unknown-windows-msvc", matches: (.x86_64, nil, nil, .win32, .msvc, .coff)) - XCTAssertTriple("wasm32-unknown-wasi", matches: (.wasm32, nil, nil, .wasi, nil, .wasm)) - } - - func testTriple() { + struct DataKnownTripleParsing { + var tripleName: String + var expectedArch: Triple.Arch? + var expectedSubArch: Triple.SubArch? + var expectedVendor: Triple.Vendor? + var expectedOs: Triple.OS? + var expectedEnvironment: Triple.Environment? + var expectedObjectFormat: Triple.ObjectFormat? + } + @Test( + "Known Triple Parsing", + arguments: [ + DataKnownTripleParsing( + tripleName: "armv7em-apple-none-eabihf-macho", + expectedArch: .arm, + expectedSubArch : .arm(.v7em), + expectedVendor: .apple, + expectedOs: .noneOS, + expectedEnvironment: .eabihf, + expectedObjectFormat: .macho + ), + DataKnownTripleParsing( + tripleName: "x86_64-apple-macosx", + expectedArch: .x86_64, + expectedSubArch: nil, + expectedVendor: .apple, + expectedOs: .macosx, + expectedEnvironment: nil, + expectedObjectFormat: .macho + ), + DataKnownTripleParsing( + tripleName: "x86_64-unknown-linux-gnu", + expectedArch: .x86_64, + expectedSubArch: nil, + expectedVendor: nil, + expectedOs: .linux, + expectedEnvironment: .gnu, + expectedObjectFormat: .elf + ), + DataKnownTripleParsing( + tripleName: "aarch64-unknown-linux-gnu", + expectedArch: .aarch64, + expectedSubArch: nil, + expectedVendor: nil, + expectedOs: .linux, + expectedEnvironment: .gnu, + expectedObjectFormat: .elf + ), + DataKnownTripleParsing( + tripleName: "aarch64-unknown-linux-android", + expectedArch: .aarch64, + expectedSubArch: nil, + expectedVendor: nil, + expectedOs: .linux, + expectedEnvironment: .android, + expectedObjectFormat: .elf + ), + DataKnownTripleParsing( + tripleName: "x86_64-unknown-windows-msvc", + expectedArch: .x86_64, + expectedSubArch: nil, + expectedVendor: nil, + expectedOs: .win32, + expectedEnvironment: .msvc, + expectedObjectFormat: .coff + ), + DataKnownTripleParsing( + tripleName: "wasm32-unknown-wasi", + expectedArch: .wasm32, + expectedSubArch: nil, + expectedVendor: nil, + expectedOs: .wasi, + expectedEnvironment: nil, + expectedObjectFormat: .wasm + ) + ] + ) + func knownTripleParsing( + data: DataKnownTripleParsing, + ) throws { + let triple = try #require( + try Triple(data.tripleName), "Unknown triple '\(data.tripleName)'.") + #expect(triple.arch == data.expectedArch, "Actual arch not as expected") + #expect(triple.subArch == data.expectedSubArch, "Actual subarch is not as expected") + #expect(triple.vendor == data.expectedVendor, "Actual Vendor is not as expected") + #expect(triple.os == data.expectedOs, "Actual OS is not as expcted") + #expect(triple.environment == data.expectedEnvironment, "Actual environment is not as expected") + #expect(triple.objectFormat == data.expectedObjectFormat, "Actual object format is not as expected") + } + + @Test + func triple() { let linux = try? Triple("x86_64-unknown-linux-gnu") - XCTAssertNotNil(linux) - XCTAssertEqual(linux!.os, .linux) - XCTAssertEqual(linux!.osVersion, Triple.Version.zero) - XCTAssertEqual(linux!.environment, .gnu) + #expect(linux != nil) + #expect(linux!.os == .linux) + #expect(linux!.osVersion == Triple.Version.zero) + #expect(linux!.environment == .gnu) let macos = try? Triple("x86_64-apple-macosx10.15") - XCTAssertNotNil(macos!) - XCTAssertEqual(macos!.osVersion, .init(parse: "10.15")) + #expect(macos! != nil) + #expect(macos!.osVersion == .init(parse: "10.15")) let newVersion = "10.12" let tripleString = macos!.tripleString(forPlatformVersion: newVersion) - XCTAssertEqual(tripleString, "x86_64-apple-macosx10.12") + #expect(tripleString == "x86_64-apple-macosx10.12") let macosNoX = try? Triple("x86_64-apple-macos12.2") - XCTAssertNotNil(macosNoX!) - XCTAssertEqual(macosNoX!.os, .macosx) - XCTAssertEqual(macosNoX!.osVersion, .init(parse: "12.2")) + #expect(macosNoX! != nil) + #expect(macosNoX!.os == .macosx) + #expect(macosNoX!.osVersion == .init(parse: "12.2")) let android = try? Triple("aarch64-unknown-linux-android24") - XCTAssertNotNil(android) - XCTAssertEqual(android!.os, .linux) - XCTAssertEqual(android!.environment, .android) + #expect(android != nil) + #expect(android!.os == .linux) + #expect(android!.environment == .android) let linuxWithABIVersion = try? Triple("x86_64-unknown-linux-gnu42") - XCTAssertEqual(linuxWithABIVersion!.environment, .gnu) + #expect(linuxWithABIVersion!.environment == .gnu) } - func testEquality() throws { + @Test + func equality() throws { let macOSTriple = try Triple("arm64-apple-macos") let macOSXTriple = try Triple("arm64-apple-macosx") - XCTAssertEqual(macOSTriple, macOSXTriple) + #expect(macOSTriple == macOSXTriple) let intelMacOSTriple = try Triple("x86_64-apple-macos") - XCTAssertNotEqual(macOSTriple, intelMacOSTriple) + #expect(macOSTriple != intelMacOSTriple) let linuxWithoutGNUABI = try Triple("x86_64-unknown-linux") let linuxWithGNUABI = try Triple("x86_64-unknown-linux-gnu") - XCTAssertNotEqual(linuxWithoutGNUABI, linuxWithGNUABI) + #expect(linuxWithoutGNUABI != linuxWithGNUABI) } - func testWASI() throws { + @Test + func WASI() throws { let wasi = try Triple("wasm32-unknown-wasi") - - // WASI dynamic libraries are only experimental, // but SwiftPM requires this property not to crash. _ = wasi.dynamicLibraryExtension } - func testNoneOSDynamicLibrary() throws { - // Dynamic libraries aren't actually supported for OS none, but swiftpm - // wants an extension to avoid crashing during build planning. - try XCTAssertEqual( - Triple("armv7em-unknown-none-coff").dynamicLibraryExtension, - ".coff") - try XCTAssertEqual( - Triple("armv7em-unknown-none-elf").dynamicLibraryExtension, - ".elf") - try XCTAssertEqual( - Triple("armv7em-unknown-none-macho").dynamicLibraryExtension, - ".macho") - try XCTAssertEqual( - Triple("armv7em-unknown-none-wasm").dynamicLibraryExtension, - ".wasm") - try XCTAssertEqual( - Triple("armv7em-unknown-none-xcoff").dynamicLibraryExtension, - ".xcoff") + @Test( + "Test dynamicLibraryExtesion attribute on Triple returns expected value", + arguments: [ + (tripleName: "armv7em-unknown-none-coff", expected: ".coff"), + (tripleName: "armv7em-unknown-none-elf", expected: ".elf"), + (tripleName: "armv7em-unknown-none-macho", expected: ".macho"), + (tripleName: "armv7em-unknown-none-wasm", expected: ".wasm"), + (tripleName: "armv7em-unknown-none-xcoff", expected: ".xcoff"), + (tripleName: "wasm32-unknown-wasi", expected: ".wasm"), // Added by bkhouri + ], + ) + func noneOSDynamicLibrary(tripleName: String, expected: String) throws { + // Dynamic libraries aren't actually supported for OS none, but swiftpm + // wants an extension to avoid crashing during build planning. + let triple = try Triple(tripleName) + #expect(triple.dynamicLibraryExtension == expected) } - func testIsRuntimeCompatibleWith() throws { - try XCTAssertTrue(Triple("x86_64-apple-macosx").isRuntimeCompatible(with: Triple("x86_64-apple-macosx"))) - try XCTAssertTrue(Triple("x86_64-unknown-linux").isRuntimeCompatible(with: Triple("x86_64-unknown-linux"))) - try XCTAssertFalse(Triple("x86_64-apple-macosx").isRuntimeCompatible(with: Triple("x86_64-apple-linux"))) - try XCTAssertTrue(Triple("x86_64-apple-macosx14.0").isRuntimeCompatible(with: Triple("x86_64-apple-macosx13.0"))) + @Test( + "isRuntimeCompatibleWith returns expected value", + arguments: [ + ( + firstTripleName: "x86_64-apple-macosx", + secondTripleName: "x86_64-apple-macosx", + isCompatible: true, + ), + ( + firstTripleName: "x86_64-unknown-linux", + secondTripleName: "x86_64-unknown-linux", + isCompatible: true, + ), + ( + firstTripleName: "x86_64-apple-macosx", + secondTripleName: "x86_64-apple-linux", + isCompatible: false, + ), + ( + firstTripleName: "x86_64-apple-macosx14.0", + secondTripleName: "x86_64-apple-macosx13.0", + isCompatible: true, + ), + ], + ) + func isRuntimeCompatibleWith( + firstTripleName: String, secondTripleName: String, isCompatible: Bool, + ) throws { + let triple = try Triple(firstTripleName) + let other = try Triple(secondTripleName) + #expect(triple.isRuntimeCompatible(with: other) == isCompatible) } } From a3cd491c61ab466dd513c903cb5ce4243b7ca84f Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 6 May 2025 00:37:52 -0700 Subject: [PATCH 78/99] [SwiftFixIt] Allow filtering diagnostics by category (#8620) ### Motivation: Helps with applying only certain fix-its i.e. for features in migration mode or concurrency. ### Modifications: - Add `categories: Set` parameter to `SwiftFixIt` initializers that is defaulted to `[]`. ### Result: `swift migrate` can use this to filter based on the feature categories to avoid applying unrelated fix-its. --- Sources/SwiftFixIt/SwiftFixit.swift | 16 +++- Tests/SwiftFixItTests/SwiftFixItTests.swift | 98 +++++++++++++++++++-- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftFixIt/SwiftFixit.swift b/Sources/SwiftFixIt/SwiftFixit.swift index 74b3ea0f214..9ce2c16717c 100644 --- a/Sources/SwiftFixIt/SwiftFixit.swift +++ b/Sources/SwiftFixIt/SwiftFixit.swift @@ -89,6 +89,7 @@ package struct SwiftFixIt /*: ~Copyable */ { package init( diagnosticFiles: [AbsolutePath], + categories: Set = [], fileSystem: any FileSystem ) throws { // Deserialize the diagnostics. @@ -97,11 +98,16 @@ package struct SwiftFixIt /*: ~Copyable */ { return try TSCUtility.SerializedDiagnostics(bytes: fileContents).diagnostics }.lazy.joined() - self = try SwiftFixIt(diagnostics: diagnostics, fileSystem: fileSystem) + self = try SwiftFixIt( + diagnostics: diagnostics, + categories: categories, + fileSystem: fileSystem + ) } init( diagnostics: some Collection, + categories: Set = [], fileSystem: any FileSystem ) throws { self.fileSystem = fileSystem @@ -143,6 +149,14 @@ package struct SwiftFixIt /*: ~Copyable */ { continue } + if !categories.isEmpty { + guard let category = convertedDiagnostic.diagMessage.category?.name, + categories.contains(category) + else { + continue + } + } + diagnosticsPerFile[consume sourceFile, default: []].append(consume convertedDiagnostic) } diff --git a/Tests/SwiftFixItTests/SwiftFixItTests.swift b/Tests/SwiftFixItTests/SwiftFixItTests.swift index 4b628a395ad..a15e764f57e 100644 --- a/Tests/SwiftFixItTests/SwiftFixItTests.swift +++ b/Tests/SwiftFixItTests/SwiftFixItTests.swift @@ -55,13 +55,18 @@ final class SwiftFixItTests: XCTestCase { private func _testAPI( _ sourceFilePathsAndEdits: [(AbsolutePath, SourceFileEdit)], - _ diagnostics: [TestDiagnostic] + _ diagnostics: [TestDiagnostic], + _ categories: Set, ) throws { for (path, edit) in sourceFilePathsAndEdits { try localFileSystem.writeFileContents(path, string: edit.input) } - let swiftFixIt = try SwiftFixIt(diagnostics: diagnostics, fileSystem: localFileSystem) + let swiftFixIt = try SwiftFixIt( + diagnostics: diagnostics, + categories: categories, + fileSystem: localFileSystem + ) try swiftFixIt.applyFixIts() for (path, edit) in sourceFilePathsAndEdits { @@ -75,6 +80,7 @@ final class SwiftFixItTests: XCTestCase { // Cannot use variadic generics: crashes. private func testAPI1File( + categories: Set = [], _ getTestCase: (String) -> TestCase ) throws { try testWithTemporaryDirectory { fixturePath in @@ -84,13 +90,15 @@ final class SwiftFixItTests: XCTestCase { try self._testAPI( [(sourceFilePath, testCase.edits)], - testCase.diagnostics + testCase.diagnostics, + categories ) } } private func testAPI2Files( - _ getTestCase: (String, String) -> TestCase<(SourceFileEdit, SourceFileEdit)> + categories: Set = [], + _ getTestCase: (String, String) -> TestCase<(SourceFileEdit, SourceFileEdit)>, ) throws { try testWithTemporaryDirectory { fixturePath in let sourceFilePath1 = fixturePath.appending(self.uniqueSwiftFileName()) @@ -100,7 +108,8 @@ final class SwiftFixItTests: XCTestCase { try self._testAPI( [(sourceFilePath1, testCase.edits.0), (sourceFilePath2, testCase.edits.1)], - testCase.diagnostics + testCase.diagnostics, + categories ) } } @@ -129,6 +138,85 @@ extension SwiftFixItTests { } } + func testCategoryFiltering() throws { + // Check that the fix-it gets ignored because category doesn't match + try self.testAPI1File(categories: ["Test"]) { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "var x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + + // Check that the fix-it gets ignored because category doesn't match + try self.testAPI1File(categories: ["Test"]) { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "var x = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + category: "Other", + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + ] + ) + } + + try self.testAPI1File(categories: ["Other", "Test"]) { (filename: String) in + .init( + edits: .init(input: "var x = 1", result: "let _ = 1"), + diagnostics: [ + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 1, offset: 0), + category: "Test", + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 1, offset: 0), + end: .init(filename: filename, line: 1, column: 4, offset: 0), + text: "let" + ), + ] + ), + TestDiagnostic( + text: "error", + level: .error, + location: .init(filename: filename, line: 1, column: 4, offset: 0), + category: "Other", + fixIts: [ + .init( + start: .init(filename: filename, line: 1, column: 5, offset: 0), + end: .init(filename: filename, line: 1, column: 6, offset: 0), + text: "_" + ), + ] + ), + ] + ) + } + } + func testFixItIgnoredDiagnostic() throws { try self.testAPI1File { (filename: String) in .init( From a40b433891d9e8641f4a38718ba92807feed519a Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Tue, 6 May 2025 16:04:27 -0400 Subject: [PATCH 79/99] Tests: Convert SPMBuildCoreTests to Swift Testing (#8625) Convert the Tests/SwiftBuildCoreTests/*.swift, and Tests/_InternalTestSupport/Misc.swift to Swift Testing. --- .../ArtifactsArchiveMetadataTests.swift | 27 +- .../BuildParametersTests.swift | 15 +- .../XCFrameworkMetadataTests.swift | 75 +++--- Tests/_InternalTestSupportTests/Misc.swift | 237 ++++++++---------- 4 files changed, 169 insertions(+), 185 deletions(-) diff --git a/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift b/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift index 0221a8cfc2f..5cdb704dd56 100644 --- a/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift +++ b/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -12,10 +12,11 @@ import Basics import PackageModel -import XCTest +import Testing -final class ArtifactsArchiveMetadataTests: XCTestCase { - func testParseMetadata() throws { +struct ArtifactsArchiveMetadataTests { + @Test + func parseMetadata() throws { let fileSystem = InMemoryFileSystem() try fileSystem.writeFileContents( "/info.json", @@ -43,7 +44,7 @@ final class ArtifactsArchiveMetadataTests: XCTestCase { ) let metadata = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: .root) - XCTAssertEqual(metadata, try ArtifactsArchiveMetadata( + let expected = try ArtifactsArchiveMetadata( schemaVersion: "1.0", artifacts: [ "protocol-buffer-compiler": ArtifactsArchiveMetadata.Artifact( @@ -61,9 +62,12 @@ final class ArtifactsArchiveMetadataTests: XCTestCase { ] ), ] - )) + ) + #expect(metadata == expected, "Actual is not as expected") } - func testParseMetadataWithoutSupportedTriple() throws { + + @Test + func parseMetadataWithoutSupportedTriple() throws { let fileSystem = InMemoryFileSystem() try fileSystem.writeFileContents( "/info.json", @@ -90,7 +94,7 @@ final class ArtifactsArchiveMetadataTests: XCTestCase { ) let metadata = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: .root) - XCTAssertEqual(metadata, ArtifactsArchiveMetadata( + let expected = ArtifactsArchiveMetadata( schemaVersion: "1.0", artifacts: [ "protocol-buffer-compiler": ArtifactsArchiveMetadata.Artifact( @@ -108,16 +112,17 @@ final class ArtifactsArchiveMetadataTests: XCTestCase { ] ), ] - )) + ) + #expect(metadata == expected, "Actual is not as expected") let binaryTarget = BinaryModule( name: "protoc", kind: .artifactsArchive, path: .root, origin: .local ) // No supportedTriples with binaryTarget should be rejected - XCTAssertThrowsError( + #expect(throws: (any Error).self) { try binaryTarget.parseArtifactArchives( for: Triple("x86_64-apple-macosx"), fileSystem: fileSystem ) - ) + } } } diff --git a/Tests/SPMBuildCoreTests/BuildParametersTests.swift b/Tests/SPMBuildCoreTests/BuildParametersTests.swift index 2c106261f3a..cf79703fe9b 100644 --- a/Tests/SPMBuildCoreTests/BuildParametersTests.swift +++ b/Tests/SPMBuildCoreTests/BuildParametersTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -14,18 +14,17 @@ import Basics import struct PackageModel.BuildEnvironment import _InternalTestSupport -import XCTest +import Testing -final class BuildParametersTests: XCTestCase { - func testConfigurationDependentProperties() throws { - // Ensure that properties that depend on the "configuration" property are - // correctly updated after modifying the configuration. +struct BuildParametersTests { + @Test + func configurationDependentProperties() throws { var parameters = mockBuildParameters( destination: .host, environment: BuildEnvironment(platform: .linux, configuration: .debug) ) - XCTAssertEqual(parameters.enableTestability, true) + #expect(parameters.enableTestability) parameters.configuration = .release - XCTAssertEqual(parameters.enableTestability, false) + #expect(!parameters.enableTestability) } } diff --git a/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift b/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift index dca0f7c81ac..2f2aa8b5bb8 100644 --- a/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift +++ b/Tests/SPMBuildCoreTests/XCFrameworkMetadataTests.swift @@ -12,10 +12,11 @@ import class Basics.InMemoryFileSystem import SPMBuildCore -import XCTest +import Testing -final class XCFrameworkMetadataTests: XCTestCase { - func testParseFramework() throws { +struct XCFrameworkMetadataTests { + @Test + func parseFramework() throws { let fileSystem = InMemoryFileSystem(files: [ "/Info.plist": """ @@ -62,28 +63,31 @@ final class XCFrameworkMetadataTests: XCTestCase { ]) let metadata = try XCFrameworkMetadata.parse(fileSystem: fileSystem, rootPath: .root) - XCTAssertEqual(metadata, - XCFrameworkMetadata(libraries: [ - XCFrameworkMetadata.Library( - libraryIdentifier: "macos-x86_64", - libraryPath: "MyFramework.framework", - headersPath: nil, - platform: "macos", - architectures: ["x86_64"], - variant: nil - ), - XCFrameworkMetadata.Library( - libraryIdentifier: "ios-arm64_x86_64-simulator", - libraryPath: "MyFramework.framework", - headersPath: nil, - platform: "ios", - architectures: ["arm64", "x86_64"], - variant: "simulator" - ), - ])) + let expected = XCFrameworkMetadata( + libraries: [ + XCFrameworkMetadata.Library( + libraryIdentifier: "macos-x86_64", + libraryPath: "MyFramework.framework", + headersPath: nil, + platform: "macos", + architectures: ["x86_64"], + variant: nil + ), + XCFrameworkMetadata.Library( + libraryIdentifier: "ios-arm64_x86_64-simulator", + libraryPath: "MyFramework.framework", + headersPath: nil, + platform: "ios", + architectures: ["arm64", "x86_64"], + variant: "simulator" + ), + ], + ) + #expect(metadata == expected) } - func testParseLibrary() throws { + @Test + func parseLibrary() throws { let fileSystem = InMemoryFileSystem(files: [ "/Info.plist": """ @@ -117,17 +121,18 @@ final class XCFrameworkMetadataTests: XCTestCase { ]) let metadata = try XCFrameworkMetadata.parse(fileSystem: fileSystem, rootPath: .root) - XCTAssertEqual(metadata, - XCFrameworkMetadata( - libraries: [ - XCFrameworkMetadata.Library( - libraryIdentifier: "macos-x86_64", - libraryPath: "MyLibrary.a", - headersPath: "Headers", - platform: "macos", - architectures: ["x86_64"], - variant: nil - ), - ])) + let expected = XCFrameworkMetadata( + libraries: [ + XCFrameworkMetadata.Library( + libraryIdentifier: "macos-x86_64", + libraryPath: "MyLibrary.a", + headersPath: "Headers", + platform: "macos", + architectures: ["x86_64"], + variant: nil + ), + ], + ) + #expect(metadata == expected) } } diff --git a/Tests/_InternalTestSupportTests/Misc.swift b/Tests/_InternalTestSupportTests/Misc.swift index 1b44eaabe37..4949358d132 100644 --- a/Tests/_InternalTestSupportTests/Misc.swift +++ b/Tests/_InternalTestSupportTests/Misc.swift @@ -11,136 +11,123 @@ //===----------------------------------------------------------------------===// import SPMBuildCore import _InternalTestSupport -import XCTest - -final class TestGetNumberOfMatches: XCTestCase { - func testEmptyStringMatchesOnEmptyStringZeroTimes() { - let matchOn = "" - let value = "" - let expectedNumMatches = 0 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testEmptyStringMatchesOnNonEmptySingleLineStringZeroTimes() { - let matchOn = "" - let value = "This is a non-empty string" - let expectedNumMatches = 0 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testEmptyStringMatchesOnNonEmptyMultilineStringWithNeLineCharacterZeroTimes() { - let matchOn = "" - let value = "This is a non-empty string\nThis is the second line" - let expectedNumMatches = 0 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testEmptyStringMatchesOnNonEmptyMultilineStringUsingTripleDoubleQuotesZeroTimes() { - let matchOn = "" - let value = """ - This is a non-empty string - This is the second line - This is the third line - """ - let expectedNumMatches = 0 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testNonEmptyStringMatchesOnEmptyStringReturnsZero() { - let matchOn = """ - This is a non-empty string - This is the second line - This is the third line - """ - let value = "" - let expectedNumMatches = 0 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testfatalErrorMatchesOnMultiLineWithTwoOccurencesReturnsTwo() { - let matchOn = "error: fatalError" - let value = """ - > swift test 25/10/24 10:44:14 - Building for debugging... - /Users/arandomuser/Documents/personal/repro-swiftpm-6605/Tests/repro-swiftpm-6605Tests/repro_swiftpm_6605Tests.swift:7:19: error: division by zero - let y = 1 / x - ^ - error: fatalError - - error: fatalError - """ - let expectedNumMatches = 2 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testfatalErrorWithLeadingNewLineMatchesOnMultiLineWithTwoOccurencesReturnsTwo() { - let matchOn = "\nerror: fatalError" - let value = """ - > swift test 25/10/24 10:44:14 - Building for debugging... - /Users/arandomuser/Documents/personal/repro-swiftpm-6605/Tests/repro-swiftpm-6605Tests/repro_swiftpm_6605Tests.swift:7:19: error: division by zero - let y = 1 / x - ^ - error: fatalError - - error: fatalError - """ - let expectedNumMatches = 2 - - let actual = getNumberOfMatches(of: matchOn, in: value) - - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") - } - - func testfatalErrorWithLeadingAndTrailingNewLineMatchesOnMultiLineWithOneOccurencesReturnsOne() { - let matchOn = "\nerror: fatalError\n" - let value = """ - > swift test 25/10/24 10:44:14 - Building for debugging... - /Users/arandomuser/Documents/personal/repro-swiftpm-6605/Tests/repro-swiftpm-6605Tests/repro_swiftpm_6605Tests.swift:7:19: error: division by zero - let y = 1 / x - ^ - error: fatalError - - error: fatalError - """ - let expectedNumMatches = 1 - +import Testing + +struct TestGetNumberOfMatches { + @Test( + arguments: [ + ( + matchOn: "", + value: "", + expectedNumMatches: 0, + id: "Empty string matches on empty string zero times", + ), + ( + matchOn: "", + value: "This is a non-empty string", + expectedNumMatches: 0, + id: "Empty string matches on non-empty string zero times", + ), + ( + matchOn: "", + value: "This is a non-empty string\nThis is the second line", + expectedNumMatches: 0, + id: "Empty string matches on non-empty multiline string with new line character zero times", + ), + ( + matchOn: "", + value: """ + This is a non-empty string + This is the second line + This is the third line + """, + expectedNumMatches: 0, + id: "Empty string matches on non-empty multiline string using triple double quotes zero times", + ), + ( + matchOn: """ + This is a non-empty string + This is the second line + This is the third line + """, + value: "", + expectedNumMatches: 0, + id: "non-empty string matches on empty string zero times", + ), + ( + matchOn: "error: fatalError", + value: """ + > swift test 25/10/24 10:44:14 + Building for debugging... + /Users/arandomuser/Documents/personal/repro-swiftpm-6605/Tests/repro-swiftpm-6605Tests/repro_swiftpm_6605Tests.swift:7:19: error: division by zero + let y = 1 / x + ^ + error: fatalError + + error: fatalError + """, + expectedNumMatches: 2, + id: "fatal error matches on multiline with two occurrences returns two", + ), + ( + matchOn: "\nerror: fatalError", + value: """ + > swift test 25/10/24 10:44:14 + Building for debugging... + /Users/arandomuser/Documents/personal/repro-swiftpm-6605/Tests/repro-swiftpm-6605Tests/repro_swiftpm_6605Tests.swift:7:19: error: division by zero + let y = 1 / x + ^ + error: fatalError + + error: fatalError + """, + expectedNumMatches: 2, + id: "fatal error with leading new line matches on multi line with two occurences returns two", + ), + ( + matchOn: "\nerror: fatalError\n", + value: """ + > swift test 25/10/24 10:44:14 + Building for debugging... + /Users/arandomuser/Documents/personal/repro-swiftpm-6605/Tests/repro-swiftpm-6605Tests/repro_swiftpm_6605Tests.swift:7:19: error: division by zero + let y = 1 / x + ^ + error: fatalError + + error: fatalError + """, + expectedNumMatches: 1, + id: "fatal error with leading and trailing new line matches on multi line with two occurences returns two", + ), + ] + ) + func getNumberOfMatchesReturnsExpectedValue( + matchOn: String, + value: String, + expectedNumMatches: Int, + id: String, + ) async throws { let actual = getNumberOfMatches(of: matchOn, in: value) - XCTAssertEqual(actual, expectedNumMatches, "Actual is not as expected") + #expect(actual == expectedNumMatches) } } -final class TestGetBuildSystemArgs: XCTestCase { - func testNilArgumentReturnsEmptyArray() { +struct TestGetBuildSystemArgs { + @Test + func nilArgumentReturnsEmptyArray() { let expected: [String] = [] let inputUnderTest: BuildSystemProvider.Kind? = nil let actual = getBuildSystemArgs(for: inputUnderTest) - XCTAssertEqual(actual, expected, "Actual is not as expected") + #expect(actual == expected) } - private func testValidArgumentsReturnsCorrectCommandLineArguments(_ inputValue: BuildSystemProvider.Kind) { + @Test( + arguments: BuildSystemProvider.Kind.allCases + ) + func validArgumentsReturnsCorrectCommandLineArguments(_ inputValue: BuildSystemProvider.Kind) { let expected = [ "--build-system", "\(inputValue)" @@ -148,18 +135,6 @@ final class TestGetBuildSystemArgs: XCTestCase { let actual = getBuildSystemArgs(for: inputValue) - XCTAssertEqual(actual, expected, "Actual is not as expected") - } - - private func testNativeReturnExpectedArray() { - self.testValidArgumentsReturnsCorrectCommandLineArguments(.native) - } - - private func testNextReturnExpectedArray() { - self.testValidArgumentsReturnsCorrectCommandLineArguments(.swiftbuild) - } - - private func testXcodeReturnExpectedArray() { - self.testValidArgumentsReturnsCorrectCommandLineArguments(.xcode) + #expect(actual == expected) } } From f770c45b9acf4d308b0f9c403fc159ce173dab07 Mon Sep 17 00:00:00 2001 From: Joseph Heck Date: Tue, 6 May 2025 13:46:55 -0700 Subject: [PATCH 80/99] normalize abstracts and help content for swift commands (#8566) Normalizes the structure of abstracts and the help content for commands ### Motivation: The results from `--help` commands were inconsistent over the past years of organic growth. ### Modifications: All command `help` and `abstracts` strings now use sentence casing and punctuation. --------- Co-authored-by: Bri Peticca --- .../Commands/PackageCommands/APIDiff.swift | 4 +- .../PackageCommands/AddDependency.swift | 18 ++-- .../Commands/PackageCommands/AddProduct.swift | 8 +- .../Commands/PackageCommands/AddSetting.swift | 6 +- .../Commands/PackageCommands/AddTarget.swift | 16 ++-- .../PackageCommands/AddTargetDependency.swift | 8 +- .../PackageCommands/ArchiveSource.swift | 4 +- .../PackageCommands/CompletionCommand.swift | 4 +- .../PackageCommands/ComputeChecksum.swift | 2 +- Sources/Commands/PackageCommands/Config.swift | 16 ++-- .../Commands/PackageCommands/Describe.swift | 6 +- .../PackageCommands/DumpCommands.swift | 4 +- .../PackageCommands/EditCommands.swift | 16 ++-- Sources/Commands/PackageCommands/Format.swift | 2 +- Sources/Commands/PackageCommands/Init.swift | 4 +- .../Commands/PackageCommands/Install.swift | 2 +- Sources/Commands/PackageCommands/Learn.swift | 2 +- .../PackageCommands/PluginCommand.swift | 14 ++-- .../PackageCommands/ResetCommands.swift | 4 +- .../Commands/PackageCommands/Resolve.swift | 10 +-- .../PackageCommands/ShowDependencies.swift | 4 +- .../PackageCommands/SwiftPackageCommand.swift | 2 +- .../PackageCommands/ToolsVersionCommand.swift | 6 +- Sources/Commands/PackageCommands/Update.swift | 8 +- Sources/Commands/SwiftBuildCommand.swift | 18 ++-- Sources/Commands/SwiftRunCommand.swift | 16 ++-- Sources/Commands/SwiftTestCommand.swift | 16 ++-- Sources/CoreCommands/Options.swift | 84 +++++++++---------- .../PackageCollectionsCommand.swift | 34 ++++---- .../PackageRegistryCommand+Auth.swift | 14 ++-- .../PackageRegistryCommand+Publish.swift | 4 +- .../PackageRegistryCommand.swift | 18 ++-- Sources/swift-bootstrap/main.swift | 30 +++---- .../BuildPrebuilts.swift | 8 +- Tests/CommandsTests/PackageCommandTests.swift | 2 +- 35 files changed, 207 insertions(+), 207 deletions(-) diff --git a/Sources/Commands/PackageCommands/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift index 3e114341fe8..f24bfd5633c 100644 --- a/Sources/Commands/PackageCommands/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -38,7 +38,7 @@ struct DeprecatedAPIDiff: ParsableCommand { struct APIDiff: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "diagnose-api-breaking-changes", - abstract: "Diagnose API-breaking changes to Swift modules in a package", + abstract: "Diagnose API-breaking changes to Swift modules in a package.", discussion: """ The diagnose-api-breaking-changes command can be used to compare the Swift API of \ a package to a baseline revision, diagnosing any breaking changes which have \ @@ -59,7 +59,7 @@ struct APIDiff: AsyncSwiftCommand { """) var breakageAllowlistPath: AbsolutePath? - @Argument(help: "The baseline treeish to compare to (e.g. a commit hash, branch name, tag, etc.)") + @Argument(help: "The baseline treeish to compare to (for example, a commit hash, branch name, tag, and so on).") var treeish: String @Option(parsing: .upToNextOption, diff --git a/Sources/Commands/PackageCommands/AddDependency.swift b/Sources/Commands/PackageCommands/AddDependency.swift index a968965b118..7f901bd47cf 100644 --- a/Sources/Commands/PackageCommands/AddDependency.swift +++ b/Sources/Commands/PackageCommands/AddDependency.swift @@ -26,34 +26,34 @@ import Workspace extension SwiftPackageCommand { struct AddDependency: SwiftCommand { package static let configuration = CommandConfiguration( - abstract: "Add a package dependency to the manifest" + abstract: "Add a package dependency to the manifest." ) - @Argument(help: "The URL or directory of the package to add") + @Argument(help: "The URL or directory of the package to add.") var dependency: String @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Option(help: "The exact package version to depend on") + @Option(help: "The exact package version to depend on.") var exact: Version? - @Option(help: "The specific package revision to depend on") + @Option(help: "The specific package revision to depend on.") var revision: String? - @Option(help: "The branch of the package to depend on") + @Option(help: "The branch of the package to depend on.") var branch: String? - @Option(help: "The package version to depend on (up to the next major version)") + @Option(help: "The package version to depend on (up to the next major version).") var from: Version? - @Option(help: "The package version to depend on (up to the next minor version)") + @Option(help: "The package version to depend on (up to the next minor version).") var upToNextMinorFrom: Version? - @Option(help: "Specify upper bound on the package version range (exclusive)") + @Option(help: "Specify upper bound on the package version range (exclusive).") var to: Version? - @Option(help: "Specify dependency type") + @Option(help: "Specify dependency type.") var type: DependencyType = .url enum DependencyType: String, Codable, CaseIterable, ExpressibleByArgument { diff --git a/Sources/Commands/PackageCommands/AddProduct.swift b/Sources/Commands/PackageCommands/AddProduct.swift index 787dc84ea99..51f8d664de3 100644 --- a/Sources/Commands/PackageCommands/AddProduct.swift +++ b/Sources/Commands/PackageCommands/AddProduct.swift @@ -36,20 +36,20 @@ extension SwiftPackageCommand { } package static let configuration = CommandConfiguration( - abstract: "Add a new product to the manifest") + abstract: "Add a new product to the manifest.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Argument(help: "The name of the new product") + @Argument(help: "The name of the new product.") var name: String - @Option(help: "The type of target to add") + @Option(help: "The type of target to add.") var type: CommandProductType = .library @Option( parsing: .upToNextOption, - help: "A list of targets that are part of this product" + help: "A list of targets that are part of this product." ) var targets: [String] = [] diff --git a/Sources/Commands/PackageCommands/AddSetting.swift b/Sources/Commands/PackageCommands/AddSetting.swift index 5a4bec1781d..ceac9ea660d 100644 --- a/Sources/Commands/PackageCommands/AddSetting.swift +++ b/Sources/Commands/PackageCommands/AddSetting.swift @@ -33,19 +33,19 @@ extension SwiftPackageCommand { } package static let configuration = CommandConfiguration( - abstract: "Add a new setting to the manifest" + abstract: "Add a new setting to the manifest." ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Option(help: "The target to add the setting to") + @Option(help: "The target to add the setting to.") var target: String @Option( name: .customLong("swift"), parsing: .unconditionalSingleValue, - help: "The Swift language setting(s) to add. Supported settings: \(SwiftSetting.allCases.map(\.rawValue).joined(separator: ", "))" + help: "The Swift language setting(s) to add. Supported settings: \(SwiftSetting.allCases.map(\.rawValue).joined(separator: ", "))." ) var _swiftSettings: [String] diff --git a/Sources/Commands/PackageCommands/AddTarget.swift b/Sources/Commands/PackageCommands/AddTarget.swift index a1fb583d15e..413f19363fc 100644 --- a/Sources/Commands/PackageCommands/AddTarget.swift +++ b/Sources/Commands/PackageCommands/AddTarget.swift @@ -36,33 +36,33 @@ extension SwiftPackageCommand { } package static let configuration = CommandConfiguration( - abstract: "Add a new target to the manifest") + abstract: "Add a new target to the manifest.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Argument(help: "The name of the new target") + @Argument(help: "The name of the new target.") var name: String - @Option(help: "The type of target to add") + @Option(help: "The type of target to add.") var type: TargetType = .library @Option( parsing: .upToNextOption, - help: "A list of target dependency names" + help: "A list of target dependency names." ) var dependencies: [String] = [] - @Option(help: "The URL for a remote binary target") + @Option(help: "The URL for a remote binary target.") var url: String? - @Option(help: "The path to a local binary target") + @Option(help: "The path to a local binary target.") var path: String? - @Option(help: "The checksum for a remote binary target") + @Option(help: "The checksum for a remote binary target.") var checksum: String? - @Option(help: "The testing library to use when generating test targets, which can be one of 'xctest', 'swift-testing', or 'none'") + @Option(help: "The testing library to use when generating test targets, which can be one of 'xctest', 'swift-testing', or 'none'.") var testingLibrary: PackageModelSyntax.AddTarget.TestHarness = .default func run(_ swiftCommandState: SwiftCommandState) throws { diff --git a/Sources/Commands/PackageCommands/AddTargetDependency.swift b/Sources/Commands/PackageCommands/AddTargetDependency.swift index 9c888546b5b..e99cd1a9e57 100644 --- a/Sources/Commands/PackageCommands/AddTargetDependency.swift +++ b/Sources/Commands/PackageCommands/AddTargetDependency.swift @@ -26,18 +26,18 @@ import Workspace extension SwiftPackageCommand { struct AddTargetDependency: SwiftCommand { package static let configuration = CommandConfiguration( - abstract: "Add a new target dependency to the manifest") + abstract: "Add a new target dependency to the manifest.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Argument(help: "The name of the new dependency") + @Argument(help: "The name of the new dependency.") var dependencyName: String - @Argument(help: "The name of the target to update") + @Argument(help: "The name of the target to update.") var targetName: String - @Option(help: "The package in which the dependency resides") + @Option(help: "The package in which the dependency resides.") var package: String? func run(_ swiftCommandState: SwiftCommandState) throws { diff --git a/Sources/Commands/PackageCommands/ArchiveSource.swift b/Sources/Commands/PackageCommands/ArchiveSource.swift index 807a9f1a6c8..c5229c1177e 100644 --- a/Sources/Commands/PackageCommands/ArchiveSource.swift +++ b/Sources/Commands/PackageCommands/ArchiveSource.swift @@ -21,7 +21,7 @@ extension SwiftPackageCommand { struct ArchiveSource: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "archive-source", - abstract: "Create a source archive for the package" + abstract: "Create a source archive for the package." ) @OptionGroup(visibility: .hidden) @@ -29,7 +29,7 @@ extension SwiftPackageCommand { @Option( name: [.short, .long], - help: "The absolute or relative path for the generated source archive" + help: "The absolute or relative path for the generated source archive." ) var output: AbsolutePath? diff --git a/Sources/Commands/PackageCommands/CompletionCommand.swift b/Sources/Commands/PackageCommands/CompletionCommand.swift index 10f51bd0676..49f68613943 100644 --- a/Sources/Commands/PackageCommands/CompletionCommand.swift +++ b/Sources/Commands/PackageCommands/CompletionCommand.swift @@ -22,7 +22,7 @@ extension SwiftPackageCommand { struct CompletionCommand: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "completion-tool", - abstract: "Completion command (for shell completions)" + abstract: "Command to generate shell completions." ) enum Mode: String, CaseIterable, ExpressibleByArgument { @@ -52,7 +52,7 @@ extension SwiftPackageCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Argument(help: "Type of completions to list") + @Argument(help: "Type of completions to list.") var mode: Mode func run(_ swiftCommandState: SwiftCommandState) async throws { diff --git a/Sources/Commands/PackageCommands/ComputeChecksum.swift b/Sources/Commands/PackageCommands/ComputeChecksum.swift index 7ea69dc2335..db417080f3c 100644 --- a/Sources/Commands/PackageCommands/ComputeChecksum.swift +++ b/Sources/Commands/PackageCommands/ComputeChecksum.swift @@ -24,7 +24,7 @@ struct ComputeChecksum: SwiftCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Argument(help: "The absolute or relative path to the binary artifact") + @Argument(help: "The absolute or relative path to the binary artifact.") var path: AbsolutePath func run(_ swiftCommandState: SwiftCommandState) throws { diff --git a/Sources/Commands/PackageCommands/Config.swift b/Sources/Commands/PackageCommands/Config.swift index 6f4b14220b0..d5243a4503b 100644 --- a/Sources/Commands/PackageCommands/Config.swift +++ b/Sources/Commands/PackageCommands/Config.swift @@ -30,7 +30,7 @@ extension SwiftPackageCommand { extension SwiftPackageCommand.Config { struct SetMirror: SwiftCommand { static let configuration = CommandConfiguration( - abstract: "Set a mirror for a dependency" + abstract: "Set a mirror for a dependency." ) @OptionGroup(visibility: .hidden) @@ -45,10 +45,10 @@ extension SwiftPackageCommand.Config { @Option(name: .customLong("mirror-url"), help: .hidden) var _deprecate_mirrorURL: String? - @Option(help: "The original url or identity") + @Option(help: "The original url or identity.") var original: String? - @Option(help: "The mirror url or identity") + @Option(help: "The mirror url or identity.") var mirror: String? func run(_ swiftCommandState: SwiftCommandState) throws { @@ -88,7 +88,7 @@ extension SwiftPackageCommand.Config { struct UnsetMirror: SwiftCommand { static let configuration = CommandConfiguration( - abstract: "Remove an existing mirror" + abstract: "Remove an existing mirror." ) @OptionGroup(visibility: .hidden) @@ -103,10 +103,10 @@ extension SwiftPackageCommand.Config { @Option(name: .customLong("mirror-url"), help: .hidden) var _deprecate_mirrorURL: String? - @Option(help: "The original url or identity") + @Option(help: "The original url or identity.") var original: String? - @Option(help: "The mirror url or identity") + @Option(help: "The mirror url or identity.") var mirror: String? func run(_ swiftCommandState: SwiftCommandState) throws { @@ -143,7 +143,7 @@ extension SwiftPackageCommand.Config { struct GetMirror: SwiftCommand { static let configuration = CommandConfiguration( - abstract: "Print mirror configuration for the given package dependency" + abstract: "Print mirror configuration for the given package dependency." ) @OptionGroup(visibility: .hidden) @@ -154,7 +154,7 @@ extension SwiftPackageCommand.Config { @Option(name: .customLong("original-url"), help: .hidden) var _deprecate_originalURL: String? - @Option(help: "The original url or identity") + @Option(help: "The original url or identity.") var original: String? func run(_ swiftCommandState: SwiftCommandState) throws { diff --git a/Sources/Commands/PackageCommands/Describe.swift b/Sources/Commands/PackageCommands/Describe.swift index 4392b0040fe..99025ef41e6 100644 --- a/Sources/Commands/PackageCommands/Describe.swift +++ b/Sources/Commands/PackageCommands/Describe.swift @@ -23,12 +23,12 @@ import struct TSCBasic.StringError extension SwiftPackageCommand { struct Describe: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Describe the current package") - + abstract: "Describe the current package.") + @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Option(help: "Set the output format") + @Option(help: "Set the output format.") var type: DescribeMode = .text func run(_ swiftCommandState: SwiftCommandState) async throws { diff --git a/Sources/Commands/PackageCommands/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift index 3d40e42644e..52daadb182f 100644 --- a/Sources/Commands/PackageCommands/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -23,7 +23,7 @@ import XCBuildSupport struct DumpSymbolGraph: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Dump Symbol Graph") + abstract: "Dump symbol graphs.") static let defaultMinimumAccessLevel = SymbolGraphExtract.AccessLevel.public @OptionGroup(visibility: .hidden) @@ -35,7 +35,7 @@ struct DumpSymbolGraph: AsyncSwiftCommand { @Flag(help: "Skip members inherited through classes or default implementations.") var skipSynthesizedMembers = false - @Option(help: "Include symbols with this access level or more. Possible values: \(SymbolGraphExtract.AccessLevel.allValueStrings.joined(separator: " | "))") + @Option(help: "Include symbols with this access level or more. Possible values: \(SymbolGraphExtract.AccessLevel.allValueStrings.joined(separator: " | ")).") var minimumAccessLevel = defaultMinimumAccessLevel @Flag(help: "Skip emitting doc comments for members inherited through classes or default implementations.") diff --git a/Sources/Commands/PackageCommands/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift index 2071e9c5329..5509c2ee51f 100644 --- a/Sources/Commands/PackageCommands/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -19,21 +19,21 @@ import Workspace extension SwiftPackageCommand { struct Edit: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Put a package in editable mode") + abstract: "Put a package in editable mode.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Option(help: "The revision to edit", transform: { Revision(identifier: $0) }) + @Option(help: "The revision to edit.", transform: { Revision(identifier: $0) }) var revision: Revision? - @Option(name: .customLong("branch"), help: "The branch to create") + @Option(name: .customLong("branch"), help: "The branch to create.") var checkoutBranch: String? - @Option(help: "Create or use the checkout at this path") + @Option(help: "Create or use the checkout at this path.") var path: AbsolutePath? - @Argument(help: "The identity of the package to edit") + @Argument(help: "The identity of the package to edit.") var packageIdentity: String func run(_ swiftCommandState: SwiftCommandState) async throws { @@ -53,16 +53,16 @@ extension SwiftPackageCommand { struct Unedit: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Remove a package from editable mode") + abstract: "Remove a package from editable mode.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @Flag(name: .customLong("force"), - help: "Unedit the package even if it has uncommitted and unpushed changes") + help: "Unedit the package even if it has uncommitted and unpushed changes.") var shouldForceRemove: Bool = false - @Argument(help: "The identity of the package to unedit") + @Argument(help: "The identity of the package to unedit.") var packageIdentity: String func run(_ swiftCommandState: SwiftCommandState) async throws { diff --git a/Sources/Commands/PackageCommands/Format.swift b/Sources/Commands/PackageCommands/Format.swift index 29399d5ec8c..ad8bf47d1af 100644 --- a/Sources/Commands/PackageCommands/Format.swift +++ b/Sources/Commands/PackageCommands/Format.swift @@ -31,7 +31,7 @@ extension SwiftPackageCommand { var globalOptions: GlobalOptions @Argument(parsing: .captureForPassthrough, - help: "Pass flag through to the swift-format tool") + help: "Pass flag through to the swift-format tool.") var swiftFormatFlags: [String] = [] func run(_ swiftCommandState: SwiftCommandState) async throws { diff --git a/Sources/Commands/PackageCommands/Init.swift b/Sources/Commands/PackageCommands/Init.swift index 5eb8293ee2a..7057845a3df 100644 --- a/Sources/Commands/PackageCommands/Init.swift +++ b/Sources/Commands/PackageCommands/Init.swift @@ -23,7 +23,7 @@ import SPMBuildCore extension SwiftPackageCommand { struct Init: SwiftCommand { public static let configuration = CommandConfiguration( - abstract: "Initialize a new package") + abstract: "Initialize a new package.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -47,7 +47,7 @@ extension SwiftPackageCommand { @OptionGroup() var testLibraryOptions: TestLibraryOptions - @Option(name: .customLong("name"), help: "Provide custom package name") + @Option(name: .customLong("name"), help: "Provide custom package name.") var packageName: String? // This command should support creating the supplied --package-path if it isn't created. diff --git a/Sources/Commands/PackageCommands/Install.swift b/Sources/Commands/PackageCommands/Install.swift index 87a0a16c493..9572c402559 100644 --- a/Sources/Commands/PackageCommands/Install.swift +++ b/Sources/Commands/PackageCommands/Install.swift @@ -30,7 +30,7 @@ extension SwiftPackageCommand { @OptionGroup() var globalOptions: GlobalOptions - @Option(help: "The name of the executable product to install") + @Option(help: "The name of the executable product to install.") var product: String? func run(_ commandState: SwiftCommandState) async throws { diff --git a/Sources/Commands/PackageCommands/Learn.swift b/Sources/Commands/PackageCommands/Learn.swift index 176f7cd45fa..b8a0a614d8a 100644 --- a/Sources/Commands/PackageCommands/Learn.swift +++ b/Sources/Commands/PackageCommands/Learn.swift @@ -23,7 +23,7 @@ extension SwiftPackageCommand { @OptionGroup() var globalOptions: GlobalOptions - static let configuration = CommandConfiguration(abstract: "Learn about Swift and this package") + static let configuration = CommandConfiguration(abstract: "Learn about Swift and this package.") func files(fileSystem: FileSystem, in directory: AbsolutePath, fileExtension: String? = nil) throws -> [AbsolutePath] { guard fileSystem.isDirectory(directory) else { diff --git a/Sources/Commands/PackageCommands/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift index fcc9b73192c..8a1b7e90212 100644 --- a/Sources/Commands/PackageCommands/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -25,7 +25,7 @@ import Workspace struct PluginCommand: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "plugin", - abstract: "Invoke a command plugin or perform other actions on command plugins" + abstract: "Invoke a command plugin or perform other actions on command plugins." ) @OptionGroup(visibility: .hidden) @@ -33,20 +33,20 @@ struct PluginCommand: AsyncSwiftCommand { @Flag( name: .customLong("list"), - help: "List the available command plugins" + help: "List the available command plugins." ) var listCommands: Bool = false struct PluginOptions: ParsableArguments { @Flag( name: .customLong("allow-writing-to-package-directory"), - help: "Allow the plugin to write to the package directory" + help: "Allow the plugin to write to the package directory." ) var allowWritingToPackageDirectory: Bool = false @Option( name: .customLong("allow-writing-to-directory"), - help: "Allow the plugin to write to an additional directory" + help: "Allow the plugin to write to an additional directory." ) var additionalAllowedWritableDirectories: [String] = [] @@ -123,7 +123,7 @@ struct PluginCommand: AsyncSwiftCommand { @Option( name: .customLong("package"), - help: "Limit available plugins to a single package with the given identity" + help: "Limit available plugins to a single package with the given identity." ) var packageIdentity: String? = nil } @@ -131,12 +131,12 @@ struct PluginCommand: AsyncSwiftCommand { @OptionGroup() var pluginOptions: PluginOptions - @Argument(help: "Verb of the command plugin to invoke") + @Argument(help: "Verb of the command plugin to invoke.") var command: String = "" @Argument( parsing: .captureForPassthrough, - help: "Arguments to pass to the command plugin" + help: "Arguments to pass to the command plugin." ) var arguments: [String] = [] diff --git a/Sources/Commands/PackageCommands/ResetCommands.swift b/Sources/Commands/PackageCommands/ResetCommands.swift index 2b8e5c02708..e8b216d1eb2 100644 --- a/Sources/Commands/PackageCommands/ResetCommands.swift +++ b/Sources/Commands/PackageCommands/ResetCommands.swift @@ -17,7 +17,7 @@ import Workspace extension SwiftPackageCommand { struct Clean: SwiftCommand { static let configuration = CommandConfiguration( - abstract: "Delete build artifacts") + abstract: "Delete build artifacts.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -41,7 +41,7 @@ extension SwiftPackageCommand { struct Reset: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Reset the complete cache/build directory") + abstract: "Reset the complete cache/build directory.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift index 7ff3f61453c..4905896b250 100644 --- a/Sources/Commands/PackageCommands/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -20,16 +20,16 @@ import enum PackageModel.TraitConfiguration extension SwiftPackageCommand { struct ResolveOptions: ParsableArguments { - @Option(help: "The version to resolve at", transform: { Version($0) }) + @Option(help: "The version to resolve at.", transform: { Version($0) }) var version: Version? - @Option(help: "The branch to resolve at") + @Option(help: "The branch to resolve at.") var branch: String? - @Option(help: "The revision to resolve at") + @Option(help: "The revision to resolve at.") var revision: String? - @Argument(help: "The name of the package to resolve") + @Argument(help: "The name of the package to resolve.") var packageName: String? /// Specifies the traits to build. @@ -39,7 +39,7 @@ extension SwiftPackageCommand { struct Resolve: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Resolve package dependencies") + abstract: "Resolve package dependencies.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions diff --git a/Sources/Commands/PackageCommands/ShowDependencies.swift b/Sources/Commands/PackageCommands/ShowDependencies.swift index 495a1679be9..6fe6f3940f3 100644 --- a/Sources/Commands/PackageCommands/ShowDependencies.swift +++ b/Sources/Commands/PackageCommands/ShowDependencies.swift @@ -23,12 +23,12 @@ import var TSCBasic.stdoutStream extension SwiftPackageCommand { struct ShowDependencies: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Print the resolved dependency graph") + abstract: "Print the resolved dependency graph.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Option(help: "Set the output format") + @Option(help: "Set the output format.") var format: ShowDependenciesMode = .text @Option(name: [.long, .customShort("o") ], diff --git a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift index 88f6811c419..8d2c7706cf9 100644 --- a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift +++ b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift @@ -29,7 +29,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand { public static var configuration = CommandConfiguration( commandName: "package", _superCommandName: "swift", - abstract: "Perform operations on Swift packages", + abstract: "Perform operations on Swift packages.", discussion: "SEE ALSO: swift build, swift run, swift test", version: SwiftVersion.current.completeDisplayString, subcommands: [ diff --git a/Sources/Commands/PackageCommands/ToolsVersionCommand.swift b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift index 035a47ef7f4..02044f71564 100644 --- a/Sources/Commands/PackageCommands/ToolsVersionCommand.swift +++ b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift @@ -21,15 +21,15 @@ import Workspace struct ToolsVersionCommand: SwiftCommand { static let configuration = CommandConfiguration( commandName: "tools-version", - abstract: "Manipulate tools version of the current package") + abstract: "Manipulate tools version of the current package.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Flag(help: "Set tools version of package to the current tools version in use") + @Flag(help: "Set tools version of package to the current tools version in use.") var setCurrent: Bool = false - @Option(help: "Set tools version of package to the given value") + @Option(help: "Set tools version of package to the given value.") var set: String? enum ToolsVersionMode { diff --git a/Sources/Commands/PackageCommands/Update.swift b/Sources/Commands/PackageCommands/Update.swift index 36a09a1aa75..2bea09a4500 100644 --- a/Sources/Commands/PackageCommands/Update.swift +++ b/Sources/Commands/PackageCommands/Update.swift @@ -21,16 +21,16 @@ import Workspace extension SwiftPackageCommand { struct Update: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Update package dependencies") - + abstract: "Update package dependencies.") + @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @Flag(name: [.long, .customShort("n")], - help: "Display the list of dependencies that can be updated") + help: "Display the list of dependencies that can be updated.") var dryRun: Bool = false - @Argument(help: "The packages to update") + @Argument(help: "The packages to update.") var packages: [String] = [] func run(_ swiftCommandState: SwiftCommandState) async throws { diff --git a/Sources/Commands/SwiftBuildCommand.swift b/Sources/Commands/SwiftBuildCommand.swift index 1c6dfb45fe8..8bc8e1aaccb 100644 --- a/Sources/Commands/SwiftBuildCommand.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -72,35 +72,35 @@ struct BuildCommandOptions: ParsableArguments { } /// If the test should be built. - @Flag(help: "Build both source and test targets") + @Flag(help: "Build both source and test targets.") var buildTests: Bool = false /// Whether to enable code coverage. @Flag(name: .customLong("code-coverage"), inversion: .prefixedEnableDisable, - help: "Enable code coverage") + help: "Enable code coverage.") var enableCodeCoverage: Bool = false /// If the binary output path should be printed. - @Flag(name: .customLong("show-bin-path"), help: "Print the binary output path") + @Flag(name: .customLong("show-bin-path"), help: "Print the binary output path.") var shouldPrintBinPath: Bool = false /// Whether to output a graphviz file visualization of the combined job graph for all targets @Flag(name: .customLong("print-manifest-job-graph"), - help: "Write the command graph for the build manifest as a Graphviz file") + help: "Write the command graph for the build manifest as a Graphviz file.") var printManifestGraphviz: Bool = false /// Whether to output a graphviz file visualization of the PIF JSON sent to Swift Build. @Flag(name: .customLong("print-pif-manifest-graph"), - help: "Write the PIF JSON sent to Swift Build as a Graphviz file") + help: "Write the PIF JSON sent to Swift Build as a Graphviz file.") var printPIFManifestGraphviz: Bool = false /// Specific target to build. - @Option(help: "Build the specified target") + @Option(help: "Build the specified target.") var target: String? /// Specific product to build. - @Option(help: "Build the specified product") + @Option(help: "Build the specified product.") var product: String? /// Testing library options. @@ -115,7 +115,7 @@ struct BuildCommandOptions: ParsableArguments { package var traits: TraitOptions /// If should link the Swift stdlib statically. - @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically") + @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically.") public var shouldLinkStaticSwiftStdlib: Bool = false } @@ -124,7 +124,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand { public static var configuration = CommandConfiguration( commandName: "build", _superCommandName: "swift", - abstract: "Build sources into binary products", + abstract: "Build sources into binary products.", discussion: "SEE ALSO: swift run, swift package, swift test", version: SwiftVersion.current.completeDisplayString, helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) diff --git a/Sources/Commands/SwiftRunCommand.swift b/Sources/Commands/SwiftRunCommand.swift index ff8db0d3a94..ac08161af22 100644 --- a/Sources/Commands/SwiftRunCommand.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -62,11 +62,11 @@ struct RunCommandOptions: ParsableArguments { static func help(for value: RunCommandOptions.RunMode) -> ArgumentHelp? { switch value { case .repl: - return "Launch Swift REPL for the package" + return "Launch Swift REPL for the package." case .debugger: - return "Launch the executable in a debugger session" + return "Launch the executable in a debugger session." case .run: - return "Launch the executable with the provided arguments" + return "Launch the executable with the provided arguments." } } } @@ -75,17 +75,17 @@ struct RunCommandOptions: ParsableArguments { @Flag var mode: RunMode = .run /// If the executable product should be built before running. - @Flag(name: .customLong("skip-build"), help: "Skip building the executable product") + @Flag(name: .customLong("skip-build"), help: "Skip building the executable product.") var shouldSkipBuild: Bool = false var shouldBuild: Bool { !shouldSkipBuild } /// If the test should be built. - @Flag(name: .customLong("build-tests"), help: "Build both source and test targets") + @Flag(name: .customLong("build-tests"), help: "Build both source and test targets.") var shouldBuildTests: Bool = false /// The executable product to run. - @Argument(help: "The executable to run", completion: .shellCommand("swift package completion-tool list-executables")) + @Argument(help: "The executable to run.", completion: .shellCommand("swift package completion-tool list-executables")) var executable: String? /// Specifies the traits to build the product with. @@ -94,7 +94,7 @@ struct RunCommandOptions: ParsableArguments { /// The arguments to pass to the executable. @Argument(parsing: .captureForPassthrough, - help: "The arguments to pass to the executable") + help: "The arguments to pass to the executable.") var arguments: [String] = [] } @@ -103,7 +103,7 @@ public struct SwiftRunCommand: AsyncSwiftCommand { public static var configuration = CommandConfiguration( commandName: "run", _superCommandName: "swift", - abstract: "Build and run an executable product", + abstract: "Build and run an executable product.", discussion: "SEE ALSO: swift build, swift package, swift test", version: SwiftVersion.current.completeDisplayString, helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) diff --git a/Sources/Commands/SwiftTestCommand.swift b/Sources/Commands/SwiftTestCommand.swift index aefa252850e..a5b1e2cd919 100644 --- a/Sources/Commands/SwiftTestCommand.swift +++ b/Sources/Commands/SwiftTestCommand.swift @@ -81,7 +81,7 @@ extension TestError: CustomStringConvertible { struct SharedOptions: ParsableArguments { @Flag(name: .customLong("skip-build"), - help: "Skip building the test target") + help: "Skip building the test target.") var shouldSkipBuilding: Bool = false /// The test product to use. This is useful when there are multiple test products @@ -160,12 +160,12 @@ struct TestCommandOptions: ParsableArguments { /// List the tests and exit. @Flag(name: [.customLong("list-tests"), .customShort("l")], - help: "Lists test methods in specifier format") + help: "Lists test methods in specifier format.") var _deprecated_shouldListTests: Bool = false /// If the path of the exported code coverage JSON should be printed. @Flag(name: [.customLong("show-codecov-path"), .customLong("show-code-coverage-path"), .customLong("show-coverage-path")], - help: "Print the path of the exported code coverage JSON file") + help: "Print the path of the exported code coverage JSON file.") var shouldPrintCodeCovPath: Bool = false var testCaseSpecifier: TestCaseSpecifier { @@ -180,13 +180,13 @@ struct TestCommandOptions: ParsableArguments { var _testCaseSpecifier: String? @Option(help: """ - Run test cases matching regular expression, Format: . \ - or ./ + Run test cases that match a regular expression, Format: '.' \ + or './'. """) var filter: [String] = [] @Option(name: .customLong("skip"), - help: "Skip test cases matching regular expression, Example: --skip PerformanceTests") + help: "Skip test cases that match a regular expression, Example: '--skip PerformanceTests'.") var _testCaseSkip: [String] = [] /// Path where the xUnit xml file should be generated. @@ -210,7 +210,7 @@ struct TestCommandOptions: ParsableArguments { /// Whether to enable code coverage. @Flag(name: .customLong("code-coverage"), inversion: .prefixedEnableDisable, - help: "Enable code coverage") + help: "Enable code coverage.") var enableCodeCoverage: Bool = false /// Configure test output. @@ -257,7 +257,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand { public static var configuration = CommandConfiguration( commandName: "test", _superCommandName: "swift", - abstract: "Build and run tests", + abstract: "Build and run tests.", discussion: "SEE ALSO: swift build, swift run, swift package", version: SwiftVersion.current.completeDisplayString, subcommands: [ diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index 29336f002f6..d12fe55f2fd 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -70,24 +70,24 @@ public struct LocationOptions: ParsableArguments { @Option( name: .customLong("package-path"), - help: "Specify the package path to operate on (default current directory). This changes the working directory before any other operation", + help: "Specify the package path to operate on (default current directory). This changes the working directory before any other operation.", completion: .directory ) public var packageDirectory: AbsolutePath? - @Option(name: .customLong("cache-path"), help: "Specify the shared cache directory path", completion: .directory) + @Option(name: .customLong("cache-path"), help: "Specify the shared cache directory path.", completion: .directory) public var cacheDirectory: AbsolutePath? @Option( name: .customLong("config-path"), - help: "Specify the shared configuration directory path", + help: "Specify the shared configuration directory path.", completion: .directory ) public var configurationDirectory: AbsolutePath? @Option( name: .customLong("security-path"), - help: "Specify the shared security directory path", + help: "Specify the shared security directory path.", completion: .directory ) public var securityDirectory: AbsolutePath? @@ -95,7 +95,7 @@ public struct LocationOptions: ParsableArguments { /// The custom .build directory, if provided. @Option( name: .customLong("scratch-path"), - help: "Specify a custom scratch directory path (default .build)", + help: "Specify a custom scratch directory path. (default .build)", completion: .directory ) var _scratchDirectory: AbsolutePath? @@ -121,7 +121,7 @@ public struct LocationOptions: ParsableArguments { /// Path to the directory containing installed Swift SDKs. @Option( name: .customLong("swift-sdks-path"), - help: "Path to the directory containing installed Swift SDKs", + help: "Path to the directory containing installed Swift SDKs.", completion: .directory ) public var swiftSDKsDirectory: AbsolutePath? @@ -165,7 +165,7 @@ public struct CachingOptions: ParsableArguments { @Flag( name: .customLong("dependency-cache"), inversion: .prefixedEnableDisable, - help: "Use a shared cache when fetching dependencies" + help: "Use a shared cache when fetching dependencies." ) public var useDependenciesCache: Bool = true @@ -180,7 +180,7 @@ public struct CachingOptions: ParsableArguments { /// Disables manifest caching. @Option( name: .customLong("manifest-cache"), - help: "Caching mode of Package.swift manifests (shared: shared cache, local: package's build directory, none: disabled)" + help: "Caching mode of Package.swift manifests. Valid values are: (shared: shared cache, local: package's build directory, none: disabled)" ) public var manifestCachingMode: ManifestCachingMode = .shared @@ -197,7 +197,7 @@ public struct CachingOptions: ParsableArguments { /// Whether to use macro prebuilts or not @Flag(name: .customLong("experimental-prebuilts"), inversion: .prefixedEnableDisable, - help: "Whether to use prebuilt swift-syntax libraries for macros") + help: "Whether to use prebuilt swift-syntax libraries for macros.") public var usePrebuilts: Bool = false } @@ -205,11 +205,11 @@ public struct LoggingOptions: ParsableArguments { public init() {} /// The verbosity of informational output. - @Flag(name: .shortAndLong, help: "Increase verbosity to include informational output") + @Flag(name: .shortAndLong, help: "Increase verbosity to include informational output.") public var verbose: Bool = false /// The verbosity of informational output. - @Flag(name: [.long, .customLong("vv")], help: "Increase verbosity to include debug output") + @Flag(name: [.long, .customLong("vv")], help: "Increase verbosity to include debug output.") public var veryVerbose: Bool = false /// Whether logging output should be limited to `.error`. @@ -230,11 +230,11 @@ public struct SecurityOptions: ParsableArguments { public init() {} /// Disables sandboxing when executing subprocesses. - @Flag(name: .customLong("disable-sandbox"), help: "Disable using the sandbox when executing subprocesses") + @Flag(name: .customLong("disable-sandbox"), help: "Disable using the sandbox when executing subprocesses.") public var shouldDisableSandbox: Bool = false /// Force usage of the netrc file even in cases where it is not allowed. - @Flag(name: .customLong("netrc"), help: "Use netrc file even in cases where other credential stores are preferred") + @Flag(name: .customLong("netrc"), help: "Use netrc file even in cases where other credential stores are preferred.") public var forceNetrc: Bool = false /// Whether to load netrc files for authenticating with remote servers @@ -243,14 +243,14 @@ public struct SecurityOptions: ParsableArguments { @Flag( inversion: .prefixedEnableDisable, exclusivity: .exclusive, - help: "Load credentials from a netrc file" + help: "Load credentials from a netrc file." ) public var netrc: Bool = true /// The path to the netrc file used when `netrc` is `true`. @Option( name: .customLong("netrc-file"), - help: "Specify the netrc file path", + help: "Specify the netrc file path.", completion: .file() ) public var netrcFilePath: AbsolutePath? @@ -262,7 +262,7 @@ public struct SecurityOptions: ParsableArguments { @Flag( inversion: .prefixedEnableDisable, exclusivity: .exclusive, - help: "Search credentials in macOS keychain" + help: "Search credentials in macOS keychain." ) public var keychain: Bool = true #else @@ -283,7 +283,7 @@ public struct SecurityOptions: ParsableArguments { @Flag( inversion: .prefixedEnableDisable, exclusivity: .exclusive, - help: "Validate signature of a signed package release downloaded from registry" + help: "Validate signature of a signed package release downloaded from registry." ) public var signatureValidation: Bool = true } @@ -298,15 +298,15 @@ public struct ResolverOptions: ParsableArguments { /// Use Package.resolved file for resolving dependencies. @Flag( name: [.long, .customLong("disable-automatic-resolution"), .customLong("only-use-versions-from-resolved-file")], - help: "Only use versions from the Package.resolved file and fail resolution if it is out-of-date" + help: "Only use versions from the Package.resolved file and fail resolution if it is out-of-date." ) public var forceResolvedVersions: Bool = false /// Skip updating dependencies from their remote during a resolution. - @Flag(name: .customLong("skip-update"), help: "Skip updating dependencies from their remote during a resolution") + @Flag(name: .customLong("skip-update"), help: "Skip updating dependencies from their remote during a resolution.") public var skipDependencyUpdate: Bool = false - @Flag(help: "Define automatic transformation of source control based dependencies to registry based ones") + @Flag(help: "Define automatic transformation of source control based dependencies to registry based ones.") public var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation = .disabled @@ -315,13 +315,13 @@ public struct ResolverOptions: ParsableArguments { @Flag( name: .customLong("experimental-prune-unused-dependencies"), help: ArgumentHelp( - "Enables the ability to prune unused dependencies of the package to avoid redundant loads during resolution", + "Enables the ability to prune unused dependencies of the package to avoid redundant loads during resolution.", visibility: .hidden ) ) public var pruneDependencies: Bool = false - @Option(help: "Default registry URL to use, instead of the registries.json configuration file") + @Option(help: "Default registry URL to use, instead of the registries.json configuration file.") public var defaultRegistryURL: URL? public enum SourceControlToRegistryDependencyTransformation: EnumerableFlag { @@ -343,11 +343,11 @@ public struct ResolverOptions: ParsableArguments { public static func help(for value: SourceControlToRegistryDependencyTransformation) -> ArgumentHelp? { switch value { case .disabled: - return "disable source control to registry transformation" + return "Disable source control to registry transformation." case .identity: - return "look up source control dependencies in the registry and use their registry identity when possible to help deduplicate across the two origins" + return "Look up source control dependencies in the registry and use their registry identity when possible to help deduplicate across the two origins." case .swizzle: - return "look up source control dependencies in the registry and use the registry to retrieve them instead of source control when possible" + return "Look up source control dependencies in the registry and use the registry to retrieve them instead of source control when possible." } } } @@ -363,28 +363,28 @@ public struct BuildOptions: ParsableArguments { @Option( name: .customLong("Xcc", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all C compiler invocations" + help: "Pass flag through to all C compiler invocations." ) var cCompilerFlags: [String] = [] @Option( name: .customLong("Xswiftc", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all Swift compiler invocations" + help: "Pass flag through to all Swift compiler invocations." ) var swiftCompilerFlags: [String] = [] @Option( name: .customLong("Xlinker", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all linker invocations" + help: "Pass flag through to all linker invocations." ) var linkerFlags: [String] = [] @Option( name: .customLong("Xcxx", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all C++ compiler invocations" + help: "Pass flag through to all C++ compiler invocations." ) var cxxCompilerFlags: [String] = [] @@ -392,7 +392,7 @@ public struct BuildOptions: ParsableArguments { name: .customLong("Xxcbuild", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp( - "Pass flag through to the Xcode build system invocations", + "Pass flag through to the Xcode build system invocations.", visibility: .hidden ) ) @@ -402,7 +402,7 @@ public struct BuildOptions: ParsableArguments { name: .customLong("Xbuild-tools-swiftc", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp( - "Pass flag to Swift compiler invocations for build-time executables (manifest and plugins)", + "Pass flag to Swift compiler invocations for build-time executables (manifest and plugins).", visibility: .hidden ) ) @@ -412,7 +412,7 @@ public struct BuildOptions: ParsableArguments { name: .customLong("Xmanifest", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp( - "Pass flag to the manifest build invocation. Deprecated: use '-Xbuild-tools-swiftc' instead", + "Pass flag to the manifest build invocation. Deprecated: use '-Xbuild-tools-swiftc' instead.", visibility: .hidden ) ) @@ -466,14 +466,14 @@ public struct BuildOptions: ParsableArguments { /// Filter for selecting a specific Swift SDK to build with. @Option( name: .customLong("swift-sdk"), - help: "Filter for selecting a specific Swift SDK to build with" + help: "Filter for selecting a specific Swift SDK to build with." ) public var swiftSDKSelector: String? /// Which compile-time sanitizers should be enabled. @Option( name: .customLong("sanitize"), - help: "Turn on runtime checks for erroneous behavior, possible values: \(Sanitizer.formattedValues)" + help: "Turn on runtime checks for erroneous behavior, possible values: \(Sanitizer.formattedValues)." ) public var sanitizers: [Sanitizer] = [] @@ -481,7 +481,7 @@ public struct BuildOptions: ParsableArguments { EnabledSanitizers(Set(sanitizers)) } - @Flag(help: "Enable or disable indexing-while-building feature") + @Flag(help: "Enable or disable indexing-while-building feature.") public var indexStoreMode: StoreMode = .autoIndexStore /// Instead of building the target, perform the minimal amount of work to prepare it for indexing. @@ -504,7 +504,7 @@ public struct BuildOptions: ParsableArguments { public var shouldEnableParseableModuleInterfaces: Bool = false /// The number of jobs for llbuild to start (aka the number of schedulerLanes) - @Option(name: .shortAndLong, help: "The number of jobs to spawn in parallel during the build process") + @Option(name: .shortAndLong, help: "The number of jobs to spawn in parallel during the build process.") public var jobs: UInt32? /// Whether to use the integrated Swift driver rather than shelling out @@ -514,7 +514,7 @@ public struct BuildOptions: ParsableArguments { /// A flag that indicates this build should check whether targets only import /// their explicitly-declared dependencies - @Option(help: "A flag that indicates this build should check whether targets only import their explicitly-declared dependencies") + @Option(help: "A flag that indicates this build should check whether targets only import their explicitly-declared dependencies.") public var explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode = .none /// Whether to use the explicit module build flow (with the integrated driver) @@ -526,7 +526,7 @@ public struct BuildOptions: ParsableArguments { var _buildSystem: BuildSystemProvider.Kind = .native /// The Debug Information Format to use. - @Option(name: .customLong("debug-info-format", withSingleDash: true), help: "The Debug Information Format to use") + @Option(name: .customLong("debug-info-format", withSingleDash: true), help: "The Debug Information Format to use.") public var debugInfoFormat: DebugInfoFormat = .dwarf public var buildSystem: BuildSystemProvider.Kind { @@ -602,12 +602,12 @@ public struct LinkerOptions: ParsableArguments { @Flag( name: .customLong("dead-strip"), inversion: .prefixedEnableDisable, - help: "Disable/enable dead code stripping by the linker" + help: "Disable/enable dead code stripping by the linker." ) public var linkerDeadStrip: Bool = true /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying - @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default") + @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default.") public var shouldDisableLocalRpath: Bool = false } @@ -622,7 +622,7 @@ public struct TestLibraryOptions: ParsableArguments { /// have the correct default value if the user didn't specify one. @Flag(name: .customLong("xctest"), inversion: .prefixedEnableDisable, - help: "Enable support for XCTest") + help: "Enable support for XCTest.") public var explicitlyEnableXCTestSupport: Bool? /// Whether to enable support for Swift Testing (as explicitly specified by the user.) @@ -631,7 +631,7 @@ public struct TestLibraryOptions: ParsableArguments { /// have the correct default value if the user didn't specify one. @Flag(name: .customLong("swift-testing"), inversion: .prefixedEnableDisable, - help: "Enable support for Swift Testing") + help: "Enable support for Swift Testing.") public var explicitlyEnableSwiftTestingLibrarySupport: Bool? /// Legacy experimental equivalent of ``explicitlyEnableSwiftTestingLibrarySupport``. diff --git a/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift b/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift index e3a95cff4e9..e054d54a71b 100644 --- a/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift +++ b/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift @@ -58,7 +58,7 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { public static var configuration = CommandConfiguration( commandName: "package-collection", _superCommandName: "swift", - abstract: "Interact with package collections", + abstract: "Interact with package collections.", discussion: "SEE ALSO: swift build, swift package, swift run, swift test", version: SwiftVersion.current.completeDisplayString, subcommands: [ @@ -77,7 +77,7 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { // MARK: Collections struct List: AsyncSwiftCommand { - static let configuration = CommandConfiguration(abstract: "List configured collections") + static let configuration = CommandConfiguration(abstract: "List configured collections.") @OptionGroup var jsonOptions: JSONOptions @@ -101,7 +101,7 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { } struct Refresh: AsyncSwiftCommand { - static let configuration = CommandConfiguration(abstract: "Refresh configured collections") + static let configuration = CommandConfiguration(abstract: "Refresh configured collections.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions @@ -115,18 +115,18 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { } struct Add: AsyncSwiftCommand { - static let configuration = CommandConfiguration(abstract: "Add a new collection") + static let configuration = CommandConfiguration(abstract: "Add a new collection.") - @Argument(help: "URL of the collection to add") + @Argument(help: "URL of the collection to add.") var collectionURL: String - @Option(name: .long, help: "Sort order for the added collection") + @Option(name: .long, help: "Sort order for the added collection.") var order: Int? - @Flag(name: .long, help: "Trust the collection even if it is unsigned") + @Flag(name: .long, help: "Trust the collection even if it is unsigned.") var trustUnsigned: Bool = false - @Flag(name: .long, help: "Skip signature check if the collection is signed") + @Flag(name: .long, help: "Skip signature check if the collection is signed.") var skipSignatureCheck: Bool = false @OptionGroup(visibility: .hidden) @@ -158,9 +158,9 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { } struct Remove: AsyncSwiftCommand { - static let configuration = CommandConfiguration(abstract: "Remove a configured collection") + static let configuration = CommandConfiguration(abstract: "Remove a configured collection.") - @Argument(help: "URL of the collection to remove") + @Argument(help: "URL of the collection to remove.") var collectionURL: String @OptionGroup(visibility: .hidden) @@ -186,15 +186,15 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { } struct Search: AsyncSwiftCommand { - static var configuration = CommandConfiguration(abstract: "Search for packages by keywords or module names") + static var configuration = CommandConfiguration(abstract: "Search for packages by keywords or module names.") @OptionGroup var jsonOptions: JSONOptions - @Flag(help: "Pick the method for searching") + @Flag(help: "Pick the method for searching.") var searchMethod: SearchMethod - @Argument(help: "Search query") + @Argument(help: "The search query.") var searchQuery: String @OptionGroup(visibility: .hidden) @@ -233,18 +233,18 @@ public struct PackageCollectionsCommand: AsyncParsableCommand { // MARK: Packages struct Describe: AsyncSwiftCommand { - static var configuration = CommandConfiguration(abstract: "Get metadata for a collection or a package included in an imported collection") + static var configuration = CommandConfiguration(abstract: "Get metadata for a collection or a package included in an imported collection.") @OptionGroup var jsonOptions: JSONOptions - @Argument(help: "URL of the package or collection to get information for") + @Argument(help: "URL of the package or collection to get information for.") var packageURL: String - @Option(name: .long, help: "Version of the package to get information for") + @Option(name: .long, help: "Version of the package to get information for.") var version: String? - @Flag(name: .long, help: "Skip signature check if the collection is signed") + @Flag(name: .long, help: "Skip signature check if the collection is signed.") var skipSignatureCheck: Bool = false @OptionGroup(visibility: .hidden) diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift index d5658857f10..a7a5cb35521 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift @@ -113,7 +113,7 @@ extension PackageRegistryCommand { } static let configuration = CommandConfiguration( - abstract: "Log in to a registry" + abstract: "Log in to a registry." ) static let maxPasswordLength = 512 @@ -125,29 +125,29 @@ extension PackageRegistryCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Argument(help: "The registry URL") + @Argument(help: "The registry URL.") var url: URL? var registryURL: URL? { self.url } - @Option(help: "Username") + @Option(help: "The username for the registry.") var username: String? - @Option(help: "Password") + @Option(help: "The password for the registry.") var password: String? - @Option(help: "Access token") + @Option(help: "The access token for the registry.") var token: String? @Option( name: .customLong("token-file"), - help: "Path to the file containing access token" + help: "Path to the file containing access token." ) var tokenFilePath: AbsolutePath? - @Flag(help: "Allow writing to netrc file without confirmation") + @Flag(help: "Allow writing to netrc file without confirmation.") var noConfirm: Bool = false private static let PLACEHOLDER_TOKEN_USER = "token" diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift index 1b1ffc700fb..abac4d8628a 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift @@ -38,7 +38,7 @@ extension PackageRegistryCommand { static let metadataFilename = "package-metadata.json" static let configuration = CommandConfiguration( - abstract: "Publish to a registry" + abstract: "Publish to a registry." ) @OptionGroup(visibility: .hidden) @@ -83,7 +83,7 @@ extension PackageRegistryCommand { ) var certificateChainPaths: [AbsolutePath] = [] - @Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL") + @Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL.") var allowInsecureHTTP: Bool = false @Flag(help: "Dry run only; prepare the archive and sign it but do not publish to the registry.") diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift index 0b7cd4b88e7..f41004fcd8e 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift @@ -23,7 +23,7 @@ public struct PackageRegistryCommand: AsyncParsableCommand { public static var configuration = CommandConfiguration( commandName: "package-registry", _superCommandName: "swift", - abstract: "Interact with package registry and manage related configuration", + abstract: "Interact with package registry and manage related configuration.", discussion: "SEE ALSO: swift package", version: SwiftVersion.current.completeDisplayString, subcommands: [ @@ -43,22 +43,22 @@ public struct PackageRegistryCommand: AsyncParsableCommand { struct Set: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Set a custom registry" + abstract: "Set a custom registry." ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Flag(help: "Apply settings to all projects for this user") + @Flag(help: "Apply settings to all projects for this user.") var global: Bool = false - @Option(help: "Associate the registry with a given scope") + @Option(help: "Associate the registry with a given scope.") var scope: String? - @Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL") + @Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL.") var allowInsecureHTTP: Bool = false - @Argument(help: "The registry URL") + @Argument(help: "The registry URL.") var url: URL var registryURL: URL { @@ -90,16 +90,16 @@ public struct PackageRegistryCommand: AsyncParsableCommand { struct Unset: AsyncSwiftCommand { static let configuration = CommandConfiguration( - abstract: "Remove a configured registry" + abstract: "Remove a configured registry." ) @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - @Flag(help: "Apply settings to all projects for this user") + @Flag(help: "Apply settings to all projects for this user.") var global: Bool = false - @Option(help: "Associate the registry with a given scope") + @Option(help: "Associate the registry with a given scope.") var scope: String? func run(_ swiftCommandState: SwiftCommandState) async throws { diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index f1727ca4e28..8265e1ecb1a 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -43,17 +43,17 @@ await { () async in struct SwiftBootstrapBuildTool: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "swift-bootstrap", - abstract: "Bootstrapping build tool, only use in the context of bootstrapping SwiftPM itself", + abstract: "Bootstrapping build tool, only use in the context of bootstrapping SwiftPM itself.", shouldDisplay: false ) @Option(name: .customLong("package-path"), - help: "Specify the package path to operate on (default current directory). This changes the working directory before any other operation", + help: "Specify the package path to operate on (default current directory). This changes the working directory before any other operation.", completion: .directory) public var packageDirectory: AbsolutePath? /// The custom .build directory, if provided. - @Option(name: .customLong("scratch-path"), help: "Specify a custom scratch directory path (default .build)", completion: .directory) + @Option(name: .customLong("scratch-path"), help: "Specify a custom scratch directory path (default .build).", completion: .directory) var _scratchDirectory: AbsolutePath? @Option(name: .customLong("build-path"), help: .hidden) @@ -63,53 +63,53 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { self._scratchDirectory ?? self._deprecated_buildPath } - @Option(name: .shortAndLong, help: "Build with configuration") + @Option(name: .shortAndLong, help: "Build with configuration.") public var configuration: BuildConfiguration = .debug @Option(name: .customLong("Xcc", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all C compiler invocations") + help: "Pass flag through to all C compiler invocations.") var cCompilerFlags: [String] = [] @Option(name: .customLong("Xswiftc", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all Swift compiler invocations") + help: "Pass flag through to all Swift compiler invocations.") var swiftCompilerFlags: [String] = [] @Option(name: .customLong("Xlinker", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all linker invocations") + help: "Pass flag through to all linker invocations.") var linkerFlags: [String] = [] @Option(name: .customLong("Xcxx", withSingleDash: true), parsing: .unconditionalSingleValue, - help: "Pass flag through to all C++ compiler invocations") + help: "Pass flag through to all C++ compiler invocations.") var cxxCompilerFlags: [String] = [] @Option(name: .customLong("Xxcbuild", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp( - "Pass flag through to the Xcode build system invocations", + "Pass flag through to the Xcode build system invocations.", visibility: .hidden)) public var xcbuildFlags: [String] = [] @Option(name: .customLong("Xbuild-tools-swiftc", withSingleDash: true), parsing: .unconditionalSingleValue, - help: ArgumentHelp("Pass flag to the manifest build invocation", + help: ArgumentHelp("Pass flag to the manifest build invocation.", visibility: .hidden)) public var manifestFlags: [String] = [] @Option( name: .customLong("arch"), - help: ArgumentHelp("Build the package for the these architectures", visibility: .hidden)) + help: ArgumentHelp("Build the package for the these architectures.", visibility: .hidden)) public var architectures: [String] = [] /// The verbosity of informational output. - @Flag(name: .shortAndLong, help: "Increase verbosity to include informational output") + @Flag(name: .shortAndLong, help: "Increase verbosity to include informational output.") public var verbose: Bool = false /// The verbosity of informational output. - @Flag(name: [.long, .customLong("vv")], help: "Increase verbosity to include debug output") + @Flag(name: [.long, .customLong("vv")], help: "Increase verbosity to include debug output.") public var veryVerbose: Bool = false /// Whether to use the integrated Swift driver rather than shelling out @@ -119,7 +119,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { /// An option that indicates this build should check whether targets only import /// their explicitly-declared dependencies - @Option(help: "Check that targets only import their explicitly-declared dependencies") + @Option(help: "Check that targets only import their explicitly-declared dependencies.") public var explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode = .none enum TargetDependencyImportCheckingMode: String, Codable, ExpressibleByArgument, CaseIterable { @@ -128,7 +128,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { } /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying - @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default") + @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default.") public var shouldDisableLocalRpath: Bool = false /// The build system to use. diff --git a/Sources/swift-build-prebuilts/BuildPrebuilts.swift b/Sources/swift-build-prebuilts/BuildPrebuilts.swift index 9d746b2a9d2..f827ff97357 100644 --- a/Sources/swift-build-prebuilts/BuildPrebuilts.swift +++ b/Sources/swift-build-prebuilts/BuildPrebuilts.swift @@ -72,16 +72,16 @@ let dockerImageRoot = "swiftlang/swift:nightly-" @main struct BuildPrebuilts: AsyncParsableCommand { - @Option(help: "The directory to generate the artifacts to") + @Option(help: "The directory to generate the artifacts to.") var stageDir = try! AbsolutePath(validating: FileManager.default.currentDirectoryPath).appending("stage") - @Flag(help: "Whether to build artifacts using docker") + @Flag(help: "Whether to build artifacts using docker.") var docker = false - @Flag(help: "Whether to build artifacts using docker only") + @Flag(help: "Whether to build artifacts using docker only.") var dockerOnly = false - @Option(help: "The command to use for docker") + @Option(help: "The command to use for docker.") var dockerCommand: String = "docker" mutating func run() async throws { diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 3b9504eb777..74181b45067 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -95,7 +95,7 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { func testCompletionTool() async throws { let stdout = try await execute(["completion-tool", "--help"]).stdout - XCTAssertMatch(stdout, .contains("OVERVIEW: Completion command (for shell completions)")) + XCTAssertMatch(stdout, .contains("OVERVIEW: Command to generate shell completions.")) } func testInitOverview() async throws { From 7571d6adc9997f9e9229bcef94670eedb33a6988 Mon Sep 17 00:00:00 2001 From: Louis Qian Date: Tue, 6 May 2025 17:22:20 -0500 Subject: [PATCH 81/99] Display PID of other running SwiftPM processes (#8575) --- Sources/CoreCommands/SwiftCommandState.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 12e193d838f..57551331b75 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -1055,29 +1055,38 @@ public final class SwiftCommandState { self.workspaceLockState = .locked let workspaceLock = try FileLock.prepareLock(fileToLock: self.scratchDirectory) + let lockFile = self.scratchDirectory.appending(".lock").pathString // Try a non-blocking lock first so that we can inform the user about an already running SwiftPM. do { try workspaceLock.lock(type: .exclusive, blocking: false) + let pid = ProcessInfo.processInfo.processIdentifier + try? String(pid).write(toFile: lockFile, atomically: true, encoding: .utf8) } catch ProcessLockError.unableToAquireLock(let errno) { if errno == EWOULDBLOCK { + let lockingPID = try? String(contentsOfFile: lockFile, encoding: .utf8) + let pidInfo = lockingPID.map { "(PID: \($0)) " } ?? "" + if self.options.locations.ignoreLock { self.outputStream .write( - "Another instance of SwiftPM is already running using '\(self.scratchDirectory)', but this will be ignored since `--ignore-lock` has been passed" + "Another instance of SwiftPM \(pidInfo)is already running using '\(self.scratchDirectory)', but this will be ignored since `--ignore-lock` has been passed" .utf8 ) self.outputStream.flush() } else { self.outputStream .write( - "Another instance of SwiftPM is already running using '\(self.scratchDirectory)', waiting until that process has finished execution..." + "Another instance of SwiftPM \(pidInfo)is already running using '\(self.scratchDirectory)', waiting until that process has finished execution..." .utf8 ) self.outputStream.flush() // Only if we fail because there's an existing lock we need to acquire again as blocking. try workspaceLock.lock(type: .exclusive, blocking: true) + + let pid = ProcessInfo.processInfo.processIdentifier + try? String(pid).write(toFile: lockFile, atomically: true, encoding: .utf8) } } } From 97fad742e4741354196d17cd42b1bb79d554ef1f Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 7 May 2025 08:33:48 -0700 Subject: [PATCH 82/99] [Commands] `swift package migrate` command (#8613) ### Motivation: `swift package migrate` could be used to migrate whole package or its individual targets to use the given feature(s) that support migration mode. This is paired with Swift compiler changes to add `:migration` mode to upcoming/experimental features. ### Modifications: - `UserToolchain` gained an ability to list features supported by the Swift compiler. - Implementation of the `add-setting` package command has been slightly refactored to allow use of manifest modification logic without having to call the command itself. - `ModuleBuildDescription` protocol can now list diagnostic files - Added SwiftMigratedCommand that accepts targets (optional) and features, performs a build and uses the newly added `SwiftFixIt` API to apply fix-its and `AddSwiftSetting` API to enable features. ### Result: SwiftPM gained a new command `swift package migrate --targets --to-feature [--to-feature <...>]` --- .../ExistentialAnyMigration/Package.swift | 10 + .../Sources/Fixed/Test.swift | 12 + .../Sources/Test.swift | 12 + Package.swift | 3 +- .../ModuleBuildDescription.swift | 8 + Sources/CMakeLists.txt | 1 + Sources/Commands/CMakeLists.txt | 2 + .../Commands/PackageCommands/AddSetting.swift | 42 +++- .../Commands/PackageCommands/Migrate.swift | 208 ++++++++++++++++++ .../PackageCommands/SwiftPackageCommand.swift | 1 + Sources/PackageModel/CMakeLists.txt | 1 + .../Toolchain+SupportedFeatures.swift | 117 ++++++++++ .../BuildSystem/BuildSystem.swift | 4 + Sources/SwiftFixIt/CMakeLists.txt | 12 +- .../{SwiftFixit.swift => SwiftFixIt.swift} | 4 +- Tests/CommandsTests/PackageCommandTests.swift | 46 ++++ 16 files changed, 468 insertions(+), 15 deletions(-) create mode 100644 Fixtures/SwiftMigrate/ExistentialAnyMigration/Package.swift create mode 100644 Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift create mode 100644 Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift create mode 100644 Sources/Commands/PackageCommands/Migrate.swift create mode 100644 Sources/PackageModel/Toolchain+SupportedFeatures.swift rename Sources/SwiftFixIt/{SwiftFixit.swift => SwiftFixIt.swift} (99%) diff --git a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Package.swift b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Package.swift new file mode 100644 index 00000000000..a85e03ae79d --- /dev/null +++ b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Package.swift @@ -0,0 +1,10 @@ +// swift-tools-version:5.8 + +import PackageDescription + +let package = Package( + name: "ExistentialAnyMigration", + targets: [ + .target(name: "Diagnostics", path: "Sources", exclude: ["Fixed"]), + ] +) diff --git a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift new file mode 100644 index 00000000000..d6ef59be44c --- /dev/null +++ b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift @@ -0,0 +1,12 @@ +protocol P { +} + +func test1(_: any P) { +} + +func test2(_: (any P).Protocol) { +} + +func test3() { + let _: [(any P)?] = [] +} diff --git a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift new file mode 100644 index 00000000000..2183e565954 --- /dev/null +++ b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift @@ -0,0 +1,12 @@ +protocol P { +} + +func test1(_: P) { +} + +func test2(_: P.Protocol) { +} + +func test3() { + let _: [P?] = [] +} diff --git a/Package.swift b/Package.swift index e989267b4dd..78b1dde6e8e 100644 --- a/Package.swift +++ b/Package.swift @@ -138,7 +138,7 @@ let package = Package( type: .dynamic, targets: ["AppleProductTypes"] ), - + .library( name: "PackagePlugin", type: .dynamic, @@ -588,6 +588,7 @@ let package = Package( "Workspace", "XCBuildSupport", "SwiftBuildSupport", + "SwiftFixIt", ] + swiftSyntaxDependencies(["SwiftIDEUtils"]), exclude: ["CMakeLists.txt", "README.md"], swiftSettings: swift6CompatibleExperimentalFeatures + [ diff --git a/Sources/Build/BuildDescription/ModuleBuildDescription.swift b/Sources/Build/BuildDescription/ModuleBuildDescription.swift index 95f657f9b46..8c3713567f1 100644 --- a/Sources/Build/BuildDescription/ModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ModuleBuildDescription.swift @@ -140,6 +140,14 @@ public enum ModuleBuildDescription: SPMBuildCore.ModuleBuildDescription { } } + public var diagnosticFiles: [AbsolutePath] { + switch self { + case .swift(let buildDescription): + buildDescription.diagnosticFiles + case .clang(_): + [] + } + } /// Determines the arguments needed to run `swift-symbolgraph-extract` for /// this module. public func symbolGraphExtractArguments() throws -> [String] { diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index e9cafd35585..ec8a339aa33 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(SourceKitLSPAPI) add_subdirectory(SPMBuildCore) add_subdirectory(SPMLLBuild) add_subdirectory(SPMSQLite3) +add_subdirectory(SwiftFixIt) add_subdirectory(swift-bootstrap) add_subdirectory(swift-build) add_subdirectory(swift-experimental-sdk) diff --git a/Sources/Commands/CMakeLists.txt b/Sources/Commands/CMakeLists.txt index 9f644e3d326..2bcf5b44d0f 100644 --- a/Sources/Commands/CMakeLists.txt +++ b/Sources/Commands/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(Commands PackageCommands/Init.swift PackageCommands/Install.swift PackageCommands/Learn.swift + PackageCommands/Migrate.swift PackageCommands/PluginCommand.swift PackageCommands/ResetCommands.swift PackageCommands/Resolve.swift @@ -64,6 +65,7 @@ target_link_libraries(Commands PUBLIC PackageGraph PackageModelSyntax SourceControl + SwiftFixIt TSCBasic TSCUtility Workspace diff --git a/Sources/Commands/PackageCommands/AddSetting.swift b/Sources/Commands/PackageCommands/AddSetting.swift index ceac9ea660d..1a3412d596c 100644 --- a/Sources/Commands/PackageCommands/AddSetting.swift +++ b/Sources/Commands/PackageCommands/AddSetting.swift @@ -67,23 +67,47 @@ extension SwiftPackageCommand { } func run(_ swiftCommandState: SwiftCommandState) throws { + if !self._swiftSettings.isEmpty { + try Self.editSwiftSettings( + of: self.target, + using: swiftCommandState, + self.swiftSettings, + verbose: !self.globalOptions.logging.quiet + ) + } + } + + package static func editSwiftSettings( + of target: String, + using swiftCommandState: SwiftCommandState, + _ settings: [(SwiftSetting, String)], + verbose: Bool = false + ) throws { let workspace = try swiftCommandState.getActiveWorkspace() guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else { throw StringError("unknown package") } - try self.applyEdits(packagePath: packagePath, workspace: workspace) + try self.applyEdits( + packagePath: packagePath, + workspace: workspace, + target: target, + swiftSettings: settings + ) } - private func applyEdits( + private static func applyEdits( packagePath: Basics.AbsolutePath, - workspace: Workspace + workspace: Workspace, + target: String, + swiftSettings: [(SwiftSetting, String)], + verbose: Bool = false ) throws { // Load the manifest file let fileSystem = workspace.fileSystem let manifestPath = packagePath.appending(component: Manifest.filename) - for (setting, value) in try self.swiftSettings { + for (setting, value) in swiftSettings { let manifestContents: ByteString do { manifestContents = try fileSystem.readFileContents(manifestPath) @@ -105,13 +129,13 @@ extension SwiftPackageCommand { switch setting { case .experimentalFeature: editResult = try AddSwiftSetting.experimentalFeature( - to: self.target, + to: target, name: value, manifest: manifestSyntax ) case .upcomingFeature: editResult = try AddSwiftSetting.upcomingFeature( - to: self.target, + to: target, name: value, manifest: manifestSyntax ) @@ -121,7 +145,7 @@ extension SwiftPackageCommand { } editResult = try AddSwiftSetting.languageMode( - to: self.target, + to: target, mode: mode, manifest: manifestSyntax ) @@ -131,7 +155,7 @@ extension SwiftPackageCommand { } editResult = try AddSwiftSetting.strictMemorySafety( - to: self.target, + to: target, manifest: manifestSyntax ) } @@ -140,7 +164,7 @@ extension SwiftPackageCommand { to: fileSystem, manifest: manifestSyntax, manifestPath: manifestPath, - verbose: !self.globalOptions.logging.quiet + verbose: verbose ) } } diff --git a/Sources/Commands/PackageCommands/Migrate.swift b/Sources/Commands/PackageCommands/Migrate.swift new file mode 100644 index 00000000000..9224663a35d --- /dev/null +++ b/Sources/Commands/PackageCommands/Migrate.swift @@ -0,0 +1,208 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ArgumentParser + +import Basics + +@_spi(SwiftPMInternal) +import CoreCommands + +import Foundation + +import PackageGraph +import PackageModel + +import SPMBuildCore +import SwiftFixIt + +import var TSCBasic.stdoutStream + +struct MigrateOptions: ParsableArguments { + @Option( + name: .customLong("targets"), + help: "The targets to migrate to specified set of features." + ) + var _targets: String? + + var targets: Set? { + self._targets.flatMap { Set($0.components(separatedBy: ",")) } + } + + @Option( + name: .customLong("to-feature"), + parsing: .unconditionalSingleValue, + help: "The Swift language upcoming/experimental feature to migrate to." + ) + var features: [String] +} + +extension SwiftPackageCommand { + struct Migrate: AsyncSwiftCommand { + package static let configuration = CommandConfiguration( + abstract: "Migrate a package or its individual targets to use the given set of features." + ) + + @OptionGroup() + public var globalOptions: GlobalOptions + + @OptionGroup() + var options: MigrateOptions + + public func run(_ swiftCommandState: SwiftCommandState) async throws { + let toolchain = try swiftCommandState.productsBuildParameters.toolchain + + let supportedFeatures = try Dictionary( + uniqueKeysWithValues: toolchain.swiftCompilerSupportedFeatures + .map { ($0.name, $0) } + ) + + // First, let's validate that all of the features are supported + // by the compiler and are migratable. + + var features: [SwiftCompilerFeature] = [] + for name in self.options.features { + guard let feature = supportedFeatures[name] else { + let migratableFeatures = supportedFeatures.map(\.value).filter(\.migratable).map(\.name) + throw ValidationError( + "Unsupported feature: \(name). Available features: \(migratableFeatures.joined(separator: ", "))" + ) + } + + guard feature.migratable else { + throw ValidationError("Feature '\(name)' is not migratable") + } + + features.append(feature) + } + + let buildSystem = try await createBuildSystem( + swiftCommandState, + features: features + ) + + // Next, let's build all of the individual targets or the + // whole project to get diagnostic files. + + print("> Starting the build.") + if let targets = self.options.targets { + for target in targets { + try await buildSystem.build(subset: .target(target)) + } + } else { + try await buildSystem.build(subset: .allIncludingTests) + } + + // Determine all of the targets we need up update. + let buildPlan = try buildSystem.buildPlan + + var modules: [any ModuleBuildDescription] = [] + if let targets = self.options.targets { + for buildDescription in buildPlan.buildModules where targets.contains(buildDescription.module.name) { + modules.append(buildDescription) + } + } else { + let graph = try await buildSystem.getPackageGraph() + for buildDescription in buildPlan.buildModules + where graph.isRootPackage(buildDescription.package) && buildDescription.module.type != .plugin + { + modules.append(buildDescription) + } + } + + // If the build suceeded, let's extract all of the diagnostic + // files from build plan and feed them to the fix-it tool. + + print("> Applying fix-its.") + for module in modules { + let fixit = try SwiftFixIt( + diagnosticFiles: module.diagnosticFiles, + fileSystem: swiftCommandState.fileSystem + ) + try fixit.applyFixIts() + } + + // Once the fix-its were applied, it's time to update the + // manifest with newly adopted feature settings. + + print("> Updating manifest.") + for module in modules.map(\.module) { + print("> Adding feature(s) to '\(module.name)'.") + for feature in features { + self.updateManifest( + for: module.name, + add: feature, + using: swiftCommandState + ) + } + } + } + + private func createBuildSystem( + _ swiftCommandState: SwiftCommandState, + features: [SwiftCompilerFeature] + ) async throws -> BuildSystem { + let toolsBuildParameters = try swiftCommandState.toolsBuildParameters + var destinationBuildParameters = try swiftCommandState.productsBuildParameters + + // Inject feature settings as flags. This is safe and not as invasive + // as trying to update manifest because in adoption mode the features + // can only produce warnings. + for feature in features { + destinationBuildParameters.flags.swiftCompilerFlags.append(contentsOf: [ + "-Xfrontend", + "-enable-\(feature.upcoming ? "upcoming" : "experimental")-feature", + "-Xfrontend", + "\(feature.name):migrate", + ]) + } + + return try await swiftCommandState.createBuildSystem( + traitConfiguration: .init(), + productsBuildParameters: destinationBuildParameters, + toolsBuildParameters: toolsBuildParameters, + // command result output goes on stdout + // ie "swift build" should output to stdout + outputStream: TSCBasic.stdoutStream + ) + } + + private func updateManifest( + for target: String, + add feature: SwiftCompilerFeature, + using swiftCommandState: SwiftCommandState + ) { + typealias SwiftSetting = SwiftPackageCommand.AddSetting.SwiftSetting + + let setting: (SwiftSetting, String) = switch feature { + case .upcoming(name: let name, migratable: _, enabledIn: _): + (.upcomingFeature, "\(name)") + case .experimental(name: let name, migratable: _): + (.experimentalFeature, "\(name)") + } + + do { + try SwiftPackageCommand.AddSetting.editSwiftSettings( + of: target, + using: swiftCommandState, + [setting] + ) + } catch { + print( + "! Couldn't update manifest due to - \(error); Please add '.enable\(feature.upcoming ? "Upcoming" : "Experimental")Feature(\"\(feature.name)\")' to target '\(target)' settings manually." + ) + } + } + + public init() {} + } +} diff --git a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift index 8d2c7706cf9..6d9b4a77df9 100644 --- a/Sources/Commands/PackageCommands/SwiftPackageCommand.swift +++ b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift @@ -45,6 +45,7 @@ public struct SwiftPackageCommand: AsyncParsableCommand { Describe.self, Init.self, Format.self, + Migrate.self, Install.self, Uninstall.self, diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index 8e42f906f12..3d5f2f509d3 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(PackageModel SwiftSDKs/SwiftSDKBundleStore.swift Toolchain.swift ToolchainConfiguration.swift + Toolchain+SupportedFeatures.swift Toolset.swift ToolsVersion.swift ToolsVersionSpecificationGeneration.swift diff --git a/Sources/PackageModel/Toolchain+SupportedFeatures.swift b/Sources/PackageModel/Toolchain+SupportedFeatures.swift new file mode 100644 index 00000000000..9cf428473f5 --- /dev/null +++ b/Sources/PackageModel/Toolchain+SupportedFeatures.swift @@ -0,0 +1,117 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics + +import enum TSCBasic.JSON +import TSCUtility + +public enum SwiftCompilerFeature { + case upcoming(name: String, migratable: Bool, enabledIn: SwiftLanguageVersion) + case experimental(name: String, migratable: Bool) + + public var upcoming: Bool { + switch self { + case .upcoming: true + case .experimental: false + } + } + + public var experimental: Bool { + switch self { + case .upcoming: false + case .experimental: true + } + } + + public var name: String { + switch self { + case .upcoming(name: let name, migratable: _, enabledIn: _): + name + case .experimental(name: let name, migratable: _): + name + } + } + + public var migratable: Bool { + switch self { + case .upcoming(name: _, migratable: let migratable, enabledIn: _): + migratable + case .experimental(name: _, migratable: let migratable): + migratable + } + } +} + +extension Toolchain { + public var supportesSupportedFeatures: Bool { + guard let features = try? swiftCompilerSupportedFeatures else { + return false + } + return !features.isEmpty + } + + public var swiftCompilerSupportedFeatures: [SwiftCompilerFeature] { + get throws { + let compilerOutput: String + do { + let result = try AsyncProcess.popen(args: swiftCompilerPath.pathString, "-print-supported-features") + compilerOutput = try result.utf8Output().spm_chomp() + } catch { + throw InternalError("Failed to get supported features info (\(error.interpolationDescription))") + } + + if compilerOutput.isEmpty { + return [] + } + + let parsedSupportedFeatures: JSON + do { + parsedSupportedFeatures = try JSON(string: compilerOutput) + } catch { + throw InternalError( + "Failed to parse supported features info (\(error.interpolationDescription)).\nRaw compiler output: \(compilerOutput)" + ) + } + + let features: JSON = try parsedSupportedFeatures.get("features") + + let upcoming: [SwiftCompilerFeature] = try features.getArray("upcoming").map { + let name: String = try $0.get("name") + let migratable: Bool? = $0.get("migratable") + let enabledIn: String = try $0.get("enabled_in") + + guard let mode = SwiftLanguageVersion(string: enabledIn) else { + throw InternalError("Unknown swift language mode: \(enabledIn)") + } + + return .upcoming( + name: name, + migratable: migratable ?? false, + enabledIn: mode + ) + } + + let experimental: [SwiftCompilerFeature] = try features.getArray("experimental").map { + let name: String = try $0.get("name") + let migratable: Bool? = $0.get("migratable") + + return .experimental( + name: name, + migratable: migratable ?? false + ) + } + + return upcoming + experimental + } + } +} diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index 658c70f7c92..992d3f9af29 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -92,6 +92,10 @@ public protocol ModuleBuildDescription { /// The build parameters. var buildParameters: BuildParameters { get } + /// The diagnostic file locations for all the source files + /// associated with this module. + var diagnosticFiles: [AbsolutePath] { get } + /// FIXME: This shouldn't be necessary and ideally /// there should be a way to ask build system to /// introduce these arguments while building for symbol diff --git a/Sources/SwiftFixIt/CMakeLists.txt b/Sources/SwiftFixIt/CMakeLists.txt index f0b192ca6f8..9f6fdba4c2b 100644 --- a/Sources/SwiftFixIt/CMakeLists.txt +++ b/Sources/SwiftFixIt/CMakeLists.txt @@ -6,7 +6,7 @@ # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for Swift project authors -add_library(SwiftFixIt STATIC +add_library(SwiftFixIt SwiftFixIt.swift) target_link_libraries(SwiftFixIt PUBLIC Basics @@ -17,8 +17,14 @@ target_link_libraries(SwiftFixIt PUBLIC SwiftSyntax::SwiftSyntax TSCBasic - TSCUtility -) + TSCUtility) +# NOTE(compnerd) workaround for CMake not setting up include flags yet set_target_properties(SwiftFixIt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +install(TARGETS SwiftFixIt + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) +set_property(GLOBAL APPEND PROPERTY SwiftPM_EXPORTS SwiftFixIt) diff --git a/Sources/SwiftFixIt/SwiftFixit.swift b/Sources/SwiftFixIt/SwiftFixIt.swift similarity index 99% rename from Sources/SwiftFixIt/SwiftFixit.swift rename to Sources/SwiftFixIt/SwiftFixIt.swift index 9ce2c16717c..a03a6c44190 100644 --- a/Sources/SwiftFixIt/SwiftFixit.swift +++ b/Sources/SwiftFixIt/SwiftFixIt.swift @@ -323,7 +323,7 @@ extension DiagnosticConverter { // emit notes with those fix-its. private static func fixIt( from diagnostic: borrowing some AnyDiagnostic, - in sourceFile: borrowing SourceFile + in sourceFile: /*borrowing*/ SourceFile ) throws -> SwiftDiagnostics.FixIt { let changes = try diagnostic.fixIts.map { fixIt in let startPosition = try sourceFile.position(of: fixIt.start) @@ -341,7 +341,7 @@ extension DiagnosticConverter { private static func highlights( from diagnostic: borrowing some AnyDiagnostic, - in sourceFile: borrowing SourceFile + in sourceFile: /*borrowing*/ SourceFile ) throws -> [Syntax] { try diagnostic.ranges.map { startLocation, endLocation in let startPosition = try sourceFile.position(of: startLocation) diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 74181b45067..21dc52bcf6d 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -2094,6 +2094,48 @@ class PackageCommandTestCase: CommandsBuildProviderTestCase { } } + func testMigrateCommand() async throws { + try XCTSkipIf( + !UserToolchain.default.supportesSupportedFeatures, + "skipping because test environment compiler doesn't support `-print-supported-features`" + ) + + try await fixture(name: "SwiftMigrate/ExistentialAnyMigration") { fixturePath in + let sourcePaths: [AbsolutePath] + let fixedSourcePaths: [AbsolutePath] + + do { + let sourcesPath = fixturePath.appending(components: "Sources") + let fixedSourcesPath = sourcesPath.appending("Fixed") + + sourcePaths = try localFileSystem.getDirectoryContents(sourcesPath).filter { filename in + filename.hasSuffix(".swift") + }.sorted().map { filename in + sourcesPath.appending(filename) + } + fixedSourcePaths = try localFileSystem.getDirectoryContents(fixedSourcesPath).filter { filename in + filename.hasSuffix(".swift") + }.sorted().map { filename in + fixedSourcesPath.appending(filename) + } + } + + _ = try await self.execute( + ["migrate", "--to-feature", "ExistentialAny"], + packagePath: fixturePath + ) + + XCTAssertEqual(sourcePaths.count, fixedSourcePaths.count) + + for (sourcePath, fixedSourcePath) in zip(sourcePaths, fixedSourcePaths) { + try XCTAssertEqual( + localFileSystem.readFileContents(sourcePath), + localFileSystem.readFileContents(fixedSourcePath) + ) + } + } + } + func testBuildToolPlugin() async throws { try await testBuildToolPlugin(staticStdlib: false) } @@ -4005,6 +4047,10 @@ class PackageCommandSwiftBuildTests: PackageCommandTestCase { throw XCTSkip("SWBINTTODO: Test fails as plugins are not currenty supported") } + override func testMigrateCommand() async throws { + throw XCTSkip("SWBINTTODO: Build plan is not currently supported") + } + #if !os(macOS) override func testCommandPluginTestingCallbacks() async throws { try XCTSkipIfWorkingDirectoryUnsupported() From d368cb00ea42cdc9f5fb2c33a9d1374614977d85 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 7 May 2025 14:39:27 -0700 Subject: [PATCH 83/99] =?UTF-8?q?[Commands]=20Adjust=20`swift=20package=20?= =?UTF-8?q?migrate`=20to=20only=20apply=20fix-its=20to=20th=E2=80=A6=20(#8?= =?UTF-8?q?629)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …e selected features ### Motivation: Use new ability of `SwiftFixIt` to filter by category, to make sure that the command only applies fix-its that are related to the selected features and nothing else. ### Modifications: - `swift package migrate` - Add an argument to `SwiftFixIt` API that lists all of the features selected by the user. ### Result: Even if there are other warnings with a single fix-it the command is not going to apply them i.e. for `var` -> `let` or `` to `_`. --- .../ExistentialAnyMigration/Sources/Fixed/Test.swift | 4 ++++ .../SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift | 4 ++++ Sources/Commands/PackageCommands/Migrate.swift | 1 + 3 files changed, 9 insertions(+) diff --git a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift index d6ef59be44c..9ebe6520552 100644 --- a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift +++ b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Fixed/Test.swift @@ -10,3 +10,7 @@ func test2(_: (any P).Protocol) { func test3() { let _: [(any P)?] = [] } + +func test4() { + var x = 42 +} diff --git a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift index 2183e565954..5081ad285fa 100644 --- a/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift +++ b/Fixtures/SwiftMigrate/ExistentialAnyMigration/Sources/Test.swift @@ -10,3 +10,7 @@ func test2(_: P.Protocol) { func test3() { let _: [P?] = [] } + +func test4() { + var x = 42 +} diff --git a/Sources/Commands/PackageCommands/Migrate.swift b/Sources/Commands/PackageCommands/Migrate.swift index 9224663a35d..a98152fc45a 100644 --- a/Sources/Commands/PackageCommands/Migrate.swift +++ b/Sources/Commands/PackageCommands/Migrate.swift @@ -126,6 +126,7 @@ extension SwiftPackageCommand { for module in modules { let fixit = try SwiftFixIt( diagnosticFiles: module.diagnosticFiles, + categories: Set(features.map(\.name)), fileSystem: swiftCommandState.fileSystem ) try fixit.applyFixIts() From 26d8417e480f9173c16dcad712f456130a68d0d0 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Thu, 8 May 2025 10:47:57 -0400 Subject: [PATCH 84/99] Tests: Convert Environment/Graph and other suites to ST (#8624) Convert `BasicsTests/Environemnt/*.swift`, `BasicsTests/Graph/*.swift` and a couple other in BasicsTests to Swift Testing --- .../_InternalTestSupport/Observability.swift | 34 ++- .../Environment/EnvironmentKeyTests.swift | 67 +++--- .../Environment/EnvironmentTests.swift | 147 +++++++------ .../Graph/AdjacencyMatrixTests.swift | 22 +- .../Graph/DirectedGraphTests.swift | 21 +- .../Graph/UndirectedGraphTests.swift | 29 +-- .../ObservabilitySystemTests.swift | 208 ++++++++++-------- .../BasicsTests/ProgressAnimationTests.swift | 15 +- Tests/BasicsTests/StringExtensionsTests.swift | 20 +- 9 files changed, 329 insertions(+), 234 deletions(-) diff --git a/Sources/_InternalTestSupport/Observability.swift b/Sources/_InternalTestSupport/Observability.swift index 74a9594f4bd..06008d655a4 100644 --- a/Sources/_InternalTestSupport/Observability.swift +++ b/Sources/_InternalTestSupport/Observability.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -18,6 +18,7 @@ import func XCTest.XCTFail import struct TSCBasic.StringError import TSCTestSupport +import Testing extension ObservabilitySystem { public static func makeForTesting(verbose: Bool = true) -> TestingObservability { @@ -139,6 +140,37 @@ public func testDiagnostics( } } +public func expectDiagnostics( + _ diagnostics: [Basics.Diagnostic], + problemsOnly: Bool = true, + sourceLocation: SourceLocation = #_sourceLocation, + handler: (DiagnosticsTestResult) throws -> Void +) throws { + try expectDiagnostics( + diagnostics, + minSeverity: problemsOnly ? .warning : .debug, + sourceLocation: sourceLocation, + handler: handler + ) +} + + +public func expectDiagnostics( + _ diagnostics: [Basics.Diagnostic], + minSeverity: Basics.Diagnostic.Severity, + sourceLocation: SourceLocation = #_sourceLocation, + handler: (DiagnosticsTestResult) throws -> Void +) throws { + let diagnostics = diagnostics.filter { $0.severity >= minSeverity } + let testResult = DiagnosticsTestResult(diagnostics) + + try handler(testResult) + + if !testResult.uncheckedDiagnostics.isEmpty { + Issue.record("unchecked diagnostics \(testResult.uncheckedDiagnostics)", sourceLocation: sourceLocation) + } +} + public func testPartialDiagnostics( _ diagnostics: [Basics.Diagnostic], minSeverity: Basics.Diagnostic.Severity, diff --git a/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift b/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift index 60f6b0cb0ee..7c616de30bb 100644 --- a/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift +++ b/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift @@ -2,91 +2,100 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation @testable import Basics -import XCTest +import Testing -final class EnvironmentKeyTests: XCTestCase { - func test_comparable() { +struct EnvironmentKeyTests { + @Test + func comparable() { let key0 = EnvironmentKey("Test") let key1 = EnvironmentKey("Test1") - XCTAssertLessThan(key0, key1) + #expect(key0 < key1) let key2 = EnvironmentKey("test") - XCTAssertLessThan(key0, key2) + #expect(key0 < key2) } - func test_customStringConvertible() { + @Test + func customStringConvertible() { let key = EnvironmentKey("Test") - XCTAssertEqual(key.description, "Test") + #expect(key.description == "Test") } - func test_encodable() throws { + @Test + func encodable() throws { let key = EnvironmentKey("Test") let data = try JSONEncoder().encode(key) let string = String(data: data, encoding: .utf8) - XCTAssertEqual(string, #""Test""#) + #expect(string == #""Test""#) } - func test_equatable() { + @Test + func equatable() { let key0 = EnvironmentKey("Test") let key1 = EnvironmentKey("Test") - XCTAssertEqual(key0, key1) + #expect(key0 == key1) let key2 = EnvironmentKey("Test2") - XCTAssertNotEqual(key0, key2) + #expect(key0 != key2) #if os(Windows) // Test case insensitivity on windows let key3 = EnvironmentKey("teSt") - XCTAssertEqual(key0, key3) + #expect(key0 == key3) #endif } - func test_expressibleByStringLiteral() { + @Test + func expressibleByStringLiteral() { let key0 = EnvironmentKey("Test") - XCTAssertEqual(key0, "Test") + #expect(key0 == "Test") } - func test_decodable() throws { + @Test + func decodable() throws { let jsonString = #""Test""# let data = jsonString.data(using: .utf8)! let key = try JSONDecoder().decode(EnvironmentKey.self, from: data) - XCTAssertEqual(key.rawValue, "Test") + #expect(key.rawValue == "Test") } - func test_hashable() { + @Test + func hashable() { var set = Set() let key0 = EnvironmentKey("Test") - XCTAssertTrue(set.insert(key0).inserted) + #expect(set.insert(key0).inserted) let key1 = EnvironmentKey("Test") - XCTAssertTrue(set.contains(key1)) - XCTAssertFalse(set.insert(key1).inserted) + #expect(set.contains(key1)) + #expect(!set.insert(key1).inserted) let key2 = EnvironmentKey("Test2") - XCTAssertFalse(set.contains(key2)) - XCTAssertTrue(set.insert(key2).inserted) + #expect(!set.contains(key2)) + #expect(set.insert(key2).inserted) #if os(Windows) // Test case insensitivity on windows let key3 = EnvironmentKey("teSt") - XCTAssertTrue(set.contains(key3)) - XCTAssertFalse(set.insert(key3).inserted) + #expect(set.contains(key3)) + #expect(!set.insert(key3).inserted) #endif - XCTAssertEqual(set, ["Test", "Test2"]) + #expect(set == ["Test", "Test2"]) } - func test_rawRepresentable() { + @Test + func rawRepresentable() { let key = EnvironmentKey(rawValue: "Test") - XCTAssertEqual(key?.rawValue, "Test") + #expect(key?.rawValue == "Test") } } diff --git a/Tests/BasicsTests/Environment/EnvironmentTests.swift b/Tests/BasicsTests/Environment/EnvironmentTests.swift index 5b0388470c6..daabc754ab2 100644 --- a/Tests/BasicsTests/Environment/EnvironmentTests.swift +++ b/Tests/BasicsTests/Environment/EnvironmentTests.swift @@ -2,209 +2,230 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation @_spi(SwiftPMInternal) @testable import Basics -import XCTest +import Testing -final class EnvironmentTests: XCTestCase { - func test_init() { +struct EnvironmentTests { + @Test + func initialize() { let environment = Environment() - XCTAssertTrue(environment.isEmpty) + #expect(environment.isEmpty) } - func test_subscript() { + @Test + func setting_and_accessing_via_subscript() { var environment = Environment() let key = EnvironmentKey("TestKey") environment[key] = "TestValue" - XCTAssertEqual(environment[key], "TestValue") + #expect(environment[key] == "TestValue") } - func test_initDictionaryFromSelf() { + @Test + func initDictionaryFromSelf() { let dictionary = [ "TestKey": "TestValue", "testKey": "TestValue2", ] let environment = Environment(dictionary) + let expectedValue: String + let expectedCount: Int + #if os(Windows) - XCTAssertEqual(environment["TestKey"], "TestValue2") - XCTAssertEqual(environment.count, 1) + expectedValue = "TestValue2" // uppercase sorts before lowercase, so the second value overwrites the first + expectedCount = 1 #else - XCTAssertEqual(environment["TestKey"], "TestValue") - XCTAssertEqual(environment.count, 2) + expectedValue = "TestValue" + expectedCount = 2 #endif + #expect(environment["TestKey"] == expectedValue) + #expect(environment.count == expectedCount) } - func test_initSelfFromDictionary() { + @Test + func initSelfFromDictionary() { let dictionary = ["TestKey": "TestValue"] let environment = Environment(dictionary) - XCTAssertEqual(environment["TestKey"], "TestValue") - XCTAssertEqual(environment.count, 1) + #expect(environment["TestKey"] == "TestValue") + #expect(environment.count == 1) } func path(_ components: String...) -> String { components.joined(separator: Environment.pathEntryDelimiter) } - func test_prependPath() { + @Test + func prependPath() { var environment = Environment() let key = EnvironmentKey(UUID().uuidString) - XCTAssertNil(environment[key]) + #expect(environment[key] == nil) environment.prependPath(key: key, value: "/bin") - XCTAssertEqual(environment[key], path("/bin")) + #expect(environment[key] == path("/bin")) environment.prependPath(key: key, value: "/usr/bin") - XCTAssertEqual(environment[key], path("/usr/bin", "/bin")) + #expect(environment[key] == path("/usr/bin", "/bin")) environment.prependPath(key: key, value: "/usr/local/bin") - XCTAssertEqual(environment[key], path("/usr/local/bin", "/usr/bin", "/bin")) + #expect(environment[key] == path("/usr/local/bin", "/usr/bin", "/bin")) environment.prependPath(key: key, value: "") - XCTAssertEqual(environment[key], path("/usr/local/bin", "/usr/bin", "/bin")) + #expect(environment[key] == path("/usr/local/bin", "/usr/bin", "/bin")) } - func test_appendPath() { + @Test + func appendPath() { var environment = Environment() let key = EnvironmentKey(UUID().uuidString) - XCTAssertNil(environment[key]) + #expect(environment[key] == nil) environment.appendPath(key: key, value: "/bin") - XCTAssertEqual(environment[key], path("/bin")) + #expect(environment[key] == path("/bin")) environment.appendPath(key: key, value: "/usr/bin") - XCTAssertEqual(environment[key], path("/bin", "/usr/bin")) + #expect(environment[key] == path("/bin", "/usr/bin")) environment.appendPath(key: key, value: "/usr/local/bin") - XCTAssertEqual(environment[key], path("/bin", "/usr/bin", "/usr/local/bin")) + #expect(environment[key] == path("/bin", "/usr/bin", "/usr/local/bin")) environment.appendPath(key: key, value: "") - XCTAssertEqual(environment[key], path("/bin", "/usr/bin", "/usr/local/bin")) + #expect(environment[key] == path("/bin", "/usr/bin", "/usr/local/bin")) } - func test_pathEntryDelimiter() { + @Test + func pathEntryDelimiter() { + let expectedPathDelimiter: String #if os(Windows) - XCTAssertEqual(Environment.pathEntryDelimiter, ";") + expectedPathDelimiter = ";" #else - XCTAssertEqual(Environment.pathEntryDelimiter, ":") + expectedPathDelimiter = ":" #endif + #expect(Environment.pathEntryDelimiter == expectedPathDelimiter) } /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. - func test_current() throws { + @Test + func current() throws { #if os(Windows) let pathEnvVarName = "Path" #else let pathEnvVarName = "PATH" #endif - - XCTAssertEqual( - Environment.current["PATH"], - ProcessInfo.processInfo.environment[pathEnvVarName] - ) + #expect(Environment.current["PATH"] == ProcessInfo.processInfo.environment[pathEnvVarName]) } /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. - func test_makeCustom() async throws { + @Test + func makeCustom() async throws { let key = EnvironmentKey(UUID().uuidString) let value = "TestValue" var customEnvironment = Environment() customEnvironment[key] = value - XCTAssertNil(Environment.current[key]) + #expect(Environment.current[key] == nil) try Environment.makeCustom(customEnvironment) { - XCTAssertEqual(Environment.current[key], value) + #expect(Environment.current[key] == value) } - XCTAssertNil(Environment.current[key]) + #expect(Environment.current[key] == nil) } /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. - func testProcess() throws { + @Test + func process() throws { let key = EnvironmentKey(UUID().uuidString) let value = "TestValue" var environment = Environment.current - XCTAssertNil(environment[key]) + #expect(environment[key] == nil) try Environment.set(key: key, value: value) environment = Environment.current // reload - XCTAssertEqual(environment[key], value) + #expect(environment[key] == value) try Environment.set(key: key, value: nil) - XCTAssertEqual(environment[key], value) // this is a copy! + #expect(environment[key] == value) // this is a copy! environment = Environment.current // reload - XCTAssertNil(environment[key]) + #expect(environment[key] == nil) } - func test_cachable() { + @Test + func cachable() { let term = EnvironmentKey("TERM") var environment = Environment() environment[.path] = "/usr/bin" environment[term] = "xterm-256color" let cachableEnvironment = environment.cachable - XCTAssertNotNil(cachableEnvironment[.path]) - XCTAssertNil(cachableEnvironment[term]) + #expect(cachableEnvironment[.path] != nil) + #expect(cachableEnvironment[term] == nil) } - func test_collection() { + @Test + func collection() { let environment: Environment = ["TestKey": "TestValue"] - XCTAssertEqual(environment.count, 1) - XCTAssertEqual(environment.first?.key, EnvironmentKey("TestKey")) - XCTAssertEqual(environment.first?.value, "TestValue") + #expect(environment.count == 1) + #expect(environment.first?.key == EnvironmentKey("TestKey")) + #expect(environment.first?.value == "TestValue") } - func test_description() { + @Test + func description() { var environment = Environment() environment[EnvironmentKey("TestKey")] = "TestValue" - XCTAssertEqual(environment.description, #"["TestKey=TestValue"]"#) + #expect(environment.description == #"["TestKey=TestValue"]"#) } - func test_encodable() throws { + @Test + func encodable() throws { var environment = Environment() environment["TestKey"] = "TestValue" let data = try JSONEncoder().encode(environment) let jsonString = String(data: data, encoding: .utf8) - XCTAssertEqual(jsonString, #"{"TestKey":"TestValue"}"#) + #expect(jsonString == #"{"TestKey":"TestValue"}"#) } - func test_equatable() { + @Test + func equatable() { let environment0: Environment = ["TestKey": "TestValue"] let environment1: Environment = ["TestKey": "TestValue"] - XCTAssertEqual(environment0, environment1) + #expect(environment0 == environment1) #if os(Windows) // Test case insensitivity on windows let environment2: Environment = ["testKey": "TestValue"] - XCTAssertEqual(environment0, environment2) + #expect(environment0 == environment2) #endif } - func test_expressibleByDictionaryLiteral() { + @Test + func expressibleByDictionaryLiteral() { let environment: Environment = ["TestKey": "TestValue"] - XCTAssertEqual(environment["TestKey"], "TestValue") + #expect(environment["TestKey"] == "TestValue") } - func test_decodable() throws { + @Test + func decodable() throws { let jsonString = #"{"TestKey":"TestValue"}"# let data = jsonString.data(using: .utf8)! let environment = try JSONDecoder().decode(Environment.self, from: data) - XCTAssertEqual(environment[EnvironmentKey("TestKey")], "TestValue") + #expect(environment[EnvironmentKey("TestKey")] == "TestValue") } } diff --git a/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift b/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift index c19178ef410..25a440120dc 100644 --- a/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift +++ b/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,28 +11,30 @@ //===----------------------------------------------------------------------===// @testable import Basics -import XCTest +import Testing -final class AdjacencyMatrixTests: XCTestCase { - func testEmpty() { +struct AdjacencyMatrixTests { + @Test + func empty() { var matrix = AdjacencyMatrix(rows: 0, columns: 0) - XCTAssertEqual(matrix.bitCount, 0) + #expect(matrix.bitCount == 0) matrix = AdjacencyMatrix(rows: 0, columns: 42) - XCTAssertEqual(matrix.bitCount, 0) + #expect(matrix.bitCount == 0) matrix = AdjacencyMatrix(rows: 42, columns: 0) - XCTAssertEqual(matrix.bitCount, 0) + #expect(matrix.bitCount == 0) } - func testBits() { + @Test + func bits() { for count in 1..<10 { var matrix = AdjacencyMatrix(rows: count, columns: count) for row in 0.. Date: Thu, 8 May 2025 14:23:40 -0400 Subject: [PATCH 85/99] Use async methods when loading manifests (#8551) ### Motivation: This follows on from my work to convert the registry code to `async` methods. ### Modifications: Switch to using async methods when loading manifests, converting synchronous code to the simpler async equivalents and converting sync API calls to their synchronous versions. ### Result: This resolves several warnings and FIXMEs, makes the code easier to read and generally implements concurrency in a more idiomatic way. --- .../Concurrency/ConcurrencyHelpers.swift | 16 + .../PackageCommands/ResetCommands.swift | 6 +- Sources/PackageLoading/ManifestLoader.swift | 991 +++++++----------- Sources/PackageMetadata/PackageMetadata.swift | 39 +- Sources/PackageRegistry/RegistryClient.swift | 14 - .../FileSystemPackageContainer.swift | 33 +- .../RegistryPackageContainer.swift | 3 +- .../SourceControlPackageContainer.swift | 33 +- .../Workspace/Workspace+Dependencies.swift | 1 - Sources/Workspace/Workspace+Editing.swift | 20 +- Sources/Workspace/Workspace+Manifests.swift | 133 ++- Sources/Workspace/Workspace+Registry.swift | 152 ++- Sources/Workspace/Workspace.swift | 155 +-- .../MockManifestLoader.swift | 28 +- Sources/swift-bootstrap/main.swift | 3 +- .../ManifestLoaderCacheTests.swift | 4 +- .../PD_4_2_LoadingTests.swift | 9 +- .../ManifestSourceGenerationTests.swift | 6 +- .../RegistryPackageContainerTests.swift | 46 +- Tests/WorkspaceTests/WorkspaceTests.swift | 32 +- 20 files changed, 736 insertions(+), 988 deletions(-) diff --git a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift index a4f46baea10..52f25250d47 100644 --- a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift +++ b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift @@ -59,4 +59,20 @@ extension DispatchQueue { } } } + + package func asyncResult(_ callback: @escaping @Sendable (Result) -> Void, _ closure: @escaping @Sendable () async throws -> T) { + let completion: @Sendable (Result) -> Void = { + result in self.async { + callback(result) + } + } + + Task { + do { + completion(.success(try await closure())) + } catch { + completion(.failure(error)) + } + } + } } diff --git a/Sources/Commands/PackageCommands/ResetCommands.swift b/Sources/Commands/PackageCommands/ResetCommands.swift index e8b216d1eb2..b9e8edbf744 100644 --- a/Sources/Commands/PackageCommands/ResetCommands.swift +++ b/Sources/Commands/PackageCommands/ResetCommands.swift @@ -27,15 +27,15 @@ extension SwiftPackageCommand { } } - struct PurgeCache: SwiftCommand { + struct PurgeCache: AsyncSwiftCommand { static let configuration = CommandConfiguration( abstract: "Purge the global repository cache.") @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftCommandState: SwiftCommandState) throws { - try swiftCommandState.getActiveWorkspace().purgeCache(observabilityScope: swiftCommandState.observabilityScope) + func run(_ swiftCommandState: SwiftCommandState) async throws { + try await swiftCommandState.getActiveWorkspace().purgeCache(observabilityScope: swiftCommandState.observabilityScope) } } diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index 3330f9b147e..76d576fb3d5 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -125,19 +125,17 @@ public protocol ManifestLoaderProtocol { dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) + delegateQueue: DispatchQueue + ) async throws -> Manifest /// Reset any internal cache held by the manifest loader. - func resetCache(observabilityScope: ObservabilityScope) + func resetCache(observabilityScope: ObservabilityScope) async /// Reset any internal cache held by the manifest loader and purge any entries in a shared cache - func purgeCache(observabilityScope: ObservabilityScope) + func purgeCache(observabilityScope: ObservabilityScope) async } -public protocol ManifestLoaderDelegate { +public protocol ManifestLoaderDelegate: Sendable { func willLoad( packageIdentity: PackageIdentity, packageLocation: String, @@ -200,72 +198,53 @@ extension ManifestLoaderProtocol { dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - do { - // find the manifest path and parse it's tools-version - let manifestPath = try ManifestLoader.findManifest(packagePath: packagePath, fileSystem: fileSystem, currentToolsVersion: currentToolsVersion) - let manifestToolsVersion = try ToolsVersionParser.parse(manifestPath: manifestPath, fileSystem: fileSystem) - // validate the manifest tools-version against the toolchain tools-version - try manifestToolsVersion.validateToolsVersion(currentToolsVersion, packageIdentity: packageIdentity, packageVersion: packageVersion?.version?.description ?? packageVersion?.revision) + delegateQueue: DispatchQueue + ) async throws -> Manifest { + // find the manifest path and parse it's tools-version + let manifestPath = try ManifestLoader.findManifest( + packagePath: packagePath, + fileSystem: fileSystem, + currentToolsVersion: currentToolsVersion + ) + let manifestToolsVersion = try ToolsVersionParser.parse(manifestPath: manifestPath, fileSystem: fileSystem) + // validate the manifest tools-version against the toolchain tools-version + try manifestToolsVersion.validateToolsVersion( + currentToolsVersion, + packageIdentity: packageIdentity, + packageVersion: packageVersion?.version?.description ?? packageVersion?.revision + ) - self.load( - manifestPath: manifestPath, - manifestToolsVersion: manifestToolsVersion, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: packageVersion, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue, - completion: completion - ) - } catch { - callbackQueue.async { - completion(.failure(error)) - } - } - } + return try await self.load( + manifestPath: manifestPath, + manifestToolsVersion: manifestToolsVersion, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: packageVersion, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + delegateQueue: delegateQueue + ) + } +} - public func load( - packagePath: AbsolutePath, - packageIdentity: PackageIdentity, - packageKind: PackageReference.Kind, - packageLocation: String, - packageVersion: (version: Version?, revision: String?)?, - currentToolsVersion: ToolsVersion, - identityResolver: IdentityResolver, - dependencyMapper: DependencyMapper, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue - ) async throws -> Manifest { - try await withCheckedThrowingContinuation { continuation in - self.load( - packagePath: packagePath, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: packageVersion, - currentToolsVersion: currentToolsVersion, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue, - completion: { - continuation.resume(with: $0) - } - ) - } +// MARK: - ManifestCacheActor + +actor ManifestCacheActor { + private var memoryCache: [ManifestLoader.CacheKey: ManifestJSONParser.Result] = [:] + + func get(key: ManifestLoader.CacheKey) -> ManifestJSONParser.Result? { + memoryCache[key] + } + + func set(key: ManifestLoader.CacheKey, value: ManifestJSONParser.Result) { + memoryCache[key] = value + } + + func clear() { + memoryCache.removeAll() } } @@ -294,12 +273,7 @@ public final class ManifestLoader: ManifestLoaderProtocol { private let databaseCacheDir: AbsolutePath? private let useInMemoryCache: Bool - private let memoryCache = ThreadSafeKeyValueStore() - - /// DispatchSemaphore to restrict concurrent manifest evaluations - private let concurrencySemaphore: DispatchSemaphore - /// OperationQueue to park pending lookups - private let evaluationQueue: OperationQueue + private let memoryCacheActor = ManifestCacheActor() public init( toolchain: UserToolchain, @@ -322,12 +296,6 @@ public final class ManifestLoader: ManifestLoaderProtocol { self.useInMemoryCache = useInMemoryCache self.databaseCacheDir = try? cacheDir.map(resolveSymlinks) - - // this queue and semaphore is used to limit the amount of concurrent manifest loading taking place - self.evaluationQueue = OperationQueue() - self.evaluationQueue.name = "org.swift.swiftpm.manifest-loader" - self.evaluationQueue.maxConcurrentOperationCount = Concurrency.maxOperations - self.concurrencySemaphore = DispatchSemaphore(value: Concurrency.maxOperations) self.pruneDependencies = pruneDependencies } @@ -342,50 +310,12 @@ public final class ManifestLoader: ManifestLoaderProtocol { dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue + delegateQueue: DispatchQueue ) async throws -> Manifest { - try await withCheckedThrowingContinuation { continuation in - self.load( - manifestPath: manifestPath, - manifestToolsVersion: manifestToolsVersion, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: packageVersion, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper, - fileSystem: fileSystem, - observabilityScope: observabilityScope, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue, - completion: { - continuation.resume(with: $0) - } - ) - } - } - - @available(*, noasync, message: "Use the async alternative") - public func load( - manifestPath: AbsolutePath, - manifestToolsVersion: ToolsVersion, - packageIdentity: PackageIdentity, - packageKind: PackageReference.Kind, - packageLocation: String, - packageVersion: (version: Version?, revision: String?)?, - identityResolver: IdentityResolver, - dependencyMapper: DependencyMapper, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { // Inform the delegate. let start = DispatchTime.now() - delegateQueue.async { - self.delegate?.willLoad( + delegateQueue.async { [delegate = self.delegate] in + delegate?.willLoad( packageIdentity: packageIdentity, packageLocation: packageLocation, manifestPath: manifestPath @@ -394,12 +324,10 @@ public final class ManifestLoader: ManifestLoaderProtocol { // Validate that the file exists. guard fileSystem.isFile(manifestPath) else { - return callbackQueue.async { - completion(.failure(PackageModel.Package.Error.noManifest(at: manifestPath, version: packageVersion?.version))) - } + throw PackageModel.Package.Error.noManifest(at: manifestPath, version: packageVersion?.version) } - self.loadAndCacheManifest( + let parsedManifest = try await self.loadAndCacheManifest( at: manifestPath, toolsVersion: manifestToolsVersion, packageIdentity: packageIdentity, @@ -411,73 +339,62 @@ public final class ManifestLoader: ManifestLoaderProtocol { fileSystem: fileSystem, observabilityScope: observabilityScope, delegate: delegate, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue - ) { parseResult in - do { - dispatchPrecondition(condition: .onQueue(callbackQueue)) - - let parsedManifest = try parseResult.get() - // Convert legacy system packages to the current target‐based model. - var products = parsedManifest.products - var targets = parsedManifest.targets - if products.isEmpty, targets.isEmpty, - fileSystem.isFile(manifestPath.parentDirectory.appending(component: moduleMapFilename)) { - try products.append(ProductDescription( - name: parsedManifest.name, - type: .library(.automatic), - targets: [parsedManifest.name]) - ) - targets.append(try TargetDescription( - name: parsedManifest.name, - path: "", - type: .system, - packageAccess: false, - pkgConfig: parsedManifest.pkgConfig, - providers: parsedManifest.providers - )) - } - - let manifest = Manifest( - displayName: parsedManifest.name, - packageIdentity: packageIdentity, - path: manifestPath, - packageKind: packageKind, - packageLocation: packageLocation, - defaultLocalization: parsedManifest.defaultLocalization, - platforms: parsedManifest.platforms, - version: packageVersion?.version, - revision: packageVersion?.revision, - toolsVersion: manifestToolsVersion, - pkgConfig: parsedManifest.pkgConfig, - providers: parsedManifest.providers, - cLanguageStandard: parsedManifest.cLanguageStandard, - cxxLanguageStandard: parsedManifest.cxxLanguageStandard, - swiftLanguageVersions: parsedManifest.swiftLanguageVersions, - dependencies: parsedManifest.dependencies, - products: products, - targets: targets, - traits: parsedManifest.traits, - pruneDependencies: self.pruneDependencies - ) + delegateQueue: delegateQueue + ) + // Convert legacy system packages to the current target‐based model. + var products = parsedManifest.products + var targets = parsedManifest.targets + if products.isEmpty, targets.isEmpty, + fileSystem.isFile(manifestPath.parentDirectory.appending(component: moduleMapFilename)) { + try products.append(ProductDescription( + name: parsedManifest.name, + type: .library(.automatic), + targets: [parsedManifest.name]) + ) + targets.append(try TargetDescription( + name: parsedManifest.name, + path: "", + type: .system, + packageAccess: false, + pkgConfig: parsedManifest.pkgConfig, + providers: parsedManifest.providers + )) + } - // Inform the delegate. - delegateQueue.async { - self.delegate?.didLoad( - packageIdentity: packageIdentity, - packageLocation: packageLocation, - manifestPath: manifestPath, - duration: start.distance(to: .now()) - ) - } + let manifest = Manifest( + displayName: parsedManifest.name, + packageIdentity: packageIdentity, + path: manifestPath, + packageKind: packageKind, + packageLocation: packageLocation, + defaultLocalization: parsedManifest.defaultLocalization, + platforms: parsedManifest.platforms, + version: packageVersion?.version, + revision: packageVersion?.revision, + toolsVersion: manifestToolsVersion, + pkgConfig: parsedManifest.pkgConfig, + providers: parsedManifest.providers, + cLanguageStandard: parsedManifest.cLanguageStandard, + cxxLanguageStandard: parsedManifest.cxxLanguageStandard, + swiftLanguageVersions: parsedManifest.swiftLanguageVersions, + dependencies: parsedManifest.dependencies, + products: products, + targets: targets, + traits: parsedManifest.traits, + pruneDependencies: self.pruneDependencies + ) - completion(.success(manifest)) - } catch { - callbackQueue.async { - completion(.failure(error)) - } - } + // Inform the delegate. + delegateQueue.async { [delegate = self.delegate] in + delegate?.didLoad( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: start.distance(to: .now()) + ) } + + return manifest } /// Load the JSON string for the given manifest. @@ -557,48 +474,24 @@ public final class ManifestLoader: ManifestLoaderProtocol { fileSystem: FileSystem, observabilityScope: ObservabilityScope, delegate: Delegate?, - delegateQueue: DispatchQueue?, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - // put callback on right queue - var completion = completion - do { - let previousCompletion = completion - completion = { result in callbackQueue.async { previousCompletion(result) } } - } - - let key : CacheKey - do { - key = try CacheKey( - packageIdentity: packageIdentity, - packageLocation: packageLocation, - manifestPath: path, - toolsVersion: toolsVersion, - env: Environment.current.cachable, - swiftpmVersion: SwiftVersion.current.displayString, - extraManifestFlags: self.extraManifestFlags, - fileSystem: fileSystem - ) - } catch { - return completion(.failure(error)) - } + delegateQueue: DispatchQueue? + ) async throws -> ManifestJSONParser.Result { + + let key = try CacheKey( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: path, + toolsVersion: toolsVersion, + env: Environment.current.cachable, + swiftpmVersion: SwiftVersion.current.displayString, + extraManifestFlags: self.extraManifestFlags, + fileSystem: fileSystem + ) // try from in-memory cache - if self.useInMemoryCache, let parsedManifest = self.memoryCache[key] { + if self.useInMemoryCache, let parsedManifest = await self.memoryCacheActor.get(key: key) { observabilityScope.emit(debug: "loading manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown") from memory cache") - return completion(.success(parsedManifest)) - } - - // make sure callback record results in-memory - do { - let previousCompletion = completion - completion = { result in - if self.useInMemoryCache, case .success(let parsedManifest) = result { - self.memoryCache[key] = parsedManifest - } - previousCompletion(result) - } + return parsedManifest } // initialize db cache @@ -614,23 +507,19 @@ public final class ManifestLoader: ManifestLoaderProtocol { configuration: configuration ) } - - // make sure callback closes db cache - do { - let previousCompletion = completion - completion = { result in - do { - try dbCache?.close() - } catch { - observabilityScope.emit( - warning: "failed closing manifest db cache", - underlyingError: error - ) - } - previousCompletion(result) + // Ensure dbCache is closed on exit + defer { + do { + try dbCache?.close() + } catch { + observabilityScope.emit( + warning: "failed closing manifest db cache", + underlyingError: error + ) } } + do { // try to get it from the cache if let evaluationResult = try dbCache?.get(key: key.sha256Checksum), let manifestJSON = evaluationResult.manifestJSON, !manifestJSON.isEmpty { @@ -648,9 +537,13 @@ public final class ManifestLoader: ManifestLoaderProtocol { emitCompilerOutput: false, observabilityScope: observabilityScope, delegate: delegate, - delegateQueue: delegateQueue + delegateQueue: delegateQueue // Pass the delegate queue ) - return completion(.success(parsedManifest)) + // Store in memory cache if enabled + if self.useInMemoryCache { + await self.memoryCacheActor.set(key: key, value: parsedManifest) + } + return parsedManifest } } catch { observabilityScope.emit( @@ -661,104 +554,79 @@ public final class ManifestLoader: ManifestLoaderProtocol { // shells out and compiles the manifest, finally output a JSON observabilityScope.emit(debug: "evaluating manifest for '\(packageIdentity)' v. \(packageVersion?.description ?? "unknown")") - do { - try self.evaluateManifest( - packageIdentity: key.packageIdentity, - packageLocation: packageLocation, - manifestPath: key.manifestPath, - manifestContents: key.manifestContents, - toolsVersion: key.toolsVersion, - observabilityScope: observabilityScope, - delegate: delegate, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue - ) { result in - dispatchPrecondition(condition: .onQueue(callbackQueue)) - do { - let evaluationResult = try result.get() - // only cache successfully parsed manifests - let parsedManifest = try self.parseManifest( - evaluationResult, - packageIdentity: packageIdentity, - packageKind: packageKind, - packagePath: path.parentDirectory, - packageLocation: packageLocation, - toolsVersion: toolsVersion, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper, - fileSystem: fileSystem, - emitCompilerOutput: true, - observabilityScope: observabilityScope, - delegate: delegate, - delegateQueue: delegateQueue - ) + let evaluationResult = try await self.evaluateManifest( + packageIdentity: key.packageIdentity, + packageLocation: packageLocation, + manifestPath: key.manifestPath, + manifestContents: key.manifestContents, + toolsVersion: key.toolsVersion, + observabilityScope: observabilityScope, + delegate: delegate, + delegateQueue: delegateQueue // Pass the delegate queue + ) - do { - self.memoryCache[key] = parsedManifest - try dbCache?.put(key: key.sha256Checksum, value: evaluationResult, observabilityScope: observabilityScope) - } catch { - observabilityScope.emit( - warning: "failed storing manifest for '\(key.packageIdentity)' in cache", - underlyingError: error - ) - } - - return completion(.success(parsedManifest)) - } catch { - return completion(.failure(error)) - } - } + // only cache successfully parsed manifests + let parsedManifest = try self.parseManifest( + evaluationResult, + packageIdentity: packageIdentity, + packageKind: packageKind, + packagePath: path.parentDirectory, + packageLocation: packageLocation, + toolsVersion: toolsVersion, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fileSystem, + emitCompilerOutput: true, + observabilityScope: observabilityScope, + delegate: delegate, + delegateQueue: delegateQueue + ) + + do { + // Store in memory cache + await self.memoryCacheActor.set(key: key, value: parsedManifest) + // Store in db cache + try dbCache?.put(key: key.sha256Checksum, value: evaluationResult, observabilityScope: observabilityScope) } catch { - return completion(.failure(error)) + observabilityScope.emit( + warning: "failed storing manifest for '\(key.packageIdentity)' in cache", + underlyingError: error + ) } + + return parsedManifest } private func validateImports( manifestPath: AbsolutePath, - toolsVersion: ToolsVersion, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void) { - // If there are no import restrictions, we do not need to validate. - guard let importRestrictions = self.importRestrictions, toolsVersion >= importRestrictions.startingToolsVersion else { - return callbackQueue.async { - completion(.success(())) - } - } - - // Allowed are the expected defaults, plus anything allowed by the configured restrictions. - let allowedImports = ["PackageDescription", "Swift", - "SwiftOnoneSupport", "_SwiftConcurrencyShims"] + importRestrictions.allowedImports + toolsVersion: ToolsVersion + ) async throws { + // If there are no import restrictions, we do not need to validate. + guard let importRestrictions = self.importRestrictions, toolsVersion >= importRestrictions.startingToolsVersion else { + return + } - // wrap the completion to free concurrency control semaphore - let completion: (Result) -> Void = { result in - self.concurrencySemaphore.signal() - callbackQueue.async { - completion(result) - } - } + // Allowed are the expected defaults, plus anything allowed by the configured restrictions. + let allowedImports = [ + "PackageDescription", + "Swift", + "SwiftOnoneSupport", + "_SwiftConcurrencyShims" + ] + importRestrictions.allowedImports + + let importScanner = SwiftcImportScanner( + swiftCompilerEnvironment: self.toolchain.swiftCompilerEnvironment, + swiftCompilerFlags: self.extraManifestFlags, + swiftCompilerPath: self.toolchain.swiftCompilerPathForManifests + ) + let result = try await importScanner.scanImports(manifestPath) - // we must not block the calling thread (for concurrency control) so nesting this in a queue - self.evaluationQueue.addOperation { - // park the evaluation thread based on the max concurrency allowed - self.concurrencySemaphore.wait() - - let importScanner = SwiftcImportScanner(swiftCompilerEnvironment: self.toolchain.swiftCompilerEnvironment, - swiftCompilerFlags: self.extraManifestFlags, - swiftCompilerPath: self.toolchain.swiftCompilerPathForManifests) - - Task { - let result = try await importScanner.scanImports(manifestPath) - let imports = result.filter { !allowedImports.contains($0) } - guard imports.isEmpty else { - callbackQueue.async { - completion(.failure(ManifestParseError.importsRestrictedModules(imports))) - } - return - } - } - } + let imports = result.filter { !allowedImports.contains($0) } + guard imports.isEmpty else { + throw ManifestParseError.importsRestrictedModules(imports) } + } /// Compiler the manifest at the given path and retrieve the JSON. fileprivate func evaluateManifest( @@ -769,10 +637,8 @@ public final class ManifestLoader: ManifestLoaderProtocol { toolsVersion: ToolsVersion, observabilityScope: ObservabilityScope, delegate: Delegate?, - delegateQueue: DispatchQueue?, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) throws { + delegateQueue: DispatchQueue? + ) async throws -> EvaluationResult { let manifestPreamble: ByteString if toolsVersion >= .v5_8 { manifestPreamble = ByteString() @@ -780,59 +646,38 @@ public final class ManifestLoader: ManifestLoaderProtocol { manifestPreamble = ByteString("\nimport Foundation") } - do { - try withTemporaryDirectory { tempDir, cleanupTempDir in - let manifestTempFilePath = tempDir.appending("manifest.swift") - // Since this isn't overwriting the original file, append Foundation library - // import to avoid having diagnostics being displayed on the incorrect line. - try localFileSystem.writeFileContents(manifestTempFilePath, bytes: ByteString(manifestContents + manifestPreamble.contents)) - - let vfsOverlayTempFilePath = tempDir.appending("vfs.yaml") - try VFSOverlay(roots: [ - VFSOverlay.File( - name: manifestPath._normalized.replacing(#"\"#, with: #"\\"#), - externalContents: manifestTempFilePath._nativePathString(escaped: true) - ) - ]).write(to: vfsOverlayTempFilePath, fileSystem: localFileSystem) + return try await Basics.withTemporaryDirectory(removeTreeOnDeinit: true) { tempDir in + let manifestTempFilePath = tempDir.appending("manifest.swift") + // Since this isn't overwriting the original file, append Foundation library + // import to avoid having diagnostics being displayed on the incorrect line. + try localFileSystem.writeFileContents(manifestTempFilePath, bytes: ByteString(manifestContents + manifestPreamble.contents)) + + let vfsOverlayTempFilePath = tempDir.appending("vfs.yaml") + try VFSOverlay(roots: [ + VFSOverlay.File( + name: manifestPath._normalized.replacing(#"\"#, with: #"\\"#), + externalContents: manifestTempFilePath._nativePathString(escaped: true) + ) + ]).write(to: vfsOverlayTempFilePath, fileSystem: localFileSystem) - validateImports( - manifestPath: manifestTempFilePath, - toolsVersion: toolsVersion, - callbackQueue: callbackQueue - ) { result in - dispatchPrecondition(condition: .onQueue(callbackQueue)) - - do { - try result.get() - - try self.evaluateManifest( - at: manifestPath, - vfsOverlayPath: vfsOverlayTempFilePath, - packageIdentity: packageIdentity, - packageLocation: packageLocation, - toolsVersion: toolsVersion, - observabilityScope: observabilityScope, - delegate: delegate, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue - ) { result in - dispatchPrecondition(condition: .onQueue(callbackQueue)) - cleanupTempDir(tempDir) - completion(result) - } - } catch { - cleanupTempDir(tempDir) - callbackQueue.async { - completion(.failure(error)) - } - } - } - } - } catch { - callbackQueue.async { - completion(.failure(error)) - } + try await validateImports( + manifestPath: manifestTempFilePath, + toolsVersion: toolsVersion + ) + let result = try await self.evaluateManifest( + at: manifestPath, + vfsOverlayPath: vfsOverlayTempFilePath, + packageIdentity: packageIdentity, + packageLocation: packageLocation, + toolsVersion: toolsVersion, + observabilityScope: observabilityScope, + delegate: delegate, + delegateQueue: delegateQueue + ) + + return result } + } /// Helper method for evaluating the manifest. @@ -844,16 +689,12 @@ public final class ManifestLoader: ManifestLoaderProtocol { toolsVersion: ToolsVersion, observabilityScope: ObservabilityScope, delegate: Delegate?, - delegateQueue: DispatchQueue?, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) throws { + delegateQueue: DispatchQueue? + ) async throws -> EvaluationResult { // The compiler has special meaning for files with extensions like .ll, .bc etc. // Assert that we only try to load files with extension .swift to avoid unexpected loading behavior. guard manifestPath.extension == "swift" else { - return callbackQueue.async { - completion(.failure(InternalError("Manifest files must contain .swift suffix in their name, given: \(manifestPath)."))) - } + throw InternalError("Manifest files must contain .swift suffix in their name, given: \(manifestPath).") } var evaluationResult = EvaluationResult() @@ -926,207 +767,185 @@ public final class ManifestLoader: ManifestLoaderProtocol { if self.serializedDiagnostics, let databaseCacheDir = self.databaseCacheDir { let diaDir = databaseCacheDir.appending("ManifestLoading") let diagnosticFile = diaDir.appending("\(packageIdentity).dia") - do { - try localFileSystem.createDirectory(diaDir, recursive: true) - cmd += ["-Xfrontend", "-serialize-diagnostics-path", "-Xfrontend", diagnosticFile.pathString] - evaluationResult.diagnosticFile = diagnosticFile - } catch { - return callbackQueue.async { - completion(.failure(error)) - } - } + try localFileSystem.createDirectory(diaDir, recursive: true) + cmd += ["-Xfrontend", "-serialize-diagnostics-path", "-Xfrontend", diagnosticFile.pathString] + evaluationResult.diagnosticFile = diagnosticFile } cmd += [manifestPath._normalized] cmd += self.extraManifestFlags - // wrap the completion to free concurrency control semaphore - let completion: (Result) -> Void = { result in - self.concurrencySemaphore.signal() - completion(result) - } - - // we must not block the calling thread (for concurrency control) so nesting this in a queue - self.evaluationQueue.addOperation { + // Compile the manifest in a temporary directory + return try await Basics.withTemporaryDirectory(removeTreeOnDeinit: true) { tmpDir in + // Set path to compiled manifest executable. + #if os(Windows) + let executableSuffix = ".exe" + #else + let executableSuffix = "" + #endif + let compiledManifestFile = tmpDir.appending("\(packageIdentity)-manifest\(executableSuffix)") + cmd += ["-o", compiledManifestFile.pathString] + + evaluationResult.compilerCommandLine = cmd + + delegateQueue?.async { [delegate = self.delegate] in + delegate?.willCompile( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath + ) + } + // Compile the manifest. + let compileStart = DispatchTime.now() + let compilerResult: AsyncProcessResult do { - // park the evaluation thread based on the max concurrency allowed - self.concurrencySemaphore.wait() - // run the evaluation - let compileStart = DispatchTime.now() - delegateQueue?.async { - delegate?.willCompile( + compilerResult = try await AsyncProcess.popen(arguments: cmd, environment: self.toolchain.swiftCompilerEnvironment) + evaluationResult.compilerOutput = try (compilerResult.utf8Output() + compilerResult.utf8stderrOutput()).spm_chuzzle() + } catch { + delegateQueue?.async { [delegate = self.delegate] in + delegate?.didCompile( packageIdentity: packageIdentity, packageLocation: packageLocation, - manifestPath: manifestPath + manifestPath: manifestPath, + duration: compileStart.distance(to: .now()) ) } - try withTemporaryDirectory { tmpDir, cleanupTmpDir in - // Set path to compiled manifest executable. - #if os(Windows) - let executableSuffix = ".exe" - #else - let executableSuffix = "" - #endif - let compiledManifestFile = tmpDir.appending("\(packageIdentity)-manifest\(executableSuffix)") - cmd += ["-o", compiledManifestFile.pathString] - - evaluationResult.compilerCommandLine = cmd - - // Compile the manifest. - AsyncProcess.popen( - arguments: cmd, - environment: self.toolchain.swiftCompilerEnvironment, - queue: callbackQueue - ) { result in - dispatchPrecondition(condition: .onQueue(callbackQueue)) - - var cleanupIfError = DelayableAction(target: tmpDir, action: cleanupTmpDir) - defer { cleanupIfError.perform() } - - let compilerResult: AsyncProcessResult - do { - compilerResult = try result.get() - evaluationResult.compilerOutput = try (compilerResult.utf8Output() + compilerResult.utf8stderrOutput()).spm_chuzzle() - } catch { - return completion(.failure(error)) - } - - // Return now if there was an error. - if compilerResult.exitStatus != .terminated(code: 0) { - return completion(.success(evaluationResult)) - } - - // Pass an open file descriptor of a file to which the JSON representation of the manifest will be written. - let jsonOutputFile = tmpDir.appending("\(packageIdentity)-output.json") - guard let jsonOutputFileDesc = fopen(jsonOutputFile.pathString, "w") else { - return completion(.failure(StringError("couldn't create the manifest's JSON output file"))) - } - - cmd = [compiledManifestFile.pathString] - #if os(Windows) - // NOTE: `_get_osfhandle` returns a non-owning, unsafe, - // unretained HANDLE. DO NOT invoke `CloseHandle` on `hFile`. - let hFile: Int = _get_osfhandle(_fileno(jsonOutputFileDesc)) - cmd += ["-handle", "\(String(hFile, radix: 16))"] - #else - cmd += ["-fileno", "\(fileno(jsonOutputFileDesc))"] - #endif - - do { - let packageDirectory = manifestPath.parentDirectory.pathString - - let gitInformation: ContextModel.GitInformation? - do { - let repo = GitRepository(path: manifestPath.parentDirectory) - gitInformation = ContextModel.GitInformation( - currentTag: repo.getCurrentTag(), - currentCommit: try repo.getCurrentRevision().identifier, - hasUncommittedChanges: repo.hasUncommittedChanges() - ) - } catch { - gitInformation = nil - } - - let contextModel = ContextModel( - packageDirectory: packageDirectory, - gitInformation: gitInformation - ) - cmd += ["-context", try contextModel.encode()] - } catch { - return completion(.failure(error)) - } - - // If enabled, run command in a sandbox. - // This provides some safety against arbitrary code execution when parsing manifest files. - // We only allow the permissions which are absolutely necessary. - if self.isManifestSandboxEnabled { - let cacheDirectories = [self.databaseCacheDir?.appending("ManifestLoading"), moduleCachePath].compactMap{ $0 } - let strictness: Sandbox.Strictness = toolsVersion < .v5_3 ? .manifest_pre_53 : .default - do { - cmd = try Sandbox.apply(command: cmd, fileSystem: localFileSystem, strictness: strictness, writableDirectories: cacheDirectories) - } catch { - return completion(.failure(error)) - } - } - - delegateQueue?.async { - delegate?.didCompile( - packageIdentity: packageIdentity, - packageLocation: packageLocation, - manifestPath: manifestPath, - duration: compileStart.distance(to: .now()) - ) - } - - // Run the compiled manifest. - - let evaluationStart = DispatchTime.now() - delegateQueue?.async { - delegate?.willEvaluate( - packageIdentity: packageIdentity, - packageLocation: packageLocation, - manifestPath: manifestPath - ) - } - - var environment = Environment.current - #if os(Windows) - let windowsPathComponent = runtimePath.pathString.replacing("/", with: "\\") - environment.prependPath(key: .path, value: windowsPathComponent) - #endif - - let cleanupAfterRunning = cleanupIfError.delay() - AsyncProcess.popen( - arguments: cmd, - environment: environment, - queue: callbackQueue - ) { result in - dispatchPrecondition(condition: .onQueue(callbackQueue)) - - defer { cleanupAfterRunning.perform() } - fclose(jsonOutputFileDesc) - - do { - let runResult = try result.get() - if let runOutput = try (runResult.utf8Output() + runResult.utf8stderrOutput()).spm_chuzzle() { - // Append the runtime output to any compiler output we've received. - evaluationResult.compilerOutput = (evaluationResult.compilerOutput ?? "") + runOutput - } - - // Return now if there was an error. - if runResult.exitStatus != .terminated(code: 0) { - // TODO: should this simply be an error? - // return completion(.failure(AsyncProcessResult.Error.nonZeroExit(runResult))) - evaluationResult.errorOutput = evaluationResult.compilerOutput - return completion(.success(evaluationResult)) - } - - // Read the JSON output that was emitted by libPackageDescription. - let jsonOutput: String = try localFileSystem.readFileContents(jsonOutputFile) - evaluationResult.manifestJSON = jsonOutput - - delegateQueue?.async { - delegate?.didEvaluate( - packageIdentity: packageIdentity, - packageLocation: packageLocation, - manifestPath: manifestPath, - duration: evaluationStart.distance(to: .now()) - ) - } - - completion(.success(evaluationResult)) - } catch { - completion(.failure(error)) - } - } - } + throw error // Re-throw process errors + } + + delegateQueue?.async { [delegate = self.delegate] in + delegate?.didCompile( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: compileStart.distance(to: .now()) + ) + } + + // Return now if there was a compilation error. + if compilerResult.exitStatus != .terminated(code: 0) { + // If there's compiler output, it's a format error. Otherwise, maybe something else went wrong. + evaluationResult.errorOutput = evaluationResult.compilerOutput ?? "Manifest compilation failed with exit status \(compilerResult.exitStatus)" + return evaluationResult // Return the result containing the error output + } + + // Pass an open file descriptor of a file to which the JSON representation of the manifest will be written. + let jsonOutputFile = tmpDir.appending("\(packageIdentity)-output.json") + guard let jsonOutputFileDesc = fopen(jsonOutputFile.pathString, "w") else { + throw StringError("couldn't create the manifest's JSON output file") + } + // Ensure the file is closed + defer { fclose(jsonOutputFileDesc) } + + + var runCmd = [compiledManifestFile.pathString] + #if os(Windows) + // NOTE: `_get_osfhandle` returns a non-owning, unsafe, + // unretained HANDLE. DO NOT invoke `CloseHandle` on `hFile`. + let hFile: Int = _get_osfhandle(_fileno(jsonOutputFileDesc)) + runCmd += ["-handle", "\(String(hFile, radix: 16))"] + #else + runCmd += ["-fileno", "\(fileno(jsonOutputFileDesc))"] + #endif + + do { + let packageDirectory = manifestPath.parentDirectory.pathString + + let gitInformation: ContextModel.GitInformation? + do { + let repo = GitRepository(path: manifestPath.parentDirectory) + // These Git operations might block, consider making them async if performance is critical + gitInformation = ContextModel.GitInformation( + currentTag: repo.getCurrentTag(), + currentCommit: try repo.getCurrentRevision().identifier, + hasUncommittedChanges: repo.hasUncommittedChanges() + ) + } catch { + // Ignore errors getting git info + gitInformation = nil + } + + let contextModel = ContextModel( + packageDirectory: packageDirectory, + gitInformation: gitInformation + ) + runCmd += ["-context", try contextModel.encode()] + } catch { + throw error // Re-throw encoding errors + } + + // If enabled, run command in a sandbox. + // This provides some safety against arbitrary code execution when parsing manifest files. + // We only allow the permissions which are absolutely necessary. + if self.isManifestSandboxEnabled { + let cacheDirectories = [self.databaseCacheDir?.appending("ManifestLoading"), moduleCachePath].compactMap{ $0 } + let strictness: Sandbox.Strictness = toolsVersion < .v5_3 ? .manifest_pre_53 : .default + do { + runCmd = try Sandbox.apply(command: runCmd, fileSystem: localFileSystem, strictness: strictness, writableDirectories: cacheDirectories) + } catch { + throw error // Re-throw sandbox errors + } + } + + // Run the compiled manifest. + let evaluationStart = DispatchTime.now() + + delegateQueue?.async { [delegate = self.delegate] in + delegate?.willEvaluate( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath + ) + } + + var environment = Environment.current + #if os(Windows) + let windowsPathComponent = runtimePath.pathString.replacing("/", with: "\\") + environment.prependPath(key: .path, value: windowsPathComponent) + #endif + + let runResult: AsyncProcessResult + do { + runResult = try await AsyncProcess.popen(arguments: runCmd, environment: environment) + if let runOutput = try (runResult.utf8Output() + runResult.utf8stderrOutput()).spm_chuzzle() { + // Append the runtime output to any compiler output we've received. + evaluationResult.compilerOutput = (evaluationResult.compilerOutput ?? "") + runOutput } } catch { - return callbackQueue.async { - completion(.failure(error)) + delegateQueue?.async { [delegate = self.delegate] in + delegate?.didEvaluate( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: evaluationStart.distance(to: .now()) + ) } + throw error // Re-throw process errors + } + + delegateQueue?.async { [delegate = self.delegate] in + delegate?.didEvaluate( + packageIdentity: packageIdentity, + packageLocation: packageLocation, + manifestPath: manifestPath, + duration: evaluationStart.distance(to: .now()) + ) } + + // Return now if there was a runtime error. + if runResult.exitStatus != .terminated(code: 0) { + // The runtime output is the error + evaluationResult.errorOutput = evaluationResult.compilerOutput + return evaluationResult // Return the result containing the error output + } + + // Read the JSON output that was emitted by libPackageDescription. + let jsonOutput: String = try localFileSystem.readFileContents(jsonOutputFile) + evaluationResult.manifestJSON = jsonOutput + + // withTemporaryDirectory handles cleanup automatically + return evaluationResult } } @@ -1166,13 +985,13 @@ public final class ManifestLoader: ManifestLoaderProtocol { } /// reset internal cache - public func resetCache(observabilityScope: ObservabilityScope) { - self.memoryCache.clear() + public func resetCache(observabilityScope: ObservabilityScope) async { + await self.memoryCacheActor.clear() } /// reset internal state and purge shared cache - public func purgeCache(observabilityScope: ObservabilityScope) { - self.resetCache(observabilityScope: observabilityScope) + public func purgeCache(observabilityScope: ObservabilityScope) async { + await self.resetCache(observabilityScope: observabilityScope) guard let manifestCacheDBPath = self.databaseCacheDir.flatMap({ Self.manifestCacheDBPath($0) }) else { return diff --git a/Sources/PackageMetadata/PackageMetadata.swift b/Sources/PackageMetadata/PackageMetadata.swift index 6cd5450ab2a..531430a09dd 100644 --- a/Sources/PackageMetadata/PackageMetadata.swift +++ b/Sources/PackageMetadata/PackageMetadata.swift @@ -329,15 +329,15 @@ public struct PackageSearchClient { timeout: DispatchTimeInterval? = .none, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @escaping (Result, Error>) -> Void + completion: @Sendable @escaping (Result, Error>) -> Void ) { - registryClient.lookupIdentities( - scmURL: scmURL, - timeout: timeout, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue, - completion: completion - ) + callbackQueue.asyncResult(completion) { + try await registryClient.lookupIdentities( + scmURL: scmURL, + timeout: timeout, + observabilityScope: observabilityScope + ) + } } public func lookupSCMURLs( @@ -345,21 +345,16 @@ public struct PackageSearchClient { timeout: DispatchTimeInterval? = .none, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @escaping (Result, Error>) -> Void + completion: @Sendable @escaping (Result, Error>) -> Void ) { - registryClient.getPackageMetadata( - package: package, - timeout: timeout, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue - ) { result in - do { - let metadata = try result.get() - let alternateLocations = metadata.alternateLocations - return completion(.success(Set(alternateLocations))) - } catch { - return completion(.failure(error)) - } + callbackQueue.asyncResult(completion) { + let metadata = try await registryClient.getPackageMetadata( + package: package, + timeout: timeout, + observabilityScope: observabilityScope + ) + let alternateLocations = metadata.alternateLocations + return Set(alternateLocations) } } } diff --git a/Sources/PackageRegistry/RegistryClient.swift b/Sources/PackageRegistry/RegistryClient.swift index eaf3228440e..2eec40a5453 100644 --- a/Sources/PackageRegistry/RegistryClient.swift +++ b/Sources/PackageRegistry/RegistryClient.swift @@ -2350,17 +2350,3 @@ extension Result { } } } - -extension DispatchQueue { - func asyncResult(_ callback: @escaping (Result) -> Void, _ closure: @escaping () async throws -> T) { - let completion: (Result) -> Void = { result in self.async { callback(result) } } - Task { - do { - completion(.success(try await closure())) - } catch { - completion(.failure(error)) - } - } - } -} - diff --git a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift index c1892c25aee..473da456bdd 100644 --- a/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/FileSystemPackageContainer.swift @@ -78,26 +78,19 @@ public struct FileSystemPackageContainer: PackageContainer { } // Load the manifest. - // FIXME: this should not block - return try await withCheckedThrowingContinuation { continuation in - manifestLoader.load( - packagePath: packagePath, - packageIdentity: self.package.identity, - packageKind: self.package.kind, - packageLocation: self.package.locationString, - packageVersion: nil, - currentToolsVersion: self.currentToolsVersion, - identityResolver: self.identityResolver, - dependencyMapper: self.dependencyMapper, - fileSystem: self.fileSystem, - observabilityScope: self.observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: { - continuation.resume(with: $0) - } - ) - } + return try await manifestLoader.load( + packagePath: packagePath, + packageIdentity: self.package.identity, + packageKind: self.package.kind, + packageLocation: self.package.locationString, + packageVersion: nil, + currentToolsVersion: self.currentToolsVersion, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope, + delegateQueue: .sharedConcurrent + ) } } diff --git a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift index 4b23d79fa1b..4884dbdb562 100644 --- a/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/RegistryPackageContainer.swift @@ -148,8 +148,7 @@ public class RegistryPackageContainer: PackageContainer { dependencyMapper: self.dependencyMapper, fileSystem: result.fileSystem, observabilityScope: self.observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) } diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index da1f56f959f..d2c9dc52fa9 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -397,26 +397,19 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri private func loadManifest(fileSystem: FileSystem, version: Version?, revision: String) async throws -> Manifest { // Load the manifest. - // FIXME: this should not block - return try await withCheckedThrowingContinuation { continuation in - self.manifestLoader.load( - packagePath: .root, - packageIdentity: self.package.identity, - packageKind: self.package.kind, - packageLocation: self.package.locationString, - packageVersion: (version: version, revision: revision), - currentToolsVersion: self.currentToolsVersion, - identityResolver: self.identityResolver, - dependencyMapper: self.dependencyMapper, - fileSystem: fileSystem, - observabilityScope: self.observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: { - continuation.resume(with: $0) - } - ) - } + return try await self.manifestLoader.load( + packagePath: .root, + packageIdentity: self.package.identity, + packageKind: self.package.kind, + packageLocation: self.package.locationString, + packageVersion: (version: version, revision: revision), + currentToolsVersion: self.currentToolsVersion, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + fileSystem: fileSystem, + observabilityScope: self.observabilityScope, + delegateQueue: .sharedConcurrent + ) } public func getEnabledTraits(traitConfiguration: TraitConfiguration, at revision: String?, version: Version?) async throws -> Set { diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index c5e080404cf..af20f11c39f 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -507,7 +507,6 @@ extension Workspace { // Ensure the cache path exists and validate that edited dependencies. self.createCacheDirectories(observabilityScope: observabilityScope) - // FIXME: this should not block // Load the root manifests and currently checked out manifests. let rootManifests = try await self.loadRootManifests( packages: root.packages, diff --git a/Sources/Workspace/Workspace+Editing.swift b/Sources/Workspace/Workspace+Editing.swift index a403ca26931..2462a9a6bc2 100644 --- a/Sources/Workspace/Workspace+Editing.swift +++ b/Sources/Workspace/Workspace+Editing.swift @@ -66,19 +66,13 @@ extension Workspace { // If there is something present at the destination, we confirm it has // a valid manifest with name canonical location as the package we are trying to edit. if fileSystem.exists(destination) { - // FIXME: this should not block - let manifest = try await withCheckedThrowingContinuation { continuation in - self.loadManifest( - packageIdentity: dependency.packageRef.identity, - packageKind: .fileSystem(destination), - packagePath: destination, - packageLocation: dependency.packageRef.locationString, - observabilityScope: observabilityScope, - completion: { - continuation.resume(with: $0) - } - ) - } + let manifest = try await self.loadManifest( + packageIdentity: dependency.packageRef.identity, + packageKind: .fileSystem(destination), + packagePath: destination, + packageLocation: dependency.packageRef.locationString, + observabilityScope: observabilityScope + ) guard dependency.packageRef.canonicalLocation == manifest.canonicalPackageLocation else { return observabilityScope diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 1eaaccba606..e7d2f5c7cff 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -842,19 +842,15 @@ extension Workspace { } // Load and return the manifest. - return await withCheckedContinuation { continuation in - self.loadManifest( - packageIdentity: managedDependency.packageRef.identity, - packageKind: packageKind, - packagePath: packagePath, - packageLocation: managedDependency.packageRef.locationString, - packageVersion: packageVersion, - fileSystem: fileSystem, - observabilityScope: observabilityScope - ) { result in - continuation.resume(returning: try? result.get()) - } - } + return try? await self.loadManifest( + packageIdentity: managedDependency.packageRef.identity, + packageKind: packageKind, + packagePath: packagePath, + packageLocation: managedDependency.packageRef.locationString, + packageVersion: packageVersion, + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) } /// Load the manifest at a given path. @@ -867,9 +863,8 @@ extension Workspace { packageLocation: String, packageVersion: Version? = nil, fileSystem: FileSystem? = nil, - observabilityScope: ObservabilityScope, - completion: @escaping (Result) -> Void - ) { + observabilityScope: ObservabilityScope + ) async throws -> Manifest { let fileSystem = fileSystem ?? self.fileSystem // Load the manifest, bracketed by the calls to the delegate callbacks. @@ -886,63 +881,63 @@ extension Workspace { } var manifestLoadingDiagnostics = [Diagnostic]() + defer { manifestLoadingScope.emit(manifestLoadingDiagnostics) } let start = DispatchTime.now() - self.manifestLoader.load( - packagePath: packagePath, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: packageVersion.map { (version: $0, revision: nil) }, - currentToolsVersion: self.currentToolsVersion, - identityResolver: self.identityResolver, - dependencyMapper: self.dependencyMapper, - fileSystem: fileSystem, - observabilityScope: manifestLoadingScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent - ) { result in + let manifest: Manifest + do { + manifest = try await self.manifestLoader.load( + packagePath: packagePath, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: packageVersion.map { (version: $0, revision: nil) }, + currentToolsVersion: self.currentToolsVersion, + identityResolver: self.identityResolver, + dependencyMapper: self.dependencyMapper, + fileSystem: fileSystem, + observabilityScope: manifestLoadingScope, + delegateQueue: .sharedConcurrent + ) + } catch { let duration = start.distance(to: .now()) - var result = result - switch result { - case .failure(let error): - manifestLoadingDiagnostics.append(.error(error)) - self.delegate?.didLoadManifest( - packageIdentity: packageIdentity, - packagePath: packagePath, - url: packageLocation, - version: packageVersion, - packageKind: packageKind, - manifest: nil, - diagnostics: manifestLoadingDiagnostics, - duration: duration - ) - case .success(let manifest): - let validator = ManifestValidator( - manifest: manifest, - sourceControlValidator: self.repositoryManager, - fileSystem: self.fileSystem - ) - let validationIssues = validator.validate() - if !validationIssues.isEmpty { - // Diagnostics.fatalError indicates that a more specific diagnostic has already been added. - result = .failure(Diagnostics.fatalError) - manifestLoadingDiagnostics.append(contentsOf: validationIssues) - } - self.delegate?.didLoadManifest( - packageIdentity: packageIdentity, - packagePath: packagePath, - url: packageLocation, - version: packageVersion, - packageKind: packageKind, - manifest: manifest, - diagnostics: manifestLoadingDiagnostics, - duration: duration - ) - } - manifestLoadingScope.emit(manifestLoadingDiagnostics) - completion(result) + manifestLoadingDiagnostics.append(.error(error)) + self.delegate?.didLoadManifest( + packageIdentity: packageIdentity, + packagePath: packagePath, + url: packageLocation, + version: packageVersion, + packageKind: packageKind, + manifest: nil, + diagnostics: manifestLoadingDiagnostics, + duration: duration + ) + throw error } + + let duration = start.distance(to: .now()) + let validator = ManifestValidator( + manifest: manifest, + sourceControlValidator: self.repositoryManager, + fileSystem: self.fileSystem + ) + let validationIssues = validator.validate() + if !validationIssues.isEmpty { + // Diagnostics.fatalError indicates that a more specific diagnostic has already been added. + manifestLoadingDiagnostics.append(contentsOf: validationIssues) + throw Diagnostics.fatalError + } + self.delegate?.didLoadManifest( + packageIdentity: packageIdentity, + packagePath: packagePath, + url: packageLocation, + version: packageVersion, + packageKind: packageKind, + manifest: manifest, + diagnostics: manifestLoadingDiagnostics, + duration: duration + ) + return manifest } /// Validates that all the edited dependencies are still present in the file system. diff --git a/Sources/Workspace/Workspace+Registry.swift b/Sources/Workspace/Workspace+Registry.swift index 17687d99ffc..329ff414491 100644 --- a/Sources/Workspace/Workspace+Registry.swift +++ b/Sources/Workspace/Workspace+Registry.swift @@ -84,11 +84,9 @@ extension Workspace { dependencyMapper: any DependencyMapper, fileSystem: any FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - self.underlying.load( + delegateQueue: DispatchQueue + ) async throws -> Manifest { + let manifest = try await self.underlying.load( manifestPath: manifestPath, manifestToolsVersion: manifestToolsVersion, packageIdentity: packageIdentity, @@ -99,81 +97,69 @@ extension Workspace { dependencyMapper: dependencyMapper, fileSystem: fileSystem, observabilityScope: observabilityScope, - delegateQueue: delegateQueue, - callbackQueue: callbackQueue - ) { result in - switch result { - case .failure(let error): - completion(.failure(error)) - case .success(let manifest): - self.transformSourceControlDependenciesToRegistry( - manifest: manifest, - transformationMode: transformationMode, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue, - completion: completion - ) - } - } + delegateQueue: delegateQueue + ) + return try await self.transformSourceControlDependenciesToRegistry( + manifest: manifest, + transformationMode: transformationMode, + observabilityScope: observabilityScope + ) } - func resetCache(observabilityScope: ObservabilityScope) { - self.underlying.resetCache(observabilityScope: observabilityScope) + func resetCache(observabilityScope: ObservabilityScope) async { + await self.underlying.resetCache(observabilityScope: observabilityScope) } - func purgeCache(observabilityScope: ObservabilityScope) { - self.underlying.purgeCache(observabilityScope: observabilityScope) + func purgeCache(observabilityScope: ObservabilityScope) async { + await self.underlying.purgeCache(observabilityScope: observabilityScope) } private func transformSourceControlDependenciesToRegistry( manifest: Manifest, transformationMode: TransformationMode, - observabilityScope: ObservabilityScope, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - let sync = DispatchGroup() - let transformations = ThreadSafeKeyValueStore() - for dependency in manifest.dependencies { - if case .sourceControl(let settings) = dependency, case .remote(let url) = settings.location { - sync.enter() - self.mapRegistryIdentity( - url: url, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue - ) { result in - defer { sync.leave() } - switch result { - case .failure(let error): - // do not raise error, only report it as warning - observabilityScope.emit( - warning: "failed querying registry identity for '\(url)'", - underlyingError: error - ) - case .success(.some(let identity)): - transformations[dependency] = identity - case .success(.none): - // no identity found - break + observabilityScope: ObservabilityScope + ) async throws -> Manifest { + var transformations = [PackageDependency: PackageIdentity]() + + try await withThrowingTaskGroup(of: (PackageDependency, PackageIdentity?).self) { group in + for dependency in manifest.dependencies { + if case .sourceControl(let settings) = dependency, case .remote(let url) = settings.location { + group.addTask { + do { + let identity = try await self.mapRegistryIdentity( + url: url, + observabilityScope: observabilityScope + ) + return (dependency, identity) + } catch { + // do not raise error, only report it as warning + observabilityScope.emit( + warning: "failed querying registry identity for '\(url)'", + underlyingError: error + ) + return (dependency, nil) + } } } } - } - // update the manifest with the transformed dependencies - sync.notify(queue: callbackQueue) { - do { - let updatedManifest = try self.transformManifest( - manifest: manifest, - transformations: transformations.get(), - transformationMode: transformationMode, - observabilityScope: observabilityScope - ) - completion(.success(updatedManifest)) - } catch { - return completion(.failure(error)) + // Collect the results from the group + for try await (dependency, identity) in group { + if let identity { + transformations[dependency] = identity + } } } + + // update the manifest with the transformed dependencies + let updatedManifest = try self.transformManifest( + manifest: manifest, + transformations: transformations, + transformationMode: transformationMode, + observabilityScope: observabilityScope + ) + + return updatedManifest } private func transformManifest( @@ -344,35 +330,29 @@ extension Workspace { private func mapRegistryIdentity( url: SourceControlURL, - observabilityScope: ObservabilityScope, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { + observabilityScope: ObservabilityScope + ) async throws -> PackageIdentity? { if let cached = self.identityLookupCache[url], cached.expirationTime > .now() { switch cached.result { case .success(let identity): - return completion(.success(identity)) + return identity; case .failure: // server error, do not try again - return completion(.success(.none)) + return nil } } - self.registryClient.lookupIdentities( - scmURL: url, - observabilityScope: observabilityScope, - callbackQueue: callbackQueue - ) { result in - switch result { - case .failure(let error): - self.identityLookupCache[url] = (result: .failure(error), expirationTime: .now() + self.cacheTTL) - completion(.failure(error)) - case .success(let identities): - // FIXME: returns first result... need to consider how to address multiple ones - let identity = identities.sorted().first - self.identityLookupCache[url] = (result: .success(identity), expirationTime: .now() + self.cacheTTL) - completion(.success(identity)) - } + do { + let identities = try await self.registryClient.lookupIdentities( + scmURL: url, + observabilityScope: observabilityScope + ) + let identity = identities.sorted().first + self.identityLookupCache[url] = (result: .success(identity), expirationTime: .now() + self.cacheTTL) + return identity + } catch { + self.identityLookupCache[url] = (result: .failure(error), expirationTime: .now() + self.cacheTTL) + throw error } } diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index dccf7d41843..90e98927e6f 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -847,10 +847,10 @@ extension Workspace { /// /// - Parameters: /// - observabilityScope: The observability scope that reports errors, warnings, etc - public func purgeCache(observabilityScope: ObservabilityScope) { + public func purgeCache(observabilityScope: ObservabilityScope) async { self.repositoryManager.purgeCache(observabilityScope: observabilityScope) self.registryDownloadsManager.purgeCache(observabilityScope: observabilityScope) - self.manifestLoader.purgeCache(observabilityScope: observabilityScope) + await self.manifestLoader.purgeCache(observabilityScope: observabilityScope) } /// Resets the entire workspace by removing the data directory. @@ -875,7 +875,7 @@ extension Workspace { self.repositoryManager.reset(observabilityScope: observabilityScope) self.registryDownloadsManager.reset(observabilityScope: observabilityScope) - self.manifestLoader.resetCache(observabilityScope: observabilityScope) + await self.manifestLoader.resetCache(observabilityScope: observabilityScope) do { try self.fileSystem.removeFileTree(self.location.scratchDirectory) } catch { @@ -1025,10 +1025,45 @@ extension Workspace { packages: [AbsolutePath], observabilityScope: ObservabilityScope ) async throws -> [AbsolutePath: Manifest] { - try await withCheckedThrowingContinuation { continuation in - self.loadRootManifests(packages: packages, observabilityScope: observabilityScope) { result in - continuation.resume(with: result) + try await withThrowingTaskGroup(of: Optional<(AbsolutePath, Manifest)>.self) { group in + var rootManifests = [AbsolutePath: Manifest]() + for package in Set(packages) { + group.addTask { + // TODO: this does not use the identity resolver which is probably fine since its the root packages + do { + let manifest = try await self.loadManifest( + packageIdentity: PackageIdentity(path: package), + packageKind: .root(package), + packagePath: package, + packageLocation: package.pathString, + observabilityScope: observabilityScope + ) + return (package, manifest) + } catch { + return nil + } + } + } + + // Collect the results. + for try await result in group { + if let (package, manifest) = result { + // Store the manifest. + rootManifests[package] = manifest + } + } + + // Check for duplicate root packages after all manifests are loaded. + let duplicateRoots = rootManifests.values.spm_findDuplicateElements(by: \.displayName) + if let firstDuplicateSet = duplicateRoots.first, let firstDuplicate = firstDuplicateSet.first { + observabilityScope.emit(error: "found multiple top-level packages named '\(firstDuplicate.displayName)'") + // Decide how to handle duplicates, e.g., throw an error or return an empty dictionary. + // For now, matching the original behavior of returning an empty dictionary on error. + // Consider throwing an error instead for better error propagation. + return [:] } + + return rootManifests } } @@ -1039,38 +1074,11 @@ extension Workspace { observabilityScope: ObservabilityScope, completion: @escaping (Result<[AbsolutePath: Manifest], Error>) -> Void ) { - let lock = NSLock() - let sync = DispatchGroup() - var rootManifests = [AbsolutePath: Manifest]() - for package in Set(packages) { - sync.enter() - // TODO: this does not use the identity resolver which is probably fine since its the root packages - self.loadManifest( - packageIdentity: PackageIdentity(path: package), - packageKind: .root(package), - packagePath: package, - packageLocation: package.pathString, + DispatchQueue.sharedConcurrent.asyncResult(completion) { + try await self.loadRootManifests( + packages: packages, observabilityScope: observabilityScope - ) { result in - defer { sync.leave() } - if case .success(let manifest) = result { - lock.withLock { - rootManifests[package] = manifest - } - } - } - } - - sync.notify(queue: .sharedConcurrent) { - // Check for duplicate root packages. - let duplicateRoots = rootManifests.values.spm_findDuplicateElements(by: \.displayName) - if !duplicateRoots.isEmpty { - let name = duplicateRoots[0][0].displayName - observabilityScope.emit(error: "found multiple top-level packages named '\(name)'") - return completion(.success([:])) - } - - completion(.success(rootManifests)) + ) } } @@ -1216,16 +1224,34 @@ extension Workspace { packageGraph: ModulesGraph, observabilityScope: ObservabilityScope ) async throws -> Package { - try await withCheckedThrowingContinuation { continuation in - self.loadPackage( - with: identity, - packageGraph: packageGraph, - observabilityScope: observabilityScope, - completion: { - continuation.resume(with: $0) - } - ) + guard let previousPackage = packageGraph.package(for: identity) else { + throw StringError("could not find package with identity \(identity)") } + + let manifest = try await self.loadManifest( + packageIdentity: identity, + packageKind: previousPackage.underlying.manifest.packageKind, + packagePath: previousPackage.path, + packageLocation: previousPackage.underlying.manifest.packageLocation, + observabilityScope: observabilityScope + ) + let builder = PackageBuilder( + identity: identity, + manifest: manifest, + productFilter: .everything, + // TODO: this will not be correct when reloading a transitive dependencies if `ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION` is enabled + path: previousPackage.path, + additionalFileRules: self.configuration.additionalFileRules, + binaryArtifacts: packageGraph.binaryArtifacts[identity] ?? [:], + prebuilts: [:], + shouldCreateMultipleTestProducts: self.configuration.shouldCreateMultipleTestProducts, + createREPLProduct: self.configuration.createREPLProduct, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope, + // For now we enable all traits + enabledTraits: Set(manifest.traits.map(\.name)) + ) + return try builder.construct() } /// Loads a single package in the context of a previously loaded graph. This can be useful for incremental loading @@ -1237,37 +1263,12 @@ extension Workspace { observabilityScope: ObservabilityScope, completion: @escaping (Result) -> Void ) { - guard let previousPackage = packageGraph.package(for: identity) else { - return completion(.failure(StringError("could not find package with identity \(identity)"))) - } - - self.loadManifest( - packageIdentity: identity, - packageKind: previousPackage.underlying.manifest.packageKind, - packagePath: previousPackage.path, - packageLocation: previousPackage.underlying.manifest.packageLocation, - observabilityScope: observabilityScope - ) { result in - let result = result.tryMap { manifest -> Package in - let builder = PackageBuilder( - identity: identity, - manifest: manifest, - productFilter: .everything, - // TODO: this will not be correct when reloading a transitive dependencies if `ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION` is enabled - path: previousPackage.path, - additionalFileRules: self.configuration.additionalFileRules, - binaryArtifacts: packageGraph.binaryArtifacts[identity] ?? [:], - prebuilts: [:], - shouldCreateMultipleTestProducts: self.configuration.shouldCreateMultipleTestProducts, - createREPLProduct: self.configuration.createREPLProduct, - fileSystem: self.fileSystem, - observabilityScope: observabilityScope, - // For now we enable all traits - enabledTraits: Set(manifest.traits.map(\.name)) - ) - return try builder.construct() - } - completion(result) + DispatchQueue.sharedConcurrent.asyncResult(completion) { + try await self.loadPackage( + with: identity, + packageGraph: packageGraph, + observabilityScope: observabilityScope + ) } } diff --git a/Sources/_InternalTestSupport/MockManifestLoader.swift b/Sources/_InternalTestSupport/MockManifestLoader.swift index 3245d7d8737..322681abde7 100644 --- a/Sources/_InternalTestSupport/MockManifestLoader.swift +++ b/Sources/_InternalTestSupport/MockManifestLoader.swift @@ -61,22 +61,18 @@ public final class MockManifestLoader: ManifestLoaderProtocol { dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { - callbackQueue.async { - let key = Key(url: packageLocation, version: packageVersion?.version) - if let result = self.manifests[key] { - return completion(.success(result)) - } else { - return completion(.failure(MockManifestLoaderError.unknownRequest("\(key)"))) - } + delegateQueue: DispatchQueue + ) async throws -> Manifest { + let key = Key(url: packageLocation, version: packageVersion?.version) + if let result = self.manifests[key] { + return result + } else { + throw MockManifestLoaderError.unknownRequest("\(key)") } } - public func resetCache(observabilityScope: ObservabilityScope) {} - public func purgeCache(observabilityScope: ObservabilityScope) {} + public func resetCache(observabilityScope: ObservabilityScope) async {} + public func purgeCache(observabilityScope: ObservabilityScope) async {} } extension ManifestLoader { @@ -120,8 +116,7 @@ extension ManifestLoader { dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), fileSystem: fileSystem, observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) } } @@ -167,8 +162,7 @@ extension ManifestLoader { dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), fileSystem: fileSystem, observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) } } diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 8265e1ecb1a..bb1ebfe311a 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -458,8 +458,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { dependencyMapper: dependencyMapper, fileSystem: fileSystem, observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) } } diff --git a/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift b/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift index a2c7a9a69df..b6f46beab36 100644 --- a/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift +++ b/Tests/PackageLoadingTests/ManifestLoaderCacheTests.swift @@ -107,7 +107,7 @@ final class ManifestLoaderCacheTests: XCTestCase { // Resetting the cache should allow us to remove the cache // directory without triggering assertions in sqlite. - manifestLoader.purgeCache(observabilityScope: observability.topScope) + await manifestLoader.purgeCache(observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) try fileSystem.removeFileTree(path) } @@ -198,7 +198,7 @@ final class ManifestLoaderCacheTests: XCTestCase { try await check(loader: noCacheLoader, expectCached: false) } - manifestLoader.purgeCache(observabilityScope: observability.topScope) + await manifestLoader.purgeCache(observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } diff --git a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift index f3dda8f8fac..507182d0d68 100644 --- a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift @@ -638,8 +638,7 @@ final class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { dependencyMapper: dependencyMapper, fileSystem: localFileSystem, observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -658,8 +657,7 @@ final class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { dependencyMapper: dependencyMapper, fileSystem: localFileSystem, observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -722,8 +720,7 @@ final class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { dependencyMapper: dependencyMapper, fileSystem: localFileSystem, observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) XCTAssertEqual(manifest.displayName, "Trivial-\(random)") diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index 0a550e24fe0..a7f1d7d8cab 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -63,8 +63,7 @@ final class ManifestSourceGenerationTests: XCTestCase { dependencyMapper: dependencyMapper, fileSystem: fs, observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -92,8 +91,7 @@ final class ManifestSourceGenerationTests: XCTestCase { dependencyMapper: dependencyMapper, fileSystem: fs, observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent + delegateQueue: .sharedConcurrent ) XCTAssertNoDiagnostics(observability.diagnostics) diff --git a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift index fcd6809d83b..127fcdf1137 100644 --- a/Tests/WorkspaceTests/RegistryPackageContainerTests.swift +++ b/Tests/WorkspaceTests/RegistryPackageContainerTests.swift @@ -262,30 +262,28 @@ final class RegistryPackageContainerTests: XCTestCase { ) struct MockManifestLoader: ManifestLoaderProtocol { - func load(manifestPath: AbsolutePath, - manifestToolsVersion: ToolsVersion, - packageIdentity: PackageIdentity, - packageKind: PackageReference.Kind, - packageLocation: String, - packageVersion: (version: Version?, revision: String?)?, - identityResolver: IdentityResolver, - dependencyMapper: DependencyMapper, - fileSystem: FileSystem, - observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void) { - completion(.success( - Manifest.createManifest( - displayName: packageIdentity.description, - path: manifestPath, - packageKind: packageKind, - packageIdentity: packageIdentity, - packageLocation: packageLocation, - platforms: [], - toolsVersion: manifestToolsVersion - ) - )) + func load( + manifestPath: AbsolutePath, + manifestToolsVersion: ToolsVersion, + packageIdentity: PackageIdentity, + packageKind: PackageReference.Kind, + packageLocation: String, + packageVersion: (version: Version?, revision: String?)?, + identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + delegateQueue: DispatchQueue + ) async throws -> Manifest { + Manifest.createManifest( + displayName: packageIdentity.description, + path: manifestPath, + packageKind: packageKind, + packageIdentity: packageIdentity, + packageLocation: packageLocation, + platforms: [], + toolsVersion: manifestToolsVersion + ) } func resetCache(observabilityScope: ObservabilityScope) {} diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 15180376160..f4772f88eda 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -12695,28 +12695,20 @@ final class WorkspaceTests: XCTestCase { dependencyMapper: DependencyMapper, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - delegateQueue: DispatchQueue, - callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void - ) { + delegateQueue: DispatchQueue + ) async throws -> Manifest { if let error { - callbackQueue.async { - completion(.failure(error)) - } + throw error } else { - callbackQueue.async { - completion(.success( - Manifest.createManifest( - displayName: packageIdentity.description, - path: manifestPath, - packageKind: packageKind, - packageIdentity: packageIdentity, - packageLocation: packageLocation, - platforms: [], - toolsVersion: manifestToolsVersion - ) - )) - } + return Manifest.createManifest( + displayName: packageIdentity.description, + path: manifestPath, + packageKind: packageKind, + packageIdentity: packageIdentity, + packageLocation: packageLocation, + platforms: [], + toolsVersion: manifestToolsVersion + ) } } From e5c210a99345eec69d2e246e628f281fd5ae60dc Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Thu, 8 May 2025 14:28:10 -0400 Subject: [PATCH 86/99] Tests: Convert more tests to Swift Testing (#8636) Continue to test conversion to Swift Testing. - Tests/BasicsTests/HTTPClientTests.swift - Tests/BasicsTests/SQLiteBackedCacheTests.swift - Tests/BuildTests/BuildPlanTraversalTests.swift - Tests/BuildTests/LLBuildManifestBuilderTests.swift - Tests/BuildTests/SwiftCompilerOutputParserTests.swift - Tests/BuildTests/WindowsBuildPlanTests.swift - Tests/_AsyncFileSystemTests/AsyncFileSystemTests.swift --- Package.swift | 11 +- Tests/BasicsTests/HTTPClientTests.swift | 272 ++++++++++-------- .../BasicsTests/SQLiteBackedCacheTests.swift | 136 ++++++--- .../BuildTests/BuildPlanTraversalTests.swift | 96 ++++--- .../LLBuildManifestBuilderTests.swift | 73 ++--- .../SwiftCompilerOutputParserTests.swift | 21 +- Tests/BuildTests/WindowsBuildPlanTests.swift | 52 ++-- .../AsyncFileSystemTests.swift | 29 +- 8 files changed, 370 insertions(+), 320 deletions(-) diff --git a/Package.swift b/Package.swift index 78b1dde6e8e..12f2a2f31bf 100644 --- a/Package.swift +++ b/Package.swift @@ -824,7 +824,12 @@ let package = Package( .testTarget( name: "BasicsTests", - dependencies: ["Basics", "_InternalTestSupport", "tsan_utils"], + dependencies: [ + "Basics", + "_InternalTestSupport", + "tsan_utils", + .product(name: "Numerics", package: "swift-numerics"), + ], exclude: [ "Archiver/Inputs/archive.tar.gz", "Archiver/Inputs/archive.zip", @@ -1077,6 +1082,9 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/swiftlang/swift-toolchain-sqlite.git", from: "1.0.0"), // For use in previewing documentation .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), + + // Test dependency + .package(url: "https://github.com/apple/swift-numerics.git", from: "1.0.3"), ] } else { package.dependencies += [ @@ -1089,6 +1097,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(path: "../swift-collections"), .package(path: "../swift-certificates"), .package(path: "../swift-toolchain-sqlite"), + .package(path: "../swift-numerics"), ] } diff --git a/Tests/BasicsTests/HTTPClientTests.swift b/Tests/BasicsTests/HTTPClientTests.swift index 33fbacee638..1ced370499f 100644 --- a/Tests/BasicsTests/HTTPClientTests.swift +++ b/Tests/BasicsTests/HTTPClientTests.swift @@ -9,14 +9,47 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation @testable import Basics import _Concurrency import _InternalTestSupport +import Testing import XCTest -final class HTTPClientTests: XCTestCase { - func testHead() async throws { +class HTTPClientXCTest: XCTestCase { + func testEponentialBackoff() async throws { + try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") + let counter = SendableBox(0) + let lastCall = SendableBox() + let maxAttempts = 5 + let errorCode = Int.random(in: 500 ..< 600) + let delay = SendableTimeInterval.milliseconds(100) + + let httpClient = HTTPClient { _, _ in + let count = await counter.value! + let expectedDelta = pow(2.0, Double(count - 1)) * delay.timeInterval()! + let delta = await lastCall.value.flatMap { Date().timeIntervalSince($0) } ?? 0 + XCTAssertEqual(delta, expectedDelta, accuracy: 0.1) + + await counter.increment() + await lastCall.resetDate() + return .init(statusCode: errorCode) + } + var request = HTTPClient.Request(method: .get, url: "http://test") + request.options.retryStrategy = .exponentialBackoff(maxAttempts: maxAttempts, baseDelay: delay) + + let response = try await httpClient.execute(request) + XCTAssertEqual(response.statusCode, errorCode) + let count = await counter.value + XCTAssertEqual(count, maxAttempts, "retries should match") + } +} + + +struct HTTPClientTests { + @Test + func head() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let responseStatus = Int.random(in: 201 ..< 500) @@ -24,18 +57,19 @@ final class HTTPClientTests: XCTestCase { let responseBody: Data? = nil let httpClient = HTTPClient { request, _ in - XCTAssertEqual(request.url, url, "url should match") - XCTAssertEqual(request.method, .head, "method should match") - assertRequestHeaders(request.headers, expected: requestHeaders) + #expect(request.url == url) + #expect(request.method == .head) + self.expectRequestHeaders(request.headers, expected: requestHeaders) return .init(statusCode: responseStatus, headers: responseHeaders, body: responseBody) } let response = try await httpClient.head(url, headers: requestHeaders) - XCTAssertEqual(response.statusCode, responseStatus, "statusCode should match") - assertResponseHeaders(response.headers, expected: responseHeaders) - XCTAssertEqual(response.body, responseBody, "body should match") + #expect(response.statusCode == responseStatus) + self.expectResponseHeaders(response.headers, expected: responseHeaders) + #expect(response.body == responseBody) } + @Test func testGet() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) @@ -44,19 +78,20 @@ final class HTTPClientTests: XCTestCase { let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in - XCTAssertEqual(request.url, url, "url should match") - XCTAssertEqual(request.method, .get, "method should match") - assertRequestHeaders(request.headers, expected: requestHeaders) + #expect(request.url == url) + #expect(request.method == .get) + self.expectRequestHeaders(request.headers, expected: requestHeaders) return .init(statusCode: responseStatus, headers: responseHeaders, body: responseBody) } let response = try await httpClient.get(url, headers: requestHeaders) - XCTAssertEqual(response.statusCode, responseStatus, "statusCode should match") - assertResponseHeaders(response.headers, expected: responseHeaders) - XCTAssertEqual(response.body, responseBody, "body should match") + #expect(response.statusCode == responseStatus) + self.expectResponseHeaders(response.headers, expected: responseHeaders) + #expect(response.body == responseBody) } - func testPost() async throws { + @Test + func post() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let requestBody = Data(UUID().uuidString.utf8) @@ -65,20 +100,21 @@ final class HTTPClientTests: XCTestCase { let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in - XCTAssertEqual(request.url, url, "url should match") - XCTAssertEqual(request.method, .post, "method should match") - assertRequestHeaders(request.headers, expected: requestHeaders) - XCTAssertEqual(request.body, requestBody, "body should match") + #expect(request.url == url) + #expect(request.method == .post) + self.expectRequestHeaders(request.headers, expected: requestHeaders) + #expect(request.body == requestBody) return .init(statusCode: responseStatus, headers: responseHeaders, body: responseBody) } let response = try await httpClient.post(url, body: requestBody, headers: requestHeaders) - XCTAssertEqual(response.statusCode, responseStatus, "statusCode should match") - assertResponseHeaders(response.headers, expected: responseHeaders) - XCTAssertEqual(response.body, responseBody, "body should match") + #expect(response.statusCode == responseStatus) + self.expectResponseHeaders(response.headers, expected: responseHeaders) + #expect(response.body == responseBody) } - func testPut() async throws { + @Test + func put() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let requestBody = Data(UUID().uuidString.utf8) @@ -87,20 +123,21 @@ final class HTTPClientTests: XCTestCase { let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in - XCTAssertEqual(request.url, url, "url should match") - XCTAssertEqual(request.method, .put, "method should match") - assertRequestHeaders(request.headers, expected: requestHeaders) - XCTAssertEqual(request.body, requestBody, "body should match") + #expect(request.url == url) + #expect(request.method == .put) + self.expectRequestHeaders(request.headers, expected: requestHeaders) + #expect(request.body == requestBody) return .init(statusCode: responseStatus, headers: responseHeaders, body: responseBody) } let response = try await httpClient.put(url, body: requestBody, headers: requestHeaders) - XCTAssertEqual(response.statusCode, responseStatus, "statusCode should match") - assertResponseHeaders(response.headers, expected: responseHeaders) - XCTAssertEqual(response.body, responseBody, "body should match") + #expect(response.statusCode == responseStatus) + self.expectResponseHeaders(response.headers, expected: responseHeaders) + #expect(response.body == responseBody) } - func testDelete() async throws { + @Test + func delete() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let responseStatus = Int.random(in: 201 ..< 500) @@ -108,19 +145,20 @@ final class HTTPClientTests: XCTestCase { let responseBody = Data(UUID().uuidString.utf8) let httpClient = HTTPClient { request, _ in - XCTAssertEqual(request.url, url, "url should match") - XCTAssertEqual(request.method, .delete, "method should match") - assertRequestHeaders(request.headers, expected: requestHeaders) + #expect(request.url == url) + #expect(request.method == .delete) + self.expectRequestHeaders(request.headers, expected: requestHeaders) return .init(statusCode: responseStatus, headers: responseHeaders, body: responseBody) } let response = try await httpClient.delete(url, headers: requestHeaders) - XCTAssertEqual(response.statusCode, responseStatus, "statusCode should match") - assertResponseHeaders(response.headers, expected: responseHeaders) - XCTAssertEqual(response.body, responseBody, "body should match") + #expect(response.statusCode == responseStatus) + self.expectResponseHeaders(response.headers, expected: responseHeaders) + #expect(response.body == responseBody) } - func testExtraHeaders() async throws { + @Test + func extraHeaders() async throws { let url = URL("http://test") let globalHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) @@ -128,7 +166,7 @@ final class HTTPClientTests: XCTestCase { let httpClient = HTTPClient(configuration: .init(requestHeaders: globalHeaders)) { request, _ in var expectedHeaders = globalHeaders expectedHeaders.merge(requestHeaders) - assertRequestHeaders(request.headers, expected: expectedHeaders) + self.expectRequestHeaders(request.headers, expected: expectedHeaders) return .init(statusCode: 200) } @@ -136,32 +174,34 @@ final class HTTPClientTests: XCTestCase { request.options.addUserAgent = true let response = try await httpClient.execute(request) - XCTAssertEqual(response.statusCode, 200, "statusCode should match") + #expect(response.statusCode == 200) } - func testUserAgent() async throws { + @Test + func userAgent() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let httpClient = HTTPClient { request, _ in - XCTAssertTrue(request.headers.contains("User-Agent"), "expecting User-Agent") - assertRequestHeaders(request.headers, expected: requestHeaders) + #expect(request.headers.contains("User-Agent"), "expecting User-Agent") + self.expectRequestHeaders(request.headers, expected: requestHeaders) return .init(statusCode: 200) } var request = HTTPClient.Request(method: .get, url: url, headers: requestHeaders) request.options.addUserAgent = true let response = try await httpClient.execute(request) - XCTAssertEqual(response.statusCode, 200, "statusCode should match") + #expect(response.statusCode == 200) } - func testNoUserAgent() async throws { + @Test + func noUserAgent() async throws { let url = URL("http://test") let requestHeaders = HTTPClientHeaders([HTTPClientHeaders.Item(name: UUID().uuidString, value: UUID().uuidString)]) let httpClient = HTTPClient { request, _ in - XCTAssertFalse(request.headers.contains("User-Agent"), "expecting User-Agent") - assertRequestHeaders(request.headers, expected: requestHeaders) + #expect(!request.headers.contains("User-Agent"), "expecting User-Agent") + self.expectRequestHeaders(request.headers, expected: requestHeaders) return .init(statusCode: 200) } @@ -169,18 +209,19 @@ final class HTTPClientTests: XCTestCase { request.options.addUserAgent = false let response = try await httpClient.execute(request) - XCTAssertEqual(response.statusCode, 200, "statusCode should match") + #expect(response.statusCode == 200) } - func testAuthorization() async throws { + @Test + func authorization() async throws { let url = URL("http://test") do { let authorization = UUID().uuidString let httpClient = HTTPClient { request, _ in - XCTAssertTrue(request.headers.contains("Authorization"), "expecting Authorization") - XCTAssertEqual(request.headers.get("Authorization").first, authorization, "expecting Authorization to match") + #expect(request.headers.contains("Authorization"), "expecting Authorization") + #expect(request.headers.get("Authorization").first == authorization) return .init(statusCode: 200) } @@ -190,12 +231,12 @@ final class HTTPClientTests: XCTestCase { } let response = try await httpClient.execute(request) - XCTAssertEqual(response.statusCode, 200, "statusCode should match") + #expect(response.statusCode == 200) } do { let httpClient = HTTPClient { request, _ in - XCTAssertFalse(request.headers.contains("Authorization"), "not expecting Authorization") + #expect(!request.headers.contains("Authorization"), "not expecting Authorization") return .init(statusCode: 200) } @@ -203,11 +244,12 @@ final class HTTPClientTests: XCTestCase { request.options.authorizationProvider = { _ in "" } let response = try await httpClient.execute(request) - XCTAssertEqual(response.statusCode, 200, "statusCode should match") + #expect(response.statusCode == 200) } } - func testValidResponseCodes() async throws { + @Test + func validResponseCodes() async throws { let statusCode = Int.random(in: 201 ..< 500) let httpClient = HTTPClient { _, _ in @@ -219,41 +261,14 @@ final class HTTPClientTests: XCTestCase { do { let response = try await httpClient.execute(request) - XCTFail("unexpected success \(response)") + Issue.record("unexpected success \(response)") } catch { - XCTAssertEqual(error as? HTTPClientError, .badResponseStatusCode(statusCode), "expected error to match") - } - } - - func testExponentialBackoff() async throws { - try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") - - let counter = SendableBox(0) - let lastCall = SendableBox() - let maxAttempts = 5 - let errorCode = Int.random(in: 500 ..< 600) - let delay = SendableTimeInterval.milliseconds(100) - - let httpClient = HTTPClient { _, _ in - let count = await counter.value! - let expectedDelta = pow(2.0, Double(count - 1)) * delay.timeInterval()! - let delta = await lastCall.value.flatMap { Date().timeIntervalSince($0) } ?? 0 - XCTAssertEqual(delta, expectedDelta, accuracy: 0.1) - - await counter.increment() - await lastCall.resetDate() - return .init(statusCode: errorCode) + #expect(error as? HTTPClientError == .badResponseStatusCode(statusCode)) } - var request = HTTPClient.Request(method: .get, url: "http://test") - request.options.retryStrategy = .exponentialBackoff(maxAttempts: maxAttempts, baseDelay: delay) - - let response = try await httpClient.execute(request) - XCTAssertEqual(response.statusCode, errorCode) - let count = await counter.value - XCTAssertEqual(count, maxAttempts, "retries should match") } - func testHostCircuitBreaker() async throws { + @Test + func hostCircuitBreaker() async throws { let maxErrors = 5 let errorCode = Int.random(in: 500 ..< 600) let age = SendableTimeInterval.seconds(5) @@ -261,7 +276,7 @@ final class HTTPClientTests: XCTestCase { let host = "http://tes-\(UUID().uuidString).com" let configuration = HTTPClientConfiguration(circuitBreakerStrategy: .hostErrors(maxErrors: maxErrors, age: age)) let httpClient = HTTPClient(configuration: configuration) { _, _ in - .init(statusCode: errorCode) + .init(statusCode: errorCode) } // make the initial errors @@ -270,10 +285,10 @@ final class HTTPClientTests: XCTestCase { for index in (0 ..< maxErrors) { let response = try await httpClient.get(URL("\(host)/\(index)/foo")) await counter.increment() - XCTAssertEqual(response.statusCode, errorCode) + #expect(response.statusCode == errorCode) } let count = await counter.value - XCTAssertEqual(count, maxErrors, "expected results count to match") + #expect(count == maxErrors) } // these should all circuit break @@ -282,19 +297,20 @@ final class HTTPClientTests: XCTestCase { for index in (0 ..< total) { do { let response = try await httpClient.get(URL("\(host)/\(index)/foo")) - XCTFail("unexpected success \(response)") + Issue.record("unexpected success \(response)") } catch { - XCTAssertEqual(error as? HTTPClientError, .circuitBreakerTriggered, "expected error to match") + #expect(error as? HTTPClientError == .circuitBreakerTriggered) } await counter.increment() } let count = await counter.value - XCTAssertEqual(count, total, "expected results count to match") + #expect(count == total) } - func testHostCircuitBreakerAging() async throws { + @Test + func hostCircuitBreakerAging() async throws { let maxErrors = 5 let errorCode = Int.random(in: 500 ..< 600) let ageInMilliseconds = 100 @@ -322,10 +338,10 @@ final class HTTPClientTests: XCTestCase { for index in (0 ..< maxErrors) { let response = try await httpClient.get(URL("\(host)/\(index)/error")) await counter.increment() - XCTAssertEqual(response.statusCode, errorCode) + #expect(response.statusCode == errorCode) } let count = await counter.value - XCTAssertEqual(count, maxErrors, "expected results count to match") + #expect(count == maxErrors) } // these should not circuit break since they are deliberately aged @@ -338,49 +354,51 @@ final class HTTPClientTests: XCTestCase { try await Task.sleep(nanoseconds: UInt64(sleepInterval.nanoseconds()!)) let response = try await httpClient.get("\(host)/\(index)/okay") count.increment() - XCTAssertEqual(response.statusCode, 200, "expected status code to match") + #expect(response.statusCode == 200) } - XCTAssertEqual(count.get(), total, "expected results count to match") + #expect(count.get() == total) } - func testHTTPClientHeaders() async throws { + @Test + func hTTPClientHeaders() async throws { var headers = HTTPClientHeaders() let items = (1 ... Int.random(in: 10 ... 20)).map { index in HTTPClientHeaders.Item(name: "header-\(index)", value: UUID().uuidString) } headers.add(items) - XCTAssertEqual(headers.count, items.count, "headers count should match") + #expect(headers.count == items.count) items.forEach { item in - XCTAssertEqual(headers.get(item.name).first, item.value, "headers value should match") + #expect(headers.get(item.name).first == item.value) } headers.add(items.first!) - XCTAssertEqual(headers.count, items.count, "headers count should match (no duplicates)") + #expect(headers.count == items.count) let name = UUID().uuidString let values = (1 ... Int.random(in: 10 ... 20)).map { "value-\($0)" } values.forEach { value in headers.add(name: name, value: value) } - XCTAssertEqual(headers.count, items.count + 1, "headers count should match (no duplicates)") - XCTAssertEqual(values, headers.get(name), "multiple headers value should match") + #expect(headers.count == items.count + 1) + #expect(values == headers.get(name)) } - func testExceedsDownloadSizeLimitProgress() async throws { + @Test + func exceedsDownloadSizeLimitProgress() async throws { let maxSize: Int64 = 50 let httpClient = HTTPClient { request, progress in switch request.method { - case .head: - return .init( - statusCode: 200, - headers: .init([.init(name: "Content-Length", value: "0")]) - ) - case .get: - try progress?(Int64(maxSize * 2), 0) - default: - XCTFail("method should match") + case .head: + return .init( + statusCode: 200, + headers: .init([.init(name: "Content-Length", value: "0")]) + ) + case .get: + try progress?(Int64(maxSize * 2), 0) + default: + Issue.record("method should match") } fatalError("unreachable") @@ -391,13 +409,14 @@ final class HTTPClientTests: XCTestCase { do { let response = try await httpClient.execute(request) - XCTFail("unexpected success \(response)") + Issue.record("unexpected success \(response)") } catch { - XCTAssertEqual(error as? HTTPClientError, .responseTooLarge(maxSize * 2), "expected error to match") + #expect(error as? HTTPClientError == .responseTooLarge(maxSize * 2)) } } - func testMaxConcurrency() async throws { + @Test + func maxConcurrency() async throws { let maxConcurrentRequests = 2 let concurrentRequests = SendableBox(0) @@ -407,7 +426,7 @@ final class HTTPClientTests: XCTestCase { await concurrentRequests.increment() if await concurrentRequests.value! > maxConcurrentRequests { - XCTFail("too many concurrent requests \(concurrentRequests), expected \(maxConcurrentRequests)") + Issue.record("too many concurrent requests \(concurrentRequests), expected \(maxConcurrentRequests)") } await concurrentRequests.decrement() @@ -428,20 +447,21 @@ final class HTTPClientTests: XCTestCase { results.append(result) } - XCTAssertEqual(results.count, total, "expected number of results to match") + #expect(results.count == total) for result in results { - XCTAssertEqual(result.statusCode, 200, "expected '200 okay' response") + #expect(result.statusCode == 200) } } } -} -private func assertRequestHeaders(_ headers: HTTPClientHeaders, expected: HTTPClientHeaders) { - let noAgent = HTTPClientHeaders(headers.filter { $0.name != "User-Agent" }) - XCTAssertEqual(noAgent, expected, "expected headers to match") -} + private func expectRequestHeaders(_ headers: HTTPClientHeaders, expected: HTTPClientHeaders, sourceLocation: SourceLocation = #_sourceLocation) { + let noAgent = HTTPClientHeaders(headers.filter { $0.name != "User-Agent" }) + #expect(noAgent == expected, sourceLocation: sourceLocation) + } + + private func expectResponseHeaders(_ headers: HTTPClientHeaders, expected: HTTPClientHeaders, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(headers == expected, sourceLocation: sourceLocation) + } -private func assertResponseHeaders(_ headers: HTTPClientHeaders, expected: HTTPClientHeaders) { - XCTAssertEqual(headers, expected, "expected headers to match") } diff --git a/Tests/BasicsTests/SQLiteBackedCacheTests.swift b/Tests/BasicsTests/SQLiteBackedCacheTests.swift index 138efa1b761..40e279ba110 100644 --- a/Tests/BasicsTests/SQLiteBackedCacheTests.swift +++ b/Tests/BasicsTests/SQLiteBackedCacheTests.swift @@ -2,25 +2,31 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation @testable import Basics import _InternalTestSupport import tsan_utils -import XCTest +import Testing -final class SQLiteBackedCacheTests: XCTestCase { - func testHappyCase() throws { +struct SQLiteBackedCacheTests { + @Test + func happyCase() throws { try testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let cache = SQLiteBackedCache(tableName: "SQLiteBackedCacheTest", path: path) - defer { XCTAssertNoThrow(try cache.close()) } + defer { + #expect(throws: Never.self) { + try cache.close() + } + } let mockData = try makeMockData(fileSystem: localFileSystem, rootPath: tmpPath) try mockData.forEach { key, value in @@ -29,38 +35,42 @@ final class SQLiteBackedCacheTests: XCTestCase { try mockData.forEach { key, _ in let result = try cache.get(key: key) - XCTAssertEqual(mockData[key], result) + #expect(mockData[key] == result) } let key = mockData.first!.key _ = try cache.put(key: key, value: "foobar", replace: false) - XCTAssertEqual(mockData[key], try cache.get(key: key)) + #expect(try cache.get(key: key) == mockData[key], "Actual is not as expected") _ = try cache.put(key: key, value: "foobar", replace: true) - XCTAssertEqual("foobar", try cache.get(key: key)) + #expect(try cache.get(key: key) == "foobar", "Actual is not as expected") try cache.remove(key: key) - XCTAssertNil(try cache.get(key: key)) + #expect(try cache.get(key: key) == nil, "Actual is not as expected") guard case .path(let cachePath) = cache.location else { - return XCTFail("invalid location \(cache.location)") + Issue.record("invalid location \(cache.location)") + return } - XCTAssertTrue(cache.fileSystem.exists(cachePath), "expected file to be written") + #expect(cache.fileSystem.exists(cachePath), "expected file to be written") } } - func testFileDeleted() throws { -#if os(Windows) - try XCTSkipIf(true, "open file cannot be deleted on Windows") -#endif - try XCTSkipIf(is_tsan_enabled()) - + @Test( + .disabled(if: (ProcessInfo.hostOperatingSystem == .windows), "open file cannot be deleted on Windows"), + .disabled(if: is_tsan_enabled(), "Disabling as tsan is enabled") + ) + func fileDeleted() throws { try testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let cache = SQLiteBackedCache(tableName: "SQLiteBackedCacheTest", path: path) - defer { XCTAssertNoThrow(try cache.close()) } + defer { + #expect(throws: Never.self) { + try cache.close() + } + } let mockData = try makeMockData(fileSystem: localFileSystem, rootPath: tmpPath) try mockData.forEach { key, value in @@ -69,40 +79,49 @@ final class SQLiteBackedCacheTests: XCTestCase { try mockData.forEach { key, _ in let result = try cache.get(key: key) - XCTAssertEqual(mockData[key], result) + #expect(mockData[key] == result) } guard case .path(let cachePath) = cache.location else { - return XCTFail("invalid location \(cache.location)") + Issue.record("invalid location \(cache.location)") + return } - XCTAssertTrue(cache.fileSystem.exists(cachePath), "expected file to exist at \(cachePath)") + #expect(cache.fileSystem.exists(cachePath), "expected file to exist at \(cachePath)") try cache.fileSystem.removeFileTree(cachePath) let key = mockData.first!.key do { let result = try cache.get(key: key) - XCTAssertNil(result) + #expect(result == nil) } do { - XCTAssertNoThrow(try cache.put(key: key, value: mockData[key]!)) + #expect(throws: Never.self) { + try cache.put(key: key, value: mockData[key]!) + } let result = try cache.get(key: key) - XCTAssertEqual(mockData[key], result) + #expect(mockData[key] == result) } - XCTAssertTrue(cache.fileSystem.exists(cachePath), "expected file to exist at \(cachePath)") + #expect(cache.fileSystem.exists(cachePath), "expected file to exist at \(cachePath)") } } - func testFileCorrupt() throws { - try XCTSkipIf(is_tsan_enabled()) + @Test( + .disabled(if: is_tsan_enabled(), "Disabling as tsan is enabled") + ) + func fileCorrupt() throws { try testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") let cache = SQLiteBackedCache(tableName: "SQLiteBackedCacheTest", path: path) - defer { XCTAssertNoThrow(try cache.close()) } + defer { + #expect(throws: Never.self) { + try cache.close() + } + } let mockData = try makeMockData(fileSystem: localFileSystem, rootPath: tmpPath) try mockData.forEach { key, value in @@ -111,36 +130,46 @@ final class SQLiteBackedCacheTests: XCTestCase { try mockData.forEach { key, _ in let result = try cache.get(key: key) - XCTAssertEqual(mockData[key], result) + #expect(mockData[key] == result) } guard case .path(let cachePath) = cache.location else { - return XCTFail("invalid location \(cache.location)") + Issue.record("invalid location \(cache.location)") + return } try cache.close() - XCTAssertTrue(cache.fileSystem.exists(cachePath), "expected file to exist at \(path)") + #expect(cache.fileSystem.exists(cachePath), "expected file to exist at \(path)") try cache.fileSystem.writeFileContents(cachePath, string: "blah") - XCTAssertThrowsError(try cache.get(key: mockData.first!.key), "expected error") { error in - XCTAssert("\(error)".contains("is not a database"), "Expected file is not a database error") + #expect { + try cache.get(key: mockData.first!.key) + } throws: { error in + return "\(error)".contains("is not a database") } - XCTAssertThrowsError(try cache.put(key: mockData.first!.key, value: mockData.first!.value), "expected error") { error in - XCTAssert("\(error)".contains("is not a database"), "Expected file is not a database error") + #expect { + try cache.put(key: mockData.first!.key, value: mockData.first!.value) + } throws: { error in + return "\(error)".contains("is not a database") } } } - func testMaxSizeNotHandled() throws { + @Test + func maxSizeNotHandled() throws { try testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") var configuration = SQLiteBackedCacheConfiguration() configuration.maxSizeInBytes = 1024 * 3 configuration.truncateWhenFull = false let cache = SQLiteBackedCache(tableName: "SQLiteBackedCacheTest", path: path, configuration: configuration) - defer { XCTAssertNoThrow(try cache.close()) } + defer { + #expect(throws: Never.self) { + try cache.close() + } + } func create() throws { let mockData = try makeMockData(fileSystem: localFileSystem, rootPath: tmpPath, count: 500) @@ -149,20 +178,28 @@ final class SQLiteBackedCacheTests: XCTestCase { } } - XCTAssertThrowsError(try create(), "expected error") { error in - XCTAssertEqual(error as? SQLite.Errors, .databaseFull, "Expected 'databaseFull' error") + #expect { + try create() + } throws: { error in + let error = try #require(error as? SQLite.Errors) + return error == .databaseFull } } } - func testMaxSizeHandled() throws { + @Test + func maxSizeHandled() throws { try testWithTemporaryDirectory { tmpPath in let path = tmpPath.appending("test.db") var configuration = SQLiteBackedCacheConfiguration() configuration.maxSizeInBytes = 1024 * 3 configuration.truncateWhenFull = true let cache = SQLiteBackedCache(tableName: "SQLiteBackedCacheTest", path: path, configuration: configuration) - defer { XCTAssertNoThrow(try cache.close()) } + defer { + #expect(throws: Never.self) { + try cache.close() + } + } var keys = [String]() let mockData = try makeMockData(fileSystem: localFileSystem, rootPath: tmpPath, count: 500) @@ -173,17 +210,18 @@ final class SQLiteBackedCacheTests: XCTestCase { do { let result = try cache.get(key: mockData.first!.key) - XCTAssertNil(result) + #expect(result == nil) } do { let result = try cache.get(key: keys.last!) - XCTAssertEqual(mockData[keys.last!], result) + #expect(mockData[keys.last!] == result) } } } - func testInitialFileCreation() throws { + @Test + func initialFileCreation() throws { try testWithTemporaryDirectory { tmpPath in let paths = [ tmpPath.appending("foo", "test.db"), @@ -194,9 +232,13 @@ final class SQLiteBackedCacheTests: XCTestCase { for path in paths { let cache = SQLiteBackedCache(tableName: "SQLiteBackedCacheTest", path: path) // Put an entry to ensure the file is created. - XCTAssertNoThrow(try cache.put(key: "foo", value: "bar")) - XCTAssertNoThrow(try cache.close()) - XCTAssertTrue(localFileSystem.exists(path), "expected file to be created at \(path)") + #expect(throws: Never.self) { + try cache.put(key: "foo", value: "bar") + } + #expect(throws: Never.self) { + try cache.close() + } + #expect(localFileSystem.exists(path), "expected file to be created at \(path)") } } } diff --git a/Tests/BuildTests/BuildPlanTraversalTests.swift b/Tests/BuildTests/BuildPlanTraversalTests.swift index 2b898cabd76..a4b2720ce83 100644 --- a/Tests/BuildTests/BuildPlanTraversalTests.swift +++ b/Tests/BuildTests/BuildPlanTraversalTests.swift @@ -9,8 +9,9 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation -import XCTest +import Testing import Basics @testable import Build @@ -20,7 +21,7 @@ import PackageGraph import _InternalTestSupport import SPMBuildCore -final class BuildPlanTraversalTests: XCTestCase { +struct BuildPlanTraversalTests { typealias Dest = BuildParameters.Destination struct Result { @@ -61,7 +62,8 @@ final class BuildPlanTraversalTests: XCTestCase { }.sorted() } - func testTrivialTraversal() async throws { + @Test + func trivialTraversal() async throws { let destinationTriple = Triple.arm64Linux let toolsTriple = Triple.x86_64MacOS @@ -85,12 +87,13 @@ final class BuildPlanTraversalTests: XCTestCase { results.append(Result(parent: $1, module: $0)) } - XCTAssertEqual(self.getParents(in: results, for: "app"), []) - XCTAssertEqual(self.getParents(in: results, for: "lib"), ["app", "test"]) - XCTAssertEqual(self.getParents(in: results, for: "test"), []) + #expect(self.getParents(in: results, for: "app") == []) + #expect(self.getParents(in: results, for: "lib") == ["app", "test"]) + #expect(self.getParents(in: results, for: "test") == []) } - func testTraversalWithDifferentDestinations() async throws { + @Test + func traversalWithDifferentDestinations() async throws { let destinationTriple = Triple.arm64Linux let toolsTriple = Triple.x86_64MacOS @@ -114,13 +117,14 @@ final class BuildPlanTraversalTests: XCTestCase { results.append(Result(parent: $1, module: $0)) } - XCTAssertEqual(self.getParents(in: results, for: "MMIO"), ["HAL"]) - XCTAssertEqual(self.getParents(in: results, for: "SwiftSyntax", destination: .host), ["MMIOMacros"]) - XCTAssertEqual(self.getParents(in: results, for: "HAL", destination: .target), ["Core", "HALTests"]) - XCTAssertEqual(self.getParents(in: results, for: "HAL", destination: .host), []) + #expect(self.getParents(in: results, for: "MMIO") == ["HAL"]) + #expect(self.getParents(in: results, for: "SwiftSyntax", destination: .host) == ["MMIOMacros"]) + #expect(self.getParents(in: results, for: "HAL", destination: .target) == ["Core", "HALTests"]) + #expect(self.getParents(in: results, for: "HAL", destination: .host) == []) } - func testRecursiveDependencyTraversal() async throws { + @Test + func recursiveDependencyTraversal() async throws { let destinationTriple = Triple.arm64Linux let toolsTriple = Triple.x86_64MacOS @@ -139,70 +143,74 @@ final class BuildPlanTraversalTests: XCTestCase { observabilityScope: scope ) - let mmioModule = try XCTUnwrap(plan.description(for: graph.module(for: "MMIO")!, context: .target)) + let mmioModule = try #require(plan.description(for: graph.module(for: "MMIO")!, context: .target)) var moduleDependencies: [(ResolvedModule, Dest, Build.ModuleBuildDescription?)] = [] plan.traverseDependencies(of: mmioModule) { product, destination, description in - XCTAssertEqual(product.name, "SwiftSyntax") - XCTAssertEqual(destination, .host) - XCTAssertNil(description) + #expect(product.name == "SwiftSyntax") + #expect(destination == .host) + #expect(description == nil) } onModule: { module, destination, description in moduleDependencies.append((module, destination, description)) } - XCTAssertEqual(moduleDependencies.count, 2) + #expect(moduleDependencies.count == 2) // The ordering is guaranteed by the traversal - XCTAssertEqual(moduleDependencies[0].0.name, "MMIOMacros") - XCTAssertEqual(moduleDependencies[1].0.name, "SwiftSyntax") + #expect(moduleDependencies[0].0.name == "MMIOMacros") + #expect(moduleDependencies[1].0.name == "SwiftSyntax") for index in 0 ..< moduleDependencies.count { - XCTAssertEqual(moduleDependencies[index].1, .host) - XCTAssertNotNil(moduleDependencies[index].2) + #expect(moduleDependencies[index].1 == .host) + #expect(moduleDependencies[index].2 != nil) } let directDependencies = mmioModule.dependencies(using: plan) - XCTAssertEqual(directDependencies.count, 1) + #expect(directDependencies.count == 1) - let dependency = try XCTUnwrap(directDependencies.first) + let dependency = try #require(directDependencies.first) if case .module(let module, let description) = dependency { - XCTAssertEqual(module.name, "MMIOMacros") - try XCTAssertEqual(XCTUnwrap(description).destination, .host) + #expect(module.name == "MMIOMacros") + let desc = try #require(description) + #expect(desc.destination == .host) } else { - XCTFail("Expected MMIOMacros module") + Issue.record("Expected MMIOMacros module") } let dependencies = mmioModule.recursiveDependencies(using: plan) - XCTAssertEqual(dependencies.count, 3) + #expect(dependencies.count == 3) // MMIOMacros (module) -> SwiftSyntax (product) -> SwiftSyntax (module) if case .module(let module, let description) = dependencies[0] { - XCTAssertEqual(module.name, "MMIOMacros") - try XCTAssertEqual(XCTUnwrap(description).destination, .host) + #expect(module.name == "MMIOMacros") + let desc = try #require(description) + #expect(desc.destination == .host) } else { - XCTFail("Expected MMIOMacros module") + Issue.record("Expected MMIOMacros module") } if case .product(let product, let description) = dependencies[1] { - XCTAssertEqual(product.name, "SwiftSyntax") - XCTAssertNil(description) + #expect(product.name == "SwiftSyntax") + #expect(description == nil) } else { - XCTFail("Expected SwiftSyntax product") + Issue.record("Expected SwiftSyntax product") } if case .module(let module, let description) = dependencies[2] { - XCTAssertEqual(module.name, "SwiftSyntax") - try XCTAssertEqual(XCTUnwrap(description).destination, .host) + #expect(module.name == "SwiftSyntax") + let desc = try #require(description) + #expect(desc.destination == .host) } else { - XCTFail("Expected SwiftSyntax module") + Issue.record("Expected SwiftSyntax module") } } - func testRecursiveDependencyTraversalWithDuplicates() async throws { + @Test + func recursiveDependencyTraversalWithDuplicates() async throws { let destinationTriple = Triple.arm64Linux let toolsTriple = Triple.x86_64MacOS @@ -221,10 +229,10 @@ final class BuildPlanTraversalTests: XCTestCase { observabilityScope: scope ) - let testModule = try XCTUnwrap(plan.description(for: graph.module(for: "MMIOMacrosTests")!, context: .host)) + let testModule = try #require(plan.description(for: graph.module(for: "MMIOMacrosTests")!, context: .host)) let dependencies = testModule.recursiveDependencies(using: plan) - XCTAssertEqual(dependencies.count, 9) + #expect(dependencies.count == 9) struct ModuleResult: Hashable { let module: ResolvedModule @@ -234,12 +242,10 @@ final class BuildPlanTraversalTests: XCTestCase { var uniqueModules = Set() for dependency in dependencies { if case .module(let module, let description) = dependency { - XCTAssertNotNil(description) - XCTAssertEqual(description!.destination, .host) - XCTAssertTrue( - uniqueModules.insert(.init(module: module, destination: description!.destination)) - .inserted - ) + #expect(description != nil) + #expect(description!.destination == .host) + #expect(uniqueModules.insert(.init(module: module, destination: description!.destination)) + .inserted) } } } diff --git a/Tests/BuildTests/LLBuildManifestBuilderTests.swift b/Tests/BuildTests/LLBuildManifestBuilderTests.swift index 7fa466a4545..f2bf17e5663 100644 --- a/Tests/BuildTests/LLBuildManifestBuilderTests.swift +++ b/Tests/BuildTests/LLBuildManifestBuilderTests.swift @@ -9,6 +9,7 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation import Basics @testable import Build @@ -24,14 +25,15 @@ import _InternalBuildTestSupport @_spi(SwiftPMInternal) import _InternalTestSupport -import XCTest +import Testing -final class LLBuildManifestBuilderTests: XCTestCase { - func testCreateProductCommand() async throws { +struct LLBuildManifestBuilderTests { + @Test + func createProductCommand() async throws { let pkg = AbsolutePath("/pkg") let fs = InMemoryFileSystem( emptyFiles: - pkg.appending(components: "Sources", "exe", "main.swift").pathString + pkg.appending(components: "Sources", "exe", "main.swift").pathString ) let observability = ObservabilitySystem.makeForTesting() @@ -77,10 +79,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { "C.exe-\(plan.destinationBuildParameters.triple)-release.exe", ] - XCTAssertEqual( - llbuild.manifest.commands.map(\.key).sorted(), - basicReleaseCommandNames.sorted() - ) + #expect(llbuild.manifest.commands.map(\.key).sorted() == basicReleaseCommandNames.sorted()) // macOS, debug build @@ -107,33 +106,24 @@ final class LLBuildManifestBuilderTests: XCTestCase { "C.exe-\(plan.destinationBuildParameters.triple)-debug.exe", ] - XCTAssertEqual( - llbuild.manifest.commands.map(\.key).sorted(), - (basicDebugCommandNames + [ - AbsolutePath("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe-entitlement.plist").pathString, - entitlementsCommandName, - ]).sorted() - ) + #expect(llbuild.manifest.commands.map(\.key).sorted() == (basicDebugCommandNames + [ + AbsolutePath("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe-entitlement.plist").pathString, + entitlementsCommandName, + ]).sorted()) - guard let entitlementsCommand = llbuild.manifest.commands[entitlementsCommandName]?.tool as? ShellTool else { - XCTFail("unexpected entitlements command type") - return - } - - XCTAssertEqual( - entitlementsCommand.inputs, - [ - .file("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe", isMutated: true), - .file("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe-entitlement.plist"), - ] - ) - XCTAssertEqual( - entitlementsCommand.outputs, - [ - .virtual("exe-\(plan.destinationBuildParameters.triple)-debug.exe-CodeSigning"), - ] + let entitlementsCommand = try #require( + llbuild.manifest.commands[entitlementsCommandName]?.tool as? ShellTool, + "unexpected entitlements command type" ) + #expect(entitlementsCommand.inputs == [ + .file("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe", isMutated: true), + .file("/path/to/build/\(plan.destinationBuildParameters.triple)/debug/exe-entitlement.plist"), + ]) + #expect(entitlementsCommand.outputs == [ + .virtual("exe-\(plan.destinationBuildParameters.triple)-debug.exe-CodeSigning"), + ]) + // Linux, release build plan = try await mockBuildPlan( @@ -158,10 +148,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { "C.exe-\(plan.destinationBuildParameters.triple)-release.exe", ] - XCTAssertEqual( - llbuild.manifest.commands.map(\.key).sorted(), - basicReleaseCommandNames.sorted() - ) + #expect(llbuild.manifest.commands.map(\.key).sorted() == basicReleaseCommandNames.sorted()) // Linux, debug build @@ -187,14 +174,12 @@ final class LLBuildManifestBuilderTests: XCTestCase { "C.exe-\(plan.destinationBuildParameters.triple)-debug.exe", ] - XCTAssertEqual( - llbuild.manifest.commands.map(\.key).sorted(), - basicDebugCommandNames.sorted() - ) + #expect(llbuild.manifest.commands.map(\.key).sorted() == basicDebugCommandNames.sorted()) } - + /// Verifies that two modules with the same name but different triples don't share same build manifest keys. - func testToolsBuildTriple() async throws { + @Test + func toolsBuildTriple() async throws { let (graph, fs, scope) = try macrosPackageGraph() let productsTriple = Triple.x86_64MacOS let toolsTriple = Triple.arm64Linux @@ -217,8 +202,8 @@ final class LLBuildManifestBuilderTests: XCTestCase { let builder = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: scope) let manifest = try builder.generateManifest(at: "/manifest") - XCTAssertNotNil(manifest.commands["C.SwiftSyntax-aarch64-unknown-linux-gnu-debug-tool.module"]) + #expect(manifest.commands["C.SwiftSyntax-aarch64-unknown-linux-gnu-debug-tool.module"] != nil) // Ensure that Objects.LinkFileList is -tool suffixed. - XCTAssertNotNil(manifest.commands[AbsolutePath("/path/to/build/aarch64-unknown-linux-gnu/debug/MMIOMacros-tool.product/Objects.LinkFileList").pathString]) + #expect(manifest.commands[AbsolutePath("/path/to/build/aarch64-unknown-linux-gnu/debug/MMIOMacros-tool.product/Objects.LinkFileList").pathString] != nil) } } diff --git a/Tests/BuildTests/SwiftCompilerOutputParserTests.swift b/Tests/BuildTests/SwiftCompilerOutputParserTests.swift index 89990daf851..f8fc46614f3 100644 --- a/Tests/BuildTests/SwiftCompilerOutputParserTests.swift +++ b/Tests/BuildTests/SwiftCompilerOutputParserTests.swift @@ -9,12 +9,14 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation -import XCTest +import Testing import Build -class SwiftCompilerOutputParserTests: XCTestCase { - func testParse() throws { +struct SwiftCompilerOutputParserTests { + @Test + func parse() throws { let delegate = MockSwiftCompilerOutputParserDelegate() let parser = SwiftCompilerOutputParser(targetName: "dummy", delegate: delegate) @@ -147,7 +149,8 @@ class SwiftCompilerOutputParserTests: XCTestCase { ], errorDescription: nil) } - func testRawTextTransformsIntoUnknown() { + @Test + func rawTextTransformsIntoUnknown() { let delegate = MockSwiftCompilerOutputParserDelegate() let parser = SwiftCompilerOutputParser(targetName: "dummy", delegate: delegate) @@ -174,7 +177,8 @@ class SwiftCompilerOutputParserTests: XCTestCase { ], errorDescription: nil) } - func testSignalledStopsParsing() { + @Test + func signalledStopsParsing() { let delegate = MockSwiftCompilerOutputParserDelegate() let parser = SwiftCompilerOutputParser(targetName: "dummy", delegate: delegate) @@ -222,12 +226,11 @@ class MockSwiftCompilerOutputParserDelegate: SwiftCompilerOutputParserDelegate { func assert( messages: [SwiftCompilerMessage], errorDescription: String?, - file: StaticString = #file, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) { - XCTAssertEqual(messages, self.messages, file: file, line: line) + #expect(messages == self.messages, sourceLocation: sourceLocation) let errorReason = (self.error as? LocalizedError)?.errorDescription ?? error?.localizedDescription - XCTAssertEqual(errorDescription, errorReason, file: file, line: line) + #expect(errorDescription == errorReason, sourceLocation: sourceLocation) self.messages = [] self.error = nil } diff --git a/Tests/BuildTests/WindowsBuildPlanTests.swift b/Tests/BuildTests/WindowsBuildPlanTests.swift index d62fa83d442..e5f3ea56a0a 100644 --- a/Tests/BuildTests/WindowsBuildPlanTests.swift +++ b/Tests/BuildTests/WindowsBuildPlanTests.swift @@ -9,8 +9,9 @@ // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +import Foundation -import XCTest +import Testing import Basics @testable import Build @@ -20,12 +21,19 @@ import _InternalTestSupport @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph -final class WindowsBuildPlanTests: XCTestCase { +struct WindowsBuildPlanTests { // Tests that our build plan is build correctly to handle separation // of object files that export symbols and ones that don't and to ensure // DLL products pick up the right ones. - func doTest(triple: Triple) async throws { + @Test( + arguments: [ + (triple: Triple.x86_64Windows, label: "x86_64-unknown-windows-msvc"), + (triple: Triple.x86_64MacOS, label: "x86_64-apple-macosx"), + (triple: Triple.x86_64Linux, label: "x86_64-unknown-linux-gnu"), + ] + ) + func validateTriple(triple: Triple, label: String) async throws { let fs = InMemoryFileSystem(emptyFiles: [ "/libPkg/Sources/coreLib/coreLib.swift", "/libPkg/Sources/dllLib/dllLib.swift", @@ -70,18 +78,6 @@ final class WindowsBuildPlanTests: XCTestCase { observabilityScope: observability.topScope ) - let label: String - switch triple { - case Triple.x86_64Windows: - label = "x86_64-unknown-windows-msvc" - case Triple.x86_64MacOS: - label = "x86_64-apple-macosx" - case Triple.x86_64Linux: - label = "x86_64-unknown-linux-gnu" - default: - label = "fixme" - } - let plan = try await BuildPlan( destinationBuildParameters: mockBuildParameters( destination: .target, @@ -105,28 +101,14 @@ final class WindowsBuildPlanTests: XCTestCase { let commands = llbuild.manifest.commands func hasStatic(_ name: String) throws -> Bool { - let tool = try XCTUnwrap(commands[name]?.tool as? SwiftCompilerTool) + let tool = try #require(commands[name]?.tool as? SwiftCompilerTool) return tool.otherArguments.contains("-static") } - XCTAssertEqual(try hasStatic("C.coreLib-\(label)-debug.module"), triple.isWindows(), label) - XCTAssertEqual(try hasStatic("C.dllLib-\(label)-debug.module"), false, label) - XCTAssertEqual(try hasStatic("C.staticLib-\(label)-debug.module"), triple.isWindows(), label) - XCTAssertEqual(try hasStatic("C.objectLib-\(label)-debug.module"), triple.isWindows(), label) - XCTAssertEqual(try hasStatic("C.exe-\(label)-debug.module"), triple.isWindows(), label) - } - - func testWindows() async throws { - try await doTest(triple: .x86_64Windows) - } - - // Make sure we didn't mess up macOS - func testMacOS() async throws { - try await doTest(triple: .x86_64MacOS) - } - - // Make sure we didn't mess up linux - func testLinux() async throws { - try await doTest(triple: .x86_64Linux) + #expect(try hasStatic("C.coreLib-\(label)-debug.module") == triple.isWindows(), "\(label)") + #expect(try hasStatic("C.dllLib-\(label)-debug.module") == false, "\(label)") + #expect(try hasStatic("C.staticLib-\(label)-debug.module") == triple.isWindows(), "\(label)") + #expect(try hasStatic("C.objectLib-\(label)-debug.module") == triple.isWindows(), "\(label)") + #expect(try hasStatic("C.exe-\(label)-debug.module") == triple.isWindows(), "\(label)") } } diff --git a/Tests/_AsyncFileSystemTests/AsyncFileSystemTests.swift b/Tests/_AsyncFileSystemTests/AsyncFileSystemTests.swift index d5ca8916871..e69338ebf41 100644 --- a/Tests/_AsyncFileSystemTests/AsyncFileSystemTests.swift +++ b/Tests/_AsyncFileSystemTests/AsyncFileSystemTests.swift @@ -1,3 +1,4 @@ +import Foundation /* This source file is part of the Swift.org open source project @@ -6,62 +7,64 @@ See http://swift.org/LICENSE.txt for license information See http://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ + */ import _AsyncFileSystem import _InternalTestSupport -import XCTest +import Testing import struct SystemPackage.FilePath -final class AsyncFileSystemTests: XCTestCase { - func testMockFileSystem() async throws { +struct AsyncFileSystemTests { + @Test + func mockFileSystem() async throws { let fs = MockFileSystem() let mockPath: FilePath = "/foo/bar" - await XCTAssertAsyncFalse(await fs.exists(mockPath)) + #expect(await !fs.exists(mockPath)) let mockContent = "baz".utf8 try await fs.write(mockPath, bytes: mockContent) - await XCTAssertAsyncTrue(await fs.exists(mockPath)) + #expect(await fs.exists(mockPath)) // Test overwriting try await fs.write(mockPath, bytes: mockContent) - await XCTAssertAsyncTrue(await fs.exists(mockPath)) + #expect(await fs.exists(mockPath)) let bytes = try await fs.withOpenReadableFile(mockPath) { fileHandle in try await fileHandle.read().reduce(into: []) { $0.append(contentsOf: $1) } } - XCTAssertEqual(bytes, Array(mockContent)) + #expect(bytes == Array(mockContent)) } - func testOSFileSystem() async throws { + @Test + func oSFileSystem() async throws { try await testWithTemporaryDirectory { tmpDir in let fs = OSFileSystem() let mockPath = FilePath(tmpDir.appending("foo").pathString) - await XCTAssertAsyncFalse(await fs.exists(mockPath)) + #expect(await !fs.exists(mockPath)) let mockContent = "baz".utf8 try await fs.write(mockPath, bytes: mockContent) - await XCTAssertAsyncTrue(await fs.exists(mockPath)) + #expect(await fs.exists(mockPath)) // Test overwriting try await fs.write(mockPath, bytes: mockContent) - await XCTAssertAsyncTrue(await fs.exists(mockPath)) + #expect(await fs.exists(mockPath)) let bytes = try await fs.withOpenReadableFile(mockPath) { fileHandle in try await fileHandle.read().reduce(into: []) { $0.append(contentsOf: $1) } } - XCTAssertEqual(bytes, Array(mockContent)) + #expect(bytes == Array(mockContent)) } } } From c28a8917fabcfbdea663d4214d36cb6d014311c8 Mon Sep 17 00:00:00 2001 From: Paulo Mattos Date: Thu, 8 May 2025 17:42:58 -0700 Subject: [PATCH 87/99] Minor improvement in SwiftBuildSupport helpers (#8631) ### Motivation: Just a minor cleanup in `SwiftBuildSupport/SwiftBuildSystem.swift` file. ### Modifications: Move the `extension PIFBuilderParameters` helper to `PIFBuilder.swift` instead. --- Sources/SwiftBuildSupport/PIFBuilder.swift | 17 +++++++++++++++++ .../SwiftBuildSupport/SwiftBuildSystem.swift | 15 --------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index c65e7ce2454..7952de26642 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -458,3 +458,20 @@ extension PIFGenerationError: CustomStringConvertible { } } } + +// MARK: - Helpers + +extension PIFBuilderParameters { + init(_ buildParameters: BuildParameters, supportedSwiftVersions: [SwiftLanguageVersion]) { + self.init( + triple: buildParameters.triple, + isPackageAccessModifierSupported: buildParameters.driverParameters.isPackageAccessModifierSupported, + enableTestability: buildParameters.enableTestability, + shouldCreateDylibForDynamicProducts: buildParameters.shouldCreateDylibForDynamicProducts, + toolchainLibDir: (try? buildParameters.toolchain.toolchainLibDir) ?? .root, + pkgConfigDirectories: buildParameters.pkgConfigDirectories, + sdkRootPath: buildParameters.toolchain.sdkRootPath, + supportedSwiftVersions: supportedSwiftVersions + ) + } +} diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 79e8b6567ea..704259808a3 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -539,21 +539,6 @@ extension String { } } -extension PIFBuilderParameters { - public init(_ buildParameters: BuildParameters, supportedSwiftVersions: [SwiftLanguageVersion]) { - self.init( - triple: buildParameters.triple, - isPackageAccessModifierSupported: buildParameters.driverParameters.isPackageAccessModifierSupported, - enableTestability: buildParameters.enableTestability, - shouldCreateDylibForDynamicProducts: buildParameters.shouldCreateDylibForDynamicProducts, - toolchainLibDir: (try? buildParameters.toolchain.toolchainLibDir) ?? .root, - pkgConfigDirectories: buildParameters.pkgConfigDirectories, - sdkRootPath: buildParameters.toolchain.sdkRootPath, - supportedSwiftVersions: supportedSwiftVersions - ) - } -} - extension Basics.Diagnostic.Severity { var isVerbose: Bool { self <= .info From a4cd504baee4572770cdc92e4a7241b376421df8 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Thu, 8 May 2025 16:26:48 -0700 Subject: [PATCH 88/99] Remove previous maintainers from CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index f0e9ef3869b..97e9881cab0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,4 +26,4 @@ Sources/XCBuildSupport/* @jakepetroules -* @bnbarham @MaxDesiatov @jakepetroules @xedin @dschaefer2 @shawnhyam @bripeticca @plemarquand @owenv @bkhouri @cmcgee1024 @daveyc123 +* @jakepetroules @dschaefer2 @shawnhyam @bripeticca @plemarquand @owenv @bkhouri @cmcgee1024 @daveyc123 From 90517562aa8e1335ef9be41545c6a8d378464fc9 Mon Sep 17 00:00:00 2001 From: Yury Yurevich Date: Fri, 9 May 2025 07:13:32 -0700 Subject: [PATCH 89/99] Add more context for validation errors for target properties. (#8612) Add more context for validation errors for target properties. ### Motivation: I came across a post https://forums.swift.org/t/getting-error-on-package-resolve/75419 which reports the error message to be not clear. I thought I could clarify the message. ``` error: 'xerr': target 'Penny' contains a value for disallowed property 'settings' ``` ### Modifications: I've updated the message for the error, so it includes more details to narrow down what is the culprit: - highlight that the property depends on the target type - show the value of the property, so that there's a hint what line in the Package.swift lead to the error (for example, linkerSettings or cxxSettings) ### Result: Before: ``` error: 'xerr': target 'Penny' contains a value for disallowed property 'settings' ``` After: ``` error: 'xerr': target 'Penny' is assigned a property 'settings' which is not accepted for the binary target type. The current value of the property has the following representation: [PackageModel.TargetBuildSettingDescription.Setting( tool: PackageModel.TargetBuildSettingDescription.Tool.linker, kind: PackageModel.TargetBuildSettingDescription.Kind.linkedFramework("AVFoundation"), condition: nil)]. ``` --- .../Manifest/TargetDescription.swift | 277 +++++++++++++++--- .../PD_5_3_LoadingTests.swift | 33 ++- .../PD_5_6_LoadingTests.swift | 24 ++ 3 files changed, 290 insertions(+), 44 deletions(-) diff --git a/Sources/PackageModel/Manifest/TargetDescription.swift b/Sources/PackageModel/Manifest/TargetDescription.swift index e546e44583a..d336bedb60a 100644 --- a/Sources/PackageModel/Manifest/TargetDescription.swift +++ b/Sources/PackageModel/Manifest/TargetDescription.swift @@ -212,51 +212,237 @@ public struct TargetDescription: Hashable, Encodable, Sendable { checksum: String? = nil, pluginUsages: [PluginUsage]? = nil ) throws { + let targetType = String(describing: type) switch type { case .regular, .executable, .test: - if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") } - if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") } - if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") } - if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } - if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") } + if url != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "url", + value: url ?? "" + ) } + if pkgConfig != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pkgConfig", + value: pkgConfig ?? "" + ) } + if providers != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "providers", + value: String(describing: providers!) + ) } + if pluginCapability != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginCapability", + value: String(describing: pluginCapability!) + ) } + if checksum != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "checksum", + value: checksum ?? "" + ) } case .system: - if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "dependencies") } - if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "exclude") } - if sources != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "sources") } - if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") } - if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") } - if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } - if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") } - if checksum != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "checksum") } - if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") } + if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "dependencies", + value: String(describing: dependencies) + ) } + if !exclude.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "exclude", + value: String(describing: exclude) + ) } + if sources != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "sources", + value: String(describing: sources!) + ) } + if !resources.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "resources", + value: String(describing: resources) + ) } + if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "publicHeadersPath", + value: publicHeadersPath ?? "" + ) } + if pluginCapability != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginCapability", + value: String(describing: pluginCapability!) + ) } + if !settings.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "settings", + value: String(describing: settings) + ) } + if checksum != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "checksum", + value: checksum ?? "" + ) } + if pluginUsages != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginUsages", + value: String(describing: pluginUsages!) + ) } case .binary: if path == nil && url == nil { throw Error.binaryTargetRequiresEitherPathOrURL(targetName: name) } - if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "dependencies") } - if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "exclude") } - if sources != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "sources") } - if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") } - if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") } - if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") } - if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") } - if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } - if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") } - if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") } + if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "dependencies", + value: String(describing: dependencies) + ) } + if !exclude.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "exclude", + value: String(describing: exclude) + ) } + if sources != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "sources", + value: String(describing: sources!) + ) } + if !resources.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "resources", + value: String(describing: resources) + ) } + if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "publicHeadersPath", + value: publicHeadersPath ?? "" + ) } + if pkgConfig != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pkgConfig", + value: pkgConfig ?? "" + ) } + if providers != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "providers", + value: String(describing: providers!) + ) } + if pluginCapability != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginCapability", + value: String(describing: pluginCapability!) + ) } + if !settings.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "settings", + value: String(describing: settings) + ) } + if pluginUsages != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginUsages", + value: String(describing: pluginUsages!) + ) } case .plugin: - if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") } - if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") } - if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") } - if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") } - if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") } - if pluginCapability == nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } - if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") } - if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") } + if pluginCapability == nil { throw Error.pluginTargetRequiresPluginCapability(targetName: name) } + if url != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "url", + value: url ?? "" + ) } + if !resources.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "resources", + value: String(describing: resources) + ) } + if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "publicHeadersPath", + value: publicHeadersPath ?? "" + ) } + if pkgConfig != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pkgConfig", + value: pkgConfig ?? "" + ) } + if providers != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "providers", + value: String(describing: providers!) + ) } + if !settings.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "settings", + value: String(describing: settings) + ) } + if pluginUsages != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginUsages", + value: String(describing: pluginUsages!) + ) } case .macro: - if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") } - if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") } - if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") } - if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") } - if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") } - if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } + if url != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "url", + value: url ?? "" + ) } + if !resources.isEmpty { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "resources", + value: String(describing: resources) + ) } + if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "publicHeadersPath", + value: publicHeadersPath ?? "" + ) } + if pkgConfig != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pkgConfig", + value: pkgConfig ?? "" + ) } + if providers != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "providers", + value: String(describing: providers!) + ) } + if pluginCapability != nil { throw Error.disallowedPropertyInTarget( + targetName: name, + targetType: targetType, + propertyName: "pluginCapability", + value: String(describing: pluginCapability!) + ) } } self.name = name @@ -404,14 +590,19 @@ import protocol Foundation.LocalizedError private enum Error: LocalizedError, Equatable { case binaryTargetRequiresEitherPathOrURL(targetName: String) - case disallowedPropertyInTarget(targetName: String, propertyName: String) - + case pluginTargetRequiresPluginCapability(targetName: String) + case disallowedPropertyInTarget(targetName: String, targetType: String, propertyName: String, value: String) + var errorDescription: String? { switch self { case .binaryTargetRequiresEitherPathOrURL(let targetName): - return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts" - case .disallowedPropertyInTarget(let targetName, let propertyName): - return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'" + "binary target '\(targetName)' must define either path or URL for its artifacts" + case .pluginTargetRequiresPluginCapability(let targetName): + "plugin target '\(targetName)' must define a plugin capability" + case .disallowedPropertyInTarget(let targetName, let targetType, let propertyName, let value): + "target '\(targetName)' is assigned a property '\(propertyName)' which is not accepted " + + "for the \(targetType) target type. The current property value has " + + "the following representation: \(value)." } } } diff --git a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift index 27bbab812d4..0c6e35a4d7f 100644 --- a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift @@ -158,7 +158,38 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { let observability = ObservabilitySystem.makeForTesting() await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in - XCTAssertEqual(error.localizedDescription, "target 'Foo' contains a value for disallowed property 'settings'") + XCTAssertEqual(error.localizedDescription, + "target 'Foo' is assigned a property 'settings' which is not accepted for the binary target type. " + + "The current property value has the following representation: " + + "[PackageModel.TargetBuildSettingDescription.Setting(" + + "tool: PackageModel.TargetBuildSettingDescription.Tool.linker, " + + "kind: PackageModel.TargetBuildSettingDescription.Kind.linkedFramework(\"AVFoundation\"), " + + "condition: nil)].") + } + } + + func testBinaryTargetRequiresPathOrUrl() async throws { + let content = """ + import PackageDescription + var fwBinaryTarget = Target.binaryTarget( + name: "nickel", + url: "https://example.com/foo.git", + checksum: "bee" + ) + fwBinaryTarget.url = nil + let package = Package(name: "foo", targets: [fwBinaryTarget]) + """ + + let observability = ObservabilitySystem.makeForTesting() + await XCTAssertAsyncThrowsError( + try await loadAndValidateManifest( + content, observabilityScope: observability.topScope + ), "expected error" + ) { error in + XCTAssertEqual( + error.localizedDescription, + "binary target 'nickel' must define either path or URL for its artifacts" + ) } } diff --git a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift index ff7d26a11d0..32b31e47301 100644 --- a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift @@ -98,6 +98,30 @@ final class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.targets[0].pluginCapability, .buildTool) } + func testPluginTargetRequiresPluginCapability() async throws { + let content = """ + import PackageDescription + var fwPluginTarget = Target.plugin( + name: "quarter", + capability: .buildTool + ) + fwPluginTarget.pluginCapability = nil + let package = Package(name: "foo", targets: [fwPluginTarget]) + """ + + let observability = ObservabilitySystem.makeForTesting() + await XCTAssertAsyncThrowsError( + try await loadAndValidateManifest( + content, observabilityScope: observability.topScope + ), "expected error" + ) { error in + XCTAssertEqual( + error.localizedDescription, + "plugin target 'quarter' must define a plugin capability" + ) + } + } + func testPluginTargetCustomization() async throws { let content = """ import PackageDescription From 3448da69bdbe4a71e140dc0a83780b78e85020f4 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 9 May 2025 15:25:05 +0100 Subject: [PATCH 90/99] Support Swift SDKs w/ many metadata files in same directory (#8638) ### Motivation: Swift SDK artifact bundle for WASI now supports both embedded and non-embedded modes. Majority of their content is overlapping, thus to avoid duplication they live in the same source tree. Currently, SwiftPM requires that Swift SDKs are located in separate directories, with `swift-sdk.json` metadata at the root of each directory. ### Modification: SwiftPM can now parse Swift SDK metadata that points directly to metadata JSON files instead of their root directories. ### Result: We can avoid changing file system layout in the artifact bundles for WASI and just place sibling "nonembedded.json" and "embedded.json" Swift SDK metadata files in the same directory in their existing layout. The change is transparent to Swift SDK users and is an optional and incremental addition for Swift SDK authors. --- .../SwiftSDKs/SwiftSDKBundleStore.swift | 8 +- .../SwiftSDKBundleTests.swift | 89 ++++++++++++++++--- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift index 7810717e683..06ef11f865c 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift @@ -370,9 +370,13 @@ public final class SwiftSDKBundleStore { var variants = [SwiftSDKBundle.Variant]() for variantMetadata in artifactMetadata.variants { - let variantConfigurationPath = bundlePath + var variantConfigurationPath = bundlePath .appending(variantMetadata.path) - .appending("swift-sdk.json") + + if variantConfigurationPath.extension != ".json" && + self.fileSystem.isDirectory(variantConfigurationPath) { + variantConfigurationPath = variantConfigurationPath.appending("swift-sdk.json") + } guard self.fileSystem.exists(variantConfigurationPath) else { self.observabilityScope.emit( diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index 0a65b116675..5a69093e5cf 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023-2024 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2025 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -27,20 +27,26 @@ private let targetTriple = try! Triple("aarch64-unknown-linux") private let jsonEncoder = JSONEncoder() private func generateBundleFiles(bundle: MockBundle) throws -> [(String, ByteString)] { - try [ + return try [ ( "\(bundle.path)/info.json", ByteString(json: """ { "artifacts" : { \(bundle.artifacts.map { - """ + let path = if let metadataPath = $0.metadataPath { + metadataPath.pathString + } else { + "\($0.id)/\(targetTriple.triple)" + } + + return """ "\($0.id)" : { "type" : "swiftSDK", "version" : "0.0.1", "variants" : [ { - "path" : "\($0.id)/\(targetTriple.triple)", + "path" : "\(path)", "supportedTriples" : \($0.supportedTriples.map(\.tripleString)) } ] @@ -55,14 +61,25 @@ private func generateBundleFiles(bundle: MockBundle) throws -> [(String, ByteStr ), ] + bundle.artifacts.map { - ( - "\(bundle.path)/\($0.id)/\(targetTriple.tripleString)/swift-sdk.json", + let path = if let metadataPath = $0.metadataPath { + "\(bundle.path)/\(metadataPath.pathString)" + } else { + "\(bundle.path)/\($0.id)/\(targetTriple.triple)/swift-sdk.json" + } + + return ( + path, ByteString(json: try generateSwiftSDKMetadata(jsonEncoder, createToolset: $0.toolsetRootPath != nil)) ) } + bundle.artifacts.compactMap { artifact in - artifact.toolsetRootPath.map { path in + let toolsetPath = if artifact.metadataPath != nil { + "\(bundle.path)/toolset.json" + } else { + "\(bundle.path)/\(artifact.id)/\(targetTriple.triple)/toolset.json" + } + return artifact.toolsetRootPath.map { path in ( - "\(bundle.path)/\(artifact.id)/\(targetTriple.tripleString)/toolset.json", + "\(toolsetPath)", ByteString(json: """ { "schemaVersion": "1.0", @@ -101,16 +118,18 @@ private struct MockBundle { private struct MockArtifact { let id: String let supportedTriples: [Triple] + var metadataPath: RelativePath? var toolsetRootPath: AbsolutePath? } -private func generateTestFileSystem(bundleArtifacts: [MockArtifact]) throws -> (some FileSystem, [MockBundle], AbsolutePath) { +private func generateTestFileSystem( + bundleArtifacts: [MockArtifact] +) throws -> (some FileSystem, [MockBundle], AbsolutePath) { let bundles = bundleArtifacts.enumerated().map { (i, artifacts) in let bundleName = "test\(i).\(artifactBundleExtension)" return MockBundle(name: "test\(i).\(artifactBundleExtension)", path: "/\(bundleName)", artifacts: [artifacts]) } - let fileSystem = try InMemoryFileSystem( files: Dictionary( uniqueKeysWithValues: bundles.flatMap { @@ -475,4 +494,54 @@ final class SwiftSDKBundleTests: XCTestCase { ) } } + + func testMetadataJSONPaths() async throws { + let toolsetRootPath = AbsolutePath("/path/to/toolpath") + let (fileSystem, bundles, swiftSDKsDirectory) = try generateTestFileSystem( + bundleArtifacts: [ + .init( + id: "\(testArtifactID)1", + supportedTriples: [arm64Triple], + metadataPath: "metadata1.json" + ), + .init( + id: "\(testArtifactID)2", + supportedTriples: [i686Triple], + metadataPath: "metadata2.json", + toolsetRootPath: toolsetRootPath + ), + ] + ) + let system = ObservabilitySystem.makeForTesting() + let archiver = MockArchiver() + + var output = [SwiftSDKBundleStore.Output]() + let store = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem: fileSystem, + observabilityScope: system.topScope, + outputHandler: { output.append($0) } + ) + + for bundle in bundles { + try await store.install(bundlePathOrURL: bundle.path, archiver) + } + + let validBundles = try store.allValidBundles + + XCTAssertEqual(validBundles.count, bundles.count) + + XCTAssertEqual(validBundles.sortedArtifactIDs, ["\(testArtifactID)1", "\(testArtifactID)2"]) + XCTAssertEqual(output.count, 2) + XCTAssertEqual(output, [ + .installationSuccessful( + bundlePathOrURL: bundles[0].path, + bundleName: AbsolutePath(bundles[0].path).components.last! + ), + .installationSuccessful( + bundlePathOrURL: bundles[1].path, + bundleName: AbsolutePath(bundles[1].path).components.last! + ), + ]) + } } From 0036a0b7960b768696ff8f5b3e25fb3ad35ee76f Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Fri, 9 May 2025 10:44:58 -0400 Subject: [PATCH 91/99] Set tools version to 6.1 (#8637) Sourcekit-lsp was a consumer of SwiftPM and the project moved to 6.1. Update SwiftPM to the same version. --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 12f2a2f31bf..bbca6f77b32 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.0 +// swift-tools-version:6.1 //===----------------------------------------------------------------------===// // @@ -953,7 +953,7 @@ let package = Package( path: "Examples/package-info/Sources/package-info" ) ], - swiftLanguageVersions: [.v5] + swiftLanguageModes: [.v5] ) #if canImport(Darwin) From 8e4dd07066a96f183d847547c52c0e60466d4852 Mon Sep 17 00:00:00 2001 From: Doug Schaefer <167107236+dschaefer2@users.noreply.github.com> Date: Fri, 9 May 2025 19:38:32 -0400 Subject: [PATCH 92/99] Forward cherry pick the prebuilts feature to main. (#8610) To get the prebuilts feature out in time for 6.1, the bulk of the work was done on the release/6.1 branch. Cherry pick those changes to the main branch to get 6.2 caught up. This is a manual cherry pick so it's going to be a bit hard to follow. I did a diff between main and release/6.1 and copied all the changes I made there. --- Sources/Basics/Concurrency/SendableBox.swift | 16 +- .../FileSystem/FileSystem+Extensions.swift | 14 + Sources/Build/BuildPlan/BuildPlan+Test.swift | 35 +- Sources/CoreCommands/Options.swift | 13 + Sources/CoreCommands/SwiftCommandState.swift | 2 + .../PackageGraph/ModulesGraph+Loading.swift | 31 +- Sources/PackageLoading/PackageBuilder.swift | 75 +- Sources/PackageModel/Module/SwiftModule.swift | 9 +- Sources/Workspace/CMakeLists.txt | 7 + Sources/Workspace/ManagedPrebuilt.swift | 51 +- .../Workspace/ManifestSigning/Base64URL.swift | 76 ++ .../ManifestSigning/CertificatePolicy.swift | 618 +++++++++ .../ManifestSigning/ManifestSigning.swift | 390 ++++++ .../Workspace/ManifestSigning/Signature.swift | 187 +++ .../Workspace/ManifestSigning/Utilities.swift | 39 + .../ManifestSigning/X509Extensions.swift | 72 + .../ManifestSigning/embedded_resources.swift | 13 + .../Workspace/Workspace+Configuration.swift | 12 + Sources/Workspace/Workspace+Prebuilts.swift | 740 ++++++---- Sources/Workspace/Workspace+State.swift | 13 +- Sources/Workspace/Workspace.swift | 47 +- .../ManifestExtensions.swift | 24 + .../_InternalTestSupport/MockWorkspace.swift | 2 + .../BuildPrebuilts.swift | 455 +++++-- Tests/BasicsTests/HTTPClientTests.swift | 8 +- Tests/BuildTests/BuildPlanTests.swift | 100 +- Tests/WorkspaceTests/PrebuiltsTests.swift | 1203 +++++++++++------ 27 files changed, 3305 insertions(+), 947 deletions(-) create mode 100644 Sources/Workspace/ManifestSigning/Base64URL.swift create mode 100644 Sources/Workspace/ManifestSigning/CertificatePolicy.swift create mode 100644 Sources/Workspace/ManifestSigning/ManifestSigning.swift create mode 100644 Sources/Workspace/ManifestSigning/Signature.swift create mode 100644 Sources/Workspace/ManifestSigning/Utilities.swift create mode 100644 Sources/Workspace/ManifestSigning/X509Extensions.swift create mode 100644 Sources/Workspace/ManifestSigning/embedded_resources.swift diff --git a/Sources/Basics/Concurrency/SendableBox.swift b/Sources/Basics/Concurrency/SendableBox.swift index 0af62417dbb..a5ec3673be0 100644 --- a/Sources/Basics/Concurrency/SendableBox.swift +++ b/Sources/Basics/Concurrency/SendableBox.swift @@ -16,24 +16,24 @@ import struct Foundation.Date /// an `async` closure. This type serves as a replacement for `ThreadSafeBox` /// implemented with Swift Concurrency primitives. public actor SendableBox { - init(_ value: Value? = nil) { + public init(_ value: Value) { self.value = value } - var value: Value? + public var value: Value + + public func set(_ value: Value) { + self.value = value + } } extension SendableBox where Value == Int { func increment() { - if let value { - self.value = value + 1 - } + self.value = value + 1 } func decrement() { - if let value { - self.value = value - 1 - } + self.value = value - 1 } } diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index 0b43b6c7d75..5f9674b2ef5 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -681,3 +681,17 @@ extension FileSystem { } } } + +extension FileSystem { + /// Do a deep enumeration, passing each file to block + public func enumerate(directory: AbsolutePath, block: (AbsolutePath) throws -> ()) throws { + for file in try getDirectoryContents(directory) { + let path = directory.appending(file) + if isDirectory(path) { + try enumerate(directory: path, block: block) + } else { + try block(path) + } + } + } +} diff --git a/Sources/Build/BuildPlan/BuildPlan+Test.swift b/Sources/Build/BuildPlan/BuildPlan+Test.swift index b8e3fc9638a..8bb0042761f 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Test.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Test.swift @@ -27,6 +27,7 @@ import struct PackageGraph.ResolvedProduct import struct PackageGraph.ResolvedModule import struct PackageModel.Sources +import enum PackageModel.BuildSettings import class PackageModel.SwiftModule import class PackageModel.Module import struct SPMBuildCore.BuildParameters @@ -80,17 +81,28 @@ extension BuildPlan { let discoveryMainFile = discoveryDerivedDir.appending(component: TestDiscoveryTool.mainFileName) var discoveryPaths: [AbsolutePath] = [] + var discoveryBuildSettings: BuildSettings.AssignmentTable = .init() discoveryPaths.append(discoveryMainFile) for testTarget in testProduct.modules { let path = discoveryDerivedDir.appending(components: testTarget.name + ".swift") discoveryPaths.append(path) + // Add in the include path from the test targets to ensure this module builds + if let flags = testTarget.underlying.buildSettings.assignments[.OTHER_SWIFT_FLAGS] { + for assignment in flags { + let values = assignment.values.filter({ $0.hasPrefix("-I") }) + if !values.isEmpty { + discoveryBuildSettings.add(.init(values: values, conditions: []), for: .OTHER_SWIFT_FLAGS) + } + } + } } let discoveryTarget = SwiftModule( name: discoveryTargetName, dependencies: testProduct.underlying.modules.map { .module($0, conditions: []) }, packageAccess: true, // test target is allowed access to package decls by default - testDiscoverySrc: Sources(paths: discoveryPaths, root: discoveryDerivedDir) + testDiscoverySrc: Sources(paths: discoveryPaths, root: discoveryDerivedDir), + buildSettings: discoveryBuildSettings ) let discoveryResolvedModule = ResolvedModule( packageIdentity: testProduct.packageIdentity, @@ -127,13 +139,28 @@ extension BuildPlan { let entryPointMainFile = entryPointDerivedDir.appending(component: entryPointMainFileName) let entryPointSources = Sources(paths: [entryPointMainFile], root: entryPointDerivedDir) + var entryPointBuildSettings: BuildSettings.AssignmentTable = .init() + for testTarget in testProduct.modules { + // Add in the include path from the test targets to ensure this module builds + if let flags = testTarget.underlying.buildSettings.assignments[.OTHER_SWIFT_FLAGS] { + for assignment in flags { + let values = assignment.values.filter({ $0.hasPrefix("-I") }) + if !values.isEmpty { + entryPointBuildSettings.add(.init(values: values, conditions: []), for: .OTHER_SWIFT_FLAGS) + } + } + } + } + let entryPointTarget = SwiftModule( name: testProduct.name, type: .library, dependencies: testProduct.underlying.modules.map { .module($0, conditions: []) } + swiftTargetDependencies, packageAccess: true, // test target is allowed access to package decls - testEntryPointSources: entryPointSources + testEntryPointSources: entryPointSources, + buildSettings: entryPointBuildSettings ) + let entryPointResolvedTarget = ResolvedModule( packageIdentity: testProduct.packageIdentity, underlying: entryPointTarget, @@ -249,7 +276,8 @@ private extension PackageModel.SwiftModule { type: PackageModel.Module.Kind? = nil, dependencies: [PackageModel.Module.Dependency], packageAccess: Bool, - testEntryPointSources sources: Sources + testEntryPointSources sources: Sources, + buildSettings: BuildSettings.AssignmentTable = .init() ) { self.init( name: name, @@ -258,6 +286,7 @@ private extension PackageModel.SwiftModule { sources: sources, dependencies: dependencies, packageAccess: packageAccess, + buildSettings: buildSettings, usesUnsafeFlags: false ) } diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index d12fe55f2fd..c892694f383 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -199,6 +199,19 @@ public struct CachingOptions: ParsableArguments { inversion: .prefixedEnableDisable, help: "Whether to use prebuilt swift-syntax libraries for macros.") public var usePrebuilts: Bool = false + + /// Hidden option to override the prebuilts download location for testing + @Option( + name: .customLong("experimental-prebuilts-download-url"), + help: .hidden + ) + public var prebuiltsDownloadURL: String? + + @Option( + name: .customLong("experimental-prebuilts-root-cert"), + help: .hidden + ) + public var prebuiltsRootCertPath: String? } public struct LoggingOptions: ParsableArguments { diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 57551331b75..4fea92bedb8 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -508,6 +508,8 @@ public final class SwiftCommandState { }, manifestImportRestrictions: .none, usePrebuilts: self.options.caching.usePrebuilts, + prebuiltsDownloadURL: options.caching.prebuiltsDownloadURL, + prebuiltsRootCertPath: options.caching.prebuiltsRootCertPath, pruneDependencies: self.options.resolver.pruneDependencies, traitConfiguration: traitConfiguration ), diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 50020c2f602..fb4c3a1bc7c 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -223,7 +223,12 @@ extension ModulesGraph { ) let rootPackages = resolvedPackages.filter { root.manifests.values.contains($0.manifest) } - checkAllDependenciesAreUsed(packages: resolvedPackages, rootPackages, observabilityScope: observabilityScope) + checkAllDependenciesAreUsed( + packages: resolvedPackages, + rootPackages, + prebuilts: prebuilts, + observabilityScope: observabilityScope + ) return try ModulesGraph( rootPackages: rootPackages, @@ -238,6 +243,7 @@ extension ModulesGraph { private func checkAllDependenciesAreUsed( packages: IdentifiableSet, _ rootPackages: [ResolvedPackage], + prebuilts: [PackageIdentity: [String: PrebuiltLibrary]], observabilityScope: ObservabilityScope ) { for package in rootPackages { @@ -315,9 +321,10 @@ private func checkAllDependenciesAreUsed( let usedByPackage = productDependencies.contains { $0.name == product.name } // We check if any of the products of this dependency is guarded by a trait. let traitGuarded = traitGuardedProductDependencies.contains(product.name) + // Consider prebuilts as used + let prebuilt = prebuilts[dependency.identity]?.keys.contains(product.name) ?? false - // If the product is either used directly or guarded by a trait we consider it as used - return usedByPackage || traitGuarded + return usedByPackage || traitGuarded || prebuilt } if !dependencyIsUsed && !observabilityScope.errorsReportedInAnyScope { @@ -733,6 +740,24 @@ private func createResolvedPackages( // Establish product dependencies. for case .product(let productRef, let conditions) in moduleBuilder.module.dependencies { + if let package = productRef.package, prebuilts[.plain(package)]?[productRef.name] != nil { + // See if we're using a prebuilt instead + if moduleBuilder.module.type == .macro { + continue + } else if moduleBuilder.module.type == .test { + // use prebuilt if this is a test that depends a macro target + // these are guaranteed built for host + if moduleBuilder.module.dependencies.contains(where: { dep in + guard let module = dep.module else { + return false + } + return module.type == .macro + }) { + continue + } + } + } + // Find the product in this package's dependency products. // Look it up by ID if module aliasing is used, otherwise by name. let product = lookupByProductIDs ? productDependencyMap[productRef.identity] : diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index df8641cf00c..112e59a77f6 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -281,8 +281,8 @@ public struct BinaryArtifact { /// A structure representing a prebuilt library to be used instead of a source dependency public struct PrebuiltLibrary { - /// The package reference. - public let packageRef: PackageReference + /// The package identity. + public let identity: PackageIdentity /// The name of the binary target the artifact corresponds to. public let libraryName: String @@ -296,8 +296,8 @@ public struct PrebuiltLibrary { /// The C modules that need their includes directory added to the include path public let cModules: [String] - public init(packageRef: PackageReference, libraryName: String, path: AbsolutePath, products: [String], cModules: [String]) { - self.packageRef = packageRef + public init(identity: PackageIdentity, libraryName: String, path: AbsolutePath, products: [String], cModules: [String]) { + self.identity = identity self.libraryName = libraryName self.path = path self.products = products @@ -1297,31 +1297,34 @@ public final class PackageBuilder { table.add(assignment, for: .SWIFT_ACTIVE_COMPILATION_CONDITIONS) } - // Add in flags for prebuilts - let prebuiltLibraries: [String: PrebuiltLibrary] = target.dependencies.reduce(into: .init()) { - guard case let .product(name: name, package: package, moduleAliases: _, condition: _) = $1, - let package = package, - let prebuilt = prebuilts[.plain(package)]?[name] - else { - return - } + // Add in flags for prebuilts if the target is a macro or a macro test. + // Currently we only support prebuilts for macros. + if target.type == .macro || target.isMacroTest(in: manifest) { + let prebuiltLibraries: [String: PrebuiltLibrary] = target.dependencies.reduce(into: .init()) { + guard case let .product(name: name, package: package, moduleAliases: _, condition: _) = $1, + let package = package, + let prebuilt = prebuilts[.plain(package)]?[name] + else { + return + } - $0[prebuilt.libraryName] = prebuilt - } + $0[prebuilt.libraryName] = prebuilt + } - for prebuilt in prebuiltLibraries.values { - let lib = prebuilt.path.appending(components: ["lib", "lib\(prebuilt.libraryName).a"]).pathString - var ldFlagsAssignment = BuildSettings.Assignment() - ldFlagsAssignment.values = [lib] - table.add(ldFlagsAssignment, for: .OTHER_LDFLAGS) + for prebuilt in prebuiltLibraries.values { + let lib = prebuilt.path.appending(components: ["lib", "lib\(prebuilt.libraryName).a"]).pathString + var ldFlagsAssignment = BuildSettings.Assignment() + ldFlagsAssignment.values = [lib] + table.add(ldFlagsAssignment, for: .OTHER_LDFLAGS) - var includeDirs: [AbsolutePath] = [prebuilt.path.appending(component: "Modules")] - for cModule in prebuilt.cModules { - includeDirs.append(prebuilt.path.appending(components: "include", cModule)) + var includeDirs: [AbsolutePath] = [prebuilt.path.appending(component: "Modules")] + for cModule in prebuilt.cModules { + includeDirs.append(prebuilt.path.appending(components: "include", cModule)) + } + var includeAssignment = BuildSettings.Assignment() + includeAssignment.values = includeDirs.map({ "-I\($0.pathString)" }) + table.add(includeAssignment, for: .OTHER_SWIFT_FLAGS) } - var includeAssignment = BuildSettings.Assignment() - includeAssignment.values = includeDirs.map({ "-I\($0.pathString)" }) - table.add(includeAssignment, for: .OTHER_SWIFT_FLAGS) } return table @@ -1908,4 +1911,26 @@ extension TargetDescription { fileprivate var usesUnsafeFlags: Bool { settings.filter(\.kind.isUnsafeFlags).isEmpty == false } + + fileprivate func isMacroTest(in manifest: Manifest) -> Bool { + guard self.type == .test else { return false } + + return self.dependencies.contains(where: { + let name: String + switch $0 { + case .byName(name: let n, condition: _): + name = n + case .target(name: let n, condition: _): + name = n + default: + return false + } + + guard let target = manifest.targetMap[name] else { + return false + } + + return target.type == .macro + }) + } } diff --git a/Sources/PackageModel/Module/SwiftModule.swift b/Sources/PackageModel/Module/SwiftModule.swift index 222c62c59ad..93b083b1ebf 100644 --- a/Sources/PackageModel/Module/SwiftModule.swift +++ b/Sources/PackageModel/Module/SwiftModule.swift @@ -28,7 +28,12 @@ public final class SwiftModule: Module { [defaultTestEntryPointName, "LinuxMain.swift"] } - public init(name: String, dependencies: [Module.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { + public init( + name: String, + dependencies: [Module.Dependency], + packageAccess: Bool, + testDiscoverySrc: Sources, + buildSettings: BuildSettings.AssignmentTable = .init()) { self.declaredSwiftVersions = [] super.init( @@ -38,7 +43,7 @@ public final class SwiftModule: Module { sources: testDiscoverySrc, dependencies: dependencies, packageAccess: packageAccess, - buildSettings: .init(), + buildSettings: buildSettings, buildSettingsDescription: [], pluginUsages: [], usesUnsafeFlags: false diff --git a/Sources/Workspace/CMakeLists.txt b/Sources/Workspace/CMakeLists.txt index c079fab70b9..d0b033eb1cf 100644 --- a/Sources/Workspace/CMakeLists.txt +++ b/Sources/Workspace/CMakeLists.txt @@ -14,6 +14,13 @@ add_library(Workspace ManagedArtifact.swift ManagedDependency.swift ManagedPrebuilt.swift + ManifestSigning/Base64URL.swift + ManifestSigning/CertificatePolicy.swift + ManifestSigning/embedded_resources.swift + ManifestSigning/ManifestSigning.swift + ManifestSigning/Signature.swift + ManifestSigning/Utilities.swift + ManifestSigning/X509Extensions.swift PackageContainer/FileSystemPackageContainer.swift PackageContainer/RegistryPackageContainer.swift PackageContainer/SourceControlPackageContainer.swift diff --git a/Sources/Workspace/ManagedPrebuilt.swift b/Sources/Workspace/ManagedPrebuilt.swift index 9c8aafc2343..b4ce241331e 100644 --- a/Sources/Workspace/ManagedPrebuilt.swift +++ b/Sources/Workspace/ManagedPrebuilt.swift @@ -10,21 +10,26 @@ // //===----------------------------------------------------------------------===// +import struct TSCUtility.Version +import struct TSCBasic.StringError + import Basics import PackageModel -import TSCBasic extension Workspace { /// A downloaded prebuilt managed by the workspace. public struct ManagedPrebuilt { - /// The package reference. - public let packageRef: PackageReference + /// The package identity + public let identity: PackageIdentity + + /// The package version + public let version: Version /// The name of the binary target the artifact corresponds to. public let libraryName: String /// The path to the extracted prebuilt artifacts - public let path: Basics.AbsolutePath + public let path: AbsolutePath /// The products in the library public let products: [String] @@ -36,7 +41,7 @@ extension Workspace { extension Workspace.ManagedPrebuilt: CustomStringConvertible { public var description: String { - return "" + return "" } } @@ -46,60 +51,60 @@ extension Workspace { /// A collection of managed artifacts which have been downloaded. public final class ManagedPrebuilts { /// A mapping from package identity, to target name, to ManagedArtifact. - private var artifactMap: [PackageIdentity: [String: ManagedPrebuilt]] + private var prebuiltMap: [PackageIdentity: [String: ManagedPrebuilt]] - internal var artifacts: AnyCollection { - AnyCollection(self.artifactMap.values.lazy.flatMap{ $0.values }) + internal var prebuilts: AnyCollection { + AnyCollection(self.prebuiltMap.values.lazy.flatMap{ $0.values }) } init() { - self.artifactMap = [:] + self.prebuiltMap = [:] } - init(_ artifacts: [ManagedPrebuilt]) throws { - let artifactsByPackagePath = Dictionary(grouping: artifacts, by: { $0.packageRef.identity }) - self.artifactMap = try artifactsByPackagePath.mapValues{ artifacts in - try Dictionary(artifacts.map { ($0.libraryName, $0) }, uniquingKeysWith: { _, _ in + init(_ prebuilts: [ManagedPrebuilt]) throws { + let prebuiltsByPackagePath = Dictionary(grouping: prebuilts, by: { $0.identity }) + self.prebuiltMap = try prebuiltsByPackagePath.mapValues{ prebuilt in + try Dictionary(prebuilt.map { ($0.libraryName, $0) }, uniquingKeysWith: { _, _ in // should be unique - throw StringError("binary artifact already exists in managed artifacts") + throw StringError("prebuilt already exists in managed prebuilts") }) } } public subscript(packageIdentity packageIdentity: PackageIdentity, targetName targetName: String) -> ManagedPrebuilt? { - self.artifactMap[packageIdentity]?[targetName] + self.prebuiltMap[packageIdentity]?[targetName] } - public func add(_ artifact: ManagedPrebuilt) { - self.artifactMap[artifact.packageRef.identity, default: [:]][artifact.libraryName] = artifact + public func add(_ prebuilt: ManagedPrebuilt) { + self.prebuiltMap[prebuilt.identity, default: [:]][prebuilt.libraryName] = prebuilt } public func remove(packageIdentity: PackageIdentity, targetName: String) { - self.artifactMap[packageIdentity]?[targetName] = nil + self.prebuiltMap[packageIdentity]?[targetName] = nil } } } extension Workspace.ManagedPrebuilts: Collection { public var startIndex: AnyIndex { - self.artifacts.startIndex + self.prebuilts.startIndex } public var endIndex: AnyIndex { - self.artifacts.endIndex + self.prebuilts.endIndex } public subscript(index: AnyIndex) -> Workspace.ManagedPrebuilt { - self.artifacts[index] + self.prebuilts[index] } public func index(after index: AnyIndex) -> AnyIndex { - self.artifacts.index(after: index) + self.prebuilts.index(after: index) } } extension Workspace.ManagedPrebuilts: CustomStringConvertible { public var description: String { - "" + "" } } diff --git a/Sources/Workspace/ManifestSigning/Base64URL.swift b/Sources/Workspace/ManifestSigning/Base64URL.swift new file mode 100644 index 00000000000..bae48bb4c62 --- /dev/null +++ b/Sources/Workspace/ManifestSigning/Base64URL.swift @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Vapor open source project +// +// Copyright (c) 2017-2020 Vapor project authors +// Licensed under MIT +// +// See LICENSE for license information +// +// SPDX-License-Identifier: MIT +// +//===----------------------------------------------------------------------===// + +import Foundation + +// Source: https://github.com/vapor/jwt-kit/blob/master/Sources/JWTKit/Utilities/Base64URL.swift + +extension DataProtocol { + func base64URLDecodedBytes() -> Data? { + var data = Data(self) + data.base64URLUnescape() + return Data(base64Encoded: data) + } + + func base64URLEncodedBytes() -> Data { + var data = Data(self).base64EncodedData() + data.base64URLEscape() + return data + } +} + +extension Data { + /// Converts base64-url encoded data to a base64 encoded data. + /// + /// https://tools.ietf.org/html/rfc4648#page-7 + mutating func base64URLUnescape() { + for i in 0 ..< self.count { + switch self[i] { + case 0x2D: self[self.index(self.startIndex, offsetBy: i)] = 0x2B + case 0x5F: self[self.index(self.startIndex, offsetBy: i)] = 0x2F + default: break + } + } + /// https://stackoverflow.com/questions/43499651/decode-base64url-to-base64-swift + let padding = count % 4 + if padding > 0 { + self += Data(repeating: 0x3D, count: 4 - padding) + } + } + + /// Converts base64 encoded data to a base64-url encoded data. + /// + /// https://tools.ietf.org/html/rfc4648#page-7 + mutating func base64URLEscape() { + for i in 0 ..< self.count { + switch self[i] { + case 0x2B: self[self.index(self.startIndex, offsetBy: i)] = 0x2D + case 0x2F: self[self.index(self.startIndex, offsetBy: i)] = 0x5F + default: break + } + } + self = split(separator: 0x3D).first ?? .init() + } +} diff --git a/Sources/Workspace/ManifestSigning/CertificatePolicy.swift b/Sources/Workspace/ManifestSigning/CertificatePolicy.swift new file mode 100644 index 00000000000..f8c26593da0 --- /dev/null +++ b/Sources/Workspace/ManifestSigning/CertificatePolicy.swift @@ -0,0 +1,618 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Dispatch +import Foundation +import Basics +import struct TSCBasic.ByteString + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import SwiftASN1 +@_implementationOnly import X509 +#else +import SwiftASN1 +import X509 +#endif + +public enum CertificatePolicyKey: Hashable, CustomStringConvertible { + case `default`(subjectUserID: String? = nil, subjectOrganizationalUnit: String? = nil) + case appleSwiftPackageCollection(subjectUserID: String? = nil, subjectOrganizationalUnit: String? = nil) + + @available(*, deprecated, message: "use `appleSwiftPackageCollection` instead") + case appleDistribution(subjectUserID: String? = nil, subjectOrganizationalUnit: String? = nil) + + /// For testing only + case custom + + public var description: String { + switch self { + case .default(let userID, let organizationalUnit): + return "Default certificate policy\(userID.map { " (userID: \($0))" } ?? "")\(organizationalUnit.map { " (organizationalUnit: \($0))" } ?? "")" + case .appleSwiftPackageCollection(let userID, let organizationalUnit): + return "Swift Package Collection certificate policy\(userID.map { " (userID: \($0))" } ?? "")\(organizationalUnit.map { " (organizationalUnit: \($0))" } ?? "")" + case .appleDistribution(let userID, let organizationalUnit): + return "Distribution certificate policy\(userID.map { " (userID: \($0))" } ?? "")\(organizationalUnit.map { " (organizationalUnit: \($0))" } ?? "")" + case .custom: + return "Custom certificate policy" + } + } + + public static let `default` = CertificatePolicyKey.default() + public static let appleSwiftPackageCollection = CertificatePolicyKey.appleSwiftPackageCollection() + @available(*, deprecated, message: "use `appleSwiftPackageCollection` instead") + public static let appleDistribution = CertificatePolicyKey.appleDistribution() +} + +// MARK: - Certificate policies + +protocol CertificatePolicy { + /// Validates the given certificate chain. + /// + /// - Parameters: + /// - certChain: The certificate being verified must be the first element of the array, with its issuer the next + /// element and so on, and the root CA certificate is last. + /// - validationTime: Overrides the timestamp used for checking certificate expiry (e.g., for testing). + /// By default the current time is used. + func validate(certChain: [Certificate], validationTime: Date) async throws +} + +extension CertificatePolicy { + /// Validates the given certificate chain. + /// + /// - Parameters: + /// - certChain: The certificate being verified must be the first element of the array, with its issuer the next + /// element and so on, and the root CA certificate is last. + func validate(certChain: [Certificate]) async throws { + try await self.validate(certChain: certChain, validationTime: Date()) + } + + func verify( + certChain: [Certificate], + trustedRoots: [Certificate]?, + @PolicyBuilder policies: () -> some VerifierPolicy, + observabilityScope: ObservabilityScope + ) async throws { + guard !certChain.isEmpty else { + throw CertificatePolicyError.emptyCertChain + } + + let policies = policies() + + var trustStore = CertificateStores.defaultTrustRoots + if let trustedRoots { + trustStore.append(contentsOf: trustedRoots) + } + + guard !trustStore.isEmpty else { + throw CertificatePolicyError.noTrustedRootCertsConfigured + } + + var verifier = Verifier(rootCertificates: CertificateStore(trustStore)) { + policies + } + let result = await verifier.validate( + leafCertificate: certChain[0], + intermediates: CertificateStore(certChain) + ) + + switch result { + case .validCertificate: + return + case .couldNotValidate(let failures): + observabilityScope.emit(error: "Failed to validate certificate chain \(certChain): \(failures)") + throw CertificatePolicyError.invalidCertChain + } + } +} + +enum CertificatePolicyError: Error, Equatable { + case noTrustedRootCertsConfigured + case emptyCertChain + case invalidCertChain +} + +/// Default policy for validating certificates used to sign package collections. +/// +/// Certificates must satisfy these conditions: +/// - The timestamp at which signing/verification is done must fall within the signing certificate’s validity period. +/// - The certificate’s “Extended Key Usage” extension must include “Code Signing”. +/// - The certificate must use either 256-bit EC (recommended) or 2048-bit RSA key. +/// - The certificate must not be revoked. The certificate authority must support OCSP. +/// - The certificate chain is valid and root certificate must be trusted. +struct DefaultCertificatePolicy: CertificatePolicy { + let trustedRoots: [Certificate] + let expectedSubjectUserID: String? + let expectedSubjectOrganizationalUnit: String? + + private let httpClient: HTTPClient + private let observabilityScope: ObservabilityScope + + /// Initializes a `DefaultCertificatePolicy`. + /// + /// - Parameters: + /// - trustedRootCertsDir: Users may specify root certificates in addition to SwiftPM's default trust + /// store by placing them in this directory. + /// - additionalTrustedRootCerts: Root certificates to be trusted in addition to those in `trustedRootCertsDir`. + /// The difference between this and `trustedRootCertsDir` is that the latter is + /// user configured and dynamic, while this is configured by SwiftPM and static. + /// - expectedSubjectUserID: The subject user ID that must match if specified. + /// - expectedSubjectOrganizationalUnit: The subject organizational unit name that must match if specified. + init( + trustedRootCertsDir: AbsolutePath?, + fileSystem: FileSystem?, + additionalTrustedRootCerts: [Certificate]?, + expectedSubjectUserID: String? = nil, + expectedSubjectOrganizationalUnit: String? = nil, + observabilityScope: ObservabilityScope + ) { + var trustedRoots = [Certificate]() + if let trustedRootCertsDir, let fileSystem { + trustedRoots + .append(contentsOf: Self.loadCerts(at: trustedRootCertsDir, fileSystem: fileSystem, observabilityScope: observabilityScope)) + } + if let additionalTrustedRootCerts { + trustedRoots.append(contentsOf: additionalTrustedRootCerts) + } + self.trustedRoots = trustedRoots + self.expectedSubjectUserID = expectedSubjectUserID + self.expectedSubjectOrganizationalUnit = expectedSubjectOrganizationalUnit + self.httpClient = HTTPClient.makeDefault() + self.observabilityScope = observabilityScope + } + + func validate(certChain: [Certificate], validationTime: Date) async throws { + guard !certChain.isEmpty else { + throw CertificatePolicyError.emptyCertChain + } + + try await self.verify( + certChain: certChain, + trustedRoots: self.trustedRoots, + policies: { + _ADPCertificatePolicy() // included for testing + // Check if subject name matches + _SubjectNamePolicy( + expectedUserID: self.expectedSubjectUserID, + expectedOrganizationalUnit: self.expectedSubjectOrganizationalUnit + ) + // Must be a code signing certificate + _CodeSigningPolicy() + // Basic validations including expiry check + RFC5280Policy(validationTime: validationTime) + // Must support OCSP + _OCSPVerifierPolicy( + httpClient: self.httpClient, + validationTime: validationTime + ) + }, + observabilityScope: self.observabilityScope + ) + } +} + +/// Policy for validating developer.apple.com Swift Package Collection certificates. +/// +/// This has the same requirements as `DefaultCertificatePolicy` plus additional +/// marker extensions for Swift Package Collection certifiicates. +struct ADPSwiftPackageCollectionCertificatePolicy: CertificatePolicy { + let trustedRoots: [Certificate] + let expectedSubjectUserID: String? + let expectedSubjectOrganizationalUnit: String? + + private let httpClient: HTTPClient + private let observabilityScope: ObservabilityScope + + /// Initializes a `ADPSwiftPackageCollectionCertificatePolicy`. + /// + /// - Parameters: + /// - trustedRootCertsDir: Users may specify root certificates in addition to SwiftPM's default trust + /// store by placing them in this directory. + /// - additionalTrustedRootCerts: Root certificates to be trusted in addition to those in `trustedRootCertsDir`. + /// The difference between this and `trustedRootCertsDir` is that the latter is + /// user configured and dynamic, while this is configured by SwiftPM and static. + /// - expectedSubjectUserID: The subject user ID that must match if specified. + /// - expectedSubjectOrganizationalUnit: The subject organizational unit name that must match if specified. + init( + trustedRootCertsDir: AbsolutePath?, + fileSystem: FileSystem?, + additionalTrustedRootCerts: [Certificate]?, + expectedSubjectUserID: String? = nil, + expectedSubjectOrganizationalUnit: String? = nil, + observabilityScope: ObservabilityScope + ) { + var trustedRoots = [Certificate]() + if let trustedRootCertsDir, let fileSystem { + trustedRoots + .append(contentsOf: Self.loadCerts(at: trustedRootCertsDir, fileSystem: fileSystem, observabilityScope: observabilityScope)) + } + if let additionalTrustedRootCerts { + trustedRoots.append(contentsOf: additionalTrustedRootCerts) + } + self.trustedRoots = trustedRoots + self.expectedSubjectUserID = expectedSubjectUserID + self.expectedSubjectOrganizationalUnit = expectedSubjectOrganizationalUnit + self.httpClient = HTTPClient.makeDefault() + self.observabilityScope = observabilityScope + } + + func validate(certChain: [Certificate], validationTime: Date) async throws { + guard !certChain.isEmpty else { + throw CertificatePolicyError.emptyCertChain + } + + try await self.verify( + certChain: certChain, + trustedRoots: self.trustedRoots, + policies: { + // Check for specific markers + _ADPSwiftPackageCertificatePolicy() + _ADPCertificatePolicy() // included for testing + // Check if subject name matches + _SubjectNamePolicy( + expectedUserID: self.expectedSubjectUserID, + expectedOrganizationalUnit: self.expectedSubjectOrganizationalUnit + ) + // Must be a code signing certificate + _CodeSigningPolicy() + // Basic validations including expiry check + RFC5280Policy(validationTime: validationTime) + // Must support OCSP + _OCSPVerifierPolicy( + httpClient: self.httpClient, + validationTime: validationTime + ) + }, + observabilityScope: self.observabilityScope + ) + } +} + +/// Policy for validating developer.apple.com Apple Distribution certificates. +/// +/// This has the same requirements as `DefaultCertificatePolicy` plus additional +/// marker extensions for Apple Distribution certifiicates. +struct ADPAppleDistributionCertificatePolicy: CertificatePolicy { + let trustedRoots: [Certificate] + let expectedSubjectUserID: String? + let expectedSubjectOrganizationalUnit: String? + + private let httpClient: HTTPClient + private let observabilityScope: ObservabilityScope + + /// Initializes a `ADPAppleDistributionCertificatePolicy`. + /// + /// - Parameters: + /// - trustedRootCertsDir: Users may specify root certificates in addition to SwiftPM's default trust + /// store by placing them in this directory. + /// - additionalTrustedRootCerts: Root certificates to be trusted in addition to those in `trustedRootCertsDir`. + /// The difference between this and `trustedRootCertsDir` is that the latter is + /// user configured and dynamic, while this is configured by SwiftPM and static. + /// - expectedSubjectUserID: The subject user ID that must match if specified. + /// - expectedSubjectOrganizationalUnit: The subject organizational unit name that must match if specified. + init( + trustedRootCertsDir: AbsolutePath?, + fileSystem: FileSystem?, + additionalTrustedRootCerts: [Certificate]?, + expectedSubjectUserID: String? = nil, + expectedSubjectOrganizationalUnit: String? = nil, + observabilityScope: ObservabilityScope + ) { + var trustedRoots = [Certificate]() + if let trustedRootCertsDir, let fileSystem { + trustedRoots + .append(contentsOf: Self.loadCerts(at: trustedRootCertsDir, fileSystem: fileSystem, observabilityScope: observabilityScope)) + } + if let additionalTrustedRootCerts { + trustedRoots.append(contentsOf: additionalTrustedRootCerts) + } + self.trustedRoots = trustedRoots + self.expectedSubjectUserID = expectedSubjectUserID + self.expectedSubjectOrganizationalUnit = expectedSubjectOrganizationalUnit + self.httpClient = HTTPClient.makeDefault() + self.observabilityScope = observabilityScope + } + + func validate(certChain: [Certificate], validationTime: Date) async throws { + guard !certChain.isEmpty else { + throw CertificatePolicyError.emptyCertChain + } + + try await self.verify( + certChain: certChain, + trustedRoots: self.trustedRoots, + policies: { + // Check for specific markers + _ADPAppleDistributionCertificatePolicy() + _ADPCertificatePolicy() // included for testing + // Check if subject name matches + _SubjectNamePolicy( + expectedUserID: self.expectedSubjectUserID, + expectedOrganizationalUnit: self.expectedSubjectOrganizationalUnit + ) + // Must be a code signing certificate + _CodeSigningPolicy() + // Basic validations including expiry check + RFC5280Policy(validationTime: validationTime) + // Must support OCSP + _OCSPVerifierPolicy( + httpClient: self.httpClient, + validationTime: validationTime + ) + }, + observabilityScope: self.observabilityScope + ) + } +} + +// MARK: - Verifier policies + +/// Policy for code signing certificates. +struct _CodeSigningPolicy: VerifierPolicy { + let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [ + ASN1ObjectIdentifier.X509ExtensionID.keyUsage, + ASN1ObjectIdentifier.X509ExtensionID.extendedKeyUsage, + ] + + func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { + let isCodeSigning = ( + try? chain.leaf.extensions.extendedKeyUsage?.contains(ExtendedKeyUsage.Usage.codeSigning) + ) ?? false + guard isCodeSigning else { + return .failsToMeetPolicy(reason: "Certificate \(chain.leaf) does not have code signing extended key usage") + } + return .meetsPolicy + } +} + +/// Policy for revocation check via OCSP. +struct _OCSPVerifierPolicy: VerifierPolicy { + let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [] + + private static let cacheTTL: DispatchTimeInterval = .seconds(5 * 60) + private let cache = ThreadSafeKeyValueStore< + UnverifiedCertificateChain, + (result: PolicyEvaluationResult, expires: DispatchTime) + >() + + private var underlying: OCSPVerifierPolicy<_OCSPRequester> + + init(httpClient: HTTPClient, validationTime: Date) { + self.underlying = OCSPVerifierPolicy( + failureMode: .soft, + requester: _OCSPRequester(httpClient: httpClient), + validationTime: validationTime + ) + } + + mutating func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { + // Look for cached result + if let cached = self.cache[chain], cached.expires < .now() { + return cached.result + } + + // This makes HTTP requests + let result = await self.underlying.chainMeetsPolicyRequirements(chain: chain) + + // Save result to cache + self.cache[chain] = (result: result, expires: .now() + Self.cacheTTL) + return result + } +} + +private struct _OCSPRequester: OCSPRequester { + let httpClient: HTTPClient + + func query(request: [UInt8], uri: String) async -> OCSPRequesterQueryResult { + guard let url = URL(string: uri), let host = url.host else { + return .terminalError(SwiftOCSPRequesterError.invalidURL(uri)) + } + + do { + let response = try await self.httpClient.post( + url, + body: Data(request), + headers: [ + "Content-Type": "application/ocsp-request", + "Host": host, + ] + ) + + guard response.statusCode == 200 else { + throw SwiftOCSPRequesterError.invalidResponse(statusCode: response.statusCode) + } + guard let responseBody = response.body else { + throw SwiftOCSPRequesterError.emptyResponse + } + return .response(Array(responseBody)) + } catch { + return .nonTerminalError(error) + } + } +} + +enum SwiftOCSPRequesterError: Error { + case invalidURL(String) + case emptyResponse + case invalidResponse(statusCode: Int) +} + +/// Policy for matching subject name. +struct _SubjectNamePolicy: VerifierPolicy { + let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [] + + let expectedUserID: String? + let expectedOrganizationalUnit: String? + + func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { + if let expectedUserID { + let userID = chain.leaf.subject.userID + guard userID == expectedUserID else { + return .failsToMeetPolicy( + reason: "Subject user ID '\(String(describing: userID))' does not match expected '\(expectedUserID)'" + ) + } + } + + if let expectedOrganizationalUnit { + let organizationUnit = chain.leaf.subject.organizationalUnitName + guard organizationUnit == expectedOrganizationalUnit else { + return .failsToMeetPolicy( + reason: "Subject organizational unit name '\(String(describing: organizationUnit))' does not match expected '\(expectedOrganizationalUnit)'" + ) + } + } + + return .meetsPolicy + } +} + +/// Policy for ADP certificates. +struct _ADPCertificatePolicy: VerifierPolicy { + /// Include custom marker extensions (which can be critical) so they would not + /// be considered unhandled and cause certificate chain validation to fail. + let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = + ASN1ObjectIdentifier.NameAttributes.adpAppleDevelopmentMarkers // included for testing + + func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { + // Not policing anything here. This policy is mainly for + // listing marker extensions to prevent chain validation + // from failing prematurely. + .meetsPolicy + } +} + +/// Policy for ADP Swift Package (Collection) certificates. +struct _ADPSwiftPackageCertificatePolicy: VerifierPolicy { + /// Include custom marker extensions (which can be critical) so they would not + /// be considered unhandled and cause certificate chain validation to fail. + let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [ + ASN1ObjectIdentifier.NameAttributes.adpSwiftPackageCollectionMarker, + ASN1ObjectIdentifier.NameAttributes.adpSwiftPackageMarker, + ] + + // developer.apple.com cert chain is always 3-long + private static let expectedCertChainLength = 3 + + func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { + guard chain.count == Self.expectedCertChainLength else { + return .failsToMeetPolicy( + reason: "Certificate chain should have length \(Self.expectedCertChainLength) but it's \(chain.count)" + ) + } + + // Package collection can be signed with "Swift Package Collection" + // or "Swift Package" certificate + guard chain.leaf.hasExtension(oid: ASN1ObjectIdentifier.NameAttributes.adpSwiftPackageCollectionMarker) + || chain.leaf.hasExtension(oid: ASN1ObjectIdentifier.NameAttributes.adpSwiftPackageMarker) + else { + return .failsToMeetPolicy(reason: "Leaf certificate missing marker OID") + } + + for marker in ASN1ObjectIdentifier.NameAttributes.wwdrIntermediateMarkers { + if chain[1].hasExtension(oid: marker) { + return .meetsPolicy + } + } + return .failsToMeetPolicy(reason: "Intermediate missing marker OID") + } +} + +/// Policy for ADP Apple Distribution certificates. +struct _ADPAppleDistributionCertificatePolicy: VerifierPolicy { + /// Include custom marker extensions (which can be critical) so they would not + /// be considered unhandled and cause certificate chain validation to fail. + let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = + ASN1ObjectIdentifier.NameAttributes.adpAppleDistributionMarkers + + // developer.apple.com cert chain is always 3-long + private static let expectedCertChainLength = 3 + + func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) async -> PolicyEvaluationResult { + guard chain.count == Self.expectedCertChainLength else { + return .failsToMeetPolicy( + reason: "Certificate chain should have length \(Self.expectedCertChainLength) but it's \(chain.count)" + ) + } + + var hasMarker = false + for marker in ASN1ObjectIdentifier.NameAttributes.adpAppleDistributionMarkers { + if chain.leaf.hasExtension(oid: marker) { + hasMarker = true + break + } + } + guard hasMarker else { + return .failsToMeetPolicy(reason: "Leaf certificate missing marker OID") + } + + for marker in ASN1ObjectIdentifier.NameAttributes.wwdrIntermediateMarkers { + if chain[1].hasExtension(oid: marker) { + return .meetsPolicy + } + } + return .failsToMeetPolicy(reason: "Intermediate missing marker OID") + } +} + +// MARK: - Default trust store + +enum Certificates { + static let appleRootsRaw = [ + PackageResources.AppleComputerRootCertificate_cer, + PackageResources.AppleIncRootCertificate_cer, + PackageResources.AppleRootCA_G2_cer, + PackageResources.AppleRootCA_G3_cer, + ] + + static let appleRoots = Self.appleRootsRaw.compactMap { + try? Certificate(derEncoded: $0) + } +} + +enum CertificateStores { + static let defaultTrustRoots = Certificates.appleRoots +} + +// MARK: - Utils + +extension CertificatePolicy { + fileprivate static func loadCerts(at directory: AbsolutePath, fileSystem: FileSystem, observabilityScope: ObservabilityScope) -> [Certificate] { + var certs = [Certificate]() + do { + try fileSystem.enumerate(directory: directory) { file in + do { + let certData = try fileSystem.readFileContents(file) + certs.append(try Certificate(derEncoded: Array(certData.contents))) + } catch { + observabilityScope.emit( + warning: "The certificate \(file.pathString) is invalid", + underlyingError: error + ) + } + } + } catch { + observabilityScope.emit( + warning: "Failed enumerating certificates directory", + underlyingError: error + ) + } + return certs + } +} + +extension HTTPClient { + fileprivate static func makeDefault() -> HTTPClient { + var httpClientConfig = HTTPClientConfiguration() + httpClientConfig.requestTimeout = .seconds(1) + return HTTPClient(configuration: httpClientConfig) + } +} diff --git a/Sources/Workspace/ManifestSigning/ManifestSigning.swift b/Sources/Workspace/ManifestSigning/ManifestSigning.swift new file mode 100644 index 00000000000..4f3bb2808c7 --- /dev/null +++ b/Sources/Workspace/ManifestSigning/ManifestSigning.swift @@ -0,0 +1,390 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + + +import Basics +import Dispatch +import Foundation +import SwiftASN1 + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import _CryptoExtras +@_implementationOnly import Crypto +@_implementationOnly import X509 +#else +import _CryptoExtras +import Crypto +import X509 +#endif + +public struct ManifestSignature: Equatable, Codable { + /// The signature + public let signature: String + + /// Details about the certificate used to generate the signature + public let certificate: Certificate + + public init(signature: String, certificate: Certificate) { + self.signature = signature + self.certificate = certificate + } + + public struct Certificate: Equatable, Codable { + /// Subject of the certificate + public let subject: Name + + /// Issuer of the certificate + public let issuer: Name + + /// Creates a `Certificate` + public init(subject: Name, issuer: Name) { + self.subject = subject + self.issuer = issuer + } + + /// Generic certificate name (e.g., subject, issuer) + public struct Name: Equatable, Codable { + /// User ID + public let userID: String? + + /// Common name + public let commonName: String? + + /// Organizational unit + public let organizationalUnit: String? + + /// Organization + public let organization: String? + + /// Creates a `Name` + public init(userID: String?, + commonName: String?, + organizationalUnit: String?, + organization: String?) { + self.userID = userID + self.commonName = commonName + self.organizationalUnit = organizationalUnit + self.organization = organization + } + } + } +} + +public protocol ManifestSigner { + /// Signs package collection using the given certificate and key. + /// + /// - Parameters: + /// - collection: The package collection to be signed + /// - certChainPaths: Paths to all DER-encoded certificates in the chain. The certificate used for signing + /// must be the first in the array. + /// - privateKeyPEM: Data of the private key (*.pem) of the certificate + /// - certPolicyKey: The key of the `CertificatePolicy` to use for validating certificates + func sign( + manifest: some Encodable, + certChainPaths: [AbsolutePath], + privateKeyPEM: Data, + fileSystem: FileSystem, + certPolicyKey: CertificatePolicyKey + ) async throws -> ManifestSignature +} + +extension ManifestSigner { + /// Signs package collection using the given certificate and key. + /// + /// - Parameters: + /// - collection: The package collection to be signed + /// - certChainPaths: Paths to all DER-encoded certificates in the chain. The certificate used for signing + /// must be the first in the array. + /// - certPrivateKeyPath: Path to the private key (*.pem) of the certificate + /// - certPolicyKey: The key of the `CertificatePolicy` to use for validating certificates + public func sign( + manifest: some Encodable, + certChainPaths: [AbsolutePath], + certPrivateKeyPath: AbsolutePath, + fileSystem: FileSystem, + certPolicyKey: CertificatePolicyKey = .default + ) async throws -> ManifestSignature { + let privateKey: Data = try fileSystem.readFileContents(certPrivateKeyPath) + return try await self.sign( + manifest: manifest, + certChainPaths: certChainPaths, + privateKeyPEM: privateKey, + fileSystem: fileSystem, + certPolicyKey: certPolicyKey + ) + } +} + +public protocol ManifestSignatureValidator { + /// Validates a signed package collection. + /// + /// - Parameters: + /// - signedCollection: The signed package collection + /// - certPolicyKey: The key of the `CertificatePolicy` to use for validating certificates + func validate( + manifest: any Encodable, + signature: ManifestSignature, + fileSystem: FileSystem, + certPolicyKey: CertificatePolicyKey + ) async throws +} + +// MARK: - Implementation + +public actor ManifestSigning: ManifestSigner, ManifestSignatureValidator { + private static let minimumRSAKeySizeInBits = 2048 + + /// Path of the optional directory containing root certificates to be trusted. + private let trustedRootCertsDir: AbsolutePath? + /// Root certificates to be trusted in additional to those found in `trustedRootCertsDir` + private let additionalTrustedRootCerts: [Certificate]? + + /// Internal cache/storage of `CertificatePolicy`s + private let certPolicies: [CertificatePolicyKey: CertificatePolicy] + + private let encoder: JSONEncoder + private let decoder: JSONDecoder + + private let observabilityScope: ObservabilityScope + + public init( + trustedRootCertsDir: AbsolutePath? = nil, + additionalTrustedRootCerts: [String]? = nil, + observabilityScope: ObservabilityScope + ) { + self.trustedRootCertsDir = trustedRootCertsDir + self.additionalTrustedRootCerts = additionalTrustedRootCerts.map { $0.compactMap { + guard let data = Data(base64Encoded: $0) else { + observabilityScope.emit(error: "The certificate \($0) is not in valid base64 encoding") + return nil + } + do { + return try Certificate(derEncoded: Array(data)) + } catch { + observabilityScope.emit( + error: "The certificate \($0) is not in valid DER format", + underlyingError: error + ) + return nil + } + } } + + self.certPolicies = [:] + self.encoder = JSONEncoder.makeWithDefaults() + self.decoder = JSONDecoder.makeWithDefaults() + self.observabilityScope = observabilityScope + } + + init(certPolicy: CertificatePolicy, observabilityScope: ObservabilityScope) { + // These should be set through the given CertificatePolicy + self.trustedRootCertsDir = nil + self.additionalTrustedRootCerts = nil + + self.certPolicies = [CertificatePolicyKey.custom: certPolicy] + self.encoder = JSONEncoder.makeWithDefaults() + self.decoder = JSONDecoder.makeWithDefaults() + self.observabilityScope = observabilityScope + } + + private func getCertificatePolicy(key: CertificatePolicyKey, fileSystem: FileSystem) throws -> CertificatePolicy { + switch key { + case .default(let subjectUserID, let subjectOrganizationalUnit): + // Create new instance each time since contents of trustedRootCertsDir might change + return DefaultCertificatePolicy( + trustedRootCertsDir: self.trustedRootCertsDir, + fileSystem: fileSystem, + additionalTrustedRootCerts: self.additionalTrustedRootCerts, + expectedSubjectUserID: subjectUserID, + expectedSubjectOrganizationalUnit: subjectOrganizationalUnit, + observabilityScope: self.observabilityScope + ) + case .appleSwiftPackageCollection(let subjectUserID, let subjectOrganizationalUnit): + // Create new instance each time since contents of trustedRootCertsDir might change + return ADPSwiftPackageCollectionCertificatePolicy( + trustedRootCertsDir: self.trustedRootCertsDir, + fileSystem: fileSystem, + additionalTrustedRootCerts: self.additionalTrustedRootCerts, + expectedSubjectUserID: subjectUserID, + expectedSubjectOrganizationalUnit: subjectOrganizationalUnit, + observabilityScope: self.observabilityScope + ) + case .appleDistribution(let subjectUserID, let subjectOrganizationalUnit): + // Create new instance each time since contents of trustedRootCertsDir might change + return ADPAppleDistributionCertificatePolicy( + trustedRootCertsDir: self.trustedRootCertsDir, + fileSystem: fileSystem, + additionalTrustedRootCerts: self.additionalTrustedRootCerts, + expectedSubjectUserID: subjectUserID, + expectedSubjectOrganizationalUnit: subjectOrganizationalUnit, + observabilityScope: self.observabilityScope + ) + case .custom: + // Custom `CertificatePolicy` can be set using the internal initializer only + guard let certPolicy = self.certPolicies[key] else { + throw ManifestSigningError.certPolicyNotFound + } + return certPolicy + } + } + + public func sign( + manifest: some Encodable, + certChainPaths: [AbsolutePath], + privateKeyPEM: Data, + fileSystem: FileSystem, + certPolicyKey: CertificatePolicyKey = .default + ) async throws -> ManifestSignature { + let certChainData: [Data] = try certChainPaths.map { try fileSystem.readFileContents($0) } + // Check that the certificate is valid + let certChain = try await self.validateCertChain(certChainData, certPolicyKey: certPolicyKey, fileSystem: fileSystem) + + let privateKeyPEMString = String(decoding: privateKeyPEM, as: UTF8.self) + + let signatureAlgorithm: Signature.Algorithm + let signatureProvider: (Data) throws -> Data + // Determine key type + do { + let privateKey = try P256.Signing.PrivateKey(pemRepresentation: privateKeyPEMString) + signatureAlgorithm = .ES256 + signatureProvider = { + try privateKey.signature(for: SHA256.hash(data: $0)).rawRepresentation + } + } catch { + do { + let privateKey = try _RSA.Signing.PrivateKey(pemRepresentation: privateKeyPEMString) + + guard privateKey.keySizeInBits >= Self.minimumRSAKeySizeInBits else { + throw ManifestSigningError.invalidKeySize(minimumBits: Self.minimumRSAKeySizeInBits) + } + + signatureAlgorithm = .RS256 + signatureProvider = { + try privateKey.signature(for: SHA256.hash(data: $0), padding: Signature.rsaSigningPadding) + .rawRepresentation + } + } catch let error as ManifestSigningError { + throw error + } catch { + throw ManifestSigningError.unsupportedKeyType + } + } + + // Generate signature + let signatureData = try Signature.generate( + payload: manifest, + certChainData: certChainData, + jsonEncoder: self.encoder, + signatureAlgorithm: signatureAlgorithm, + signatureProvider: signatureProvider + ) + + guard let signature = String(bytes: signatureData, encoding: .utf8) else { + throw ManifestSigningError.invalidSignature + } + + let certificate = certChain.first! // !-safe because certChain cannot be empty at this point + return ManifestSignature( + signature: signature, + certificate: ManifestSignature.Certificate( + subject: ManifestSignature.Certificate.Name(from: certificate.subject), + issuer: ManifestSignature.Certificate.Name(from: certificate.issuer) + ) + ) + } + + public func validate( + manifest: any Encodable, + signature: ManifestSignature, + fileSystem: FileSystem, + certPolicyKey: CertificatePolicyKey = .default + ) async throws { + let signatureBytes = Data(signature.signature.utf8).copyBytes() + + // Parse the signature + let certChainValidate = { certChainData in + try await self.validateCertChain(certChainData, certPolicyKey: certPolicyKey, fileSystem: fileSystem) + } + let signature = try await Signature.parse( + signatureBytes, + certChainValidate: certChainValidate, + jsonDecoder: self.decoder + ) + + // Verify the collection embedded in the signature is the same as received + // i.e., the signature is associated with the given collection and not another + guard try self.encoder.encode(manifest) == signature.payload else { + throw ManifestSigningError.invalidSignature + } + } + + private func validateCertChain( + _ certChainData: [Data], + certPolicyKey: CertificatePolicyKey, + fileSystem: FileSystem + ) async throws -> [Certificate] { + guard !certChainData.isEmpty else { + throw ManifestSigningError.emptyCertChain + } + + do { + let certChain = try certChainData.map { try Certificate(derEncoded: Array($0)) } + let certPolicy = try self.getCertificatePolicy(key: certPolicyKey, fileSystem: fileSystem) + + do { + try await certPolicy.validate(certChain: certChain) + return certChain + } catch { + self.observabilityScope.emit( + error: "\(certPolicyKey): The certificate chain is invalid", + underlyingError: error + ) + + if CertificatePolicyError.noTrustedRootCertsConfigured == error as? CertificatePolicyError { + throw ManifestSigningError.noTrustedRootCertsConfigured + } else { + throw ManifestSigningError.invalidCertChain + } + } + } catch let error as ManifestSigningError { + throw error + } catch { + self.observabilityScope.emit( + error: "An error has occurred while validating certificate chain", + underlyingError: error + ) + throw ManifestSigningError.invalidCertChain + } + } +} + +public enum ManifestSigningError: Error, Equatable { + case certPolicyNotFound + case emptyCertChain + case noTrustedRootCertsConfigured + case invalidCertChain + + case invalidSignature + + case unsupportedKeyType + case invalidKeySize(minimumBits: Int) +} + +extension ManifestSignature.Certificate.Name { + fileprivate init(from name: DistinguishedName) { + self.init( + userID: name.userID, + commonName: name.commonName, + organizationalUnit: name.organizationalUnitName, + organization: name.organizationName + ) + } +} diff --git a/Sources/Workspace/ManifestSigning/Signature.swift b/Sources/Workspace/ManifestSigning/Signature.swift new file mode 100644 index 00000000000..76e27d45d10 --- /dev/null +++ b/Sources/Workspace/ManifestSigning/Signature.swift @@ -0,0 +1,187 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// +// This source file is part of the Vapor open source project +// +// Copyright (c) 2017-2020 Vapor project authors +// Licensed under MIT +// +// See LICENSE for license information +// +// SPDX-License-Identifier: MIT +// +//===----------------------------------------------------------------------===// + +import Foundation + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import _CryptoExtras +@_implementationOnly import Crypto +@_implementationOnly import X509 +#else +import _CryptoExtras +import Crypto +import X509 +#endif + +// The logic in this source file loosely follows https://www.rfc-editor.org/rfc/rfc7515.html +// for JSON Web Signature (JWS). + +struct Signature { + let header: Header + let payload: Data + let signature: Data +} + +extension Signature { + enum Algorithm: String, Codable { + case RS256 // RSASSA-PKCS1-v1_5 using SHA-256 + case ES256 // ECDSA using P-256 and SHA-256 + } + + struct Header: Equatable, Codable { + // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.1 + let algorithm: Algorithm + + /// Base64 encoded certificate chain + let certChain: [String] + + enum CodingKeys: String, CodingKey { + case algorithm = "alg" + case certChain = "x5c" + } + } +} + +// Reference: https://github.com/vapor/jwt-kit/blob/master/Sources/JWTKit/JWTSerializer.swift +extension Signature { + static let rsaSigningPadding = _RSA.Signing.Padding.insecurePKCS1v1_5 + + static func generate( + payload: some Encodable, + certChainData: [Data], + jsonEncoder: JSONEncoder, + signatureAlgorithm: Signature.Algorithm, + signatureProvider: @escaping (Data) throws -> Data + ) throws -> Data { + let header = Signature.Header( + algorithm: signatureAlgorithm, + certChain: certChainData.map { $0.base64EncodedString() } + ) + let headerData = try jsonEncoder.encode(header) + let encodedHeader = headerData.base64URLEncodedBytes() + + let payloadData = try jsonEncoder.encode(payload) + let encodedPayload = payloadData.base64URLEncodedBytes() + + // https://www.rfc-editor.org/rfc/rfc7515.html#section-5.1 + // Signing input: BASE64URL(header) + '.' + BASE64URL(payload) + let signatureData = try signatureProvider(encodedHeader + .period + encodedPayload) + let encodedSignature = signatureData.base64URLEncodedBytes() + + // Result: header.payload.signature + let bytes = encodedHeader + + .period + + encodedPayload + + .period + + encodedSignature + return bytes + } +} + +// Reference: https://github.com/vapor/jwt-kit/blob/master/Sources/JWTKit/JWTParser.swift +extension Signature { + typealias CertChainValidate = ([Data]) async throws -> [Certificate] + + static func parse( + _ signature: String, + certChainValidate: CertChainValidate, + jsonDecoder: JSONDecoder + ) async throws -> Signature { + let bytes = Array(signature.utf8) + return try await Self.parse(bytes, certChainValidate: certChainValidate, jsonDecoder: jsonDecoder) + } + + static func parse( + _ signature: some DataProtocol, + certChainValidate: CertChainValidate, + jsonDecoder: JSONDecoder + ) async throws -> Signature { + let parts = signature.copyBytes().split(separator: .period) + guard parts.count == 3 else { + throw SignatureError.malformedSignature + } + + let encodedHeader = parts[0] + let encodedPayload = parts[1] + let encodedSignature = parts[2] + + guard let headerBytes = encodedHeader.base64URLDecodedBytes(), + let header = try? jsonDecoder.decode(Header.self, from: headerBytes) + else { + throw SignatureError.malformedSignature + } + + // Signature header contains the certificate and public key for verification + let certChainData = header.certChain.compactMap { Data(base64Encoded: $0) } + // Make sure we restore all certs successfully + guard certChainData.count == header.certChain.count else { + throw SignatureError.malformedSignature + } + + let certChain = try await certChainValidate(certChainData) + + guard let payloadBytes = encodedPayload.base64URLDecodedBytes(), + let signatureBytes = encodedSignature.base64URLDecodedBytes() + else { + throw SignatureError.malformedSignature + } + + // Extract public key from the certificate + let certificate = certChain.first! // !-safe because certChain is not empty at this point + // Verify the key was used to generate the signature + let message = Data(encodedHeader) + .period + Data(encodedPayload) + let digest = SHA256.hash(data: message) + + switch header.algorithm { + case .ES256: + guard let publicKey = P256.Signing.PublicKey(certificate.publicKey) else { + throw SignatureError.invalidPublicKey + } + guard try publicKey.isValidSignature(.init(rawRepresentation: signatureBytes), for: digest) + else { + throw SignatureError.invalidSignature + } + case .RS256: + guard let publicKey = _RSA.Signing.PublicKey(certificate.publicKey) else { + throw SignatureError.invalidPublicKey + } + guard publicKey.isValidSignature( + .init(rawRepresentation: signatureBytes), + for: digest, + padding: .insecurePKCS1v1_5 + ) else { + throw SignatureError.invalidSignature + } + } + + return Signature(header: header, payload: payloadBytes, signature: signatureBytes) + } +} + +enum SignatureError: Error { + case malformedSignature + case invalidSignature + case invalidPublicKey +} diff --git a/Sources/Workspace/ManifestSigning/Utilities.swift b/Sources/Workspace/ManifestSigning/Utilities.swift new file mode 100644 index 00000000000..625b7930d45 --- /dev/null +++ b/Sources/Workspace/ManifestSigning/Utilities.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +extension DataProtocol { + func copyBytes() -> [UInt8] { + [UInt8](unsafeUninitializedCapacity: self.count) { buffer, initializedCount in + self.copyBytes(to: buffer) + initializedCount = self.count + } + } +} + +extension UInt8 { + static var period: UInt8 { + UInt8(ascii: ".") + } +} + +/// Cannot use `extension Data` if `period` is going to be used with +/// `+` operator via leading-dot syntax, for example: `Data(...) + .period` +/// because `+` is declared as `(Self, Other) -> Self` where +/// `Other: RangeReplaceableCollection, Other.Element == Self.Element` +/// which means that `.period` couldn't get `Data` inferred from the first argument. +extension RangeReplaceableCollection where Self == Data { + static var period: Data { + Data([UInt8.period]) + } +} diff --git a/Sources/Workspace/ManifestSigning/X509Extensions.swift b/Sources/Workspace/ManifestSigning/X509Extensions.swift new file mode 100644 index 00000000000..bef5d7ca1ce --- /dev/null +++ b/Sources/Workspace/ManifestSigning/X509Extensions.swift @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if USE_IMPL_ONLY_IMPORTS +@_implementationOnly import SwiftASN1 +@_implementationOnly import X509 +#else +import SwiftASN1 +import X509 +#endif + +extension Certificate { + func hasExtension(oid: ASN1ObjectIdentifier) -> Bool { + self.extensions[oid: oid] != nil + } +} + +extension ASN1ObjectIdentifier.NameAttributes { + static let userID: ASN1ObjectIdentifier = [0, 9, 2342, 19_200_300, 100, 1, 1] + + // Marker OIDs for ADP certificates + static let adpSwiftPackageMarker: ASN1ObjectIdentifier = [1, 2, 840, 113_635, 100, 6, 1, 35] + static let adpSwiftPackageCollectionMarker: ASN1ObjectIdentifier = [1, 2, 840, 113_635, 100, 6, 1, 35] + static let adpAppleDevelopmentMarkers: [ASN1ObjectIdentifier] = [ + [1, 2, 840, 113_635, 100, 6, 1, 2], + [1, 2, 840, 113_635, 100, 6, 1, 12], + ] + static let adpAppleDistributionMarkers: [ASN1ObjectIdentifier] = [ + [1, 2, 840, 113_635, 100, 6, 1, 4], + [1, 2, 840, 113_635, 100, 6, 1, 7], + ] + static let wwdrIntermediateMarkers: [ASN1ObjectIdentifier] = [ + [1, 2, 840, 113_635, 100, 6, 2, 1], + [1, 2, 840, 113_635, 100, 6, 2, 15], + ] +} + +extension DistinguishedName { + var userID: String? { + self.stringAttribute(oid: ASN1ObjectIdentifier.NameAttributes.userID) + } + + var commonName: String? { + self.stringAttribute(oid: ASN1ObjectIdentifier.NameAttributes.commonName) + } + + var organizationalUnitName: String? { + self.stringAttribute(oid: ASN1ObjectIdentifier.NameAttributes.organizationalUnitName) + } + + var organizationName: String? { + self.stringAttribute(oid: ASN1ObjectIdentifier.NameAttributes.organizationName) + } + + private func stringAttribute(oid: ASN1ObjectIdentifier) -> String? { + for relativeDistinguishedName in self { + for attribute in relativeDistinguishedName where attribute.type == oid { + return attribute.value.description + } + } + return nil + } +} diff --git a/Sources/Workspace/ManifestSigning/embedded_resources.swift b/Sources/Workspace/ManifestSigning/embedded_resources.swift new file mode 100644 index 00000000000..a1dbeb69b2f --- /dev/null +++ b/Sources/Workspace/ManifestSigning/embedded_resources.swift @@ -0,0 +1,13 @@ +struct PackageResources { +static let AppleWWDRCAG2_cer: [UInt8] = [48,130,2,247,48,130,2,124,160,3,2,1,2,2,8,111,239,216,245,233,163,167,238,48,10,6,8,42,134,72,206,61,4,3,2,48,103,49,27,48,25,6,3,85,4,3,12,18,65,112,112,108,101,32,82,111,111,116,32,67,65,32,45,32,71,51,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,30,23,13,49,52,48,53,48,54,50,51,52,51,50,52,90,23,13,50,57,48,53,48,54,50,51,52,51,50,52,90,48,129,128,49,52,48,50,6,3,85,4,3,12,43,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,65,32,45,32,71,50,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,89,48,19,6,7,42,134,72,206,61,2,1,6,8,42,134,72,206,61,3,1,7,3,66,0,4,221,240,183,6,75,207,221,115,4,19,196,67,137,3,147,128,251,94,246,116,173,66,97,82,88,2,38,83,25,200,99,34,7,9,82,97,202,196,217,87,239,109,38,104,139,116,145,140,196,249,128,104,40,252,9,104,240,16,218,233,208,46,201,26,163,129,247,48,129,244,48,70,6,8,43,6,1,5,5,7,1,1,4,58,48,56,48,54,6,8,43,6,1,5,5,7,48,1,134,42,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,52,45,97,112,112,108,101,114,111,111,116,99,97,103,51,48,29,6,3,85,29,14,4,22,4,20,132,182,132,204,58,134,98,114,22,89,148,232,26,163,189,72,223,58,223,11,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,255,48,31,6,3,85,29,35,4,24,48,22,128,20,187,176,222,161,88,51,136,154,164,138,153,222,190,189,235,175,218,203,36,171,48,55,6,3,85,29,31,4,48,48,46,48,44,160,42,160,40,134,38,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,97,112,112,108,101,114,111,111,116,99,97,103,51,46,99,114,108,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,15,4,2,5,0,48,10,6,8,42,134,72,206,61,4,3,2,3,105,0,48,102,2,49,0,217,177,199,49,198,35,246,79,77,208,217,71,197,125,24,105,17,135,113,39,211,104,173,224,155,101,154,190,108,223,63,70,166,144,9,190,110,161,59,44,176,81,137,65,60,135,210,191,2,49,0,252,100,82,137,75,60,93,191,107,28,194,137,152,47,114,174,181,155,170,26,11,35,84,119,84,232,21,67,162,197,18,218,151,80,115,124,94,208,110,178,92,98,80,215,81,241,58,90] +static let AppleWWDRCAG3_cer: [UInt8] = [48,130,4,81,48,130,3,57,160,3,2,1,2,2,16,124,175,105,10,37,183,57,254,123,155,68,122,193,120,197,238,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,30,23,13,50,48,48,50,49,57,49,56,49,51,52,55,90,23,13,51,48,48,50,50,48,48,48,48,48,48,48,90,48,117,49,68,48,66,6,3,85,4,3,12,59,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,11,48,9,6,3,85,4,11,12,2,71,51,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,216,245,137,252,168,89,11,135,199,76,145,46,45,86,144,211,120,29,164,48,233,165,72,239,11,67,191,45,132,251,36,93,69,27,229,235,89,54,18,92,84,100,158,108,98,45,180,198,151,202,204,60,246,214,6,145,252,229,2,166,28,106,180,121,213,103,203,172,233,63,7,17,225,132,188,71,29,102,142,57,162,232,180,73,237,58,210,225,16,96,122,142,147,140,202,192,218,12,192,131,208,227,249,145,214,167,140,200,193,115,226,174,70,209,98,157,146,168,144,96,55,125,104,150,205,141,224,252,155,250,120,187,227,123,175,45,23,221,91,254,128,89,35,117,23,184,18,193,237,27,229,52,206,254,34,181,158,73,176,229,85,191,143,132,162,60,168,186,197,134,149,22,228,0,201,159,3,208,204,179,58,22,123,214,5,92,157,235,71,174,13,181,45,140,14,105,77,64,208,75,76,59,4,158,141,33,177,172,46,67,84,48,206,193,138,134,148,152,75,223,106,13,63,254,251,28,174,151,23,194,120,10,48,224,95,31,84,59,73,183,37,0,26,55,130,210,86,151,165,82,73,124,96,56,155,239,2,3,1,0,1,163,129,239,48,129,236,48,18,6,3,85,29,19,1,1,255,4,8,48,6,1,1,255,2,1,0,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,68,6,8,43,6,1,5,5,7,1,1,4,56,48,54,48,52,6,8,43,6,1,5,5,7,48,1,134,40,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,51,45,97,112,112,108,101,114,111,111,116,99,97,48,46,6,3,85,29,31,4,39,48,37,48,35,160,33,160,31,134,29,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,114,111,111,116,46,99,114,108,48,29,6,3,85,29,14,4,22,4,20,9,254,192,21,144,249,175,100,10,146,18,185,38,40,99,12,151,236,167,178,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,1,4,2,5,0,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,173,101,19,232,246,224,129,119,68,2,71,66,190,95,165,57,32,234,98,169,253,197,144,201,115,19,213,158,155,208,170,15,141,141,203,237,1,207,108,40,64,91,199,85,36,65,248,252,207,193,181,35,233,220,236,241,111,202,128,29,119,194,196,97,73,37,103,175,15,202,57,37,173,211,227,122,204,51,40,13,14,46,161,87,64,115,250,230,92,174,6,81,41,237,227,133,12,79,97,220,50,22,139,119,208,68,202,93,114,3,49,70,156,174,155,64,26,250,244,224,211,62,250,47,140,102,159,151,196,84,89,239,210,72,244,7,153,73,96,89,25,199,221,148,209,192,193,108,127,120,33,239,12,235,59,108,153,130,75,82,96,56,181,57,130,109,236,82,49,83,190,15,145,76,73,73,116,143,166,81,203,132,71,78,29,117,39,110,189,249,210,92,243,127,194,108,11,9,54,226,100,228,194,55,3,20,25,213,234,106,148,170,169,219,254,246,154,8,103,139,239,43,184,170,23,73,131,175,207,203,188,233,207,234,149,113,176,180,69,162,204,229,135,170,10,195,65,58,121,92,218,80,52,157,149,59] +static let AppleWWDRCAG5_cer: [UInt8] = [48,130,4,85,48,130,3,61,160,3,2,1,2,2,20,59,126,128,10,238,211,2,161,230,236,219,151,217,202,172,40,156,241,105,148,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,30,23,13,50,48,49,50,49,54,49,57,51,56,53,54,90,23,13,51,48,49,50,49,48,48,48,48,48,48,48,90,48,117,49,68,48,66,6,3,85,4,3,12,59,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,11,48,9,6,3,85,4,11,12,2,71,53,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,159,93,218,31,251,250,188,208,75,34,60,199,214,38,250,23,247,244,159,30,62,100,17,102,154,239,94,190,171,52,189,170,44,33,110,214,30,148,33,133,44,116,193,14,16,97,170,60,99,74,126,74,227,200,117,214,207,194,156,8,72,123,106,185,41,177,201,102,253,200,138,22,209,178,4,60,130,186,128,108,226,255,10,114,235,172,254,59,111,72,190,158,139,17,77,149,125,178,123,144,47,2,28,78,252,73,190,215,111,181,79,190,120,198,218,222,178,205,201,217,102,17,59,90,233,9,69,115,137,80,212,78,226,104,5,126,218,117,156,75,1,174,218,227,207,109,116,77,222,83,208,213,26,56,130,58,208,157,227,93,41,123,177,129,234,41,39,11,251,96,124,134,18,251,240,98,79,6,231,76,209,31,217,109,158,237,115,218,120,33,13,157,112,217,70,87,144,95,103,132,193,115,119,10,168,98,98,182,174,112,134,187,117,35,137,173,121,246,230,164,212,253,131,38,50,10,59,36,146,91,32,161,156,209,47,128,24,155,221,60,11,231,181,253,236,128,87,15,23,158,63,43,135,39,187,247,2,3,1,0,1,163,129,239,48,129,236,48,18,6,3,85,29,19,1,1,255,4,8,48,6,1,1,255,2,1,0,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,68,6,8,43,6,1,5,5,7,1,1,4,56,48,54,48,52,6,8,43,6,1,5,5,7,48,1,134,40,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,51,45,97,112,112,108,101,114,111,111,116,99,97,48,46,6,3,85,29,31,4,39,48,37,48,35,160,33,160,31,134,29,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,114,111,111,116,46,99,114,108,48,29,6,3,85,29,14,4,22,4,20,25,139,151,141,74,91,97,120,87,244,165,92,53,18,138,57,8,227,176,117,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,1,4,2,5,0,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,90,196,53,162,217,230,10,158,82,162,0,130,34,119,205,114,47,144,249,34,175,20,37,204,170,10,66,96,146,194,189,117,29,235,76,202,21,229,126,107,64,16,94,251,91,69,116,13,240,122,125,136,145,206,222,99,139,66,137,210,24,223,65,179,88,158,6,8,135,72,221,64,175,35,213,237,235,44,96,229,235,164,127,28,233,156,131,214,148,95,76,98,57,138,78,174,19,141,125,67,184,220,94,172,190,24,64,242,230,15,44,93,19,147,241,123,101,168,194,104,72,222,255,180,150,155,12,251,50,54,158,158,13,149,95,56,10,139,150,210,18,108,183,139,11,142,215,62,236,111,1,170,206,77,69,96,219,132,113,102,98,229,25,101,48,205,138,255,69,83,251,110,251,136,119,67,195,72,99,249,247,81,179,114,94,121,226,86,207,188,187,132,185,56,90,120,174,17,72,82,187,13,53,139,148,222,204,116,154,104,245,73,96,148,172,242,168,179,21,134,144,208,204,48,67,134,211,229,112,2,81,180,129,192,168,151,212,234,234,176,140,9,64,239,56,147,178,205,34,3,235,103,67,101,200,69] +static let AppleRootCA_G2_cer: [UInt8] = [48,130,5,146,48,130,3,122,160,3,2,1,2,2,8,1,224,229,181,131,103,163,224,48,13,6,9,42,134,72,134,247,13,1,1,12,5,0,48,103,49,27,48,25,6,3,85,4,3,12,18,65,112,112,108,101,32,82,111,111,116,32,67,65,32,45,32,71,50,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,30,23,13,49,52,48,52,51,48,49,56,49,48,48,57,90,23,13,51,57,48,52,51,48,49,56,49,48,48,57,90,48,103,49,27,48,25,6,3,85,4,3,12,18,65,112,112,108,101,32,82,111,111,116,32,67,65,32,45,32,71,50,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,130,2,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,2,15,0,48,130,2,10,2,130,2,1,0,216,17,18,72,72,218,41,138,73,197,28,199,236,110,51,109,254,77,251,224,28,222,172,94,226,54,167,36,249,127,80,107,76,206,185,48,84,39,229,179,214,237,37,230,48,182,5,55,94,20,34,17,197,232,170,27,210,251,178,210,9,149,56,164,239,42,73,140,93,62,113,102,3,56,251,22,245,133,136,228,90,146,12,4,50,242,200,64,251,82,95,159,246,192,241,227,186,69,160,80,213,18,139,242,221,222,145,134,35,240,245,182,114,46,1,218,11,246,46,57,8,95,25,161,99,65,11,28,167,148,193,134,196,83,47,118,246,10,215,12,209,131,63,26,83,25,243,87,213,39,127,252,19,184,248,146,141,252,211,40,67,60,181,104,0,37,93,39,98,211,221,85,221,68,32,144,131,53,147,197,191,184,25,251,107,227,220,8,66,230,175,109,250,158,64,202,78,133,133,120,73,177,215,195,193,48,57,50,171,126,95,170,211,139,111,159,45,26,33,104,112,103,179,163,241,152,65,109,145,124,248,215,219,168,231,95,33,26,140,51,191,49,116,183,184,209,244,224,34,244,191,114,52,223,247,129,77,113,125,81,161,226,179,240,211,40,22,115,111,205,204,173,55,125,78,235,173,64,225,63,129,253,247,61,10,62,162,241,189,49,150,41,89,220,194,25,128,140,91,116,198,44,211,16,83,38,29,20,79,196,212,129,102,60,135,103,51,39,20,8,233,180,119,132,52,82,143,137,248,104,152,23,191,195,187,170,19,147,31,93,84,47,168,199,124,251,13,20,190,21,61,36,52,242,154,220,117,65,102,34,180,1,214,11,175,144,158,12,234,98,248,155,89,60,8,226,150,52,228,99,222,188,55,212,235,12,136,3,67,11,80,175,160,52,221,80,77,21,251,90,36,216,12,250,12,99,158,31,3,177,225,238,225,170,67,244,102,101,40,55,2,49,239,1,199,30,209,204,159,109,202,84,58,64,219,206,207,79,70,139,74,101,154,106,198,104,108,215,204,153,27,71,176,114,195,119,143,196,247,97,156,116,31,206,253,107,161,194,156,148,130,171,148,162,231,189,27,186,185,112,57,149,23,197,41,243,57,88,52,245,196,164,198,123,96,185,102,67,80,63,110,97,252,14,249,134,170,96,12,67,75,149,2,3,1,0,1,163,66,48,64,48,29,6,3,85,29,14,4,22,4,20,196,153,19,108,24,3,194,123,192,163,160,13,127,114,128,122,28,119,38,141,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,255,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,13,6,9,42,134,72,134,247,13,1,1,12,5,0,3,130,2,1,0,81,166,243,226,244,184,61,147,191,45,206,15,187,91,225,85,20,78,78,209,229,206,121,93,129,127,254,182,240,135,51,248,239,148,229,126,220,106,121,167,28,190,240,148,183,166,209,48,156,200,13,10,117,158,125,146,149,126,24,157,126,194,113,105,124,20,234,207,131,14,228,20,66,158,116,14,16,205,171,26,186,17,97,129,120,216,241,181,69,64,120,171,168,192,206,251,125,99,55,104,246,231,251,175,198,195,75,236,31,54,38,19,84,134,148,114,178,234,2,237,139,109,228,12,166,144,192,87,117,207,140,66,125,92,230,49,125,243,201,178,146,105,70,14,136,248,227,45,66,178,56,168,166,25,141,241,159,205,238,106,101,188,26,176,37,189,167,41,253,244,62,162,117,73,191,158,219,201,247,167,30,99,153,225,92,70,255,146,5,140,250,30,32,249,134,148,86,37,229,180,87,56,157,235,136,100,20,33,73,33,57,191,98,102,169,177,162,202,111,63,33,96,197,137,212,69,54,200,152,124,189,246,254,153,73,128,59,44,210,166,167,136,3,4,49,25,183,182,58,97,69,250,201,242,35,200,99,115,191,86,137,49,176,217,124,98,167,123,21,168,136,138,171,56,64,194,204,18,255,21,227,240,55,223,55,114,203,204,152,230,191,162,188,250,38,138,113,86,215,231,36,27,72,68,62,158,252,159,201,204,26,236,67,60,1,188,52,120,200,105,245,198,230,86,236,6,9,54,144,235,20,74,27,94,201,136,35,218,3,48,145,11,184,54,62,249,231,181,40,111,190,63,236,60,143,101,29,229,192,30,135,164,170,186,152,253,146,227,108,38,119,221,6,180,100,6,135,244,78,214,186,74,170,22,168,244,5,103,102,150,186,226,85,121,195,44,93,73,143,128,73,43,138,18,199,118,128,81,223,186,189,101,93,62,55,71,99,49,233,229,244,197,63,75,173,4,138,122,113,44,175,9,67,55,15,168,227,50,79,244,69,182,109,151,54,236,132,245,10,1,234,23,187,133,141,66,147,112,195,80,229,20,139,191,63,195,65,15,221,34,4,35,8,138,186,109,113,68,171,115,9,58,201,249,82,128,9,223,186,233,230,22,202,46,46,76,178,211,220,229,4,84,178,212,52,128,50,181,188,15,23,225] +static let AppleComputerRootCertificate_cer: [UInt8] = [48,130,5,186,48,130,4,162,160,3,2,1,2,2,1,1,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,48,129,134,49,11,48,9,6,3,85,4,6,19,2,85,83,49,29,48,27,6,3,85,4,10,19,20,65,112,112,108,101,32,67,111,109,112,117,116,101,114,44,32,73,110,99,46,49,45,48,43,6,3,85,4,11,19,36,65,112,112,108,101,32,67,111,109,112,117,116,101,114,32,67,101,114,116,105,102,105,99,97,116,101,32,65,117,116,104,111,114,105,116,121,49,41,48,39,6,3,85,4,3,19,32,65,112,112,108,101,32,82,111,111,116,32,67,101,114,116,105,102,105,99,97,116,101,32,65,117,116,104,111,114,105,116,121,48,30,23,13,48,53,48,50,49,48,48,48,49,56,49,52,90,23,13,50,53,48,50,49,48,48,48,49,56,49,52,90,48,129,134,49,11,48,9,6,3,85,4,6,19,2,85,83,49,29,48,27,6,3,85,4,10,19,20,65,112,112,108,101,32,67,111,109,112,117,116,101,114,44,32,73,110,99,46,49,45,48,43,6,3,85,4,11,19,36,65,112,112,108,101,32,67,111,109,112,117,116,101,114,32,67,101,114,116,105,102,105,99,97,116,101,32,65,117,116,104,111,114,105,116,121,49,41,48,39,6,3,85,4,3,19,32,65,112,112,108,101,32,82,111,111,116,32,67,101,114,116,105,102,105,99,97,116,101,32,65,117,116,104,111,114,105,116,121,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,228,145,169,9,31,145,219,30,71,80,235,5,237,94,121,132,45,235,54,162,87,76,85,236,139,25,137,222,249,75,108,245,7,171,34,48,2,232,24,62,248,80,9,211,127,65,168,152,249,209,202,102,156,36,107,17,208,163,187,228,27,42,195,31,149,158,122,12,164,71,139,91,212,22,55,51,203,196,15,77,206,20,105,209,201,25,114,245,93,14,213,127,95,155,242,37,3,186,85,143,77,93,13,241,100,53,35,21,75,21,89,29,179,148,247,246,156,158,207,80,186,193,88,80,103,143,8,180,32,247,203,172,44,32,111,112,182,63,1,48,140,183,67,207,15,157,61,243,43,73,40,26,200,254,206,181,185,14,217,94,28,214,203,61,181,58,173,244,15,14,0,146,11,177,33,22,46,116,213,60,13,219,98,22,171,163,113,146,71,83,85,193,175,47,65,179,248,251,227,112,205,230,163,76,69,126,31,76,107,80,150,65,137,196,116,98,11,16,131,65,135,51,138,129,177,48,88,236,90,4,50,140,104,179,143,29,222,101,115,255,103,94,101,188,73,216,118,159,51,20,101,161,119,148,201,45,2,3,1,0,1,163,130,2,47,48,130,2,43,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,255,48,29,6,3,85,29,14,4,22,4,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,130,1,41,6,3,85,29,32,4,130,1,32,48,130,1,28,48,130,1,24,6,9,42,134,72,134,247,99,100,5,1,48,130,1,9,48,65,6,8,43,6,1,5,5,7,2,1,22,53,104,116,116,112,115,58,47,47,119,119,119,46,97,112,112,108,101,46,99,111,109,47,99,101,114,116,105,102,105,99,97,116,101,97,117,116,104,111,114,105,116,121,47,116,101,114,109,115,46,104,116,109,108,48,129,195,6,8,43,6,1,5,5,7,2,2,48,129,182,26,129,179,82,101,108,105,97,110,99,101,32,111,110,32,116,104,105,115,32,99,101,114,116,105,102,105,99,97,116,101,32,98,121,32,97,110,121,32,112,97,114,116,121,32,97,115,115,117,109,101,115,32,97,99,99,101,112,116,97,110,99,101,32,111,102,32,116,104,101,32,116,104,101,110,32,97,112,112,108,105,99,97,98,108,101,32,115,116,97,110,100,97,114,100,32,116,101,114,109,115,32,97,110,100,32,99,111,110,100,105,116,105,111,110,115,32,111,102,32,117,115,101,44,32,99,101,114,116,105,102,105,99,97,116,101,32,112,111,108,105,99,121,32,97,110,100,32,99,101,114,116,105,102,105,99,97,116,105,111,110,32,112,114,97,99,116,105,99,101,32,115,116,97,116,101,109,101,110,116,115,46,48,68,6,3,85,29,31,4,61,48,59,48,57,160,55,160,53,134,51,104,116,116,112,115,58,47,47,119,119,119,46,97,112,112,108,101,46,99,111,109,47,99,101,114,116,105,102,105,99,97,116,101,97,117,116,104,111,114,105,116,121,47,114,111,111,116,46,99,114,108,48,85,6,8,43,6,1,5,5,7,1,1,4,73,48,71,48,69,6,8,43,6,1,5,5,7,48,2,134,57,104,116,116,112,115,58,47,47,119,119,119,46,97,112,112,108,101,46,99,111,109,47,99,101,114,116,105,102,105,99,97,116,101,97,117,116,104,111,114,105,116,121,47,99,97,115,105,103,110,101,114,115,46,104,116,109,108,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,3,130,1,1,0,157,218,45,40,88,47,125,118,4,185,4,211,62,206,183,102,99,78,143,47,212,254,75,173,114,189,163,57,198,82,77,5,152,82,245,137,81,1,36,121,190,26,50,247,229,68,139,75,68,7,57,130,214,90,202,180,32,94,217,174,21,93,29,140,29,50,191,56,49,98,72,93,199,225,144,177,248,36,64,248,95,88,155,81,93,87,157,193,229,255,60,204,114,33,110,196,233,233,161,119,215,44,23,38,195,63,235,154,232,11,3,186,233,179,74,114,235,51,9,91,173,230,98,49,106,232,175,47,213,175,30,87,118,143,127,55,45,46,2,92,221,99,201,242,113,184,38,64,223,21,141,117,68,63,121,189,230,29,153,225,67,44,62,173,111,190,185,164,254,14,53,25,81,99,177,195,222,181,146,62,81,120,1,115,138,164,35,202,164,136,241,30,92,31,65,22,45,126,149,10,170,233,137,65,152,27,26,221,203,32,191,71,94,12,38,197,85,53,77,198,48,139,153,103,20,199,9,31,186,71,199,218,1,9,135,36,66,149,189,19,96,25,10,239,234,127,14,110,205,193,68,67,58,74,213,227] +static let AppleWWDRCAG7_cer: [UInt8] = [48,130,4,85,48,130,3,61,160,3,2,1,2,2,20,52,24,88,255,1,254,6,63,142,241,159,31,233,60,1,180,193,70,255,201,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,30,23,13,50,50,49,49,49,55,50,48,52,48,53,51,90,23,13,50,51,49,49,49,55,50,48,52,48,53,50,90,48,117,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,11,12,2,71,55,49,68,48,66,6,3,85,4,3,12,59,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,172,174,209,211,182,138,177,27,40,149,173,222,81,81,76,63,51,225,78,92,175,179,252,2,210,101,79,42,212,116,104,140,117,23,101,207,40,228,72,9,152,113,82,50,44,22,78,120,142,146,86,100,169,221,139,205,226,212,199,59,162,18,69,55,2,25,98,100,47,127,97,198,211,89,34,191,171,249,20,163,237,182,158,153,186,47,241,177,220,48,66,79,182,182,178,198,116,170,98,86,187,237,68,54,15,209,229,32,116,147,87,58,93,158,220,1,252,120,111,105,22,53,195,110,1,194,158,114,212,113,54,177,118,9,122,190,13,42,151,241,176,94,11,27,18,173,43,171,7,223,99,45,245,136,233,50,18,162,85,88,67,118,60,61,23,128,31,41,219,177,169,159,171,150,5,157,21,220,221,219,78,15,231,19,124,90,43,46,2,65,246,238,122,106,182,115,96,247,151,25,20,139,149,51,56,147,218,0,37,54,174,85,90,124,159,249,244,98,4,7,116,233,29,167,149,87,35,172,43,36,218,100,79,40,114,233,150,176,8,60,136,27,20,99,67,215,137,174,152,193,242,45,142,227,81,37,2,3,1,0,1,163,129,239,48,129,236,48,18,6,3,85,29,19,1,1,255,4,8,48,6,1,1,255,2,1,0,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,68,6,8,43,6,1,5,5,7,1,1,4,56,48,54,48,52,6,8,43,6,1,5,5,7,48,1,134,40,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,51,45,97,112,112,108,101,114,111,111,116,99,97,48,46,6,3,85,29,31,4,39,48,37,48,35,160,33,160,31,134,29,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,114,111,111,116,46,99,114,108,48,29,6,3,85,29,14,4,22,4,20,93,66,16,108,27,187,199,82,151,78,68,189,19,39,185,58,18,119,131,43,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,1,4,2,5,0,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,3,130,1,1,0,82,163,8,41,19,101,183,181,31,230,52,3,237,246,24,119,188,61,202,39,181,112,236,236,108,51,75,246,153,37,148,7,158,33,51,3,106,245,125,10,46,43,223,8,169,130,222,74,127,225,231,11,75,5,184,39,152,73,221,193,12,134,215,129,100,174,130,156,196,69,241,234,143,28,34,48,227,162,37,97,68,137,254,133,122,86,240,114,146,124,12,68,84,36,183,181,164,159,38,240,50,249,87,157,106,121,108,64,237,15,52,6,6,66,255,216,14,49,101,109,230,46,1,217,6,191,197,202,13,174,71,212,5,23,136,60,233,156,228,25,100,109,138,148,173,17,220,1,246,229,120,175,173,232,112,215,13,93,129,62,70,42,44,174,36,161,199,210,224,125,191,29,245,54,107,197,222,169,10,154,128,177,17,94,178,100,126,179,193,87,204,194,108,89,153,183,244,176,221,49,218,72,214,106,129,238,178,139,80,53,22,230,231,92,113,98,110,176,242,81,226,168,109,248,42,203,104,115,198,166,196,26,67,73,1,62,169,169,0,138,134,108,131,136,210,92,112,87,122,218,248,118,204,25,213,176] +static let AppleIncRootCertificate_cer: [UInt8] = [48,130,4,187,48,130,3,163,160,3,2,1,2,2,1,2,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,30,23,13,48,54,48,52,50,53,50,49,52,48,51,54,90,23,13,51,53,48,50,48,57,50,49,52,48,51,54,90,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,228,145,169,9,31,145,219,30,71,80,235,5,237,94,121,132,45,235,54,162,87,76,85,236,139,25,137,222,249,75,108,245,7,171,34,48,2,232,24,62,248,80,9,211,127,65,168,152,249,209,202,102,156,36,107,17,208,163,187,228,27,42,195,31,149,158,122,12,164,71,139,91,212,22,55,51,203,196,15,77,206,20,105,209,201,25,114,245,93,14,213,127,95,155,242,37,3,186,85,143,77,93,13,241,100,53,35,21,75,21,89,29,179,148,247,246,156,158,207,80,186,193,88,80,103,143,8,180,32,247,203,172,44,32,111,112,182,63,1,48,140,183,67,207,15,157,61,243,43,73,40,26,200,254,206,181,185,14,217,94,28,214,203,61,181,58,173,244,15,14,0,146,11,177,33,22,46,116,213,60,13,219,98,22,171,163,113,146,71,83,85,193,175,47,65,179,248,251,227,112,205,230,163,76,69,126,31,76,107,80,150,65,137,196,116,98,11,16,131,65,135,51,138,129,177,48,88,236,90,4,50,140,104,179,143,29,222,101,115,255,103,94,101,188,73,216,118,159,51,20,101,161,119,148,201,45,2,3,1,0,1,163,130,1,122,48,130,1,118,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,255,48,29,6,3,85,29,14,4,22,4,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,130,1,17,6,3,85,29,32,4,130,1,8,48,130,1,4,48,130,1,0,6,9,42,134,72,134,247,99,100,5,1,48,129,242,48,42,6,8,43,6,1,5,5,7,2,1,22,30,104,116,116,112,115,58,47,47,119,119,119,46,97,112,112,108,101,46,99,111,109,47,97,112,112,108,101,99,97,47,48,129,195,6,8,43,6,1,5,5,7,2,2,48,129,182,26,129,179,82,101,108,105,97,110,99,101,32,111,110,32,116,104,105,115,32,99,101,114,116,105,102,105,99,97,116,101,32,98,121,32,97,110,121,32,112,97,114,116,121,32,97,115,115,117,109,101,115,32,97,99,99,101,112,116,97,110,99,101,32,111,102,32,116,104,101,32,116,104,101,110,32,97,112,112,108,105,99,97,98,108,101,32,115,116,97,110,100,97,114,100,32,116,101,114,109,115,32,97,110,100,32,99,111,110,100,105,116,105,111,110,115,32,111,102,32,117,115,101,44,32,99,101,114,116,105,102,105,99,97,116,101,32,112,111,108,105,99,121,32,97,110,100,32,99,101,114,116,105,102,105,99,97,116,105,111,110,32,112,114,97,99,116,105,99,101,32,115,116,97,116,101,109,101,110,116,115,46,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,3,130,1,1,0,92,54,153,76,45,120,183,237,140,155,220,243,119,155,242,118,210,119,48,79,193,31,133,131,133,27,153,61,71,55,242,169,155,64,142,44,212,177,144,18,216,190,244,115,155,238,210,100,15,203,121,79,52,216,162,62,249,120,255,107,200,7,236,125,57,131,139,83,32,211,56,196,177,191,154,79,10,107,255,43,252,89,167,5,9,124,23,64,86,17,30,116,211,183,139,35,59,71,163,213,111,36,226,235,209,183,112,223,15,69,225,39,202,241,109,120,237,231,181,23,23,168,220,126,34,53,202,37,213,217,15,214,107,212,162,36,35,17,247,161,172,143,115,129,96,198,27,91,9,47,146,178,248,68,72,240,96,56,158,21,245,61,38,103,32,138,51,106,247,13,130,207,222,235,163,47,249,83,106,91,100,192,99,51,119,247,58,7,44,86,235,218,15,33,14,218,186,115,25,79,181,217,54,127,193,135,85,217,167,153,185,50,66,251,216,213,113,158,126,161,82,183,27,189,147,66,36,18,42,199,15,29,182,77,156,94,99,200,75,128,23,80,170,138,213,218,228,252,208,9,7,55,176,117,117,33] +static let AppleWWDRCAG4_cer: [UInt8] = [48,130,4,85,48,130,3,61,160,3,2,1,2,2,20,19,220,119,149,82,113,229,61,198,50,232,204,255,229,33,243,204,197,206,210,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,30,23,13,50,48,49,50,49,54,49,57,51,54,48,52,90,23,13,51,48,49,50,49,48,48,48,48,48,48,48,90,48,117,49,68,48,66,6,3,85,4,3,12,59,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,11,48,9,6,3,85,4,11,12,2,71,52,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,208,31,120,170,122,39,50,176,70,95,231,23,118,216,160,157,14,41,142,173,61,50,165,196,107,55,201,228,65,145,106,183,121,113,93,12,52,4,96,117,247,174,67,143,71,196,134,30,8,232,191,214,57,82,47,30,103,252,113,241,130,109,60,126,6,82,118,157,44,188,213,67,233,177,180,188,64,58,120,81,93,81,161,37,225,190,108,145,157,107,33,89,24,65,213,15,141,109,65,42,57,74,33,224,144,159,78,19,79,208,140,154,50,184,215,106,146,30,37,106,164,50,206,34,25,133,5,96,220,2,74,242,90,235,119,121,2,125,192,151,84,108,146,142,118,1,230,70,143,229,230,42,251,162,176,173,24,173,109,51,133,56,35,139,234,138,150,237,159,174,102,79,163,12,64,39,109,149,208,98,136,217,67,41,39,253,237,164,191,83,46,144,21,101,60,217,46,98,100,51,29,108,106,221,142,33,170,164,95,21,198,48,237,95,230,140,54,146,148,183,220,57,2,0,251,100,140,212,12,129,242,63,213,52,151,135,117,38,194,111,174,3,99,33,12,123,212,27,177,98,197,2,156,189,253,175,2,3,1,0,1,163,129,239,48,129,236,48,18,6,3,85,29,19,1,1,255,4,8,48,6,1,1,255,2,1,0,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,68,6,8,43,6,1,5,5,7,1,1,4,56,48,54,48,52,6,8,43,6,1,5,5,7,48,1,134,40,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,51,45,97,112,112,108,101,114,111,111,116,99,97,48,46,6,3,85,29,31,4,39,48,37,48,35,160,33,160,31,134,29,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,114,111,111,116,46,99,114,108,48,29,6,3,85,29,14,4,22,4,20,91,217,250,29,231,154,26,11,163,153,118,34,80,134,62,145,200,91,119,168,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,1,4,2,5,0,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,63,86,61,158,229,182,195,121,230,69,32,104,189,191,115,139,44,18,158,2,227,174,128,34,140,4,30,195,82,200,112,128,168,251,206,167,176,213,66,68,130,3,130,79,6,252,59,73,20,251,216,116,82,133,175,167,157,33,231,1,18,3,159,205,64,88,208,1,215,191,50,131,43,83,88,40,60,238,156,159,84,118,61,100,39,198,126,141,29,56,77,45,174,129,230,185,165,184,156,137,148,247,159,199,135,165,81,102,52,27,57,113,57,36,227,135,103,239,165,177,104,123,140,238,61,247,174,182,123,226,210,255,223,97,198,106,117,73,149,34,68,168,4,252,148,184,11,46,57,17,73,18,209,229,129,234,89,0,79,91,60,90,54,218,122,34,115,9,9,105,205,192,124,234,226,36,254,152,68,184,248,239,178,113,63,26,93,212,93,126,51,77,156,29,36,190,0,240,144,3,88,153,65,61,49,90,98,64,175,57,168,81,67,146,171,4,168,156,194,77,177,75,210,171,124,74,95,235,157,59,188,79,136,64,6,19,255,144,23,138,8,71,41,232,98,152,41,165,79,17,5,105,58,207,242,159] +static let AppleWWDRCAG6_cer: [UInt8] = [48,130,3,22,48,130,2,156,160,3,2,1,2,2,20,34,193,161,71,10,116,115,105,239,83,134,18,201,198,159,61,56,243,108,215,48,10,6,8,42,134,72,206,61,4,3,3,48,103,49,27,48,25,6,3,85,4,3,12,18,65,112,112,108,101,32,82,111,111,116,32,67,65,32,45,32,71,51,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,30,23,13,50,49,48,51,49,55,50,48,51,55,49,48,90,23,13,51,54,48,51,49,57,48,48,48,48,48,48,90,48,117,49,68,48,66,6,3,85,4,3,12,59,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,11,48,9,6,3,85,4,11,12,2,71,54,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,118,48,16,6,7,42,134,72,206,61,2,1,6,5,43,129,4,0,34,3,98,0,4,110,196,10,11,222,15,174,85,166,101,121,215,130,220,115,117,82,75,241,61,18,25,137,224,13,17,169,158,9,247,55,163,211,240,147,4,111,177,67,139,134,193,152,248,66,157,157,255,178,174,143,23,247,6,112,165,214,176,190,111,58,215,145,135,34,120,231,29,192,72,254,220,56,31,154,106,143,116,125,202,113,74,255,196,100,86,231,136,6,205,129,145,161,34,26,72,163,129,250,48,129,247,48,18,6,3,85,29,19,1,1,255,4,8,48,6,1,1,255,2,1,0,48,31,6,3,85,29,35,4,24,48,22,128,20,187,176,222,161,88,51,136,154,164,138,153,222,190,189,235,175,218,203,36,171,48,70,6,8,43,6,1,5,5,7,1,1,4,58,48,56,48,54,6,8,43,6,1,5,5,7,48,1,134,42,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,51,45,97,112,112,108,101,114,111,111,116,99,97,103,51,48,55,6,3,85,29,31,4,48,48,46,48,44,160,42,160,40,134,38,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,97,112,112,108,101,114,111,111,116,99,97,103,51,46,99,114,108,48,29,6,3,85,29,14,4,22,4,20,63,47,148,35,81,211,80,201,154,40,61,237,176,124,229,207,165,144,98,153,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,1,4,2,5,0,48,10,6,8,42,134,72,206,61,4,3,3,3,104,0,48,101,2,48,64,94,20,170,228,140,138,162,3,2,62,220,56,247,64,90,7,174,251,9,161,6,37,123,159,64,31,196,169,145,157,232,36,163,136,43,78,158,227,19,109,117,87,155,28,92,197,249,2,49,0,211,72,252,90,173,149,239,54,185,49,83,116,228,80,194,55,223,44,239,21,85,81,79,140,49,88,144,189,247,131,172,117,198,120,249,17,141,177,128,242,71,152,38,118,189,39,12,104] +static let AppleWWDRCAG8_cer: [UInt8] = [48,130,4,85,48,130,3,61,160,3,2,1,2,2,20,84,181,11,175,121,13,141,127,140,175,104,76,86,47,80,105,10,26,186,95,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,48,98,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,19,10,65,112,112,108,101,32,73,110,99,46,49,38,48,36,6,3,85,4,11,19,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,22,48,20,6,3,85,4,3,19,13,65,112,112,108,101,32,82,111,111,116,32,67,65,48,30,23,13,50,51,48,54,50,48,50,51,51,55,49,53,90,23,13,50,53,48,49,50,52,48,48,48,48,48,48,90,48,117,49,11,48,9,6,3,85,4,6,19,2,85,83,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,11,12,2,71,56,49,68,48,66,6,3,85,4,3,12,59,65,112,112,108,101,32,87,111,114,108,100,119,105,100,101,32,68,101,118,101,108,111,112,101,114,32,82,101,108,97,116,105,111,110,115,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,48,130,1,34,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,208,64,16,212,8,249,82,33,99,79,60,104,247,212,156,130,2,140,131,104,188,183,141,127,136,231,66,77,44,239,34,165,194,137,222,189,12,215,77,97,80,45,57,228,48,115,165,210,213,104,154,51,242,239,217,42,173,46,31,3,186,248,165,241,66,11,87,46,70,198,208,88,211,112,95,247,178,55,239,106,31,62,191,137,57,194,173,254,245,5,251,88,150,49,200,135,44,213,35,186,117,176,44,245,171,160,186,242,32,12,76,235,36,99,105,233,34,64,240,142,88,91,222,100,123,137,27,81,18,4,224,23,178,89,205,223,241,231,206,175,44,50,232,105,193,208,8,126,29,162,71,115,209,75,59,253,185,133,220,71,167,40,130,121,41,72,137,2,40,226,193,236,44,31,91,134,252,216,182,203,113,192,115,1,75,252,5,17,17,108,1,3,52,90,246,129,194,94,31,124,20,14,222,63,107,33,203,79,245,215,97,230,132,3,146,188,231,37,41,158,205,91,12,193,219,227,196,58,217,118,87,172,47,56,20,25,200,183,177,162,85,79,95,78,142,25,89,169,218,253,114,218,176,4,33,5,2,3,1,0,1,163,129,239,48,129,236,48,18,6,3,85,29,19,1,1,255,4,8,48,6,1,1,255,2,1,0,48,31,6,3,85,29,35,4,24,48,22,128,20,43,208,105,71,148,118,9,254,244,107,141,46,64,166,247,71,77,127,8,94,48,68,6,8,43,6,1,5,5,7,1,1,4,56,48,54,48,52,6,8,43,6,1,5,5,7,48,1,134,40,104,116,116,112,58,47,47,111,99,115,112,46,97,112,112,108,101,46,99,111,109,47,111,99,115,112,48,51,45,97,112,112,108,101,114,111,111,116,99,97,48,46,6,3,85,29,31,4,39,48,37,48,35,160,33,160,31,134,29,104,116,116,112,58,47,47,99,114,108,46,97,112,112,108,101,46,99,111,109,47,114,111,111,116,46,99,114,108,48,29,6,3,85,29,14,4,22,4,20,181,189,188,128,196,12,227,56,164,244,183,173,35,179,239,68,206,185,90,133,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,16,6,10,42,134,72,134,247,99,100,6,2,1,4,2,5,0,48,13,6,9,42,134,72,134,247,13,1,1,5,5,0,3,130,1,1,0,76,179,235,122,57,148,74,149,102,250,22,49,234,13,138,143,93,66,56,198,7,205,232,201,34,172,252,8,66,126,95,147,95,49,42,168,34,193,118,25,43,238,174,114,233,189,86,207,73,36,146,8,247,205,25,117,195,249,26,39,13,14,220,41,141,63,182,219,165,79,76,114,196,96,145,137,17,126,36,149,67,34,96,103,188,223,255,171,229,60,22,23,157,130,20,78,157,244,117,254,80,185,173,5,178,184,121,246,149,214,16,229,246,83,244,207,35,230,119,15,236,184,75,105,248,226,158,2,49,217,142,217,202,190,99,94,95,225,160,105,206,193,205,110,73,163,120,97,175,57,60,106,75,10,52,37,100,213,48,181,60,171,221,17,129,57,113,232,192,195,88,128,17,16,26,17,210,194,188,89,240,138,217,53,115,192,230,109,228,201,125,98,20,62,130,2,169,16,105,55,121,97,111,240,70,148,62,53,245,171,172,99,208,216,94,89,87,105,209,184,75,110,93,212,95,19,156,112,78,37,17,220,36,44,30,22,158,150,90,4,237,69,238,58,238,103,93,143,195,134,176,245,76,31,104,16] +static let AppleRootCA_G3_cer: [UInt8] = [48,130,2,67,48,130,1,201,160,3,2,1,2,2,8,45,197,252,136,210,197,75,149,48,10,6,8,42,134,72,206,61,4,3,3,48,103,49,27,48,25,6,3,85,4,3,12,18,65,112,112,108,101,32,82,111,111,116,32,67,65,32,45,32,71,51,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,30,23,13,49,52,48,52,51,48,49,56,49,57,48,54,90,23,13,51,57,48,52,51,48,49,56,49,57,48,54,90,48,103,49,27,48,25,6,3,85,4,3,12,18,65,112,112,108,101,32,82,111,111,116,32,67,65,32,45,32,71,51,49,38,48,36,6,3,85,4,11,12,29,65,112,112,108,101,32,67,101,114,116,105,102,105,99,97,116,105,111,110,32,65,117,116,104,111,114,105,116,121,49,19,48,17,6,3,85,4,10,12,10,65,112,112,108,101,32,73,110,99,46,49,11,48,9,6,3,85,4,6,19,2,85,83,48,118,48,16,6,7,42,134,72,206,61,2,1,6,5,43,129,4,0,34,3,98,0,4,152,233,47,61,64,114,164,237,147,34,114,129,19,28,221,16,149,241,197,163,78,113,220,20,22,217,14,229,166,5,42,119,100,123,95,78,56,211,187,28,68,181,127,245,31,182,50,98,93,201,233,132,91,79,48,79,17,90,0,253,88,88,12,165,245,15,44,77,7,71,19,117,218,151,151,151,111,49,92,237,43,157,123,32,59,216,185,84,217,94,153,164,58,81,10,49,163,66,48,64,48,29,6,3,85,29,14,4,22,4,20,187,176,222,161,88,51,136,154,164,138,153,222,190,189,235,175,218,203,36,171,48,15,6,3,85,29,19,1,1,255,4,5,48,3,1,1,255,48,14,6,3,85,29,15,1,1,255,4,4,3,2,1,6,48,10,6,8,42,134,72,206,61,4,3,3,3,104,0,48,101,2,49,0,131,233,193,196,22,94,26,93,52,24,217,237,239,244,108,14,0,70,75,184,223,178,70,17,197,15,253,230,122,140,161,166,107,206,194,3,212,156,245,147,198,116,184,106,223,170,35,21,2,48,109,102,138,16,202,212,13,212,79,205,141,67,62,180,138,99,165,51,110,227,109,218,23,183,100,31,200,83,38,249,136,98,116,57,11,23,91,203,81,168,12,232,24,3,231,162,178,40] +} \ No newline at end of file diff --git a/Sources/Workspace/Workspace+Configuration.swift b/Sources/Workspace/Workspace+Configuration.swift index 671ffba09f4..9fecb0a0888 100644 --- a/Sources/Workspace/Workspace+Configuration.swift +++ b/Sources/Workspace/Workspace+Configuration.swift @@ -792,6 +792,12 @@ public struct WorkspaceConfiguration { /// Whether or not to use prebuilt swift-syntax for macros public var usePrebuilts: Bool + /// String URL to allow override of the prebuilts download location + public var prebuiltsDownloadURL: String? + + /// Path to root certificate used when validating the manifest signing during testing + public var prebuiltsRootCertPath: String? + /// Whether to omit unused dependencies. public var pruneDependencies: Bool @@ -812,6 +818,8 @@ public struct WorkspaceConfiguration { defaultRegistry: Registry?, manifestImportRestrictions: (startingToolsVersion: ToolsVersion, allowedImports: [String])?, usePrebuilts: Bool, + prebuiltsDownloadURL: String?, + prebuiltsRootCertPath: String?, pruneDependencies: Bool, traitConfiguration: TraitConfiguration ) { @@ -828,6 +836,8 @@ public struct WorkspaceConfiguration { self.defaultRegistry = defaultRegistry self.manifestImportRestrictions = manifestImportRestrictions self.usePrebuilts = usePrebuilts + self.prebuiltsDownloadURL = prebuiltsDownloadURL + self.prebuiltsRootCertPath = prebuiltsRootCertPath self.pruneDependencies = pruneDependencies self.traitConfiguration = traitConfiguration } @@ -848,6 +858,8 @@ public struct WorkspaceConfiguration { defaultRegistry: .none, manifestImportRestrictions: .none, usePrebuilts: false, + prebuiltsDownloadURL: nil, + prebuiltsRootCertPath: nil, pruneDependencies: false, traitConfiguration: .default ) diff --git a/Sources/Workspace/Workspace+Prebuilts.swift b/Sources/Workspace/Workspace+Prebuilts.swift index 8cca15bde2c..9c16a5edb51 100644 --- a/Sources/Workspace/Workspace+Prebuilts.swift +++ b/Sources/Workspace/Workspace+Prebuilts.swift @@ -87,17 +87,20 @@ extension Workspace { case macos_x86_64 case windows_aarch64 case windows_x86_64 - // noble is currently missing + case ubuntu_noble_aarch64 + case ubuntu_noble_x86_64 case ubuntu_jammy_aarch64 case ubuntu_jammy_x86_64 case ubuntu_focal_aarch64 case ubuntu_focal_x86_64 - // bookworm is currently missing - // fedora39 is currently missing + case fedora_39_aarch64 + case fedora_39_x86_64 case amazonlinux2_aarch64 case amazonlinux2_x86_64 case rhel_ubi9_aarch64 case rhel_ubi9_x86_64 + case debian_12_aarch64 + case debian_12_x86_64 public enum Arch: String { case x86_64 @@ -109,35 +112,16 @@ extension Workspace { case windows case linux } + } + } - public var arch: Arch { - switch self { - case .macos_aarch64, .windows_aarch64, - .ubuntu_jammy_aarch64, .ubuntu_focal_aarch64, - .amazonlinux2_aarch64, - .rhel_ubi9_aarch64: - return .aarch64 - case .macos_x86_64, .windows_x86_64, - .ubuntu_jammy_x86_64, .ubuntu_focal_x86_64, - .amazonlinux2_x86_64, - .rhel_ubi9_x86_64: - return .x86_64 - } - } + public struct SignedPrebuiltsManifest: Codable { + public var manifest: PrebuiltsManifest + public var signature: ManifestSignature - public var os: OS { - switch self { - case .macos_aarch64, .macos_x86_64: - return .macos - case .windows_aarch64, .windows_x86_64: - return .windows - case .ubuntu_jammy_aarch64, .ubuntu_jammy_x86_64, - .ubuntu_focal_aarch64, .ubuntu_focal_x86_64, - .amazonlinux2_aarch64, .amazonlinux2_x86_64, - .rhel_ubi9_aarch64, .rhel_ubi9_x86_64: - return .linux - } - } + public init(manifest: PrebuiltsManifest, signature: ManifestSignature) { + self.manifest = manifest + self.signature = signature } } @@ -146,15 +130,21 @@ extension Workspace { let httpClient: HTTPClient? let archiver: Archiver? let useCache: Bool? + let hostPlatform: PrebuiltsManifest.Platform? + let rootCertPath: AbsolutePath? public init( httpClient: HTTPClient? = .none, archiver: Archiver? = .none, - useCache: Bool? = .none + useCache: Bool? = .none, + hostPlatform: PrebuiltsManifest.Platform? = nil, + rootCertPath: AbsolutePath? = nil ) { self.httpClient = httpClient self.archiver = archiver self.useCache = useCache + self.hostPlatform = hostPlatform + self.rootCertPath = rootCertPath } } @@ -170,50 +160,72 @@ extension Workspace { private let cachePath: AbsolutePath? private let delegate: Delegate? private let hashAlgorithm: HashAlgorithm = SHA256() + private let prebuiltsDownloadURL: URL + private let rootCertPath: AbsolutePath? + let hostPlatform: PrebuiltsManifest.Platform init( fileSystem: FileSystem, + hostPlatform: PrebuiltsManifest.Platform, authorizationProvider: AuthorizationProvider?, scratchPath: AbsolutePath, cachePath: AbsolutePath?, customHTTPClient: HTTPClient?, customArchiver: Archiver?, - delegate: Delegate? + delegate: Delegate?, + prebuiltsDownloadURL: String?, + rootCertPath: AbsolutePath? ) { self.fileSystem = fileSystem + self.hostPlatform = hostPlatform self.authorizationProvider = authorizationProvider self.httpClient = customHTTPClient ?? HTTPClient() + +#if os(Linux) + self.archiver = customArchiver ?? TarArchiver(fileSystem: fileSystem) +#else self.archiver = customArchiver ?? ZipArchiver(fileSystem: fileSystem) +#endif + self.scratchPath = scratchPath self.cachePath = cachePath self.delegate = delegate + if let prebuiltsDownloadURL, let url = URL(string: prebuiltsDownloadURL) { + self.prebuiltsDownloadURL = url + } else { + self.prebuiltsDownloadURL = URL(string: "https://download.swift.org/prebuilts")! + } + self.rootCertPath = rootCertPath + + self.prebuiltPackages = [ + // TODO: we should have this in a manifest somewhere, not hardcoded like this + .init( + identity: .plain("swift-syntax"), + packageRefs: [ + .init( + identity: .plain("swift-syntax"), + kind: .remoteSourceControl("https://github.com/swiftlang/swift-syntax.git") + ), + .init( + identity: .plain("swift-syntax"), + kind: .remoteSourceControl("git@github.com:swiftlang/swift-syntax.git") + ), + ] + ), + ] } struct PrebuiltPackage { - let packageRef: PackageReference - let prebuiltsURL: URL + let identity: PackageIdentity + let packageRefs: [PackageReference] } - private let prebuiltPackages: [PrebuiltPackage] = [ - .init( - packageRef: .init( - identity: .plain("swift-syntax"), - kind: .remoteSourceControl("https://github.com/swiftlang/swift-syntax.git") - ), - prebuiltsURL: URL( - string: - "https://github.com/dschaefer2/swift-syntax/releases/download" - )! - ), - ] + private let prebuiltPackages: [PrebuiltPackage] // Version of the compiler we're building against - private let swiftVersion = - "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)" + private let swiftVersion = "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)" - fileprivate func findPrebuilts(packages: [PackageReference]) - -> [PrebuiltPackage] - { + fileprivate func findPrebuilts(packages: [PackageReference]) -> [PrebuiltPackage] { var prebuilts: [PrebuiltPackage] = [] for packageRef in packages { guard case let .remoteSourceControl(pkgURL) = packageRef.kind else { @@ -222,21 +234,23 @@ extension Workspace { } if let prebuilt = prebuiltPackages.first(where: { - guard case let .remoteSourceControl(prebuiltURL) = $0.packageRef.kind, - $0.packageRef.identity == packageRef.identity else { - return false - } - - if pkgURL == prebuiltURL { - return true - } else if !pkgURL.lastPathComponent.hasSuffix(".git") { - // try with the git extension - // TODO: Does this need to be in the PackageRef Equatable? - let gitURL = SourceControlURL(pkgURL.absoluteString + ".git") - return gitURL == prebuiltURL - } else { - return false - } + $0.packageRefs.contains(where: { + guard case let .remoteSourceControl(prebuiltURL) = $0.kind, + $0.identity == packageRef.identity else { + return false + } + + if pkgURL == prebuiltURL { + return true + } else if !pkgURL.lastPathComponent.hasSuffix(".git") { + // try with the git extension + // TODO: Does this need to be in the PackageRef Equatable? + let gitURL = SourceControlURL(pkgURL.absoluteString + ".git") + return gitURL == prebuiltURL + } else { + return false + } + }) }) { prebuilts.append(prebuilt) } @@ -250,17 +264,41 @@ extension Workspace { observabilityScope: ObservabilityScope ) async throws -> PrebuiltsManifest? { let manifestFile = swiftVersion + "-manifest.json" - let prebuiltsDir = cachePath ?? scratchPath - let destination = prebuiltsDir.appending( - components: package.packageRef.identity.description, - manifestFile - ) - if fileSystem.exists(destination) { + let manifestPath = try RelativePath(validating: "\(package.identity)/\(version)/\(manifestFile)") + let destination = scratchPath.appending(manifestPath) + let cacheDest = cachePath?.appending(manifestPath) + + func loadManifest() async throws -> PrebuiltsManifest? { do { - return try JSONDecoder().decode( - PrebuiltsManifest.self, - from: try Data(contentsOf: destination.asURL) + let signedManifest = try JSONDecoder().decode( + path: destination, + fileSystem: fileSystem, + as: SignedPrebuiltsManifest.self ) + + // Check the signature + // Ignore errors coming from the certificate loading, that will shutdown the build + // instead of letting it continue with build from source. + if let rootCertPath { + try await withTemporaryDirectory(fileSystem: fileSystem) { tmpDir in + try fileSystem.copy(from: rootCertPath, to: tmpDir.appending(rootCertPath.basename)) + let validator = ManifestSigning(trustedRootCertsDir: tmpDir, observabilityScope: ObservabilitySystem.NOOP) + try await validator.validate( + manifest: signedManifest.manifest, + signature: signedManifest.signature, + fileSystem: fileSystem + ) + }.value + } else { + let validator = ManifestSigning(observabilityScope: ObservabilitySystem.NOOP) + try await validator.validate( + manifest: signedManifest.manifest, + signature: signedManifest.signature, + fileSystem: fileSystem + ) + } + + return signedManifest.manifest } catch { // redownload it observabilityScope.emit( @@ -268,56 +306,85 @@ extension Workspace { underlyingError: error ) try fileSystem.removeFileTree(destination) + return nil } } - try fileSystem.createDirectory( - destination.parentDirectory, - recursive: true - ) - let manifestURL = package.prebuiltsURL.appending( - components: version.description, - manifestFile - ) - var headers = HTTPClientHeaders() - headers.add(name: "Accept", value: "application/json") - var request = HTTPClient.Request.download( - url: manifestURL, - headers: headers, - fileSystem: self.fileSystem, - destination: destination - ) - request.options.authorizationProvider = - self.authorizationProvider?.httpAuthorizationHeader(for:) - request.options.retryStrategy = .exponentialBackoff( - maxAttempts: 3, - baseDelay: .milliseconds(50) - ) - request.options.validResponseCodes = [200] + if fileSystem.exists(destination), let manifest = try? await loadManifest() { + return manifest + } else if let cacheDest, fileSystem.exists(cacheDest) { + // Pull it out of the cache + try fileSystem.createDirectory(destination.parentDirectory, recursive: true) + try fileSystem.copy(from: cacheDest, to: destination) - do { - _ = try await self.httpClient.execute(request) { _, _ in - // TODO: send to delegate + if let manifest = try? await loadManifest() { + return manifest } - } catch { - observabilityScope.emit( - info: "Prebuilt \(manifestFile)", - underlyingError: error - ) + } else if fileSystem.exists(destination.parentDirectory) { + // We tried previously and were not able to find the manifest. + // Don't try again to avoid excessive server traffic return nil } - do { - let data = try fileSystem.readFileContents(destination) - return try JSONDecoder().decode( - PrebuiltsManifest.self, - from: Data(data.contents) + try fileSystem.createDirectory(destination.parentDirectory, recursive: true) + + let manifestURL = self.prebuiltsDownloadURL.appending( + components: package.identity.description, version.description, manifestFile + ) + + if manifestURL.scheme == "file" { + let sourcePath = try AbsolutePath(validating: manifestURL.path) + if fileSystem.exists(sourcePath) { + // simply copy it over + try fileSystem.copy(from: sourcePath, to: destination) + } else { + return nil + } + } else { + var headers = HTTPClientHeaders() + headers.add(name: "Accept", value: "application/json") + var request = HTTPClient.Request.download( + url: manifestURL, + headers: headers, + fileSystem: self.fileSystem, + destination: destination ) - } catch { - observabilityScope.emit( - info: "Failed to decode prebuilt manifest", - underlyingError: error + request.options.authorizationProvider = + self.authorizationProvider?.httpAuthorizationHeader(for:) + request.options.retryStrategy = .exponentialBackoff( + maxAttempts: 3, + baseDelay: .milliseconds(50) ) + request.options.validResponseCodes = [200] + + do { + _ = try await self.httpClient.execute(request) { _, _ in + // TODO: send to delegate + } + } catch { + observabilityScope.emit( + info: "Prebuilt \(manifestFile)", + underlyingError: error + ) + // Create an empty manifest so we don't keep trying to download it + let manifest = PrebuiltsManifest(libraries: []) + try? fileSystem.writeFileContents(destination, data: JSONEncoder().encode(manifest)) + return nil + } + } + + if let manifest = try await loadManifest() { + // Cache the manifest + if let cacheDest { + if fileSystem.exists(cacheDest) { + try fileSystem.removeFileTree(cacheDest) + } + try fileSystem.createDirectory(cacheDest.parentDirectory, recursive: true) + try fileSystem.copy(from: destination, to: cacheDest) + } + + return manifest + } else { return nil } } @@ -335,102 +402,125 @@ extension Workspace { artifact: PrebuiltsManifest.Library.Artifact, observabilityScope: ObservabilityScope ) async throws -> AbsolutePath? { - let artifactName = - "\(swiftVersion)-\(library.name)-\(artifact.platform.rawValue)" - let scratchDir = scratchPath.appending( - package.packageRef.identity.description - ) + let artifactName = "\(swiftVersion)-\(library.name)-\(artifact.platform.rawValue)" + let scratchDir = scratchPath.appending(components: package.identity.description, version.description) + let artifactDir = scratchDir.appending(artifactName) guard !fileSystem.exists(artifactDir) else { return artifactDir } - let artifactFile = artifactName + ".zip" - let prebuiltsDir = cachePath ?? scratchPath - let destination = prebuiltsDir.appending( - components: package.packageRef.identity.description, - artifactFile - ) + let artifactFile = artifactName + (hostPlatform.os == .linux ? ".tar.gz" : ".zip") + let destination = scratchDir.appending(artifactFile) + let cacheFile = cachePath?.appending(components: package.identity.description, version.description, artifactFile) let zipExists = fileSystem.exists(destination) if try (!zipExists || !check(path: destination, checksum: artifact.checksum)) { - if zipExists { - observabilityScope.emit(info: "Prebuilt artifact \(artifactFile) checksum mismatch, redownloading.") - try fileSystem.removeFileTree(destination) - } + try fileSystem.createDirectory(destination.parentDirectory, recursive: true) + + if let cacheFile, fileSystem.exists(cacheFile), try check(path: cacheFile, checksum: artifact.checksum) { + // Copy over the cached file + observabilityScope.emit(info: "Using cached \(artifactFile)") + try fileSystem.copy(from: cacheFile, to: destination) + } else { + if zipExists { + // Exists but failed checksum + observabilityScope.emit(info: "Prebuilt artifact \(artifactFile) checksum mismatch, redownloading.") + try fileSystem.removeFileTree(destination) + } - try fileSystem.createDirectory( - destination.parentDirectory, - recursive: true - ) + // Download + let artifactURL = self.prebuiltsDownloadURL.appending( + components: package.identity.description, version.description, artifactFile + ) - // Download - let artifactURL = package.prebuiltsURL.appending( - components: version.description, - artifactFile - ) - let fetchStart = DispatchTime.now() - var headers = HTTPClientHeaders() - headers.add(name: "Accept", value: "application/octet-stream") - var request = HTTPClient.Request.download( - url: artifactURL, - headers: headers, - fileSystem: self.fileSystem, - destination: destination - ) - request.options.authorizationProvider = - self.authorizationProvider?.httpAuthorizationHeader(for:) - request.options.retryStrategy = .exponentialBackoff( - maxAttempts: 3, - baseDelay: .milliseconds(50) - ) - request.options.validResponseCodes = [200] + let fetchStart = DispatchTime.now() + if artifactURL.scheme == "file" { + let artifactPath = try AbsolutePath(validating: artifactURL.path) + if fileSystem.exists(artifactPath) { + try fileSystem.copy(from: artifactPath, to: destination) + self.delegate?.didDownloadPrebuilt( + from: artifactURL.absoluteString, + result: .success((destination, false)), + duration: fetchStart.distance(to: .now()) + ) + } else { + return nil + } + } else { + var headers = HTTPClientHeaders() + headers.add(name: "Accept", value: "application/octet-stream") + var request = HTTPClient.Request.download( + url: artifactURL, + headers: headers, + fileSystem: self.fileSystem, + destination: destination + ) + request.options.authorizationProvider = + self.authorizationProvider?.httpAuthorizationHeader(for:) + request.options.retryStrategy = .exponentialBackoff( + maxAttempts: 3, + baseDelay: .milliseconds(50) + ) + request.options.validResponseCodes = [200] - self.delegate?.willDownloadPrebuilt( - from: artifactURL.absoluteString, - fromCache: false - ) - do { - _ = try await self.httpClient.execute(request) { - bytesDownloaded, - totalBytesToDownload in - self.delegate?.downloadingPrebuilt( + self.delegate?.willDownloadPrebuilt( + from: artifactURL.absoluteString, + fromCache: false + ) + do { + _ = try await self.httpClient.execute(request) { + bytesDownloaded, + totalBytesToDownload in + self.delegate?.downloadingPrebuilt( + from: artifactURL.absoluteString, + bytesDownloaded: bytesDownloaded, + totalBytesToDownload: totalBytesToDownload + ) + } + } catch { + observabilityScope.emit( + info: "Prebuilt artifact \(artifactFile)", + underlyingError: error + ) + self.delegate?.didDownloadPrebuilt( + from: artifactURL.absoluteString, + result: .failure(error), + duration: fetchStart.distance(to: .now()) + ) + return nil + } + + // Check the checksum + if try !check(path: destination, checksum: artifact.checksum) { + let errorString = + "Prebuilt artifact \(artifactFile) checksum mismatch" + observabilityScope.emit(info: errorString) + self.delegate?.didDownloadPrebuilt( + from: artifactURL.absoluteString, + result: .failure(StringError(errorString)), + duration: fetchStart.distance(to: .now()) + ) + return nil + } + + self.delegate?.didDownloadPrebuilt( from: artifactURL.absoluteString, - bytesDownloaded: bytesDownloaded, - totalBytesToDownload: totalBytesToDownload + result: .success((destination, false)), + duration: fetchStart.distance(to: .now()) ) } - } catch { - observabilityScope.emit( - info: "Prebuilt artifact \(artifactFile)", - underlyingError: error - ) - self.delegate?.didDownloadPrebuilt( - from: artifactURL.absoluteString, - result: .failure(error), - duration: fetchStart.distance(to: .now()) - ) - return nil - } - // Check the checksum - if try !check(path: destination, checksum: artifact.checksum) { - let errorString = - "Prebuilt artifact \(artifactFile) checksum mismatch" - observabilityScope.emit(info: errorString) - self.delegate?.didDownloadPrebuilt( - from: artifactURL.absoluteString, - result: .failure(StringError(errorString)), - duration: fetchStart.distance(to: .now()) - ) - return nil + if let cacheFile { + // Cache the zip file + if fileSystem.exists(cacheFile) { + try fileSystem.removeFileTree(cacheFile) + } else { + try fileSystem.createDirectory(cacheFile.parentDirectory, recursive: true) + } + try fileSystem.copy(from: destination, to: cacheFile) + } } - - self.delegate?.didDownloadPrebuilt( - from: artifactURL.absoluteString, - result: .success((destination, false)), - duration: fetchStart.distance(to: .now()) - ) } // Extract @@ -461,18 +551,16 @@ extension Workspace { addedOrUpdatedPackages: [PackageReference], observabilityScope: ObservabilityScope ) async throws { - guard let prebuiltsManager = self.prebuiltsManager else { + guard let prebuiltsManager else { // Disabled return } - for prebuilt in prebuiltsManager.findPrebuilts( - packages: try manifests.requiredPackages - ) { + let addedPrebuilts = ManagedPrebuilts() + + for prebuilt in prebuiltsManager.findPrebuilts(packages: try manifests.requiredPackages) { guard - let manifest = manifests.allDependencyManifests[ - prebuilt.packageRef.identity - ], + let manifest = manifests.allDependencyManifests[prebuilt.identity], let packageVersion = manifest.manifest.version, let prebuiltManifest = try await prebuiltsManager .downloadManifest( @@ -484,14 +572,10 @@ extension Workspace { continue } - let hostPlatform = hostPrebuiltsPlatform + let hostPlatform = prebuiltsManager.hostPlatform for library in prebuiltManifest.libraries { - for artifact in library.artifacts { - guard artifact.platform == hostPlatform else { - continue - } - + for artifact in library.artifacts where artifact.platform == hostPlatform { if let path = try await prebuiltsManager .downloadPrebuilt( package: prebuilt, @@ -500,116 +584,194 @@ extension Workspace { artifact: artifact, observabilityScope: observabilityScope ) - { + { // Add to workspace state let managedPrebuilt = ManagedPrebuilt( - packageRef: prebuilt.packageRef, + identity: prebuilt.identity, + version: packageVersion, libraryName: library.name, path: path, products: library.products, cModules: library.cModules ) + addedPrebuilts.add(managedPrebuilt) await self.state.prebuilts.add(managedPrebuilt) - try await self.state.save() } } } } + + for prebuilt in await self.state.prebuilts.prebuilts { + if !addedPrebuilts.contains(where: { $0.identity == prebuilt.identity && $0.version == prebuilt.version }) { + await self.state.prebuilts.remove(packageIdentity: prebuilt.identity, targetName: prebuilt.libraryName) + } + } + + try await self.state.save() } +} - var hostPrebuiltsPlatform: PrebuiltsManifest.Platform? { - if self.hostToolchain.targetTriple.isDarwin() { - switch self.hostToolchain.targetTriple.arch { - case .aarch64: - return .macos_aarch64 - case .x86_64: - return .macos_x86_64 +extension Workspace.PrebuiltsManifest.Platform { + public var arch: Arch { + switch self { + case .macos_aarch64, .windows_aarch64, + .ubuntu_noble_aarch64, .ubuntu_jammy_aarch64, .ubuntu_focal_aarch64, + .fedora_39_aarch64, + .amazonlinux2_aarch64, + .rhel_ubi9_aarch64, + .debian_12_aarch64: + return .aarch64 + case .macos_x86_64, .windows_x86_64, + .ubuntu_noble_x86_64, .ubuntu_jammy_x86_64, .ubuntu_focal_x86_64, + .fedora_39_x86_64, + .amazonlinux2_x86_64, + .rhel_ubi9_x86_64, + .debian_12_x86_64: + return .x86_64 + } + } + + public var os: OS { + switch self { + case .macos_aarch64, .macos_x86_64: + return .macos + case .windows_aarch64, .windows_x86_64: + return .windows + case .ubuntu_noble_aarch64, .ubuntu_noble_x86_64, + .ubuntu_jammy_aarch64, .ubuntu_jammy_x86_64, + .ubuntu_focal_aarch64, .ubuntu_focal_x86_64, + .fedora_39_aarch64, .fedora_39_x86_64, + .amazonlinux2_aarch64, .amazonlinux2_x86_64, + .rhel_ubi9_aarch64, .rhel_ubi9_x86_64, + .debian_12_aarch64, .debian_12_x86_64: + return .linux + } + } + + /// Determine host platform based on compilation target + public static var hostPlatform: Self? { + let arch: Arch? +#if arch(arm64) + arch = .aarch64 +#elseif arch(x86_64) + arch = .x86_64 +#else + arch = nil +#endif + guard let arch else { + return nil + } + +#if os(macOS) + switch arch { + case .aarch64: + return .macos_aarch64 + case .x86_64: + return .macos_x86_64 + } +#elseif os(Windows) + switch arch { + case .aarch64: + return .windows_aarch64 + case .x86_64: + return .windows_x86_64 + } +#elseif os(Linux) + // Load up the os-release file into a dictionary + guard let osData = try? String(contentsOfFile: "/etc/os-release", encoding: .utf8) + else { + return nil + } + let osLines = osData.split(separator: "\n") + let osDict = osLines.reduce(into: [Substring: String]()) { + (dict, line) in + let parts = line.split(separator: "=", maxSplits: 2) + dict[parts[0]] = parts[1...].joined(separator: "=").trimmingCharacters(in: ["\""]) + } + + switch osDict["ID"] { + case "ubuntu": + switch osDict["VERSION_CODENAME"] { + case "noble": + switch arch { + case .aarch64: + return .ubuntu_noble_aarch64 + case .x86_64: + return .ubuntu_noble_x86_64 + } + case "jammy": + switch arch { + case .aarch64: + return .ubuntu_jammy_aarch64 + case .x86_64: + return .ubuntu_jammy_x86_64 + } + case "focal": + switch arch { + case .aarch64: + return .ubuntu_focal_aarch64 + case .x86_64: + return .ubuntu_focal_x86_64 + } default: return nil } - } else if self.hostToolchain.targetTriple.isWindows() { - switch self.hostToolchain.targetTriple.arch { - case .aarch64: - return .windows_aarch64 - case .x86_64: - return .windows_x86_64 + case "fedora": + switch osDict["VERSION_ID"] { + case "39", "41": + switch arch { + case .aarch64: + return .fedora_39_aarch64 + case .x86_64: + return .fedora_39_x86_64 + } default: return nil } - } else if self.hostToolchain.targetTriple.isLinux() { - // Load up the os-release file into a dictionary - guard let osData = try? String(contentsOfFile: "/etc/os-release", encoding: .utf8) - else { + case "amzn": + switch osDict["VERSION_ID"] { + case "2": + switch arch { + case .aarch64: + return .amazonlinux2_aarch64 + case .x86_64: + return .amazonlinux2_x86_64 + } + default: return nil } - let osLines = osData.split(separator: "\n") - let osDict = osLines.reduce(into: [Substring: String]()) { - (dict, line) in - let parts = line.split(separator: "=", maxSplits: 2) - dict[parts[0]] = parts[1...].joined(separator: "=").trimmingCharacters(in: ["\""]) + case "rhel": + guard let version = osDict["VERSION_ID"] else { + return nil } - - switch osDict["ID"] { - case "ubuntu": - switch osDict["VERSION_CODENAME"] { - case "jammy": - switch self.hostToolchain.targetTriple.arch { - case .aarch64: - return .ubuntu_jammy_aarch64 - case .x86_64: - return .ubuntu_jammy_x86_64 - default: - return nil - } - case "focal": - switch self.hostToolchain.targetTriple.arch { - case .aarch64: - return .ubuntu_focal_aarch64 - case .x86_64: - return .ubuntu_focal_x86_64 - default: - return nil - } - default: - return nil - } - case "amzn": - switch osDict["VERSION_ID"] { - case "2": - switch self.hostToolchain.targetTriple.arch { - case .aarch64: - return .amazonlinux2_aarch64 - case .x86_64: - return .amazonlinux2_x86_64 - default: - return nil - } - default: - return nil - } - case "rhel": - guard let version = osDict["VERSION_ID"] else { - return nil + switch version.split(separator: ".")[0] { + case "9": + switch arch { + case .aarch64: + return .rhel_ubi9_aarch64 + case .x86_64: + return .rhel_ubi9_x86_64 } - switch version.split(separator: ".")[0] { - case "9": - switch self.hostToolchain.targetTriple.arch { - case .aarch64: - return .rhel_ubi9_aarch64 - case .x86_64: - return .rhel_ubi9_x86_64 - default: - return nil - } - default: - return nil + default: + return nil + } + case "debian": + switch osDict["VERSION_ID"] { + case "12": + switch arch { + case .aarch64: + return .debian_12_aarch64 + case .x86_64: + return .debian_12_x86_64 } default: return nil } - } else { + default: return nil } +#else + return nil +#endif } - } diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index 3c395133031..5563f16246e 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -209,7 +209,7 @@ extension WorkspaceStateStorage { self.object = .init( dependencies: dependencies.map { .init($0) }.sorted { $0.packageRef.identity < $1.packageRef.identity }, artifacts: artifacts.map { .init($0) }.sorted { $0.packageRef.identity < $1.packageRef.identity }, - prebuilts: prebuilts.map { .init($0) }.sorted { $0.packageRef.identity < $1.packageRef.identity } + prebuilts: prebuilts.map { .init($0) }.sorted { $0.identity < $1.identity } ) } @@ -455,14 +455,16 @@ extension WorkspaceStateStorage { } struct Prebuilt: Codable { - let packageRef: PackageReference + let identity: PackageIdentity + let version: TSCUtility.Version let libraryName: String let path: Basics.AbsolutePath let products: [String] let cModules: [String] init(_ managedPrebuilt: Workspace.ManagedPrebuilt) { - self.packageRef = .init(managedPrebuilt.packageRef) + self.identity = managedPrebuilt.identity + self.version = managedPrebuilt.version self.libraryName = managedPrebuilt.libraryName self.path = managedPrebuilt.path self.products = managedPrebuilt.products @@ -534,8 +536,9 @@ extension Workspace.ManagedArtifact { extension Workspace.ManagedPrebuilt { fileprivate init(_ prebuilt: WorkspaceStateStorage.V7.Prebuilt) throws { - try self.init( - packageRef: .init(prebuilt.packageRef), + self.init( + identity: prebuilt.identity, + version: prebuilt.version, libraryName: prebuilt.libraryName, path: prebuilt.path, products: prebuilt.products, diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 90e98927e6f..116c8e44bb8 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -568,19 +568,30 @@ public class Workspace { // register the binary artifacts downloader with the cancellation handler cancellator?.register(name: "binary artifacts downloads", handler: binaryArtifactsManager) - let prebuiltsManager: PrebuiltsManager? = configuration.usePrebuilts ? PrebuiltsManager( - fileSystem: fileSystem, - authorizationProvider: authorizationProvider, - scratchPath: location.prebuiltsDirectory, - cachePath: customPrebuiltsManager?.useCache == false || !configuration - .sharedDependenciesCacheEnabled ? .none : location.sharedPrebuiltsCacheDirectory, - customHTTPClient: customPrebuiltsManager?.httpClient, - customArchiver: customPrebuiltsManager?.archiver, - delegate: delegate.map(WorkspacePrebuiltsManagerDelegate.init(workspaceDelegate:)) - ) : .none - // register the prebuilt packages downloader with the cancellation handler - if let prebuiltsManager { + if configuration.usePrebuilts, let hostPlatform = customPrebuiltsManager?.hostPlatform ?? PrebuiltsManifest.Platform.hostPlatform { + let rootCertPath: AbsolutePath? + if let path = configuration.prebuiltsRootCertPath { + rootCertPath = try AbsolutePath(validating: path) + } else { + rootCertPath = nil + } + + let prebuiltsManager = PrebuiltsManager( + fileSystem: fileSystem, + hostPlatform: hostPlatform, + authorizationProvider: authorizationProvider, + scratchPath: location.prebuiltsDirectory, + cachePath: customPrebuiltsManager?.useCache == false || !configuration.sharedDependenciesCacheEnabled ? .none : location.sharedPrebuiltsCacheDirectory, + customHTTPClient: customPrebuiltsManager?.httpClient, + customArchiver: customPrebuiltsManager?.archiver, + delegate: delegate.map(WorkspacePrebuiltsManagerDelegate.init(workspaceDelegate:)), + prebuiltsDownloadURL: configuration.prebuiltsDownloadURL, + rootCertPath: customPrebuiltsManager?.rootCertPath ?? rootCertPath + ) cancellator?.register(name: "package prebuilts downloads", handler: prebuiltsManager) + self.prebuiltsManager = prebuiltsManager + } else { + self.prebuiltsManager = nil } // initialize @@ -599,7 +610,6 @@ public class Workspace { self.registryClient = registryClient self.registryDownloadsManager = registryDownloadsManager self.binaryArtifactsManager = binaryArtifactsManager - self.prebuiltsManager = prebuiltsManager self.identityResolver = identityResolver self.dependencyMapper = dependencyMapper @@ -955,17 +965,12 @@ extension Workspace { } let prebuilts: [PackageIdentity: [String: PrebuiltLibrary]] = await self.state.prebuilts.reduce(into: .init()) { - let prebuilt = PrebuiltLibrary( - packageRef: $1.packageRef, - libraryName: $1.libraryName, - path: $1.path, - products: $1.products, - cModules: $1.cModules - ) + let prebuilt = PrebuiltLibrary(identity: $1.identity, libraryName: $1.libraryName, path: $1.path, products: $1.products, cModules: $1.cModules) for product in $1.products { - $0[$1.packageRef.identity, default: [:]][product] = prebuilt + $0[$1.identity, default: [:]][product] = prebuilt } } + // Load the graph. let packageGraph = try ModulesGraph.load( root: manifests.root, diff --git a/Sources/_InternalTestSupport/ManifestExtensions.swift b/Sources/_InternalTestSupport/ManifestExtensions.swift index 7f65ad5cf2d..225ae53d84c 100644 --- a/Sources/_InternalTestSupport/ManifestExtensions.swift +++ b/Sources/_InternalTestSupport/ManifestExtensions.swift @@ -288,4 +288,28 @@ extension Manifest { pruneDependencies: false ) } + + public func with(dependencies: [PackageDependency]) -> Manifest { + Manifest( + displayName: self.displayName, + packageIdentity: self.packageIdentity, + path: self.path, + packageKind: self.packageKind, + packageLocation: self.packageLocation, + defaultLocalization: self.defaultLocalization, + platforms: self.platforms, + version: self.version, + revision: self.revision, + toolsVersion: self.toolsVersion, + pkgConfig: self.pkgConfig, + providers: self.providers, + cLanguageStandard: self.cLanguageStandard, + cxxLanguageStandard: self.cxxLanguageStandard, + swiftLanguageVersions: self.swiftLanguageVersions, + dependencies: dependencies, + products: self.products, + targets: self.targets, + traits: self.traits + ) + } } diff --git a/Sources/_InternalTestSupport/MockWorkspace.swift b/Sources/_InternalTestSupport/MockWorkspace.swift index b7ce26427a2..441f06ddd8b 100644 --- a/Sources/_InternalTestSupport/MockWorkspace.swift +++ b/Sources/_InternalTestSupport/MockWorkspace.swift @@ -403,6 +403,8 @@ public final class MockWorkspace { defaultRegistry: self.defaultRegistry, manifestImportRestrictions: .none, usePrebuilts: self.customPrebuiltsManager != nil, + prebuiltsDownloadURL: nil, + prebuiltsRootCertPath: nil, pruneDependencies: self.pruneDependencies, traitConfiguration: self.traitConfiguration ), diff --git a/Sources/swift-build-prebuilts/BuildPrebuilts.swift b/Sources/swift-build-prebuilts/BuildPrebuilts.swift index f827ff97357..4db1740dc17 100644 --- a/Sources/swift-build-prebuilts/BuildPrebuilts.swift +++ b/Sources/swift-build-prebuilts/BuildPrebuilts.swift @@ -33,6 +33,7 @@ struct PrebuiltRepos: Identifiable { let tag: String let manifest: Workspace.PrebuiltsManifest let cModulePaths: [String: [String]] + let addProduct: (Workspace.PrebuiltsManifest.Library, AbsolutePath) async throws -> () var id: String { tag } } @@ -48,9 +49,77 @@ var prebuiltRepos: IdentifiableSet = [ .init( name: "MacroSupport", products: [ + "SwiftBasicFormat", + "SwiftCompilerPlugin", + "SwiftDiagnostics", + "SwiftIDEUtils", + "SwiftOperators", + "SwiftParser", + "SwiftParserDiagnostics", + "SwiftRefactor", + "SwiftSyntax", + "SwiftSyntaxBuilder", + "SwiftSyntaxMacros", + "SwiftSyntaxMacroExpansion", "SwiftSyntaxMacrosTestSupport", + "SwiftSyntaxMacrosGenericTestSupport", + "_SwiftCompilerPluginMessageHandling", + "_SwiftLibraryPluginProvider", + ], + cModules: [ + "_SwiftSyntaxCShims", + ] + ), + ]), + cModulePaths: [ + "_SwiftSyntaxCShims": ["Sources", "_SwiftSyntaxCShims"] + ], + addProduct: { library, repoDir in + let targets = [ + "SwiftBasicFormat", + "SwiftCompilerPlugin", + "SwiftDiagnostics", + "SwiftIDEUtils", + "SwiftOperators", + "SwiftParser", + "SwiftParserDiagnostics", + "SwiftRefactor", + "SwiftSyntax", + "SwiftSyntaxBuilder", + "SwiftSyntaxMacros", + "SwiftSyntaxMacroExpansion", + "SwiftSyntaxMacrosTestSupport", + "SwiftSyntaxMacrosGenericTestSupport", + "SwiftCompilerPluginMessageHandling", + "SwiftLibraryPluginProvider", + ] + try await shell("swift package add-product \(library.name) --type static-library --targets \(targets.joined(separator: " "))", cwd: repoDir) + } + ), + .init( + tag:"601.0.1", + manifest: .init(libraries: [ + .init( + name: "MacroSupport", + products: [ + "SwiftBasicFormat", "SwiftCompilerPlugin", - "SwiftSyntaxMacros" + "SwiftDiagnostics", + "SwiftIDEUtils", + "SwiftIfConfig", + "SwiftLexicalLookup", + "SwiftOperators", + "SwiftParser", + "SwiftParserDiagnostics", + "SwiftRefactor", + "SwiftSyntax", + "SwiftSyntaxBuilder", + "SwiftSyntaxMacros", + "SwiftSyntaxMacroExpansion", + "SwiftSyntaxMacrosTestSupport", + "SwiftSyntaxMacrosGenericTestSupport", + "_SwiftCompilerPluginMessageHandling", + "_SwiftLibraryPluginProvider", ], cModules: [ "_SwiftSyntaxCShims", @@ -60,15 +129,47 @@ var prebuiltRepos: IdentifiableSet = [ ]), cModulePaths: [ "_SwiftSyntaxCShims": ["Sources", "_SwiftSyntaxCShims"] - ] + ], + addProduct: { library, repoDir in + let targets = [ + "SwiftBasicFormat", + "SwiftCompilerPlugin", + "SwiftDiagnostics", + "SwiftIDEUtils", + "SwiftIfConfig", + "SwiftLexicalLookup", + "SwiftOperators", + "SwiftParser", + "SwiftParserDiagnostics", + "SwiftRefactor", + "SwiftSyntax", + "SwiftSyntaxBuilder", + "SwiftSyntaxMacros", + "SwiftSyntaxMacroExpansion", + "SwiftSyntaxMacrosTestSupport", + "SwiftSyntaxMacrosGenericTestSupport", + "SwiftCompilerPluginMessageHandling", + "SwiftLibraryPluginProvider", + ] + // swift package add-product doesn't work here since it's now computed + let packageFile = repoDir.appending(component: "Package.swift") + var package = try String(contentsOf: packageFile.asURL) + package.replace("products: products,", with: """ + products: products + [ + .library(name: "\(library.name)", type: .static, targets: [ + \(targets.map({ "\"\($0)\"" }).joined(separator: ",")) + ]) + ], + """) + try package.write(to: packageFile.asURL, atomically: true, encoding: .utf8) + } ), ] ), ] -let manifestHost = URL(string: "https://github.com/dschaefer2/swift-syntax/releases/download")! let swiftVersion = "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)" -let dockerImageRoot = "swiftlang/swift:nightly-" +let dockerImageRoot = "swiftlang/swift:nightly-6.1-" @main struct BuildPrebuilts: AsyncParsableCommand { @@ -84,49 +185,110 @@ struct BuildPrebuilts: AsyncParsableCommand { @Option(help: "The command to use for docker.") var dockerCommand: String = "docker" + @Flag(help: "Whether to build the prebuilt artifacts") + var build = false + + @Flag(help: "Whether to sign the manifest") + var sign = false + + @Option(name: .customLong("private-key-path"), help: "The path to certificate's private key (PEM encoded)") + var privateKeyPathStr: String? + + @Option(name: .customLong("cert-chain-path"), help: "Path to a certificate (DER encoded) in the chain. The certificate used for signing must be first and the root certificate last.") + var certChainPathStrs: [String] = [] + + @Flag(help: .hidden) + var testSigning: Bool = false + + func validate() throws { + if sign && !testSigning { + guard privateKeyPathStr != nil else { + throw ValidationError("No private key path provided") + } + + guard !certChainPathStrs.isEmpty else { + throw ValidationError("No certificates provided") + } + } + + if !build && !sign && !testSigning { + throw ValidationError("Requires one of --build or --sign or both") + } + } + mutating func run() async throws { - let fm = FileManager.default + if build { + try await build() + } + + if sign || testSigning { + try await sign() + } + } + + mutating func build() async throws { + let fileSystem = localFileSystem + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted print("Stage directory: \(stageDir)") - try fm.removeItem(atPath: stageDir.pathString) - try fm.createDirectory(atPath: stageDir.pathString, withIntermediateDirectories: true) - _ = fm.changeCurrentDirectoryPath(stageDir.pathString) + + let srcDir = stageDir.appending("src") + let libDir = stageDir.appending("lib") + let modulesDir = stageDir.appending("Modules") + let includesDir = stageDir.appending("include") + + if fileSystem.exists(srcDir) { + try fileSystem.removeFileTree(srcDir) + } + try fileSystem.createDirectory(srcDir, recursive: true) + + if fileSystem.exists(libDir) { + try fileSystem.removeFileTree(libDir) + } + + if fileSystem.exists(modulesDir) { + try fileSystem.removeFileTree(modulesDir) + } + + if fileSystem.exists(includesDir) { + try fileSystem.removeFileTree(includesDir) + } for repo in prebuiltRepos.values { - let repoDir = stageDir.appending(repo.url.lastPathComponent) - let libDir = stageDir.appending("lib") - let modulesDir = stageDir.appending("modules") - let includesDir = stageDir.appending("include") + let repoDir = srcDir.appending(repo.url.lastPathComponent) let scratchDir = repoDir.appending(".build") let buildDir = scratchDir.appending("release") let srcModulesDir = buildDir.appending("Modules") + let prebuiltDir = stageDir.appending(repo.url.lastPathComponent) - try await shell("git clone \(repo.url)") + try await shell("git clone \(repo.url)", cwd: srcDir) for version in repo.versions { - _ = fm.changeCurrentDirectoryPath(repoDir.pathString) - try await shell("git checkout \(version.tag)") + let versionDir = prebuiltDir.appending(version.tag) + if !fileSystem.exists(versionDir) { + try fileSystem.createDirectory(versionDir, recursive: true) + } + + try await shell("git checkout \(version.tag)", cwd: repoDir) var newLibraries: IdentifiableSet = [] for library in version.manifest.libraries { - // TODO: this is assuming products map to target names which is not always true - try await shell("swift package add-product \(library.name) --type static-library --targets \(library.products.joined(separator: " "))") - - var newArtifacts: [Workspace.PrebuiltsManifest.Library.Artifact] = [] + try await version.addProduct(library, repoDir) for platform in Workspace.PrebuiltsManifest.Platform.allCases { guard canBuild(platform) else { continue } - try fm.createDirectory(atPath: libDir.pathString, withIntermediateDirectories: true) - try fm.createDirectory(atPath: modulesDir.pathString, withIntermediateDirectories: true) - try fm.createDirectory(atPath: includesDir.pathString, withIntermediateDirectories: true) + try fileSystem.createDirectory(libDir, recursive: true) + try fileSystem.createDirectory(modulesDir, recursive: true) + try fileSystem.createDirectory(includesDir, recursive: true) // Clean out the scratch dir - if fm.fileExists(atPath: scratchDir.pathString) { - try fm.removeItem(atPath: scratchDir.pathString) + if fileSystem.exists(scratchDir) { + try fileSystem.removeFileTree(scratchDir) } // Build @@ -134,87 +296,155 @@ struct BuildPrebuilts: AsyncParsableCommand { if docker, let dockerTag = platform.dockerTag, let dockerPlatform = platform.arch.dockerPlatform { cmd += "\(dockerCommand) run --rm --platform \(dockerPlatform) -v \(repoDir):\(repoDir) -w \(repoDir) \(dockerImageRoot)\(dockerTag) " } - cmd += "swift build -c release --arch \(platform.arch) --product \(library.name)" - try await shell(cmd) + cmd += "swift build -c release -debug-info-format none --arch \(platform.arch) --product \(library.name)" + try await shell(cmd, cwd: repoDir) // Copy the library to staging let lib = "lib\(library.name).a" - try fm.copyItem(atPath: buildDir.appending(lib).pathString, toPath: libDir.appending(lib).pathString) + try fileSystem.copy(from: buildDir.appending(lib), to: libDir.appending(lib)) // Copy the swiftmodules - for file in try fm.contentsOfDirectory(atPath: srcModulesDir.pathString) { - try fm.copyItem(atPath: srcModulesDir.appending(file).pathString, toPath: modulesDir.appending(file).pathString) + for file in try fileSystem.getDirectoryContents(srcModulesDir) { + try fileSystem.copy(from: srcModulesDir.appending(file), to: modulesDir.appending(file)) } - // Copy the C module headers + // Do a deep copy of the C module headers for cModule in library.cModules { let cModuleDir = version.cModulePaths[cModule] ?? ["Sources", cModule] let srcIncludeDir = repoDir.appending(components: cModuleDir).appending("include") let destIncludeDir = includesDir.appending(cModule) - try fm.createDirectory(atPath: destIncludeDir.pathString, withIntermediateDirectories: true) - for file in try fm.contentsOfDirectory(atPath: srcIncludeDir.pathString) { - try fm.copyItem(atPath: srcIncludeDir.appending(file).pathString, toPath: destIncludeDir.appending(file).pathString) + + try fileSystem.createDirectory(destIncludeDir, recursive: true) + try fileSystem.enumerate(directory: srcIncludeDir) { srcPath in + let destPath = destIncludeDir.appending(srcPath.relative(to: srcIncludeDir)) + try fileSystem.createDirectory(destPath.parentDirectory) + try fileSystem.copy(from: srcPath, to: destPath) } } // Zip it up - _ = fm.changeCurrentDirectoryPath(stageDir.pathString) - let zipFile = stageDir.appending("\(swiftVersion)-\(library.name)-\(platform).zip") let contentDirs = ["lib", "Modules"] + (library.cModules.isEmpty ? [] : ["include"]) #if os(Windows) - try await shell("tar -acf \(zipFile.pathString) \(contentDirs.joined(separator: " "))") + let zipFile = versionDir.appending("\(swiftVersion)-\(library.name)-\(platform).zip") + try await shell("tar -acf \(zipFile.pathString) \(contentDirs.joined(separator: " "))", cwd: stageDir) + let contents = try ByteString(Data(contentsOf: zipFile.asURL)) +#elseif os(Linux) + let tarFile = versionDir.appending("\(swiftVersion)-\(library.name)-\(platform).tar.gz") + try await shell("tar -zcf \(tarFile.pathString) \(contentDirs.joined(separator: " "))", cwd: stageDir) + let contents = try ByteString(Data(contentsOf: tarFile.asURL)) #else - try await shell("zip -r \(zipFile.pathString) \(contentDirs.joined(separator: " "))") + let zipFile = versionDir.appending("\(swiftVersion)-\(library.name)-\(platform).zip") + try await shell("zip -r \(zipFile.pathString) \(contentDirs.joined(separator: " "))", cwd: stageDir) + let contents = try ByteString(Data(contentsOf: zipFile.asURL)) #endif - _ = fm.changeCurrentDirectoryPath(repoDir.pathString) - let contents = try ByteString(Data(contentsOf: zipFile.asURL)) let checksum = SHA256().hash(contents).hexadecimalRepresentation + let artifact: Workspace.PrebuiltsManifest.Library.Artifact = + .init(platform: platform, checksum: checksum) - newArtifacts.append(.init(platform: platform, checksum: checksum)) + let artifactJsonFile = versionDir.appending("\(swiftVersion)-\(library.name)-\(platform).zip.json") + try fileSystem.writeFileContents(artifactJsonFile, data: encoder.encode(artifact)) - try fm.removeItem(atPath: libDir.pathString) - try fm.removeItem(atPath: modulesDir.pathString) - try fm.removeItem(atPath: includesDir.pathString) + try fileSystem.removeFileTree(libDir) + try fileSystem.removeFileTree(modulesDir) + try fileSystem.removeFileTree(includesDir) } + let decoder = JSONDecoder() let newLibrary = Workspace.PrebuiltsManifest.Library( name: library.name, products: library.products, cModules: library.cModules, - artifacts: newArtifacts + artifacts: try fileSystem.getDirectoryContents(versionDir) + .filter({ $0.hasSuffix(".zip.json")}) + .compactMap({ + let data: Data = try fileSystem.readFileContents(versionDir.appending($0)) + return try? decoder.decode(Workspace.PrebuiltsManifest.Library.Artifact.self, from: data) + }) ) newLibraries.insert(newLibrary) - try await shell("git reset --hard") + try await shell("git restore .", cwd: repoDir) } + } + } - if let oldManifest = try await downloadManifest(version: version) { - // Add in elements from the old manifest we haven't generated - for library in oldManifest.libraries { - if var newLibrary = newLibraries[library.name] { - var newArtifacts = IdentifiableSet(newLibrary.artifacts) - for oldArtifact in library.artifacts { - if !newArtifacts.contains(id: oldArtifact.id) { - newArtifacts.insert(oldArtifact) - } - } - newLibrary.artifacts = .init(newArtifacts.values) - newLibraries.insert(newLibrary) - } else { - newLibraries.insert(library) - } - } + try fileSystem.changeCurrentWorkingDirectory(to: stageDir) + try fileSystem.removeFileTree(srcDir) + } + + mutating func sign() async throws { + let fileSystem = localFileSystem + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let decoder = JSONDecoder() + + for repo in prebuiltRepos.values { + let prebuiltDir = stageDir.appending(repo.url.lastPathComponent) + for version in repo.versions { + let versionDir = prebuiltDir.appending(version.tag) + let manifestFile = versionDir.appending("\(swiftVersion)-manifest.json") + + var manifest = version.manifest + manifest.libraries = try manifest.libraries.map({ + .init(name: $0.name, + products: $0.products, + cModules: $0.cModules, + artifacts: try fileSystem.getDirectoryContents(versionDir) + .filter({ $0.hasSuffix(".zip.json")}) + .compactMap({ + let data: Data = try fileSystem.readFileContents(versionDir.appending($0)) + return try? decoder.decode(Workspace.PrebuiltsManifest.Library.Artifact.self, from: data) + }) + ) + }) + + if testSigning { + // Use SwiftPM's test certificate chain and private key for testing + let certsPath = try AbsolutePath(validating: #file) + .parentDirectory.parentDirectory.parentDirectory + .appending(components: "Fixtures", "Signing", "Certificates") + privateKeyPathStr = certsPath.appending("Test_rsa_key.pem").pathString + certChainPathStrs = [ + certsPath.appending("Test_rsa.cer").pathString, + certsPath.appending("TestIntermediateCA.cer").pathString, + certsPath.appending("TestRootCA.cer").pathString + ] + } + + guard let privateKeyPathStr else { + fatalError("No private key path provided") } - let newManifest = Workspace.PrebuiltsManifest(libraries: .init(newLibraries.values)) - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let manifestData = try encoder.encode(newManifest) - let manifestFile = stageDir.appending("\(swiftVersion)-manifest.json") - try manifestData.write(to: manifestFile.asURL) + let certChainPaths = try certChainPathStrs.map { try make(path: $0) } + + guard let rootCertPath = certChainPaths.last else { + fatalError("No certificates provided") + } + + let privateKeyPath = try make(path: privateKeyPathStr) + + try await withTemporaryDirectory { tmpDir in + try fileSystem.copy(from: rootCertPath, to: tmpDir.appending(rootCertPath.basename)) + + let signer = ManifestSigning( + trustedRootCertsDir: tmpDir, + observabilityScope: ObservabilitySystem { _, diagnostic in print(diagnostic) }.topScope + ) + + let signature = try await signer.sign( + manifest: manifest, + certChainPaths: certChainPaths, + certPrivateKeyPath: privateKeyPath, + fileSystem: fileSystem + ) + + let signedManifest = Workspace.SignedPrebuiltsManifest(manifest: manifest, signature: signature) + try encoder.encode(signedManifest).write(to: manifestFile.asURL) + } } } + } func canBuild(_ platform: Workspace.PrebuiltsManifest.Platform) -> Bool { @@ -229,70 +459,53 @@ struct BuildPrebuilts: AsyncParsableCommand { if platform.os == .windows { return true } +#elseif os(Linux) + if platform == Workspace.PrebuiltsManifest.Platform.hostPlatform { + return true + } #endif return docker && platform.os == .linux } - func shell(_ command: String) async throws { + func make(path: String) throws -> AbsolutePath { + if let path = try? AbsolutePath(validating: path) { + // It's already absolute + return path + } + + return try AbsolutePath(validating: FileManager.default.currentDirectoryPath) + .appending(RelativePath(validating: path)) + } + +} + +func shell(_ command: String, cwd: AbsolutePath) async throws { + _ = FileManager.default.changeCurrentDirectoryPath(cwd.pathString) + #if os(Windows) - let arguments = ["C:\\Windows\\System32\\cmd.exe", "/c", command] + let arguments = ["C:\\Windows\\System32\\cmd.exe", "/c", command] #else - let arguments = ["/bin/bash", "-c", command] + let arguments = ["/bin/bash", "-c", command] #endif - let process = AsyncProcess( - arguments: arguments, - outputRedirection: .none - ) - print("Running:", command) - try process.launch() - let result = try await process.waitUntilExit() - switch result.exitStatus { - case .terminated(code: let code): - if code != 0 { - throw StringError("Command exited with code \(code): \(command)") - } + let process = AsyncProcess( + arguments: arguments, + outputRedirection: .none + ) + print("Running:", command) + try process.launch() + let result = try await process.waitUntilExit() + switch result.exitStatus { + case .terminated(code: let code): + if code != 0 { + throw StringError("Command exited with code \(code): \(command)") + } #if os(Windows) - case .abnormal(exception: let exception): - throw StringError("Command threw exception \(exception): \(command)") + case .abnormal(exception: let exception): + throw StringError("Command threw exception \(exception): \(command)") #else - case .signalled(signal: let signal): - throw StringError("Command exited on signal \(signal): \(command)") + case .signalled(signal: let signal): + throw StringError("Command exited on signal \(signal): \(command)") #endif - } - } - - func downloadManifest(version: PrebuiltRepos.Version) async throws -> Workspace.PrebuiltsManifest? { - let fm = FileManager.default - let manifestFile = swiftVersion + "-manifest.json" - let destination = stageDir.appending(manifestFile) - if fm.fileExists(atPath: destination.pathString) { - do { - return try JSONDecoder().decode( - Workspace.PrebuiltsManifest.self, - from: Data(contentsOf: destination.asURL) - ) - } catch { - // redownload it - try fm.removeItem(atPath: destination.pathString) - } - } - let manifestURL = manifestHost.appending(components: version.tag, manifestFile) - print("Downloading:", manifestURL.absoluteString) - let httpClient = HTTPClient() - var headers = HTTPClientHeaders() - headers.add(name: "Accept", value: "application/json") - var request = HTTPClient.Request(kind: .generic(.get), url: manifestURL) - request.options.validResponseCodes = [200] - - let response = try? await httpClient.execute(request) { _, _ in } - if let body = response?.body { - return try JSONDecoder().decode( - Workspace.PrebuiltsManifest.self, - from: body - ) - } - - return nil } } diff --git a/Tests/BasicsTests/HTTPClientTests.swift b/Tests/BasicsTests/HTTPClientTests.swift index 1ced370499f..6ea4442b3db 100644 --- a/Tests/BasicsTests/HTTPClientTests.swift +++ b/Tests/BasicsTests/HTTPClientTests.swift @@ -21,15 +21,15 @@ class HTTPClientXCTest: XCTestCase { func testEponentialBackoff() async throws { try XCTSkipOnWindows(because: "https://github.com/swiftlang/swift-package-manager/issues/8501") let counter = SendableBox(0) - let lastCall = SendableBox() + let lastCall = SendableBox(Date()) let maxAttempts = 5 let errorCode = Int.random(in: 500 ..< 600) let delay = SendableTimeInterval.milliseconds(100) let httpClient = HTTPClient { _, _ in - let count = await counter.value! + let count = await counter.value let expectedDelta = pow(2.0, Double(count - 1)) * delay.timeInterval()! - let delta = await lastCall.value.flatMap { Date().timeIntervalSince($0) } ?? 0 + let delta = await Date().timeIntervalSince(lastCall.value) XCTAssertEqual(delta, expectedDelta, accuracy: 0.1) await counter.increment() @@ -425,7 +425,7 @@ struct HTTPClientTests { let httpClient = HTTPClient(configuration: configuration) { request, _ in await concurrentRequests.increment() - if await concurrentRequests.value! > maxConcurrentRequests { + if await concurrentRequests.value > maxConcurrentRequests { Issue.record("too many concurrent requests \(concurrentRequests), expected \(maxConcurrentRequests)") } diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index a222ee9e8ad..be957b476b5 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -4667,6 +4667,104 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } } + func testPrebuiltsFlags() async throws { + // Make sure the include path for the prebuilts get passed to the + // generated test entry point and discover targets on Linux/Windows + let observability = ObservabilitySystem.makeForTesting() + + let prebuiltLibrary = PrebuiltLibrary( + identity: .plain("swift-syntax"), + libraryName: "MacroSupport", + path: "/MyPackage/.build/prebuilts/swift-syntax/600.0.1/6.1-MacroSupport-macos_aarch64", + products: [ + "SwiftBasicFormat", + "SwiftCompilerPlugin", + "SwiftDiagnostics", + "SwiftIDEUtils", + "SwiftOperators", + "SwiftParser", + "SwiftParserDiagnostics", + "SwiftRefactor", + "SwiftSyntax", + "SwiftSyntaxBuilder", + "SwiftSyntaxMacros", + "SwiftSyntaxMacroExpansion", + "SwiftSyntaxMacrosTestSupport", + "SwiftSyntaxMacrosGenericTestSupport", + "_SwiftCompilerPluginMessageHandling", + "_SwiftLibraryPluginProvider" + ], + cModules: ["_SwiftSyntaxCShims"] + ) + + let fs = InMemoryFileSystem( + emptyFiles: [ + "/MyPackage/Sources/MyMacroMacros/MyMacroMacros.swift", + "/MyPackage/Sources/MyMacros/MyMacros.swift", + "/MyPackage/Sources/MyMacroTests/MyMacroTests.swift" + ] + ) + + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "MyPackage", + path: "/MyPackage", + targets: [ + TargetDescription(name: "MyMacroMacros", type: .macro), + TargetDescription( + name: "MyMacros", + dependencies: [ + "MyMacroMacros", + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ] + ), + TargetDescription( + name: "MyMacroTests", + dependencies: [ + "MyMacroMacros", + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ], + type: .test + ) + ] + ) + ], + prebuilts: [prebuiltLibrary.identity: prebuiltLibrary.products.reduce(into: [:]) { + $0[$1] = prebuiltLibrary + }], + observabilityScope: observability.topScope + ) + + func checkTriple(triple: Basics.Triple) async throws { + let result = try await BuildPlanResult( + plan: mockBuildPlan( + triple: triple, + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + ) + +#if os(Windows) + let modulesDir = "-I\(prebuiltLibrary.path.pathString)\\Modules" +#else + let modulesDir = "-I\(prebuiltLibrary.path.pathString)/Modules" +#endif + let mytest = try XCTUnwrap(result.allTargets(named: "MyMacroTests").first) + XCTAssert(try mytest.swift().compileArguments().contains(modulesDir)) + let entryPoint = try XCTUnwrap(result.allTargets(named: "MyPackagePackageTests").first) + XCTAssert(try entryPoint.swift().compileArguments().contains(modulesDir)) + let discovery = try XCTUnwrap(result.allTargets(named: "MyPackagePackageDiscoveredTests").first) + XCTAssert(try discovery.swift().compileArguments().contains(modulesDir)) + } + + try await checkTriple(triple: .x86_64Linux) + try await checkTriple(triple: .x86_64Windows) + } + func testExtraBuildFlags() async throws { let fs = InMemoryFileSystem( emptyFiles: @@ -7002,7 +7100,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { let myLib = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyLib" })).swift() XCTAssertFalse(myLib.additionalFlags.contains(where: { $0.contains("-tool")}), "flags shouldn't contain tools items") - + // Make sure the tests do have the include path and the module map from the lib let myMacroTests = try XCTUnwrap(plan.targets.first(where: { $0.module.name == "MyMacroTests" })).swift() let flags = myMacroTests.additionalFlags.joined(separator: " ") diff --git a/Tests/WorkspaceTests/PrebuiltsTests.swift b/Tests/WorkspaceTests/PrebuiltsTests.swift index 8af88b8464e..c36f73e0b37 100644 --- a/Tests/WorkspaceTests/PrebuiltsTests.swift +++ b/Tests/WorkspaceTests/PrebuiltsTests.swift @@ -18,6 +18,7 @@ import Basics import struct TSCBasic.SHA256 import struct TSCBasic.ByteString import struct TSCUtility.Version +import PackageGraph import PackageModel import Workspace import XCTest @@ -26,89 +27,137 @@ import _InternalTestSupport final class PrebuiltsTests: XCTestCase { let swiftVersion = "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)" - func initData(artifact: Data, swiftSyntaxVersion: String) throws -> (Workspace.PrebuiltsManifest, MockPackage, MockPackage) { - let manifest = Workspace.PrebuiltsManifest(libraries: [ - .init( - name: "MacroSupport", - products: [ - "SwiftSyntaxMacrosTestSupport", - "SwiftCompilerPlugin", - "SwiftSyntaxMacros" - ], - cModules: [ - "_SwiftSyntaxCShims" - ], - artifacts: [ - .init( - platform: .macos_aarch64, - checksum: SHA256().hash(ByteString(artifact)).hexadecimalRepresentation - ) - ] - ) - ]) - - let rootPackage = try MockPackage( - name: "Foo", - targets: [ - MockTarget( - name: "FooMacros", - dependencies: [ - .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), - .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + func with( + fileSystem: FileSystem, + artifact: Data, + swiftSyntaxVersion: String, + swiftSyntaxURL: String? = nil, + run: (Workspace.SignedPrebuiltsManifest, AbsolutePath, MockPackage, MockPackage) async throws -> () + ) async throws { + try await fixture(name: "Signing") { fixturePath in + let swiftSyntaxURL = swiftSyntaxURL ?? "https://github.com/swiftlang/swift-syntax" + + let manifest = Workspace.PrebuiltsManifest(libraries: [ + .init( + name: "MacroSupport", + products: [ + "SwiftSyntaxMacrosTestSupport", + "SwiftCompilerPlugin", + "SwiftSyntaxMacros" + ], + cModules: [ + "_SwiftSyntaxCShims" ], - type: .macro - ), - MockTarget( - name: "Foo", - dependencies: ["FooMacros"] - ), - MockTarget( - name: "FooClient", - dependencies: ["Foo"], - type: .executable - ), - MockTarget( - name: "FooTests", - dependencies: [ - "FooMacros", - .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + artifacts: [ + .init( + platform: .macos_aarch64, + checksum: SHA256().hash(ByteString(artifact)).hexadecimalRepresentation + ) ] ) - ], - dependencies: [ - .sourceControl( - url: "https://github.com/swiftlang/swift-syntax", - requirement: .exact(try XCTUnwrap(Version(swiftSyntaxVersion))) - ) + ]) + + let certsPath = fixturePath.appending("Certificates") + + let certPaths = [ + certsPath.appending("Test_rsa.cer"), + certsPath.appending("TestIntermediateCA.cer"), + certsPath.appending("TestRootCA.cer"), ] - ) - - let swiftSyntax = try MockPackage( - name: "swift-syntax", - url: "https://github.com/swiftlang/swift-syntax", - targets: [ - MockTarget(name: "SwiftSyntaxMacrosTestSupport"), - MockTarget(name: "SwiftCompilerPlugin"), - MockTarget(name: "SwiftSyntaxMacros"), - ], - products: [ - MockProduct(name: "SwiftSyntaxMacrosTestSupport", modules: ["SwiftSyntaxMacrosTestSupport"]), - MockProduct(name: "SwiftCompilerPlugin", modules: ["SwiftCompilerPlugin"]), - MockProduct(name: "SwiftSyntaxMacros", modules: ["SwiftSyntaxMacros"]), - ], - versions: ["600.0.1", "600.0.2"] - ) - - return (manifest, rootPackage, swiftSyntax) + let privateKeyPath = certsPath.appending("Test_rsa_key.pem") + + // Copy into in memory file system + for path in certPaths + [privateKeyPath] { + try fileSystem.writeFileContents(path, data: Data(contentsOf: path.asURL)) + } + + let rootCertPath = certPaths.last! + let trustDir = certsPath.appending("Trust") + try fileSystem.createDirectory(trustDir, recursive: true) + try fileSystem.copy(from: rootCertPath, to: trustDir.appending(rootCertPath.basename)) + + let signer = ManifestSigning( + trustedRootCertsDir: trustDir, + observabilityScope: ObservabilitySystem { _, diagnostic in print(diagnostic) }.topScope + ) + + let signature = try await signer.sign( + manifest: manifest, + certChainPaths: certPaths, + certPrivateKeyPath: privateKeyPath, + fileSystem: fileSystem + ) + + // Make sure the signing is valid + try await signer.validate(manifest: manifest, signature: signature, fileSystem: fileSystem) + + let signedManifest = Workspace.SignedPrebuiltsManifest(manifest: manifest, signature: signature) + + let rootPackage = try MockPackage( + name: "Foo", + targets: [ + MockTarget( + name: "FooMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ], + type: .macro + ), + MockTarget( + name: "Foo", + dependencies: ["FooMacros"] + ), + MockTarget( + name: "FooClient", + dependencies: ["Foo"], + type: .executable + ), + MockTarget( + name: "FooTests", + dependencies: [ + "FooMacros", + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ], + type: .test + ), + ], + dependencies: [ + .sourceControl( + url: swiftSyntaxURL, + requirement: .exact(try XCTUnwrap(Version(swiftSyntaxVersion))) + ) + ] + ) + + let swiftSyntax = try MockPackage( + name: "swift-syntax", + url: swiftSyntaxURL, + targets: [ + MockTarget(name: "SwiftSyntaxMacrosTestSupport"), + MockTarget(name: "SwiftCompilerPlugin"), + MockTarget(name: "SwiftSyntaxMacros"), + ], + products: [ + MockProduct(name: "SwiftSyntaxMacrosTestSupport", modules: ["SwiftSyntaxMacrosTestSupport"]), + MockProduct(name: "SwiftCompilerPlugin", modules: ["SwiftCompilerPlugin"]), + MockProduct(name: "SwiftSyntaxMacros", modules: ["SwiftSyntaxMacros"]), + ], + versions: ["600.0.1", "600.0.2", "601.0.0"] + ) + + try await run(signedManifest, rootCertPath, rootPackage, swiftSyntax) + } } - func checkSettings(_ target: Module, usePrebuilt: Bool) throws { + func checkSettings(_ rootPackage: ResolvedPackage, _ targetName: String, usePrebuilt: Bool) throws { + let target = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == targetName })) if usePrebuilt { let swiftFlags = try XCTUnwrap(target.buildSettings.assignments[.OTHER_SWIFT_FLAGS]).flatMap({ $0.values }) - XCTAssertTrue(swiftFlags.contains("-I\(AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/Modules").pathString)")) - XCTAssertTrue(swiftFlags.contains("-I\(AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/include/_SwiftSyntaxCShims").pathString)")) + XCTAssertTrue(swiftFlags.contains("-I/tmp/ws/.build/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64/Modules".fixwin)) + XCTAssertTrue(swiftFlags.contains("-I/tmp/ws/.build/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64/include/_SwiftSyntaxCShims".fixwin)) let ldFlags = try XCTUnwrap(target.buildSettings.assignments[.OTHER_LDFLAGS]).flatMap({ $0.values }) - XCTAssertTrue(ldFlags.contains(AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64/lib/libMacroSupport.a").pathString)) + XCTAssertTrue(ldFlags.contains("/tmp/ws/.build/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64/lib/libMacroSupport.a".fixwin)) } else { XCTAssertNil(target.buildSettings.assignments[.OTHER_SWIFT_FLAGS]) XCTAssertNil(target.buildSettings.assignments[.OTHER_LDFLAGS]) @@ -118,466 +167,736 @@ final class PrebuiltsTests: XCTestCase { func testSuccessPath() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() + let artifact = Data() + + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: artifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } + } + + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTAssertEqual(archivePath, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64.zip")) + XCTAssertEqual(destination, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64")) + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error || $0.severity == .warning }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } + } + } + func testVersionChange() async throws { + let sandbox = AbsolutePath("/tmp/ws") + let fs = InMemoryFileSystem() let artifact = Data() - let (manifest, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - let manifestData = try JSONEncoder().encode(manifest) - let httpClient = HTTPClient { request, progressHandler in - guard case .download(let fileSystem, let destination) = request.kind else { - throw StringError("invalid request \(request.kind)") + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: artifact) + return .okay() + } else { + // make sure it's the updated one + XCTAssertEqual( + request.url, + "https://download.swift.org/prebuilts/swift-syntax/601.0.0/\(self.swiftVersion)-manifest.json" + ) + return .notFound() + } + } + + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTAssertEqual(archivePath, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64.zip")) + XCTAssertEqual(destination, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64")) + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) + + try await workspace.checkPackageGraph(roots: [rootPackage.name]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error || $0.severity == .warning }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } + + // Change the version of swift syntax to one that doesn't have prebuilts + try await workspace.closeWorkspace(resetState: false, resetResolvedFile: false) + let key = MockManifestLoader.Key(url: sandbox.appending(components: "roots", rootPackage.name).pathString) + let oldManifest = try XCTUnwrap(workspace.manifestLoader.manifests[key]) + let oldSCM: PackageDependency.SourceControl + if case let .sourceControl(scm) = oldManifest.dependencies[0] { + oldSCM = scm + } else { + XCTFail("not source control") + return + } + let newDep = PackageDependency.sourceControl( + identity: oldSCM.identity, + nameForTargetDependencyResolutionOnly: oldSCM.nameForTargetDependencyResolutionOnly, + location: oldSCM.location, + requirement: .exact(try XCTUnwrap(Version("601.0.0"))), + productFilter: oldSCM.productFilter + ) + let newManifest = oldManifest.with(dependencies: [newDep]) + workspace.manifestLoader.manifests[key] = newManifest + + try await workspace.checkPackageGraph(roots: [rootPackage.name]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error || $0.severity == .warning }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) } - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - try fileSystem.writeFileContents(destination, data: manifestData) - return .okay() - } else if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { - try fileSystem.writeFileContents(destination, data: artifact) - return .okay() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() + // Change it back + try await workspace.closeWorkspace(resetState: false, resetResolvedFile: false) + workspace.manifestLoader.manifests[key] = oldManifest + + try await workspace.checkPackageGraph(roots: [rootPackage.name]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error || $0.severity == .warning }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) } } + } - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTAssertEqual(archivePath.pathString, AbsolutePath("/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip").pathString) - XCTAssertEqual(destination.pathString, AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64").pathString) - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver - ), - customHostTriple: Triple("arm64-apple-macosx15.0") - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: true) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: true) + func testSSHURL() async throws { + let sandbox = AbsolutePath("/tmp/ws") + let fs = InMemoryFileSystem() + let artifact = Data() + + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1", swiftSyntaxURL: "git@github.com:swiftlang/swift-syntax.git") { + manifest, rootCertPath, rootPackage, swiftSyntax in + + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: artifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } + } + + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTAssertEqual(archivePath, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64.zip")) + XCTAssertEqual(destination, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64")) + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testCachedArtifact() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let cacheFile = try AbsolutePath(validating: "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip") + let cacheFile = try AbsolutePath(validating: "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip") try fs.writeFileContents(cacheFile, data: artifact) - let (manifest, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - let manifestData = try JSONEncoder().encode(manifest) - - let httpClient = HTTPClient { request, progressHandler in - guard case .download(let fileSystem, let destination) = request.kind else { - throw StringError("invalid request \(request.kind)") + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + XCTFail("Unexpect download of archive") + try fileSystem.writeFileContents(destination, data: artifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } } - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - try fileSystem.writeFileContents(destination, data: manifestData) - return .okay() - } else if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { - XCTFail("Unexpect download of archive") - try fileSystem.writeFileContents(destination, data: artifact) - return .okay() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() - } - } + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTAssertEqual(archivePath, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64.zip")) + XCTAssertEqual(destination, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64")) + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTAssertEqual(archivePath.pathString, AbsolutePath("/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip").pathString) - XCTAssertEqual(destination.pathString, AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64").pathString) - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver - ), - customHostTriple: Triple("arm64-apple-macosx15.0") - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: true) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: true) + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testUnsupportedSwiftSyntaxVersion() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (_, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.2") - - let httpClient = HTTPClient { request, progressHandler in - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.2/\(self.swiftVersion)-manifest.json" { - return .notFound() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() - } - } - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTFail("Unexpected call to archiver") - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.2") { _, rootCertPath, rootPackage, swiftSyntax in + let secondFetch = SendableBox(false) + + let httpClient = HTTPClient { request, progressHandler in + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.2/\(self.swiftVersion)-manifest.json" { + let secondFetch = await secondFetch.value + XCTAssertFalse(secondFetch, "unexpected second fetch") + return .notFound() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } + } + + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTFail("Unexpected call to archiver") + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + rootCertPath: rootCertPath + ) ) - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: false) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: false) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } + + await secondFetch.set(true) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testUnsupportedArch() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (manifest, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - let manifestData = try JSONEncoder().encode(manifest) - let httpClient = HTTPClient { request, progressHandler in - guard case .download(let fileSystem, let destination) = request.kind else { - throw StringError("invalid request \(request.kind)") + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } } - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - try fileSystem.writeFileContents(destination, data: manifestData) - return .okay() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() - } - } + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTFail("Unexpected call to archiver") + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .ubuntu_noble_x86_64, + rootCertPath: rootCertPath + ) + ) - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTFail("Unexpected call to archiver") - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver - ), - customHostTriple: Triple("86_64-unknown-linux-gnu") - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: false) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: false) + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testUnsupportedSwiftVersion() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (_, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - - let httpClient = HTTPClient { request, progressHandler in - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - // Pretend it's a different swift version - return .notFound() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() + + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { _, rootCertPath, rootPackage, swiftSyntax in + let httpClient = HTTPClient { request, progressHandler in + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + // Pretend it's a different swift version + return .notFound() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } } - } - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTFail("Unexpected call to archiver") - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTFail("Unexpected call to archiver") + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + rootCertPath: rootCertPath + ) ) - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: false) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: false) - } + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } + } } - func testBadChecksumHttp() async throws { + func testBadSignature() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (manifest, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - let manifestData = try JSONEncoder().encode(manifest) - - let fakeArtifact = Data([56]) - let httpClient = HTTPClient { request, progressHandler in - guard case .download(let fileSystem, let destination) = request.kind else { - throw StringError("invalid request \(request.kind)") + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { goodManifest, rootCertPath, rootPackage, swiftSyntax in + // Make a change in the manifest + var manifest = goodManifest.manifest + manifest.libraries[0].artifacts[0] = .init(platform: manifest.libraries[0].artifacts[0].platform, checksum: "BAD") + let badManifest = Workspace.SignedPrebuiltsManifest( + manifest: manifest, + signature: goodManifest.signature + ) + let manifestData = try JSONEncoder().encode(badManifest) + + let fakeArtifact = Data([56]) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: fakeArtifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } } - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - try fileSystem.writeFileContents(destination, data: manifestData) - return .okay() - } else if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { - try fileSystem.writeFileContents(destination, data: fakeArtifact) - return .okay() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTFail("Unexpected call to archiver") + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + XCTAssertTrue(diagnostics.contains(where: { $0.message == "Failed to decode prebuilt manifest: invalidSignature" })) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) } } + } + + func testBadChecksumHttp() async throws { + let sandbox = AbsolutePath("/tmp/ws") + let fs = InMemoryFileSystem() + let artifact = Data() + + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let fakeArtifact = Data([56]) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: fakeArtifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } + } + + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTFail("Unexpected call to archiver") + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTFail("Unexpected call to archiver") - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver - ), - customHostTriple: Triple("arm64-apple-macosx15.0") - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: false) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: false) + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testBadChecksumCache() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (manifest, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - let manifestData = try JSONEncoder().encode(manifest) - - let fakeArtifact = Data([56]) - let cacheFile = try AbsolutePath(validating: "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip") - try fs.writeFileContents(cacheFile, data: fakeArtifact) - let httpClient = HTTPClient { request, progressHandler in - guard case .download(let fileSystem, let destination) = request.kind else { - throw StringError("invalid request \(request.kind)") + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, rootCertPath, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let fakeArtifact = Data([56]) + let cacheFile = try AbsolutePath(validating: "/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip") + try fs.writeFileContents(cacheFile, data: fakeArtifact) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + try fileSystem.writeFileContents(destination, data: manifestData) + return .okay() + } else if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { + try fileSystem.writeFileContents(destination, data: artifact) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } } - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - try fileSystem.writeFileContents(destination, data: manifestData) - return .okay() - } else if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip" { - try fileSystem.writeFileContents(destination, data: artifact) - return .okay() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() - } - } + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTAssertEqual(archivePath, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64.zip")) + XCTAssertEqual(destination, sandbox.appending(components: ".build", "prebuilts", "swift-syntax", "600.0.1", "\(self.swiftVersion)-MacroSupport-macos_aarch64")) + completion(.success(())) + }) + + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver, + hostPlatform: .macos_aarch64, + rootCertPath: rootCertPath + ) + ) - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTAssertEqual(archivePath.pathString, AbsolutePath("/home/user/caches/org.swift.swiftpm/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64.zip").pathString) - XCTAssertEqual(destination.pathString, AbsolutePath("/tmp/ws/.build/prebuilts/swift-syntax/\(self.swiftVersion)-MacroSupport-macos_aarch64").pathString) - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver - ), - customHostTriple: Triple("arm64-apple-macosx15.0") - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: true) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: true) + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: true) + try checkSettings(rootPackage, "FooTests", usePrebuilt: true) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testBadManifest() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (manifest, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - let manifestData = try JSONEncoder().encode(manifest) - let httpClient = HTTPClient { request, progressHandler in - guard case .download(let fileSystem, let destination) = request.kind else { - throw StringError("invalid request \(request.kind)") + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { manifest, _, rootPackage, swiftSyntax in + let manifestData = try JSONEncoder().encode(manifest) + + let httpClient = HTTPClient { request, progressHandler in + guard case .download(let fileSystem, let destination) = request.kind else { + throw StringError("invalid request \(request.kind)") + } + + if request.url == "https://download.swift.org/prebuilts/swift-syntax/600.0.1/\(self.swiftVersion)-manifest.json" { + let badManifestData = manifestData + Data("bad".utf8) + try fileSystem.writeFileContents(destination, data: badManifestData) + return .okay() + } else { + XCTFail("Unexpected URL \(request.url)") + return .notFound() + } } - if request.url == "https://github.com/dschaefer2/swift-syntax/releases/download/600.0.1/\(self.swiftVersion)-manifest.json" { - let badManifestData = manifestData + Data("bad".utf8) - try fileSystem.writeFileContents(destination, data: badManifestData) - return .okay() - } else { - XCTFail("Unexpected URL \(request.url)") - return .notFound() - } - } + let archiver = MockArchiver(handler: { _, archivePath, destination, completion in + XCTFail("Unexpected call to archiver") + completion(.success(())) + }) - let archiver = MockArchiver(handler: { _, archivePath, destination, completion in - XCTFail("Unexpected call to archiver") - completion(.success(())) - }) - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - prebuiltsManager: .init( - httpClient: httpClient, - archiver: archiver + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ], + prebuiltsManager: .init( + httpClient: httpClient, + archiver: archiver + ) ) - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: false) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: false) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } func testDisabled() async throws { let sandbox = AbsolutePath("/tmp/ws") let fs = InMemoryFileSystem() - let artifact = Data() - let (_, rootPackage, swiftSyntax) = try initData(artifact: artifact, swiftSyntaxVersion: "600.0.1") - - let workspace = try await MockWorkspace( - sandbox: sandbox, - fileSystem: fs, - roots: [ - rootPackage - ], - packages: [ - swiftSyntax - ], - customHostTriple: Triple("arm64-apple-macosx15.0") - ) - - try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in - XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) - let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) - let macroTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooMacros" })) - try checkSettings(macroTarget, usePrebuilt: false) - let testTarget = try XCTUnwrap(rootPackage.underlying.modules.first(where: { $0.name == "FooTests" })) - try checkSettings(testTarget, usePrebuilt: false) + + try await with(fileSystem: fs, artifact: artifact, swiftSyntaxVersion: "600.0.1") { _, _, rootPackage, swiftSyntax in + let workspace = try await MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + rootPackage + ], + packages: [ + swiftSyntax + ] + ) + + try await workspace.checkPackageGraph(roots: ["Foo"]) { modulesGraph, diagnostics in + XCTAssertTrue(diagnostics.filter({ $0.severity == .error }).isEmpty) + let rootPackage = try XCTUnwrap(modulesGraph.rootPackages.first) + try checkSettings(rootPackage, "FooMacros", usePrebuilt: false) + try checkSettings(rootPackage, "FooTests", usePrebuilt: false) + try checkSettings(rootPackage, "Foo", usePrebuilt: false) + try checkSettings(rootPackage, "FooClient", usePrebuilt: false) + } } } } + +extension String { + var fixwin: String { + #if os(Windows) + return self.replacingOccurrences(of: "/", with: "\\") + #else + return self + #endif + } +} From 7a927cd38a64fc88cacda3b8f160e386f1cfbc0a Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Sun, 11 May 2025 09:26:59 -0700 Subject: [PATCH 93/99] Do not try to add rpath arguments on noneOS (#8358) --- .../ProductBuildDescription.swift | 2 +- Tests/BuildTests/BuildPlanTests.swift | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index a98633d91ad..b07da282921 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -270,7 +270,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // Set rpath such that dynamic libraries are looked up // adjacent to the product, unless overridden. - if !self.buildParameters.linkingParameters.shouldDisableLocalRpath { + if triple.os != .noneOS, !self.buildParameters.linkingParameters.shouldDisableLocalRpath { switch triple.objectFormat { case .elf: args += ["-Xlinker", "-rpath=$ORIGIN"] diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index be957b476b5..8655d79a652 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -6655,6 +6655,44 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { } } + func testNoRpathForOSNone() async throws { + let fileSystem = InMemoryFileSystem( + emptyFiles: + "/Pkg/Sources/exe/main.swift" + ) + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fileSystem, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [ + TargetDescription(name: "exe"), + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + let toolchain = try UserToolchain.default + let result = try await BuildPlanResult(plan: mockBuildPlan( + triple: Triple("arm64-unknown-none"), + toolchain: toolchain, + graph: graph, + fileSystem: fileSystem, + observabilityScope: observability.topScope + )) + result.checkProductsCount(1) + + // Assert the objects getting linked contain all the bitcode objects + // built by the Swift Target + let exeLinkArguments = try result.buildProduct(for: "exe").linkArguments() + let exeLinkArgumentsNegativePattern: [StringPattern] = ["-rpath"] + XCTAssertNoMatch(exeLinkArguments, exeLinkArgumentsNegativePattern) + } + func testPackageDependencySetsUserModuleVersion() async throws { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", "/ExtPkg/Sources/ExtLib/best.swift") From 2b0505e24798468102e9ec1937bb485346c59695 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Mon, 12 May 2025 06:42:58 -0700 Subject: [PATCH 94/99] Use mergeable symbols in embedded (#8654) --- .../SwiftModuleBuildDescription.swift | 30 ++++++---- Tests/BuildTests/BuildPlanTests.swift | 56 +++++++++++++++++-- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift index 721153fb251..69e482aec34 100644 --- a/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift @@ -490,9 +490,19 @@ public final class SwiftModuleBuildDescription { args += ["-v"] } - // Enable batch mode whenever WMO is off. - if !self.useWholeModuleOptimization { - args += ["-enable-batch-mode"] + if self.useWholeModuleOptimization { + args.append("-whole-module-optimization") + args.append("-num-threads") + args.append(String(ProcessInfo.processInfo.activeProcessorCount)) + } else { + args.append("-incremental") + args.append("-enable-batch-mode") + } + + // Workaround for https://github.com/swiftlang/swift-package-manager/issues/8648 + if self.useMergeableSymbols { + args.append("-Xfrontend") + args.append("-mergeable-symbols") } args += ["-serialize-diagnostics"] @@ -779,14 +789,6 @@ public final class SwiftModuleBuildDescription { result.append(outputFileMapPath.pathString) } - if self.useWholeModuleOptimization { - result.append("-whole-module-optimization") - result.append("-num-threads") - result.append(String(ProcessInfo.processInfo.activeProcessorCount)) - } else { - result.append("-incremental") - } - result.append("-c") result.append(contentsOf: self.sources.map(\.pathString)) @@ -1044,6 +1046,12 @@ public final class SwiftModuleBuildDescription { return true } } + + // Workaround for https://github.com/swiftlang/swift-package-manager/issues/8648 + /// Whether to build Swift code with -Xfrontend -mergeable-symbols. + package var useMergeableSymbols: Bool { + return self.target.underlying.isEmbeddedSwiftTarget + } } extension SwiftModuleBuildDescription { diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 8655d79a652..2230c6d2fc2 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -2240,17 +2240,61 @@ class BuildPlanTestCase: BuildSystemProviderTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) - // WMO should always be on with Embedded - let plan = try await mockBuildPlan( + // -Xfrontend -mergeable symbols should be passed with Embedded + let result = try await BuildPlanResult(plan: mockBuildPlan( graph: graph, fileSystem: fs, observabilityScope: observability.topScope + )) + result.checkTargetsCount(1) + + // Compile Swift Target + let aCompileArguments = try result.moduleBuildDescription(for: "A").swift().compileArguments() + let aCompileArgumentsPattern: [StringPattern] = ["-whole-module-optimization"] + let aCompileArgumentsNegativePattern: [StringPattern] = ["-wmo"] + XCTAssertMatch(aCompileArguments, aCompileArgumentsPattern) + XCTAssertNoMatch(aCompileArguments, aCompileArgumentsNegativePattern) + } + + // Workaround for: https://github.com/swiftlang/swift-package-manager/issues/8648 + func test_mergeableSymbols_enabledInEmbedded() async throws { + let Pkg: AbsolutePath = "/Pkg" + let fs: FileSystem = InMemoryFileSystem( + emptyFiles: + Pkg.appending(components: "Sources", "A", "A.swift").pathString ) - let a = try BuildPlanResult(plan: plan) - .moduleBuildDescription(for: "A").swift().emitCommandLine() - XCTAssertMatch(a, ["-whole-module-optimization"]) - XCTAssertNoMatch(a, ["-wmo"]) + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: .init(validating: Pkg.pathString), + targets: [ + TargetDescription( + name: "A", + settings: [.init(tool: .swift, kind: .enableExperimentalFeature("Embedded"))] + ), + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + // -Xfrontend -mergeable symbols should be passed with Embedded + let result = try await BuildPlanResult(plan: mockBuildPlan( + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + result.checkTargetsCount(1) + + // Compile Swift Target + let aCompileArguments = try result.moduleBuildDescription(for: "A").swift().compileArguments() + let aCompileArgumentsPattern: [StringPattern] = ["-Xfrontend", "-mergeable-symbols"] + XCTAssertMatch(aCompileArguments, aCompileArgumentsPattern) } func testREPLArguments() async throws { From 063c917748d8f06ff72f165174177b3b5c280b97 Mon Sep 17 00:00:00 2001 From: "Bassam (Sam) Khouri" Date: Mon, 12 May 2025 18:53:01 -0400 Subject: [PATCH 95/99] Revert "Tests: Convert Environment/Graph and other suites to ST" (#8647) --- .../_InternalTestSupport/Observability.swift | 34 +-- .../Environment/EnvironmentKeyTests.swift | 67 +++--- .../Environment/EnvironmentTests.swift | 147 ++++++------- .../Graph/AdjacencyMatrixTests.swift | 22 +- .../Graph/DirectedGraphTests.swift | 21 +- .../Graph/UndirectedGraphTests.swift | 29 ++- .../ObservabilitySystemTests.swift | 208 ++++++++---------- .../BasicsTests/ProgressAnimationTests.swift | 15 +- Tests/BasicsTests/StringExtensionsTests.swift | 20 +- 9 files changed, 234 insertions(+), 329 deletions(-) diff --git a/Sources/_InternalTestSupport/Observability.swift b/Sources/_InternalTestSupport/Observability.swift index 06008d655a4..74a9594f4bd 100644 --- a/Sources/_InternalTestSupport/Observability.swift +++ b/Sources/_InternalTestSupport/Observability.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors +// Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -18,7 +18,6 @@ import func XCTest.XCTFail import struct TSCBasic.StringError import TSCTestSupport -import Testing extension ObservabilitySystem { public static func makeForTesting(verbose: Bool = true) -> TestingObservability { @@ -140,37 +139,6 @@ public func testDiagnostics( } } -public func expectDiagnostics( - _ diagnostics: [Basics.Diagnostic], - problemsOnly: Bool = true, - sourceLocation: SourceLocation = #_sourceLocation, - handler: (DiagnosticsTestResult) throws -> Void -) throws { - try expectDiagnostics( - diagnostics, - minSeverity: problemsOnly ? .warning : .debug, - sourceLocation: sourceLocation, - handler: handler - ) -} - - -public func expectDiagnostics( - _ diagnostics: [Basics.Diagnostic], - minSeverity: Basics.Diagnostic.Severity, - sourceLocation: SourceLocation = #_sourceLocation, - handler: (DiagnosticsTestResult) throws -> Void -) throws { - let diagnostics = diagnostics.filter { $0.severity >= minSeverity } - let testResult = DiagnosticsTestResult(diagnostics) - - try handler(testResult) - - if !testResult.uncheckedDiagnostics.isEmpty { - Issue.record("unchecked diagnostics \(testResult.uncheckedDiagnostics)", sourceLocation: sourceLocation) - } -} - public func testPartialDiagnostics( _ diagnostics: [Basics.Diagnostic], minSeverity: Basics.Diagnostic.Severity, diff --git a/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift b/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift index 7c616de30bb..60f6b0cb0ee 100644 --- a/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift +++ b/Tests/BasicsTests/Environment/EnvironmentKeyTests.swift @@ -2,100 +2,91 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors +// Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -import Foundation @testable import Basics -import Testing +import XCTest -struct EnvironmentKeyTests { - @Test - func comparable() { +final class EnvironmentKeyTests: XCTestCase { + func test_comparable() { let key0 = EnvironmentKey("Test") let key1 = EnvironmentKey("Test1") - #expect(key0 < key1) + XCTAssertLessThan(key0, key1) let key2 = EnvironmentKey("test") - #expect(key0 < key2) + XCTAssertLessThan(key0, key2) } - @Test - func customStringConvertible() { + func test_customStringConvertible() { let key = EnvironmentKey("Test") - #expect(key.description == "Test") + XCTAssertEqual(key.description, "Test") } - @Test - func encodable() throws { + func test_encodable() throws { let key = EnvironmentKey("Test") let data = try JSONEncoder().encode(key) let string = String(data: data, encoding: .utf8) - #expect(string == #""Test""#) + XCTAssertEqual(string, #""Test""#) } - @Test - func equatable() { + func test_equatable() { let key0 = EnvironmentKey("Test") let key1 = EnvironmentKey("Test") - #expect(key0 == key1) + XCTAssertEqual(key0, key1) let key2 = EnvironmentKey("Test2") - #expect(key0 != key2) + XCTAssertNotEqual(key0, key2) #if os(Windows) // Test case insensitivity on windows let key3 = EnvironmentKey("teSt") - #expect(key0 == key3) + XCTAssertEqual(key0, key3) #endif } - @Test - func expressibleByStringLiteral() { + func test_expressibleByStringLiteral() { let key0 = EnvironmentKey("Test") - #expect(key0 == "Test") + XCTAssertEqual(key0, "Test") } - @Test - func decodable() throws { + func test_decodable() throws { let jsonString = #""Test""# let data = jsonString.data(using: .utf8)! let key = try JSONDecoder().decode(EnvironmentKey.self, from: data) - #expect(key.rawValue == "Test") + XCTAssertEqual(key.rawValue, "Test") } - @Test - func hashable() { + func test_hashable() { var set = Set() let key0 = EnvironmentKey("Test") - #expect(set.insert(key0).inserted) + XCTAssertTrue(set.insert(key0).inserted) let key1 = EnvironmentKey("Test") - #expect(set.contains(key1)) - #expect(!set.insert(key1).inserted) + XCTAssertTrue(set.contains(key1)) + XCTAssertFalse(set.insert(key1).inserted) let key2 = EnvironmentKey("Test2") - #expect(!set.contains(key2)) - #expect(set.insert(key2).inserted) + XCTAssertFalse(set.contains(key2)) + XCTAssertTrue(set.insert(key2).inserted) #if os(Windows) // Test case insensitivity on windows let key3 = EnvironmentKey("teSt") - #expect(set.contains(key3)) - #expect(!set.insert(key3).inserted) + XCTAssertTrue(set.contains(key3)) + XCTAssertFalse(set.insert(key3).inserted) #endif - #expect(set == ["Test", "Test2"]) + XCTAssertEqual(set, ["Test", "Test2"]) } - @Test - func rawRepresentable() { + func test_rawRepresentable() { let key = EnvironmentKey(rawValue: "Test") - #expect(key?.rawValue == "Test") + XCTAssertEqual(key?.rawValue, "Test") } } diff --git a/Tests/BasicsTests/Environment/EnvironmentTests.swift b/Tests/BasicsTests/Environment/EnvironmentTests.swift index daabc754ab2..5b0388470c6 100644 --- a/Tests/BasicsTests/Environment/EnvironmentTests.swift +++ b/Tests/BasicsTests/Environment/EnvironmentTests.swift @@ -2,230 +2,209 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021-2025 Apple Inc. and the Swift project authors +// Copyright (c) 2021 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -import Foundation @_spi(SwiftPMInternal) @testable import Basics -import Testing +import XCTest -struct EnvironmentTests { - @Test - func initialize() { +final class EnvironmentTests: XCTestCase { + func test_init() { let environment = Environment() - #expect(environment.isEmpty) + XCTAssertTrue(environment.isEmpty) } - @Test - func setting_and_accessing_via_subscript() { + func test_subscript() { var environment = Environment() let key = EnvironmentKey("TestKey") environment[key] = "TestValue" - #expect(environment[key] == "TestValue") + XCTAssertEqual(environment[key], "TestValue") } - @Test - func initDictionaryFromSelf() { + func test_initDictionaryFromSelf() { let dictionary = [ "TestKey": "TestValue", "testKey": "TestValue2", ] let environment = Environment(dictionary) - let expectedValue: String - let expectedCount: Int - #if os(Windows) - expectedValue = "TestValue2" // uppercase sorts before lowercase, so the second value overwrites the first - expectedCount = 1 + XCTAssertEqual(environment["TestKey"], "TestValue2") + XCTAssertEqual(environment.count, 1) #else - expectedValue = "TestValue" - expectedCount = 2 + XCTAssertEqual(environment["TestKey"], "TestValue") + XCTAssertEqual(environment.count, 2) #endif - #expect(environment["TestKey"] == expectedValue) - #expect(environment.count == expectedCount) } - @Test - func initSelfFromDictionary() { + func test_initSelfFromDictionary() { let dictionary = ["TestKey": "TestValue"] let environment = Environment(dictionary) - #expect(environment["TestKey"] == "TestValue") - #expect(environment.count == 1) + XCTAssertEqual(environment["TestKey"], "TestValue") + XCTAssertEqual(environment.count, 1) } func path(_ components: String...) -> String { components.joined(separator: Environment.pathEntryDelimiter) } - @Test - func prependPath() { + func test_prependPath() { var environment = Environment() let key = EnvironmentKey(UUID().uuidString) - #expect(environment[key] == nil) + XCTAssertNil(environment[key]) environment.prependPath(key: key, value: "/bin") - #expect(environment[key] == path("/bin")) + XCTAssertEqual(environment[key], path("/bin")) environment.prependPath(key: key, value: "/usr/bin") - #expect(environment[key] == path("/usr/bin", "/bin")) + XCTAssertEqual(environment[key], path("/usr/bin", "/bin")) environment.prependPath(key: key, value: "/usr/local/bin") - #expect(environment[key] == path("/usr/local/bin", "/usr/bin", "/bin")) + XCTAssertEqual(environment[key], path("/usr/local/bin", "/usr/bin", "/bin")) environment.prependPath(key: key, value: "") - #expect(environment[key] == path("/usr/local/bin", "/usr/bin", "/bin")) + XCTAssertEqual(environment[key], path("/usr/local/bin", "/usr/bin", "/bin")) } - @Test - func appendPath() { + func test_appendPath() { var environment = Environment() let key = EnvironmentKey(UUID().uuidString) - #expect(environment[key] == nil) + XCTAssertNil(environment[key]) environment.appendPath(key: key, value: "/bin") - #expect(environment[key] == path("/bin")) + XCTAssertEqual(environment[key], path("/bin")) environment.appendPath(key: key, value: "/usr/bin") - #expect(environment[key] == path("/bin", "/usr/bin")) + XCTAssertEqual(environment[key], path("/bin", "/usr/bin")) environment.appendPath(key: key, value: "/usr/local/bin") - #expect(environment[key] == path("/bin", "/usr/bin", "/usr/local/bin")) + XCTAssertEqual(environment[key], path("/bin", "/usr/bin", "/usr/local/bin")) environment.appendPath(key: key, value: "") - #expect(environment[key] == path("/bin", "/usr/bin", "/usr/local/bin")) + XCTAssertEqual(environment[key], path("/bin", "/usr/bin", "/usr/local/bin")) } - @Test - func pathEntryDelimiter() { - let expectedPathDelimiter: String + func test_pathEntryDelimiter() { #if os(Windows) - expectedPathDelimiter = ";" + XCTAssertEqual(Environment.pathEntryDelimiter, ";") #else - expectedPathDelimiter = ":" + XCTAssertEqual(Environment.pathEntryDelimiter, ":") #endif - #expect(Environment.pathEntryDelimiter == expectedPathDelimiter) } /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. - @Test - func current() throws { + func test_current() throws { #if os(Windows) let pathEnvVarName = "Path" #else let pathEnvVarName = "PATH" #endif - #expect(Environment.current["PATH"] == ProcessInfo.processInfo.environment[pathEnvVarName]) + + XCTAssertEqual( + Environment.current["PATH"], + ProcessInfo.processInfo.environment[pathEnvVarName] + ) } /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. - @Test - func makeCustom() async throws { + func test_makeCustom() async throws { let key = EnvironmentKey(UUID().uuidString) let value = "TestValue" var customEnvironment = Environment() customEnvironment[key] = value - #expect(Environment.current[key] == nil) + XCTAssertNil(Environment.current[key]) try Environment.makeCustom(customEnvironment) { - #expect(Environment.current[key] == value) + XCTAssertEqual(Environment.current[key], value) } - #expect(Environment.current[key] == nil) + XCTAssertNil(Environment.current[key]) } /// Important: This test is inherently race-prone, if it is proven to be /// flaky, it should run in a singled threaded environment/removed entirely. - @Test - func process() throws { + func testProcess() throws { let key = EnvironmentKey(UUID().uuidString) let value = "TestValue" var environment = Environment.current - #expect(environment[key] == nil) + XCTAssertNil(environment[key]) try Environment.set(key: key, value: value) environment = Environment.current // reload - #expect(environment[key] == value) + XCTAssertEqual(environment[key], value) try Environment.set(key: key, value: nil) - #expect(environment[key] == value) // this is a copy! + XCTAssertEqual(environment[key], value) // this is a copy! environment = Environment.current // reload - #expect(environment[key] == nil) + XCTAssertNil(environment[key]) } - @Test - func cachable() { + func test_cachable() { let term = EnvironmentKey("TERM") var environment = Environment() environment[.path] = "/usr/bin" environment[term] = "xterm-256color" let cachableEnvironment = environment.cachable - #expect(cachableEnvironment[.path] != nil) - #expect(cachableEnvironment[term] == nil) + XCTAssertNotNil(cachableEnvironment[.path]) + XCTAssertNil(cachableEnvironment[term]) } - @Test - func collection() { + func test_collection() { let environment: Environment = ["TestKey": "TestValue"] - #expect(environment.count == 1) - #expect(environment.first?.key == EnvironmentKey("TestKey")) - #expect(environment.first?.value == "TestValue") + XCTAssertEqual(environment.count, 1) + XCTAssertEqual(environment.first?.key, EnvironmentKey("TestKey")) + XCTAssertEqual(environment.first?.value, "TestValue") } - @Test - func description() { + func test_description() { var environment = Environment() environment[EnvironmentKey("TestKey")] = "TestValue" - #expect(environment.description == #"["TestKey=TestValue"]"#) + XCTAssertEqual(environment.description, #"["TestKey=TestValue"]"#) } - @Test - func encodable() throws { + func test_encodable() throws { var environment = Environment() environment["TestKey"] = "TestValue" let data = try JSONEncoder().encode(environment) let jsonString = String(data: data, encoding: .utf8) - #expect(jsonString == #"{"TestKey":"TestValue"}"#) + XCTAssertEqual(jsonString, #"{"TestKey":"TestValue"}"#) } - @Test - func equatable() { + func test_equatable() { let environment0: Environment = ["TestKey": "TestValue"] let environment1: Environment = ["TestKey": "TestValue"] - #expect(environment0 == environment1) + XCTAssertEqual(environment0, environment1) #if os(Windows) // Test case insensitivity on windows let environment2: Environment = ["testKey": "TestValue"] - #expect(environment0 == environment2) + XCTAssertEqual(environment0, environment2) #endif } - @Test - func expressibleByDictionaryLiteral() { + func test_expressibleByDictionaryLiteral() { let environment: Environment = ["TestKey": "TestValue"] - #expect(environment["TestKey"] == "TestValue") + XCTAssertEqual(environment["TestKey"], "TestValue") } - @Test - func decodable() throws { + func test_decodable() throws { let jsonString = #"{"TestKey":"TestValue"}"# let data = jsonString.data(using: .utf8)! let environment = try JSONDecoder().decode(Environment.self, from: data) - #expect(environment[EnvironmentKey("TestKey")] == "TestValue") + XCTAssertEqual(environment[EnvironmentKey("TestKey")], "TestValue") } } diff --git a/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift b/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift index 25a440120dc..c19178ef410 100644 --- a/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift +++ b/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2024-2025 Apple Inc. and the Swift project authors +// Copyright (c) 2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,30 +11,28 @@ //===----------------------------------------------------------------------===// @testable import Basics -import Testing +import XCTest -struct AdjacencyMatrixTests { - @Test - func empty() { +final class AdjacencyMatrixTests: XCTestCase { + func testEmpty() { var matrix = AdjacencyMatrix(rows: 0, columns: 0) - #expect(matrix.bitCount == 0) + XCTAssertEqual(matrix.bitCount, 0) matrix = AdjacencyMatrix(rows: 0, columns: 42) - #expect(matrix.bitCount == 0) + XCTAssertEqual(matrix.bitCount, 0) matrix = AdjacencyMatrix(rows: 42, columns: 0) - #expect(matrix.bitCount == 0) + XCTAssertEqual(matrix.bitCount, 0) } - @Test - func bits() { + func testBits() { for count in 1..<10 { var matrix = AdjacencyMatrix(rows: count, columns: count) for row in 0.. Date: Mon, 12 May 2025 10:16:08 -0700 Subject: [PATCH 96/99] [cmake] Turn `SwiftFixIt` into a static library This is currently only intended to be used by a few commands and shouldn't be vended. --- Sources/SwiftFixIt/CMakeLists.txt | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/SwiftFixIt/CMakeLists.txt b/Sources/SwiftFixIt/CMakeLists.txt index 9f6fdba4c2b..9b8bad43d97 100644 --- a/Sources/SwiftFixIt/CMakeLists.txt +++ b/Sources/SwiftFixIt/CMakeLists.txt @@ -6,7 +6,7 @@ # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for Swift project authors -add_library(SwiftFixIt +add_library(SwiftFixIt STATIC SwiftFixIt.swift) target_link_libraries(SwiftFixIt PUBLIC Basics @@ -22,9 +22,3 @@ target_link_libraries(SwiftFixIt PUBLIC # NOTE(compnerd) workaround for CMake not setting up include flags yet set_target_properties(SwiftFixIt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) - -install(TARGETS SwiftFixIt - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) -set_property(GLOBAL APPEND PROPERTY SwiftPM_EXPORTS SwiftFixIt) From f4035c1cf13b649234177ea7d60612e253793623 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 13 May 2025 09:17:31 -0700 Subject: [PATCH 97/99] Build SwiftBuild as part of CMake bootstrapping (#8445) Build on https://github.com/swiftlang/swift-build/pull/376 to begin building Swift Build during the CMake bootstrap of SwiftPM Depends on: https://github.com/swiftlang/swift-installer-scripts/pull/417 --- CMakeLists.txt | 1 + Package.swift | 3 +-- Sources/SwiftBuildSupport/CMakeLists.txt | 2 ++ Sources/SwiftBuildSupport/DotPIFSerializer.swift | 3 --- Sources/SwiftBuildSupport/PIF.swift | 3 --- Sources/SwiftBuildSupport/PIFBuilder.swift | 16 +--------------- .../PackagePIFBuilder+Helpers.swift | 3 --- .../PackagePIFBuilder+Plugins.swift | 3 --- .../SwiftBuildSupport/PackagePIFBuilder.swift | 4 ---- .../PackagePIFProjectBuilder+Modules.swift | 4 ---- .../PackagePIFProjectBuilder+Products.swift | 3 --- .../PackagePIFProjectBuilder.swift | 3 --- Sources/SwiftBuildSupport/SwiftBuildSystem.swift | 15 +-------------- Tests/CommandsTests/BuildCommandTests.swift | 4 ---- Tests/CommandsTests/TestCommandTests.swift | 8 -------- Utilities/bootstrap | 14 ++++++++++++++ 16 files changed, 20 insertions(+), 69 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a585788051c..a638ceb75e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ if(FIND_PM_DEPS) find_package(SwiftASN1 CONFIG REQUIRED) find_package(SwiftCertificates CONFIG REQUIRED) find_package(SwiftCrypto CONFIG REQUIRED) + find_package(SwiftBuild CONFIG REQUIRED) endif() find_package(dispatch QUIET) diff --git a/Package.swift b/Package.swift index bbca6f77b32..ef625e8e9a2 100644 --- a/Package.swift +++ b/Package.swift @@ -1108,8 +1108,7 @@ if ProcessInfo.processInfo.environment["ENABLE_APPLE_PRODUCT_TYPES"] == "1" { } } -if ProcessInfo.processInfo.environment["SWIFTPM_SWBUILD_FRAMEWORK"] == nil && - ProcessInfo.processInfo.environment["SWIFTPM_NO_SWBUILD_DEPENDENCY"] == nil { +if ProcessInfo.processInfo.environment["SWIFTPM_SWBUILD_FRAMEWORK"] == nil { let swiftbuildsupport: Target = package.targets.first(where: { $0.name == "SwiftBuildSupport" } )! swiftbuildsupport.dependencies += [ diff --git a/Sources/SwiftBuildSupport/CMakeLists.txt b/Sources/SwiftBuildSupport/CMakeLists.txt index ed516095346..3bd426bbff4 100644 --- a/Sources/SwiftBuildSupport/CMakeLists.txt +++ b/Sources/SwiftBuildSupport/CMakeLists.txt @@ -24,6 +24,8 @@ target_link_libraries(SwiftBuildSupport PUBLIC TSCBasic TSCUtility PackageGraph + SwiftBuild::SwiftBuild + SwiftBuild::SWBBuildService ) set_target_properties(SwiftBuildSupport PROPERTIES diff --git a/Sources/SwiftBuildSupport/DotPIFSerializer.swift b/Sources/SwiftBuildSupport/DotPIFSerializer.swift index c7c681ecb12..daa20748085 100644 --- a/Sources/SwiftBuildSupport/DotPIFSerializer.swift +++ b/Sources/SwiftBuildSupport/DotPIFSerializer.swift @@ -9,7 +9,6 @@ import Basics import Foundation import protocol TSCBasic.OutputByteStream -#if canImport(SwiftBuild) import SwiftBuild /// Serializes the specified PIF as a **Graphviz** directed graph. @@ -223,5 +222,3 @@ fileprivate extension String { "\"" } } - -#endif diff --git a/Sources/SwiftBuildSupport/PIF.swift b/Sources/SwiftBuildSupport/PIF.swift index b455edbd32b..ff2fde612a1 100644 --- a/Sources/SwiftBuildSupport/PIF.swift +++ b/Sources/SwiftBuildSupport/PIF.swift @@ -17,7 +17,6 @@ import PackageModel import struct TSCBasic.ByteString -#if canImport(SwiftBuild) import enum SwiftBuild.ProjectModel /// The Project Interchange Format (PIF) is a structured representation of the @@ -286,5 +285,3 @@ extension PIF { workspace.signature = try signature(of: workspace) } } - -#endif // SwiftBuild diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 7952de26642..d42cca3d1c8 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -24,9 +24,7 @@ import func TSCBasic.memoize import func TSCBasic.topologicalSort import var TSCBasic.stdoutStream -#if canImport(SwiftBuild) import enum SwiftBuild.ProjectModel -#endif /// The parameters required by `PIFBuilder`. struct PIFBuilderParameters { @@ -103,7 +101,6 @@ public final class PIFBuilder { printPIFManifestGraphviz: Bool = false, buildParameters: BuildParameters ) throws -> String { - #if canImport(SwiftBuild) let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder() if !preservePIFModelStructure { @@ -129,13 +126,8 @@ public final class PIFBuilder { } return pifString - #else - fatalError("Swift Build support is not linked in.") - #endif } - - #if canImport(SwiftBuild) - + private var cachedPIF: PIF.TopLevelObject? /// Constructs a `PIF.TopLevelObject` representing the package graph. @@ -192,8 +184,6 @@ public final class PIFBuilder { return PIF.TopLevelObject(workspace: workspace) } } - - #endif // Convenience method for generating PIF. public static func generatePIF( @@ -214,8 +204,6 @@ public final class PIFBuilder { } } -#if canImport(SwiftBuild) - fileprivate final class PackagePIFBuilderDelegate: PackagePIFBuilder.BuildDelegate { let package: ResolvedPackage @@ -421,8 +409,6 @@ fileprivate func buildAggregateProject( return aggregateProject } -#endif - public enum PIFGenerationError: Error { case rootPackageNotFound, multipleRootPackagesFound diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index b0273deb5a3..90cc33e8832 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -61,8 +61,6 @@ func targetName(forProductName name: String, suffix: String? = nil) -> String { return "\(name)\(suffix)-product" } -#if canImport(SwiftBuild) - import enum SwiftBuild.ProjectModel // MARK: - PIF GUID Helpers @@ -1216,4 +1214,3 @@ extension UserDefaults { } } -#endif diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift index 2f89010bfc9..f78bb1107fa 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Plugins.swift @@ -18,8 +18,6 @@ import enum Basics.Sandbox import struct Basics.AbsolutePath import struct Basics.SourceControlURL -#if canImport(SwiftBuild) - import enum SwiftBuild.ProjectModel extension PackagePIFBuilder { @@ -136,4 +134,3 @@ extension PackagePIFBuilder { } } -#endif diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift index b786a209001..a4769ca3140 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder.swift @@ -32,8 +32,6 @@ import struct PackageGraph.ModulesGraph import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage -#if canImport(SwiftBuild) - import enum SwiftBuild.ProjectModel typealias GUID = SwiftBuild.ProjectModel.GUID @@ -668,5 +666,3 @@ extension PackagePIFBuilder.LinkedPackageBinary { } } } - -#endif diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index ae3de279e84..a0e0c7c3049 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -26,8 +26,6 @@ import class PackageModel.SystemLibraryModule import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage -#if canImport(SwiftBuild) - import enum SwiftBuild.ProjectModel /// Extension to create PIF **modules** for a given package. @@ -884,5 +882,3 @@ extension PackagePIFProjectBuilder { self.builtModulesAndProducts.append(systemModule) } } - -#endif diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift index 65a209fb8d2..9525b69ba76 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Products.swift @@ -28,8 +28,6 @@ import struct PackageGraph.ResolvedModule import struct PackageGraph.ResolvedPackage import struct PackageGraph.ResolvedProduct -#if canImport(SwiftBuild) - import enum SwiftBuild.ProjectModel /// Extension to create PIF **products** for a given package. @@ -1014,4 +1012,3 @@ private struct PackageRegistrySignature: Encodable { let formatVersion = 2 } -#endif diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift index c37a6e6f3b9..2239072df56 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder.swift @@ -31,8 +31,6 @@ import struct PackageGraph.ResolvedPackage import struct PackageLoading.FileRuleDescription import struct PackageLoading.TargetSourcesBuilder -#if canImport(SwiftBuild) - import struct SwiftBuild.Pair import enum SwiftBuild.ProjectModel import struct SwiftBuild.SwiftBuildFileType @@ -549,4 +547,3 @@ struct PackagePIFProjectBuilder { } } -#endif diff --git a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift index 704259808a3..bdf91184927 100644 --- a/Sources/SwiftBuildSupport/SwiftBuildSystem.swift +++ b/Sources/SwiftBuildSupport/SwiftBuildSystem.swift @@ -30,13 +30,10 @@ import func TSCBasic.withTemporaryFile import enum TSCUtility.Diagnostics -#if canImport(SwiftBuild) import Foundation import SWBBuildService import SwiftBuild -#endif -#if canImport(SwiftBuild) struct SessionFailedError: Error { var error: Error @@ -155,7 +152,6 @@ private final class PlanningOperationDelegate: SWBPlanningOperationDelegate, Sen .deferred } } -#endif public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { private let buildParameters: BuildParameters @@ -232,7 +228,6 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { } public func build(subset: BuildSubset) async throws { - #if canImport(SwiftBuild) guard !buildParameters.shouldSkipBuilding else { return } @@ -247,12 +242,9 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { try await startSWBuildOperation(pifTargetName: subset.pifTargetName) - #else - fatalError("Swift Build support is not linked in.") - #endif + } - #if canImport(SwiftBuild) private func startSWBuildOperation(pifTargetName: String) async throws { let buildStartTime = ContinuousClock.Instant.now @@ -511,7 +503,6 @@ public final class SwiftBuildSystem: SPMBuildCore.BuildSystem { return pifBuilder } } - #endif public func cancel(deadline: DispatchTime) throws {} @@ -545,8 +536,6 @@ extension Basics.Diagnostic.Severity { } } -#if canImport(SwiftBuild) - fileprivate extension SwiftBuild.SwiftBuildMessage.DiagnosticInfo.Location { var userDescription: String? { switch self { @@ -573,5 +562,3 @@ fileprivate extension SwiftBuild.SwiftBuildMessage.DiagnosticInfo.Location { } } } - -#endif diff --git a/Tests/CommandsTests/BuildCommandTests.swift b/Tests/CommandsTests/BuildCommandTests.swift index 08356ea7b2f..c2030da4632 100644 --- a/Tests/CommandsTests/BuildCommandTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -942,10 +942,6 @@ class BuildCommandSwiftBuildTests: BuildCommandTestCases { override func testBuildSystemDefaultSettings() async throws { try XCTSkipIfWorkingDirectoryUnsupported() - if ProcessInfo.processInfo.environment["SWIFTPM_NO_SWBUILD_DEPENDENCY"] != nil { - throw XCTSkip("SWIFTPM_NO_SWBUILD_DEPENDENCY is set so skipping because SwiftPM doesn't have the swift-build capability built inside.") - } - try await super.testBuildSystemDefaultSettings() } diff --git a/Tests/CommandsTests/TestCommandTests.swift b/Tests/CommandsTests/TestCommandTests.swift index 134b3f2d900..681bd6f3eca 100644 --- a/Tests/CommandsTests/TestCommandTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -632,18 +632,10 @@ class TestCommandSwiftBuildTests: TestCommandTestCase { } override func testFatalErrorDisplayedCorrectNumberOfTimesWhenSingleXCTestHasFatalErrorInBuildCompilation() async throws { - guard ProcessInfo.processInfo.environment["SWIFTPM_NO_SWBUILD_DEPENDENCY"] == nil else { - throw XCTSkip("Skipping test because SwiftBuild is not linked in.") - } - try await super.testFatalErrorDisplayedCorrectNumberOfTimesWhenSingleXCTestHasFatalErrorInBuildCompilation() } override func testListWithSkipBuildAndNoBuildArtifacts() async throws { - guard ProcessInfo.processInfo.environment["SWIFTPM_NO_SWBUILD_DEPENDENCY"] == nil else { - throw XCTSkip("Skipping test because SwiftBuild is not linked in.") - } - try await super.testListWithSkipBuildAndNoBuildArtifacts() } diff --git a/Utilities/bootstrap b/Utilities/bootstrap index 374849a7050..d6bdd3d2f2f 100755 --- a/Utilities/bootstrap +++ b/Utilities/bootstrap @@ -243,6 +243,7 @@ def parse_global_args(args): args.source_dirs["swift-certificates"] = os.path.join(args.project_root, "..", "swift-certificates") args.source_dirs["swift-asn1"] = os.path.join(args.project_root, "..", "swift-asn1") args.source_dirs["swift-syntax"] = os.path.join(args.project_root, "..", "swift-syntax") + args.source_dirs["swift-build"] = os.path.join(args.project_root, "..", "swift-build") args.source_root = os.path.join(args.project_root, "Sources") if platform.system() == 'Darwin': @@ -442,6 +443,16 @@ def build(args): build_dependency(args, "swift-certificates", ["-DSwiftASN1_DIR=" + os.path.join(args.build_dirs["swift-asn1"], "cmake/modules"), "-DSwiftCrypto_DIR=" + os.path.join(args.build_dirs["swift-crypto"], "cmake/modules")]) + swift_build_cmake_flags = [ + get_llbuild_cmake_arg(args), + "-DSwiftSystem_DIR=" + os.path.join(args.build_dirs["swift-system"], "cmake/modules"), + "-DSwiftASN1_DIR=" + os.path.join(args.build_dirs["swift-asn1"], "cmake/modules"), + "-DSwiftCrypto_DIR=" + os.path.join(args.build_dirs["swift-crypto"], "cmake/modules"), + "-DTSC_DIR=" + os.path.join(args.build_dirs["tsc"], "cmake/modules"), + "-DArgumentParser_DIR=" + os.path.join(args.build_dirs["swift-argument-parser"], "cmake/modules"), + "-DSwiftDriver_DIR=" + os.path.join(args.build_dirs["swift-driver"], "cmake/modules"), + ] + build_dependency(args, "swift-build", swift_build_cmake_flags) build_swiftpm_with_cmake(args) build_swiftpm_with_swiftpm(args,integrated_swift_driver=False) @@ -718,6 +729,7 @@ def build_swiftpm_with_cmake(args): "-DSwiftCrypto_DIR=" + os.path.join(args.build_dirs["swift-crypto"], "cmake/modules"), "-DSwiftASN1_DIR=" + os.path.join(args.build_dirs["swift-asn1"], "cmake/modules"), "-DSwiftCertificates_DIR=" + os.path.join(args.build_dirs["swift-certificates"], "cmake/modules"), + "-DSwiftBuild_DIR=" + os.path.join(args.build_dirs["swift-build"], "cmake/modules"), "-DSWIFTPM_PATH_TO_SWIFT_SYNTAX_SOURCE=" + args.source_dirs["swift-syntax"], ] @@ -738,6 +750,7 @@ def build_swiftpm_with_cmake(args): add_rpath_for_cmake_build(args, os.path.join(args.build_dirs["swift-collections"], "lib")) add_rpath_for_cmake_build(args, os.path.join(args.build_dirs["swift-asn1"], "lib")) add_rpath_for_cmake_build(args, os.path.join(args.build_dirs["swift-certificates"], "lib")) + add_rpath_for_cmake_build(args, os.path.join(args.build_dirs["swift-build"], "lib")) # rpaths for compatibility libraries for lib_path in get_swift_backdeploy_library_paths(args): @@ -875,6 +888,7 @@ def get_swiftpm_env_cmd(args): os.path.join(args.build_dirs["swift-collections"], "lib"), os.path.join(args.build_dirs["swift-asn1"], "lib"), os.path.join(args.build_dirs["swift-certificates"], "lib"), + os.path.join(args.build_dirs["swift-build"], "lib"), ] if platform.system() == 'Darwin': From ba10e8a93f46db7d362468d4fee1b88ce0d3ab91 Mon Sep 17 00:00:00 2001 From: Sam Khouri Date: Tue, 13 May 2025 13:52:33 -0400 Subject: [PATCH 98/99] Remove Swift-numerics dependency A `swift-numerics` package dependency was added to convert an XCTest `XCTAssertEqual(_:_:accuracy:_:file:line:)` API do use the `isApproximatelyEqual()` API in `swift-numerics`, but converting that test resulted in a flaky behaviour. As such, the test was not converted to Swift Testing, but the swift-numerics dependency remained. This change removes the swift-numerics dependency as it's no currently required. --- Package.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Package.swift b/Package.swift index ef625e8e9a2..a8426ef5761 100644 --- a/Package.swift +++ b/Package.swift @@ -828,7 +828,6 @@ let package = Package( "Basics", "_InternalTestSupport", "tsan_utils", - .product(name: "Numerics", package: "swift-numerics"), ], exclude: [ "Archiver/Inputs/archive.tar.gz", @@ -1082,9 +1081,6 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/swiftlang/swift-toolchain-sqlite.git", from: "1.0.0"), // For use in previewing documentation .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), - - // Test dependency - .package(url: "https://github.com/apple/swift-numerics.git", from: "1.0.3"), ] } else { package.dependencies += [ @@ -1097,7 +1093,6 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(path: "../swift-collections"), .package(path: "../swift-certificates"), .package(path: "../swift-toolchain-sqlite"), - .package(path: "../swift-numerics"), ] } From d9934bd56c08d2e2a0f2c123b2137cd100d3d572 Mon Sep 17 00:00:00 2001 From: Doug Schaefer Date: Tue, 13 May 2025 15:40:32 -0400 Subject: [PATCH 99/99] Clean up merge. A few things slipped through the git merge including parts of the prebuilts feature and the new PIF/PIF Builder. --- Sources/Basics/Graph/GraphAlgorithms.swift | 47 ------------------- .../ModuleBuildDescription.swift | 22 --------- Sources/Build/BuildPlan/BuildPlan.swift | 12 ++--- Sources/SwiftBuildSupport/PIF.swift | 2 - Sources/SwiftBuildSupport/PIFBuilder.swift | 7 --- Sources/Workspace/ManagedPrebuilt.swift | 3 +- .../BuildTests/BuildPlanTraversalTests.swift | 1 - Tests/WorkspaceTests/PrebuiltsTests.swift | 10 ++++ Utilities/build-using-self | 1 - 9 files changed, 17 insertions(+), 88 deletions(-) diff --git a/Sources/Basics/Graph/GraphAlgorithms.swift b/Sources/Basics/Graph/GraphAlgorithms.swift index deee4941985..8ccc6038cc0 100644 --- a/Sources/Basics/Graph/GraphAlgorithms.swift +++ b/Sources/Basics/Graph/GraphAlgorithms.swift @@ -131,50 +131,3 @@ public func depthFirstSearch( } } } - -/// Implements a pre-order depth-first search that traverses the whole graph and -/// doesn't distinguish between unique and duplicate nodes. The visitor can abort -/// a path as needed to prune the tree. -/// The method expects the graph to be acyclic but doesn't check that. -/// -/// - Parameters: -/// - nodes: The list of input nodes to sort. -/// - successors: A closure for fetching the successors of a particular node. -/// - onNext: A callback to indicate the node currently being processed -/// including its parent (if any) and its depth. Returns whether to -/// continue down the current path. -/// -/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges -/// reachable from the input nodes via the relation. -public enum DepthFirstContinue { - case `continue` - case abort -} - -public func depthFirstSearch( - _ nodes: [T], - successors: (T) throws -> [T], - visitNext: (T, _ parent: T?) throws -> DepthFirstContinue -) rethrows { - var stack = OrderedSet>() - - for node in nodes { - precondition(stack.isEmpty) - stack.append(TraversalNode(parent: nil, curr: node)) - - while !stack.isEmpty { - let node = stack.removeLast() - - if try visitNext(node.curr, node.parent) == .continue { - for succ in try successors(node.curr) { - stack.append( - TraversalNode( - parent: node.curr, - curr: succ - ) - ) - } - } - } - } -} diff --git a/Sources/Build/BuildDescription/ModuleBuildDescription.swift b/Sources/Build/BuildDescription/ModuleBuildDescription.swift index c406000b30b..8c3713567f1 100644 --- a/Sources/Build/BuildDescription/ModuleBuildDescription.swift +++ b/Sources/Build/BuildDescription/ModuleBuildDescription.swift @@ -195,30 +195,8 @@ extension ModuleBuildDescription { var dependencies: [Dependency] = [] plan.traverseDependencies(of: self) { product, _, description in dependencies.append(.product(product, description)) - return .continue } onModule: { module, _, description in dependencies.append(.module(module, description)) - return .continue - } - return dependencies - } - - package func recursiveLinkDependencies(using plan: BuildPlan) -> [Dependency] { - var dependencies: [Dependency] = [] - plan.traverseDependencies(of: self) { product, _, description in - guard product.type != .macro && product.type != .plugin else { - return .abort - } - - dependencies.append(.product(product, description)) - return .continue - } onModule: { module, _, description in - guard module.type != .macro && module.type != .plugin else { - return .abort - } - - dependencies.append(.module(module, description)) - return .continue } return dependencies } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 4554c12d980..7f41e6cccf5 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -1175,8 +1175,8 @@ extension BuildPlan { package func traverseDependencies( of description: ModuleBuildDescription, - onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> DepthFirstContinue, - onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> DepthFirstContinue + onProduct: (ResolvedProduct, BuildParameters.Destination, ProductBuildDescription?) -> Void, + onModule: (ResolvedModule, BuildParameters.Destination, ModuleBuildDescription?) -> Void ) { var visited = Set() func successors( @@ -1217,16 +1217,16 @@ extension BuildPlan { case .package: [] } - } visitNext: { module, _ in + } onNext: { module, _ in switch module { case .package: - return .continue + break case .product(let product, let destination): - return onProduct(product, destination, self.description(for: product, context: destination)) + onProduct(product, destination, self.description(for: product, context: destination)) case .module(let module, let destination): - return onModule(module, destination, self.description(for: module, context: destination)) + onModule(module, destination, self.description(for: module, context: destination)) } } } diff --git a/Sources/SwiftBuildSupport/PIF.swift b/Sources/SwiftBuildSupport/PIF.swift index febb9ff0fbf..ff2fde612a1 100644 --- a/Sources/SwiftBuildSupport/PIF.swift +++ b/Sources/SwiftBuildSupport/PIF.swift @@ -285,5 +285,3 @@ extension PIF { workspace.signature = try signature(of: workspace) } } - -#endif // SwiftBuild diff --git a/Sources/SwiftBuildSupport/PIFBuilder.swift b/Sources/SwiftBuildSupport/PIFBuilder.swift index 3cf8cdf4125..d42cca3d1c8 100644 --- a/Sources/SwiftBuildSupport/PIFBuilder.swift +++ b/Sources/SwiftBuildSupport/PIFBuilder.swift @@ -101,7 +101,6 @@ public final class PIFBuilder { printPIFManifestGraphviz: Bool = false, buildParameters: BuildParameters ) throws -> String { - #if canImport(SwiftBuild) let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder() if !preservePIFModelStructure { @@ -128,10 +127,6 @@ public final class PIFBuilder { return pifString } - - #if canImport(SwiftBuild) - - private var cachedPIF: PIF.TopLevelObject? private var cachedPIF: PIF.TopLevelObject? @@ -189,8 +184,6 @@ public final class PIFBuilder { return PIF.TopLevelObject(workspace: workspace) } } - - #endif // Convenience method for generating PIF. public static func generatePIF( diff --git a/Sources/Workspace/ManagedPrebuilt.swift b/Sources/Workspace/ManagedPrebuilt.swift index fd02680d86b..b4ce241331e 100644 --- a/Sources/Workspace/ManagedPrebuilt.swift +++ b/Sources/Workspace/ManagedPrebuilt.swift @@ -15,7 +15,6 @@ import struct TSCBasic.StringError import Basics import PackageModel -import TSCBasic extension Workspace { /// A downloaded prebuilt managed by the workspace. @@ -30,7 +29,7 @@ extension Workspace { public let libraryName: String /// The path to the extracted prebuilt artifacts - public let path: Basics.AbsolutePath + public let path: AbsolutePath /// The products in the library public let products: [String] diff --git a/Tests/BuildTests/BuildPlanTraversalTests.swift b/Tests/BuildTests/BuildPlanTraversalTests.swift index 16b8f8f7d16..a4b2720ce83 100644 --- a/Tests/BuildTests/BuildPlanTraversalTests.swift +++ b/Tests/BuildTests/BuildPlanTraversalTests.swift @@ -152,7 +152,6 @@ struct BuildPlanTraversalTests { #expect(description == nil) } onModule: { module, destination, description in moduleDependencies.append((module, destination, description)) - return .continue } #expect(moduleDependencies.count == 2) diff --git a/Tests/WorkspaceTests/PrebuiltsTests.swift b/Tests/WorkspaceTests/PrebuiltsTests.swift index 5cf3e7a2507..c36f73e0b37 100644 --- a/Tests/WorkspaceTests/PrebuiltsTests.swift +++ b/Tests/WorkspaceTests/PrebuiltsTests.swift @@ -890,3 +890,13 @@ final class PrebuiltsTests: XCTestCase { } } } + +extension String { + var fixwin: String { + #if os(Windows) + return self.replacingOccurrences(of: "/", with: "\\") + #else + return self + #endif + } +} diff --git a/Utilities/build-using-self b/Utilities/build-using-self index add1e2a41d7..7bb2b9048ee 100755 --- a/Utilities/build-using-self +++ b/Utilities/build-using-self @@ -163,7 +163,6 @@ def filterNone(items: t.Iterable) -> t.Iterable: def main() -> None: args = get_arguments() - ignore = "-Xlinker /ignore:4217" if os.name == "nt" else "" logging.getLogger().setLevel(logging.DEBUG if args.is_verbose else logging.INFO) logging.debug("Args: %r", args) ignore_args = ["-Xlinker", "/ignore:4217"] if os.name == "nt" else []