atllbuild.swift 28 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"
Drew's avatar
Drew committed
259
        case XCTestify = "xctestify"
260
        case XCTestStrict = "xctest-strict"
261
		case IncludeWithUser = "include-with-user"
262
        case PublishProduct = "publish-product"
263
        case UmbrellaHeader = "umbrella-header"
Drew's avatar
Drew committed
264
        case ModuleMap = "module-map"
Drew's avatar
Drew committed
265
        case WholeModuleOptimization = "whole-module-optimization"
266
        case Framework = "framework"
267
        case ExecutableName = "executable-name"
Drew's avatar
Drew committed
268
        case Bitcode = "bitcode"
Drew's avatar
Drew committed
269
        case Magic = "magic"
270

271

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

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

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

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

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

Drew's avatar
Drew committed
325 326


327 328 329 330
        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
331

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

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

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

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

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

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

428 429


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

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

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

Drew's avatar
Drew committed
471 472
                case .Linux:
                break
Drew's avatar
Drew committed
473

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

            case .Linux:
Drew's avatar
Drew committed
497
                break
Drew's avatar
Drew committed
498

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

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

513 514 515 516 517 518 519
        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 }

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

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

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

Drew's avatar
Drew committed
580
        let bootstrapOnly: Bool
Drew's avatar
Drew committed
581

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

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

Drew's avatar
Drew committed
594 595 596 597 598 599 600 601 602
        if task[Options.Magic.rawValue] == nil || task[Options.Magic.rawValue]?.bool == true {
            switch(Platform.buildPlatform) {
                case .OSX:
                linkOptions.append(contentsOf: ["-Xlinker","-dead_strip"])
                default:
                break
            }
        }

603
        let sdk: Bool
Drew's avatar
Drew committed
604
        if task[Options.LinkSDK.rawValue]?.bool == false {
605 606 607
            sdk = false
        }
        else { sdk = true }
608

609
        let llbuildyamlpath : Path
Drew's avatar
Drew committed
610

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

Drew's avatar
Drew committed
619 620 621 622 623 624
        let enableWMO: Bool
        if let wmo = task[Options.WholeModuleOptimization.rawValue]?.bool {
            enableWMO = wmo
        }
        else { enableWMO = false }

625
        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)
626
        let _ = try? yaml.write(to: llbuildyamlpath)
Drew's avatar
Drew committed
627
        if bootstrapOnly { return }
Drew's avatar
Drew committed
628 629

        switch moduleMap {
630 631 632 633 634 635 636 637 638
        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
639
        }
640

Drew's avatar
Drew committed
641
        //SR-566
642
        let cmd = "\(findToolPath(toolName: "swift-build-tool",toolchain: toolchain)) -f \(llbuildyamlpath)"
643
        anarchySystem(cmd)
Drew's avatar
Drew committed
644
        if task[Options.PublishProduct.rawValue]?.bool == true {
645 646 647 648 649 650 651 652
            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:
653
                    try FS.copyItem(from: workDirectory + "products/\(executableName)", to: Path("bin/\(executableName)"))
654 655 656 657 658 659
                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
660
                case .None:
661
                    break
Drew's avatar
Drew committed
662
                case .Synthesized:
663 664 665 666
                    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
667
            }
668
        }
Drew's avatar
Drew committed
669

670 671
    }
}