atllbuild.swift 30.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright (c) 2016 Anarchy Tools Contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Drew's avatar
Drew committed
14

15
import atfoundation
16
import atpkg
Drew's avatar
Drew committed
17

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 /**Synthesize a module map.
 - parameter name: The name of the module to synthesize
 - parameter umbrellaHeader: A path to the umbrella header.  The path must be relative to the exported module map file.
 - returns String contents of the synthesized modulemap
 */
 private func synthesizeModuleMap(name: String, umbrellaHeader: String?) -> String {
     var s = ""
     s += "module \(name) {\n"
     if let u = umbrellaHeader {
         s += "  umbrella header \"\(u)\"\n"
     }
     s += "\n"
     s += "}\n"
     return s
 }
Drew's avatar
Drew committed
33

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
 private struct Atbin {
    let manifest: Package
    let path: Path

    var name: String { return manifest.name }

    init(path: Path) {
        self.path = path
        self.manifest = try! Package(filepath: path.appending("compiled.atpkg"), overlay: [], focusOnTask: nil)
    }

    var linkDirective: String {
        return path.appending(self.manifest.payload!).description
    }

    var moduleName: String {
        let n = self.manifest.payload!
        if n.hasSuffix(".a") {
            return n.subString(toIndex: n.characters.index(n.characters.endIndex, offsetBy: -2))
        }
        if n.hasSuffix(".dylib") {
            return n.subString(toIndex: n.characters.index(n.characters.endIndex, offsetBy: -6))
        }
        if n.hasSuffix(".so") {
            return n.subString(toIndex: n.characters.index(n.characters.endIndex, offsetBy: -3))
        }
        fatalError("Unknown payload \(n)")
    }

    var swiftModule: Path? {
        let modulePath = self.path + (Platform.targetPlatform.description + ".swiftmodule")
        if FS.fileExists(path: modulePath) { return modulePath }
        return nil
    }

    var clangModule: Path? {
        let modulePath = self.path + "module.modulemap"
        if FS.fileExists(path: modulePath) { return modulePath }
        return nil
    }

    var swiftDoc: Path? {
        let docPath = Path(Platform.targetPlatform.description + ".swiftDoc")
        if FS.fileExists(path: docPath) { return docPath }
        return nil
    }
 }

Drew's avatar
Drew committed
82

Drew's avatar
Drew committed
83 84
/**The ATllbuild tool builds a swift module via llbuild.
For more information on this tool, see `docs/attllbuild.md` */
Drew's avatar
Drew committed
85
final class ATllbuild : Tool {
86

87 88 89 90 91 92 93
    /**We inject this sourcefile in xctestify=true on OSX
    On Linux, the API requires you to explicitly list tests
    which is not required on OSX.  Injecting this file into test targets
    will enforce that API on OSX as well */
    private static let xcTestCaseProvider: String = { () -> String in
        var s = ""
        s += "import XCTest\n"
94
        s += "public func testCase<T: XCTestCase>(_ allTests: [(String, (T) -> () throws -> Void)]) -> XCTestCase {\n"
95
        s += "    fatalError(\"Can't get here.\")\n"
96 97
        s += "}\n"
        s += "\n"
98
        s += "public func XCTMain(_ testCases: [XCTestCase]) {\n"
99 100 101 102 103
        s += "    fatalError(\"Can't get here.\")\n"
        s += "}\n"
        s += "\n"
        return s
    }()
104

Drew's avatar
Drew committed
105 106 107 108
    enum OutputType: String {
        case Executable = "executable"
        case StaticLibrary = "static-library"
        case DynamicLibrary = "dynamic-library"
109
    }
Drew's avatar
Drew committed
110 111 112 113 114

    enum ModuleMapType {
        case None
        case Synthesized
    }
115

116 117 118 119 120
    /**
     * Calculates the llbuild.yaml contents for the given configuration options
     *   - parameter sources: A resolved list of swift sources
     *   - parameter workdir: A temporary working directory for `atllbuild` to use
     *   - parameter modulename: The name of the module to be built.
121
     *   - parameter executableName: The name of the executable to be built.  Typically the same as the module name.
Drew's avatar
Drew committed
122
     *   - parameter enableWMO: Whether to use `enable-whole-module-optimization`, see https://github.com/aciidb0mb3r/swift-llbuild/blob/cfd7aa4e6e14797112922ae12ae7f3af997a41c6/docs/buildsystem.rst
123 124
     *   - returns: The string contents for llbuild.yaml suitable for processing by swift-build-tool
     */
125
    private func llbuildyaml(sources: [Path], workdir: Path, modulename: String, linkSDK: Bool, compileOptions: [String], linkOptions: [String], outputType: OutputType, linkWithProduct:[String], linkWithAtbin:[Atbin], swiftCPath: Path, executableName: String, enableWMO: Bool) -> String {
126
        let productPath = workdir.appending("products")
Drew's avatar
Drew committed
127 128
        //this format is largely undocumented, but I reverse-engineered it from SwiftPM.
        var yaml = "client:\n  name: swift-build\n\n"
129

Drew's avatar
Drew committed
130 131
        yaml += "tools: {}\n\n"

132

Drew's avatar
Drew committed
133
        yaml += "targets:\n"
Drew's avatar
Drew committed
134 135
        yaml += "  \"\": [<atllbuild>]\n"
        yaml += "  atllbuild: [<atllbuild>]\n"
136

Drew's avatar
Drew committed
137
        //this is the "compile" command
138

Drew's avatar
Drew committed
139 140 141
        yaml += "commands:\n"
        yaml += "  <atllbuild-swiftc>:\n"
        yaml += "     tool: swift-compiler\n"
Drew's avatar
Drew committed
142
        yaml += "     executable: \"\(swiftCPath)\"\n"
143 144 145
        let inputs = String.join(parts: sources.map { path in path.description }, delimiter: "\", \"")
        yaml += "     inputs: [\"\(inputs)\"]\n"
        yaml += "     sources: [\"\(inputs)\"]\n"
146

Drew's avatar
Drew committed
147 148
        //swiftPM wants "objects" which is just a list of %.swift.o files.  We have to put them in a temp directory though.
        let objects = sources.map { (source) -> String in
149
            workdir.appending("objects").appending(source.basename() + ".o").description
Drew's avatar
Drew committed
150 151
        }
        yaml += "     objects: \(objects)\n"
Drew's avatar
Drew committed
152 153
        //this crazy syntax is how llbuild specifies outputs
        var llbuild_outputs = ["<atllbuild-swiftc>"]
154
        llbuild_outputs.append(contentsOf: objects)
Drew's avatar
Drew committed
155
        yaml += "     outputs: \(llbuild_outputs)\n"
156

Drew's avatar
Drew committed
157 158 159
        yaml += "     enable-whole-module-optimization: \(enableWMO ? "true" : "false")\n"
        yaml += "     num-threads: 8\n"

160 161 162
        switch(outputType) {
        case .Executable:
            break
Drew's avatar
Drew committed
163
        case .StaticLibrary, .DynamicLibrary:
164 165
            yaml += "     is-library: true\n" //I have no idea what the effect of this is, but swiftPM does it, so I'm including it.
        }
166

Drew's avatar
Drew committed
167
        yaml += "     module-name: \(modulename)\n"
168
        let swiftModulePath = productPath.appending(modulename + ".swiftmodule")
169
        yaml += "     module-output-path: \(swiftModulePath)\n"
Drew's avatar
Drew committed
170
        yaml += "     temps-path: \(workdir)/llbuildtmp\n"
171

Drew's avatar
Drew committed
172
        var args : [String] = []
173
        args += ["-j8", "-D", "ATBUILD", "-I", workdir.appending("products").description + "/"]
174

175
        if linkSDK {
Drew's avatar
Drew committed
176
            if let sdkPath = Platform.targetPlatform.sdkPath {
Drew's avatar
Drew committed
177 178 179 180 181 182
                if swiftCPath.description.contains(string: "Xcode-beta") {
                    //evil evil hack
                    args += ["-sdk","/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/"]
                }
                else { args += ["-sdk", sdkPath] }
                
Drew's avatar
Drew committed
183
            }
184
        }
185
        args += compileOptions
186

Drew's avatar
Drew committed
187
        yaml += "     other-args: \(args)\n"
188

Drew's avatar
Drew committed
189 190
        //and this is the "link" command
        yaml += "  <atllbuild>:\n"
191 192 193 194 195
        switch(outputType) {
        case .Executable:
            yaml += "    tool: shell\n"
            //this crazy syntax is how sbt declares a dependency
            var llbuild_inputs = ["<atllbuild-swiftc>"]
196
            llbuild_inputs += objects
197 198
            var builtProducts = linkWithProduct.map { (workdir + ("products/"+$0)).description }
            builtProducts += linkWithAtbin.map {$0.linkDirective}
199
            llbuild_inputs += builtProducts
200
            let executablePath = productPath.appending(executableName)
201
            yaml += "    inputs: \(llbuild_inputs)\n"
202
            yaml += "    outputs: [\"<atllbuild>\", \"\(executablePath)\"]\n"
203
            //and now we have the crazy 'args'
204 205 206 207
            args = [swiftCPath.description, "-o", executablePath.description]
            args += objects
            args += builtProducts
            args += linkOptions
208
            yaml += "    args: \(args)\n"
209 210
            yaml += "    description: Linking executable \(executablePath)\n"
            return yaml
Drew's avatar
Drew committed
211

212

213 214 215
        case .StaticLibrary:
            yaml += "    tool: shell\n"
            var llbuild_inputs = ["<atllbuild-swiftc>"]
216
            llbuild_inputs.append(contentsOf: objects)
217
            yaml += "    inputs: \(llbuild_inputs)\n"
218
            let libPath = productPath.appending(modulename + ".a")
219
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
220

221 222 223 224 225 226 227 228
            //build the crazy args, mostly consisting of an `ar` shell command
            var shellCmd = "rm -rf \(libPath); ar cr '\(libPath)'"
            for obj in objects {
                shellCmd += " '\(obj)'"
            }
            let args = "[\"/bin/sh\",\"-c\",\(shellCmd)]"
            yaml += "    args: \(args)\n"
            yaml += "    description: \"Linking Library:  \(libPath)\""
229
            return yaml
Drew's avatar
Drew committed
230 231 232 233

        case .DynamicLibrary:
            yaml += "    tool: shell\n"
            var llbuild_inputs = ["<atllbuild-swiftc>"]
234
            llbuild_inputs += objects
235 236
            var builtProducts = linkWithProduct.map { (workdir + ("products/"+$0)).description }
            builtProducts += linkWithAtbin.map {$0.linkDirective}
237
            llbuild_inputs += builtProducts
Drew's avatar
Drew committed
238
            yaml += "    inputs: \(llbuild_inputs)\n"
239
            let libPath = productPath.appending(modulename + Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
240
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
241 242 243 244
            var args = [swiftCPath.description, "-o", libPath.description, "-emit-library"]
            args += objects
            args += builtProducts
            args += linkOptions
Drew's avatar
Drew committed
245 246 247
            yaml += "    args: \(args)\n"
            yaml += "    description: \"Linking Library:  \(libPath)\""
            return yaml
248
        }
249
     }
250

Drew's avatar
Drew committed
251
    enum Options: String {
Drew's avatar
Drew committed
252 253 254
        case Tool = "tool"
        case Name = "name"
        case Dependencies = "dependencies"
255 256 257
        case OutputType = "output-type"
        case Source = "sources"
        case BootstrapOnly = "bootstrap-only"
Drew's avatar
Drew committed
258
        case llBuildYaml = "llbuildyaml"
259 260 261
        case CompileOptions = "compile-options"
        case LinkOptions = "link-options"
        case LinkSDK = "link-sdk"
262 263
        case LinkWithProduct = "link-with-product"
        case LinkWithAtbin = "link-with-atbin"
Drew's avatar
Drew committed
264
        case XCTestify = "xctestify"
265
        case XCTestStrict = "xctest-strict"
266
		case IncludeWithUser = "include-with-user"
267
        case PublishProduct = "publish-product"
268
        case UmbrellaHeader = "umbrella-header"
Drew's avatar
Drew committed
269
        case ModuleMap = "module-map"
Drew's avatar
Drew committed
270
        case WholeModuleOptimization = "whole-module-optimization"
271
        case Framework = "framework"
272
        case ExecutableName = "executable-name"
Drew's avatar
Drew committed
273
        case Bitcode = "bitcode"
Drew's avatar
Drew committed
274
        case Magic = "magic"
275

276

Drew's avatar
Drew committed
277 278 279 280 281 282 283 284 285 286 287 288
        static var allOptions : [Options] {
            return [
                Name,
                Dependencies,
                OutputType,
                Source,
                BootstrapOnly,
                llBuildYaml,
                CompileOptions,
                LinkOptions,
                LinkSDK,
                LinkWithProduct,
289
				LinkWithAtbin,
Drew's avatar
Drew committed
290 291
                XCTestify,
                XCTestStrict,
292
				IncludeWithUser,
293
                PublishProduct,
Drew's avatar
Drew committed
294
				UmbrellaHeader,
295
                WholeModuleOptimization,
296
                Framework,
Drew's avatar
Drew committed
297
                ExecutableName,
Drew's avatar
Drew committed
298 299
                Bitcode,
                Magic
Drew's avatar
Drew committed
300 301 302
            ]
        }
    }
Drew's avatar
Drew committed
303

Drew's avatar
Drew committed
304
    func run(task: Task, toolchain: String) {
305

306
        //warn if we don't understand an option
307 308 309 310
        var knownOptions = Options.allOptions.map({$0.rawValue})
        for option in Task.Option.allOptions.map({$0.rawValue}) {
            knownOptions.append(option)
        }
311
        for key in task.allKeys {
312
            if !knownOptions.contains(key) {
Drew's avatar
Drew committed
313
                print("Warning: unknown option \(key) for task \(task.qualifiedName)")
314 315
            }
        }
316

Drew's avatar
Drew committed
317
        //create the working directory
318
        let workDirectory = Path(".atllbuild")
319

Drew's avatar
Drew committed
320 321 322 323
        //NSFileManager is pretty anal about throwing errors if we try to remove something that doesn't exist, etc.
        //We just want to create a state where .atllbuild/objects and .atllbuild/llbuildtmp and .atllbuild/products exists.
        //and in particular, without erasing the product directory, since that accumulates build products across
        //multiple invocations of atllbuild.
Drew's avatar
Drew committed
324
        if Process.arguments.contains("--clean") {
325 326
            let _ = try? FS.removeItem(path: workDirectory.appending("objects"), recursive: true)
            let _ = try? FS.removeItem(path: workDirectory.appending("llbuildtmp"), recursive: true)
Drew's avatar
Drew committed
327
        }
328
        let _ = try? FS.removeItem(path: workDirectory.appending("include"), recursive: true)
329

Drew's avatar
Drew committed
330 331


332 333 334 335
        let _ = try? FS.createDirectory(path: workDirectory)
        let _ = try? FS.createDirectory(path: workDirectory.appending("products"))
        let _ = try? FS.createDirectory(path: workDirectory.appending("objects"))
        let _ = try? FS.createDirectory(path: workDirectory.appending("include"))
Drew's avatar
Drew committed
336

337
        ///MARK: parse arguments
Drew's avatar
Drew committed
338
        var linkWithProduct: [String] = []
Drew's avatar
Drew committed
339 340 341 342
        if let arr_ = task[Options.LinkWithProduct.rawValue] {
            guard let arr = arr_.vector else {
                fatalError("Non-vector link directive \(arr_)")
            }
Drew's avatar
Drew committed
343
            for product in arr {
Drew's avatar
Drew committed
344 345
                guard var p = product.string else { fatalError("non-string product \(product)") }
                if p.hasSuffix(".dynamic") {
346
                    p.replace(searchTerm: ".dynamic", replacement: Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
347
                }
Drew's avatar
Drew committed
348 349 350
                linkWithProduct.append(p)
            }
        }
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378

        ///DEPRECATED PRODUCT CHECK
        if let arr_ = task["link-with"] {
            print("Warning: link-with is deprecated; please use link-with-product or link-with-atbin")
            sleep(5)
            guard let arr = arr_.vector else {
                fatalError("Non-vector link directive \(arr_)")
            }
            for product in arr {
                guard var p = product.string else { fatalError("non-string product \(product)") }
                if p.hasSuffix(".dynamic") {
                    p.replace(searchTerm: ".dynamic", replacement: Platform.targetPlatform.dynamicLibraryExtension)
                }
                linkWithProduct.append(p)
            }
        }

        var linkWithAtbin: [Atbin] = []
        if let arr_ = task[Options.LinkWithAtbin.rawValue] {
            guard let arr = arr_.vector else {
                fatalError("Non-vector link directive \(arr_)")
            }
            for product in arr {
                guard var p = product.string else { fatalError("non-string product \(product)") }
                linkWithAtbin.append(Atbin(path: task.importedPath.appending(p)))
            }
        }

Drew's avatar
Drew committed
379 380
        guard case .some(.StringLiteral(let outputTypeString)) = task[Options.OutputType.rawValue] else {
            fatalError("No \(Options.OutputType.rawValue) for task \(task)")
381
        }
Drew's avatar
Drew committed
382 383
        guard let outputType = OutputType(rawValue: outputTypeString) else {
            fatalError("Unknown \(Options.OutputType.rawValue) \(outputTypeString)")
384
        }
385

386
        var compileOptions: [String] = []
Drew's avatar
Drew committed
387
        if let opts = task[Options.CompileOptions.rawValue]?.vector {
388
            for o in opts {
389
                guard let os = o.string else { fatalError("Compile option \(o) is not a string") }
390 391 392
                compileOptions.append(os)
            }
        }
Drew's avatar
Drew committed
393

394 395 396 397 398 399 400 401 402 403
        //copy the atbin module / swiftdoc into our include directory
        let includeAtbinPath = workDirectory + "include/atbin"
        let _ = try? FS.createDirectory(path: includeAtbinPath, intermediate: true)
        for atbin in linkWithAtbin {
            if let path = atbin.swiftModule {
                try! FS.copyItem(from: path, to: Path("\(includeAtbinPath)/\(atbin.moduleName).swiftmodule"))
            }
        }
        if linkWithAtbin.count > 0 { compileOptions.append(contentsOf: ["-I",includeAtbinPath.description])}

404
        if let includePaths = task[Options.IncludeWithUser.rawValue]?.vector {
Drew's avatar
Drew committed
405 406 407
            for path_s in includePaths {
                guard let path = path_s.string else { fatalError("Non-string path \(path_s)") }
                compileOptions.append("-I")
408
                compileOptions.append((userPath() + path).description)
Drew's avatar
Drew committed
409 410
            }
        }
Drew's avatar
Drew committed
411
        var linkOptions: [String] = []
Drew's avatar
Drew committed
412
        if let opts = task[Options.LinkOptions.rawValue]?.vector {
Drew's avatar
Drew committed
413 414 415 416 417
            for o in opts {
                guard let os = o.string else { fatalError("Link option \(o) is not a string") }
                linkOptions.append(os)
            }
        }
Drew's avatar
Drew committed
418

Drew's avatar
Drew committed
419 420 421 422 423 424 425 426 427 428 429 430 431 432
        let bitcode: Bool
        //do we have an explicit bitcode setting?
        if let b = task[Options.Bitcode.rawValue] {
            bitcode = b.bool!
        }
        else {
            bitcode = false
        }
        //todo: enable by default for iOS, but we can't due to SR-1493
        if bitcode {
            compileOptions.append("-embed-bitcode")
            linkOptions.append(contentsOf: ["-embed-bitcode"])
        }

433 434


Drew's avatar
Drew committed
435
        //check for modulemaps
436 437 438 439 440 441 442 443 444 445 446 447 448 449
        /*per http://clang.llvm.org/docs/Modules.html#command-line-parameters, pretty much
        the only way to do this is to create a file called `module.modulemap`.  That
        potentially conflicts with other modulemaps, so we give it its own directory, namespaced
        by the product name. */
        func installModuleMap(moduleMapPath: Path, productName: String) {
           let includePathName = workDirectory + "include/\(productName)"
            let _ = try? FS.createDirectory(path: includePathName, intermediate: true)
            do {
                try FS.copyItem(from: moduleMapPath, to: includePathName.appending("module.modulemap"))
            } catch {
                fatalError("Could not copy modulemap to \(includePathName): \(error)")
            }
            compileOptions.append(contentsOf: ["-I", includePathName.description])
        }
Drew's avatar
Drew committed
450
        for product in linkWithProduct {
451
            let productName = product.split(character: ".")[0]
452
            let moduleMapPath = workDirectory + "products/\(productName).modulemap"
453
            if FS.fileExists(path: moduleMapPath) {
454 455 456 457 458 459 460
                installModuleMap(moduleMapPath: moduleMapPath, productName: productName)
            }
        }

        for product in linkWithAtbin {
            if let moduleMapPath = product.clangModule {
                installModuleMap(moduleMapPath: moduleMapPath, productName: product.name)
Drew's avatar
Drew committed
461 462 463
            }
        }

Drew's avatar
Drew committed
464
        guard let sourceDescriptions = task[Options.Source.rawValue]?.vector?.flatMap({$0.string}) else { fatalError("Can't find sources for atllbuild.") }
465 466
        var sources = collectSources(sourceDescriptions: sourceDescriptions, taskForCalculatingPath: task)

Drew's avatar
Drew committed
467
        //xctestify
Drew's avatar
Drew committed
468
        if task[Options.XCTestify.rawValue]?.bool == true {
469
            precondition(outputType == .Executable, "You must use :\(Options.OutputType.rawValue) executable with xctestify.")
Drew's avatar
Drew committed
470
            //inject platform-specific flags
Drew's avatar
Drew committed
471 472
            switch(Platform.targetPlatform) {
                case .OSX:
473 474
                compileOptions.append(contentsOf: ["-F", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/"])
                linkOptions.append(contentsOf: ["-F", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/", "-target", "x86_64-apple-macosx10.11", "-Xlinker", "-rpath", "-Xlinker", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/", "-Xlinker", "-bundle"])
475

Drew's avatar
Drew committed
476 477
                case .Linux:
                break
Drew's avatar
Drew committed
478

Drew's avatar
Drew committed
479
                case .iOS, .iOSGeneric:
Drew's avatar
Drew committed
480
                fatalError("\(Options.XCTestify.rawValue) is not supported for iOS")
Drew's avatar
Drew committed
481
            }
482
        }
Drew's avatar
Drew committed
483
        if task[Options.XCTestStrict.rawValue]?.bool == true {
Drew's avatar
Drew committed
484
            switch Platform.targetPlatform {
485
            case .OSX:
Drew's avatar
Drew committed
486
                //inject XCTestCaseProvider.swift
487 488 489 490 491 492 493 494 495 496 497 498
                do {
                    let xcTestCaseProviderPath = try FS.temporaryDirectory(prefix: "XCTestCase")
                    do {
                        try ATllbuild.xcTestCaseProvider.write(to: xcTestCaseProviderPath.appending("XCTestCaseProvider.swift"))
                        sources.append(xcTestCaseProviderPath.appending("XCTestCaseProvider.swift"))
                    } catch {
                        print(xcTestCaseProviderPath)
                        fatalError("Could not inject XCTestCaseProvider: \(error)")
                    }
                } catch {
                    fatalError("Could not create temp dir for XCTestCaseProvider: \(error)")
                }
Drew's avatar
Drew committed
499
                
500 501

            case .Linux:
Drew's avatar
Drew committed
502
                break
Drew's avatar
Drew committed
503

Drew's avatar
Drew committed
504
                case .iOS, .iOSGeneric:
Drew's avatar
Drew committed
505
                fatalError("\(Options.XCTestStrict.rawValue) is not supported for iOS")
Drew's avatar
Drew committed
506
            }
Drew's avatar
Drew committed
507
        }
Drew's avatar
Drew committed
508 509 510 511 512 513 514
        let moduleMap: ModuleMapType
        if task[Options.ModuleMap.rawValue]?.string == "synthesized" {
            moduleMap = .Synthesized
        }
        else {
            moduleMap = .None
        }
Drew's avatar
Drew committed
515

Drew's avatar
Drew committed
516
        guard let name = task[Options.Name.rawValue]?.string else { fatalError("No name for atllbuild task") }
517

518 519 520 521 522 523 524
        let executableName: String
        if let e = task[Options.ExecutableName.rawValue]?.string { 
            precondition(outputType == .Executable, "Must use \(Options.OutputType.rawValue) 'executable' when using \(Options.ExecutableName.rawValue)")
            executableName = e 
        }
        else { executableName = name }

525 526
        if task[Options.Framework.rawValue]?.bool == true {
            #if !os(OSX)
527
            fatalError("\(Options.Framework.rawValue) is not supported on this host.")
528 529 530 531
            #endif
            linkOptions.append("-Xlinker")
            linkOptions.append("-install_name")
            linkOptions.append("-Xlinker")
532 533 534 535 536 537 538 539 540
            switch(Platform.targetPlatform) {
                case .OSX:
                linkOptions.append("@rpath/\(name).framework/Versions/A/\(name)")
                case .iOS(let arch):
                linkOptions.append("@rpath/\(name).framework/\(name)")
                default:
                fatalError("\(Options.Framework.rawValue) not supported when targeting \(Platform.targetPlatform)")
            }
            
541
        }
542

543
        if let umbrellaHeader = task[Options.UmbrellaHeader.rawValue]?.string {
Drew's avatar
Drew committed
544
            precondition(moduleMap == .Synthesized, ":\(Options.ModuleMap.rawValue) \"synthesized\" must be used with the \(Options.UmbrellaHeader.rawValue) option")
545
            let s = synthesizeModuleMap(name: name, umbrellaHeader: "Umbrella.h")
546 547 548 549 550 551
            do {
                try s.write(to: workDirectory + "include/module.modulemap")
                try FS.copyItem(from: task.importedPath + umbrellaHeader, to: workDirectory + "include/Umbrella.h")
            } catch {
                fatalError("Could not synthesize module map from umbrella header: \(error)")
            }
552
            compileOptions.append("-I")
553
            compileOptions.append(workDirectory.appending("include").description + "/")
554 555
            compileOptions.append("-import-underlying-module")
        }
556

Drew's avatar
Drew committed
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
        //inject target
        switch(Platform.targetPlatform) {
            case .iOS(let arch):
            switch(arch) {
                case .x86_64:
                compileOptions.append(contentsOf: ["-target","x86_64-apple-ios9.3"])
                linkOptions.append(contentsOf: ["-target","x86_64-apple-ios9.3"])

                case .i386:
                compileOptions.append(contentsOf: ["-target","i386-apple-ios9.3"])
                linkOptions.append(contentsOf: ["-target","i386-apple-ios9.3"])

                case .arm64:
                compileOptions.append(contentsOf: ["-target","arm64-apple-ios9.3"])
                linkOptions.append(contentsOf: ["-target","arm64-apple-ios9.3"])

                case .armv7:
                compileOptions.append(contentsOf: ["-target","armv7-apple-ios9.3"])
                linkOptions.append(contentsOf: ["-target","armv7-apple-ios9.3"])

            }
            linkOptions.append(contentsOf: ["-Xlinker", "-syslibroot","-Xlinker",Platform.targetPlatform.sdkPath!])
            case .OSX, .Linux:
                break //not required
Drew's avatar
Drew committed
581 582
            case .iOSGeneric:
                fatalError("Generic platform iOS cannot be used with atllbuild; choose a specific platform or use atbin")
Drew's avatar
Drew committed
583 584
        }

Drew's avatar
Drew committed
585
        let bootstrapOnly: Bool
Drew's avatar
Drew committed
586

Drew's avatar
Drew committed
587
        if task[Options.BootstrapOnly.rawValue]?.bool == true {
Drew's avatar
Drew committed
588
            bootstrapOnly = true
Drew's avatar
Drew committed
589 590
            //update the build platform to be the one passed on the CLI
            Platform.buildPlatform = Platform.targetPlatform
Drew's avatar
Drew committed
591 592 593 594
        }
        else {
            bootstrapOnly = false
        }
595

Drew's avatar
Drew committed
596 597
        ///The next task will not be bootstrapped.
        defer { Platform.buildPlatform = Platform.hostPlatform }
598

599
        let sdk: Bool
Drew's avatar
Drew committed
600
        if task[Options.LinkSDK.rawValue]?.bool == false {
601 602 603
            sdk = false
        }
        else { sdk = true }
604

605
        let llbuildyamlpath : Path
Drew's avatar
Drew committed
606

Drew's avatar
Drew committed
607
        if let value = task[Options.llBuildYaml.rawValue]?.string {
608
            llbuildyamlpath = Path(value)
Drew's avatar
Drew committed
609 610
        }
        else {
611
            llbuildyamlpath = workDirectory.appending("llbuild.yaml")
Drew's avatar
Drew committed
612
        }
613
        let swiftCPath = findToolPath(toolName: "swiftc", toolchain: toolchain)
614

615
        var enableWMO: Bool
Drew's avatar
Drew committed
616 617
        if let wmo = task[Options.WholeModuleOptimization.rawValue]?.bool {
            enableWMO = wmo
Drew's avatar
Drew committed
618
            //we can't deprecate WMO due to a bug in swift-preview-1 that prevents it from being useable in some cases on Linux
Drew's avatar
Drew committed
619 620 621
        }
        else { enableWMO = false }

622 623 624 625 626 627 628 629 630 631 632 633 634 635
        // MARK: Configurations

        if currentConfiguration.testingEnabled == true {
            compileOptions.append("-enable-testing")
        }

        //"stripped" is implemented as "included" here, that is a bug
        //see https://github.com/AnarchyTools/atbuild/issues/73
        if currentConfiguration.debugInstrumentation == .Included || currentConfiguration.debugInstrumentation == .Stripped {
            compileOptions.append("-g")
        }

        if currentConfiguration.optimize == true {
            compileOptions.append("-O")
Drew's avatar
Drew committed
636 637 638 639 640 641 642 643
            switch(Platform.buildPlatform) {
                case .Linux:
                //don't enable WMO on Linux
                //due to bug in swift-preview-1
                break
                default:
                enableWMO = true
            }
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
        }
        if task[Options.Magic.rawValue] != nil {
            print("Warning: Magic is deprecated.  Please migrate to --configuration none.  If --configuration none won't work for your usecase, file a bug at https://github.com/AnarchyTools/atbuild/issues")
            sleep(5)
        }

        if currentConfiguration.fastCompile==false  && task[Options.Magic.rawValue]?.bool != false {
            switch(Platform.buildPlatform) {
                case .OSX:
                linkOptions.append(contentsOf: ["-Xlinker","-dead_strip"])
                default:
                break
            }
        }

        switch(currentConfiguration) {
            case .Debug:
            compileOptions.append("-DATBUILD_DEBUG")
            case .Release:
            compileOptions.append("-DATBUILD_RELEASE")
            case .Benchmark:
            compileOptions.append("-DATBUILD_BENCH")
            case .Test:
            compileOptions.append("-DATBUILD_TEST")
            case .None:
            break //too much magic to insert an arg in this case
            case .User(let str):
            compileOptions.append("-DATBUILD_\(str)")
        }

        // MARK: emit llbuildyaml 

676
        let yaml = llbuildyaml(sources: sources, workdir: workDirectory, modulename: name, linkSDK: sdk, compileOptions: compileOptions, linkOptions: linkOptions, outputType: outputType, linkWithProduct: linkWithProduct, linkWithAtbin: linkWithAtbin, swiftCPath: swiftCPath, executableName: executableName, enableWMO: enableWMO)
677
        let _ = try? yaml.write(to: llbuildyamlpath)
Drew's avatar
Drew committed
678
        if bootstrapOnly { return }
Drew's avatar
Drew committed
679

680 681
        //MARK: execute build

Drew's avatar
Drew committed
682
        switch moduleMap {
683 684 685 686 687 688 689 690 691
        case .None:
            break
        case .Synthesized:
            let s = synthesizeModuleMap(name: name, umbrellaHeader: nil)
            do {
                try s.write(to: workDirectory + "products/\(name).modulemap")
            } catch {
                fatalError("Could not write synthesized module map: \(error)")
            }
Drew's avatar
Drew committed
692
        }
693 694

        let cmd = "\(findToolPath(toolName: "swift-build-tool",toolchain: toolchain)) -f \(llbuildyamlpath)"
695
        anarchySystem(cmd)
Drew's avatar
Drew committed
696
        if task[Options.PublishProduct.rawValue]?.bool == true {
697 698 699 700 701 702 703 704
            do {
                if !FS.isDirectory(path: Path("bin")) {
                    try FS.createDirectory(path: Path("bin"))
                }
                try FS.copyItem(from: workDirectory + "products/\(name).swiftmodule", to: Path("bin/\(name).swiftmodule"))
                try FS.copyItem(from: workDirectory + "products/\(name).swiftdoc", to: Path("bin/\(name).swiftdoc"))
                switch outputType {
                case .Executable:
705
                    try FS.copyItem(from: workDirectory + "products/\(executableName)", to: Path("bin/\(executableName)"))
706 707 708 709 710 711
                case .StaticLibrary:
                    try FS.copyItem(from: workDirectory + "products/\(name).a", to: Path("bin/\(name).a"))
                case .DynamicLibrary:
                    try FS.copyItem(from: workDirectory + ("products/\(name)" + Platform.targetPlatform.dynamicLibraryExtension) , to: Path("bin/\(name)" + Platform.targetPlatform.dynamicLibraryExtension))
                }
                switch moduleMap {
Drew's avatar
Drew committed
712
                case .None:
713
                    break
Drew's avatar
Drew committed
714
                case .Synthesized:
715 716 717 718
                    try FS.copyItem(from: workDirectory + "products/\(name).modulemap", to: Path("bin/\(name).modulemap"))
                }
            } catch {
                print("Could not publish product: \(error)")
Drew's avatar
Drew committed
719
            }
720
        }
Drew's avatar
Drew committed
721

722 723
    }
}