diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index 2277a6f638f..289c48dec48 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -45,6 +45,9 @@ package final class ProductBuildDescription: SPMBuildCore.ProductBuildDescriptio // Computed during build planning. var dylibs: [ProductBuildDescription] = [] + /// The list of provided libraries that are going to be used by this product. + var providedLibraries: [String: AbsolutePath] = [:] + /// Any additional flags to be added. These flags are expected to be computed during build planning. var additionalFlags: [String] = [] @@ -156,6 +159,8 @@ package final class ProductBuildDescription: SPMBuildCore.ProductBuildDescriptio args += ["-F", self.buildParameters.buildPath.pathString] } + self.providedLibraries.forEach { args += ["-L", $1.pathString, "-l", $0] } + args += ["-L", self.buildParameters.buildPath.pathString] args += try ["-o", binaryPath.pathString] args += ["-module-name", self.product.name.spm_mangledToC99ExtendedIdentifier()] diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index 10fa290f50d..e2ea4abc021 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -424,6 +424,8 @@ extension LLBuildManifestBuilder { if target.underlying is BinaryTarget { return } // Ignore Plugin Targets. if target.underlying is PluginTarget { return } + // Ignore Provided Libraries. + if target.underlying is ProvidedLibraryTarget { return } // Depend on the binary for executable targets. if target.type == .executable { diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 9255948a749..6055c2c708d 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -269,7 +269,7 @@ package final class BuildOperation: PackageStructureDelegate, SPMBuildCore.Build // TODO: Currently this function will only match frameworks. func detectUnexpressedDependencies( - availableLibraries: [LibraryMetadata], + availableLibraries: [ProvidedLibrary], targetDependencyMap: [String: [String]]? ) { // Ensure we only emit these once, regardless of how many builds are being done. @@ -279,8 +279,8 @@ package final class BuildOperation: PackageStructureDelegate, SPMBuildCore.Build Self.didEmitUnexpressedDependencies = true let availableFrameworks = Dictionary(uniqueKeysWithValues: availableLibraries.compactMap { - if let identity = Set($0.identities.map(\.identity)).spm_only { - return ("\($0.productName!).framework", identity) + if let identity = Set($0.metadata.identities.map(\.identity)).spm_only { + return ("\($0.metadata.productName).framework", identity) } else { return nil } diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index c3932f0d626..c1731786e1d 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -115,6 +115,8 @@ extension BuildPlan { } buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths + buildProduct.providedLibraries = dependencies.providedLibraries + buildProduct.availableTools = dependencies.availableTools } @@ -127,6 +129,7 @@ extension BuildPlan { staticTargets: [ResolvedModule], systemModules: [ResolvedModule], libraryBinaryPaths: Set, + providedLibraries: [String: AbsolutePath], availableTools: [String: AbsolutePath] ) { /* Prior to tools-version 5.9, we used to erroneously recursively traverse executable/plugin dependencies and statically include their @@ -204,6 +207,7 @@ extension BuildPlan { var staticTargets = [ResolvedModule]() var systemModules = [ResolvedModule]() var libraryBinaryPaths: Set = [] + var providedLibraries = [String: AbsolutePath]() var availableTools = [String: AbsolutePath]() for dependency in allTargets { @@ -257,6 +261,8 @@ extension BuildPlan { } case .plugin: continue + case .providedLibrary: + providedLibraries[target.name] = target.underlying.path } case .product(let product, _): @@ -274,7 +280,7 @@ extension BuildPlan { } } - return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools) + return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, providedLibraries, availableTools) } /// Extracts the artifacts from an artifactsArchive diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index 058631598f2..dadb3c776e8 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -14,6 +14,7 @@ import struct Basics.InternalError import class PackageModel.BinaryTarget import class PackageModel.ClangTarget import class PackageModel.SystemLibraryTarget +import class PackageModel.ProvidedLibraryTarget extension BuildPlan { func plan(swiftTarget: SwiftTargetBuildDescription) throws { @@ -48,6 +49,10 @@ extension BuildPlan { swiftTarget.libraryBinaryPaths.insert(library.libraryPath) } } + case let target as ProvidedLibraryTarget: + swiftTarget.additionalFlags += [ + "-I", target.path.pathString + ] default: break } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 8f03a297328..3fd34b1f65c 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -433,7 +433,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { toolsVersion: toolsVersion, fileSystem: fileSystem )) - case is SystemLibraryTarget, is BinaryTarget: + case is SystemLibraryTarget, is BinaryTarget, is ProvidedLibraryTarget: break default: throw InternalError("unhandled \(target.underlying)") diff --git a/Sources/Commands/PackageCommands/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift index db6a4e8004c..f20d5c44a2f 100644 --- a/Sources/Commands/PackageCommands/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -74,7 +74,6 @@ extension SwiftPackageCommand { packageName: packageName, forceRemove: shouldForceRemove, root: swiftCommandState.getWorkspaceRoot(), - availableLibraries: swiftCommandState.getHostToolchain().providedLibraries, observabilityScope: swiftCommandState.observabilityScope ) } diff --git a/Sources/Commands/PackageCommands/Update.swift b/Sources/Commands/PackageCommands/Update.swift index b4ced1bad0e..fa2586c2c4f 100644 --- a/Sources/Commands/PackageCommands/Update.swift +++ b/Sources/Commands/PackageCommands/Update.swift @@ -73,7 +73,7 @@ extension SwiftPackageCommand { case .removed: report += "\n" report += "- \(package.identity) \(currentVersion)" - case .unchanged: + case .unchanged, .usesLibrary: continue } } diff --git a/Sources/Commands/Snippets/Cards/TopCard.swift b/Sources/Commands/Snippets/Cards/TopCard.swift index 7b915234db0..bb0398fda1e 100644 --- a/Sources/Commands/Snippets/Cards/TopCard.swift +++ b/Sources/Commands/Snippets/Cards/TopCard.swift @@ -153,6 +153,8 @@ fileprivate extension Target.Kind { return "snippets" case .macro: return "macros" + case .providedLibrary: + return "provided libraries" } } } diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 754a5401ea4..b94ff2268cd 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -607,7 +607,6 @@ package final class SwiftCommandState { explicitProduct: explicitProduct, forceResolvedVersions: options.resolver.forceResolvedVersions, testEntryPointPath: testEntryPointPath, - availableLibraries: self.getHostToolchain().providedLibraries, observabilityScope: self.observabilityScope ) diff --git a/Sources/PackageGraph/BoundVersion.swift b/Sources/PackageGraph/BoundVersion.swift index 7aa90a33ff6..459dee7f956 100644 --- a/Sources/PackageGraph/BoundVersion.swift +++ b/Sources/PackageGraph/BoundVersion.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import struct PackageModel.ProvidedLibrary import struct TSCUtility.Version /// A bound version for a package within an assignment. @@ -22,7 +23,7 @@ public enum BoundVersion: Equatable, Hashable { case excluded /// The version of the package to include. - case version(Version) + case version(Version, library: ProvidedLibrary? = nil) /// The package assignment is unversioned. case unversioned @@ -36,7 +37,7 @@ extension BoundVersion: CustomStringConvertible { switch self { case .excluded: return "excluded" - case .version(let version): + case .version(let version, _): return version.description case .unversioned: return "unversioned" diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 59687da73f3..b72e3fb265e 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -32,7 +32,6 @@ extension ModulesGraph { customPlatformsRegistry: PlatformRegistry? = .none, customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, testEntryPointPath: AbsolutePath? = nil, - availableLibraries: [LibraryMetadata], fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> ModulesGraph { @@ -160,7 +159,6 @@ extension ModulesGraph { unsafeAllowedPackages: unsafeAllowedPackages, platformRegistry: customPlatformsRegistry ?? .default, platformVersionProvider: platformVersionProvider, - availableLibraries: availableLibraries, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -250,7 +248,6 @@ private func createResolvedPackages( unsafeAllowedPackages: Set, platformRegistry: PlatformRegistry, platformVersionProvider: PlatformVersionProvider, - availableLibraries: [LibraryMetadata], fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws -> [ResolvedPackage] { @@ -529,32 +526,26 @@ private func createResolvedPackages( t.name != productRef.name } - let identitiesAvailableInSDK = availableLibraries.flatMap { $0.identities.map { $0.identity } } - // TODO: Do we have to care about "name" vs. identity here? - if let name = productRef.package, identitiesAvailableInSDK.contains(PackageIdentity.plain(name)) { - // Do not emit any diagnostic. - } else { - // Find a product name from the available product dependencies that is most similar to the required product name. - let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames)) - var packageContainingBestMatchedProduct: String? - if let bestMatchedProductName, productRef.name == bestMatchedProductName { - let dependentPackages = packageBuilder.dependencies.map(\.package) - for p in dependentPackages where p.targets.contains(where: { $0.name == bestMatchedProductName }) { - packageContainingBestMatchedProduct = p.identity.description - break - } + // Find a product name from the available product dependencies that is most similar to the required product name. + let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames)) + var packageContainingBestMatchedProduct: String? + if let bestMatchedProductName, productRef.name == bestMatchedProductName { + let dependentPackages = packageBuilder.dependencies.map(\.package) + for p in dependentPackages where p.targets.contains(where: { $0.name == bestMatchedProductName }) { + packageContainingBestMatchedProduct = p.identity.description + break } - let error = PackageGraphError.productDependencyNotFound( - package: package.identity.description, - targetName: targetBuilder.target.name, - dependencyProductName: productRef.name, - dependencyPackageName: productRef.package, - dependencyProductInDecl: !declProductsAsDependency.isEmpty, - similarProductName: bestMatchedProductName, - packageContainingSimilarProduct: packageContainingBestMatchedProduct - ) - packageObservabilityScope.emit(error) } + let error = PackageGraphError.productDependencyNotFound( + package: package.identity.description, + targetName: targetBuilder.target.name, + dependencyProductName: productRef.name, + dependencyPackageName: productRef.package, + dependencyProductInDecl: !declProductsAsDependency.isEmpty, + similarProductName: bestMatchedProductName, + packageContainingSimilarProduct: packageContainingBestMatchedProduct + ) + packageObservabilityScope.emit(error) } continue } diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 8160fc5f84a..e37b0ab4d54 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -488,7 +488,6 @@ public func loadModulesGraph( shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, createREPLProduct: createREPLProduct, customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, - availableLibraries: [], fileSystem: fileSystem, observabilityScope: observabilityScope ) diff --git a/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift index d73e41ffdf9..c20021d3642 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift @@ -97,6 +97,7 @@ final class ContainerProvider { ) { result in let result = result.tryMap { container -> PubGrubPackageContainer in let pubGrubContainer = PubGrubPackageContainer(underlying: container, pins: self.pins) + // only cache positive results self.containersCache[package] = pubGrubContainer return pubGrubContainer @@ -107,12 +108,9 @@ final class ContainerProvider { } /// Starts prefetching the given containers. - func prefetch(containers identifiers: [PackageReference], availableLibraries: [LibraryMetadata]) { - let filteredIdentifiers = identifiers.filter { - $0.matchingPrebuiltLibrary(in: availableLibraries) == nil - } + func prefetch(containers identifiers: [PackageReference]) { // Process each container. - for identifier in filteredIdentifiers { + for identifier in identifiers { var needsFetching = false self.prefetches.memoize(identifier) { let group = DispatchGroup() diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift index ffee1492d1e..c2e28f21713 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift @@ -105,7 +105,7 @@ public struct PubGrubDependencyResolver { private let pins: PinsStore.Pins /// The packages that are available in a prebuilt form in SDK or a toolchain - private let availableLibraries: [LibraryMetadata] + private let availableLibraries: [ProvidedLibrary] /// The container provider used to load package containers. private let provider: ContainerProvider @@ -125,7 +125,7 @@ public struct PubGrubDependencyResolver { public init( provider: PackageContainerProvider, pins: PinsStore.Pins = [:], - availableLibraries: [LibraryMetadata] = [], + availableLibraries: [ProvidedLibrary] = [], skipDependenciesUpdates: Bool = false, prefetchBasedOnResolvedFile: Bool = false, observabilityScope: ObservabilityScope, @@ -203,7 +203,7 @@ public struct PubGrubDependencyResolver { let pins = self.pins.values .map(\.packageRef) .filter { !inputs.overriddenPackages.keys.contains($0) } - self.provider.prefetch(containers: pins, availableLibraries: self.availableLibraries) + self.provider.prefetch(containers: pins) } let state = State(root: root, overriddenPackages: inputs.overriddenPackages) @@ -236,28 +236,27 @@ public struct PubGrubDependencyResolver { let boundVersion: BoundVersion switch assignment.term.requirement { case .exact(let version): - boundVersion = .version(version) + if let library = package.matchingPrebuiltLibrary(in: availableLibraries), + version == library.version + { + boundVersion = .version(version, library: library) + } else { + boundVersion = .version(version) + } case .range, .any, .empty, .ranges: throw InternalError("unexpected requirement value for assignment \(assignment.term)") } - // Strip packages that have prebuilt libraries only if they match library version. - // - // FIXME: This is built on assumption that libraries are part of the SDK and are - // always available in include/library paths, but what happens if they are - // part of a toolchain instead? Builder needs an indicator that certain path - // has to be included when building packages that depend on prebuilt libraries. - if let library = package.matchingPrebuiltLibrary(in: availableLibraries), - boundVersion == .version(.init(stringLiteral: library.version)) - { - continue - } - let products = assignment.term.node.productFilter - // TODO: replace with async/await when available - let container = try temp_await { self.provider.getContainer(for: package, completion: $0) } - let updatePackage = try container.underlying.loadPackageReference(at: boundVersion) + let updatePackage: PackageReference + if case .version(_, let library) = boundVersion, library != nil { + updatePackage = package + } else { + // TODO: replace with async/await when available + let container = try temp_await { self.provider.getContainer(for: package, completion: $0) } + updatePackage = try container.underlying.loadPackageReference(at: boundVersion) + } if var existing = flattenedAssignments[updatePackage] { guard existing.binding == boundVersion else { @@ -498,8 +497,9 @@ public struct PubGrubDependencyResolver { // initiate prefetch of known packages that will be used to make the decision on the next step self.provider.prefetch( - containers: state.solution.undecided.map(\.node.package), - availableLibraries: self.availableLibraries + containers: state.solution.undecided.map(\.node.package).filter { + $0.matchingPrebuiltLibrary(in: self.availableLibraries) == nil + } ) // If decision making determines that no more decisions are to be @@ -745,11 +745,9 @@ public struct PubGrubDependencyResolver { continue } - let version = Version(stringLiteral: library.version) - - if pkgTerm.requirement.contains(version) { - self.delegate?.didResolve(term: pkgTerm, version: version, duration: start.distance(to: .now())) - state.decide(pkgTerm.node, at: version) + if pkgTerm.requirement.contains(library.version) { + self.delegate?.didResolve(term: pkgTerm, version: library.version, duration: start.distance(to: .now())) + state.decide(pkgTerm.node, at: library.version) return completion(.success(pkgTerm.node)) } } @@ -895,14 +893,14 @@ extension PackageRequirement { } extension PackageReference { - public func matchingPrebuiltLibrary(in availableLibraries: [LibraryMetadata]) -> LibraryMetadata? { + public func matchingPrebuiltLibrary(in availableLibraries: [ProvidedLibrary]) -> ProvidedLibrary? { switch self.kind { case .fileSystem, .localSourceControl, .root: return nil // can never match a prebuilt library case .registry(let identity): if let registryIdentity = identity.registry { return availableLibraries.first( - where: { $0.identities.contains( + where: { $0.metadata.identities.contains( where: { $0 == .packageIdentity( scope: registryIdentity.scope.description, name: registryIdentity.name.description @@ -916,7 +914,7 @@ extension PackageReference { } case .remoteSourceControl(let url): return availableLibraries.first(where: { - $0.identities.contains(where: { $0 == .sourceControl(url: url) }) + $0.metadata.identities.contains(where: { $0 == .sourceControl(url: url) }) }) } } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index c81ccb1fadd..76d96c1d98e 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -536,6 +536,16 @@ public final class PackageBuilder { throw ModuleError.artifactNotFound(targetName: target.name, expectedArtifactName: target.name) } return artifact.path + } else if let targetPath = target.path, target.type == .providedLibrary { + guard let path = try? AbsolutePath(validating: targetPath) else { + throw ModuleError.invalidCustomPath(target: target.name, path: targetPath) + } + + if !self.fileSystem.isDirectory(path) { + throw ModuleError.unsupportedTargetPath(targetPath) + } + + return path } else if let subpath = target.path { // If there is a custom path defined, use that. if subpath == "" || subpath == "." { return self.packagePath @@ -844,6 +854,11 @@ public final class PackageBuilder { path: potentialModule.path, origin: artifactOrigin ) + } else if potentialModule.type == .providedLibrary { + return ProvidedLibraryTarget( + name: potentialModule.name, + path: potentialModule.path + ) } // Check for duplicate target dependencies diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index 26a19962746..a7b13798bb4 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -51,6 +51,7 @@ add_library(PackageModel Target/BinaryTarget.swift Target/ClangTarget.swift Target/PluginTarget.swift + Target/ProvidedLibraryTarget.swift Target/SwiftTarget.swift Target/SystemLibraryTarget.swift Target/Target.swift diff --git a/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift b/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift index 7e12d31a6f3..4a0312b6e78 100644 --- a/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift +++ b/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift @@ -11,10 +11,19 @@ //===----------------------------------------------------------------------===// import Basics -import Foundation +import struct TSCUtility.Version -public struct LibraryMetadata: Decodable { - public enum Identity: Equatable, Decodable { +public struct ProvidedLibrary: Hashable { + public let location: AbsolutePath + public let metadata: LibraryMetadata + + public var version: Version { + .init(stringLiteral: metadata.version) + } +} + +public struct LibraryMetadata: Hashable, Decodable { + public enum Identity: Hashable, Decodable { case packageIdentity(scope: String, name: String) case sourceControl(url: SourceControlURL) } @@ -24,7 +33,7 @@ public struct LibraryMetadata: Decodable { /// The version that was built (e.g., 509.0.2) public let version: String /// The product name, if it differs from the module name (e.g., SwiftParser). - public let productName: String? + public let productName: String let schemaVersion: Int } diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 7556b79f7b9..73adb0802e0 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -554,3 +554,50 @@ extension Manifest: Encodable { try container.encode(self.packageKind, forKey: .packageKind) } } + +extension Manifest { + package static func forProvidedLibrary( + fileSystem: FileSystem, + package: PackageReference, + libraryPath: AbsolutePath, + version: Version + ) throws -> Manifest { + let names = try fileSystem.getDirectoryContents(libraryPath).filter { + $0.hasSuffix("swiftmodule") + }.map { + let components = $0.split(separator: ".") + return String(components[0]) + } + + let products: [ProductDescription] = try names.map { + try .init(name: $0, type: .library(.automatic), targets: [$0]) + } + + let targets: [TargetDescription] = try names.map { + try .init( + name: $0, + path: libraryPath.pathString, + type: .providedLibrary + ) + } + + return .init( + displayName: package.identity.description, + path: libraryPath.appending(component: "provided-library.json"), + packageKind: package.kind, + packageLocation: package.locationString, + defaultLocalization: nil, + platforms: [], + version: version, + revision: nil, + toolsVersion: .v6_0, + pkgConfig: nil, + providers: nil, + cLanguageStandard: nil, + cxxLanguageStandard: nil, + swiftLanguageVersions: nil, + products: products, + targets: targets + ) + } +} diff --git a/Sources/PackageModel/Manifest/TargetDescription.swift b/Sources/PackageModel/Manifest/TargetDescription.swift index 57df7f6d37a..1c943a5313c 100644 --- a/Sources/PackageModel/Manifest/TargetDescription.swift +++ b/Sources/PackageModel/Manifest/TargetDescription.swift @@ -21,6 +21,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable { case binary case plugin case `macro` + case providedLibrary } /// Represents a target's dependency on another entity. @@ -222,6 +223,19 @@ public struct TargetDescription: Hashable, Encodable, Sendable { 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") } + case .providedLibrary: + if path == nil { throw Error.providedLibraryTargetRequiresPath(targetName: name) } + if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") } + 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") } } self.name = name @@ -370,13 +384,16 @@ import protocol Foundation.LocalizedError private enum Error: LocalizedError, Equatable { case binaryTargetRequiresEitherPathOrURL(targetName: String) case disallowedPropertyInTarget(targetName: String, propertyName: String) - + case providedLibraryTargetRequiresPath(targetName: 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)'" + case .providedLibraryTargetRequiresPath(let targetName): + return "provided library target '\(targetName)' does not define a path to the library" } } } diff --git a/Sources/PackageModel/ManifestSourceGeneration.swift b/Sources/PackageModel/ManifestSourceGeneration.swift index f2b0d65c45c..211c44032ee 100644 --- a/Sources/PackageModel/ManifestSourceGeneration.swift +++ b/Sources/PackageModel/ManifestSourceGeneration.swift @@ -317,6 +317,8 @@ fileprivate extension SourceCodeFragment { self.init(enum: "plugin", subnodes: params, multiline: true) case .macro: self.init(enum: "macro", subnodes: params, multiline: true) + case .providedLibrary: + self.init(enum: "providedLibrary", subnodes: params, multiline: true) } } diff --git a/Sources/PackageModel/PackageReference.swift b/Sources/PackageModel/PackageReference.swift index ff6b448101c..d4cec83d3ac 100644 --- a/Sources/PackageModel/PackageReference.swift +++ b/Sources/PackageModel/PackageReference.swift @@ -203,7 +203,7 @@ extension PackageReference: CustomStringConvertible { extension PackageReference.Kind: Encodable { private enum CodingKeys: String, CodingKey { - case root, fileSystem, localSourceControl, remoteSourceControl, registry + case root, fileSystem, localSourceControl, remoteSourceControl, registry, providedLibrary } public func encode(to encoder: Encoder) throws { diff --git a/Sources/PackageModel/Target/ProvidedLibraryTarget.swift b/Sources/PackageModel/Target/ProvidedLibraryTarget.swift new file mode 100644 index 00000000000..a8ed95b23ae --- /dev/null +++ b/Sources/PackageModel/Target/ProvidedLibraryTarget.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath + +/// Represents a target library that comes from a toolchain in prebuilt form. +public final class ProvidedLibraryTarget: Target { + public init( + name: String, + path: AbsolutePath + ) { + let sources = Sources(paths: [], root: path) + super.init( + name: name, + type: .providedLibrary, + path: sources.root, + sources: sources, + dependencies: [], + packageAccess: false, + buildSettings: .init(), + buildSettingsDescription: [], + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift index 40bb1a575e9..1074e353c87 100644 --- a/Sources/PackageModel/Target/Target.swift +++ b/Sources/PackageModel/Target/Target.swift @@ -21,6 +21,7 @@ public class Target: PolymorphicCodableProtocol { SystemLibraryTarget.self, BinaryTarget.self, PluginTarget.self, + ProvidedLibraryTarget.self, ] /// The target kind. @@ -33,6 +34,7 @@ public class Target: PolymorphicCodableProtocol { case plugin case snippet case `macro` + case providedLibrary } /// A group a target belongs to that allows customizing access boundaries. A target is treated as diff --git a/Sources/PackageModel/Toolchain.swift b/Sources/PackageModel/Toolchain.swift index cd7db9c68ba..8009f62d326 100644 --- a/Sources/PackageModel/Toolchain.swift +++ b/Sources/PackageModel/Toolchain.swift @@ -44,7 +44,7 @@ public protocol Toolchain { var installedSwiftPMConfiguration: InstalledSwiftPMConfiguration { get } /// Metadata for libraries provided by the used toolchain. - var providedLibraries: [LibraryMetadata] { get } + var providedLibraries: [ProvidedLibrary] { get } /// The root path to the Swift SDK used by this toolchain. var sdkRootPath: AbsolutePath? { get } diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index 9b7042332bb..7d1f833e903 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -88,7 +88,7 @@ public final class UserToolchain: Toolchain { public let installedSwiftPMConfiguration: InstalledSwiftPMConfiguration - public let providedLibraries: [LibraryMetadata] + public let providedLibraries: [ProvidedLibrary] /// Returns the runtime library for the given sanitizer. public func runtimeLibrary(for sanitizer: Sanitizer) throws -> AbsolutePath { @@ -487,7 +487,7 @@ public final class UserToolchain: Toolchain { searchStrategy: SearchStrategy = .default, customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil, customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil, - customProvidedLibraries: [LibraryMetadata]? = nil + customProvidedLibraries: [ProvidedLibrary]? = nil ) throws { self.swiftSDK = swiftSDK self.environment = environment @@ -551,7 +551,15 @@ public final class UserToolchain: Toolchain { self.providedLibraries = try Self.loadJSONResource( config: path, type: [LibraryMetadata].self, - default: []) + default: [] + ).map { + .init( + location: path.parentDirectory.appending(component: $0.productName), + metadata: $0 + ) + }.filter { + localFileSystem.isDirectory($0.location) + } } // Use the triple from Swift SDK or compute the host triple using swiftc. diff --git a/Sources/PackageModelSyntax/AddTarget.swift b/Sources/PackageModelSyntax/AddTarget.swift index 0ae83b78e0c..7fda6f58229 100644 --- a/Sources/PackageModelSyntax/AddTarget.swift +++ b/Sources/PackageModelSyntax/AddTarget.swift @@ -62,7 +62,7 @@ public struct AddTarget { ) let outerDirectory: String? = switch target.type { - case .binary, .plugin, .system: nil + case .binary, .plugin, .system, .providedLibrary: nil case .executable, .regular, .macro: "Sources" case .test: "Tests" } @@ -167,7 +167,7 @@ public struct AddTarget { } let sourceFileText: SourceFileSyntax = switch target.type { - case .binary, .plugin, .system: + case .binary, .plugin, .system, .providedLibrary: fatalError("should have exited above") case .macro: diff --git a/Sources/PackageModelSyntax/TargetDescription+Syntax.swift b/Sources/PackageModelSyntax/TargetDescription+Syntax.swift index f47f6590f06..5081932bed8 100644 --- a/Sources/PackageModelSyntax/TargetDescription+Syntax.swift +++ b/Sources/PackageModelSyntax/TargetDescription+Syntax.swift @@ -27,6 +27,7 @@ extension TargetDescription: ManifestSyntaxRepresentable { case .regular: "target" case .system: "systemLibrary" case .test: "testTarget" + case .providedLibrary: "providedLibrary" } } diff --git a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index 9edfb0a7bb4..0ea946cb38e 100644 --- a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -280,7 +280,7 @@ fileprivate extension WireInput.Target.TargetInfo.SourceModuleKind { self = .test case .macro: self = .macro - case .binary, .plugin, .systemModule: + case .binary, .plugin, .systemModule, .providedLibrary: throw StringError("unexpected target kind \(kind) for source module") } } diff --git a/Sources/SPMTestSupport/MockBuildTestHelper.swift b/Sources/SPMTestSupport/MockBuildTestHelper.swift index 1b8824b928c..9ce3ac5fa67 100644 --- a/Sources/SPMTestSupport/MockBuildTestHelper.swift +++ b/Sources/SPMTestSupport/MockBuildTestHelper.swift @@ -39,7 +39,7 @@ package struct MockToolchain: PackageModel.Toolchain { package let swiftPluginServerPath: AbsolutePath? = nil package let extraFlags = PackageModel.BuildFlags() package let installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default - package let providedLibraries = [LibraryMetadata]() + package let providedLibraries = [ProvidedLibrary]() package func getClangCompiler() throws -> AbsolutePath { "/fake/path/to/clang" diff --git a/Sources/SPMTestSupport/MockWorkspace.swift b/Sources/SPMTestSupport/MockWorkspace.swift index 717fc4f50ee..f2f24792322 100644 --- a/Sources/SPMTestSupport/MockWorkspace.swift +++ b/Sources/SPMTestSupport/MockWorkspace.swift @@ -368,7 +368,6 @@ package final class MockWorkspace { packageName: packageName, forceRemove: forceRemove, root: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) } @@ -467,7 +466,6 @@ package final class MockWorkspace { let graph = try workspace.loadPackageGraph( rootInput: rootInput, forceResolvedVersions: forceResolvedVersions, - availableLibraries: [], // assume no provided libraries for testing. expectedSigningEntities: expectedSigningEntities, observabilityScope: observability.topScope ) @@ -506,7 +504,6 @@ package final class MockWorkspace { try workspace.loadPackageGraph( rootInput: rootInput, forceResolvedVersions: forceResolvedVersions, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) } @@ -532,7 +529,6 @@ package final class MockWorkspace { let dependencyManifests = try workspace.loadDependencyManifests( root: root, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) @@ -541,7 +537,6 @@ package final class MockWorkspace { dependencyManifests: dependencyManifests, pinsStore: pinsStore, constraints: [], - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) @@ -752,7 +747,6 @@ package final class MockWorkspace { let graphRoot = PackageGraphRoot(input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope) let manifests = try workspace.loadDependencyManifests( root: graphRoot, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) result(manifests, observability.diagnostics) diff --git a/Sources/Workspace/ManagedDependency.swift b/Sources/Workspace/ManagedDependency.swift index b771f37221c..5f2fd75cfc5 100644 --- a/Sources/Workspace/ManagedDependency.swift +++ b/Sources/Workspace/ManagedDependency.swift @@ -34,6 +34,9 @@ extension Workspace { /// The dependency is downloaded from a registry. case registryDownload(version: Version) + /// The dependency is part of the toolchain in a binary form. + case providedLibrary(at: AbsolutePath, version: Version) + /// The dependency is in edited state. /// /// If the path is non-nil, the dependency is managed by a user and is @@ -51,6 +54,8 @@ extension Workspace { return "sourceControlCheckout (\(checkoutState))" case .registryDownload(let version): return "registryDownload (\(version))" + case .providedLibrary(let path, let version): + return "library (\(path) @ \(version)" case .edited: return "edited" case .custom: @@ -146,6 +151,17 @@ extension Workspace { ) } + public static func providedLibrary( + packageRef: PackageReference, + library: ProvidedLibrary + ) throws -> ManagedDependency { + ManagedDependency( + packageRef: packageRef, + state: .providedLibrary(at: library.location, version: library.version), + subpath: try RelativePath(validating: packageRef.identity.description) + ) + } + /// Create an edited dependency public static func edited( packageRef: PackageReference, diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index 6e634df9fd4..03f48dd0120 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -354,7 +354,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri let revision: Revision var version: Version? switch boundVersion { - case .version(let v): + case .version(let v, _): guard let tag = try self.knownVersions()[v] else { throw StringError("unknown tag \(v)") } diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index 5ece297dc4a..2e99b7d2107 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -37,7 +37,7 @@ import class PackageGraph.PinsStore import struct PackageGraph.PubGrubDependencyResolver import struct PackageGraph.Term import class PackageLoading.ManifestLoader -import struct PackageModel.LibraryMetadata +import struct PackageModel.ProvidedLibrary import enum PackageModel.PackageDependency import struct PackageModel.PackageIdentity import struct PackageModel.PackageReference @@ -57,7 +57,6 @@ extension Workspace { root: PackageGraphRootInput, packages: [String] = [], dryRun: Bool = false, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> [(PackageReference, Workspace.PackageStateChange)]? { let start = DispatchTime.now() @@ -88,7 +87,6 @@ extension Workspace { ) let currentManifests = try self.loadDependencyManifests( root: graphRoot, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) @@ -123,7 +121,6 @@ extension Workspace { // Resolve the dependencies. let resolver = try self.createResolver( pins: pins, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) self.activeResolver = resolver @@ -166,7 +163,6 @@ extension Workspace { // Load the updated manifests. let updatedDependencyManifests = try self.loadDependencyManifests( root: graphRoot, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) // If we have missing packages, something is fundamentally wrong with the resolution of the graph @@ -200,7 +196,6 @@ extension Workspace { func _resolve( root: PackageGraphRootInput, explicitProduct: String?, - availableLibraries: [LibraryMetadata], resolvedFileStrategy: ResolvedFileStrategy, observabilityScope: ObservabilityScope ) throws -> DependencyManifests { @@ -216,7 +211,6 @@ extension Workspace { return try self._resolveBasedOnResolvedVersionsFile( root: root, explicitProduct: explicitProduct, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) case .update(let forceResolution): @@ -253,7 +247,6 @@ extension Workspace { let (manifests, precomputationResult) = try self.tryResolveBasedOnResolvedVersionsFile( root: root, explicitProduct: explicitProduct, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) switch precomputationResult { @@ -277,7 +270,6 @@ extension Workspace { return try self.resolveAndUpdateResolvedFile( root: root, explicitProduct: explicitProduct, - availableLibraries: availableLibraries, forceResolution: forceResolution, constraints: [], observabilityScope: observabilityScope @@ -304,13 +296,11 @@ extension Workspace { func _resolveBasedOnResolvedVersionsFile( root: PackageGraphRootInput, explicitProduct: String?, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> DependencyManifests { let (manifests, precomputationResult) = try self.tryResolveBasedOnResolvedVersionsFile( root: root, explicitProduct: explicitProduct, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) switch precomputationResult { @@ -343,7 +333,6 @@ extension Workspace { fileprivate func tryResolveBasedOnResolvedVersionsFile( root: PackageGraphRootInput, explicitProduct: String?, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> (DependencyManifests, ResolutionPrecomputationResult) { // Ensure the cache path exists. @@ -370,7 +359,6 @@ extension Workspace { return try ( self.loadDependencyManifests( root: graphRoot, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ), .notRequired @@ -383,6 +371,20 @@ extension Workspace { // automatically manage the parallelism. let group = DispatchGroup() for pin in pinsStore.pins.values { + // Provided library doesn't have a container, we need to inject a special depedency. + if let library = pin.packageRef.matchingPrebuiltLibrary(in: self.providedLibraries), + case .version(library.version, _) = pin.state + { + try self.state.dependencies.add( + .providedLibrary( + packageRef: pin.packageRef, + library: library + ) + ) + try self.state.save() + continue + } + group.enter() let observabilityScope = observabilityScope.makeChildScope( description: "requesting package containers", @@ -430,6 +432,8 @@ extension Workspace { return !pin.state.equals(checkoutState) case .registryDownload(let version): return !pin.state.equals(version) + case .providedLibrary: + return false case .edited, .fileSystem, .custom: return true } @@ -463,7 +467,6 @@ extension Workspace { let currentManifests = try self.loadDependencyManifests( root: graphRoot, automaticallyAddManagedDependencies: true, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) @@ -478,7 +481,6 @@ extension Workspace { dependencyManifests: currentManifests, pinsStore: pinsStore, constraints: [], - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) @@ -495,7 +497,6 @@ extension Workspace { func resolveAndUpdateResolvedFile( root: PackageGraphRootInput, explicitProduct: String? = nil, - availableLibraries: [LibraryMetadata], forceResolution: Bool, constraints: [PackageContainerConstraint], observabilityScope: ObservabilityScope @@ -523,7 +524,6 @@ extension Workspace { ) let currentManifests = try self.loadDependencyManifests( root: graphRoot, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) guard !observabilityScope.errorsReported else { @@ -560,7 +560,6 @@ extension Workspace { dependencyManifests: currentManifests, pinsStore: pinsStore, constraints: constraints, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) @@ -596,7 +595,6 @@ extension Workspace { // Perform dependency resolution. let resolver = try self.createResolver( pins: pinsStore.pins, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) self.activeResolver = resolver @@ -627,7 +625,6 @@ extension Workspace { // Update the pinsStore. let updatedDependencyManifests = try self.loadDependencyManifests( root: graphRoot, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) // If we still have missing packages, something is fundamentally wrong with the resolution of the graph @@ -690,7 +687,7 @@ extension Workspace { metadata: packageRef.diagnosticsMetadata ).trap { switch state { - case .added, .updated, .unchanged: + case .added, .updated, .unchanged, .usesLibrary: break case .removed: try self.remove(package: packageRef) @@ -719,12 +716,27 @@ extension Workspace { productFilter: state.products, observabilityScope: observabilityScope ) - case .removed, .unchanged: + case .removed, .unchanged, .usesLibrary: break } } } + // Handle provided libraries + for (packageRef, state) in packageStateChanges { + observabilityScope.makeChildScope( + description: "adding provided libraries", + metadata: packageRef.diagnosticsMetadata + ).trap { + if case .usesLibrary(let library) = state { + try self.state.dependencies.add( + .providedLibrary(packageRef: packageRef, library: library) + ) + try self.state.save() + } + } + } + // Inform the delegate if nothing was updated. if packageStateChanges.filter({ $0.1 == .unchanged }).count == packageStateChanges.count { delegate?.dependenciesUpToDate() @@ -836,7 +848,6 @@ extension Workspace { dependencyManifests: DependencyManifests, pinsStore: PinsStore, constraints: [PackageContainerConstraint], - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> ResolutionPrecomputationResult { let computedConstraints = @@ -853,7 +864,7 @@ extension Workspace { let resolver = PubGrubDependencyResolver( provider: precomputationProvider, pins: pinsStore.pins, - availableLibraries: availableLibraries, + availableLibraries: self.providedLibraries, observabilityScope: observabilityScope ) let result = resolver.solve(constraints: computedConstraints) @@ -966,6 +977,9 @@ extension Workspace { /// The package is updated. case updated(State) + /// The package is replaced with a prebuilt library + case usesLibrary(ProvidedLibrary) + public var description: String { switch self { case .added(let requirement): @@ -976,15 +990,17 @@ extension Workspace { return "unchanged" case .updated(let requirement): return "updated(\(requirement))" + case .usesLibrary(let library): + return "usesLibrary(\(library.metadata.productName))" } } public var isAddedOrUpdated: Bool { switch self { case .added, .updated: - return true - case .unchanged, .removed: - return false + true + case .unchanged, .removed, .usesLibrary: + false } } } @@ -1033,6 +1049,8 @@ extension Workspace { packageStateChanges[binding.package.identity] = (binding.package, .updated(newState)) case .registryDownload: throw InternalError("Unexpected unversioned binding for downloaded dependency") + case .providedLibrary: + throw InternalError("Unexpected unversioned binding for library dependency") case .custom: throw InternalError("Unexpected unversioned binding for custom dependency") } @@ -1100,15 +1118,20 @@ extension Workspace { packageStateChanges[binding.package.identity] = (binding.package, .added(newState)) } - case .version(let version): - let stateChange: PackageStateChange - switch currentDependency?.state { - case .sourceControlCheckout(.version(version, _)), .registryDownload(version), .custom(version, _): - stateChange = .unchanged - case .edited, .fileSystem, .sourceControlCheckout, .registryDownload, .custom: - stateChange = .updated(.init(requirement: .version(version), products: binding.products)) + case .version(let version, let library): + let stateChange: PackageStateChange = switch currentDependency?.state { + case .sourceControlCheckout(.version(version, _)), + .registryDownload(version), + .providedLibrary(_, version: version), + .custom(version, _): + library.flatMap { .usesLibrary($0) } ?? .unchanged + case .edited, .fileSystem, .sourceControlCheckout, .registryDownload, .providedLibrary, .custom: + .updated(.init(requirement: .version(version), products: binding.products)) case nil: - stateChange = .added(.init(requirement: .version(version), products: binding.products)) + library.flatMap { .usesLibrary($0) } ?? .added(.init( + requirement: .version(version), + products: binding.products + )) } packageStateChanges[binding.package.identity] = (binding.package, stateChange) } @@ -1126,7 +1149,6 @@ extension Workspace { /// Creates resolver for the workspace. fileprivate func createResolver( pins: PinsStore.Pins, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> PubGrubDependencyResolver { var delegate: DependencyResolverDelegate @@ -1143,7 +1165,7 @@ extension Workspace { return PubGrubDependencyResolver( provider: packageContainerProvider, pins: pins, - availableLibraries: availableLibraries, + availableLibraries: self.providedLibraries, skipDependenciesUpdates: self.configuration.skipDependenciesUpdates, prefetchBasedOnResolvedFile: self.configuration.prefetchBasedOnResolvedFile, observabilityScope: observabilityScope, diff --git a/Sources/Workspace/Workspace+Editing.swift b/Sources/Workspace/Workspace+Editing.swift index c27afe324a6..cf429ad2eaa 100644 --- a/Sources/Workspace/Workspace+Editing.swift +++ b/Sources/Workspace/Workspace+Editing.swift @@ -15,7 +15,7 @@ import class Basics.ObservabilityScope import struct Basics.RelativePath import func Basics.temp_await import struct PackageGraph.PackageGraphRootInput -import struct PackageModel.LibraryMetadata +import struct PackageModel.ProvidedLibrary import struct SourceControl.Revision import class TSCBasic.InMemoryFileSystem @@ -52,6 +52,9 @@ extension Workspace { case .registryDownload: observabilityScope.emit(error: "registry dependency '\(dependency.packageRef.identity)' can't be edited") return + case .providedLibrary: + observabilityScope.emit(error: "library dependency '\(dependency.packageRef.identity)' can't be edited") + return case .custom: observabilityScope.emit(error: "custom dependency '\(dependency.packageRef.identity)' can't be edited") return @@ -170,7 +173,6 @@ extension Workspace { dependency: ManagedDependency, forceRemove: Bool, root: PackageGraphRootInput? = nil, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws { // Compute if we need to force remove. @@ -235,7 +237,6 @@ extension Workspace { try self._resolve( root: root, explicitProduct: .none, - availableLibraries: availableLibraries, resolvedFileStrategy: .update(forceResolution: false), observabilityScope: observabilityScope ) diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index e202a624e51..511dd191d50 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -28,7 +28,7 @@ import struct PackageGraph.PackageGraphRoot import class PackageLoading.ManifestLoader import struct PackageLoading.ManifestValidator import struct PackageLoading.ToolsVersionParser -import struct PackageModel.LibraryMetadata +import struct PackageModel.ProvidedLibrary import class PackageModel.Manifest import struct PackageModel.PackageIdentity import struct PackageModel.PackageReference @@ -61,8 +61,6 @@ extension Workspace { private let workspace: Workspace - private let availableLibraries: [LibraryMetadata] - private let observabilityScope: ObservabilityScope private let _dependencies: LoadableResult<( @@ -81,20 +79,17 @@ extension Workspace { fileSystem: FileSystem )], workspace: Workspace, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) { self.root = root self.dependencies = dependencies self.workspace = workspace - self.availableLibraries = availableLibraries self.observabilityScope = observabilityScope self._dependencies = LoadableResult { try Self.computeDependencies( root: root, dependencies: dependencies, workspace: workspace, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) } @@ -151,7 +146,7 @@ extension Workspace { result.insert(packageRef) } - case .registryDownload, .edited, .custom: + case .registryDownload, .edited, .providedLibrary, .custom: continue case .fileSystem: result.insert(dependency.packageRef) @@ -173,7 +168,6 @@ extension Workspace { fileSystem: FileSystem )], workspace: Workspace, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> ( @@ -204,17 +198,6 @@ extension Workspace { return PackageReference(identity: $0.key, kind: $0.1.packageKind) }) - let identitiesAvailableInSDK = availableLibraries.flatMap { - $0.identities.map { - $0.ref - }.filter { - // We "trust the process" here, if an identity from the SDK is available, filter it. - !availableIdentities.contains($0) - }.map { - $0.identity - } - } - var inputIdentities: OrderedCollections.OrderedSet = [] let inputNodes: [GraphLoadingNode] = root.packages.map { identity, package in inputIdentities.append(package.reference) @@ -292,11 +275,6 @@ extension Workspace { } requiredIdentities = inputIdentities.union(requiredIdentities) - let identitiesToFilter = requiredIdentities.filter { - return identitiesAvailableInSDK.contains($0.identity) - } - requiredIdentities = requiredIdentities.subtracting(identitiesToFilter) - // We should never have loaded a manifest we don't need. assert( availableIdentities.isSubset(of: requiredIdentities), @@ -343,7 +321,7 @@ extension Workspace { products: productFilter ) allConstraints.append(constraint) - case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: + case .sourceControlCheckout, .registryDownload, .fileSystem, .providedLibrary, .custom: break } allConstraints += try externalManifest.dependencyConstraints(productFilter: productFilter) @@ -358,7 +336,7 @@ extension Workspace { for (_, managedDependency, productFilter, _) in dependencies { switch managedDependency.state { - case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: continue + case .sourceControlCheckout, .registryDownload, .fileSystem, .providedLibrary, .custom: continue case .edited: break } // FIXME: We shouldn't need to construct a new package reference object here. @@ -394,6 +372,8 @@ extension Workspace { return path ?? self.location.editSubdirectory(for: dependency) case .fileSystem(let path): return path + case .providedLibrary(let path, _): + return path case .custom(_, let path): return path } @@ -436,7 +416,6 @@ extension Workspace { public func loadDependencyManifests( root: PackageGraphRoot, automaticallyAddManagedDependencies: Bool = false, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws -> DependencyManifests { let prepopulateManagedDependencies: ([PackageReference]) throws -> Void = { refs in @@ -479,7 +458,6 @@ extension Workspace { // Validates that all the managed dependencies are still present in the file system. self.fixManagedDependencies( - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) guard !observabilityScope.errorsReported else { @@ -488,7 +466,6 @@ extension Workspace { root: root, dependencies: [], workspace: self, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) } @@ -562,7 +539,6 @@ extension Workspace { root: root, dependencies: [], workspace: self, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) } @@ -623,7 +599,6 @@ extension Workspace { root: root, dependencies: dependencies, workspace: self, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) } @@ -684,6 +659,14 @@ extension Workspace { case .registryDownload(let downloadedVersion): packageKind = managedDependency.packageRef.kind packageVersion = downloadedVersion + case .providedLibrary(let path, let version): + let manifest: Manifest? = try? .forProvidedLibrary( + fileSystem: fileSystem, + package: managedDependency.packageRef, + libraryPath: path, + version: version + ) + return completion(manifest) case .custom(let availableVersion, _): packageKind = managedDependency.packageRef.kind packageVersion = availableVersion @@ -816,7 +799,6 @@ extension Workspace { /// If some edited dependency is removed from the file system, mark it as unedited and /// fallback on the original checkout. private func fixManagedDependencies( - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) { // Reset managed dependencies if the state file was removed during the lifetime of the Workspace object. @@ -890,13 +872,16 @@ extension Workspace { try self.unedit( dependency: dependency, forceRemove: true, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) observabilityScope .emit(.editedDependencyMissing(packageName: dependency.packageRef.identity.description)) + case .providedLibrary(_, version: _): + // TODO: If the dependency is not available we can turn it into a source control dependency + break + case .fileSystem: self.state.dependencies.remove(dependency.packageRef.identity) try self.state.save() diff --git a/Sources/Workspace/Workspace+Pinning.swift b/Sources/Workspace/Workspace+Pinning.swift index b51e69fb04f..6a652a937ad 100644 --- a/Sources/Workspace/Workspace+Pinning.swift +++ b/Sources/Workspace/Workspace+Pinning.swift @@ -145,7 +145,7 @@ extension PinsStore.Pin { packageRef: dependency.packageRef, state: .version(version, revision: .none) ) - case .edited, .fileSystem, .custom: + case .edited, .fileSystem, .providedLibrary, .custom: // NOOP return nil } diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index 0d70ee2af0c..4c4e971b16f 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -256,6 +256,15 @@ extension WorkspaceStateStorage { let version = try container.decode(String.self, forKey: .version) return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) + case "providedLibrary": + let path = try container.decode(AbsolutePath.self, forKey: .path) + let version = try container.decode(String.self, forKey: .version) + return try self.init( + underlying: .providedLibrary( + at: path, + version: TSCUtility.Version(versionString: version) + ) + ) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( @@ -286,6 +295,10 @@ extension WorkspaceStateStorage { case .registryDownload(let version): try container.encode("registryDownload", forKey: .name) try container.encode(version, forKey: .version) + case .providedLibrary(let path, let version): + try container.encode("providedLibrary", forKey: .name) + try container.encode(path, forKey: .path) + try container.encode(version, forKey: .version) case .edited(_, let path): try container.encode("edited", forKey: .name) try container.encode(path, forKey: .path) @@ -612,6 +625,15 @@ extension WorkspaceStateStorage { let version = try container.decode(String.self, forKey: .version) return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) + case "providedLibrary": + let path = try container.decode(AbsolutePath.self, forKey: .path) + let version = try container.decode(String.self, forKey: .version) + return try self.init( + underlying: .providedLibrary( + at: path, + version: TSCUtility.Version(versionString: version) + ) + ) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( @@ -642,6 +664,10 @@ extension WorkspaceStateStorage { case .registryDownload(let version): try container.encode("registryDownload", forKey: .name) try container.encode(version, forKey: .version) + case .providedLibrary(let path, let version): + try container.encode("providedLibrary", forKey: .name) + try container.encode(path, forKey: .path) + try container.encode(version, forKey: .version) case .edited(_, let path): try container.encode("edited", forKey: .name) try container.encode(path, forKey: .path) diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index e6016e3d554..26bcd60543c 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -572,9 +572,9 @@ public class Workspace { ) } - fileprivate var providedLibraries: [LibraryMetadata] { + var providedLibraries: [ProvidedLibrary] { // Note: Eventually, we should get these from the individual SDKs, but the first step is providing the metadata centrally in the toolchain. - return self.hostToolchain.providedLibraries + self.hostToolchain.providedLibraries } } @@ -626,7 +626,6 @@ extension Workspace { packageName: String, forceRemove: Bool, root: PackageGraphRootInput, - availableLibraries: [LibraryMetadata], observabilityScope: ObservabilityScope ) throws { guard let dependency = self.state.dependencies[.plain(packageName)] else { @@ -643,7 +642,6 @@ extension Workspace { dependency: dependency, forceRemove: forceRemove, root: root, - availableLibraries: availableLibraries, observabilityScope: observabilityScope ) } @@ -664,7 +662,6 @@ extension Workspace { try self._resolve( root: root, explicitProduct: explicitProduct, - availableLibraries: self.providedLibraries, resolvedFileStrategy: forceResolvedVersions ? .lockFile : forceResolution ? .update(forceResolution: true) : .bestEffort, observabilityScope: observabilityScope @@ -708,6 +705,8 @@ extension Workspace { defaultRequirement = checkoutState.requirement case .registryDownload(let version), .custom(let version, _): defaultRequirement = .versionSet(.exact(version)) + case .providedLibrary(_, version: let version): + defaultRequirement = .versionSet(.exact(version)) case .fileSystem: throw StringError("local dependency '\(dependency.packageRef.identity)' can't be resolved") case .edited: @@ -736,7 +735,6 @@ extension Workspace { // Run the resolution. try self.resolveAndUpdateResolvedFile( root: root, - availableLibraries: self.providedLibraries, forceResolution: false, constraints: [constraint], observabilityScope: observabilityScope @@ -754,7 +752,6 @@ extension Workspace { try self._resolveBasedOnResolvedVersionsFile( root: root, explicitProduct: .none, - availableLibraries: self.providedLibraries, observabilityScope: observabilityScope ) } @@ -865,7 +862,6 @@ extension Workspace { root: root, packages: packages, dryRun: dryRun, - availableLibraries: self.providedLibraries, observabilityScope: observabilityScope ) } @@ -877,7 +873,6 @@ extension Workspace { forceResolvedVersions: Bool = false, customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, testEntryPointPath: AbsolutePath? = nil, - availableLibraries: [LibraryMetadata], expectedSigningEntities: [PackageIdentity: RegistryReleaseMetadata.SigningEntity] = [:], observabilityScope: ObservabilityScope ) throws -> ModulesGraph { @@ -897,7 +892,6 @@ extension Workspace { let manifests = try self._resolve( root: root, explicitProduct: explicitProduct, - availableLibraries: availableLibraries, resolvedFileStrategy: forceResolvedVersions ? .lockFile : .bestEffort, observabilityScope: observabilityScope ) @@ -924,7 +918,6 @@ extension Workspace { createREPLProduct: self.configuration.createREPLProduct, customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, testEntryPointPath: testEntryPointPath, - availableLibraries: self.providedLibraries, fileSystem: self.fileSystem, observabilityScope: observabilityScope ) @@ -946,7 +939,6 @@ extension Workspace { try self.loadPackageGraph( rootInput: PackageGraphRootInput(packages: [rootPath]), explicitProduct: explicitProduct, - availableLibraries: self.providedLibraries, observabilityScope: observabilityScope ) } @@ -1350,6 +1342,8 @@ extension Workspace { } case .registryDownload(let version)?, .custom(let version, _): result.append("resolved to '\(version)'") + case .providedLibrary(_, version: let version): + result.append("resolved to '\(version)'") case .edited?: result.append("edited") case .fileSystem?: diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 3d77fd3b95d..aa2cfc994df 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -391,6 +391,9 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { case .macro: // Macros are not supported when using XCBuild, similar to package plugins. return + case .providedLibrary: + // Provided libraries don't need to be built. + return } } diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 6cf38354370..5b7ca511708 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -390,7 +390,6 @@ struct SwiftBootstrapBuildTool: ParsableCommand { partial[item.key] = (manifest: item.value, fs: self.fileSystem) }, binaryArtifacts: [:], - availableLibraries: [], // assume no provided libraries during bootstrap fileSystem: fileSystem, observabilityScope: observabilityScope ) diff --git a/Tests/BuildTests/BuildOperationTests.swift b/Tests/BuildTests/BuildOperationTests.swift index 21c40cafb5d..35e72c63462 100644 --- a/Tests/BuildTests/BuildOperationTests.swift +++ b/Tests/BuildTests/BuildOperationTests.swift @@ -52,12 +52,15 @@ final class BuildOperationTests: XCTestCase { buildOp.detectUnexpressedDependencies( availableLibraries: [ .init( - identities: [ - .sourceControl(url: .init("https://example.com/org/foo")) - ], - version: "1.0.0", - productName: "Best", - schemaVersion: 1 + location: "/foo", + metadata: .init( + identities: [ + .sourceControl(url: .init("https://example.com/org/foo")) + ], + version: "1.0.0", + productName: "Best", + schemaVersion: 1 + ) ) ], targetDependencyMap: ["Lunch": []] diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift index 6ce71ad87e5..24a4570325d 100644 --- a/Tests/BuildTests/BuildPlanTests.swift +++ b/Tests/BuildTests/BuildPlanTests.swift @@ -6260,4 +6260,112 @@ final class BuildPlanTests: XCTestCase { let dylibs = Array(buildProduct.dylibs.map({$0.product.name})).sorted() XCTAssertEqual(dylibs, ["BarLogging", "FooLogging"]) } + + func testSwiftPackageWithProvidedLibraries() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/A/Sources/ATarget/main.swift", + "/Libraries/B/BTarget.swiftmodule", + "/Libraries/C/CTarget.swiftmodule" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "A", + path: "/A", + dependencies: [ + .localSourceControl(path: "/B", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/C", requirement: .upToNextMajor(from: "1.0.0")), + ], + products: [ + ProductDescription( + name: "A", + type: .executable, + targets: ["ATarget"] + ) + ], + targets: [ + TargetDescription(name: "ATarget", dependencies: ["BLibrary", "CLibrary"]) + ] + ), + Manifest.createFileSystemManifest( + displayName: "B", + path: "/B", + products: [ + ProductDescription(name: "BLibrary", type: .library(.automatic), targets: ["BTarget"]), + ], + targets: [ + TargetDescription( + name: "BTarget", + path: "/Libraries/B", + type: .providedLibrary + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "C", + path: "/C", + products: [ + ProductDescription(name: "CLibrary", type: .library(.automatic), targets: ["CTarget"]), + ], + targets: [ + TargetDescription( + name: "CTarget", + path: "/Libraries/C", + type: .providedLibrary + ) + ] + ), + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + + let plan = try BuildPlan( + buildParameters: mockBuildParameters(), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + let result = try BuildPlanResult(plan: plan) + + result.checkProductsCount(1) + result.checkTargetsCount(1) + + XCTAssertMatch( + try result.target(for: "ATarget").swiftTarget().compileArguments(), + [ + .anySequence, + "-I", "/Libraries/C", + "-I", "/Libraries/B", + .anySequence + ] + ) + + let linkerArgs = try result.buildProduct(for: "A").linkArguments() + + XCTAssertMatch( + linkerArgs, + [ + .anySequence, + "-L", "/Libraries/B", + "-l", "BTarget", + .anySequence + ] + ) + + XCTAssertMatch( + linkerArgs, + [ + .anySequence, + "-L", "/Libraries/C", + "-l", "CTarget", + .anySequence + ] + ) + } } diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 43a71bcf1a7..3dcad8f34d1 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -3328,7 +3328,6 @@ final class PackageCommandTests: CommandsTestCase { // Load the package graph. let _ = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index 47218b62df3..c81842f822a 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -445,7 +445,6 @@ final class PluginTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -632,7 +631,6 @@ final class PluginTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -730,7 +728,6 @@ final class PluginTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -1046,7 +1043,6 @@ final class PluginTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssert(packageGraph.packages.count == 4, "\(packageGraph.packages)") diff --git a/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift b/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift index 9d92488bbf3..054cf84ea20 100644 --- a/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift @@ -69,7 +69,7 @@ class DependencyResolverRealWorldPerfTests: XCTestCasePerf { switch resolver.solve(constraints: graph.constraints) { case .success(let result): let result: [(container: PackageReference, version: Version)] = result.compactMap { - guard case .version(let version) = $0.boundVersion else { + guard case .version(let version, _) = $0.boundVersion else { XCTFail("Unexpected result") return nil } diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index 78b0298697e..c469caa9e88 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -97,7 +97,6 @@ final class PackageGraphPerfTests: XCTestCasePerf { identityResolver: identityResolver, externalManifests: externalManifests, binaryArtifacts: [:], - availableLibraries: [], // assume no provided libraries for testing. fileSystem: fs, observabilityScope: observability.topScope ) diff --git a/Tests/PackageGraphTests/PubgrubTests.swift b/Tests/PackageGraphTests/PubgrubTests.swift index 7684413fa48..850ab22a48b 100644 --- a/Tests/PackageGraphTests/PubgrubTests.swift +++ b/Tests/PackageGraphTests/PubgrubTests.swift @@ -2023,13 +2023,16 @@ final class PubGrubTestsBasicGraphs: XCTestCase { try builder.serve(fooRef, at: .version(.init(stringLiteral: "1.2.0"))) try builder.serve(fooRef, at: .version(.init(stringLiteral: "2.0.0"))) - let availableLibraries: [LibraryMetadata] = [ + let availableLibraries: [ProvidedLibrary] = [ .init( - identities: [.sourceControl(url: "https://example.com/org/foo")], - version: "1.0.0", - productName: nil, - schemaVersion: 1 - ), + location: .init("/foo"), + metadata: .init( + identities: [.sourceControl(url: "https://example.com/org/foo")], + version: "1.0.0", + productName: "foo", + schemaVersion: 1 + ) + ) ] let resolver = builder.create(availableLibraries: availableLibraries) @@ -2041,8 +2044,10 @@ final class PubGrubTestsBasicGraphs: XCTestCase { ]) let result = resolver.solve(constraints: dependencies1) - // Available libraries are filtered from the resolver results, so this is expected to be empty. - AssertResult(result, []) + print(try result.get()) + AssertResult(result, [ + ("foo", .version(.init(stringLiteral: "1.0.0"), library: availableLibraries.first!)), + ]) let result2 = resolver.solve(constraints: dependencies2) AssertResult(result2, [ @@ -2080,13 +2085,16 @@ final class PubGrubTestsBasicGraphs: XCTestCase { try builder.serve("target", at: "1.0.0") try builder.serve("target", at: "2.0.0") - let availableLibraries: [LibraryMetadata] = [ + let availableLibraries: [ProvidedLibrary] = [ .init( - identities: [.sourceControl(url: "https://example.com/org/foo")], - version: "1.1.0", - productName: nil, - schemaVersion: 1 - ), + location: .init("/foo"), + metadata: .init( + identities: [.sourceControl(url: "https://example.com/org/foo")], + version: "1.1.0", + productName: "foo", + schemaVersion: 1 + ) + ) ] let resolver = builder.create(availableLibraries: availableLibraries) @@ -2095,13 +2103,14 @@ final class PubGrubTestsBasicGraphs: XCTestCase { "target": (.versionSet(.range(.upToNextMajor(from: "2.0.0"))), .everything), ]) - // This behavior requires an explanation - "foo" is elided because 1.1.0 is prebuilt. - // It matches "root" requirements but without prebuilt library the solver would pick - // "1.0.0" because "foo" 1.1.0 dependency version requirements are incompatible with - // "target" 2.0.0. + // This behavior requires an explanation - "foo" is selected to be 1.1.0 because its + // prebuilt matches "root" requirements but without prebuilt library the solver would + // pick "1.0.0" because "foo" 1.1.0 dependency version requirements are incompatible + // with "target" 2.0.0. let result = resolver.solve(constraints: dependencies) AssertResult(result, [ + ("foo", .version(.init(stringLiteral: "1.1.0"), library: availableLibraries.first!)), ("target", .version(.init(stringLiteral: "2.0.0"))), ]) } @@ -2126,13 +2135,16 @@ final class PubGrubTestsBasicGraphs: XCTestCase { "bar": [fooRef: (.versionSet(.range(.upToNextMinor(from: "2.0.0"))), .everything)], ]) - let availableLibraries: [LibraryMetadata] = [ + let availableLibraries: [ProvidedLibrary] = [ .init( - identities: [.sourceControl(url: "https://example.com/org/foo")], - version: "1.0.0", - productName: nil, - schemaVersion: 1 - ), + location: .init("/foo"), + metadata: .init( + identities: [.sourceControl(url: "https://example.com/org/foo")], + version: "1.0.0", + productName: "foo", + schemaVersion: 1 + ) + ) ] let resolver = builder.create(availableLibraries: availableLibraries) @@ -3305,7 +3317,9 @@ private func AssertBindings( ) } for package in packages { - guard let binding = bindings.first(where: { $0.package.identity == package.identity }) else { + guard let binding = bindings.first(where: { + $0.package.identity == package.identity + }) else { XCTFail("No binding found for \(package.identity).", file: file, line: line) continue } @@ -3329,7 +3343,12 @@ private func AssertResult( ) { switch result { case .success(let bindings): - AssertBindings(bindings, packages.map { (PackageIdentity($0.identifier), $0.version) }, file: file, line: line) + AssertBindings( + bindings, + packages.map { (PackageIdentity($0.identifier), $0.version) }, + file: file, + line: line + ) case .failure(let error): XCTFail("Unexpected error: \(error)", file: file, line: line) } @@ -3376,7 +3395,7 @@ public class MockContainer: PackageContainer { public func toolsVersionsAppropriateVersionsDescending() throws -> [Version] { var versions: [Version] = [] for version in self._versions.reversed() { - guard case .version(let v) = version else { continue } + guard case .version(let v, _) = version else { continue } versions.append(v) } return versions @@ -3385,7 +3404,7 @@ public class MockContainer: PackageContainer { public func versionsAscending() throws -> [Version] { var versions: [Version] = [] for version in self._versions { - guard case .version(let v) = version else { continue } + guard case .version(let v, _) = version else { continue } versions.append(v) } return versions @@ -3452,7 +3471,7 @@ public class MockContainer: PackageContainer { self._versions.append(version) self._versions = self._versions .sorted(by: { lhs, rhs -> Bool in - guard case .version(let lv) = lhs, case .version(let rv) = rhs else { + guard case .version(let lv, _) = lhs, case .version(let rv, _) = rhs else { return true } return lv < rv @@ -3507,7 +3526,7 @@ public class MockContainer: PackageContainer { let versions = dependencies.keys.compactMap(Version.init(_:)) self._versions = versions .sorted() - .map(BoundVersion.version) + .map { .version($0) } } } @@ -3666,7 +3685,7 @@ class DependencyGraphBuilder { let container = self .containers[packageReference.identity.description] ?? MockContainer(package: packageReference) - if case .version(let v) = version { + if case .version(let v, _) = version { container.versionsToolsVersions[v] = toolsVersion ?? container.toolsVersion } @@ -3704,7 +3723,7 @@ class DependencyGraphBuilder { func create( pins: PinsStore.Pins = [:], - availableLibraries: [LibraryMetadata] = [], + availableLibraries: [ProvidedLibrary] = [], delegate: DependencyResolverDelegate? = .none ) -> PubGrubDependencyResolver { defer { diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index 58668f9e5db..eb9be4cd970 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -306,7 +306,6 @@ final class PluginInvocationTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -687,7 +686,6 @@ final class PluginInvocationTests: XCTestCase { // Load the package graph. XCTAssertThrowsError(try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope )) { error in var diagnosed = false @@ -767,7 +765,6 @@ final class PluginInvocationTests: XCTestCase { // Load the package graph. XCTAssertThrowsError(try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope)) { error in var diagnosed = false if let realError = error as? PackageGraphError, @@ -877,7 +874,6 @@ final class PluginInvocationTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics) @@ -1061,7 +1057,6 @@ final class PluginInvocationTests: XCTestCase { let graph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) let dict = try await workspace.loadPluginImports(packageGraph: graph) @@ -1209,7 +1204,6 @@ final class PluginInvocationTests: XCTestCase { // Load the package graph. let packageGraph = try workspace.loadPackageGraph( rootInput: rootInput, - availableLibraries: [], // assume no provided libraries for testing. observabilityScope: observability.topScope ) XCTAssertNoDiagnostics(observability.diagnostics)