Skip to content

Commit

Permalink
Add experimental-destination configuration subcommand (#6252)
Browse files Browse the repository at this point in the history
For configuring destination bundles that can't be self-contained and have to reference absolute paths we need new to provide a CLI. This comes as a new `swift experimental-destination configuration` subcommand per the proposed changes in swiftlang/swift-evolution#1942.

`DestinationCommand` protocol is refined to wait for the observability handler to flush all output. Existing destination-related subcommands are modified to conform to it. Additionally, it now passes a newly initialized observability scope and configuration values.

Two new subcommands are added: `configuration show` and `configuration reset`. For actually updating configuration values a third subcommand `configuration set` will be added in a separate PR.
  • Loading branch information
MaxDesiatov authored Mar 10, 2023
1 parent 5a7b7fe commit 52d9590
Show file tree
Hide file tree
Showing 12 changed files with 425 additions and 95 deletions.
11 changes: 1 addition & 10 deletions Sources/CoreCommands/SwiftToolObservabilityHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public struct SwiftToolObservabilityHandler: ObservabilityHandlerProvider {
self.outputHandler.prompt(message: message, completion: completion)
}

func wait(timeout: DispatchTime) {
public func wait(timeout: DispatchTime) {
self.outputHandler.wait(timeout: timeout)
}

Expand Down Expand Up @@ -212,12 +212,3 @@ extension Basics.Diagnostic.Severity {
return self <= .info
}
}

extension ObservabilitySystem {
public static func swiftTool(
outputStream: OutputByteStream = stdoutStream,
logLevel: Basics.Diagnostic.Severity = .warning
) -> ObservabilitySystem {
.init(SwiftToolObservabilityHandler(outputStream: stdoutStream, logLevel: logLevel))
}
}
3 changes: 3 additions & 0 deletions Sources/CrossCompilationDestinationsTool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_library(CrossCompilationDestinationsTool
Configuration/ConfigureDestination.swift
Configuration/ResetConfiguration.swift
Configuration/ShowConfiguration.swift
DestinationCommand.swift
InstallDestination.swift
ListDestinations.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import ArgumentParser

struct ConfigureDestination: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "configuration",
abstract: """
Manages configuration options for installed cross-compilation destinations.
""",
subcommands: [
ResetConfiguration.self,
ShowConfiguration.self,
]
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import ArgumentParser
import Basics
import CoreCommands
import PackageModel

import struct TSCBasic.AbsolutePath

struct ResetConfiguration: DestinationCommand {
static let configuration = CommandConfiguration(
commandName: "reset",
abstract: """
Resets configuration properties currently applied to a given destination and run-time triple. If no specific \
property is specified, all of them are reset for the destination.
"""
)

@OptionGroup(visibility: .hidden)
var locations: LocationOptions

@Flag(help: "Reset custom configuration for a path to a directory containing the SDK root.")
var sdkRootPath = false

@Flag(help: "Reset custom configuration for a path to a directory containing Swift resources for dynamic linking.")
var swiftResourcesPath = false

@Flag(help: "Reset custom configuration for a path to a directory containing Swift resources for static linking.")
var swiftStaticResourcesPath = false

@Flag(help: "Reset custom configuration for a path to a directory containing headers.")
var includeSearchPath = false

@Flag(help: "Reset custom configuration for a path to a directory containing libraries.")
var librarySearchPath = false

@Flag(help: "Reset custom configuration for a path to a toolset file.")
var toolsetPath = false

@Argument(
help: """
An identifier of an already installed destination. Use the `list` subcommand to see all available \
identifiers.
"""
)
var destinationID: String

@Argument(help: "The run-time triple of the destination to configure.")
var runTimeTriple: String

func run(
buildTimeTriple: Triple,
_ destinationsDirectory: AbsolutePath,
_ observabilityScope: ObservabilityScope
) throws {
let configurationStore = try DestinationConfigurationStore(
buildTimeTriple: buildTimeTriple,
destinationsDirectoryPath: destinationsDirectory,
fileSystem: fileSystem,
observabilityScope: observabilityScope
)

let triple = try Triple(runTimeTriple)

guard var destination = try configurationStore.readConfiguration(
destinationID: destinationID,
runTimeTriple: triple
) else {
throw DestinationError.destinationNotFound(
artifactID: destinationID,
builtTimeTriple: buildTimeTriple,
runTimeTriple: triple
)
}

var configuration = destination.pathsConfiguration
var shouldResetAll = true

if sdkRootPath {
configuration.sdkRootPath = nil
shouldResetAll = false
}

if swiftResourcesPath {
configuration.swiftResourcesPath = nil
shouldResetAll = false
}

if swiftStaticResourcesPath {
configuration.swiftResourcesPath = nil
shouldResetAll = false
}

if includeSearchPath {
configuration.includeSearchPaths = nil
shouldResetAll = false
}

if librarySearchPath {
configuration.librarySearchPaths = nil
shouldResetAll = false
}

if toolsetPath {
configuration.toolsetPaths = nil
shouldResetAll = false
}

if shouldResetAll {
if try !configurationStore.resetConfiguration(destinationID: destinationID, runTimeTriple: triple) {
observabilityScope.emit(
warning: "No configuration for destination \(destinationID)"
)
}
} else {
try configurationStore.updateConfiguration(destinationID: destinationID, destination: destination)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import ArgumentParser
import Basics
import CoreCommands
import PackageModel

import struct TSCBasic.AbsolutePath

struct ShowConfiguration: DestinationCommand {
static let configuration = CommandConfiguration(
commandName: "show",
abstract: """
Prints all configuration properties currently applied to a given destination and run-time triple.
"""
)

@OptionGroup(visibility: .hidden)
var locations: LocationOptions

@Argument(
help: """
An identifier of an already installed destination. Use the `list` subcommand to see all available \
identifiers.
"""
)
var destinationID: String

@Argument(help: "The run-time triple of the destination to configure.")
var runTimeTriple: String

func run(
buildTimeTriple: Triple,
_ destinationsDirectory: AbsolutePath,
_ observabilityScope: ObservabilityScope
) throws {
let configurationStore = try DestinationConfigurationStore(
buildTimeTriple: buildTimeTriple,
destinationsDirectoryPath: destinationsDirectory,
fileSystem: fileSystem,
observabilityScope: observabilityScope
)

let triple = try Triple(runTimeTriple)

guard let configuration = try configurationStore.readConfiguration(
destinationID: destinationID,
runTimeTriple: triple
)?.pathsConfiguration else {
throw DestinationError.destinationNotFound(
artifactID: destinationID,
builtTimeTriple: buildTimeTriple,
runTimeTriple: triple
)
}

print(configuration)
}
}

extension Destination.PathsConfiguration: CustomStringConvertible {
public var description: String {
"""
sdkRootPath: \(sdkRootPath.configurationString)
swiftResourcesPath: \(swiftResourcesPath.configurationString)
swiftStaticResourcesPath: \(swiftStaticResourcesPath.configurationString)
includeSearchPaths: \(includeSearchPaths.configurationString)
librarySearchPaths: \(librarySearchPaths.configurationString)
toolsetPaths: \(toolsetPaths.configurationString)
"""
}
}

extension Optional where Wrapped == AbsolutePath {
fileprivate var configurationString: String {
self?.pathString ?? "not set"
}
}

extension Optional where Wrapped == [AbsolutePath] {
fileprivate var configurationString: String {
self?.map(\.pathString).description ?? "not set"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,31 @@
//===----------------------------------------------------------------------===//

import ArgumentParser
import Basics
import CoreCommands
import TSCBasic
import Dispatch
import PackageModel

import struct TSCBasic.AbsolutePath
import protocol TSCBasic.FileSystem
import var TSCBasic.localFileSystem
import var TSCBasic.stdoutStream

/// A protocol for functions and properties common to all destination subcommands.
protocol DestinationCommand: ParsableCommand {
/// Common locations options provided by ArgumentParser.
var locations: LocationOptions { get }

/// Run a command operating on cross-compilation destinations, passing it required configuration values.
/// - Parameters:
/// - buildTimeTriple: triple of the machine this command is running on.
/// - destinationsDirectory: directory containing destination artifact bundles and their configuration.
/// - observabilityScope: observability scope used for logging.
func run(
buildTimeTriple: Triple,
_ destinationsDirectory: AbsolutePath,
_ observabilityScope: ObservabilityScope
) throws
}

extension DestinationCommand {
Expand All @@ -43,4 +61,31 @@ extension DestinationCommand {

return destinationsDirectory
}

public func run() throws {
let observabilityHandler = SwiftToolObservabilityHandler(outputStream: stdoutStream, logLevel: .info)
let observabilitySystem = ObservabilitySystem(observabilityHandler)
let observabilityScope = observabilitySystem.topScope
let destinationsDirectory = try self.getOrCreateDestinationsDirectory()

let hostToolchain = try UserToolchain(destination: Destination.hostDestination())
let triple = Triple.getHostTriple(usingSwiftCompiler: hostToolchain.swiftCompilerPath)

var commandError: Error? = nil
do {
try self.run(buildTimeTriple: triple, destinationsDirectory, observabilityScope)
if observabilityScope.errorsReported {
throw ExitCode.failure
}
} catch {
commandError = error
}

// wait for all observability items to process
observabilityHandler.wait(timeout: .now() + 5)

if let error = commandError {
throw error
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ struct InstallDestination: DestinationCommand {
@Argument(help: "A local filesystem path or a URL of an artifact bundle to install.")
var bundlePathOrURL: String

func run() throws {
let observabilitySystem = ObservabilitySystem.swiftTool()
let observabilityScope = observabilitySystem.topScope
let destinationsDirectory = try self.getOrCreateDestinationsDirectory()

func run(
buildTimeTriple: Triple,
_ destinationsDirectory: AbsolutePath,
_ observabilityScope: ObservabilityScope
) throws {
if
let bundleURL = URL(string: bundlePathOrURL),
let scheme = bundleURL.scheme,
Expand Down Expand Up @@ -78,6 +78,6 @@ struct InstallDestination: DestinationCommand {
throw StringError("Argument `\(bundlePathOrURL)` is neither a valid filesystem path nor a URL.")
}

print("Destination artifact bundle at `\(bundlePathOrURL)` successfully installed.")
observabilityScope.emit(info: "Destination artifact bundle at `\(bundlePathOrURL)` successfully installed.")
}
}
Loading

0 comments on commit 52d9590

Please sign in to comment.