Skip to content

Commit 5c104d4

Browse files
committed
Generate Xcode projects
We create dynamic libraries for modules since there is no concept in Xcode itself of a target that has no binary product (SwiftPM compiles the modules but the eventual binary products are declared separately, so two modules can be linked into the same binary target). There are things left to do, see TODO.md.
1 parent fc8d089 commit 5c104d4

File tree

13 files changed

+639
-32
lines changed

13 files changed

+639
-32
lines changed

Package.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let package = Package(
2525
/** Base types for the package-engine */
2626
name: "PackageType",
2727
dependencies: ["PackageDescription", "Utility"]), //FIXME dependency on PackageDescription sucks
28-
Target( //FIXME Carpet is too general, we only need `Path`
28+
Target( //FIXME Utility is too general, we only need `Path`
2929
name: "ManifestParser",
3030
dependencies: ["PackageDescription", "PackageType"]),
3131
Target(
@@ -44,9 +44,12 @@ let package = Package(
4444
/** Common components of both executables */
4545
name: "Multitool",
4646
dependencies: ["PackageType"]),
47+
Target(
48+
name: "Xcodeproj",
49+
dependencies: ["PackageType"]),
4750
Target(
4851
name: "swift-build",
49-
dependencies: ["ManifestParser", "Get", "Transmute", "Build", "Multitool"]),
52+
dependencies: ["ManifestParser", "Get", "Transmute", "Build", "Multitool", "Xcodeproj"]),
5053
Target(
5154
name: "swift-test",
5255
dependencies: ["Multitool"]),

Sources/Utility/Toolchain.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,32 @@ public struct Toolchain: Installation {
2626
return path
2727
}
2828
}
29+
30+
#if os(OSX)
31+
if let cmdpath = (try? popen(["xcrun", "--find", cmd]))?.chuzzle() {
32+
return cmdpath
33+
}
34+
#endif
35+
2936
return cmd
3037
}
3138

39+
//TODO better
40+
public static var prefix: String {
41+
return swiftc.parentDirectory.parentDirectory.parentDirectory
42+
}
43+
3244
/// the location of swiftc relative to our installation
33-
public static var swiftc = getenv("SWIFT_EXEC") ?? Toolchain.which("swiftc")
45+
public static let swiftc = getenv("SWIFT_EXEC") ?? Toolchain.which("swiftc")
3446

3547
/// the location of swift_build_tool relatve to our installation
36-
public static var swift_build_tool = getenv("SWIFT_BUILD_TOOL") ?? Toolchain.which("swift-build-tool")
48+
public static let swift_build_tool = getenv("SWIFT_BUILD_TOOL") ?? Toolchain.which("swift-build-tool")
3749
}
3850

3951
#if os(OSX)
4052
extension Toolchain {
41-
public static var sysroot = getenv("SYSROOT") ?? (try? POSIX.popen(["xcrun", "--sdk", "macosx", "--show-sdk-path"]))?.chuzzle()
42-
public static var platformPath = (try? POSIX.popen(["xcrun", "--sdk", "macosx", "--show-sdk-platform-path"]))?.chuzzle()
53+
public static let sysroot = getenv("SYSROOT") ?? (try? POSIX.popen(["xcrun", "--sdk", "macosx", "--show-sdk-path"]))?.chuzzle()
54+
public static let platformPath = (try? POSIX.popen(["xcrun", "--sdk", "macosx", "--show-sdk-platform-path"]))?.chuzzle()
4355
}
4456
#endif
4557

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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+
12+
In an effort to provide:
13+
14+
1. Unique reference identifiers
15+
2. Human readable reference identifiers
16+
3. Stable reference identifiers
17+
18+
(as opposed to the generated UUIDs Xcode typically generates)
19+
20+
We create identifiers with a constant-length unique prefix and
21+
a unique suffix where the suffix is the filename or module name
22+
and since we guarantee uniqueness at the PackageDescription
23+
layer for these properties we satisfy the above constraints.
24+
*/
25+
26+
import struct Utility.Toolchain
27+
import struct Utility.Path
28+
import PackageType
29+
30+
let rootObjectReference = "__RootObject_"
31+
let rootBuildConfigurationListReference = "___RootConfs_"
32+
let rootBuildConfigurationReference = "_______Debug_"
33+
let rootGroupReference = "___RootGroup_"
34+
let productsGroupReference = "____Products_"
35+
let sourcesGroupReference = "_____Sources_"
36+
let testsGroupReference = "_______Tests_"
37+
let linkPhaseFileRefPrefix = "_LinkFileRef_"
38+
let sourceGroupFileRefPrefix = "__PBXFileRef_"
39+
let compilePhaseFileRefPrefix = "__src_cc_ref_"
40+
41+
extension Module {
42+
var dependencyReference: String { return "__Dependency_\(c99name)" }
43+
var productReference: String { return "_____Product_\(c99name)" }
44+
var targetReference: String { return "______Target_\(c99name)" }
45+
var groupReference: String { return "_______Group_\(c99name)" }
46+
var configurationListReference: String { return "_______Confs_\(c99name)" }
47+
var configurationReference: String { return "___DebugConf_\(c99name)" }
48+
var compilePhaseReference: String { return "CompilePhase_\(c99name)" }
49+
var linkPhaseReference: String { return "___LinkPhase_\(c99name)" }
50+
}
51+
52+
func fileRef(forLinkPhaseChild module: Module) -> String {
53+
return linkPhaseFileRefPrefix + module.c99name
54+
}
55+
56+
private func fileRef(suffixForModuleSourceFile path: String, srcroot: String) -> String {
57+
let path = Path(path).relative(to: srcroot)
58+
return path.characters.map{ c -> String in
59+
switch c {
60+
case "\\":
61+
return "\\\\"
62+
case "'":
63+
return "\'"
64+
default:
65+
return "\(c)"
66+
}
67+
}.joinWithSeparator("")
68+
}
69+
70+
func fileRefs(forModuleSources module: SwiftModule, srcroot: String) -> [(String, String)] {
71+
return module.sources.relativePaths.map { relativePath in
72+
let path = Path.join(module.sources.root, relativePath)
73+
let suffix = fileRef(suffixForModuleSourceFile: path, srcroot: srcroot)
74+
return ("'\(sourceGroupFileRefPrefix)\(suffix)'", relativePath)
75+
}
76+
}
77+
78+
func fileRefs(forCompilePhaseSourcesInModule module: SwiftModule, srcroot: String) -> [(String, String)] {
79+
return fileRefs(forModuleSources: module, srcroot: srcroot).map { ref1, relativePath in
80+
let path = Path.join(module.sources.root, relativePath)
81+
let suffix = fileRef(suffixForModuleSourceFile: path, srcroot: srcroot)
82+
return (ref1, "'\(compilePhaseFileRefPrefix)\(suffix)'")
83+
}
84+
}
85+
86+
extension SwiftModule {
87+
private var isLibrary: Bool {
88+
return type == .Library
89+
}
90+
91+
var type: String {
92+
if self is TestModule {
93+
return "com.apple.product-type.bundle.unit-test"
94+
} else if isLibrary {
95+
return "com.apple.product-type.library.dynamic"
96+
} else {
97+
return "com.apple.product-type.tool"
98+
}
99+
}
100+
101+
var explicitFileType: String {
102+
func suffix() -> String {
103+
if self is TestModule {
104+
return "wrapper.cfbundle"
105+
} else if isLibrary {
106+
return "dylib"
107+
} else {
108+
return "executable"
109+
}
110+
}
111+
return "compiled.mach-o.\(suffix())"
112+
}
113+
114+
var productPath: String {
115+
if self is TestModule {
116+
return "\(c99name).xctest"
117+
} else if isLibrary {
118+
return "\(c99name).dylib"
119+
} else {
120+
return name
121+
}
122+
}
123+
124+
var linkPhaseFileRefs: String {
125+
return recursiveDependencies.map{ fileRef(forLinkPhaseChild: $0) }.joinWithSeparator(", ")
126+
}
127+
128+
var nativeTargetDependencies: String {
129+
return dependencies.map{ $0.dependencyReference }.joinWithSeparator(", ")
130+
}
131+
132+
var productName: String {
133+
if isLibrary && !(self is TestModule) {
134+
// you can go without a lib prefix, but something unexpected will break
135+
return "'lib$(TARGET_NAME)'"
136+
} else {
137+
return "'$(TARGET_NAME)'"
138+
}
139+
}
140+
141+
var buildSettings: String {
142+
var buildSettings = ["PRODUCT_NAME": productName]
143+
buildSettings["PRODUCT_MODULE_NAME"] = c99name
144+
buildSettings["OTHER_SWIFT_FLAGS"] = "-DXcode"
145+
buildSettings["MACOSX_DEPLOYMENT_TARGET"] = "'10.10'"
146+
buildSettings["SWIFT_OPTIMIZATION_LEVEL"] = "-Onone"
147+
148+
// prevents Xcode project upgrade warnings
149+
buildSettings["COMBINE_HIDPI_IMAGES"] = "YES"
150+
151+
if self is TestModule {
152+
buildSettings["EMBEDDED_CONTENT_CONTAINS_SWIFT"] = "YES"
153+
154+
//FIXME this should not be required
155+
buildSettings["LD_RUNPATH_SEARCH_PATHS"] = "'@loader_path/../Frameworks'"
156+
157+
} else {
158+
159+
//FIXME we should not have to set this ourselves, and in fact
160+
// it is problematic because now you must regenerate the xcodeproj
161+
// whenever the toolchain changes :(
162+
// static linking would be better since that is what we are meant
163+
// to do while swift has no ABI compatability.
164+
// probably the real solution is to generate frameworks since then
165+
// Xcode will embed the swift runtime libs
166+
buildSettings["LD_RUNPATH_SEARCH_PATHS"] = "'\(Toolchain.prefix)/usr/lib/swift/macosx'"
167+
168+
if isLibrary {
169+
buildSettings["ENABLE_TESTABILITY"] = "YES"
170+
buildSettings["DYLIB_INSTALL_NAME_BASE"] = "'$(CONFIGURATION_BUILD_DIR)'"
171+
} else {
172+
// override default behavior, instead link dynamically
173+
buildSettings["SWIFT_FORCE_STATIC_LINK_STDLIB"] = "NO"
174+
buildSettings["SWIFT_FORCE_DYNAMIC_LINK_STDLIB"] = "YES"
175+
}
176+
}
177+
178+
return buildSettings.map{ "\($0) = \($1);" }.joinWithSeparator(" ")
179+
}
180+
}
181+
182+
183+
extension SwiftModule {
184+
var blueprintIdentifier: String {
185+
return targetReference
186+
}
187+
188+
var buildableName: String {
189+
if isLibrary && !(self is TestModule) {
190+
return "lib\(productPath)"
191+
} else {
192+
return productPath
193+
}
194+
}
195+
196+
var blueprintName: String {
197+
return name
198+
}
199+
}

Sources/Xcodeproj/TODO.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
The following features are welcome, please submit a PR:
2+
3+
* Our references should remain human readable, but they aren't unique enough, eg. if a module is called ProxyFoo then the TargetProxy for Foo will conflict with the Target for ProxyFoo
4+
* “Blue Folder” everything not part of the build
5+
* Split out tests and non-tests in Products group
6+
* Enable code coverage
7+
* Allow frameworks instead of dylibs and add a command line toggle
8+
* Release configuration
9+
* Nest Groups for module sources, eg. Sources/Bar/Foo/Baz.swift would be in Xcode groups: Source -> Bar -> Foo -> Baz.swift
10+
* Put dependencies in Package-named sub groups of a group called "Dependencies"

Sources/Xcodeproj/generate().swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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 PackageType
12+
import Utility
13+
import POSIX
14+
15+
/**
16+
Generates an xcodeproj at the specified path.
17+
- Returns: the path to the generated project
18+
*/
19+
public func generate(path path: String, package: Package, modules: [SwiftModule], products: [Product]) throws -> String {
20+
21+
let rootdir = try mkdir(path, "\(package.name).xcodeproj")
22+
let schemedir = try mkdir(rootdir, "xcshareddata/xcschemes")
23+
24+
////// the pbxproj file describes the project and its targets
25+
try open(rootdir, "project.pbxproj") { fwrite in
26+
pbxproj(package: package, modules: modules, products: products, printer: fwrite)
27+
}
28+
29+
////// the scheme acts like an aggregate target for all our targets
30+
/// it also allows has all tests associated
31+
try open(schemedir, "\(package.name).xcscheme") { fwrite in
32+
xcscheme(packageName: package.name, modules: modules, printer: fwrite)
33+
}
34+
35+
////// we generate this file to ensure our main scheme is listed
36+
/// before any inferred schemes Xcode may autocreate
37+
try open(schemedir, "xcschememanagement.plist") { fwrite in
38+
fwrite("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
39+
fwrite("<plist version=\"1.0\">")
40+
fwrite("<dict>")
41+
fwrite(" <key>SchemeUserState</key>")
42+
fwrite(" <dict>")
43+
fwrite(" <key>\(package.name).xcscheme</key>")
44+
fwrite(" <dict></dict>")
45+
fwrite(" </dict>")
46+
fwrite(" <key>SuppressBuildableAutocreation</key>")
47+
fwrite(" <dict></dict>")
48+
fwrite("</dict>")
49+
fwrite("</plist>")
50+
}
51+
52+
return rootdir
53+
}
54+
55+
56+
private func open(path: String..., body: ((String) -> Void) -> Void) throws {
57+
var error: ErrorType? = nil
58+
59+
try Utility.fopen(Path.join(path), mode: .Write) { fp in
60+
body { line in
61+
if error == nil {
62+
do {
63+
try fputs(line, fp)
64+
try fputs("\n", fp)
65+
} catch let caught {
66+
error = caught
67+
}
68+
}
69+
}
70+
}
71+
72+
guard error == nil else { throw error! }
73+
}

0 commit comments

Comments
 (0)