Skip to content

DNM/WIP: support libraries in .artifactbundles #6967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions Sources/Build/BuildPlan/BuildPlan+Product.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -330,10 +341,16 @@ extension BuildPlan {
}

/// Extracts the artifacts from an artifactsArchive
private func parseArtifactsArchive(for binaryTarget: BinaryModule, triple: Triple) throws -> [ExecutableInfo] {
try self.externalExecutablesCache.memoize(key: binaryTarget) {
let execInfos = try binaryTarget.parseArtifactArchives(for: triple, fileSystem: self.fileSystem)
private func parseExecutableArtifactsArchive(for module: BinaryModule, triple: Triple) throws -> [ExecutableInfo] {
try self.externalExecutablesCache.memoize(key: module) {
let execInfos = try module.parseExecutableArtifactArchives(for: triple, fileSystem: self.fileSystem)
return execInfos.filter { !$0.supportedTriples.isEmpty }
}
}

private func parseLibraryArtifactsArchive(for module: BinaryModule, triple: Triple) throws -> [LibraryInfo] {
try self.externalLibrariesCache.memoize(key: module) {
try module.parseLibraryArtifactArchives(for: triple, fileSystem: self.fileSystem)
}
}
}
2 changes: 1 addition & 1 deletion Sources/Build/BuildPlan/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]]()
Expand Down
4 changes: 2 additions & 2 deletions Sources/PackageLoading/ManifestLoader+Validation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 21 additions & 6 deletions Sources/PackageModel/ArtifactsArchiveMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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]) {
Expand All @@ -39,11 +39,12 @@ 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
case swiftSDK

// Can't be marked as formally deprecated as we still need to use this value for warning users.
Expand All @@ -53,12 +54,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 {
Expand All @@ -78,15 +86,17 @@ extension ArtifactsArchiveMetadata {
)

switch (version.major, version.minor) {
case (1, 1), (1, 0):
case (1, 2), (1, 1), (1, 0):
return decodedMetadata
default:
throw StringError(
"invalid `schemaVersion` of bundle manifest at `\(path)`: \(decodedMetadata.schemaVersion)"
)
}
} catch {
throw StringError("failed parsing ArtifactsArchive info.json at '\(path)': \(error.interpolationDescription)")
throw StringError(
"failed parsing ArtifactsArchive info.json at '\(path)': \(error.interpolationDescription)"
)
}
}
}
Expand Down Expand Up @@ -117,11 +127,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.decodeIfPresent([String].self, forKey: .supportedTriples)?.map { try Triple($0) }
self.path = try RelativePath(validating: container.decode(String.self, forKey: .path))
self.libraryMetadata = try container.decodeIfPresent(
ArtifactsArchiveMetadata.LibraryMetadata.self,
forKey: .libraryMetadata
)
}
}
16 changes: 12 additions & 4 deletions Sources/PackageModel/Module/BinaryModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions Sources/PackagePlugin/PackageModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ public struct BinaryArtifactTarget: Target {
public enum Kind {
case xcframework
case artifactsArchive
case libraryArchive
}

// Represents the original location of a binary artifact.
Expand Down
2 changes: 2 additions & 0 deletions Sources/PackagePlugin/PluginContextDeserializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ internal struct PluginContextDeserializer {
switch kind {
case .artifactsArchive:
artifactKind = .artifactsArchive
case .libraryArchive:
artifactKind = .libraryArchive
case .xcframework:
artifactKind = .xcframework
}
Expand Down
1 change: 1 addition & 0 deletions Sources/PackagePlugin/PluginMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ enum HostToPluginMessage: Codable {

enum BinaryArtifactKind: Codable {
case xcframework
case libraryArchive
case artifactsArchive
}

Expand Down
30 changes: 26 additions & 4 deletions Sources/SPMBuildCore/BinaryTarget+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -53,16 +56,20 @@ extension BinaryModule {
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
Expand All @@ -82,6 +89,21 @@ extension BinaryModule {
}
}
}

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,10 @@ 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
case .xcframework:
artifactKind = .xcframework
case .unknown:
Expand Down
10 changes: 8 additions & 2 deletions Sources/SPMBuildCore/Plugins/PluginInvocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 7 additions & 2 deletions Sources/Workspace/Workspace+BinaryArtifacts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 .artifactsArchive
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",
Expand Down
18 changes: 14 additions & 4 deletions Sources/Workspace/Workspace+State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -427,14 +427,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
}
Expand All @@ -445,7 +448,9 @@ extension WorkspaceStateStorage {
case .xcframework:
return .xcframework
case .artifactsArchive:
return .artifactsArchive
return .executableArchive
case .libraryArchive:
return .libraryArchive
case .unknown:
return .unknown
}
Expand Down Expand Up @@ -811,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
}
Expand All @@ -829,7 +837,9 @@ extension WorkspaceStateStorage {
case .xcframework:
return .xcframework
case .artifactsArchive:
return .artifactsArchive
return .executableArchive
case .libraryArchive:
return .libraryArchive
case .unknown:
return .unknown
}
Expand Down
Loading