Skip to content

6.0: [SE-0301] Implement package manifest editing command-line options #7494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BuildSupport/SwiftSyntax/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
include(FetchContent)

set(BUILD_SHARED_LIBS OFF)

if(DEFINED SWIFTPM_PATH_TO_SWIFT_SYNTAX_SOURCE)
file(TO_CMAKE_PATH "${SWIFTPM_PATH_TO_SWIFT_SYNTAX_SOURCE}" swift_syntax_path)
FetchContent_Declare(SwiftSyntax
SOURCE_DIR "${swift_syntax_path}")
else()
FetchContent_Declare(SwiftSyntax
GIT_REPOSITORY https://github.com/apple/swift-syntax
GIT_TAG main)
endif()
FetchContent_MakeAvailable(SwiftSyntax)
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,6 @@ find_package(SQLite3 REQUIRED)
# Enable `package` modifier for the whole package.
add_compile_options("$<$<COMPILE_LANGUAGE:Swift>:-package-name;SwiftPM>")

add_subdirectory(BuildSupport/SwiftSyntax)
add_subdirectory(Sources)
add_subdirectory(cmake/modules)
30 changes: 30 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ let swiftPMDataModelProduct = (
"PackageLoading",
"PackageMetadata",
"PackageModel",
"PackageModelSyntax",
"SourceControl",
"Workspace",
]
Expand Down Expand Up @@ -236,6 +237,23 @@ let package = Package(
swiftSettings: packageModelResourcesSettings
),

.target(
/** Primary Package model objects relationship to SwiftSyntax */
name: "PackageModelSyntax",
dependencies: [
"Basics",
"PackageLoading",
"PackageModel",
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
],
exclude: ["CMakeLists.txt"]
),

.target(
/** Package model conventions and loading support */
name: "PackageLoading",
Expand Down Expand Up @@ -404,10 +422,12 @@ let package = Package(
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "OrderedCollections", package: "swift-collections"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
"Basics",
"Build",
"CoreCommands",
"PackageGraph",
"PackageModelSyntax",
"SourceControl",
"Workspace",
"XCBuildSupport",
Expand Down Expand Up @@ -613,6 +633,14 @@ let package = Package(
name: "PackageModelTests",
dependencies: ["PackageModel", "SPMTestSupport"]
),
.testTarget(
name: "PackageModelSyntaxTests",
dependencies: [
"PackageModelSyntax",
"SPMTestSupport",
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
]
),
.testTarget(
name: "PackageGraphTests",
dependencies: ["PackageGraph", "SPMTestSupport"]
Expand Down Expand Up @@ -764,6 +792,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.2")),
.package(url: "https://github.com/apple/swift-driver.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "3.0.0")),
.package(url: "https://github.com/apple/swift-syntax.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "1.1.1")),
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.1")),
.package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "1.0.1")),
Expand All @@ -774,6 +803,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
.package(path: "../swift-argument-parser"),
.package(path: "../swift-driver"),
.package(path: "../swift-crypto"),
.package(path: "../swift-syntax"),
.package(path: "../swift-system"),
.package(path: "../swift-collections"),
.package(path: "../swift-certificates"),
Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_subdirectory(PackageFingerprint)
add_subdirectory(PackageGraph)
add_subdirectory(PackageLoading)
add_subdirectory(PackageModel)
add_subdirectory(PackageModelSyntax)
add_subdirectory(PackagePlugin)
add_subdirectory(PackageRegistry)
add_subdirectory(PackageSigning)
Expand Down
4 changes: 4 additions & 0 deletions Sources/Commands/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(Commands
PackageCommands/AddDependency.swift
PackageCommands/AddProduct.swift
PackageCommands/AddTarget.swift
PackageCommands/APIDiff.swift
PackageCommands/ArchiveSource.swift
PackageCommands/CompletionCommand.swift
Expand Down Expand Up @@ -56,6 +59,7 @@ target_link_libraries(Commands PUBLIC
CoreCommands
LLBuildManifest
PackageGraph
PackageModelSyntax
SourceControl
TSCBasic
TSCUtility
Expand Down
155 changes: 155 additions & 0 deletions Sources/Commands/PackageCommands/AddDependency.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2024 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 PackageModelSyntax
import SwiftParser
import SwiftSyntax
import TSCBasic
import TSCUtility
import Workspace

extension SwiftPackageCommand {
struct AddDependency: SwiftCommand {
package static let configuration = CommandConfiguration(
abstract: "Add a package dependency to the manifest")

@Argument(help: "The URL or directory of the package to add")
var dependency: String

@OptionGroup(visibility: .hidden)
var globalOptions: GlobalOptions

@Option(help: "The exact package version to depend on")
var exact: Version?

@Option(help: "The specific package revision to depend on")
var revision: String?

@Option(help: "The branch of the package to depend on")
var branch: String?

@Option(help: "The package version to depend on (up to the next major version)")
var from: Version?

@Option(help: "The package version to depend on (up to the next minor version)")
var upToNextMinorFrom: Version?

@Option(help: "Specify upper bound on the package version range (exclusive)")
var to: Version?

func run(_ swiftCommandState: SwiftCommandState) throws {
let workspace = try swiftCommandState.getActiveWorkspace()

guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else {
throw StringError("unknown package")
}

// Load the manifest file
let fileSystem = workspace.fileSystem
let manifestPath = packagePath.appending("Package.swift")
let manifestContents: ByteString
do {
manifestContents = try fileSystem.readFileContents(manifestPath)
} catch {
throw StringError("cannot find package manifest in \(manifestPath)")
}

// Parse the manifest.
let manifestSyntax = manifestContents.withData { data in
data.withUnsafeBytes { buffer in
buffer.withMemoryRebound(to: UInt8.self) { buffer in
Parser.parse(source: buffer)
}
}
}

let identity = PackageIdentity(url: .init(dependency))

// Collect all of the possible version requirements.
var requirements: [PackageDependency.SourceControl.Requirement] = []
if let exact {
requirements.append(.exact(exact))
}

if let branch {
requirements.append(.branch(branch))
}

if let revision {
requirements.append(.revision(revision))
}

if let from {
requirements.append(.range(.upToNextMajor(from: from)))
}

if let upToNextMinorFrom {
requirements.append(.range(.upToNextMinor(from: upToNextMinorFrom)))
}

if requirements.count > 1 {
throw StringError("must specify at most one of --exact, --branch, --revision, --from, or --up-to-next-minor-from")
}

guard let firstRequirement = requirements.first else {
throw StringError("must specify one of --exact, --branch, --revision, --from, or --up-to-next-minor-from")
}

let requirement: PackageDependency.SourceControl.Requirement
if case .range(let range) = firstRequirement {
if let to {
requirement = .range(range.lowerBound..<to)
} else {
requirement = .range(range)
}
} else {
requirement = firstRequirement

if to != nil {
throw StringError("--to can only be specified with --from or --up-to-next-minor-from")
}
}

// Figure out the location of the package.
let location: PackageDependency.SourceControl.Location
if let path = try? Basics.AbsolutePath(validating: dependency) {
location = .local(path)
} else {
location = .remote(.init(dependency))
}

let packageDependency: PackageDependency = .sourceControl(
identity: identity,
nameForTargetDependencyResolutionOnly: nil,
location: location,
requirement: requirement,
productFilter: .everything
)

let editResult = try AddPackageDependency.addPackageDependency(
packageDependency,
to: manifestSyntax
)

try editResult.applyEdits(
to: fileSystem,
manifest: manifestSyntax,
manifestPath: manifestPath,
verbose: !globalOptions.logging.quiet
)
}
}
}
118 changes: 118 additions & 0 deletions Sources/Commands/PackageCommands/AddProduct.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2024 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 PackageModelSyntax
import SwiftParser
import SwiftSyntax
import TSCBasic
import TSCUtility
import Workspace

extension SwiftPackageCommand {
struct AddProduct: SwiftCommand {
/// The package product type used for the command-line. This is a
/// subset of `ProductType` that expands out the library types.
enum CommandProductType: String, Codable, ExpressibleByArgument {
case executable
case library
case staticLibrary = "static-library"
case dynamicLibrary = "dynamic-library"
case plugin
}

package static let configuration = CommandConfiguration(
abstract: "Add a new product to the manifest")

@OptionGroup(visibility: .hidden)
var globalOptions: GlobalOptions

@Argument(help: "The name of the new product")
var name: String

@Option(help: "The type of target to add, which can be one of 'executable', 'library', 'static-library', 'dynamic-library', or 'plugin'")
var type: CommandProductType = .library

@Option(
parsing: .upToNextOption,
help: "A list of targets that are part of this product"
)
var targets: [String] = []

@Option(help: "The URL for a remote binary target")
var url: String?

@Option(help: "The path to a local binary target")
var path: String?

@Option(help: "The checksum for a remote binary target")
var checksum: String?

func run(_ swiftCommandState: SwiftCommandState) throws {
let workspace = try swiftCommandState.getActiveWorkspace()

guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else {
throw StringError("unknown package")
}

// Load the manifest file
let fileSystem = workspace.fileSystem
let manifestPath = packagePath.appending("Package.swift")
let manifestContents: ByteString
do {
manifestContents = try fileSystem.readFileContents(manifestPath)
} catch {
throw StringError("cannot find package manifest in \(manifestPath)")
}

// Parse the manifest.
let manifestSyntax = manifestContents.withData { data in
data.withUnsafeBytes { buffer in
buffer.withMemoryRebound(to: UInt8.self) { buffer in
Parser.parse(source: buffer)
}
}
}

// Map the product type.
let type: ProductType = switch self.type {
case .executable: .executable
case .library: .library(.automatic)
case .dynamicLibrary: .library(.dynamic)
case .staticLibrary: .library(.static)
case .plugin: .plugin
}

let product = try ProductDescription(
name: name,
type: type,
targets: targets
)

let editResult = try PackageModelSyntax.AddProduct.addProduct(
product,
to: manifestSyntax
)

try editResult.applyEdits(
to: fileSystem,
manifest: manifestSyntax,
manifestPath: manifestPath,
verbose: !globalOptions.logging.quiet
)
}
}
}

Loading