Skip to content
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

[SE-0085] Add a package subcommand to handle package-oriented operations #364

Merged
merged 2 commits into from
May 22, 2016
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
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
52 changes: 0 additions & 52 deletions Sources/Commands/SwiftBuildTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
295 changes: 295 additions & 0 deletions Sources/Commands/SwiftPackageTool.swift
Original file line number Diff line number Diff line change
@@ -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", "--help", "-h":
self = .Usage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also --help?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, I got right of that by mistake. I'll add it back and update the PR. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in d0e65e5

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[=<type>] Initialize a new package (executable|library)")
print(" fetch Fetch package dependencies")
print(" update Update package dependencies")
print(" generate-xcodeproj[=<path>] Generates an Xcode project")
print(" show-dependencies[=<format>] Print dependency graph (text|dot|json)")
print(" dump-package[=<path>] Print Package.swift as JSON")
print("")
print("OPTIONS:")
print(" --chdir <path> Change working directory before any command [-C]")
print(" --color <mode> Specify color mode (auto|always|never)")
print(" --verbose Increase verbosity of informational output [-v]")
print(" -Xcc <flag> Pass flag through to all C compiler instantiations")
print(" -Xlinker <flag> Pass flag through to all linker instantiations")
print(" -Xswiftc <flag> Pass flag through to all Swift compiler instantiations")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random thought, I wonder if xcodeproj generation might be ok to leave on swift build? That would allow us to keep all build related options there. From a certain perspective it is a "different way of building" although obvious from other perspectives it is a completely separate command.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered that, but it really didn't feel right from a usage perspective. We can open up a separate SE for that, I think. There is still some refactoring to do here, and I think we can find a way to not have to repeat this information, from a technical perspective (through a richer way of being able to define options, and which options apply to which subcommands).

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
}
Loading