Skip to content

Commit c17d175

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 de3e01f commit c17d175

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")
@@ -1292,8 +1295,11 @@ public class BuildPlan {
12921295
/// Cache for pkgConfig flags.
12931296
private var pkgConfigCache = [SystemLibraryTarget: (cFlags: [String], libs: [String])]()
12941297

1295-
/// Cache for xcframework library information.
1296-
private var xcFrameworkCache = [BinaryTarget: LibraryInfo?]()
1298+
/// Cache for library information.
1299+
private var externalLibrariesCache = [BinaryTarget: LibraryInfo]()
1300+
1301+
/// Cache for tools information.
1302+
private var externalToolsCache = [BinaryTarget: [ToolInfo]]()
12971303

12981304
private static func makeTestManifestTargets(
12991305
_ buildParameters: BuildParameters,
@@ -1497,7 +1503,7 @@ public class BuildPlan {
14971503
// Add flags for system targets.
14981504
for systemModule in dependencies.systemModules {
14991505
guard case let target as SystemLibraryTarget = systemModule.underlyingTarget else {
1500-
fatalError("This should not be possible.")
1506+
throw InternalError("This should not be possible.")
15011507
}
15021508
// Add pkgConfig libs arguments.
15031509
buildProduct.additionalFlags += pkgConfig(for: target).libs
@@ -1567,6 +1573,8 @@ public class BuildPlan {
15671573
// FIXME: We should write this as a custom llbuild task once we adopt it
15681574
// as a library.
15691575
try buildProduct.writeLinkFilelist(fileSystem)
1576+
1577+
buildProduct.availableTools = dependencies.availableTools
15701578
}
15711579

15721580
/// Computes the dependencies of a product.
@@ -1576,7 +1584,8 @@ public class BuildPlan {
15761584
dylibs: [ResolvedProduct],
15771585
staticTargets: [ResolvedTarget],
15781586
systemModules: [ResolvedTarget],
1579-
libraryBinaryPaths: Set<AbsolutePath>
1587+
libraryBinaryPaths: Set<AbsolutePath>,
1588+
availableTools: [String: AbsolutePath]
15801589
) {
15811590

15821591
// Sort the product targets in topological order.
@@ -1604,6 +1613,7 @@ public class BuildPlan {
16041613
var staticTargets = [ResolvedTarget]()
16051614
var systemModules = [ResolvedTarget]()
16061615
var libraryBinaryPaths: Set<AbsolutePath> = []
1616+
var availableTools = [String: AbsolutePath]()
16071617

16081618
for dependency in allTargets {
16091619
switch dependency {
@@ -1626,8 +1636,13 @@ public class BuildPlan {
16261636
guard let binaryTarget = target.underlyingTarget as? BinaryTarget else {
16271637
throw InternalError("invalid binary target '\(target.name)'")
16281638
}
1629-
if case .xcframework = binaryTarget.kind, let library = self.xcFrameworkLibrary(for: binaryTarget) {
1639+
switch binaryTarget.kind {
1640+
case .xcframework:
1641+
let library = try self.parseXCFramework(for: binaryTarget)
16301642
libraryBinaryPaths.insert(library.binaryPath)
1643+
case .toolsArchive:
1644+
let tools = try self.parseToolsArchive(for: binaryTarget)
1645+
tools.forEach { availableTools[$0.name] = $0.binaryPath }
16311646
}
16321647
case .extension:
16331648
continue
@@ -1648,7 +1663,7 @@ public class BuildPlan {
16481663
}
16491664
}
16501665

1651-
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths)
1666+
return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools)
16521667
}
16531668

16541669
/// Plan a Clang target.
@@ -1676,7 +1691,8 @@ public class BuildPlan {
16761691
clangTarget.additionalFlags += ["-fmodule-map-file=\(target.moduleMapPath.pathString)"]
16771692
clangTarget.additionalFlags += pkgConfig(for: target).cFlags
16781693
case let target as BinaryTarget:
1679-
if let library = xcFrameworkLibrary(for: target) {
1694+
if case .xcframework = target.kind {
1695+
let library = try self.parseXCFramework(for: target)
16801696
if let headersPath = library.headersPath {
16811697
clangTarget.additionalFlags += ["-I", headersPath.pathString]
16821698
}
@@ -1710,7 +1726,8 @@ public class BuildPlan {
17101726
swiftTarget.additionalFlags += ["-Xcc", "-fmodule-map-file=\(target.moduleMapPath.pathString)"]
17111727
swiftTarget.additionalFlags += pkgConfig(for: target).cFlags
17121728
case let target as BinaryTarget:
1713-
if let library = xcFrameworkLibrary(for: target) {
1729+
if case .xcframework = target.kind {
1730+
let library = try self.parseXCFramework(for: target)
17141731
if let headersPath = library.headersPath {
17151732
swiftTarget.additionalFlags += ["-Xcc", "-I", "-Xcc", headersPath.pathString]
17161733
}
@@ -1833,53 +1850,64 @@ public class BuildPlan {
18331850
return result
18341851
}
18351852

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

18451858
// Check that it supports the target platform and architecture.
1846-
guard let library = info.libraries.first(where: {
1847-
return $0.platform == buildParameters.triple.os.asXCFrameworkPlatformString && $0.architectures.contains(buildParameters.triple.arch.rawValue)
1859+
guard let library = metadata.libraries.first(where: {
1860+
$0.platform == buildParameters.triple.os.asXCFrameworkPlatformString && $0.architectures.contains(buildParameters.triple.arch.rawValue)
18481861
}) else {
1849-
diagnostics.emit(error: """
1862+
throw StringError("""
18501863
artifact '\(target.name)' does not support the target platform and architecture \
18511864
('\(buildParameters.triple)')
18521865
""")
1853-
return nil
18541866
}
18551867

18561868
let libraryDirectory = target.artifactPath.appending(component: library.libraryIdentifier)
18571869
let binaryPath = libraryDirectory.appending(component: library.libraryPath)
18581870
let headersPath = library.headersPath.map({ libraryDirectory.appending(component: $0) })
1871+
18591872
return LibraryInfo(binaryPath: binaryPath, headersPath: headersPath)
18601873
}
1874+
}
18611875

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

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

1873-
/// Information about a library.
1894+
/// Information about a library from a binary dependency.
18741895
private struct LibraryInfo: Equatable {
1875-
18761896
/// The path to the binary.
18771897
let binaryPath: AbsolutePath
18781898

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

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

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)