Skip to content

Commit d632f36

Browse files
authored
Commands: add experimental-destination command (#5911)
This change also adds `experimental-destination list` subcommand, which prints a list of available destinations. Subcommands that allow adding and removing destinations will be added in future PRs. New `DestinationCommand` and `ListDestinations` types are added, conforming to `ParsableCommand`. They don't conform to `SwiftTool`, so the new subcommand can be invoked in an arbitrary directory that doesn't contain a `Package.swift` manifest. `ArtifactsArchiveMetadata` schema version validation is implemented to accept new 1.1 schema version with the new destinations bundle type.
1 parent 5e7063a commit d632f36

File tree

12 files changed

+319
-25
lines changed

12 files changed

+319
-25
lines changed

Package.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,18 @@ let package = Package(
368368
exclude: ["CMakeLists.txt", "README.md"]
369369
),
370370

371+
.target(
372+
/** Interacts with cross-compilation destinations */
373+
name: "CrossCompilationDestinationsTool",
374+
dependencies: [
375+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
376+
"Basics",
377+
"CoreCommands",
378+
"SPMBuildCore",
379+
"PackageModel",
380+
]
381+
),
382+
371383
.target(
372384
/** Interacts with package collections */
373385
name: "PackageCollectionsTool",
@@ -417,6 +429,11 @@ let package = Package(
417429
dependencies: ["CoreCommands"],
418430
exclude: ["CMakeLists.txt"]
419431
),
432+
.executableTarget(
433+
/** Interacts with cross-compilation destinations */
434+
name: "swift-experimental-destination",
435+
dependencies: ["Commands", "CrossCompilationDestinationsTool"]
436+
),
420437
.executableTarget(
421438
/** Runs package tests */
422439
name: "swift-test",
@@ -437,7 +454,13 @@ let package = Package(
437454
.executableTarget(
438455
/** Multi-tool entry point for SwiftPM. */
439456
name: "swift-package-manager",
440-
dependencies: ["Commands", "Basics", "PackageCollectionsTool", "PackageRegistryTool"]
457+
dependencies: [
458+
"Basics",
459+
"Commands",
460+
"CrossCompilationDestinationsTool",
461+
"PackageCollectionsTool",
462+
"PackageRegistryTool"
463+
]
441464
),
442465
.executableTarget(
443466
/** Interact with package registry */

Sources/Basics/FileSystem+Extensions.swift

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,26 +224,65 @@ extension FileSystem {
224224
}
225225
}
226226

227-
// MARK: - cross-compilation SDKs
227+
// MARK: - cross-compilation destinations
228228

229229
private let crossCompilationDestinationsDirectoryName = "destinations"
230230

231231
extension FileSystem {
232232
/// SwiftPM cross-compilation destinations directory (if exists)
233233
public var swiftPMCrossCompilationDestinationsDirectory: AbsolutePath {
234234
get throws {
235-
if let path = try self.idiomaticSwiftPMDirectory {
235+
if let path = try idiomaticSwiftPMDirectory {
236236
return path.appending(component: crossCompilationDestinationsDirectoryName)
237237
} else {
238-
return try self.dotSwiftPMCrossCompilationDestinationsDirectory
238+
return try dotSwiftPMCrossCompilationDestinationsDirectory
239239
}
240240
}
241241
}
242242

243243
fileprivate var dotSwiftPMCrossCompilationDestinationsDirectory: AbsolutePath {
244244
get throws {
245-
return try self.dotSwiftPM.appending(component: crossCompilationDestinationsDirectoryName)
245+
return try dotSwiftPM.appending(component: crossCompilationDestinationsDirectoryName)
246+
}
247+
}
248+
249+
public func getSharedCrossCompilationDestinationsDirectory(
250+
explicitDirectory: AbsolutePath?
251+
) throws -> AbsolutePath? {
252+
if let explicitDestinationsDirectory = explicitDirectory {
253+
// Create the explicit SDKs path if necessary
254+
if !exists(explicitDestinationsDirectory) {
255+
try createDirectory(explicitDestinationsDirectory, recursive: true)
256+
}
257+
return explicitDestinationsDirectory
258+
} else {
259+
return try swiftPMCrossCompilationDestinationsDirectory
260+
}
261+
}
262+
263+
public func getOrCreateSwiftPMCrossCompilationDestinationsDirectory() throws -> AbsolutePath {
264+
let idiomaticDestinationsDirectory = try swiftPMCrossCompilationDestinationsDirectory
265+
266+
// Create idiomatic if necessary
267+
if !exists(idiomaticDestinationsDirectory) {
268+
try createDirectory(idiomaticDestinationsDirectory, recursive: true)
269+
}
270+
// Create ~/.swiftpm if necessary
271+
if !exists(try dotSwiftPM) {
272+
try createDirectory(dotSwiftPM, recursive: true)
273+
}
274+
// Create ~/.swiftpm/destinations symlink if necessary
275+
// locking ~/.swiftpm to protect from concurrent access
276+
try withLock(on: dotSwiftPM, type: .exclusive) {
277+
if !exists(try dotSwiftPMCrossCompilationDestinationsDirectory, followSymlink: false) {
278+
try createSymbolicLink(
279+
dotSwiftPMCrossCompilationDestinationsDirectory,
280+
pointingAt: idiomaticDestinationsDirectory,
281+
relative: false
282+
)
283+
}
246284
}
285+
return idiomaticDestinationsDirectory
247286
}
248287
}
249288

Sources/CoreCommands/Options.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public struct LocationOptions: ParsableArguments {
8585
public var customCompileDestination: AbsolutePath?
8686

8787
@Option(name: .customLong("experimental-destinations-path"), help: .hidden, completion: .directory)
88-
var crossCompilationDestinationsDirectory: AbsolutePath?
88+
public var crossCompilationDestinationsDirectory: AbsolutePath?
8989
}
9090

9191
public struct CachingOptions: ParsableArguments {

Sources/CoreCommands/SwiftTool.swift

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public final class SwiftTool {
150150
/// Path to the shared configuration directory
151151
public let sharedConfigurationDirectory: AbsolutePath?
152152

153-
/// Path to the cross-compilation SDK directory.
153+
/// Path to the cross-compilation destinations directory.
154154
public let sharedCrossCompilationDestinationsDirectory: AbsolutePath?
155155

156156
/// Cancellator to handle cancellation of outstanding work when handling SIGINT
@@ -299,7 +299,9 @@ public final class SwiftTool {
299299
self.sharedSecurityDirectory = try getSharedSecurityDirectory(options: self.options, fileSystem: fileSystem)
300300
self.sharedConfigurationDirectory = try getSharedConfigurationDirectory(options: self.options, fileSystem: fileSystem)
301301
self.sharedCacheDirectory = try getSharedCacheDirectory(options: self.options, fileSystem: fileSystem)
302-
self.sharedCrossCompilationDestinationsDirectory = try getSharedCrossCompilationDestinationsDirectory(options: self.options, fileSystem: fileSystem)
302+
self.sharedCrossCompilationDestinationsDirectory = try fileSystem.getSharedCrossCompilationDestinationsDirectory(
303+
explicitDirectory: self.options.locations.crossCompilationDestinationsDirectory
304+
)
303305

304306
// set global process logging handler
305307
Process.loggingHandler = { self.observabilityScope.emit(debug: $0) }
@@ -772,21 +774,6 @@ private func getSharedCacheDirectory(options: GlobalOptions, fileSystem: FileSys
772774
}
773775
}
774776

775-
private func getSharedCrossCompilationDestinationsDirectory(
776-
options: GlobalOptions,
777-
fileSystem: FileSystem
778-
) throws -> AbsolutePath? {
779-
if let explicitDestinationsDirectory = options.locations.crossCompilationDestinationsDirectory {
780-
// Create the explicit destinations path if necessary
781-
if !fileSystem.exists(explicitDestinationsDirectory) {
782-
try fileSystem.createDirectory(explicitDestinationsDirectory, recursive: true)
783-
}
784-
return explicitDestinationsDirectory
785-
} else {
786-
return try fileSystem.swiftPMCrossCompilationDestinationsDirectory
787-
}
788-
}
789-
790777
extension Basics.Diagnostic {
791778
static func unsupportedFlag(_ flag: String) -> Self {
792779
.warning("\(flag) is an *unsupported* option which can be removed at any time; do not rely on it")
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import Basics
15+
import CoreCommands
16+
import SPMBuildCore
17+
import PackageModel
18+
import TSCBasic
19+
20+
struct ListDestinations: ParsableCommand {
21+
static let configuration = CommandConfiguration(
22+
commandName: "list",
23+
abstract:
24+
"""
25+
Print a list of IDs of available cross-compilation destinations available on the filesystem.
26+
"""
27+
)
28+
29+
@OptionGroup()
30+
var locations: LocationOptions
31+
32+
func run() throws {
33+
let fileSystem = localFileSystem
34+
let observabilitySystem = ObservabilitySystem(
35+
SwiftToolObservabilityHandler(outputStream: stdoutStream, logLevel: .warning)
36+
)
37+
let observabilityScope = observabilitySystem.topScope
38+
39+
guard var destinationsDirectory = try fileSystem.getSharedCrossCompilationDestinationsDirectory(
40+
explicitDirectory: locations.crossCompilationDestinationsDirectory
41+
) else {
42+
let expectedPath = try fileSystem.swiftPMCrossCompilationDestinationsDirectory
43+
throw StringError(
44+
"Couldn't find or create a directory where cross-compilation destinations are stored: \(expectedPath)"
45+
)
46+
}
47+
48+
if !fileSystem.exists(destinationsDirectory) {
49+
destinationsDirectory = try fileSystem.getOrCreateSwiftPMCrossCompilationDestinationsDirectory()
50+
}
51+
52+
// Get absolute paths to available destination bundles.
53+
let destinationBundles = try fileSystem.getDirectoryContents(destinationsDirectory).filter {
54+
$0.hasSuffix(BinaryTarget.Kind.artifactsArchive.fileExtension)
55+
}.map {
56+
destinationsDirectory.appending(components: [$0])
57+
}
58+
59+
// Enumerate available bundles and parse manifests for each of them, then validate supplied destinations.
60+
for bundlePath in destinationBundles {
61+
do {
62+
let destinationsBundle = try DestinationsBundle.parseAndValidate(
63+
bundlePath: bundlePath,
64+
fileSystem: fileSystem,
65+
observabilityScope: observabilityScope
66+
)
67+
68+
destinationsBundle.artifacts.keys.forEach { print($0) }
69+
} catch {
70+
observabilityScope.emit(
71+
.warning(
72+
"Couldn't parse `info.json` manifest of a destination bundle at \(bundlePath): \(error)"
73+
)
74+
)
75+
}
76+
}
77+
}
78+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import Basics
15+
16+
public struct SwiftDestinationTool: ParsableCommand {
17+
public static let configuration = CommandConfiguration(
18+
commandName: "experimental-destination",
19+
_superCommandName: "swift",
20+
abstract: "Perform operations on Swift cross-compilation destinations.",
21+
version: SwiftVersion.current.completeDisplayString,
22+
subcommands: [
23+
ListDestinations.self,
24+
],
25+
helpNames: [.short, .long, .customLong("help", withSingleDash: true)])
26+
27+
public init() {}
28+
}

Sources/SPMBuildCore/ArtifactsArchiveMetadata.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import PackageModel
1515
import TSCBasic
1616

1717
import struct TSCUtility.Triple
18+
import struct TSCUtility.Version
1819

1920
public struct ArtifactsArchiveMetadata: Equatable {
2021
public let schemaVersion: String
@@ -42,6 +43,7 @@ public struct ArtifactsArchiveMetadata: Equatable {
4243
// This can also support resource-only artifacts as well. For example, 3d models along with associated textures, or fonts, etc.
4344
public enum ArtifactType: String, RawRepresentable, Decodable {
4445
case executable
46+
case crossCompilationDestination
4547
}
4648

4749
public struct Variant: Equatable {
@@ -65,7 +67,19 @@ extension ArtifactsArchiveMetadata {
6567
do {
6668
let data: Data = try fileSystem.readFileContents(path)
6769
let decoder = JSONDecoder.makeWithDefaults()
68-
return try decoder.decode(ArtifactsArchiveMetadata.self, from: data)
70+
let decodedMetadata = try decoder.decode(ArtifactsArchiveMetadata.self, from: data)
71+
let version = try Version(
72+
versionString: decodedMetadata.schemaVersion,
73+
usesLenientParsing: true
74+
)
75+
76+
switch (version.major, version.minor) {
77+
case (1, 1), (1, 0):
78+
return decodedMetadata
79+
default:
80+
throw StringError("invalid `schemaVersion` of bundle manifest at `\(path)`: \(decodedMetadata.schemaVersion)")
81+
}
82+
6983
} catch {
7084
throw StringError("failed parsing ArtifactsArchive info.json at '\(path)': \(error)")
7185
}

Sources/SPMBuildCore/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_library(SPMBuildCore
1414
BuildSystemCommand.swift
1515
BuildSystemDelegate.swift
1616
BuiltTestProduct.swift
17+
DestinationsBundle.swift
1718
PluginContextSerializer.swift
1819
PluginInvocation.swift
1920
PluginMessages.swift

0 commit comments

Comments
 (0)