Skip to content

Commit

Permalink
Rewrite spm-manifest-tool using swift-argument-parser and the updated…
Browse files Browse the repository at this point in the history
… SPMPackageEditor
  • Loading branch information
owenv committed Nov 8, 2020
1 parent 5b26311 commit 07948f6
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 166 deletions.
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -316,5 +316,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] != nil {
dependencies: ["Workspace", "PackageModel", "PackageLoading",
"SourceControl", "SwiftSyntax", "SwiftToolsSupport-auto"]),
.testTarget(name: "SPMPackageEditorTests", dependencies: ["SPMPackageEditor", "SPMTestSupport"]),
.target(name: "swiftpm-manifest-tool",
dependencies: ["SPMPackageEditor", "ArgumentParser"])
]
}
100 changes: 37 additions & 63 deletions Sources/SPMPackageEditor/PackageEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ public final class PackageEditor {
let context: PackageEditorContext

/// Create a package editor instance.
public convenience init(buildDir: AbsolutePath, toolchain: UserToolchain) throws {
self.init(context: try PackageEditorContext(buildDir: buildDir, toolchain: toolchain))
public convenience init(manifestPath: AbsolutePath,
buildDir: AbsolutePath,
toolchain: UserToolchain) throws {
self.init(context: try PackageEditorContext(manifestPath: manifestPath,
buildDir: buildDir,
toolchain: toolchain))
}

/// Create a package editor instance.
Expand All @@ -41,41 +45,40 @@ public final class PackageEditor {
}

/// Add a package dependency.
public func addPackageDependency(options: Options.AddPackageDependency) throws {
var options = options

public func addPackageDependency(url: String, requirement: PackageDependencyRequirement?) throws {
var requirement = requirement
let manifestPath = context.manifestPath
// Validate that the package doesn't already contain this dependency.
// FIXME: We need to handle version-specific manifests.
let loadedManifest = try context.loadManifest(at: options.manifestPath.parentDirectory)
let loadedManifest = try context.loadManifest(at: context.manifestPath.parentDirectory)
let containsDependency = loadedManifest.dependencies.contains {
return PackageReference.computeIdentity(packageURL: options.url) == PackageReference.computeIdentity(packageURL: $0.url)
return PackageReference.computeIdentity(packageURL: url) == PackageReference.computeIdentity(packageURL: $0.url)
}
guard !containsDependency else {
throw StringError("Already has dependency \(options.url)")
throw StringError("Already has dependency \(url)")
}

// If the input URL is a path, force the requirement to be a local package.
if TSCUtility.URL.scheme(options.url) == nil {
assert(options.requirement == nil || options.requirement == .localPackage)
options.requirement = .localPackage
if TSCUtility.URL.scheme(url) == nil {
assert(requirement == nil || requirement == .localPackage)
requirement = .localPackage
}

// Load the dependency manifest depending on the inputs.
let dependencyManifest: Manifest
let requirement: PackageDependencyRequirement
if options.requirement == .localPackage {
if requirement == .localPackage {
// For local packages, load the manifest and get the first library product name.
let path = AbsolutePath(options.url, relativeTo: fs.currentWorkingDirectory!)
let path = AbsolutePath(url, relativeTo: fs.currentWorkingDirectory!)
dependencyManifest = try context.loadManifest(at: path)
requirement = .localPackage
} else {
// Otherwise, first lookup the dependency.
let spec = RepositorySpecifier(url: options.url)
let spec = RepositorySpecifier(url: url)
let handle = try await{ context.repositoryManager.lookup(repository: spec, completion: $0) }
let repo = try handle.open()

// Compute the requirement.
if let inputRequirement = options.requirement {
if let inputRequirement = requirement {
requirement = inputRequirement
} else {
// Use the latest version or the master branch.
Expand All @@ -85,15 +88,15 @@ public final class PackageEditor {
}

// Load the manifest.
let revision = try repo.resolveRevision(identifier: requirement.ref!)
let revision = try repo.resolveRevision(identifier: requirement!.ref!)
let repoFS = try repo.openFileView(revision: revision)
dependencyManifest = try context.loadManifest(at: .root, fs: repoFS)
}

// Add the package dependency.
let manifestContents = try fs.readFileContents(options.manifestPath).cString
let manifestContents = try fs.readFileContents(manifestPath).cString
let editor = try ManifestRewriter(manifestContents)
try editor.addPackageDependency(url: options.url, requirement: requirement)
try editor.addPackageDependency(url: url, requirement: requirement!)

// Add the product in the first regular target, if possible.
let productName = dependencyManifest.products.filter{ $0.type.isLibrary }.map{ $0.name }.first
Expand All @@ -105,40 +108,39 @@ public final class PackageEditor {
}

// FIXME: We should verify our edits by loading the edited manifest before writing it to disk.
try fs.writeFileContents(options.manifestPath, bytes: ByteString(encodingAsUTF8: editor.editedManifest))
try fs.writeFileContents(manifestPath, bytes: ByteString(encodingAsUTF8: editor.editedManifest))
}

/// Add a new target.
public func addTarget(options: Options.AddTarget) throws {
let manifest = options.manifestPath
let targetName = options.targetName
public func addTarget(name targetName: String, type targetType: TargetType?) throws {
let manifestPath = context.manifestPath
let testTargetName = targetName + "Tests"

// Validate that the package doesn't already contain this dependency.
// FIXME: We need to handle version-specific manifests.
let loadedManifest = try context.loadManifest(at: options.manifestPath.parentDirectory)
let loadedManifest = try context.loadManifest(at: manifestPath.parentDirectory)
if loadedManifest.targets.contains(where: { $0.name == targetName }) {
throw StringError("Already has a target named \(targetName)")
}

let manifestContents = try fs.readFileContents(options.manifestPath).cString
let manifestContents = try fs.readFileContents(manifestPath).cString
let editor = try ManifestRewriter(manifestContents)
try editor.addTarget(targetName: targetName)
try editor.addTarget(targetName: testTargetName, type: .test)
try editor.addTargetDependency(target: testTargetName, dependency: targetName)

// FIXME: We should verify our edits by loading the edited manifest before writing it to disk.
try fs.writeFileContents(manifest, bytes: ByteString(encodingAsUTF8: editor.editedManifest))
try fs.writeFileContents(manifestPath, bytes: ByteString(encodingAsUTF8: editor.editedManifest))

// Write template files.
let targetPath = manifest.parentDirectory.appending(components: "Sources", targetName)
let targetPath = manifestPath.parentDirectory.appending(components: "Sources", targetName)
if !localFileSystem.exists(targetPath) {
let file = targetPath.appending(components: targetName + ".swift")
try fs.createDirectory(targetPath)
try fs.writeFileContents(file, bytes: "")
}

let testTargetPath = manifest.parentDirectory.appending(components: "Tests", testTargetName)
let testTargetPath = manifestPath.parentDirectory.appending(components: "Tests", testTargetName)
if !fs.exists(testTargetPath) {
let file = testTargetPath.appending(components: testTargetName + ".swift")
try fs.createDirectory(testTargetPath)
Expand Down Expand Up @@ -204,40 +206,6 @@ public enum PackageDependencyRequirement: Equatable {
}
}

public enum Options {
public struct AddPackageDependency {
public var manifestPath: AbsolutePath
public var url: String
public var requirement: PackageDependencyRequirement?

public init(
manifestPath: AbsolutePath,
url: String,
requirement: PackageDependencyRequirement? = nil
) {
self.manifestPath = manifestPath
self.url = url
self.requirement = requirement
}
}

public struct AddTarget {
public var manifestPath: AbsolutePath
public var targetName: String
public var targetType: TargetType

public init(
manifestPath: AbsolutePath,
targetName: String,
targetType: TargetType = .regular
) {
self.manifestPath = manifestPath
self.targetName = targetName
self.targetType = targetType
}
}
}

extension ProductType {
var isLibrary: Bool {
switch self {
Expand All @@ -251,6 +219,8 @@ extension ProductType {

/// The global context for package editor.
public final class PackageEditorContext {
/// Path to the package manifest.
let manifestPath: AbsolutePath

/// Path to the build directory of the package.
let buildDir: AbsolutePath
Expand All @@ -264,7 +234,11 @@ public final class PackageEditorContext {
/// The file system in use.
let fs: FileSystem

public init(buildDir: AbsolutePath, toolchain: UserToolchain, fs: FileSystem = localFileSystem) throws {
public init(manifestPath: AbsolutePath,
buildDir: AbsolutePath,
toolchain: UserToolchain,
fs: FileSystem = localFileSystem) throws {
self.manifestPath = manifestPath
self.buildDir = buildDir
self.fs = fs

Expand Down

0 comments on commit 07948f6

Please sign in to comment.