atllbuild.swift 33.4 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
 /**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.
21
 - parameter headers: A list of headers to import.  The path must be relative to the exported module map file.
22 23
 - returns String contents of the synthesized modulemap
 */
24
 private func synthesizeModuleMap(name: String, umbrellaHeader: String?, headers: [String], link: [String]) -> String {
25 26 27 28 29
     var s = ""
     s += "module \(name) {\n"
     if let u = umbrellaHeader {
         s += "  umbrella header \"\(u)\"\n"
     }
30 31 32 33 34 35
     for header in headers {
        s += "  header \"\(header)\"\n"
     }
     for l in link {
        s += "  link \"\(l)\"\n"
     }
36 37 38 39
     s += "\n"
     s += "}\n"
     return s
 }
Drew's avatar
Drew committed
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 82 83 84 85 86 87 88
 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
89

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

94 95 96 97 98 99 100
    /**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"
101
        s += "public func testCase<T: XCTestCase>(_ allTests: [(String, (T) -> () throws -> Void)]) -> XCTestCase {\n"
102
        s += "    fatalError(\"Can't get here.\")\n"
103 104
        s += "}\n"
        s += "\n"
105
        s += "public func XCTMain(_ testCases: [XCTestCase]) {\n"
106 107 108 109 110
        s += "    fatalError(\"Can't get here.\")\n"
        s += "}\n"
        s += "\n"
        return s
    }()
111

Drew's avatar
Drew committed
112 113 114 115
    enum OutputType: String {
        case Executable = "executable"
        case StaticLibrary = "static-library"
        case DynamicLibrary = "dynamic-library"
116
    }
Drew's avatar
Drew committed
117 118 119 120 121

    enum ModuleMapType {
        case None
        case Synthesized
    }
122

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

Drew's avatar
Drew committed
137 138
        yaml += "tools: {}\n\n"

139

Drew's avatar
Drew committed
140
        yaml += "targets:\n"
Drew's avatar
Drew committed
141 142
        yaml += "  \"\": [<atllbuild>]\n"
        yaml += "  atllbuild: [<atllbuild>]\n"
143

Drew's avatar
Drew committed
144
        //this is the "compile" command
145

Drew's avatar
Drew committed
146 147 148
        yaml += "commands:\n"
        yaml += "  <atllbuild-swiftc>:\n"
        yaml += "     tool: swift-compiler\n"
Drew's avatar
Drew committed
149
        yaml += "     executable: \"\(swiftCPath)\"\n"
150
        let inputs = String.join(parts: swiftSources.map { path in path.description }, delimiter: "\", \"")
151 152
        yaml += "     inputs: [\"\(inputs)\"]\n"
        yaml += "     sources: [\"\(inputs)\"]\n"
153

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

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

167 168 169
        switch(outputType) {
        case .Executable:
            break
Drew's avatar
Drew committed
170
        case .StaticLibrary, .DynamicLibrary:
171 172
            yaml += "     is-library: true\n" //I have no idea what the effect of this is, but swiftPM does it, so I'm including it.
        }
173

Drew's avatar
Drew committed
174
        yaml += "     module-name: \(modulename)\n"
175
        let swiftModulePath = productPath.appending(modulename + ".swiftmodule")
176
        yaml += "     module-output-path: \(swiftModulePath)\n"
Drew's avatar
Drew committed
177
        yaml += "     temps-path: \(workdir)/llbuildtmp\n"
178

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

182
        if linkSDK {
Drew's avatar
Drew committed
183
            if let sdkPath = Platform.targetPlatform.sdkPath {
Drew's avatar
Drew committed
184 185 186 187 188 189
                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
190
            }
191
        }
192
        args += compileOptions
193

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

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215

        var llbuild_inputs = ["<atllbuild-swiftc>"]

        //the "C" commands
        for source in cSources {
            let cmdName = "<atllbuild-\(source.basename().split(character: ".")[0])>"
            yaml += "  \(cmdName):\n"
            yaml += "    tool: shell\n"
            yaml += "    inputs: [\"\(source)\"]\n"
            let c_object = workdir.appending("objects").appending(source.basename() + ".o").description
            yaml += "    outputs: [\"\(c_object)\"]\n"
            var cargs = ["clang","-c",source.description,"-o",c_object]
            cargs += cCompileOptions
            yaml += "    args: \(cargs)\n"

            //add c_objects to our link step
            objects += [c_object]
            llbuild_inputs += [cmdName]
        }

Drew's avatar
Drew committed
216 217
        //and this is the "link" command
        yaml += "  <atllbuild>:\n"
218 219 220 221
        switch(outputType) {
        case .Executable:
            yaml += "    tool: shell\n"
            //this crazy syntax is how sbt declares a dependency
222
            llbuild_inputs += objects
223 224
            var builtProducts = linkWithProduct.map { (workdir + ("products/"+$0)).description }
            builtProducts += linkWithAtbin.map {$0.linkDirective}
225
            llbuild_inputs += builtProducts
226
            let executablePath = productPath.appending(executableName)
227
            yaml += "    inputs: \(llbuild_inputs)\n"
228
            yaml += "    outputs: [\"<atllbuild>\", \"\(executablePath)\"]\n"
229
            //and now we have the crazy 'args'
230 231 232 233
            args = [swiftCPath.description, "-o", executablePath.description]
            args += objects
            args += builtProducts
            args += linkOptions
234
            yaml += "    args: \(args)\n"
235 236
            yaml += "    description: Linking executable \(executablePath)\n"
            return yaml
Drew's avatar
Drew committed
237

238

239 240
        case .StaticLibrary:
            yaml += "    tool: shell\n"
241
            llbuild_inputs.append(contentsOf: objects)
242
            yaml += "    inputs: \(llbuild_inputs)\n"
243
            let libPath = productPath.appending(modulename + ".a")
244
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
245

246 247 248 249 250 251 252 253
            //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)\""
254
            return yaml
Drew's avatar
Drew committed
255 256 257

        case .DynamicLibrary:
            yaml += "    tool: shell\n"
258
            llbuild_inputs += objects
259 260
            var builtProducts = linkWithProduct.map { (workdir + ("products/"+$0)).description }
            builtProducts += linkWithAtbin.map {$0.linkDirective}
261
            llbuild_inputs += builtProducts
Drew's avatar
Drew committed
262
            yaml += "    inputs: \(llbuild_inputs)\n"
263
            let libPath = productPath.appending(modulename + Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
264
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
265 266 267 268
            var args = [swiftCPath.description, "-o", libPath.description, "-emit-library"]
            args += objects
            args += builtProducts
            args += linkOptions
Drew's avatar
Drew committed
269 270 271
            yaml += "    args: \(args)\n"
            yaml += "    description: \"Linking Library:  \(libPath)\""
            return yaml
272
        }
273
     }
274

Drew's avatar
Drew committed
275
    enum Options: String {
Drew's avatar
Drew committed
276 277 278
        case Tool = "tool"
        case Name = "name"
        case Dependencies = "dependencies"
279 280 281
        case OutputType = "output-type"
        case Source = "sources"
        case BootstrapOnly = "bootstrap-only"
Drew's avatar
Drew committed
282
        case llBuildYaml = "llbuildyaml"
283
        case CompileOptions = "compile-options"
284
        case CCompileOptions = "c-compile-options"
285 286
        case LinkOptions = "link-options"
        case LinkSDK = "link-sdk"
287 288
        case LinkWithProduct = "link-with-product"
        case LinkWithAtbin = "link-with-atbin"
Drew's avatar
Drew committed
289
        case XCTestify = "xctestify"
290
        case XCTestStrict = "xctest-strict"
291
		case IncludeWithUser = "include-with-user"
292
        case PublishProduct = "publish-product"
293
        case UmbrellaHeader = "umbrella-header"
Drew's avatar
Drew committed
294
        case ModuleMap = "module-map"
295
        case ModuleMapLink = "module-map-link"
Drew's avatar
Drew committed
296
        case WholeModuleOptimization = "whole-module-optimization"
297
        case Framework = "framework"
298
        case ExecutableName = "executable-name"
Drew's avatar
Drew committed
299
        case Bitcode = "bitcode"
Drew's avatar
Drew committed
300
        case Magic = "magic"
301

302

Drew's avatar
Drew committed
303 304 305 306 307 308 309 310 311
        static var allOptions : [Options] {
            return [
                Name,
                Dependencies,
                OutputType,
                Source,
                BootstrapOnly,
                llBuildYaml,
                CompileOptions,
312
                CCompileOptions,
Drew's avatar
Drew committed
313 314 315
                LinkOptions,
                LinkSDK,
                LinkWithProduct,
316
				LinkWithAtbin,
Drew's avatar
Drew committed
317 318
                XCTestify,
                XCTestStrict,
319
				IncludeWithUser,
320
                PublishProduct,
Drew's avatar
Drew committed
321
				UmbrellaHeader,
322
                ModuleMap,
323
                ModuleMapLink,
324
                WholeModuleOptimization,
325
                Framework,
Drew's avatar
Drew committed
326
                ExecutableName,
Drew's avatar
Drew committed
327 328
                Bitcode,
                Magic
Drew's avatar
Drew committed
329 330 331
            ]
        }
    }
Drew's avatar
Drew committed
332

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

335
        //warn if we don't understand an option
336 337 338 339
        var knownOptions = Options.allOptions.map({$0.rawValue})
        for option in Task.Option.allOptions.map({$0.rawValue}) {
            knownOptions.append(option)
        }
340
        for key in task.allKeys {
341
            if !knownOptions.contains(key) {
Drew's avatar
Drew committed
342
                print("Warning: unknown option \(key) for task \(task.qualifiedName)")
343 344
            }
        }
345

Drew's avatar
Drew committed
346
        //create the working directory
347
        let workDirectory = Path(".atllbuild")
348

Drew's avatar
Drew committed
349 350 351 352
        //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
353
        if Process.arguments.contains("--clean") {
354 355
            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
356
        }
357
        let _ = try? FS.removeItem(path: workDirectory.appending("include"), recursive: true)
358

Drew's avatar
Drew committed
359 360


361 362 363 364
        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
365

366
        ///MARK: parse arguments
Drew's avatar
Drew committed
367
        var linkWithProduct: [String] = []
Drew's avatar
Drew committed
368 369 370 371
        if let arr_ = task[Options.LinkWithProduct.rawValue] {
            guard let arr = arr_.vector else {
                fatalError("Non-vector link directive \(arr_)")
            }
Drew's avatar
Drew committed
372
            for product in arr {
Drew's avatar
Drew committed
373 374
                guard var p = product.string else { fatalError("non-string product \(product)") }
                if p.hasSuffix(".dynamic") {
375
                    p.replace(searchTerm: ".dynamic", replacement: Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
376
                }
Drew's avatar
Drew committed
377 378 379
                linkWithProduct.append(p)
            }
        }
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

        ///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
408 409
        guard case .some(.StringLiteral(let outputTypeString)) = task[Options.OutputType.rawValue] else {
            fatalError("No \(Options.OutputType.rawValue) for task \(task)")
410
        }
Drew's avatar
Drew committed
411 412
        guard let outputType = OutputType(rawValue: outputTypeString) else {
            fatalError("Unknown \(Options.OutputType.rawValue) \(outputTypeString)")
413
        }
414

415
        var compileOptions: [String] = []
Drew's avatar
Drew committed
416
        if let opts = task[Options.CompileOptions.rawValue]?.vector {
417
            for o in opts {
418
                guard let os = o.string else { fatalError("Compile option \(o) is not a string") }
419 420 421
                compileOptions.append(os)
            }
        }
Drew's avatar
Drew committed
422

423 424 425 426 427 428 429 430
        var cCompileOptions: [String] = []
        if let opts = task[Options.CCompileOptions.rawValue]?.vector {
            for o in opts {
                guard let os = o.string else { fatalError("C compile option \(o) is not a string")}
                cCompileOptions.append(os)
            }
        }

431 432 433 434 435 436 437 438 439 440
        //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])}

441
        if let includePaths = task[Options.IncludeWithUser.rawValue]?.vector {
Drew's avatar
Drew committed
442 443 444
            for path_s in includePaths {
                guard let path = path_s.string else { fatalError("Non-string path \(path_s)") }
                compileOptions.append("-I")
445
                compileOptions.append((userPath() + path).description)
Drew's avatar
Drew committed
446 447
            }
        }
Drew's avatar
Drew committed
448
        var linkOptions: [String] = []
Drew's avatar
Drew committed
449
        if let opts = task[Options.LinkOptions.rawValue]?.vector {
Drew's avatar
Drew committed
450 451 452 453 454
            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
455

Drew's avatar
Drew committed
456 457 458 459 460 461 462 463
        let bitcode: Bool
        //do we have an explicit bitcode setting?
        if let b = task[Options.Bitcode.rawValue] {
            bitcode = b.bool!
        }
        else {
            bitcode = false
        }
464 465 466 467 468 469 470 471

        //separate sources
        guard let sourceDescriptions = task[Options.Source.rawValue]?.vector?.flatMap({$0.string}) else { fatalError("Can't find sources for atllbuild.") }

        var sources = collectSources(sourceDescriptions: sourceDescriptions, taskForCalculatingPath: task)

        let cSources = sources.filter({$0.description.hasSuffix(".c")})
        
Drew's avatar
Drew committed
472 473 474 475
        //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"])
476 477 478
            if cSources.count > 0 {
                print("Warning: bitcode is not supported for C sources")
            }
Drew's avatar
Drew committed
479 480
        }

481 482


Drew's avatar
Drew committed
483
        //check for modulemaps
484 485 486 487 488 489 490 491 492 493 494 495 496 497
        /*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
498
        for product in linkWithProduct {
499
            let productName = product.split(character: ".")[0]
500
            let moduleMapPath = workDirectory + "products/\(productName).modulemap"
501
            if FS.fileExists(path: moduleMapPath) {
502 503 504 505 506 507 508
                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
509 510 511
            }
        }

512

Drew's avatar
Drew committed
513
        //xctestify
Drew's avatar
Drew committed
514
        if task[Options.XCTestify.rawValue]?.bool == true {
515
            precondition(outputType == .Executable, "You must use :\(Options.OutputType.rawValue) executable with xctestify.")
Drew's avatar
Drew committed
516
            //inject platform-specific flags
Drew's avatar
Drew committed
517 518
            switch(Platform.targetPlatform) {
                case .OSX:
519 520
                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"])
521

Drew's avatar
Drew committed
522 523
                case .Linux:
                break
Drew's avatar
Drew committed
524

Drew's avatar
Drew committed
525
                case .iOS, .iOSGeneric:
Drew's avatar
Drew committed
526
                fatalError("\(Options.XCTestify.rawValue) is not supported for iOS")
Drew's avatar
Drew committed
527
            }
528
        }
Drew's avatar
Drew committed
529
        if task[Options.XCTestStrict.rawValue]?.bool == true {
Drew's avatar
Drew committed
530
            switch Platform.targetPlatform {
531
            case .OSX:
Drew's avatar
Drew committed
532
                //inject XCTestCaseProvider.swift
533 534 535 536 537 538 539 540 541 542 543 544
                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
545
                
546 547

            case .Linux:
Drew's avatar
Drew committed
548
                break
Drew's avatar
Drew committed
549

Drew's avatar
Drew committed
550
                case .iOS, .iOSGeneric:
Drew's avatar
Drew committed
551
                fatalError("\(Options.XCTestStrict.rawValue) is not supported for iOS")
Drew's avatar
Drew committed
552
            }
Drew's avatar
Drew committed
553
        }
Drew's avatar
Drew committed
554 555 556 557 558 559 560
        let moduleMap: ModuleMapType
        if task[Options.ModuleMap.rawValue]?.string == "synthesized" {
            moduleMap = .Synthesized
        }
        else {
            moduleMap = .None
        }
Drew's avatar
Drew committed
561

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

564 565 566 567 568 569 570
        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 }

571 572
        if task[Options.Framework.rawValue]?.bool == true {
            #if !os(OSX)
573
            fatalError("\(Options.Framework.rawValue) is not supported on this host.")
574 575 576 577
            #endif
            linkOptions.append("-Xlinker")
            linkOptions.append("-install_name")
            linkOptions.append("-Xlinker")
578 579 580 581 582 583 584 585 586
            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)")
            }
            
587
        }
588

589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
        let hSources = sources.filter({$0.description.hasSuffix(".h")}).map({$0.description})
        let umbrellaHeader = task[Options.UmbrellaHeader.rawValue]?.string

        var moduleMapLinks: [String] = []
        if let links = task[Options.ModuleMapLink.rawValue]?.vector {
            precondition(moduleMap == .Synthesized, ":\(Options.ModuleMap.rawValue) \"synthesized\" must be used with the \(Options.ModuleMapLink.rawValue) option")
            for link in links {
                guard case .StringLiteral(let l) = link else {
                    fatalError("Non-string \(Options.ModuleMapLink.rawValue) \(link)")
                }
                moduleMapLinks.append(l)
            }
        }

        if hSources.count > 0 || umbrellaHeader != nil { //we need to import the underlying module
            precondition(moduleMap == .Synthesized, ":\(Options.ModuleMap.rawValue) \"synthesized\" must be used with the \(Options.UmbrellaHeader.rawValue) option or when compiling C headers")
            let umbrellaHeaderArg: String?
            if umbrellaHeader != nil { umbrellaHeaderArg = "Umbrella.h" } else {umbrellaHeaderArg = nil}
            //add ../../ to hsources to get out from inside workDirectory/include
            let relativeHSources = hSources.map({"../../" + $0})
            let s = synthesizeModuleMap(name: name, umbrellaHeader: umbrellaHeaderArg, headers: relativeHSources, link: moduleMapLinks)
610 611
            do {
                try s.write(to: workDirectory + "include/module.modulemap")
612 613 614 615
                if let u = umbrellaHeader {
                    try FS.copyItem(from: task.importedPath + u, to: workDirectory + "include/Umbrella.h")
                }
                
616
            } catch {
617
                fatalError("Could not synthesize module map during build: \(error)")
618
            }
619
            compileOptions.append("-I")
620
            compileOptions.append(workDirectory.appending("include").description + "/")
621 622
            compileOptions.append("-import-underlying-module")
        }
623

Drew's avatar
Drew committed
624
        //inject target
625

Drew's avatar
Drew committed
626 627
        switch(Platform.targetPlatform) {
            case .iOS(let arch):
628
            let targetTuple: [String]
Drew's avatar
Drew committed
629 630
            switch(arch) {
                case .x86_64:
631
                targetTuple = ["-target","x86_64-apple-ios9.3"] 
Drew's avatar
Drew committed
632 633

                case .i386:
634
                targetTuple = ["-target","i386-apple-ios9.3"]
Drew's avatar
Drew committed
635 636

                case .arm64:
637
                targetTuple = ["-target","arm64-apple-ios9.3"]
Drew's avatar
Drew committed
638 639

                case .armv7:
640
                targetTuple = ["-target","armv7-apple-ios9.3"]
Drew's avatar
Drew committed
641 642

            }
643 644 645
            compileOptions.append(contentsOf: targetTuple)
            linkOptions.append(contentsOf: targetTuple)
            cCompileOptions.append(contentsOf: targetTuple)
Drew's avatar
Drew committed
646 647 648
            linkOptions.append(contentsOf: ["-Xlinker", "-syslibroot","-Xlinker",Platform.targetPlatform.sdkPath!])
            case .OSX, .Linux:
                break //not required
Drew's avatar
Drew committed
649 650
            case .iOSGeneric:
                fatalError("Generic platform iOS cannot be used with atllbuild; choose a specific platform or use atbin")
Drew's avatar
Drew committed
651 652
        }

Drew's avatar
Drew committed
653
        let bootstrapOnly: Bool
Drew's avatar
Drew committed
654

Drew's avatar
Drew committed
655
        if task[Options.BootstrapOnly.rawValue]?.bool == true {
Drew's avatar
Drew committed
656
            bootstrapOnly = true
Drew's avatar
Drew committed
657 658
            //update the build platform to be the one passed on the CLI
            Platform.buildPlatform = Platform.targetPlatform
Drew's avatar
Drew committed
659 660 661 662
        }
        else {
            bootstrapOnly = false
        }
663

Drew's avatar
Drew committed
664 665
        ///The next task will not be bootstrapped.
        defer { Platform.buildPlatform = Platform.hostPlatform }
666

667
        let sdk: Bool
Drew's avatar
Drew committed
668
        if task[Options.LinkSDK.rawValue]?.bool == false {
669 670 671
            sdk = false
        }
        else { sdk = true }
672

673
        let llbuildyamlpath : Path
Drew's avatar
Drew committed
674

Drew's avatar
Drew committed
675
        if let value = task[Options.llBuildYaml.rawValue]?.string {
676
            llbuildyamlpath = Path(value)
Drew's avatar
Drew committed
677 678
        }
        else {
679
            llbuildyamlpath = workDirectory.appending("llbuild.yaml")
Drew's avatar
Drew committed
680
        }
681
        let swiftCPath = findToolPath(toolName: "swiftc", toolchain: toolchain)
682

683
        var enableWMO: Bool
Drew's avatar
Drew committed
684 685
        if let wmo = task[Options.WholeModuleOptimization.rawValue]?.bool {
            enableWMO = wmo
Drew's avatar
Drew committed
686
            //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
687 688 689
        }
        else { enableWMO = false }

690 691 692 693 694 695 696 697 698 699
        // 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")
700
            cCompileOptions.append("-g")
701 702 703 704
        }

        if currentConfiguration.optimize == true {
            compileOptions.append("-O")
705
            cCompileOptions.append("-Os")
Drew's avatar
Drew committed
706 707 708 709 710 711 712 713
            switch(Platform.buildPlatform) {
                case .Linux:
                //don't enable WMO on Linux
                //due to bug in swift-preview-1
                break
                default:
                enableWMO = true
            }
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
        }
        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")
732
            cCompileOptions.append("-DATBUILD_DEBUG")
733 734
            case .Release:
            compileOptions.append("-DATBUILD_RELEASE")
735
            cCompileOptions.append("-DATBUILD_RELEASE")
736 737
            case .Benchmark:
            compileOptions.append("-DATBUILD_BENCH")
738 739
            cCompileOptions.append("-DATBUILD_BENCH")

740 741
            case .Test:
            compileOptions.append("-DATBUILD_TEST")
742
            cCompileOptions.append("-DATBUILD_TEST")
743 744 745 746
            case .None:
            break //too much magic to insert an arg in this case
            case .User(let str):
            compileOptions.append("-DATBUILD_\(str)")
747
            cCompileOptions.append("-DATBUILD_\(str)")
748 749 750 751
        }

        // MARK: emit llbuildyaml 

752 753 754 755 756 757 758 759 760
        //separate sources
        let swiftSources = sources.filter({$0.description.hasSuffix(".swift")})

        if hSources.count > 0 {
            precondition(moduleMap == .Synthesized,"Use :\(Options.ModuleMap.rawValue) \"synthesized\" when compiling C headers")
        }


        let yaml = llbuildyaml(swiftSources: swiftSources,cSources: cSources, workdir: workDirectory, modulename: name, linkSDK: sdk, compileOptions: compileOptions, cCompileOptions: cCompileOptions, linkOptions: linkOptions, outputType: outputType, linkWithProduct: linkWithProduct, linkWithAtbin: linkWithAtbin, swiftCPath: swiftCPath, executableName: executableName, enableWMO: enableWMO)
761
        let _ = try? yaml.write(to: llbuildyamlpath)
Drew's avatar
Drew committed
762
        if bootstrapOnly { return }
Drew's avatar
Drew committed
763

764 765
        //MARK: execute build

Drew's avatar
Drew committed
766
        switch moduleMap {
767 768 769
        case .None:
            break
        case .Synthesized:
770 771
            // "public" modulemap
            let s = synthesizeModuleMap(name: name, umbrellaHeader: nil, headers: [], link: moduleMapLinks)
772 773 774 775 776
            do {
                try s.write(to: workDirectory + "products/\(name).modulemap")
            } catch {
                fatalError("Could not write synthesized module map: \(error)")
            }
Drew's avatar
Drew committed
777
        }
778 779

        let cmd = "\(findToolPath(toolName: "swift-build-tool",toolchain: toolchain)) -f \(llbuildyamlpath)"
780
        anarchySystem(cmd)
Drew's avatar
Drew committed
781
        if task[Options.PublishProduct.rawValue]?.bool == true {
782 783 784 785 786 787 788 789
            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:
790
                    try FS.copyItem(from: workDirectory + "products/\(executableName)", to: Path("bin/\(executableName)"))
791 792 793 794 795 796
                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
797
                case .None:
798
                    break
Drew's avatar
Drew committed
799
                case .Synthesized:
800 801 802 803
                    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
804
            }
805
        }
Drew's avatar
Drew committed
806

807 808
    }
}