From f9480a50f74db0435720a9ce2881702d12878825 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 29 Sep 2023 16:41:04 +0100 Subject: [PATCH 1/6] Add `.libraryArchive` artifact kind in `PackageModel` --- Sources/Build/BuildPlan.swift | 4 +- .../ArtifactsArchiveMetadata.swift | 1 + Sources/PackageModel/Target.swift | 911 ------------------ .../PackageModel/Target/BinaryTarget.swift | 139 +++ Sources/PackageModel/Target/ClangTarget.swift | 109 +++ .../PackageModel/Target/PluginTarget.swift | 174 ++++ Sources/PackageModel/Target/SwiftTarget.swift | 132 +++ .../Target/SystemLibraryTarget.swift | 70 ++ Sources/PackageModel/Target/Target.swift | 350 +++++++ Sources/PackagePlugin/PackageModel.swift | 1 + .../PluginContextDeserializer.swift | 2 + Sources/PackagePlugin/PluginMessages.swift | 1 + .../PluginContextSerializer.swift | 2 + Sources/Workspace/Workspace+State.swift | 5 + 14 files changed, 988 insertions(+), 913 deletions(-) delete mode 100644 Sources/PackageModel/Target.swift create mode 100644 Sources/PackageModel/Target/BinaryTarget.swift create mode 100644 Sources/PackageModel/Target/ClangTarget.swift create mode 100644 Sources/PackageModel/Target/PluginTarget.swift create mode 100644 Sources/PackageModel/Target/SwiftTarget.swift create mode 100644 Sources/PackageModel/Target/SystemLibraryTarget.swift create mode 100644 Sources/PackageModel/Target/Target.swift diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index f35b4754f11..aa42cf4795e 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -219,10 +219,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Cache for pkgConfig flags. private var pkgConfigCache = [SystemLibraryTarget: (cFlags: [String], libs: [String])]() - /// Cache for library information. + /// Cache for library information. private var externalLibrariesCache = [BinaryTarget: [LibraryInfo]]() - /// Cache for tools information. + /// Cache for tools information. private var externalExecutablesCache = [BinaryTarget: [ExecutableInfo]]() /// The filesystem to operate on. diff --git a/Sources/PackageModel/ArtifactsArchiveMetadata.swift b/Sources/PackageModel/ArtifactsArchiveMetadata.swift index 4c08b01fb55..fd63dc031f2 100644 --- a/Sources/PackageModel/ArtifactsArchiveMetadata.swift +++ b/Sources/PackageModel/ArtifactsArchiveMetadata.swift @@ -42,6 +42,7 @@ public struct ArtifactsArchiveMetadata: Equatable { // 3d models along with associated textures, or fonts, etc. public enum ArtifactType: String, RawRepresentable, Decodable { case executable + case library case swiftSDK // Can't be marked as formally deprecated as we still need to use this value for warning users. diff --git a/Sources/PackageModel/Target.swift b/Sources/PackageModel/Target.swift deleted file mode 100644 index ef6201e62b9..00000000000 --- a/Sources/PackageModel/Target.swift +++ /dev/null @@ -1,911 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 Basics -import Dispatch - -import protocol TSCUtility.PolymorphicCodableProtocol - -public class Target: PolymorphicCodableProtocol { - public static var implementations: [PolymorphicCodableProtocol.Type] = [ - SwiftTarget.self, - ClangTarget.self, - SystemLibraryTarget.self, - BinaryTarget.self, - PluginTarget.self, - ] - - /// The target kind. - public enum Kind: String, Codable { - case executable - case library - case systemModule = "system-target" - case test - case binary - case plugin - case snippet - case `macro` - } - - /// A group a target belongs to that allows customizing access boundaries. A target is treated as - /// a client outside of the package if `excluded`, inside the package boundary if `package`. - public enum Group: Codable, Equatable { - case package - case excluded - } - /// A reference to a product from a target dependency. - public struct ProductReference: Codable { - - /// The name of the product dependency. - public let name: String - - /// The name of the package containing the product. - public let package: String? - - /// Module aliases for targets of this product dependency. The key is an - /// original target name and the value is a new unique name that also - /// becomes the name of its .swiftmodule binary. - public let moduleAliases: [String: String]? - - /// Fully qualified name for this product dependency: package ID + name of the product - public var identity: String { - if let package { - return package.lowercased() + "_" + name - } else { - // this is hit only if this product is referenced `.byName(name)` - // which assumes the name of this product, its package, and its target - // all have the same name - return name.lowercased() + "_" + name - } - } - - /// Creates a product reference instance. - public init(name: String, package: String?, moduleAliases: [String: String]? = nil) { - self.name = name - self.package = package - self.moduleAliases = moduleAliases - } - } - - /// A target dependency to a target or product. - public enum Dependency { - /// A dependency referencing another target, with conditions. - case target(_ target: Target, conditions: [PackageConditionProtocol]) - - /// A dependency referencing a product, with conditions. - case product(_ product: ProductReference, conditions: [PackageConditionProtocol]) - - /// The target if the dependency is a target dependency. - public var target: Target? { - if case .target(let target, _) = self { - return target - } else { - return nil - } - } - - /// The product reference if the dependency is a product dependency. - public var product: ProductReference? { - if case .product(let product, _) = self { - return product - } else { - return nil - } - } - - /// The dependency conditions. - public var conditions: [PackageConditionProtocol] { - switch self { - case .target(_, let conditions): - return conditions - case .product(_, let conditions): - return conditions - } - } - - /// The name of the target or product of the dependency. - public var name: String { - switch self { - case .target(let target, _): - return target.name - case .product(let product, _): - return product.name - } - } - } - - /// A usage of a plugin target or product. Implemented as a dependency - /// for now and added to the `dependencies` array, since they currently - /// have exactly the same characteristics and to avoid duplicating the - /// implementation for now. - public typealias PluginUsage = Dependency - - /// The name of the target. - /// - /// NOTE: This name is not the language-level target (i.e., the importable - /// name) name in many cases, instead use c99name if you need uniqueness. - public private(set) var name: String - - /// Module aliases needed to build this target. The key is an original name of a - /// dependent target and the value is a new unique name mapped to the name - /// of its .swiftmodule binary. - public private(set) var moduleAliases: [String: String]? - /// Used to store pre-chained / pre-overriden module aliases - public private(set) var prechainModuleAliases: [String: String]? - /// Used to store aliases that should be referenced directly in source code - public private(set) var directRefAliases: [String: [String]]? - - /// Add module aliases (if applicable) for dependencies of this target. - /// - /// For example, adding an alias `Bar` for a target name `Foo` will result in - /// compiling references to `Foo` in source code of this target as `Bar.swiftmodule`. - /// If the name argument `Foo` is the same as this target's name, this target will be - /// renamed as `Bar` and the resulting binary will be `Bar.swiftmodule`. - /// - /// - Parameters: - /// - name: The original name of a dependent target or this target - /// - alias: A new unique name mapped to the resulting binary name - public func addModuleAlias(for name: String, as alias: String) { - if moduleAliases == nil { - moduleAliases = [name: alias] - } else { - moduleAliases?[name] = alias - } - } - - public func removeModuleAlias(for name: String) { - moduleAliases?.removeValue(forKey: name) - if moduleAliases?.isEmpty ?? false { - moduleAliases = nil - } - } - - public func addPrechainModuleAlias(for name: String, as alias: String) { - if prechainModuleAliases == nil { - prechainModuleAliases = [name: alias] - } else { - prechainModuleAliases?[name] = alias - } - } - public func addDirectRefAliases(for name: String, as aliases: [String]) { - if directRefAliases == nil { - directRefAliases = [name: aliases] - } else { - directRefAliases?[name] = aliases - } - } - - @discardableResult - public func applyAlias() -> Bool { - // If there's an alias for this target, rename - if let alias = moduleAliases?[name] { - self.name = alias - self.c99name = alias.spm_mangledToC99ExtendedIdentifier() - return true - } - return false - } - - /// The dependencies of this target. - public let dependencies: [Dependency] - - /// The language-level target name. - public private(set) var c99name: String - - /// The bundle name, if one is being generated. - public var bundleName: String? { - return resources.isEmpty ? nil : potentialBundleName - } - public let potentialBundleName: String? - - /// Suffix that's expected for test targets. - public static let testModuleNameSuffix = "Tests" - - /// The kind of target. - public let type: Kind - - /// If true, access to package declarations from other targets is allowed. - public let packageAccess: Bool - - /// The path of the target. - public let path: AbsolutePath - - /// The sources for the target. - public let sources: Sources - - /// The resource files in the target. - public let resources: [Resource] - - /// Files in the target that were marked as ignored. - public let ignored: [AbsolutePath] - - /// Other kinds of files in the target. - public let others: [AbsolutePath] - - /// The build settings assignments of this target. - public let buildSettings: BuildSettings.AssignmentTable - - /// The usages of package plugins by this target. - public let pluginUsages: [PluginUsage] - - /// Whether or not this target uses any custom unsafe flags. - public let usesUnsafeFlags: Bool - - fileprivate init( - name: String, - potentialBundleName: String? = nil, - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency], - packageAccess: Bool, - buildSettings: BuildSettings.AssignmentTable, - pluginUsages: [PluginUsage], - usesUnsafeFlags: Bool - ) { - self.name = name - self.potentialBundleName = potentialBundleName - self.type = type - self.path = path - self.sources = sources - self.resources = resources - self.ignored = ignored - self.others = others - self.dependencies = dependencies - self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() - self.packageAccess = packageAccess - self.buildSettings = buildSettings - self.pluginUsages = pluginUsages - self.usesUnsafeFlags = usesUnsafeFlags - } - - private enum CodingKeys: String, CodingKey { - case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, packageAccess, buildSettings, pluginUsages, usesUnsafeFlags - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - // FIXME: dependencies property is skipped on purpose as it points to - // the actual target dependency object. - try container.encode(name, forKey: .name) - try container.encode(potentialBundleName, forKey: .potentialBundleName) - try container.encode(type, forKey: .type) - try container.encode(path, forKey: .path) - try container.encode(sources, forKey: .sources) - try container.encode(resources, forKey: .resources) - try container.encode(ignored, forKey: .ignored) - try container.encode(others, forKey: .others) - try container.encode(packageAccess, forKey: .packageAccess) - try container.encode(buildSettings, forKey: .buildSettings) - // FIXME: pluginUsages property is skipped on purpose as it points to - // the actual target dependency object. - try container.encode(usesUnsafeFlags, forKey: .usesUnsafeFlags) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.potentialBundleName = try container.decodeIfPresent(String.self, forKey: .potentialBundleName) - self.type = try container.decode(Kind.self, forKey: .type) - self.path = try container.decode(AbsolutePath.self, forKey: .path) - self.sources = try container.decode(Sources.self, forKey: .sources) - self.resources = try container.decode([Resource].self, forKey: .resources) - self.ignored = try container.decode([AbsolutePath].self, forKey: .ignored) - self.others = try container.decode([AbsolutePath].self, forKey: .others) - // FIXME: dependencies property is skipped on purpose as it points to - // the actual target dependency object. - self.dependencies = [] - self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() - self.packageAccess = try container.decode(Bool.self, forKey: .packageAccess) - self.buildSettings = try container.decode(BuildSettings.AssignmentTable.self, forKey: .buildSettings) - // FIXME: pluginUsages property is skipped on purpose as it points to - // the actual target dependency object. - self.pluginUsages = [] - self.usesUnsafeFlags = try container.decode(Bool.self, forKey: .usesUnsafeFlags) - } -} - -extension Target: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - - public static func == (lhs: Target, rhs: Target) -> Bool { - ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } -} - -extension Target: CustomStringConvertible { - public var description: String { - return "<\(Swift.type(of: self)): \(name)>" - } -} - -public final class SwiftTarget: Target { - - /// The default name for the test entry point file located in a package. - public static let defaultTestEntryPointName = "XCTMain.swift" - - /// The list of all supported names for the test entry point file located in a package. - public static var testEntryPointNames: [String] { - [defaultTestEntryPointName, "LinuxMain.swift"] - } - - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { - self.swiftVersion = .v5 - - super.init( - name: name, - type: .library, - path: .root, - sources: testDiscoverySrc, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - /// The swift version of this target. - public let swiftVersion: SwiftLanguageVersion - - public init( - name: String, - potentialBundleName: String? = nil, - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - packageAccess: Bool, - swiftVersion: SwiftLanguageVersion, - buildSettings: BuildSettings.AssignmentTable = .init(), - pluginUsages: [PluginUsage] = [], - usesUnsafeFlags: Bool - ) { - self.swiftVersion = swiftVersion - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: buildSettings, - pluginUsages: pluginUsages, - usesUnsafeFlags: usesUnsafeFlags - ) - } - - /// Create an executable Swift target from test entry point file. - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testEntryPointPath: AbsolutePath) { - // Look for the first swift test target and use the same swift version - // for linux main target. This will need to change if we move to a model - // where we allow per target swift language version build settings. - let swiftTestTarget = dependencies.first { - guard case .target(let target as SwiftTarget, _) = $0 else { return false } - return target.type == .test - }.flatMap { $0.target as? SwiftTarget } - - // FIXME: This is not very correct but doesn't matter much in practice. - // We need to select the latest Swift language version that can - // satisfy the current tools version but there is not a good way to - // do that currently. - self.swiftVersion = swiftTestTarget?.swiftVersion ?? SwiftLanguageVersion(string: String(SwiftVersion.current.major)) ?? .v4 - let sources = Sources(paths: [testEntryPointPath], root: testEntryPointPath.parentDirectory) - - super.init( - name: name, - type: .executable, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case swiftVersion - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(swiftVersion, forKey: .swiftVersion) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.swiftVersion = try container.decode(SwiftLanguageVersion.self, forKey: .swiftVersion) - try super.init(from: decoder) - } - - public var supportsTestableExecutablesFeature: Bool { - // Exclude macros from testable executables if they are built as dylibs. - #if BUILD_MACROS_AS_DYLIBS - return type == .executable || type == .snippet - #else - return type == .executable || type == .macro || type == .snippet - #endif - } -} - -public final class SystemLibraryTarget: Target { - - /// The name of pkgConfig file, if any. - public let pkgConfig: String? - - /// List of system package providers, if any. - public let providers: [SystemPackageProviderDescription]? - - /// True if this system library should become implicit target - /// dependency of its dependent packages. - public let isImplicit: Bool - - public init( - name: String, - path: AbsolutePath, - isImplicit: Bool = true, - pkgConfig: String? = nil, - providers: [SystemPackageProviderDescription]? = nil - ) { - let sources = Sources(paths: [], root: path) - self.pkgConfig = pkgConfig - self.providers = providers - self.isImplicit = isImplicit - super.init( - name: name, - type: .systemModule, - path: sources.root, - sources: sources, - dependencies: [], - packageAccess: false, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case pkgConfig, providers, isImplicit - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(pkgConfig, forKey: .pkgConfig) - try container.encode(providers, forKey: .providers) - try container.encode(isImplicit, forKey: .isImplicit) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.pkgConfig = try container.decodeIfPresent(String.self, forKey: .pkgConfig) - self.providers = try container.decodeIfPresent([SystemPackageProviderDescription].self, forKey: .providers) - self.isImplicit = try container.decode(Bool.self, forKey: .isImplicit) - try super.init(from: decoder) - } -} - -public final class ClangTarget: Target { - - /// The default public include directory component. - public static let defaultPublicHeadersComponent = "include" - - /// The path to include directory. - public let includeDir: 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 - - /// The headers present in the target. - /// - /// Note that this contains both public and non-public headers. - public let headers: [AbsolutePath] - - /// True if this is a C++ target. - public let isCXX: Bool - - /// The C language standard flag. - public let cLanguageStandard: String? - - /// The C++ language standard flag. - public let cxxLanguageStandard: String? - - public init( - name: String, - potentialBundleName: String? = nil, - cLanguageStandard: String?, - cxxLanguageStandard: String?, - includeDir: AbsolutePath, - moduleMapType: ModuleMapType, - headers: [AbsolutePath] = [], - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - buildSettings: BuildSettings.AssignmentTable = .init(), - usesUnsafeFlags: Bool - ) throws { - guard includeDir.isDescendantOfOrEqual(to: sources.root) else { - throw StringError("\(includeDir) should be contained in the source root \(sources.root)") - } - self.isCXX = sources.containsCXXFiles - self.cLanguageStandard = cLanguageStandard - self.cxxLanguageStandard = cxxLanguageStandard - self.includeDir = includeDir - self.moduleMapType = moduleMapType - self.headers = headers - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: false, - buildSettings: buildSettings, - pluginUsages: [], - usesUnsafeFlags: usesUnsafeFlags - ) - } - - private enum CodingKeys: String, CodingKey { - case includeDir, moduleMapType, headers, isCXX, cLanguageStandard, cxxLanguageStandard - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(includeDir, forKey: .includeDir) - try container.encode(moduleMapType, forKey: .moduleMapType) - try container.encode(headers, forKey: .headers) - try container.encode(isCXX, forKey: .isCXX) - try container.encode(cLanguageStandard, forKey: .cLanguageStandard) - try container.encode(cxxLanguageStandard, forKey: .cxxLanguageStandard) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.includeDir = try container.decode(AbsolutePath.self, forKey: .includeDir) - self.moduleMapType = try container.decode(ModuleMapType.self, forKey: .moduleMapType) - self.headers = try container.decode([AbsolutePath].self, forKey: .headers) - self.isCXX = try container.decode(Bool.self, forKey: .isCXX) - self.cLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cLanguageStandard) - self.cxxLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cxxLanguageStandard) - try super.init(from: decoder) - } -} - -public final class BinaryTarget: Target { - /// The kind of binary artifact. - public let kind: Kind - - /// The original source of the binary artifact. - public let origin: Origin - - /// The binary artifact path. - public var artifactPath: AbsolutePath { - return self.sources.root - } - - public init( - name: String, - kind: Kind, - path: AbsolutePath, - origin: Origin - ) { - self.origin = origin - self.kind = kind - let sources = Sources(paths: [], root: path) - super.init( - name: name, - type: .binary, - path: .root, - sources: sources, - dependencies: [], - packageAccess: false, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case kind - case origin - case artifactSource // backwards compatibility 2/2021 - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.origin, forKey: .origin) - try container.encode(self.kind, forKey: .kind) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - // backwards compatibility 2/2021 - if !container.contains(.kind) { - self.kind = .xcframework - } else { - self.kind = try container.decode(Kind.self, forKey: .kind) - } - // backwards compatibility 2/2021 - if container.contains(.artifactSource) { - self.origin = try container.decode(Origin.self, forKey: .artifactSource) - } else { - self.origin = try container.decode(Origin.self, forKey: .origin) - } - try super.init(from: decoder) - } - - public enum Kind: String, RawRepresentable, Codable, CaseIterable { - case xcframework - case artifactsArchive - case unknown // for non-downloaded artifacts - - public var fileExtension: String { - switch self { - case .xcframework: - return "xcframework" - case .artifactsArchive: - return "artifactbundle" - case .unknown: - return "unknown" - } - } - } - - public var containsExecutable: Bool { - // FIXME: needs to be revisited once libraries are supported in artifact bundles - return self.kind == .artifactsArchive - } - - public enum Origin: Equatable, Codable { - - /// Represents an artifact that was downloaded from a remote URL. - case remote(url: String) - - /// Represents an artifact that was available locally. - case local - - private enum CodingKeys: String, CodingKey { - case remote, local - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .remote(let a1): - var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .remote) - try unkeyedContainer.encode(a1) - case .local: - try container.encodeNil(forKey: .local) - } - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - guard let key = values.allKeys.first(where: values.contains) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) - } - switch key { - case .remote: - var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) - let a1 = try unkeyedValues.decode(String.self) - self = .remote(url: a1) - case .local: - self = .local - } - } - } -} - -public final class PluginTarget: Target { - - /// Declared capability of the plugin. - public let capability: PluginCapability - - /// API version to use for PackagePlugin API availability. - public let apiVersion: ToolsVersion - - public init( - name: String, - sources: Sources, - apiVersion: ToolsVersion, - pluginCapability: PluginCapability, - dependencies: [Target.Dependency] = [], - packageAccess: Bool - ) { - self.capability = pluginCapability - self.apiVersion = apiVersion - super.init( - name: name, - type: .plugin, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case capability - case apiVersion - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.capability, forKey: .capability) - try container.encode(self.apiVersion, forKey: .apiVersion) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.capability = try container.decode(PluginCapability.self, forKey: .capability) - self.apiVersion = try container.decode(ToolsVersion.self, forKey: .apiVersion) - try super.init(from: decoder) - } -} - -public enum PluginCapability: Hashable, Codable { - case buildTool - case command(intent: PluginCommandIntent, permissions: [PluginPermission]) - - private enum CodingKeys: String, CodingKey { - case buildTool, command - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .buildTool: - try container.encodeNil(forKey: .buildTool) - case .command(let a1, let a2): - var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command) - try unkeyedContainer.encode(a1) - try unkeyedContainer.encode(a2) - } - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - guard let key = values.allKeys.first(where: values.contains) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) - } - switch key { - case .buildTool: - self = .buildTool - case .command: - var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) - let a1 = try unkeyedValues.decode(PluginCommandIntent.self) - let a2 = try unkeyedValues.decode([PluginPermission].self) - self = .command(intent: a1, permissions: a2) - } - } - - public init(from desc: TargetDescription.PluginCapability) { - switch desc { - case .buildTool: - self = .buildTool - case .command(let intent, let permissions): - self = .command(intent: .init(from: intent), permissions: permissions.map{ .init(from: $0) }) - } - } -} - -public enum PluginCommandIntent: Hashable, Codable { - case documentationGeneration - case sourceCodeFormatting - case custom(verb: String, description: String) - - public init(from desc: TargetDescription.PluginCommandIntent) { - switch desc { - case .documentationGeneration: - self = .documentationGeneration - case .sourceCodeFormatting: - self = .sourceCodeFormatting - case .custom(let verb, let description): - self = .custom(verb: verb, description: description) - } - } -} - -public enum PluginNetworkPermissionScope: Hashable, Codable { - case none - case local(ports: [Int]) - case all(ports: [Int]) - case docker - case unixDomainSocket - - init(_ scope: TargetDescription.PluginNetworkPermissionScope) { - switch scope { - case .none: self = .none - case .local(let ports): self = .local(ports: ports) - case .all(let ports): self = .all(ports: ports) - case .docker: self = .docker - case .unixDomainSocket: self = .unixDomainSocket - } - } - - public var label: String { - switch self { - case .all: return "all" - case .local: return "local" - case .none: return "none" - case .docker: return "docker unix domain socket" - case .unixDomainSocket: return "unix domain socket" - } - } - - public var ports: [Int] { - switch self { - case .all(let ports): return ports - case .local(let ports): return ports - case .none, .docker, .unixDomainSocket: return [] - } - } -} - -public enum PluginPermission: Hashable, Codable { - case allowNetworkConnections(scope: PluginNetworkPermissionScope, reason: String) - case writeToPackageDirectory(reason: String) - - public init(from desc: TargetDescription.PluginPermission) { - switch desc { - case .allowNetworkConnections(let scope, let reason): - self = .allowNetworkConnections(scope: .init(scope), reason: reason) - case .writeToPackageDirectory(let reason): - self = .writeToPackageDirectory(reason: reason) - } - } -} - -public extension Sequence where Iterator.Element == Target { - var executables: [Target] { - return filter { - switch $0.type { - case .binary: - return ($0 as? BinaryTarget)?.containsExecutable == true - case .executable, .snippet, .macro: - return true - default: - return false - } - } - } -} diff --git a/Sources/PackageModel/Target/BinaryTarget.swift b/Sources/PackageModel/Target/BinaryTarget.swift new file mode 100644 index 00000000000..819179a8300 --- /dev/null +++ b/Sources/PackageModel/Target/BinaryTarget.swift @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 struct Basics.AbsolutePath + +public final class BinaryTarget: Target { + /// The kind of binary artifact. + public let kind: Kind + + /// The original source of the binary artifact. + public let origin: Origin + + /// The binary artifact path. + public var artifactPath: AbsolutePath { + return self.sources.root + } + + public init( + name: String, + kind: Kind, + path: AbsolutePath, + origin: Origin + ) { + self.origin = origin + self.kind = kind + let sources = Sources(paths: [], root: path) + super.init( + name: name, + type: .binary, + path: .root, + sources: sources, + dependencies: [], + packageAccess: false, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case kind + case origin + case artifactSource // backwards compatibility 2/2021 + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.origin, forKey: .origin) + try container.encode(self.kind, forKey: .kind) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + // backwards compatibility 2/2021 + if !container.contains(.kind) { + self.kind = .xcframework + } else { + self.kind = try container.decode(Kind.self, forKey: .kind) + } + // backwards compatibility 2/2021 + if container.contains(.artifactSource) { + self.origin = try container.decode(Origin.self, forKey: .artifactSource) + } else { + self.origin = try container.decode(Origin.self, forKey: .origin) + } + try super.init(from: decoder) + } + + public enum Kind: String, RawRepresentable, Codable, CaseIterable { + case xcframework + case libraryArchive + case artifactsArchive + case unknown // for non-downloaded artifacts + + public var fileExtension: String { + switch self { + case .xcframework: + return "xcframework" + case .artifactsArchive, .libraryArchive: + return "artifactbundle" + case .unknown: + return "unknown" + } + } + } + + public var containsExecutable: Bool { + // FIXME: needs to be revisited once libraries are supported in artifact bundles + return self.kind == .artifactsArchive + } + + public enum Origin: Equatable, Codable { + + /// Represents an artifact that was downloaded from a remote URL. + case remote(url: String) + + /// Represents an artifact that was available locally. + case local + + private enum CodingKeys: String, CodingKey { + case remote, local + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .remote(let a1): + var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .remote) + try unkeyedContainer.encode(a1) + case .local: + try container.encodeNil(forKey: .local) + } + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + guard let key = values.allKeys.first(where: values.contains) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) + } + switch key { + case .remote: + var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) + let a1 = try unkeyedValues.decode(String.self) + self = .remote(url: a1) + case .local: + self = .local + } + } + } +} diff --git a/Sources/PackageModel/Target/ClangTarget.swift b/Sources/PackageModel/Target/ClangTarget.swift new file mode 100644 index 00000000000..d38f559a40d --- /dev/null +++ b/Sources/PackageModel/Target/ClangTarget.swift @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 struct Basics.AbsolutePath +import struct Basics.StringError + +public final class ClangTarget: Target { + /// The default public include directory component. + public static let defaultPublicHeadersComponent = "include" + + /// The path to include directory. + public let includeDir: 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 + + /// The headers present in the target. + /// + /// Note that this contains both public and non-public headers. + public let headers: [AbsolutePath] + + /// True if this is a C++ target. + public let isCXX: Bool + + /// The C language standard flag. + public let cLanguageStandard: String? + + /// The C++ language standard flag. + public let cxxLanguageStandard: String? + + public init( + name: String, + potentialBundleName: String? = nil, + cLanguageStandard: String?, + cxxLanguageStandard: String?, + includeDir: AbsolutePath, + moduleMapType: ModuleMapType, + headers: [AbsolutePath] = [], + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency] = [], + buildSettings: BuildSettings.AssignmentTable = .init(), + usesUnsafeFlags: Bool + ) throws { + guard includeDir.isDescendantOfOrEqual(to: sources.root) else { + throw StringError("\(includeDir) should be contained in the source root \(sources.root)") + } + self.isCXX = sources.containsCXXFiles + self.cLanguageStandard = cLanguageStandard + self.cxxLanguageStandard = cxxLanguageStandard + self.includeDir = includeDir + self.moduleMapType = moduleMapType + self.headers = headers + super.init( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + packageAccess: false, + buildSettings: buildSettings, + pluginUsages: [], + usesUnsafeFlags: usesUnsafeFlags + ) + } + + private enum CodingKeys: String, CodingKey { + case includeDir, moduleMapType, headers, isCXX, cLanguageStandard, cxxLanguageStandard + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(includeDir, forKey: .includeDir) + try container.encode(moduleMapType, forKey: .moduleMapType) + try container.encode(headers, forKey: .headers) + try container.encode(isCXX, forKey: .isCXX) + try container.encode(cLanguageStandard, forKey: .cLanguageStandard) + try container.encode(cxxLanguageStandard, forKey: .cxxLanguageStandard) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.includeDir = try container.decode(AbsolutePath.self, forKey: .includeDir) + self.moduleMapType = try container.decode(ModuleMapType.self, forKey: .moduleMapType) + self.headers = try container.decode([AbsolutePath].self, forKey: .headers) + self.isCXX = try container.decode(Bool.self, forKey: .isCXX) + self.cLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cLanguageStandard) + self.cxxLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cxxLanguageStandard) + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/PluginTarget.swift b/Sources/PackageModel/Target/PluginTarget.swift new file mode 100644 index 00000000000..4e5ddb874c3 --- /dev/null +++ b/Sources/PackageModel/Target/PluginTarget.swift @@ -0,0 +1,174 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 +// +//===----------------------------------------------------------------------===// + +public final class PluginTarget: Target { + /// Declared capability of the plugin. + public let capability: PluginCapability + + /// API version to use for PackagePlugin API availability. + public let apiVersion: ToolsVersion + + public init( + name: String, + sources: Sources, + apiVersion: ToolsVersion, + pluginCapability: PluginCapability, + dependencies: [Target.Dependency] = [], + packageAccess: Bool + ) { + self.capability = pluginCapability + self.apiVersion = apiVersion + super.init( + name: name, + type: .plugin, + path: .root, + sources: sources, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case capability + case apiVersion + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.capability, forKey: .capability) + try container.encode(self.apiVersion, forKey: .apiVersion) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.capability = try container.decode(PluginCapability.self, forKey: .capability) + self.apiVersion = try container.decode(ToolsVersion.self, forKey: .apiVersion) + try super.init(from: decoder) + } +} + +public enum PluginCapability: Hashable, Codable { + case buildTool + case command(intent: PluginCommandIntent, permissions: [PluginPermission]) + + private enum CodingKeys: String, CodingKey { + case buildTool, command + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .buildTool: + try container.encodeNil(forKey: .buildTool) + case .command(let a1, let a2): + var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command) + try unkeyedContainer.encode(a1) + try unkeyedContainer.encode(a2) + } + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + guard let key = values.allKeys.first(where: values.contains) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) + } + switch key { + case .buildTool: + self = .buildTool + case .command: + var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) + let a1 = try unkeyedValues.decode(PluginCommandIntent.self) + let a2 = try unkeyedValues.decode([PluginPermission].self) + self = .command(intent: a1, permissions: a2) + } + } + + public init(from desc: TargetDescription.PluginCapability) { + switch desc { + case .buildTool: + self = .buildTool + case .command(let intent, let permissions): + self = .command(intent: .init(from: intent), permissions: permissions.map{ .init(from: $0) }) + } + } +} + +public enum PluginCommandIntent: Hashable, Codable { + case documentationGeneration + case sourceCodeFormatting + case custom(verb: String, description: String) + + public init(from desc: TargetDescription.PluginCommandIntent) { + switch desc { + case .documentationGeneration: + self = .documentationGeneration + case .sourceCodeFormatting: + self = .sourceCodeFormatting + case .custom(let verb, let description): + self = .custom(verb: verb, description: description) + } + } +} + +public enum PluginNetworkPermissionScope: Hashable, Codable { + case none + case local(ports: [Int]) + case all(ports: [Int]) + case docker + case unixDomainSocket + + init(_ scope: TargetDescription.PluginNetworkPermissionScope) { + switch scope { + case .none: self = .none + case .local(let ports): self = .local(ports: ports) + case .all(let ports): self = .all(ports: ports) + case .docker: self = .docker + case .unixDomainSocket: self = .unixDomainSocket + } + } + + public var label: String { + switch self { + case .all: return "all" + case .local: return "local" + case .none: return "none" + case .docker: return "docker unix domain socket" + case .unixDomainSocket: return "unix domain socket" + } + } + + public var ports: [Int] { + switch self { + case .all(let ports): return ports + case .local(let ports): return ports + case .none, .docker, .unixDomainSocket: return [] + } + } +} + +public enum PluginPermission: Hashable, Codable { + case allowNetworkConnections(scope: PluginNetworkPermissionScope, reason: String) + case writeToPackageDirectory(reason: String) + + public init(from desc: TargetDescription.PluginPermission) { + switch desc { + case .allowNetworkConnections(let scope, let reason): + self = .allowNetworkConnections(scope: .init(scope), reason: reason) + case .writeToPackageDirectory(let reason): + self = .writeToPackageDirectory(reason: reason) + } + } +} diff --git a/Sources/PackageModel/Target/SwiftTarget.swift b/Sources/PackageModel/Target/SwiftTarget.swift new file mode 100644 index 00000000000..f23f79db6fe --- /dev/null +++ b/Sources/PackageModel/Target/SwiftTarget.swift @@ -0,0 +1,132 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 struct Basics.AbsolutePath +import struct Basics.SwiftVersion + +public final class SwiftTarget: Target { + /// The default name for the test entry point file located in a package. + public static let defaultTestEntryPointName = "XCTMain.swift" + + /// The list of all supported names for the test entry point file located in a package. + public static var testEntryPointNames: [String] { + [defaultTestEntryPointName, "LinuxMain.swift"] + } + + public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { + self.swiftVersion = .v5 + + super.init( + name: name, + type: .library, + path: .root, + sources: testDiscoverySrc, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + /// The swift version of this target. + public let swiftVersion: SwiftLanguageVersion + + public init( + name: String, + potentialBundleName: String? = nil, + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency] = [], + packageAccess: Bool, + swiftVersion: SwiftLanguageVersion, + buildSettings: BuildSettings.AssignmentTable = .init(), + pluginUsages: [PluginUsage] = [], + usesUnsafeFlags: Bool + ) { + self.swiftVersion = swiftVersion + super.init( + name: name, + potentialBundleName: potentialBundleName, + type: type, + path: path, + sources: sources, + resources: resources, + ignored: ignored, + others: others, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: buildSettings, + pluginUsages: pluginUsages, + usesUnsafeFlags: usesUnsafeFlags + ) + } + + /// Create an executable Swift target from test entry point file. + public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testEntryPointPath: AbsolutePath) { + // Look for the first swift test target and use the same swift version + // for linux main target. This will need to change if we move to a model + // where we allow per target swift language version build settings. + let swiftTestTarget = dependencies.first { + guard case .target(let target as SwiftTarget, _) = $0 else { return false } + return target.type == .test + }.flatMap { $0.target as? SwiftTarget } + + // FIXME: This is not very correct but doesn't matter much in practice. + // We need to select the latest Swift language version that can + // satisfy the current tools version but there is not a good way to + // do that currently. + self.swiftVersion = swiftTestTarget?.swiftVersion ?? SwiftLanguageVersion(string: String(SwiftVersion.current.major)) ?? .v4 + let sources = Sources(paths: [testEntryPointPath], root: testEntryPointPath.parentDirectory) + + super.init( + name: name, + type: .executable, + path: .root, + sources: sources, + dependencies: dependencies, + packageAccess: packageAccess, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case swiftVersion + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(swiftVersion, forKey: .swiftVersion) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.swiftVersion = try container.decode(SwiftLanguageVersion.self, forKey: .swiftVersion) + try super.init(from: decoder) + } + + public var supportsTestableExecutablesFeature: Bool { + // Exclude macros from testable executables if they are built as dylibs. + #if BUILD_MACROS_AS_DYLIBS + return type == .executable || type == .snippet + #else + return type == .executable || type == .macro || type == .snippet + #endif + } +} diff --git a/Sources/PackageModel/Target/SystemLibraryTarget.swift b/Sources/PackageModel/Target/SystemLibraryTarget.swift new file mode 100644 index 00000000000..beb99b670e5 --- /dev/null +++ b/Sources/PackageModel/Target/SystemLibraryTarget.swift @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 struct Basics.AbsolutePath + +public final class SystemLibraryTarget: Target { + + /// The name of pkgConfig file, if any. + public let pkgConfig: String? + + /// List of system package providers, if any. + public let providers: [SystemPackageProviderDescription]? + + /// True if this system library should become implicit target + /// dependency of its dependent packages. + public let isImplicit: Bool + + public init( + name: String, + path: AbsolutePath, + isImplicit: Bool = true, + pkgConfig: String? = nil, + providers: [SystemPackageProviderDescription]? = nil + ) { + let sources = Sources(paths: [], root: path) + self.pkgConfig = pkgConfig + self.providers = providers + self.isImplicit = isImplicit + super.init( + name: name, + type: .systemModule, + path: sources.root, + sources: sources, + dependencies: [], + packageAccess: false, + buildSettings: .init(), + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + private enum CodingKeys: String, CodingKey { + case pkgConfig, providers, isImplicit + } + + public override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(pkgConfig, forKey: .pkgConfig) + try container.encode(providers, forKey: .providers) + try container.encode(isImplicit, forKey: .isImplicit) + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.pkgConfig = try container.decodeIfPresent(String.self, forKey: .pkgConfig) + self.providers = try container.decodeIfPresent([SystemPackageProviderDescription].self, forKey: .providers) + self.isImplicit = try container.decode(Bool.self, forKey: .isImplicit) + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift new file mode 100644 index 00000000000..c6c767dee4e --- /dev/null +++ b/Sources/PackageModel/Target/Target.swift @@ -0,0 +1,350 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-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 Basics +import Dispatch + +import protocol TSCUtility.PolymorphicCodableProtocol + +public class Target: PolymorphicCodableProtocol { + public static var implementations: [PolymorphicCodableProtocol.Type] = [ + SwiftTarget.self, + ClangTarget.self, + SystemLibraryTarget.self, + BinaryTarget.self, + PluginTarget.self, + ] + + /// The target kind. + public enum Kind: String, Codable { + case executable + case library + case systemModule = "system-target" + case test + case binary + case plugin + case snippet + case `macro` + } + + /// A group a target belongs to that allows customizing access boundaries. A target is treated as + /// a client outside of the package if `excluded`, inside the package boundary if `package`. + public enum Group: Codable, Equatable { + case package + case excluded + } + /// A reference to a product from a target dependency. + public struct ProductReference: Codable { + + /// The name of the product dependency. + public let name: String + + /// The name of the package containing the product. + public let package: String? + + /// Module aliases for targets of this product dependency. The key is an + /// original target name and the value is a new unique name that also + /// becomes the name of its .swiftmodule binary. + public let moduleAliases: [String: String]? + + /// Fully qualified name for this product dependency: package ID + name of the product + public var identity: String { + if let package { + return package.lowercased() + "_" + name + } else { + // this is hit only if this product is referenced `.byName(name)` + // which assumes the name of this product, its package, and its target + // all have the same name + return name.lowercased() + "_" + name + } + } + + /// Creates a product reference instance. + public init(name: String, package: String?, moduleAliases: [String: String]? = nil) { + self.name = name + self.package = package + self.moduleAliases = moduleAliases + } + } + + /// A target dependency to a target or product. + public enum Dependency { + /// A dependency referencing another target, with conditions. + case target(_ target: Target, conditions: [PackageConditionProtocol]) + + /// A dependency referencing a product, with conditions. + case product(_ product: ProductReference, conditions: [PackageConditionProtocol]) + + /// The target if the dependency is a target dependency. + public var target: Target? { + if case .target(let target, _) = self { + return target + } else { + return nil + } + } + + /// The product reference if the dependency is a product dependency. + public var product: ProductReference? { + if case .product(let product, _) = self { + return product + } else { + return nil + } + } + + /// The dependency conditions. + public var conditions: [PackageConditionProtocol] { + switch self { + case .target(_, let conditions): + return conditions + case .product(_, let conditions): + return conditions + } + } + + /// The name of the target or product of the dependency. + public var name: String { + switch self { + case .target(let target, _): + return target.name + case .product(let product, _): + return product.name + } + } + } + + /// A usage of a plugin target or product. Implemented as a dependency + /// for now and added to the `dependencies` array, since they currently + /// have exactly the same characteristics and to avoid duplicating the + /// implementation for now. + public typealias PluginUsage = Dependency + + /// The name of the target. + /// + /// NOTE: This name is not the language-level target (i.e., the importable + /// name) name in many cases, instead use c99name if you need uniqueness. + public private(set) var name: String + + /// Module aliases needed to build this target. The key is an original name of a + /// dependent target and the value is a new unique name mapped to the name + /// of its .swiftmodule binary. + public private(set) var moduleAliases: [String: String]? + /// Used to store pre-chained / pre-overriden module aliases + public private(set) var prechainModuleAliases: [String: String]? + /// Used to store aliases that should be referenced directly in source code + public private(set) var directRefAliases: [String: [String]]? + + /// Add module aliases (if applicable) for dependencies of this target. + /// + /// For example, adding an alias `Bar` for a target name `Foo` will result in + /// compiling references to `Foo` in source code of this target as `Bar.swiftmodule`. + /// If the name argument `Foo` is the same as this target's name, this target will be + /// renamed as `Bar` and the resulting binary will be `Bar.swiftmodule`. + /// + /// - Parameters: + /// - name: The original name of a dependent target or this target + /// - alias: A new unique name mapped to the resulting binary name + public func addModuleAlias(for name: String, as alias: String) { + if moduleAliases == nil { + moduleAliases = [name: alias] + } else { + moduleAliases?[name] = alias + } + } + + public func removeModuleAlias(for name: String) { + moduleAliases?.removeValue(forKey: name) + if moduleAliases?.isEmpty ?? false { + moduleAliases = nil + } + } + + public func addPrechainModuleAlias(for name: String, as alias: String) { + if prechainModuleAliases == nil { + prechainModuleAliases = [name: alias] + } else { + prechainModuleAliases?[name] = alias + } + } + public func addDirectRefAliases(for name: String, as aliases: [String]) { + if directRefAliases == nil { + directRefAliases = [name: aliases] + } else { + directRefAliases?[name] = aliases + } + } + + @discardableResult + public func applyAlias() -> Bool { + // If there's an alias for this target, rename + if let alias = moduleAliases?[name] { + self.name = alias + self.c99name = alias.spm_mangledToC99ExtendedIdentifier() + return true + } + return false + } + + /// The dependencies of this target. + public let dependencies: [Dependency] + + /// The language-level target name. + public private(set) var c99name: String + + /// The bundle name, if one is being generated. + public var bundleName: String? { + return resources.isEmpty ? nil : potentialBundleName + } + public let potentialBundleName: String? + + /// Suffix that's expected for test targets. + public static let testModuleNameSuffix = "Tests" + + /// The kind of target. + public let type: Kind + + /// If true, access to package declarations from other targets is allowed. + public let packageAccess: Bool + + /// The path of the target. + public let path: AbsolutePath + + /// The sources for the target. + public let sources: Sources + + /// The resource files in the target. + public let resources: [Resource] + + /// Files in the target that were marked as ignored. + public let ignored: [AbsolutePath] + + /// Other kinds of files in the target. + public let others: [AbsolutePath] + + /// The build settings assignments of this target. + public let buildSettings: BuildSettings.AssignmentTable + + /// The usages of package plugins by this target. + public let pluginUsages: [PluginUsage] + + /// Whether or not this target uses any custom unsafe flags. + public let usesUnsafeFlags: Bool + + init( + name: String, + potentialBundleName: String? = nil, + type: Kind, + path: AbsolutePath, + sources: Sources, + resources: [Resource] = [], + ignored: [AbsolutePath] = [], + others: [AbsolutePath] = [], + dependencies: [Target.Dependency], + packageAccess: Bool, + buildSettings: BuildSettings.AssignmentTable, + pluginUsages: [PluginUsage], + usesUnsafeFlags: Bool + ) { + self.name = name + self.potentialBundleName = potentialBundleName + self.type = type + self.path = path + self.sources = sources + self.resources = resources + self.ignored = ignored + self.others = others + self.dependencies = dependencies + self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() + self.packageAccess = packageAccess + self.buildSettings = buildSettings + self.pluginUsages = pluginUsages + self.usesUnsafeFlags = usesUnsafeFlags + } + + private enum CodingKeys: String, CodingKey { + case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, packageAccess, buildSettings, pluginUsages, usesUnsafeFlags + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + // FIXME: dependencies property is skipped on purpose as it points to + // the actual target dependency object. + try container.encode(name, forKey: .name) + try container.encode(potentialBundleName, forKey: .potentialBundleName) + try container.encode(type, forKey: .type) + try container.encode(path, forKey: .path) + try container.encode(sources, forKey: .sources) + try container.encode(resources, forKey: .resources) + try container.encode(ignored, forKey: .ignored) + try container.encode(others, forKey: .others) + try container.encode(packageAccess, forKey: .packageAccess) + try container.encode(buildSettings, forKey: .buildSettings) + // FIXME: pluginUsages property is skipped on purpose as it points to + // the actual target dependency object. + try container.encode(usesUnsafeFlags, forKey: .usesUnsafeFlags) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.potentialBundleName = try container.decodeIfPresent(String.self, forKey: .potentialBundleName) + self.type = try container.decode(Kind.self, forKey: .type) + self.path = try container.decode(AbsolutePath.self, forKey: .path) + self.sources = try container.decode(Sources.self, forKey: .sources) + self.resources = try container.decode([Resource].self, forKey: .resources) + self.ignored = try container.decode([AbsolutePath].self, forKey: .ignored) + self.others = try container.decode([AbsolutePath].self, forKey: .others) + // FIXME: dependencies property is skipped on purpose as it points to + // the actual target dependency object. + self.dependencies = [] + self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() + self.packageAccess = try container.decode(Bool.self, forKey: .packageAccess) + self.buildSettings = try container.decode(BuildSettings.AssignmentTable.self, forKey: .buildSettings) + // FIXME: pluginUsages property is skipped on purpose as it points to + // the actual target dependency object. + self.pluginUsages = [] + self.usesUnsafeFlags = try container.decode(Bool.self, forKey: .usesUnsafeFlags) + } +} + +extension Target: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } + + public static func == (lhs: Target, rhs: Target) -> Bool { + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + } +} + +extension Target: CustomStringConvertible { + public var description: String { + return "<\(Swift.type(of: self)): \(name)>" + } +} + +public extension Sequence where Iterator.Element == Target { + var executables: [Target] { + return filter { + switch $0.type { + case .binary: + return ($0 as? BinaryTarget)?.containsExecutable == true + case .executable, .snippet, .macro: + return true + default: + return false + } + } + } +} diff --git a/Sources/PackagePlugin/PackageModel.swift b/Sources/PackagePlugin/PackageModel.swift index 2dd84db4f80..be2c9c2b585 100644 --- a/Sources/PackagePlugin/PackageModel.swift +++ b/Sources/PackagePlugin/PackageModel.swift @@ -349,6 +349,7 @@ public struct BinaryArtifactTarget: Target { public enum Kind { case xcframework case artifactsArchive + case libraryArchive } // Represents the original location of a binary artifact. diff --git a/Sources/PackagePlugin/PluginContextDeserializer.swift b/Sources/PackagePlugin/PluginContextDeserializer.swift index 22fbd3dcfc5..7dfd609dc69 100644 --- a/Sources/PackagePlugin/PluginContextDeserializer.swift +++ b/Sources/PackagePlugin/PluginContextDeserializer.swift @@ -139,6 +139,8 @@ internal struct PluginContextDeserializer { switch kind { case .artifactsArchive: artifactKind = .artifactsArchive + case .libraryArchive: + artifactKind = .libraryArchive case .xcframework: artifactKind = .xcframework } diff --git a/Sources/PackagePlugin/PluginMessages.swift b/Sources/PackagePlugin/PluginMessages.swift index b65307e9af3..d27c967a36c 100644 --- a/Sources/PackagePlugin/PluginMessages.swift +++ b/Sources/PackagePlugin/PluginMessages.swift @@ -178,6 +178,7 @@ enum HostToPluginMessage: Codable { enum BinaryArtifactKind: Codable { case xcframework + case libraryArchive case artifactsArchive } diff --git a/Sources/SPMBuildCore/PluginContextSerializer.swift b/Sources/SPMBuildCore/PluginContextSerializer.swift index 7af829e9f93..814b06acfd8 100644 --- a/Sources/SPMBuildCore/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/PluginContextSerializer.swift @@ -133,6 +133,8 @@ internal struct PluginContextSerializer { switch target.kind { case .artifactsArchive: artifactKind = .artifactsArchive + case .libraryArchive: + artifactKind = .libraryArchive case .xcframework: artifactKind = .xcframework case .unknown: diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index f5e152e34ed..cf759059bd3 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -380,6 +380,7 @@ extension WorkspaceStateStorage { enum Kind: String, Codable { case xcframework case artifactsArchive + case libraryArchive case unknown init(_ underlying: BinaryTarget.Kind) { @@ -388,6 +389,8 @@ extension WorkspaceStateStorage { self = .xcframework case .artifactsArchive: self = .artifactsArchive + case .libraryArchive: + self = .libraryArchive case .unknown: self = .unknown } @@ -399,6 +402,8 @@ extension WorkspaceStateStorage { return .xcframework case .artifactsArchive: return .artifactsArchive + case .libraryArchive: + return .libraryArchive case .unknown: return .unknown } From 7cf76995bd4e0cd436582274d35fe0fe7035051b Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 3 Oct 2023 15:31:47 +0100 Subject: [PATCH 2/6] Add PoC build support for library artifact bundles --- Sources/Build/BuildPlan.swift | 81 ++++++++++++++++--- .../ArtifactsArchiveMetadata.swift | 26 ++++-- .../BinaryTarget+Extensions.swift | 30 ++++++- Sources/SPMBuildCore/PluginInvocation.swift | 2 +- .../Workspace/Workspace+BinaryArtifacts.swift | 2 +- 5 files changed, 116 insertions(+), 25 deletions(-) diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index aa42cf4795e..5274675b837 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -809,8 +809,15 @@ public class BuildPlan: SPMBuildCore.BuildPlan { libraryBinaryPaths.insert(library.libraryPath) } case .artifactsArchive: - let tools = try self.parseArtifactsArchive(for: binaryTarget) - tools.forEach { availableTools[$0.name] = $0.executablePath } + let tools = try self.parseExecutableArtifactsArchive(for: binaryTarget) + for tool in tools { + availableTools[tool.name] = tool.executablePath + } + case .libraryArchive: + let libraries = try self.parseLibraryArtifactsArchive(for: binaryTarget) + for library in libraries { + libraryBinaryPaths.insert(library.libraryPath) + } case.unknown: throw InternalError("unknown binary target '\(target.name)' type") } @@ -843,7 +850,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { case is SwiftTarget: if case let .swift(dependencyTargetDescription)? = targetMap[dependency] { if let moduleMap = dependencyTargetDescription.moduleMap { - clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] + clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap)"] } } @@ -854,14 +861,15 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Add the modulemap of the dependency if it has one. if case let .clang(dependencyTargetDescription)? = targetMap[dependency] { if let moduleMap = dependencyTargetDescription.moduleMap { - clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap.pathString)"] + clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMap)"] } } case let target as SystemLibraryTarget: - clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"] + clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath)"] clangTarget.additionalFlags += try pkgConfig(for: target).cFlags case let target as BinaryTarget: - if case .xcframework = target.kind { + switch target.kind { + case .xcframework: let libraries = try self.parseXCFramework(for: target) for library in libraries { library.headersPaths.forEach { @@ -869,6 +877,23 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } clangTarget.libraryBinaryPaths.insert(library.libraryPath) } + + case .libraryArchive: + let libraries = try self.parseLibraryArtifactsArchive(for: target) + for library in libraries { + library.headersPaths.forEach { + clangTarget.additionalFlags += ["-I", $0.pathString] + } + clangTarget.libraryBinaryPaths.insert(library.libraryPath) + if let moduleMapPath = library.moduleMapPath { + clangTarget.additionalFlags += ["-fmodule-map-file=\(moduleMapPath)"] + } + } + + default: + observabilityScope.emit( + warning: "Kind \(target.kind) of binary target \(target.name) is not supported." + ) } default: continue } @@ -894,11 +919,14 @@ public class BuildPlan: SPMBuildCore.BuildPlan { "-Xcc", "-fmodule-map-file=\(moduleMap.pathString)", "-Xcc", "-I", "-Xcc", target.clangTarget.includeDir.pathString, ] + case let target as SystemLibraryTarget: - swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"] + swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath)"] swiftTarget.additionalFlags += try pkgConfig(for: target).cFlags + case let target as BinaryTarget: - if case .xcframework = target.kind { + switch target.kind { + case .xcframework: let libraries = try self.parseXCFramework(for: target) for library in libraries { library.headersPaths.forEach { @@ -906,7 +934,25 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } swiftTarget.libraryBinaryPaths.insert(library.libraryPath) } + + case .libraryArchive: + let libraries = try self.parseLibraryArtifactsArchive(for: target) + for library in libraries { + library.headersPaths.forEach { + swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString] + } + swiftTarget.libraryBinaryPaths.insert(library.libraryPath) + if let moduleMapPath = library.moduleMapPath { + swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(moduleMapPath)"] + } + } + + default: + observabilityScope.emit( + warning: "Kind \(target.kind) of binary target \(target.name) is not supported." + ) } + default: break } @@ -1030,15 +1076,24 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Extracts the library information from an XCFramework. private func parseXCFramework(for target: BinaryTarget) throws -> [LibraryInfo] { try self.externalLibrariesCache.memoize(key: target) { - return try target.parseXCFrameworks(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) + try target.parseXCFrameworks(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) } } - /// Extracts the artifacts from an artifactsArchive - private func parseArtifactsArchive(for target: BinaryTarget) throws -> [ExecutableInfo] { + /// Extracts the executable artifacts from an `.artifactbundle`. + private func parseExecutableArtifactsArchive(for target: BinaryTarget) throws -> [ExecutableInfo] { try self.externalExecutablesCache.memoize(key: target) { - let execInfos = try target.parseArtifactArchives(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) - return execInfos.filter{!$0.supportedTriples.isEmpty} + let execInfos = try target.parseExecutableArtifactArchives( + for: self.buildParameters.targetTriple, + fileSystem: self.fileSystem + ) + return execInfos.filter { !$0.supportedTriples.isEmpty } + } + } + + private func parseLibraryArtifactsArchive(for target: BinaryTarget) throws -> [LibraryInfo] { + try self.externalLibrariesCache.memoize(key: target) { + try target.parseLibraryArtifactArchives(for: self.buildParameters.targetTriple, fileSystem: self.fileSystem) } } } diff --git a/Sources/PackageModel/ArtifactsArchiveMetadata.swift b/Sources/PackageModel/ArtifactsArchiveMetadata.swift index fd63dc031f2..1aa1db0c82f 100644 --- a/Sources/PackageModel/ArtifactsArchiveMetadata.swift +++ b/Sources/PackageModel/ArtifactsArchiveMetadata.swift @@ -26,7 +26,7 @@ public struct ArtifactsArchiveMetadata: Equatable { public struct Artifact: Equatable { public let type: ArtifactType - let version: String + public let version: String public let variants: [Variant] public init(type: ArtifactsArchiveMetadata.ArtifactType, version: String, variants: [Variant]) { @@ -37,9 +37,9 @@ public struct ArtifactsArchiveMetadata: Equatable { } // In the future we are likely to extend the ArtifactsArchive file format to carry other types of artifacts beyond - // executables and Swift SDKs. Additional fields may be required to support these new artifact + // executables, libraries, and Swift SDKs. Additional fields may be required to support these new artifact // types e.g. headers path for libraries. This can also support resource-only artifacts as well. For example, - // 3d models along with associated textures, or fonts, etc. + // 3D models along with associated textures, or fonts, etc. public enum ArtifactType: String, RawRepresentable, Decodable { case executable case library @@ -52,12 +52,19 @@ public struct ArtifactsArchiveMetadata: Equatable { public struct Variant: Equatable { public let path: RelativePath public let supportedTriples: [Triple] + public let libraryMetadata: LibraryMetadata? - public init(path: RelativePath, supportedTriples: [Triple]) { + public init(path: RelativePath, supportedTriples: [Triple], libraryMetadata: LibraryMetadata? = nil) { self.path = path self.supportedTriples = supportedTriples + self.libraryMetadata = libraryMetadata } } + + public struct LibraryMetadata: Equatable, Decodable { + public let headerPaths: [RelativePath] + public let moduleMapPath: RelativePath? + } } extension ArtifactsArchiveMetadata { @@ -77,7 +84,7 @@ extension ArtifactsArchiveMetadata { ) switch (version.major, version.minor) { - case (1, 1), (1, 0): + case (1, 2), (1, 1), (1, 0): return decodedMetadata default: throw StringError( @@ -85,7 +92,9 @@ extension ArtifactsArchiveMetadata { ) } } catch { - throw StringError("failed parsing ArtifactsArchive info.json at '\(path)': \(error.interpolationDescription)") + throw StringError( + "failed parsing ArtifactsArchive info.json at '\(path)': \(error.interpolationDescription)" + ) } } } @@ -116,11 +125,16 @@ extension ArtifactsArchiveMetadata.Variant: Decodable { enum CodingKeys: String, CodingKey { case path case supportedTriples + case libraryMetadata } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.supportedTriples = try container.decode([String].self, forKey: .supportedTriples).map { try Triple($0) } self.path = try RelativePath(validating: container.decode(String.self, forKey: .path)) + self.libraryMetadata = try container.decode( + ArtifactsArchiveMetadata.LibraryMetadata.self, + forKey: .libraryMetadata + ) } } diff --git a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift index 278c3b0167e..14811b2fc84 100644 --- a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift +++ b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift @@ -22,6 +22,9 @@ public struct LibraryInfo: Equatable { /// The paths to the headers directories. public let headersPaths: [AbsolutePath] + + /// The path to the module map of this library. + public let moduleMapPath: AbsolutePath? } /// Information about an executable from a binary dependency. @@ -53,16 +56,20 @@ extension BinaryTarget { let libraryFile = try AbsolutePath(validating: library.libraryPath, relativeTo: libraryDir) let headersDirs = try library.headersPath .map { [try AbsolutePath(validating: $0, relativeTo: libraryDir)] } ?? [] + [libraryDir] - return [LibraryInfo(libraryPath: libraryFile, headersPaths: headersDirs)] + return [LibraryInfo(libraryPath: libraryFile, headersPaths: headersDirs, moduleMapPath: nil)] + } + + @available(*, deprecated, renamed: "parseExecutableArtifactArchives") + public func parseArtifactArchives(for triple: Triple, fileSystem: any FileSystem) throws -> [ExecutableInfo] { + try self.parseExecutableArtifactArchives(for: triple, fileSystem: fileSystem) } - public func parseArtifactArchives(for triple: Triple, fileSystem: FileSystem) throws -> [ExecutableInfo] { + public func parseExecutableArtifactArchives(for triple: Triple, fileSystem: any FileSystem) throws -> [ExecutableInfo] { // The host triple might contain a version which we don't want to take into account here. let versionLessTriple = try triple.withoutVersion() // We return at most a single variant of each artifact. let metadata = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: self.artifactPath) - // Currently we filter out everything except executables. - // TODO: Add support for libraries + // Filter out everything except executables. let executables = metadata.artifacts.filter { $0.value.type == .executable } // Construct an ExecutableInfo for each matching variant. return try executables.flatMap { entry in @@ -79,6 +86,21 @@ extension BinaryTarget { } } } + + public func parseLibraryArtifactArchives(for triple: Triple, fileSystem: any FileSystem) throws -> [LibraryInfo] { + try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: self.artifactPath).artifacts + .filter { $0.value.type == .library } + .flatMap { entry in + // Construct a `LibraryInfo` for each matching variant. + entry.value.variants.map { + LibraryInfo( + libraryPath: self.artifactPath.appending($0.path), + headersPaths: $0.libraryMetadata?.headerPaths.map { self.artifactPath.appending($0) } ?? [], + moduleMapPath: $0.libraryMetadata?.moduleMapPath.map { self.artifactPath.appending($0) } + ) + } + } + } } extension Triple { diff --git a/Sources/SPMBuildCore/PluginInvocation.swift b/Sources/SPMBuildCore/PluginInvocation.swift index 62aeb695b6e..d664f4903d2 100644 --- a/Sources/SPMBuildCore/PluginInvocation.swift +++ b/Sources/SPMBuildCore/PluginInvocation.swift @@ -543,7 +543,7 @@ public extension PluginTarget { // For a binary target we create a `vendedTool`. if let target = executableOrBinaryTarget as? BinaryTarget { // TODO: Memoize this result for the host triple - let execInfos = try target.parseArtifactArchives(for: hostTriple, fileSystem: fileSystem) + let execInfos = try target.parseExecutableArtifactArchives(for: hostTriple, fileSystem: fileSystem) return try execInfos.map{ .vendedTool(name: $0.name, path: $0.executablePath, supportedTriples: try $0.supportedTriples.map{ try $0.withoutVersion().tripleString }) } } // For an executable target we create a `builtTool`. diff --git a/Sources/Workspace/Workspace+BinaryArtifacts.swift b/Sources/Workspace/Workspace+BinaryArtifacts.swift index e3bd82d2d32..0ffa6fcede9 100644 --- a/Sources/Workspace/Workspace+BinaryArtifacts.swift +++ b/Sources/Workspace/Workspace+BinaryArtifacts.swift @@ -703,7 +703,7 @@ extension Workspace.BinaryArtifactsManager { if let infoJSON = files.first(where: { $0.basename.lowercased() == "info.json" }) { do { _ = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: infoJSON.parentDirectory) - return .artifactsArchive + return .libraryArchive } catch { observabilityScope.emit( debug: "info.json found in '\(path)' but failed to parse", From 447a1b3525d64872bda7e7a081d52c4c0942635a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Apr 2025 17:00:09 +0100 Subject: [PATCH 3/6] Fix build errors after conflict resolution --- .../Build/BuildPlan/BuildPlan+Product.swift | 27 +- Sources/Build/BuildPlan/BuildPlan.swift | 2 +- .../PackageModel/Module/BinaryModule.swift | 16 +- .../PackageModel/Target/BinaryTarget.swift | 139 ------- Sources/PackageModel/Target/ClangTarget.swift | 109 ------ .../PackageModel/Target/PluginTarget.swift | 174 --------- Sources/PackageModel/Target/SwiftTarget.swift | 132 ------- .../Target/SystemLibraryTarget.swift | 70 ---- Sources/PackageModel/Target/Target.swift | 350 ------------------ .../Plugins/PluginContextSerializer.swift | 2 +- .../Plugins/PluginInvocation.swift | 10 +- Sources/Workspace/Workspace+State.swift | 13 +- 12 files changed, 53 insertions(+), 991 deletions(-) delete mode 100644 Sources/PackageModel/Target/BinaryTarget.swift delete mode 100644 Sources/PackageModel/Target/ClangTarget.swift delete mode 100644 Sources/PackageModel/Target/PluginTarget.swift delete mode 100644 Sources/PackageModel/Target/SwiftTarget.swift delete mode 100644 Sources/PackageModel/Target/SystemLibraryTarget.swift delete mode 100644 Sources/PackageModel/Target/Target.swift diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index 038ab6515db..ecbad1a916c 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -26,6 +26,7 @@ import class PackageModel.SwiftModule import class PackageModel.SystemLibraryModule import struct SPMBuildCore.BuildParameters import struct SPMBuildCore.ExecutableInfo +import struct SPMBuildCore.LibraryInfo import func TSCBasic.topologicalSort extension BuildPlan { @@ -296,11 +297,21 @@ extension BuildPlan { for library in libraries { libraryBinaryPaths.insert(library.libraryPath) } - case .artifactsArchive: - let tools = try self.parseArtifactsArchive( + case .executableArchive: + let tools = try self.parseExecutableArtifactsArchive( for: binaryTarget, triple: productDescription.buildParameters.triple ) - tools.forEach { availableTools[$0.name] = $0.executablePath } + for tool in tools { + availableTools[tool.name] = tool.executablePath + } + case .libraryArchive: + let libraries = try self.parseLibraryArtifactsArchive( + for: binaryTarget, + triple: productDescription.buildParameters.triple + ) + for library in libraries { + libraryBinaryPaths.insert(library.libraryPath) + } case .unknown: throw InternalError("unknown binary target '\(module.name)' type") } @@ -330,10 +341,16 @@ extension BuildPlan { } /// Extracts the artifacts from an artifactsArchive - private func parseArtifactsArchive(for binaryTarget: BinaryModule, triple: Triple) throws -> [ExecutableInfo] { + private func parseExecutableArtifactsArchive(for binaryTarget: BinaryModule, triple: Triple) throws -> [ExecutableInfo] { try self.externalExecutablesCache.memoize(key: binaryTarget) { - let execInfos = try binaryTarget.parseArtifactArchives(for: triple, fileSystem: self.fileSystem) + let execInfos = try binaryTarget.parseExecutableArtifactArchives(for: triple, fileSystem: self.fileSystem) return execInfos.filter { !$0.supportedTriples.isEmpty } } } + + private func parseLibraryArtifactsArchive(for target: BinaryModule, triple: Triple) throws -> [LibraryInfo] { + try self.externalLibrariesCache.memoize(key: target) { + try target.parseLibraryArtifactArchives(for: triple, fileSystem: self.fileSystem) + } + } } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 01ffe8ec665..f677ce99287 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -235,7 +235,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { private var pkgConfigCache = [SystemLibraryModule: (cFlags: [String], libs: [String])]() /// Cache for library information. - private var externalLibrariesCache = [BinaryModule: [LibraryInfo]]() + var externalLibrariesCache = [BinaryModule: [LibraryInfo]]() /// Cache for tools information. var externalExecutablesCache = [BinaryModule: [ExecutableInfo]]() diff --git a/Sources/PackageModel/Module/BinaryModule.swift b/Sources/PackageModel/Module/BinaryModule.swift index 5f28ff0ddb8..3f4de58896b 100644 --- a/Sources/PackageModel/Module/BinaryModule.swift +++ b/Sources/PackageModel/Module/BinaryModule.swift @@ -55,14 +55,23 @@ public final class BinaryModule: Module { public enum Kind: String, RawRepresentable, CaseIterable { case xcframework - case artifactsArchive + + /// Artifact bundles containing libraries. + case libraryArchive + + /// Artifact bundles containing executables. + case executableArchive + case unknown // for non-downloaded artifacts + @available(*, deprecated, renamed: "executableArchive", message: "Renamed to clearly distinguish from `case libraryArchive`") + public static let artifactsArchive = Self.executableArchive + public var fileExtension: String { switch self { case .xcframework: return "xcframework" - case .artifactsArchive: + case .executableArchive, .libraryArchive: return "artifactbundle" case .unknown: return "unknown" @@ -71,8 +80,7 @@ public final class BinaryModule: Module { } public var containsExecutable: Bool { - // FIXME: needs to be revisited once libraries are supported in artifact bundles - return self.kind == .artifactsArchive + return self.kind == .executableArchive } public enum Origin: Equatable { diff --git a/Sources/PackageModel/Target/BinaryTarget.swift b/Sources/PackageModel/Target/BinaryTarget.swift deleted file mode 100644 index 819179a8300..00000000000 --- a/Sources/PackageModel/Target/BinaryTarget.swift +++ /dev/null @@ -1,139 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 struct Basics.AbsolutePath - -public final class BinaryTarget: Target { - /// The kind of binary artifact. - public let kind: Kind - - /// The original source of the binary artifact. - public let origin: Origin - - /// The binary artifact path. - public var artifactPath: AbsolutePath { - return self.sources.root - } - - public init( - name: String, - kind: Kind, - path: AbsolutePath, - origin: Origin - ) { - self.origin = origin - self.kind = kind - let sources = Sources(paths: [], root: path) - super.init( - name: name, - type: .binary, - path: .root, - sources: sources, - dependencies: [], - packageAccess: false, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case kind - case origin - case artifactSource // backwards compatibility 2/2021 - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.origin, forKey: .origin) - try container.encode(self.kind, forKey: .kind) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - // backwards compatibility 2/2021 - if !container.contains(.kind) { - self.kind = .xcframework - } else { - self.kind = try container.decode(Kind.self, forKey: .kind) - } - // backwards compatibility 2/2021 - if container.contains(.artifactSource) { - self.origin = try container.decode(Origin.self, forKey: .artifactSource) - } else { - self.origin = try container.decode(Origin.self, forKey: .origin) - } - try super.init(from: decoder) - } - - public enum Kind: String, RawRepresentable, Codable, CaseIterable { - case xcframework - case libraryArchive - case artifactsArchive - case unknown // for non-downloaded artifacts - - public var fileExtension: String { - switch self { - case .xcframework: - return "xcframework" - case .artifactsArchive, .libraryArchive: - return "artifactbundle" - case .unknown: - return "unknown" - } - } - } - - public var containsExecutable: Bool { - // FIXME: needs to be revisited once libraries are supported in artifact bundles - return self.kind == .artifactsArchive - } - - public enum Origin: Equatable, Codable { - - /// Represents an artifact that was downloaded from a remote URL. - case remote(url: String) - - /// Represents an artifact that was available locally. - case local - - private enum CodingKeys: String, CodingKey { - case remote, local - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .remote(let a1): - var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .remote) - try unkeyedContainer.encode(a1) - case .local: - try container.encodeNil(forKey: .local) - } - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - guard let key = values.allKeys.first(where: values.contains) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) - } - switch key { - case .remote: - var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) - let a1 = try unkeyedValues.decode(String.self) - self = .remote(url: a1) - case .local: - self = .local - } - } - } -} diff --git a/Sources/PackageModel/Target/ClangTarget.swift b/Sources/PackageModel/Target/ClangTarget.swift deleted file mode 100644 index d38f559a40d..00000000000 --- a/Sources/PackageModel/Target/ClangTarget.swift +++ /dev/null @@ -1,109 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 struct Basics.AbsolutePath -import struct Basics.StringError - -public final class ClangTarget: Target { - /// The default public include directory component. - public static let defaultPublicHeadersComponent = "include" - - /// The path to include directory. - public let includeDir: 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 - - /// The headers present in the target. - /// - /// Note that this contains both public and non-public headers. - public let headers: [AbsolutePath] - - /// True if this is a C++ target. - public let isCXX: Bool - - /// The C language standard flag. - public let cLanguageStandard: String? - - /// The C++ language standard flag. - public let cxxLanguageStandard: String? - - public init( - name: String, - potentialBundleName: String? = nil, - cLanguageStandard: String?, - cxxLanguageStandard: String?, - includeDir: AbsolutePath, - moduleMapType: ModuleMapType, - headers: [AbsolutePath] = [], - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - buildSettings: BuildSettings.AssignmentTable = .init(), - usesUnsafeFlags: Bool - ) throws { - guard includeDir.isDescendantOfOrEqual(to: sources.root) else { - throw StringError("\(includeDir) should be contained in the source root \(sources.root)") - } - self.isCXX = sources.containsCXXFiles - self.cLanguageStandard = cLanguageStandard - self.cxxLanguageStandard = cxxLanguageStandard - self.includeDir = includeDir - self.moduleMapType = moduleMapType - self.headers = headers - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: false, - buildSettings: buildSettings, - pluginUsages: [], - usesUnsafeFlags: usesUnsafeFlags - ) - } - - private enum CodingKeys: String, CodingKey { - case includeDir, moduleMapType, headers, isCXX, cLanguageStandard, cxxLanguageStandard - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(includeDir, forKey: .includeDir) - try container.encode(moduleMapType, forKey: .moduleMapType) - try container.encode(headers, forKey: .headers) - try container.encode(isCXX, forKey: .isCXX) - try container.encode(cLanguageStandard, forKey: .cLanguageStandard) - try container.encode(cxxLanguageStandard, forKey: .cxxLanguageStandard) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.includeDir = try container.decode(AbsolutePath.self, forKey: .includeDir) - self.moduleMapType = try container.decode(ModuleMapType.self, forKey: .moduleMapType) - self.headers = try container.decode([AbsolutePath].self, forKey: .headers) - self.isCXX = try container.decode(Bool.self, forKey: .isCXX) - self.cLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cLanguageStandard) - self.cxxLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cxxLanguageStandard) - try super.init(from: decoder) - } -} diff --git a/Sources/PackageModel/Target/PluginTarget.swift b/Sources/PackageModel/Target/PluginTarget.swift deleted file mode 100644 index 4e5ddb874c3..00000000000 --- a/Sources/PackageModel/Target/PluginTarget.swift +++ /dev/null @@ -1,174 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 -// -//===----------------------------------------------------------------------===// - -public final class PluginTarget: Target { - /// Declared capability of the plugin. - public let capability: PluginCapability - - /// API version to use for PackagePlugin API availability. - public let apiVersion: ToolsVersion - - public init( - name: String, - sources: Sources, - apiVersion: ToolsVersion, - pluginCapability: PluginCapability, - dependencies: [Target.Dependency] = [], - packageAccess: Bool - ) { - self.capability = pluginCapability - self.apiVersion = apiVersion - super.init( - name: name, - type: .plugin, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case capability - case apiVersion - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.capability, forKey: .capability) - try container.encode(self.apiVersion, forKey: .apiVersion) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.capability = try container.decode(PluginCapability.self, forKey: .capability) - self.apiVersion = try container.decode(ToolsVersion.self, forKey: .apiVersion) - try super.init(from: decoder) - } -} - -public enum PluginCapability: Hashable, Codable { - case buildTool - case command(intent: PluginCommandIntent, permissions: [PluginPermission]) - - private enum CodingKeys: String, CodingKey { - case buildTool, command - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self { - case .buildTool: - try container.encodeNil(forKey: .buildTool) - case .command(let a1, let a2): - var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .command) - try unkeyedContainer.encode(a1) - try unkeyedContainer.encode(a2) - } - } - - public init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - guard let key = values.allKeys.first(where: values.contains) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Did not find a matching key")) - } - switch key { - case .buildTool: - self = .buildTool - case .command: - var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) - let a1 = try unkeyedValues.decode(PluginCommandIntent.self) - let a2 = try unkeyedValues.decode([PluginPermission].self) - self = .command(intent: a1, permissions: a2) - } - } - - public init(from desc: TargetDescription.PluginCapability) { - switch desc { - case .buildTool: - self = .buildTool - case .command(let intent, let permissions): - self = .command(intent: .init(from: intent), permissions: permissions.map{ .init(from: $0) }) - } - } -} - -public enum PluginCommandIntent: Hashable, Codable { - case documentationGeneration - case sourceCodeFormatting - case custom(verb: String, description: String) - - public init(from desc: TargetDescription.PluginCommandIntent) { - switch desc { - case .documentationGeneration: - self = .documentationGeneration - case .sourceCodeFormatting: - self = .sourceCodeFormatting - case .custom(let verb, let description): - self = .custom(verb: verb, description: description) - } - } -} - -public enum PluginNetworkPermissionScope: Hashable, Codable { - case none - case local(ports: [Int]) - case all(ports: [Int]) - case docker - case unixDomainSocket - - init(_ scope: TargetDescription.PluginNetworkPermissionScope) { - switch scope { - case .none: self = .none - case .local(let ports): self = .local(ports: ports) - case .all(let ports): self = .all(ports: ports) - case .docker: self = .docker - case .unixDomainSocket: self = .unixDomainSocket - } - } - - public var label: String { - switch self { - case .all: return "all" - case .local: return "local" - case .none: return "none" - case .docker: return "docker unix domain socket" - case .unixDomainSocket: return "unix domain socket" - } - } - - public var ports: [Int] { - switch self { - case .all(let ports): return ports - case .local(let ports): return ports - case .none, .docker, .unixDomainSocket: return [] - } - } -} - -public enum PluginPermission: Hashable, Codable { - case allowNetworkConnections(scope: PluginNetworkPermissionScope, reason: String) - case writeToPackageDirectory(reason: String) - - public init(from desc: TargetDescription.PluginPermission) { - switch desc { - case .allowNetworkConnections(let scope, let reason): - self = .allowNetworkConnections(scope: .init(scope), reason: reason) - case .writeToPackageDirectory(let reason): - self = .writeToPackageDirectory(reason: reason) - } - } -} diff --git a/Sources/PackageModel/Target/SwiftTarget.swift b/Sources/PackageModel/Target/SwiftTarget.swift deleted file mode 100644 index f23f79db6fe..00000000000 --- a/Sources/PackageModel/Target/SwiftTarget.swift +++ /dev/null @@ -1,132 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 struct Basics.AbsolutePath -import struct Basics.SwiftVersion - -public final class SwiftTarget: Target { - /// The default name for the test entry point file located in a package. - public static let defaultTestEntryPointName = "XCTMain.swift" - - /// The list of all supported names for the test entry point file located in a package. - public static var testEntryPointNames: [String] { - [defaultTestEntryPointName, "LinuxMain.swift"] - } - - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { - self.swiftVersion = .v5 - - super.init( - name: name, - type: .library, - path: .root, - sources: testDiscoverySrc, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - /// The swift version of this target. - public let swiftVersion: SwiftLanguageVersion - - public init( - name: String, - potentialBundleName: String? = nil, - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency] = [], - packageAccess: Bool, - swiftVersion: SwiftLanguageVersion, - buildSettings: BuildSettings.AssignmentTable = .init(), - pluginUsages: [PluginUsage] = [], - usesUnsafeFlags: Bool - ) { - self.swiftVersion = swiftVersion - super.init( - name: name, - potentialBundleName: potentialBundleName, - type: type, - path: path, - sources: sources, - resources: resources, - ignored: ignored, - others: others, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: buildSettings, - pluginUsages: pluginUsages, - usesUnsafeFlags: usesUnsafeFlags - ) - } - - /// Create an executable Swift target from test entry point file. - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testEntryPointPath: AbsolutePath) { - // Look for the first swift test target and use the same swift version - // for linux main target. This will need to change if we move to a model - // where we allow per target swift language version build settings. - let swiftTestTarget = dependencies.first { - guard case .target(let target as SwiftTarget, _) = $0 else { return false } - return target.type == .test - }.flatMap { $0.target as? SwiftTarget } - - // FIXME: This is not very correct but doesn't matter much in practice. - // We need to select the latest Swift language version that can - // satisfy the current tools version but there is not a good way to - // do that currently. - self.swiftVersion = swiftTestTarget?.swiftVersion ?? SwiftLanguageVersion(string: String(SwiftVersion.current.major)) ?? .v4 - let sources = Sources(paths: [testEntryPointPath], root: testEntryPointPath.parentDirectory) - - super.init( - name: name, - type: .executable, - path: .root, - sources: sources, - dependencies: dependencies, - packageAccess: packageAccess, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case swiftVersion - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(swiftVersion, forKey: .swiftVersion) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.swiftVersion = try container.decode(SwiftLanguageVersion.self, forKey: .swiftVersion) - try super.init(from: decoder) - } - - public var supportsTestableExecutablesFeature: Bool { - // Exclude macros from testable executables if they are built as dylibs. - #if BUILD_MACROS_AS_DYLIBS - return type == .executable || type == .snippet - #else - return type == .executable || type == .macro || type == .snippet - #endif - } -} diff --git a/Sources/PackageModel/Target/SystemLibraryTarget.swift b/Sources/PackageModel/Target/SystemLibraryTarget.swift deleted file mode 100644 index beb99b670e5..00000000000 --- a/Sources/PackageModel/Target/SystemLibraryTarget.swift +++ /dev/null @@ -1,70 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 struct Basics.AbsolutePath - -public final class SystemLibraryTarget: Target { - - /// The name of pkgConfig file, if any. - public let pkgConfig: String? - - /// List of system package providers, if any. - public let providers: [SystemPackageProviderDescription]? - - /// True if this system library should become implicit target - /// dependency of its dependent packages. - public let isImplicit: Bool - - public init( - name: String, - path: AbsolutePath, - isImplicit: Bool = true, - pkgConfig: String? = nil, - providers: [SystemPackageProviderDescription]? = nil - ) { - let sources = Sources(paths: [], root: path) - self.pkgConfig = pkgConfig - self.providers = providers - self.isImplicit = isImplicit - super.init( - name: name, - type: .systemModule, - path: sources.root, - sources: sources, - dependencies: [], - packageAccess: false, - buildSettings: .init(), - pluginUsages: [], - usesUnsafeFlags: false - ) - } - - private enum CodingKeys: String, CodingKey { - case pkgConfig, providers, isImplicit - } - - public override func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(pkgConfig, forKey: .pkgConfig) - try container.encode(providers, forKey: .providers) - try container.encode(isImplicit, forKey: .isImplicit) - try super.encode(to: encoder) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.pkgConfig = try container.decodeIfPresent(String.self, forKey: .pkgConfig) - self.providers = try container.decodeIfPresent([SystemPackageProviderDescription].self, forKey: .providers) - self.isImplicit = try container.decode(Bool.self, forKey: .isImplicit) - try super.init(from: decoder) - } -} diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift deleted file mode 100644 index c6c767dee4e..00000000000 --- a/Sources/PackageModel/Target/Target.swift +++ /dev/null @@ -1,350 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift open source project -// -// Copyright (c) 2014-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 Basics -import Dispatch - -import protocol TSCUtility.PolymorphicCodableProtocol - -public class Target: PolymorphicCodableProtocol { - public static var implementations: [PolymorphicCodableProtocol.Type] = [ - SwiftTarget.self, - ClangTarget.self, - SystemLibraryTarget.self, - BinaryTarget.self, - PluginTarget.self, - ] - - /// The target kind. - public enum Kind: String, Codable { - case executable - case library - case systemModule = "system-target" - case test - case binary - case plugin - case snippet - case `macro` - } - - /// A group a target belongs to that allows customizing access boundaries. A target is treated as - /// a client outside of the package if `excluded`, inside the package boundary if `package`. - public enum Group: Codable, Equatable { - case package - case excluded - } - /// A reference to a product from a target dependency. - public struct ProductReference: Codable { - - /// The name of the product dependency. - public let name: String - - /// The name of the package containing the product. - public let package: String? - - /// Module aliases for targets of this product dependency. The key is an - /// original target name and the value is a new unique name that also - /// becomes the name of its .swiftmodule binary. - public let moduleAliases: [String: String]? - - /// Fully qualified name for this product dependency: package ID + name of the product - public var identity: String { - if let package { - return package.lowercased() + "_" + name - } else { - // this is hit only if this product is referenced `.byName(name)` - // which assumes the name of this product, its package, and its target - // all have the same name - return name.lowercased() + "_" + name - } - } - - /// Creates a product reference instance. - public init(name: String, package: String?, moduleAliases: [String: String]? = nil) { - self.name = name - self.package = package - self.moduleAliases = moduleAliases - } - } - - /// A target dependency to a target or product. - public enum Dependency { - /// A dependency referencing another target, with conditions. - case target(_ target: Target, conditions: [PackageConditionProtocol]) - - /// A dependency referencing a product, with conditions. - case product(_ product: ProductReference, conditions: [PackageConditionProtocol]) - - /// The target if the dependency is a target dependency. - public var target: Target? { - if case .target(let target, _) = self { - return target - } else { - return nil - } - } - - /// The product reference if the dependency is a product dependency. - public var product: ProductReference? { - if case .product(let product, _) = self { - return product - } else { - return nil - } - } - - /// The dependency conditions. - public var conditions: [PackageConditionProtocol] { - switch self { - case .target(_, let conditions): - return conditions - case .product(_, let conditions): - return conditions - } - } - - /// The name of the target or product of the dependency. - public var name: String { - switch self { - case .target(let target, _): - return target.name - case .product(let product, _): - return product.name - } - } - } - - /// A usage of a plugin target or product. Implemented as a dependency - /// for now and added to the `dependencies` array, since they currently - /// have exactly the same characteristics and to avoid duplicating the - /// implementation for now. - public typealias PluginUsage = Dependency - - /// The name of the target. - /// - /// NOTE: This name is not the language-level target (i.e., the importable - /// name) name in many cases, instead use c99name if you need uniqueness. - public private(set) var name: String - - /// Module aliases needed to build this target. The key is an original name of a - /// dependent target and the value is a new unique name mapped to the name - /// of its .swiftmodule binary. - public private(set) var moduleAliases: [String: String]? - /// Used to store pre-chained / pre-overriden module aliases - public private(set) var prechainModuleAliases: [String: String]? - /// Used to store aliases that should be referenced directly in source code - public private(set) var directRefAliases: [String: [String]]? - - /// Add module aliases (if applicable) for dependencies of this target. - /// - /// For example, adding an alias `Bar` for a target name `Foo` will result in - /// compiling references to `Foo` in source code of this target as `Bar.swiftmodule`. - /// If the name argument `Foo` is the same as this target's name, this target will be - /// renamed as `Bar` and the resulting binary will be `Bar.swiftmodule`. - /// - /// - Parameters: - /// - name: The original name of a dependent target or this target - /// - alias: A new unique name mapped to the resulting binary name - public func addModuleAlias(for name: String, as alias: String) { - if moduleAliases == nil { - moduleAliases = [name: alias] - } else { - moduleAliases?[name] = alias - } - } - - public func removeModuleAlias(for name: String) { - moduleAliases?.removeValue(forKey: name) - if moduleAliases?.isEmpty ?? false { - moduleAliases = nil - } - } - - public func addPrechainModuleAlias(for name: String, as alias: String) { - if prechainModuleAliases == nil { - prechainModuleAliases = [name: alias] - } else { - prechainModuleAliases?[name] = alias - } - } - public func addDirectRefAliases(for name: String, as aliases: [String]) { - if directRefAliases == nil { - directRefAliases = [name: aliases] - } else { - directRefAliases?[name] = aliases - } - } - - @discardableResult - public func applyAlias() -> Bool { - // If there's an alias for this target, rename - if let alias = moduleAliases?[name] { - self.name = alias - self.c99name = alias.spm_mangledToC99ExtendedIdentifier() - return true - } - return false - } - - /// The dependencies of this target. - public let dependencies: [Dependency] - - /// The language-level target name. - public private(set) var c99name: String - - /// The bundle name, if one is being generated. - public var bundleName: String? { - return resources.isEmpty ? nil : potentialBundleName - } - public let potentialBundleName: String? - - /// Suffix that's expected for test targets. - public static let testModuleNameSuffix = "Tests" - - /// The kind of target. - public let type: Kind - - /// If true, access to package declarations from other targets is allowed. - public let packageAccess: Bool - - /// The path of the target. - public let path: AbsolutePath - - /// The sources for the target. - public let sources: Sources - - /// The resource files in the target. - public let resources: [Resource] - - /// Files in the target that were marked as ignored. - public let ignored: [AbsolutePath] - - /// Other kinds of files in the target. - public let others: [AbsolutePath] - - /// The build settings assignments of this target. - public let buildSettings: BuildSettings.AssignmentTable - - /// The usages of package plugins by this target. - public let pluginUsages: [PluginUsage] - - /// Whether or not this target uses any custom unsafe flags. - public let usesUnsafeFlags: Bool - - init( - name: String, - potentialBundleName: String? = nil, - type: Kind, - path: AbsolutePath, - sources: Sources, - resources: [Resource] = [], - ignored: [AbsolutePath] = [], - others: [AbsolutePath] = [], - dependencies: [Target.Dependency], - packageAccess: Bool, - buildSettings: BuildSettings.AssignmentTable, - pluginUsages: [PluginUsage], - usesUnsafeFlags: Bool - ) { - self.name = name - self.potentialBundleName = potentialBundleName - self.type = type - self.path = path - self.sources = sources - self.resources = resources - self.ignored = ignored - self.others = others - self.dependencies = dependencies - self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() - self.packageAccess = packageAccess - self.buildSettings = buildSettings - self.pluginUsages = pluginUsages - self.usesUnsafeFlags = usesUnsafeFlags - } - - private enum CodingKeys: String, CodingKey { - case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, packageAccess, buildSettings, pluginUsages, usesUnsafeFlags - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - // FIXME: dependencies property is skipped on purpose as it points to - // the actual target dependency object. - try container.encode(name, forKey: .name) - try container.encode(potentialBundleName, forKey: .potentialBundleName) - try container.encode(type, forKey: .type) - try container.encode(path, forKey: .path) - try container.encode(sources, forKey: .sources) - try container.encode(resources, forKey: .resources) - try container.encode(ignored, forKey: .ignored) - try container.encode(others, forKey: .others) - try container.encode(packageAccess, forKey: .packageAccess) - try container.encode(buildSettings, forKey: .buildSettings) - // FIXME: pluginUsages property is skipped on purpose as it points to - // the actual target dependency object. - try container.encode(usesUnsafeFlags, forKey: .usesUnsafeFlags) - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.potentialBundleName = try container.decodeIfPresent(String.self, forKey: .potentialBundleName) - self.type = try container.decode(Kind.self, forKey: .type) - self.path = try container.decode(AbsolutePath.self, forKey: .path) - self.sources = try container.decode(Sources.self, forKey: .sources) - self.resources = try container.decode([Resource].self, forKey: .resources) - self.ignored = try container.decode([AbsolutePath].self, forKey: .ignored) - self.others = try container.decode([AbsolutePath].self, forKey: .others) - // FIXME: dependencies property is skipped on purpose as it points to - // the actual target dependency object. - self.dependencies = [] - self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() - self.packageAccess = try container.decode(Bool.self, forKey: .packageAccess) - self.buildSettings = try container.decode(BuildSettings.AssignmentTable.self, forKey: .buildSettings) - // FIXME: pluginUsages property is skipped on purpose as it points to - // the actual target dependency object. - self.pluginUsages = [] - self.usesUnsafeFlags = try container.decode(Bool.self, forKey: .usesUnsafeFlags) - } -} - -extension Target: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } - - public static func == (lhs: Target, rhs: Target) -> Bool { - ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } -} - -extension Target: CustomStringConvertible { - public var description: String { - return "<\(Swift.type(of: self)): \(name)>" - } -} - -public extension Sequence where Iterator.Element == Target { - var executables: [Target] { - return filter { - switch $0.type { - case .binary: - return ($0 as? BinaryTarget)?.containsExecutable == true - case .executable, .snippet, .macro: - return true - default: - return false - } - } - } -} diff --git a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index 550541d5a36..a652378959e 100644 --- a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -152,7 +152,7 @@ internal struct PluginContextSerializer { case let target as BinaryModule: let artifactKind: WireInput.Target.TargetInfo.BinaryArtifactKind switch target.kind { - case .artifactsArchive: + case .executableArchive: artifactKind = .artifactsArchive case .libraryArchive: artifactKind = .libraryArchive diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index a2720cd2a95..d86ef9ecad7 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -686,8 +686,14 @@ fileprivate func collectAccessibleTools( // For a binary target we create a `vendedTool`. if let module = executableOrBinaryModule as? BinaryModule { // TODO: Memoize this result for the host triple - let execInfos = try module.parseArtifactArchives(for: hostTriple, fileSystem: fileSystem) - return try execInfos.map{ .vendedTool(name: $0.name, path: $0.executablePath, supportedTriples: try $0.supportedTriples.map{ try $0.withoutVersion().tripleString }) } + let execInfos = try module.parseExecutableArtifactArchives(for: hostTriple, fileSystem: fileSystem) + return try execInfos.map { + .vendedTool( + name: $0.name, + path: $0.executablePath, + supportedTriples: try $0.supportedTriples.map { try $0.withoutVersion().tripleString } + ) + } } // For an executable target we create a `builtTool`. else if executableOrBinaryModule.type == .executable { diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index da610a5b77a..75508db1431 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -434,7 +434,7 @@ extension WorkspaceStateStorage { switch underlying { case .xcframework: self = .xcframework - case .artifactsArchive: + case .executableArchive: self = .artifactsArchive case .libraryArchive: self = .libraryArchive @@ -448,7 +448,9 @@ extension WorkspaceStateStorage { case .xcframework: return .xcframework case .artifactsArchive: - return .artifactsArchive + return .executableArchive + case .libraryArchive: + return .libraryArchive case .unknown: return .unknown } @@ -814,14 +816,17 @@ extension WorkspaceStateStorage { enum Kind: String, Codable { case xcframework case artifactsArchive + case libraryArchive case unknown init(_ underlying: BinaryModule.Kind) { switch underlying { case .xcframework: self = .xcframework - case .artifactsArchive: + case .executableArchive: self = .artifactsArchive + case .libraryArchive: + self = .libraryArchive case .unknown: self = .unknown } @@ -832,7 +837,7 @@ extension WorkspaceStateStorage { case .xcframework: return .xcframework case .artifactsArchive: - return .artifactsArchive + return .executableArchive case .libraryArchive: return .libraryArchive case .unknown: From fd9406e966233d7237917a32485db0a0799ed3fb Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Apr 2025 17:08:05 +0100 Subject: [PATCH 4/6] Apply suggestions from code review --- Sources/Build/BuildPlan/BuildPlan+Product.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index ecbad1a916c..75bd42c8d0e 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -341,16 +341,16 @@ extension BuildPlan { } /// Extracts the artifacts from an artifactsArchive - private func parseExecutableArtifactsArchive(for binaryTarget: BinaryModule, triple: Triple) throws -> [ExecutableInfo] { + private func parseExecutableArtifactsArchive(for module: BinaryModule, triple: Triple) throws -> [ExecutableInfo] { try self.externalExecutablesCache.memoize(key: binaryTarget) { - let execInfos = try binaryTarget.parseExecutableArtifactArchives(for: triple, fileSystem: self.fileSystem) + let execInfos = try module.parseExecutableArtifactArchives(for: triple, fileSystem: self.fileSystem) return execInfos.filter { !$0.supportedTriples.isEmpty } } } - private func parseLibraryArtifactsArchive(for target: BinaryModule, triple: Triple) throws -> [LibraryInfo] { - try self.externalLibrariesCache.memoize(key: target) { - try target.parseLibraryArtifactArchives(for: triple, fileSystem: self.fileSystem) + private func parseLibraryArtifactsArchive(for module: BinaryModule, triple: Triple) throws -> [LibraryInfo] { + try self.externalLibrariesCache.memoize(key: module) { + try module.parseLibraryArtifactArchives(for: triple, fileSystem: self.fileSystem) } } } From 2574ecd39b08a48002b50408f7db0472b2abc805 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Apr 2025 17:08:43 +0100 Subject: [PATCH 5/6] Fix incorrect argument name --- Sources/Build/BuildPlan/BuildPlan+Product.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index 75bd42c8d0e..fc37d09a697 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -342,7 +342,7 @@ extension BuildPlan { /// Extracts the artifacts from an artifactsArchive private func parseExecutableArtifactsArchive(for module: BinaryModule, triple: Triple) throws -> [ExecutableInfo] { - try self.externalExecutablesCache.memoize(key: binaryTarget) { + try self.externalExecutablesCache.memoize(key: module) { let execInfos = try module.parseExecutableArtifactArchives(for: triple, fileSystem: self.fileSystem) return execInfos.filter { !$0.supportedTriples.isEmpty } } From eee424dc373132d457afeacb89a89f7e954de193 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Apr 2025 17:35:19 +0100 Subject: [PATCH 6/6] Fix failing tests --- Sources/PackageLoading/ManifestLoader+Validation.swift | 4 ++-- Sources/PackageModel/ArtifactsArchiveMetadata.swift | 2 +- Sources/Workspace/Workspace+BinaryArtifacts.swift | 9 +++++++-- Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift | 8 ++++---- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Sources/PackageLoading/ManifestLoader+Validation.swift b/Sources/PackageLoading/ManifestLoader+Validation.swift index 285736c1198..4d63aace922 100644 --- a/Sources/PackageLoading/ManifestLoader+Validation.swift +++ b/Sources/PackageLoading/ManifestLoader+Validation.swift @@ -16,10 +16,10 @@ import PackageModel public struct ManifestValidator { static var supportedLocalBinaryDependencyExtensions: [String] { - ["zip"] + BinaryModule.Kind.allCases.filter{ $0 != .unknown }.map { $0.fileExtension } + Set(["zip"] + BinaryModule.Kind.allCases.filter { $0 != .unknown }.map { $0.fileExtension }).sorted() } static var supportedRemoteBinaryDependencyExtensions: [String] { - ["zip", "artifactbundleindex"] + ["artifactbundleindex", "zip"] } private let manifest: Manifest diff --git a/Sources/PackageModel/ArtifactsArchiveMetadata.swift b/Sources/PackageModel/ArtifactsArchiveMetadata.swift index 0f90bc146d8..dfdb6ac1215 100644 --- a/Sources/PackageModel/ArtifactsArchiveMetadata.swift +++ b/Sources/PackageModel/ArtifactsArchiveMetadata.swift @@ -134,7 +134,7 @@ extension ArtifactsArchiveMetadata.Variant: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) self.supportedTriples = try container.decodeIfPresent([String].self, forKey: .supportedTriples)?.map { try Triple($0) } self.path = try RelativePath(validating: container.decode(String.self, forKey: .path)) - self.libraryMetadata = try container.decode( + self.libraryMetadata = try container.decodeIfPresent( ArtifactsArchiveMetadata.LibraryMetadata.self, forKey: .libraryMetadata ) diff --git a/Sources/Workspace/Workspace+BinaryArtifacts.swift b/Sources/Workspace/Workspace+BinaryArtifacts.swift index 0da1a610945..80c03fe77e2 100644 --- a/Sources/Workspace/Workspace+BinaryArtifacts.swift +++ b/Sources/Workspace/Workspace+BinaryArtifacts.swift @@ -781,8 +781,13 @@ extension Workspace.BinaryArtifactsManager { if let infoJSON = files.first(where: { $0.basename.lowercased() == "info.json" }) { do { - _ = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: infoJSON.parentDirectory) - return .libraryArchive + let metadata = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: infoJSON.parentDirectory) + // FIXME: needs to distinguish archive kinds more reliably. + if metadata.artifacts.values.flatMap(\.variants).contains(where: { $0.libraryMetadata != nil }) { + return .libraryArchive + } else { + return .executableArchive + } } catch { observabilityScope.emit( debug: "info.json found in '\(path)' but failed to parse", diff --git a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift index 27bbab812d4..7dfb0e442d0 100644 --- a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift @@ -268,7 +268,7 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in - result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'xcframework', 'artifactbundle'", severity: .error) + result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'artifactbundle', 'xcframework', 'zip'", severity: .error) } } @@ -293,7 +293,7 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in - result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'artifactbundleindex'", severity: .error) + result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'artifactbundleindex', 'zip'", severity: .error) } } @@ -315,7 +315,7 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in - result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'xcframework', 'artifactbundle'", severity: .error) + result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'artifactbundle', 'xcframework', 'zip'", severity: .error) } } @@ -340,7 +340,7 @@ final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in - result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'artifactbundleindex'", severity: .error) + result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'artifactbundleindex', 'zip'", severity: .error) } }