Skip to content

Commit a8fb81c

Browse files
committed
Merge pull request #364 from abertelrud/package-subcommand
[SE-0085] Add a `package` subcommand to handle package-oriented operations
2 parents b81b2b9 + d0e65e5 commit a8fb81c

File tree

6 files changed

+368
-52
lines changed

6 files changed

+368
-52
lines changed

Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ let package = Package(
8989
dependencies: ["Basic", "Build", "Get", "PackageGraph", "Xcodeproj"]),
9090
Target(
9191
/** The main executable provided by SwiftPM */
92+
name: "swift-package",
93+
dependencies: ["Commands"]),
94+
Target(
95+
/** Builds packages */
9296
name: "swift-build",
9397
dependencies: ["Commands"]),
9498
Target(

Sources/Commands/SwiftBuildTool.swift

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -401,58 +401,6 @@ enum CleanMode: CustomStringConvertible {
401401
}
402402
}
403403

404-
enum InitMode: CustomStringConvertible {
405-
case Library, Executable
406-
407-
private init(_ rawValue: String?) throws {
408-
switch rawValue?.lowercased() {
409-
case "library"?, "lib"?:
410-
self = .Library
411-
case nil, "executable"?, "exec"?, "exe"?:
412-
self = .Executable
413-
default:
414-
throw OptionParserError.InvalidUsage("invalid initialization mode: \(rawValue)")
415-
}
416-
}
417-
418-
var description: String {
419-
switch self {
420-
case .Library: return "library"
421-
case .Executable: return "executable"
422-
}
423-
}
424-
}
425-
426404
private func ==(lhs: Mode, rhs: Mode) -> Bool {
427405
return lhs.description == rhs.description
428406
}
429-
430-
enum ShowDependenciesMode: CustomStringConvertible {
431-
case Text, DOT, JSON
432-
433-
private init(_ rawValue: String?) throws {
434-
guard let rawValue = rawValue else {
435-
self = .Text
436-
return
437-
}
438-
439-
switch rawValue.lowercased() {
440-
case "text":
441-
self = .Text
442-
case "dot":
443-
self = .DOT
444-
case "json":
445-
self = .JSON
446-
default:
447-
throw OptionParserError.InvalidUsage("invalid show dependencies mode: \(rawValue)")
448-
}
449-
}
450-
451-
var description: String {
452-
switch self {
453-
case .Text: return "text"
454-
case .DOT: return "dot"
455-
case .JSON: return "json"
456-
}
457-
}
458-
}
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2015 - 2016 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Basic
12+
import Build
13+
import Get
14+
import PackageLoading
15+
import PackageModel
16+
import Utility
17+
import Xcodeproj
18+
19+
#if HasCustomVersionString
20+
import VersionInfo
21+
#endif
22+
23+
import enum Build.Configuration
24+
import enum Utility.ColorWrap
25+
import protocol Build.Toolchain
26+
27+
import func POSIX.chdir
28+
29+
/// Additional conformance for our Options type.
30+
extension PackageToolOptions: XcodeprojOptions {}
31+
32+
private enum Mode: Argument, Equatable, CustomStringConvertible {
33+
case Init(InitMode)
34+
case Doctor
35+
case ShowDependencies(ShowDependenciesMode)
36+
case Fetch
37+
case Update
38+
case Usage
39+
case Version
40+
case GenerateXcodeproj(String?)
41+
case DumpPackage(String?)
42+
43+
init?(argument: String, pop: () -> String?) throws {
44+
switch argument {
45+
case "init", "initialize":
46+
self = try .Init(InitMode(pop()))
47+
case "doctor":
48+
self = .Doctor
49+
case "show-dependencies", "-D":
50+
self = try .ShowDependencies(ShowDependenciesMode(pop()))
51+
case "fetch":
52+
self = .Fetch
53+
case "update":
54+
self = .Update
55+
case "help", "usage", "--help", "-h":
56+
self = .Usage
57+
case "version":
58+
self = .Version
59+
case "generate-xcodeproj":
60+
self = .GenerateXcodeproj(pop())
61+
case "dump-package":
62+
self = .DumpPackage(pop())
63+
default:
64+
return nil
65+
}
66+
}
67+
68+
var description: String {
69+
switch self {
70+
case .Init(let type): return "init=\(type)"
71+
case .Doctor: return "doctor"
72+
case .ShowDependencies: return "show-dependencies"
73+
case .GenerateXcodeproj: return "generate-xcodeproj"
74+
case .Fetch: return "fetch"
75+
case .Update: return "update"
76+
case .Usage: return "help"
77+
case .Version: return "version"
78+
case .DumpPackage: return "dump-package"
79+
}
80+
}
81+
}
82+
83+
private enum PackageToolFlag: Argument {
84+
case chdir(String)
85+
case colorMode(ColorWrap.Mode)
86+
case Xcc(String)
87+
case Xld(String)
88+
case Xswiftc(String)
89+
case xcconfigOverrides(String)
90+
case ignoreDependencies
91+
case verbose(Int)
92+
93+
init?(argument: String, pop: () -> String?) throws {
94+
95+
func forcePop() throws -> String {
96+
guard let value = pop() else { throw OptionParserError.ExpectedAssociatedValue(argument) }
97+
return value
98+
}
99+
100+
switch argument {
101+
case Flag.chdir, Flag.C:
102+
self = try .chdir(forcePop())
103+
case "--verbose", "-v":
104+
self = .verbose(1)
105+
case "-vv":
106+
self = .verbose(2)
107+
case "--color":
108+
let rawValue = try forcePop()
109+
guard let mode = ColorWrap.Mode(rawValue) else {
110+
throw OptionParserError.InvalidUsage("invalid color mode: \(rawValue)")
111+
}
112+
self = .colorMode(mode)
113+
case "--ignore-dependencies":
114+
self = .ignoreDependencies
115+
default:
116+
return nil
117+
}
118+
}
119+
}
120+
121+
private class PackageToolOptions: Options {
122+
var verbosity: Int = 0
123+
var colorMode: ColorWrap.Mode = .Auto
124+
var Xcc: [String] = []
125+
var Xld: [String] = []
126+
var Xswiftc: [String] = []
127+
var xcconfigOverrides: String? = nil
128+
var ignoreDependencies: Bool = false
129+
}
130+
131+
/// swift-build tool namespace
132+
public struct SwiftPackageTool {
133+
let args: [String]
134+
135+
public init(args: [String]) {
136+
self.args = args
137+
}
138+
139+
public func run() {
140+
do {
141+
let args = Array(Process.arguments.dropFirst())
142+
let (mode, opts) = try parse(commandLineArguments: args)
143+
144+
verbosity = Verbosity(rawValue: opts.verbosity)
145+
colorMode = opts.colorMode
146+
147+
if let dir = opts.chdir {
148+
try chdir(dir)
149+
}
150+
151+
func parseManifest(path: String, baseURL: String) throws -> Manifest {
152+
let swiftc = ToolDefaults.SWIFT_EXEC
153+
let libdir = ToolDefaults.libdir
154+
return try Manifest(path: path, baseURL: baseURL, swiftc: swiftc, libdir: libdir)
155+
}
156+
157+
func fetch(_ root: String) throws -> (rootPackage: Package, externalPackages:[Package]) {
158+
let manifest = try parseManifest(path: root, baseURL: root)
159+
if opts.ignoreDependencies {
160+
return (Package(manifest: manifest, url: manifest.path.parentDirectory), [])
161+
} else {
162+
return try get(manifest, manifestParser: parseManifest)
163+
}
164+
}
165+
166+
switch mode {
167+
case .Init(let initMode):
168+
let initPackage = try InitPackage(mode: initMode)
169+
try initPackage.writePackageStructure()
170+
171+
case .Update:
172+
try Utility.removeFileTree(opts.path.Packages)
173+
fallthrough
174+
175+
case .Fetch:
176+
_ = try fetch(opts.path.root)
177+
178+
case .Usage:
179+
usage()
180+
181+
case .Doctor:
182+
doctor()
183+
184+
case .ShowDependencies(let mode):
185+
let (rootPackage, _) = try fetch(opts.path.root)
186+
dumpDependenciesOf(rootPackage: rootPackage, mode: mode)
187+
188+
case .Version:
189+
#if HasCustomVersionString
190+
print(String(cString: VersionInfo.DisplayString()))
191+
#else
192+
print("Swift Package Manager – Swift 3.0")
193+
#endif
194+
195+
case .GenerateXcodeproj(let outpath):
196+
let (rootPackage, externalPackages) = try fetch(opts.path.root)
197+
let (modules, externalModules, products) = try transmute(rootPackage, externalPackages: externalPackages)
198+
199+
let xcodeModules = modules.flatMap { $0 as? XcodeModuleProtocol }
200+
let externalXcodeModules = externalModules.flatMap { $0 as? XcodeModuleProtocol }
201+
202+
let projectName: String
203+
let dstdir: String
204+
let packageName = rootPackage.name
205+
206+
switch outpath {
207+
case let outpath? where outpath.hasSuffix(".xcodeproj"):
208+
// if user specified path ending with .xcodeproj, use that
209+
projectName = String(outpath.basename.characters.dropLast(10))
210+
dstdir = outpath.parentDirectory
211+
case let outpath?:
212+
dstdir = outpath
213+
projectName = packageName
214+
case _:
215+
dstdir = opts.path.root
216+
projectName = packageName
217+
}
218+
let outpath = try Xcodeproj.generate(dstdir: dstdir.abspath, projectName: projectName, srcroot: opts.path.root, modules: xcodeModules, externalModules: externalXcodeModules, products: products, options: opts)
219+
220+
print("generated:", outpath.prettyPath)
221+
222+
case .DumpPackage(let packagePath):
223+
224+
let root = packagePath ?? opts.path.root
225+
let manifest = try parseManifest(path: root, baseURL: root)
226+
let package = manifest.package
227+
let json = try jsonString(package: package)
228+
print(json)
229+
}
230+
231+
} catch {
232+
handle(error: error, usage: usage)
233+
}
234+
}
235+
236+
private func usage(_ print: (String) -> Void = { print($0) }) {
237+
// .........10.........20.........30.........40.........50.........60.........70..
238+
print("OVERVIEW: Perform operations on a swift package")
239+
print("")
240+
print("USAGE: swift package [command] [options]")
241+
print("")
242+
print("COMMANDS:")
243+
print(" init[=<type>] Initialize a new package (executable|library)")
244+
print(" fetch Fetch package dependencies")
245+
print(" update Update package dependencies")
246+
print(" generate-xcodeproj[=<path>] Generates an Xcode project")
247+
print(" show-dependencies[=<format>] Print dependency graph (text|dot|json)")
248+
print(" dump-package[=<path>] Print Package.swift as JSON")
249+
print("")
250+
print("OPTIONS:")
251+
print(" --chdir <path> Change working directory before any command [-C]")
252+
print(" --color <mode> Specify color mode (auto|always|never)")
253+
print(" --verbose Increase verbosity of informational output [-v]")
254+
print(" -Xcc <flag> Pass flag through to all C compiler instantiations")
255+
print(" -Xlinker <flag> Pass flag through to all linker instantiations")
256+
print(" -Xswiftc <flag> Pass flag through to all Swift compiler instantiations")
257+
print("")
258+
}
259+
260+
private func parse(commandLineArguments args: [String]) throws -> (Mode, PackageToolOptions) {
261+
let (mode, flags): (Mode?, [PackageToolFlag]) = try Basic.parseOptions(arguments: args)
262+
263+
let opts = PackageToolOptions()
264+
for flag in flags {
265+
switch flag {
266+
case .chdir(let path):
267+
opts.chdir = path
268+
case .Xcc(let value):
269+
opts.Xcc.append(value)
270+
case .Xld(let value):
271+
opts.Xld.append(value)
272+
case .Xswiftc(let value):
273+
opts.Xswiftc.append(value)
274+
case .verbose(let amount):
275+
opts.verbosity += amount
276+
case .colorMode(let mode):
277+
opts.colorMode = mode
278+
case .xcconfigOverrides(let path):
279+
opts.xcconfigOverrides = path
280+
case .ignoreDependencies:
281+
opts.ignoreDependencies = true
282+
}
283+
}
284+
if let mode = mode {
285+
return (mode, opts)
286+
}
287+
else {
288+
throw OptionParserError.InvalidUsage("no command provided: \(args)")
289+
}
290+
}
291+
}
292+
293+
private func ==(lhs: Mode, rhs: Mode) -> Bool {
294+
return lhs.description == rhs.description
295+
}

0 commit comments

Comments
 (0)