Skip to content

Commit 834fdfa

Browse files
committed
add support for tools archive
motivation: to support the package extensions feature, we need to support binary dependencies with tools that can be distributed across different platforms changes: * introuce new binary dependency type "toolsArchive" with the extension "toar" * the structure of the archive is inspired by XCFramework, and it can contain multiple tools/executables targeting multiple platforms and architectures using target triplets to indicate such support * update BuildPlan code to parse "toar" files and extract the tools available to the target in a [String: AbsolutePath] which can be later hooks to be used by extensions * refactory XCFramework code * update call-sites and tests and add new tests
1 parent 4b18559 commit 834fdfa

File tree

10 files changed

+419
-190
lines changed

10 files changed

+419
-190
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 68 additions & 29 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 externalToolsCache = [BinaryTarget: [ToolInfo]]()
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,13 @@ 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) {
1641+
switch binaryTarget.kind {
1642+
case .xcframework:
1643+
let library = try self.parseXCFramework(for: binaryTarget)
16321644
libraryBinaryPaths.insert(library.binaryPath)
1645+
case .toolsArchive:
1646+
let tools = try self.parseToolsArchive(for: binaryTarget)
1647+
tools.forEach { availableTools[$0.name] = $0.binaryPath }
16331648
}
16341649
case .extension:
16351650
continue
@@ -1650,7 +1665,7 @@ public class BuildPlan {
16501665
}
16511666
}
16521667

1653-
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths)
1668+
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools)
16541669
}
16551670

16561671
/// Plan a Clang target.
@@ -1678,7 +1693,8 @@ public class BuildPlan {
16781693
clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"]
16791694
clangTarget.additionalFlags += pkgConfig(for: target).cFlags
16801695
case let target as BinaryTarget:
1681-
if let library = xcFrameworkLibrary(for: target) {
1696+
if case .xcframework = target.kind {
1697+
let library = try self.parseXCFramework(for: target)
16821698
if let headersPath = library.headersPath {
16831699
clangTarget.additionalFlags += ["-I", headersPath.pathString]
16841700
}
@@ -1712,7 +1728,8 @@ public class BuildPlan {
17121728
swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"]
17131729
swiftTarget.additionalFlags += pkgConfig(for: target).cFlags
17141730
case let target as BinaryTarget:
1715-
if let library = xcFrameworkLibrary(for: target) {
1731+
if case .xcframework = target.kind {
1732+
let library = try self.parseXCFramework(for: target)
17161733
if let headersPath = library.headersPath {
17171734
swiftTarget.additionalFlags += ["-Xcc", "-I", "-Xcc", headersPath.pathString]
17181735
}
@@ -1835,53 +1852,64 @@ public class BuildPlan {
18351852
return result
18361853
}
18371854

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-
}
1855+
/// Extracts the library information from an XCFramework.
1856+
private func parseXCFramework(for target: BinaryTarget) throws -> LibraryInfo {
1857+
try self.externalLibrariesCache.memoize(key: target) {
1858+
let metadata = try XCFrameworkMetadata.parse(fileSystem: self.fileSystem, rootPath: target.artifactPath)
18461859

18471860
// 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)
1861+
guard let library = metadata.libraries.first(where: {
1862+
$0.platform == buildParameters.triple.os.asXCFrameworkPlatformString && $0.architectures.contains(buildParameters.triple.arch.rawValue)
18501863
}) else {
1851-
diagnostics.emit(error: """
1864+
throw StringError("""
18521865
artifact '\(target.name)' does not support the target platform and architecture \
18531866
('\(buildParameters.triple)')
18541867
""")
1855-
return nil
18561868
}
18571869

18581870
let libraryDirectory = target.artifactPath.appending(component: library.libraryIdentifier)
18591871
let binaryPath = libraryDirectory.appending(component: library.libraryPath)
18601872
let headersPath = library.headersPath.map({ libraryDirectory.appending(component: $0) })
1873+
18611874
return LibraryInfo(binaryPath: binaryPath, headersPath: headersPath)
18621875
}
1876+
}
18631877

1864-
// If we don't have the library information yet, calculate it.
1865-
if let xcFramework = xcFrameworkCache[target] {
1866-
return xcFramework
1867-
}
1878+
/// Extracts the executables info from an executablesArchive
1879+
private func parseToolsArchive(for target: BinaryTarget) throws -> [ToolInfo] {
1880+
try self.externalToolsCache.memoize(key: target) {
1881+
let metadata = try ToolsArchiveMetadata.parse(fileSystem: self.fileSystem, rootPath: target.artifactPath)
18681882

1869-
let xcFramework = calculateLibraryInfo()
1870-
xcFrameworkCache[target] = xcFramework
1871-
return xcFramework
1883+
// filter the tools that are relevant to the triple
1884+
let supportedTools = metadata.tools.filter { $0.value.contains(where: { $0.supportedTriplets.contains(buildParameters.triple) }) }
1885+
// flatten the tools for each access
1886+
return supportedTools.reduce(into: [ToolInfo](), { partial, entry in
1887+
let tools = entry.value.map {
1888+
ToolInfo(name: entry.key, binaryPath: target.artifactPath.appending(RelativePath($0.path)))
1889+
}
1890+
partial.append(contentsOf: tools)
1891+
})
1892+
}
18721893
}
18731894
}
18741895

1875-
/// Information about a library.
1896+
/// Information about a library from a binary dependency.
18761897
private struct LibraryInfo: Equatable {
1877-
18781898
/// The path to the binary.
18791899
let binaryPath: AbsolutePath
18801900

18811901
/// The path to the headers directory, if one exists.
18821902
let headersPath: AbsolutePath?
18831903
}
18841904

1905+
/// Information about an executable from a binary dependency.
1906+
private struct ToolInfo: Equatable {
1907+
/// The tool name
1908+
let name: String
1909+
/// The path to the binary.
1910+
let binaryPath: AbsolutePath
1911+
}
1912+
18851913
private extension Diagnostic.Message {
18861914
static var swiftBackDeployError: Diagnostic.Message {
18871915
.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/")
@@ -1980,3 +2008,14 @@ fileprivate extension Triple {
19802008
isLinux() || arch == .wasm32
19812009
}
19822010
}
2011+
2012+
// FIXME: move this to TSC
2013+
extension Triple: Hashable {
2014+
public func hash(into hasher: inout Hasher) {
2015+
hasher.combine(self.tripleString)
2016+
hasher.combine(self.arch)
2017+
hasher.combine(self.vendor)
2018+
hasher.combine(self.os)
2019+
hasher.combine(self.abi)
2020+
}
2021+
}

Sources/Build/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ add_library(Build
1313
ManifestBuilder.swift
1414
SPMSwiftDriverExecutor.swift
1515
SwiftCompilerOutputParser.swift
16-
XCFrameworkInfo.swift)
16+
ToolsArchiveMetadata.swift
17+
XCFrameworkMetadata.swift)
1718
target_link_libraries(Build PUBLIC
1819
TSCBasic
1920
Basics
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 ToolsArchiveMetadata: Equatable {
18+
public let schemaVersion: String
19+
public let tools: [String: [Support]]
20+
21+
public init(schemaVersion: String, tools: [String: [ToolsArchiveMetadata.Support]]) {
22+
self.schemaVersion = schemaVersion
23+
self.tools = tools
24+
}
25+
26+
public struct Support: Equatable {
27+
let path: String
28+
let supportedTriplets: [Triple]
29+
30+
public init(path: String, supportedTriplets: [Triple]) {
31+
self.path = path
32+
self.supportedTriplets = supportedTriplets
33+
}
34+
}
35+
}
36+
37+
extension ToolsArchiveMetadata {
38+
public static func parse(fileSystem: FileSystem, rootPath: AbsolutePath) throws -> ToolsArchiveMetadata {
39+
let path = rootPath.appending(component: "info.json")
40+
guard fileSystem.exists(path) else {
41+
throw StringError("ExecutablesArchive info.json not found at '\(rootPath)'")
42+
}
43+
44+
do {
45+
let bytes = try fileSystem.readFileContents(path)
46+
return try bytes.withData { data in
47+
let decoder = JSONDecoder.makeWithDefaults()
48+
return try decoder.decode(ToolsArchiveMetadata.self, from: data)
49+
}
50+
} catch {
51+
throw StringError("failed parsing ExecutablesArchive info.json at '\(path)': \(error)")
52+
}
53+
}
54+
}
55+
56+
extension ToolsArchiveMetadata: Decodable {
57+
enum CodingKeys: String, CodingKey {
58+
case schemaVersion
59+
case tools = "availableTools"
60+
}
61+
}
62+
63+
extension ToolsArchiveMetadata.Support: Decodable {
64+
enum CodingKeys: String, CodingKey {
65+
case path
66+
case supportedTriplets
67+
}
68+
69+
public init(from decoder: Decoder) throws {
70+
let container = try decoder.container(keyedBy: CodingKeys.self)
71+
self.path = try container.decode(String.self, forKey: .path)
72+
self.supportedTriplets = try container.decode([String].self, forKey: .supportedTriplets).map { try Triple($0) }
73+
}
74+
}
Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
/*
2-
This source file is part of the Swift.org open source project
2+
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5-
Licensed under Apache License v2.0 with Runtime Library Exception
4+
Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
66

7-
See http://swift.org/LICENSE.txt for license information
8-
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9-
*/
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
1010

11-
import TSCBasic
12-
import TSCUtility
11+
import Foundation
1312
import PackageModel
1413
import SPMBuildCore
15-
import Foundation
14+
import TSCBasic
15+
import TSCUtility
1616

17-
public struct XCFrameworkInfo: Equatable {
17+
public struct XCFrameworkMetadata: Equatable {
1818
public struct Library: Equatable {
1919
public let libraryIdentifier: String
2020
public let libraryPath: String
@@ -44,35 +44,32 @@ public struct XCFrameworkInfo: Equatable {
4444
}
4545
}
4646

47-
extension XCFrameworkInfo {
48-
public init?(path: AbsolutePath, diagnostics: DiagnosticsEngine, fileSystem: FileSystem) {
47+
extension XCFrameworkMetadata {
48+
public static func parse(fileSystem: FileSystem, rootPath: AbsolutePath) throws -> XCFrameworkMetadata {
49+
let path = rootPath.appending(component: "Info.plist")
4950
guard fileSystem.exists(path) else {
50-
diagnostics.emit(error: "missing XCFramework Info.plist at '\(path)'")
51-
return nil
51+
throw StringError("XCFramework Info.plist not found at '\(rootPath)'")
5252
}
5353

5454
do {
55-
let plistBytes = try fileSystem.readFileContents(path)
56-
57-
let decoder = PropertyListDecoder()
58-
self = try plistBytes.withData({ data in
59-
try decoder.decode(XCFrameworkInfo.self, from: data)
60-
})
55+
let bytes = try fileSystem.readFileContents(path)
56+
return try bytes.withData { data in
57+
let decoder = PropertyListDecoder()
58+
return try decoder.decode(XCFrameworkMetadata.self, from: data)
59+
}
6160
} catch {
62-
diagnostics.emit(error: "failed parsing XCFramework Info.plist at '\(path)': \(error)")
63-
return nil
61+
throw StringError("failed parsing XCFramework Info.plist at '\(path)': \(error)")
6462
}
6563
}
6664
}
6765

68-
extension XCFrameworkInfo: Decodable {
66+
extension XCFrameworkMetadata: Decodable {
6967
enum CodingKeys: String, CodingKey {
7068
case libraries = "AvailableLibraries"
7169
}
7270
}
7371

74-
extension Triple.Arch: Decodable { }
75-
extension XCFrameworkInfo.Library: Decodable {
72+
extension XCFrameworkMetadata.Library: Decodable {
7673
enum CodingKeys: String, CodingKey {
7774
case libraryIdentifier = "LibraryIdentifier"
7875
case libraryPath = "LibraryPath"
@@ -81,3 +78,5 @@ extension XCFrameworkInfo.Library: Decodable {
8178
case architectures = "SupportedArchitectures"
8279
}
8380
}
81+
82+
extension Triple.Arch: Decodable {}

Sources/PackageModel/Target.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -517,14 +517,14 @@ public final class BinaryTarget: Target {
517517

518518
public enum Kind: String, RawRepresentable, Codable, CaseIterable {
519519
case xcframework
520-
// TODO: add new types
521-
//case swiftLibraryArchive
522-
//case swiftExecutableArchive
520+
case toolsArchive
523521

524522
public var fileExtension: String {
525523
switch self {
526524
case .xcframework:
527525
return "xcframework"
526+
case .toolsArchive:
527+
return "toar"
528528
}
529529
}
530530

0 commit comments

Comments
 (0)