Skip to content

Commit 32c49a3

Browse files
authored
add artifacts archive file format (#3295)
motivation: to support the package extensions feature, we need to support binary dependencies with tools that can be distributed across different platforms changes: * introduce new binary dependency type "artifactsArchive" with the extension "arar" * the structure of the archive is inspired by XCFramework, and it can contain multiple tools/executables targeting multiple platforms and architectures using target triples to indicate such support * update BuildPlan code to parse "arar" files and extract the tools available to the target in a [String: AbsolutePath] which * can be later hooks to be used by extensions * refactor XCFramework code * update call-sites and tests and add new tests
1 parent 4d9d2c8 commit 32c49a3

10 files changed

+476
-201
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Foundation
12+
import PackageModel
13+
import SPMBuildCore
14+
import TSCBasic
15+
import TSCUtility
16+
17+
public struct ArtifactsArchiveMetadata: Equatable {
18+
public let schemaVersion: String
19+
public let artifacts: [String: Artifact]
20+
21+
public init(schemaVersion: String, artifacts: [String: Artifact]) {
22+
self.schemaVersion = schemaVersion
23+
self.artifacts = artifacts
24+
}
25+
26+
public struct Artifact: Equatable {
27+
let type: ArtifactType
28+
let version: String
29+
let variants: [Variant]
30+
31+
public init(type: ArtifactsArchiveMetadata.ArtifactType, version: String, variants: [Variant]) {
32+
self.type = type
33+
self.version = version
34+
self.variants = variants
35+
}
36+
}
37+
38+
// In the future we are likely to extend the ArtifactsArchive file format to carry other types of artifacts beyond executables.
39+
// Additional fields may be required to support these new artifact types e.g. headers path for libraries.
40+
// This can also support resource-only artifacts as well. For example, 3d models along with associated textures, or fonts, etc.
41+
public enum ArtifactType: String, RawRepresentable, Decodable {
42+
case executable
43+
}
44+
45+
public struct Variant: Equatable {
46+
let path: String
47+
let supportedTriples: [Triple]
48+
49+
public init(path: String, supportedTriples: [Triple]) {
50+
self.path = path
51+
self.supportedTriples = supportedTriples
52+
}
53+
}
54+
}
55+
56+
extension ArtifactsArchiveMetadata {
57+
public static func parse(fileSystem: FileSystem, rootPath: AbsolutePath) throws -> ArtifactsArchiveMetadata {
58+
let path = rootPath.appending(component: "info.json")
59+
guard fileSystem.exists(path) else {
60+
throw StringError("ArtifactsArchive info.json not found at '\(rootPath)'")
61+
}
62+
63+
do {
64+
let bytes = try fileSystem.readFileContents(path)
65+
return try bytes.withData { data in
66+
let decoder = JSONDecoder.makeWithDefaults()
67+
return try decoder.decode(ArtifactsArchiveMetadata.self, from: data)
68+
}
69+
} catch {
70+
throw StringError("failed parsing ArtifactsArchive info.json at '\(path)': \(error)")
71+
}
72+
}
73+
}
74+
75+
extension ArtifactsArchiveMetadata: Decodable {
76+
enum CodingKeys: String, CodingKey {
77+
case schemaVersion
78+
case artifacts
79+
}
80+
}
81+
82+
extension ArtifactsArchiveMetadata.Artifact: Decodable {
83+
enum CodingKeys: String, CodingKey {
84+
case type
85+
case version
86+
case variants
87+
}
88+
89+
public init(from decoder: Decoder) throws {
90+
let container = try decoder.container(keyedBy: CodingKeys.self)
91+
self.type = try container.decode(ArtifactsArchiveMetadata.ArtifactType.self, forKey: .type)
92+
self.version = try container.decode(String.self, forKey: .version)
93+
self.variants = try container.decode([ArtifactsArchiveMetadata.Variant].self, forKey: .variants)
94+
}
95+
}
96+
97+
extension ArtifactsArchiveMetadata.Variant: Decodable {
98+
enum CodingKeys: String, CodingKey {
99+
case path
100+
case supportedTriples
101+
}
102+
103+
public init(from decoder: Decoder) throws {
104+
let container = try decoder.container(keyedBy: CodingKeys.self)
105+
self.supportedTriples = try container.decode([String].self, forKey: .supportedTriples).map { try Triple($0) }
106+
self.path = try container.decode(String.self, forKey: .path)
107+
}
108+
}

Sources/Build/BuildPlan.swift

Lines changed: 79 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,9 @@ public final class ProductBuildDescription {
10431043
/// Paths to the binary libraries the product depends on.
10441044
fileprivate var libraryBinaryPaths: Set<AbsolutePath> = []
10451045

1046+
/// Paths to tools shipped in binary dependencies
1047+
var availableTools: [String: AbsolutePath] = [:]
1048+
10461049
/// Path to the temporary directory for this product.
10471050
var tempsPath: AbsolutePath {
10481051
return buildParameters.buildPath.appending(component: product.name + ".product")
@@ -1294,8 +1297,11 @@ public class BuildPlan {
12941297
/// Cache for pkgConfig flags.
12951298
private var pkgConfigCache = [SystemLibraryTarget: (cFlags: [String], libs: [String])]()
12961299

1297-
/// Cache for xcframework library information.
1298-
private var xcFrameworkCache = [BinaryTarget: LibraryInfo?]()
1300+
/// Cache for library information.
1301+
private var externalLibrariesCache = [BinaryTarget: [LibraryInfo]]()
1302+
1303+
/// Cache for tools information.
1304+
private var externalExecutablesCache = [BinaryTarget: [ExecutableInfo]]()
12991305

13001306
private static func makeTestManifestTargets(
13011307
_ buildParameters: BuildParameters,
@@ -1499,7 +1505,7 @@ public class BuildPlan {
14991505
// Add flags for system targets.
15001506
for systemModule in dependencies.systemModules {
15011507
guard case let target as SystemLibraryTarget = systemModule.underlyingTarget else {
1502-
fatalError("This should not be possible.")
1508+
throw InternalError("This should not be possible.")
15031509
}
15041510
// Add pkgConfig libs arguments.
15051511
buildProduct.additionalFlags += pkgConfig(for: target).libs
@@ -1569,6 +1575,8 @@ public class BuildPlan {
15691575
// FIXME: We should write this as a custom llbuild task once we adopt it
15701576
// as a library.
15711577
try buildProduct.writeLinkFilelist(fileSystem)
1578+
1579+
buildProduct.availableTools = dependencies.availableTools
15721580
}
15731581

15741582
/// Computes the dependencies of a product.
@@ -1578,7 +1586,8 @@ public class BuildPlan {
15781586
dylibs: [ResolvedProduct],
15791587
staticTargets: [ResolvedTarget],
15801588
systemModules: [ResolvedTarget],
1581-
libraryBinaryPaths: Set<AbsolutePath>
1589+
libraryBinaryPaths: Set<AbsolutePath>,
1590+
availableTools: [String: AbsolutePath]
15821591
) {
15831592

15841593
// Sort the product targets in topological order.
@@ -1606,6 +1615,7 @@ public class BuildPlan {
16061615
var staticTargets = [ResolvedTarget]()
16071616
var systemModules = [ResolvedTarget]()
16081617
var libraryBinaryPaths: Set<AbsolutePath> = []
1618+
var availableTools = [String: AbsolutePath]()
16091619

16101620
for dependency in allTargets {
16111621
switch dependency {
@@ -1628,8 +1638,15 @@ public class BuildPlan {
16281638
guard let binaryTarget = target.underlyingTarget as? BinaryTarget else {
16291639
throw InternalError("invalid binary target '\(target.name)'")
16301640
}
1631-
if case .xcframework = binaryTarget.kind, let library = self.xcFrameworkLibrary(for: binaryTarget) {
1632-
libraryBinaryPaths.insert(library.binaryPath)
1641+
switch binaryTarget.kind {
1642+
case .xcframework:
1643+
let libraries = try self.parseXCFramework(for: binaryTarget)
1644+
for library in libraries {
1645+
libraryBinaryPaths.insert(library.libraryPath)
1646+
}
1647+
case .artifactsArchive:
1648+
let tools = try self.parseArtifactsArchive(for: binaryTarget)
1649+
tools.forEach { availableTools[$0.name] = $0.executablePath }
16331650
}
16341651
case .extension:
16351652
continue
@@ -1650,7 +1667,7 @@ public class BuildPlan {
16501667
}
16511668
}
16521669

1653-
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths)
1670+
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools)
16541671
}
16551672

16561673
/// Plan a Clang target.
@@ -1678,11 +1695,14 @@ public class BuildPlan {
16781695
clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"]
16791696
clangTarget.additionalFlags += pkgConfig(for: target).cFlags
16801697
case let target as BinaryTarget:
1681-
if let library = xcFrameworkLibrary(for: target) {
1682-
if let headersPath = library.headersPath {
1683-
clangTarget.additionalFlags += ["-I", headersPath.pathString]
1698+
if case .xcframework = target.kind {
1699+
let libraries = try self.parseXCFramework(for: target)
1700+
for library in libraries {
1701+
if let headersPath = library.headersPath {
1702+
clangTarget.additionalFlags += ["-I", headersPath.pathString]
1703+
}
1704+
clangTarget.libraryBinaryPaths.insert(library.libraryPath)
16841705
}
1685-
clangTarget.libraryBinaryPaths.insert(library.binaryPath)
16861706
}
16871707
default: continue
16881708
}
@@ -1712,11 +1732,14 @@ public class BuildPlan {
17121732
swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"]
17131733
swiftTarget.additionalFlags += pkgConfig(for: target).cFlags
17141734
case let target as BinaryTarget:
1715-
if let library = xcFrameworkLibrary(for: target) {
1716-
if let headersPath = library.headersPath {
1717-
swiftTarget.additionalFlags += ["-Xcc", "-I", "-Xcc", headersPath.pathString]
1735+
if case .xcframework = target.kind {
1736+
let libraries = try self.parseXCFramework(for: target)
1737+
for library in libraries {
1738+
if let headersPath = library.headersPath {
1739+
swiftTarget.additionalFlags += ["-Xcc", "-I", "-Xcc", headersPath.pathString]
1740+
}
1741+
swiftTarget.libraryBinaryPaths.insert(library.libraryPath)
17181742
}
1719-
swiftTarget.libraryBinaryPaths.insert(library.binaryPath)
17201743
}
17211744
default:
17221745
break
@@ -1835,53 +1858,69 @@ public class BuildPlan {
18351858
return result
18361859
}
18371860

1838-
/// Extracts the library to building against from a XCFramework.
1839-
private func xcFrameworkLibrary(for target: BinaryTarget) -> LibraryInfo? {
1840-
func calculateLibraryInfo() -> LibraryInfo? {
1841-
// Parse the XCFramework's Info.plist.
1842-
let infoPath = target.artifactPath.appending(component: "Info.plist")
1843-
guard let info = XCFrameworkInfo(path: infoPath, diagnostics: diagnostics, fileSystem: fileSystem) else {
1844-
return nil
1845-
}
1861+
/// Extracts the library information from an XCFramework.
1862+
private func parseXCFramework(for target: BinaryTarget) throws -> [LibraryInfo] {
1863+
try self.externalLibrariesCache.memoize(key: target) {
1864+
let metadata = try XCFrameworkMetadata.parse(fileSystem: self.fileSystem, rootPath: target.artifactPath)
18461865

18471866
// Check that it supports the target platform and architecture.
1848-
guard let library = info.libraries.first(where: {
1849-
return $0.platform == buildParameters.triple.os.asXCFrameworkPlatformString && $0.architectures.contains(buildParameters.triple.arch.rawValue)
1867+
guard let library = metadata.libraries.first(where: {
1868+
$0.platform == buildParameters.triple.os.asXCFrameworkPlatformString && $0.architectures.contains(buildParameters.triple.arch.rawValue)
18501869
}) else {
1851-
diagnostics.emit(error: """
1870+
throw StringError("""
18521871
artifact '\(target.name)' does not support the target platform and architecture \
18531872
('\(buildParameters.triple)')
18541873
""")
1855-
return nil
18561874
}
18571875

18581876
let libraryDirectory = target.artifactPath.appending(component: library.libraryIdentifier)
1859-
let binaryPath = libraryDirectory.appending(component: library.libraryPath)
1860-
let headersPath = library.headersPath.map({ libraryDirectory.appending(component: $0) })
1861-
return LibraryInfo(binaryPath: binaryPath, headersPath: headersPath)
1862-
}
1877+
let libraryPath = libraryDirectory.appending(RelativePath(library.libraryPath))
1878+
let headersPath = library.headersPath.map({ libraryDirectory.appending(RelativePath($0)) })
18631879

1864-
// If we don't have the library information yet, calculate it.
1865-
if let xcFramework = xcFrameworkCache[target] {
1866-
return xcFramework
1880+
return [LibraryInfo(libraryPath: libraryPath, headersPath: headersPath)]
18671881
}
1882+
}
1883+
1884+
/// Extracts the artifacts from an artifactsArchive
1885+
private func parseArtifactsArchive(for target: BinaryTarget) throws -> [ExecutableInfo] {
1886+
try self.externalExecutablesCache.memoize(key: target) {
1887+
let metadata = try ArtifactsArchiveMetadata.parse(fileSystem: self.fileSystem, rootPath: target.artifactPath)
18681888

1869-
let xcFramework = calculateLibraryInfo()
1870-
xcFrameworkCache[target] = xcFramework
1871-
return xcFramework
1889+
// filter the artifacts that are relevant to the triple
1890+
// FIXME: this filter needs to become more sophisticated
1891+
let supportedArtifacts = metadata.artifacts.filter { $0.value.variants.contains(where: { $0.supportedTriples.contains(buildParameters.triple) }) }
1892+
// TODO: add support for libraries
1893+
let executables = supportedArtifacts.filter { $0.value.type == .executable }
1894+
1895+
// flatten the results for easy access
1896+
return executables.reduce(into: [ExecutableInfo](), { partial, entry in
1897+
let executables = entry.value.variants.map {
1898+
ExecutableInfo(name: entry.key, executablePath: target.artifactPath.appending(RelativePath($0.path)))
1899+
}
1900+
partial.append(contentsOf: executables)
1901+
})
1902+
}
18721903
}
18731904
}
18741905

1875-
/// Information about a library.
1906+
/// Information about a library from a binary dependency.
18761907
private struct LibraryInfo: Equatable {
1877-
18781908
/// The path to the binary.
1879-
let binaryPath: AbsolutePath
1909+
let libraryPath: AbsolutePath
18801910

18811911
/// The path to the headers directory, if one exists.
18821912
let headersPath: AbsolutePath?
18831913
}
18841914

1915+
/// Information about an executable from a binary dependency.
1916+
private struct ExecutableInfo: Equatable {
1917+
/// The tool name
1918+
let name: String
1919+
1920+
/// The path to the executable.
1921+
let executablePath: AbsolutePath
1922+
}
1923+
18851924
private extension Diagnostic.Message {
18861925
static var swiftBackDeployError: Diagnostic.Message {
18871926
.warning("Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from \"More Downloads\" for Apple Developers at https://developer.apple.com/download/more/")

Sources/Build/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(Build
10+
ArtifactsArchiveMetadata.swift
1011
BuildOperationBuildSystemDelegateHandler.swift
1112
BuildOperation.swift
1213
BuildPlan.swift
1314
ManifestBuilder.swift
1415
SPMSwiftDriverExecutor.swift
1516
SwiftCompilerOutputParser.swift
16-
XCFrameworkInfo.swift)
17+
XCFrameworkMetadata.swift)
1718
target_link_libraries(Build PUBLIC
1819
TSCBasic
1920
Basics

0 commit comments

Comments
 (0)