From 84e7ac1d1db052c659229d457bddb9a97be6cb29 Mon Sep 17 00:00:00 2001 From: Anders Bertelrud Date: Fri, 20 May 2016 16:16:07 -0700 Subject: [PATCH 1/2] [SE-0085] Add a `package` subcommand to handle package-oriented operations. This moves the package-management commands from the `build` subcommand to `package`, but leaves building (and, currently, cleaning) on the `build` subcommand. The `test` subcommand is likewise unmodified. I had to temporarily move down the enums for InitMode and ShowDependen- ciesMode, so that both package and build can share them, but this will be consolidated again when build and test are reimplemented in terms of package as well. The build and test functionality is not yet moved in order to minimize changes while still ending up with the desired func- tionality. --- Package.swift | 4 + Sources/Commands/SwiftBuildTool.swift | 52 ---- Sources/Commands/SwiftPackageTool.swift | 295 +++++++++++++++++++++++ Sources/Commands/init.swift | 24 ++ Sources/Commands/show-dependencies.swift | 31 +++ Sources/swift-package/main.swift | 14 ++ 6 files changed, 368 insertions(+), 52 deletions(-) create mode 100644 Sources/Commands/SwiftPackageTool.swift create mode 100644 Sources/swift-package/main.swift diff --git a/Package.swift b/Package.swift index 703b073be21..b08b3d6d86f 100644 --- a/Package.swift +++ b/Package.swift @@ -89,6 +89,10 @@ let package = Package( dependencies: ["Basic", "Build", "Get", "PackageGraph", "Xcodeproj"]), Target( /** The main executable provided by SwiftPM */ + name: "swift-package", + dependencies: ["Commands"]), + Target( + /** Builds packages */ name: "swift-build", dependencies: ["Commands"]), Target( diff --git a/Sources/Commands/SwiftBuildTool.swift b/Sources/Commands/SwiftBuildTool.swift index 5fce7805095..162615ced9c 100644 --- a/Sources/Commands/SwiftBuildTool.swift +++ b/Sources/Commands/SwiftBuildTool.swift @@ -401,58 +401,6 @@ enum CleanMode: CustomStringConvertible { } } -enum InitMode: CustomStringConvertible { - case Library, Executable - - private init(_ rawValue: String?) throws { - switch rawValue?.lowercased() { - case "library"?, "lib"?: - self = .Library - case nil, "executable"?, "exec"?, "exe"?: - self = .Executable - default: - throw OptionParserError.InvalidUsage("invalid initialization mode: \(rawValue)") - } - } - - var description: String { - switch self { - case .Library: return "library" - case .Executable: return "executable" - } - } -} - private func ==(lhs: Mode, rhs: Mode) -> Bool { return lhs.description == rhs.description } - -enum ShowDependenciesMode: CustomStringConvertible { - case Text, DOT, JSON - - private init(_ rawValue: String?) throws { - guard let rawValue = rawValue else { - self = .Text - return - } - - switch rawValue.lowercased() { - case "text": - self = .Text - case "dot": - self = .DOT - case "json": - self = .JSON - default: - throw OptionParserError.InvalidUsage("invalid show dependencies mode: \(rawValue)") - } - } - - var description: String { - switch self { - case .Text: return "text" - case .DOT: return "dot" - case .JSON: return "json" - } - } -} diff --git a/Sources/Commands/SwiftPackageTool.swift b/Sources/Commands/SwiftPackageTool.swift new file mode 100644 index 00000000000..300cd8d351d --- /dev/null +++ b/Sources/Commands/SwiftPackageTool.swift @@ -0,0 +1,295 @@ +/* + This source file is part of the Swift.org open source project + + Copyright 2015 - 2016 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 Swift project authors +*/ + +import Basic +import Build +import Get +import PackageLoading +import PackageModel +import Utility +import Xcodeproj + +#if HasCustomVersionString +import VersionInfo +#endif + +import enum Build.Configuration +import enum Utility.ColorWrap +import protocol Build.Toolchain + +import func POSIX.chdir + +/// Additional conformance for our Options type. +extension PackageToolOptions: XcodeprojOptions {} + +private enum Mode: Argument, Equatable, CustomStringConvertible { + case Init(InitMode) + case Doctor + case ShowDependencies(ShowDependenciesMode) + case Fetch + case Update + case Usage + case Version + case GenerateXcodeproj(String?) + case DumpPackage(String?) + + init?(argument: String, pop: () -> String?) throws { + switch argument { + case "init", "initialize": + self = try .Init(InitMode(pop())) + case "doctor": + self = .Doctor + case "show-dependencies", "-D": + self = try .ShowDependencies(ShowDependenciesMode(pop())) + case "fetch": + self = .Fetch + case "update": + self = .Update + case "help", "usage", "-h": + self = .Usage + case "version": + self = .Version + case "generate-xcodeproj": + self = .GenerateXcodeproj(pop()) + case "dump-package": + self = .DumpPackage(pop()) + default: + return nil + } + } + + var description: String { + switch self { + case .Init(let type): return "init=\(type)" + case .Doctor: return "doctor" + case .ShowDependencies: return "show-dependencies" + case .GenerateXcodeproj: return "generate-xcodeproj" + case .Fetch: return "fetch" + case .Update: return "update" + case .Usage: return "help" + case .Version: return "version" + case .DumpPackage: return "dump-package" + } + } +} + +private enum PackageToolFlag: Argument { + case chdir(String) + case colorMode(ColorWrap.Mode) + case Xcc(String) + case Xld(String) + case Xswiftc(String) + case xcconfigOverrides(String) + case ignoreDependencies + case verbose(Int) + + init?(argument: String, pop: () -> String?) throws { + + func forcePop() throws -> String { + guard let value = pop() else { throw OptionParserError.ExpectedAssociatedValue(argument) } + return value + } + + switch argument { + case Flag.chdir, Flag.C: + self = try .chdir(forcePop()) + case "--verbose", "-v": + self = .verbose(1) + case "-vv": + self = .verbose(2) + case "--color": + let rawValue = try forcePop() + guard let mode = ColorWrap.Mode(rawValue) else { + throw OptionParserError.InvalidUsage("invalid color mode: \(rawValue)") + } + self = .colorMode(mode) + case "--ignore-dependencies": + self = .ignoreDependencies + default: + return nil + } + } +} + +private class PackageToolOptions: Options { + var verbosity: Int = 0 + var colorMode: ColorWrap.Mode = .Auto + var Xcc: [String] = [] + var Xld: [String] = [] + var Xswiftc: [String] = [] + var xcconfigOverrides: String? = nil + var ignoreDependencies: Bool = false +} + +/// swift-build tool namespace +public struct SwiftPackageTool { + let args: [String] + + public init(args: [String]) { + self.args = args + } + + public func run() { + do { + let args = Array(Process.arguments.dropFirst()) + let (mode, opts) = try parse(commandLineArguments: args) + + verbosity = Verbosity(rawValue: opts.verbosity) + colorMode = opts.colorMode + + if let dir = opts.chdir { + try chdir(dir) + } + + func parseManifest(path: String, baseURL: String) throws -> Manifest { + let swiftc = ToolDefaults.SWIFT_EXEC + let libdir = ToolDefaults.libdir + return try Manifest(path: path, baseURL: baseURL, swiftc: swiftc, libdir: libdir) + } + + func fetch(_ root: String) throws -> (rootPackage: Package, externalPackages:[Package]) { + let manifest = try parseManifest(path: root, baseURL: root) + if opts.ignoreDependencies { + return (Package(manifest: manifest, url: manifest.path.parentDirectory), []) + } else { + return try get(manifest, manifestParser: parseManifest) + } + } + + switch mode { + case .Init(let initMode): + let initPackage = try InitPackage(mode: initMode) + try initPackage.writePackageStructure() + + case .Update: + try Utility.removeFileTree(opts.path.Packages) + fallthrough + + case .Fetch: + _ = try fetch(opts.path.root) + + case .Usage: + usage() + + case .Doctor: + doctor() + + case .ShowDependencies(let mode): + let (rootPackage, _) = try fetch(opts.path.root) + dumpDependenciesOf(rootPackage: rootPackage, mode: mode) + + case .Version: + #if HasCustomVersionString + print(String(cString: VersionInfo.DisplayString())) + #else + print("Swift Package Manager – Swift 3.0") + #endif + + case .GenerateXcodeproj(let outpath): + let (rootPackage, externalPackages) = try fetch(opts.path.root) + let (modules, externalModules, products) = try transmute(rootPackage, externalPackages: externalPackages) + + let xcodeModules = modules.flatMap { $0 as? XcodeModuleProtocol } + let externalXcodeModules = externalModules.flatMap { $0 as? XcodeModuleProtocol } + + let projectName: String + let dstdir: String + let packageName = rootPackage.name + + switch outpath { + case let outpath? where outpath.hasSuffix(".xcodeproj"): + // if user specified path ending with .xcodeproj, use that + projectName = String(outpath.basename.characters.dropLast(10)) + dstdir = outpath.parentDirectory + case let outpath?: + dstdir = outpath + projectName = packageName + case _: + dstdir = opts.path.root + projectName = packageName + } + let outpath = try Xcodeproj.generate(dstdir: dstdir.abspath, projectName: projectName, srcroot: opts.path.root, modules: xcodeModules, externalModules: externalXcodeModules, products: products, options: opts) + + print("generated:", outpath.prettyPath) + + case .DumpPackage(let packagePath): + + let root = packagePath ?? opts.path.root + let manifest = try parseManifest(path: root, baseURL: root) + let package = manifest.package + let json = try jsonString(package: package) + print(json) + } + + } catch { + handle(error: error, usage: usage) + } + } + + private func usage(_ print: (String) -> Void = { print($0) }) { + // .........10.........20.........30.........40.........50.........60.........70.. + print("OVERVIEW: Perform operations on a swift package") + print("") + print("USAGE: swift package [command] [options]") + print("") + print("COMMANDS:") + print(" init[=] Initialize a new package (executable|library)") + print(" fetch Fetch package dependencies") + print(" update Update package dependencies") + print(" generate-xcodeproj[=] Generates an Xcode project") + print(" show-dependencies[=] Print dependency graph (text|dot|json)") + print(" dump-package[=] Print Package.swift as JSON") + print("") + print("OPTIONS:") + print(" --chdir Change working directory before any command [-C]") + print(" --color Specify color mode (auto|always|never)") + print(" --verbose Increase verbosity of informational output [-v]") + print(" -Xcc Pass flag through to all C compiler instantiations") + print(" -Xlinker Pass flag through to all linker instantiations") + print(" -Xswiftc Pass flag through to all Swift compiler instantiations") + print("") + } + + private func parse(commandLineArguments args: [String]) throws -> (Mode, PackageToolOptions) { + let (mode, flags): (Mode?, [PackageToolFlag]) = try Basic.parseOptions(arguments: args) + + let opts = PackageToolOptions() + for flag in flags { + switch flag { + case .chdir(let path): + opts.chdir = path + case .Xcc(let value): + opts.Xcc.append(value) + case .Xld(let value): + opts.Xld.append(value) + case .Xswiftc(let value): + opts.Xswiftc.append(value) + case .verbose(let amount): + opts.verbosity += amount + case .colorMode(let mode): + opts.colorMode = mode + case .xcconfigOverrides(let path): + opts.xcconfigOverrides = path + case .ignoreDependencies: + opts.ignoreDependencies = true + } + } + if let mode = mode { + return (mode, opts) + } + else { + throw OptionParserError.InvalidUsage("no command provided: \(args)") + } + } +} + +private func ==(lhs: Mode, rhs: Mode) -> Bool { + return lhs.description == rhs.description +} diff --git a/Sources/Commands/init.swift b/Sources/Commands/init.swift index 6a87fd94ad4..65b6b88ce04 100644 --- a/Sources/Commands/init.swift +++ b/Sources/Commands/init.swift @@ -8,6 +8,7 @@ See http://swift.org/CONTRIBUTORS.txt for Swift project authors */ +import Basic import PackageModel import POSIX @@ -162,3 +163,26 @@ final class InitPackage { try fputs("}\n", testsFileFP) } } + +/// Represents a package type for the purposes of initialization. +enum InitMode: CustomStringConvertible { + case Library, Executable + + init(_ rawValue: String?) throws { + switch rawValue?.lowercased() { + case "library"?, "lib"?: + self = .Library + case nil, "executable"?, "exec"?, "exe"?: + self = .Executable + default: + throw OptionParserError.InvalidUsage("invalid initialization type: \(rawValue)") + } + } + + var description: String { + switch self { + case .Library: return "library" + case .Executable: return "executable" + } + } +} diff --git a/Sources/Commands/show-dependencies.swift b/Sources/Commands/show-dependencies.swift index 9a096c99f1e..30c5cb93d11 100644 --- a/Sources/Commands/show-dependencies.swift +++ b/Sources/Commands/show-dependencies.swift @@ -8,6 +8,7 @@ See http://swift.org/CONTRIBUTORS.txt for Swift project authors */ +import Basic import PackageModel import struct PackageDescription.Version @@ -115,3 +116,33 @@ private final class JsonDumper: DependenciesDumper { recursiveWalk(rootpkg: rootpkg) } } + +enum ShowDependenciesMode: CustomStringConvertible { + case Text, DOT, JSON + + init(_ rawValue: String?) throws { + guard let rawValue = rawValue else { + self = .Text + return + } + + switch rawValue.lowercased() { + case "text": + self = .Text + case "dot": + self = .DOT + case "json": + self = .JSON + default: + throw OptionParserError.InvalidUsage("invalid show dependencies mode: \(rawValue)") + } + } + + var description: String { + switch self { + case .Text: return "text" + case .DOT: return "dot" + case .JSON: return "json" + } + } +} diff --git a/Sources/swift-package/main.swift b/Sources/swift-package/main.swift new file mode 100644 index 00000000000..a28afe40106 --- /dev/null +++ b/Sources/swift-package/main.swift @@ -0,0 +1,14 @@ +/* + This source file is part of the Swift.org open source project + + Copyright 2015 - 2016 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 Swift project authors +*/ + +import Commands + +let tool = SwiftPackageTool(args: Array(Process.arguments.dropFirst())) +tool.run() From d0e65e5d070b19b4e28a9c84d71e34e4edb9ce75 Mon Sep 17 00:00:00 2001 From: Anders Bertelrud Date: Sat, 21 May 2016 21:22:06 -0700 Subject: [PATCH 2/2] Add back --help as a way to get help --- Sources/Commands/SwiftPackageTool.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Commands/SwiftPackageTool.swift b/Sources/Commands/SwiftPackageTool.swift index 300cd8d351d..655008e5ce9 100644 --- a/Sources/Commands/SwiftPackageTool.swift +++ b/Sources/Commands/SwiftPackageTool.swift @@ -52,7 +52,7 @@ private enum Mode: Argument, Equatable, CustomStringConvertible { self = .Fetch case "update": self = .Update - case "help", "usage", "-h": + case "help", "usage", "--help", "-h": self = .Usage case "version": self = .Version