atllbuild.swift 30.3 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 {
177
                args += ["-sdk", sdkPath]
Drew's avatar
Drew committed
178
            }
179
        }
180
        args += compileOptions
181

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

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

207

208 209 210
        case .StaticLibrary:
            yaml += "    tool: shell\n"
            var llbuild_inputs = ["<atllbuild-swiftc>"]
211
            llbuild_inputs.append(contentsOf: objects)
212
            yaml += "    inputs: \(llbuild_inputs)\n"
213
            let libPath = productPath.appending(modulename + ".a")
214
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
215

216 217 218 219 220 221 222 223
            //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)\""
224
            return yaml
Drew's avatar
Drew committed
225 226 227 228

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

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

272

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

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

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

Drew's avatar
Drew committed
314
        //create the working directory
315
        let workDirectory = Path(".atllbuild")
316

Drew's avatar
Drew committed
317 318 319 320
        //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
321
        if Process.arguments.contains("--clean") {
322 323
            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
324
        }
325
        let _ = try? FS.removeItem(path: workDirectory.appending("include"), recursive: true)
326

Drew's avatar
Drew committed
327 328


329 330 331 332
        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
333

334
        ///MARK: parse arguments
Drew's avatar
Drew committed
335
        var linkWithProduct: [String] = []
Drew's avatar
Drew committed
336 337 338 339
        if let arr_ = task[Options.LinkWithProduct.rawValue] {
            guard let arr = arr_.vector else {
                fatalError("Non-vector link directive \(arr_)")
            }
Drew's avatar
Drew committed
340
            for product in arr {
Drew's avatar
Drew committed
341 342
                guard var p = product.string else { fatalError("non-string product \(product)") }
                if p.hasSuffix(".dynamic") {
343
                    p.replace(searchTerm: ".dynamic", replacement: Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
344
                }
Drew's avatar
Drew committed
345 346 347
                linkWithProduct.append(p)
            }
        }
348 349 350 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

        ///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
376 377
        guard case .some(.StringLiteral(let outputTypeString)) = task[Options.OutputType.rawValue] else {
            fatalError("No \(Options.OutputType.rawValue) for task \(task)")
378
        }
Drew's avatar
Drew committed
379 380
        guard let outputType = OutputType(rawValue: outputTypeString) else {
            fatalError("Unknown \(Options.OutputType.rawValue) \(outputTypeString)")
381
        }
382

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

391 392 393 394 395 396 397 398 399 400
        //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])}

401
        if let includePaths = task[Options.IncludeWithUser.rawValue]?.vector {
Drew's avatar
Drew committed
402 403 404
            for path_s in includePaths {
                guard let path = path_s.string else { fatalError("Non-string path \(path_s)") }
                compileOptions.append("-I")
405
                compileOptions.append((userPath() + path).description)
Drew's avatar
Drew committed
406 407
            }
        }
Drew's avatar
Drew committed
408
        var linkOptions: [String] = []
Drew's avatar
Drew committed
409
        if let opts = task[Options.LinkOptions.rawValue]?.vector {
Drew's avatar
Drew committed
410 411 412 413 414
            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
415

Drew's avatar
Drew committed
416 417 418 419 420 421 422 423 424 425 426 427 428 429
        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"])
        }

430 431


Drew's avatar
Drew committed
432
        //check for modulemaps
433 434 435 436 437 438 439 440 441 442 443 444 445 446
        /*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
447
        for product in linkWithProduct {
448
            let productName = product.split(character: ".")[0]
449
            let moduleMapPath = workDirectory + "products/\(productName).modulemap"
450
            if FS.fileExists(path: moduleMapPath) {
451 452 453 454 455 456 457
                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
458 459 460
            }
        }

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

Drew's avatar
Drew committed
464
        //xctestify
Drew's avatar
Drew committed
465
        if task[Options.XCTestify.rawValue]?.bool == true {
466
            precondition(outputType == .Executable, "You must use :\(Options.OutputType.rawValue) executable with xctestify.")
Drew's avatar
Drew committed
467
            //inject platform-specific flags
Drew's avatar
Drew committed
468 469
            switch(Platform.targetPlatform) {
                case .OSX:
470 471
                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"])
472

Drew's avatar
Drew committed
473 474
                case .Linux:
                break
Drew's avatar
Drew committed
475

Drew's avatar
Drew committed
476
                case .iOS, .iOSGeneric:
Drew's avatar
Drew committed
477
                fatalError("\(Options.XCTestify.rawValue) is not supported for iOS")
Drew's avatar
Drew committed
478
            }
479
        }
Drew's avatar
Drew committed
480
        if task[Options.XCTestStrict.rawValue]?.bool == true {
Drew's avatar
Drew committed
481
            switch Platform.targetPlatform {
482
            case .OSX:
Drew's avatar
Drew committed
483
                //inject XCTestCaseProvider.swift
484 485 486 487 488 489 490 491 492 493 494 495
                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
496
                
497 498

            case .Linux:
Drew's avatar
Drew committed
499
                break
Drew's avatar
Drew committed
500

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

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

515 516 517 518 519 520 521
        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 }

522 523
        if task[Options.Framework.rawValue]?.bool == true {
            #if !os(OSX)
524
            fatalError("\(Options.Framework.rawValue) is not supported on this host.")
525 526 527 528
            #endif
            linkOptions.append("-Xlinker")
            linkOptions.append("-install_name")
            linkOptions.append("-Xlinker")
529 530 531 532 533 534 535 536 537
            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)")
            }
            
538
        }
539

540
        if let umbrellaHeader = task[Options.UmbrellaHeader.rawValue]?.string {
Drew's avatar
Drew committed
541
            precondition(moduleMap == .Synthesized, ":\(Options.ModuleMap.rawValue) \"synthesized\" must be used with the \(Options.UmbrellaHeader.rawValue) option")
542
            let s = synthesizeModuleMap(name: name, umbrellaHeader: "Umbrella.h")
543 544 545 546 547 548
            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)")
            }
549
            compileOptions.append("-I")
550
            compileOptions.append(workDirectory.appending("include").description + "/")
551 552
            compileOptions.append("-import-underlying-module")
        }
553

Drew's avatar
Drew committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
        //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
578 579
            case .iOSGeneric:
                fatalError("Generic platform iOS cannot be used with atllbuild; choose a specific platform or use atbin")
Drew's avatar
Drew committed
580 581
        }

Drew's avatar
Drew committed
582
        let bootstrapOnly: Bool
Drew's avatar
Drew committed
583

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

Drew's avatar
Drew committed
593 594
        ///The next task will not be bootstrapped.
        defer { Platform.buildPlatform = Platform.hostPlatform }
595

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

602
        let llbuildyamlpath : Path
Drew's avatar
Drew committed
603

Drew's avatar
Drew committed
604
        if let value = task[Options.llBuildYaml.rawValue]?.string {
605
            llbuildyamlpath = Path(value)
Drew's avatar
Drew committed
606 607
        }
        else {
608
            llbuildyamlpath = workDirectory.appending("llbuild.yaml")
Drew's avatar
Drew committed
609
        }
610
        let swiftCPath: Path
Drew's avatar
Drew committed
611
        if let c = task[Options.SwiftCPath.rawValue]?.string {
Drew's avatar
Drew committed
612
            print("Warning: \(Options.SwiftCPath.rawValue) is deprecated and will be removed in a future release of atbuild.  Use --toolchain to specify a different toolchain, or --platform when bootstrapping to a different platform.")
Drew's avatar
Drew committed
613
            sleep(5)
614
            swiftCPath = Path(c)
Drew's avatar
Drew committed
615 616
        }
        else {
617
            swiftCPath = findToolPath(toolName: "swiftc", toolchain: toolchain)
Drew's avatar
Drew committed
618
        }
619

620
        var enableWMO: Bool
Drew's avatar
Drew committed
621 622
        if let wmo = task[Options.WholeModuleOptimization.rawValue]?.bool {
            enableWMO = wmo
Drew's avatar
Drew committed
623
            //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
624 625 626
        }
        else { enableWMO = false }

627 628 629 630 631 632 633 634 635 636 637 638 639 640
        // 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
641 642 643 644 645 646 647 648
            switch(Platform.buildPlatform) {
                case .Linux:
                //don't enable WMO on Linux
                //due to bug in swift-preview-1
                break
                default:
                enableWMO = true
            }
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 676 677 678 679 680
        }
        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 

681
        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)
682
        let _ = try? yaml.write(to: llbuildyamlpath)
Drew's avatar
Drew committed
683
        if bootstrapOnly { return }
Drew's avatar
Drew committed
684

685 686
        //MARK: execute build

Drew's avatar
Drew committed
687
        switch moduleMap {
688 689 690 691 692 693 694 695 696
        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
697
        }
698 699

        let cmd = "\(findToolPath(toolName: "swift-build-tool",toolchain: toolchain)) -f \(llbuildyamlpath)"
700
        anarchySystem(cmd)
Drew's avatar
Drew committed
701
        if task[Options.PublishProduct.rawValue]?.bool == true {
702 703 704 705 706 707 708 709
            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:
710
                    try FS.copyItem(from: workDirectory + "products/\(executableName)", to: Path("bin/\(executableName)"))
711 712 713 714 715 716
                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
717
                case .None:
718
                    break
Drew's avatar
Drew committed
719
                case .Synthesized:
720 721 722 723
                    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
724
            }
725
        }
Drew's avatar
Drew committed
726

727 728
    }
}