atllbuild.swift 20.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

Drew's avatar
Drew committed
34

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

39 40 41 42 43 44 45
    /**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"
46
        s += "public func testCase<T: XCTestCase>(_ allTests: [(String, T -> () throws -> Void)]) -> XCTestCase {\n"
47
        s += "    fatalError(\"Can't get here.\")\n"
48 49
        s += "}\n"
        s += "\n"
50
        s += "public func XCTMain(_ testCases: [XCTestCase]) {\n"
51 52 53 54 55
        s += "    fatalError(\"Can't get here.\")\n"
        s += "}\n"
        s += "\n"
        return s
    }()
56

57 58 59
    enum OutputType {
        case Executable
        case StaticLibrary
Drew's avatar
Drew committed
60
        case DynamicLibrary
61
    }
Drew's avatar
Drew committed
62 63 64 65 66

    enum ModuleMapType {
        case None
        case Synthesized
    }
67

68 69 70 71 72 73 74
    /**
     * 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.
     *   - returns: The string contents for llbuild.yaml suitable for processing by swift-build-tool
     */
75 76
    func llbuildyaml(sources: [Path], workdir: Path, modulename: String, linkSDK: Bool, compileOptions: [String], linkOptions: [String], outputType: OutputType, linkWithProduct:[String], swiftCPath: Path) -> String {
        let productPath = workdir.appending("products")
Drew's avatar
Drew committed
77 78
        //this format is largely undocumented, but I reverse-engineered it from SwiftPM.
        var yaml = "client:\n  name: swift-build\n\n"
79

Drew's avatar
Drew committed
80 81
        yaml += "tools: {}\n\n"

82

Drew's avatar
Drew committed
83
        yaml += "targets:\n"
Drew's avatar
Drew committed
84 85
        yaml += "  \"\": [<atllbuild>]\n"
        yaml += "  atllbuild: [<atllbuild>]\n"
86

Drew's avatar
Drew committed
87
        //this is the "compile" command
88

Drew's avatar
Drew committed
89 90 91
        yaml += "commands:\n"
        yaml += "  <atllbuild-swiftc>:\n"
        yaml += "     tool: swift-compiler\n"
Drew's avatar
Drew committed
92
        yaml += "     executable: \"\(swiftCPath)\"\n"
Drew's avatar
Drew committed
93 94
        yaml += "     inputs: \(sources)\n"
        yaml += "     sources: \(sources)\n"
95

Drew's avatar
Drew committed
96 97
        //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
98
            workdir.appending("objects").appending(source.basename() + ".o").description
Drew's avatar
Drew committed
99 100
        }
        yaml += "     objects: \(objects)\n"
Drew's avatar
Drew committed
101 102
        //this crazy syntax is how llbuild specifies outputs
        var llbuild_outputs = ["<atllbuild-swiftc>"]
103
        llbuild_outputs.append(contentsOf: objects)
Drew's avatar
Drew committed
104
        yaml += "     outputs: \(llbuild_outputs)\n"
105

106 107 108
        switch(outputType) {
        case .Executable:
            break
Drew's avatar
Drew committed
109
        case .StaticLibrary, .DynamicLibrary:
110 111
            yaml += "     is-library: true\n" //I have no idea what the effect of this is, but swiftPM does it, so I'm including it.
        }
112

Drew's avatar
Drew committed
113
        yaml += "     module-name: \(modulename)\n"
114
        let swiftModulePath = productPath.appending(modulename + ".swiftmodule")
115
        yaml += "     module-output-path: \(swiftModulePath)\n"
Drew's avatar
Drew committed
116
        yaml += "     temps-path: \(workdir)/llbuildtmp\n"
117

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

121
        if linkSDK {
Drew's avatar
Drew committed
122
            if let sdkPath = Platform.targetPlatform.sdkPath {
123
                args += ["-sdk", sdkPath]
Drew's avatar
Drew committed
124
            }
125
        }
126
        args += compileOptions
127

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

Drew's avatar
Drew committed
130 131
        //and this is the "link" command
        yaml += "  <atllbuild>:\n"
132 133 134 135 136
        switch(outputType) {
        case .Executable:
            yaml += "    tool: shell\n"
            //this crazy syntax is how sbt declares a dependency
            var llbuild_inputs = ["<atllbuild-swiftc>"]
137
            llbuild_inputs += objects
138
            let builtProducts = linkWithProduct.map { (workdir + ("products/"+$0)).description }
139 140
            llbuild_inputs += builtProducts
            let executablePath = productPath.appending(modulename)
141
            yaml += "    inputs: \(llbuild_inputs)\n"
142
            yaml += "    outputs: [\"<atllbuild>\", \"\(executablePath)\"]\n"
143
            //and now we have the crazy 'args'
144 145 146 147
            args = [swiftCPath.description, "-o", executablePath.description]
            args += objects
            args += builtProducts
            args += linkOptions
148
            yaml += "    args: \(args)\n"
149 150
            yaml += "    description: Linking executable \(executablePath)\n"
            return yaml
Drew's avatar
Drew committed
151

152

153 154 155
        case .StaticLibrary:
            yaml += "    tool: shell\n"
            var llbuild_inputs = ["<atllbuild-swiftc>"]
156
            llbuild_inputs.append(contentsOf: objects)
157
            yaml += "    inputs: \(llbuild_inputs)\n"
158
            let libPath = productPath.appending(modulename + ".a")
159
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
160

161 162 163 164 165 166 167 168
            //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)\""
169
            return yaml
Drew's avatar
Drew committed
170 171 172 173

        case .DynamicLibrary:
            yaml += "    tool: shell\n"
            var llbuild_inputs = ["<atllbuild-swiftc>"]
174
            llbuild_inputs += objects
175
            let builtProducts = linkWithProduct.map { (workdir + ("products/"+$0)).description }
176
            llbuild_inputs += builtProducts
Drew's avatar
Drew committed
177
            yaml += "    inputs: \(llbuild_inputs)\n"
178
            let libPath = productPath.appending(modulename + Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
179
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
180 181 182 183
            var args = [swiftCPath.description, "-o", libPath.description, "-emit-library"]
            args += objects
            args += builtProducts
            args += linkOptions
Drew's avatar
Drew committed
184 185 186
            yaml += "    args: \(args)\n"
            yaml += "    description: \"Linking Library:  \(libPath)\""
            return yaml
187
        }
188
     }
189

Drew's avatar
Drew committed
190 191 192 193
    private enum Options: String {
        case Tool = "tool"
        case Name = "name"
        case Dependencies = "dependencies"
194 195 196
        case OutputType = "output-type"
        case Source = "sources"
        case BootstrapOnly = "bootstrap-only"
Drew's avatar
Drew committed
197
        case llBuildYaml = "llbuildyaml"
198 199 200 201 202
        case CompileOptions = "compile-options"
        case LinkOptions = "link-options"
        case LinkSDK = "link-sdk"
        case LinkWithProduct = "link-with"
        case SwiftCPath = "swiftc-path"
Drew's avatar
Drew committed
203
        case XCTestify = "xctestify"
204
        case XCTestStrict = "xctest-strict"
205
		case IncludeWithUser = "include-with-user"
206
        case PublishProduct = "publish-product"
207
        case UmbrellaHeader = "umbrella-header"
Drew's avatar
Drew committed
208
        case ModuleMap = "module-map"
Drew's avatar
Drew committed
209
        case WholeModuleOptimization = "whole-module-optimization"
210
        case Framework = "framework"
211

212

Drew's avatar
Drew committed
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
        static var allOptions : [Options] {
            return [
                Name,
                Dependencies,
                OutputType,
                Source,
                BootstrapOnly,
                llBuildYaml,
                CompileOptions,
                LinkOptions,
                LinkSDK,
                LinkWithProduct,
                SwiftCPath,
                XCTestify,
                XCTestStrict,
228
				IncludeWithUser,
229
                PublishProduct,
Drew's avatar
Drew committed
230
				UmbrellaHeader,
231 232
                WholeModuleOptimization,
                Framework
Drew's avatar
Drew committed
233 234 235
            ]
        }
    }
Drew's avatar
Drew committed
236

Drew's avatar
Drew committed
237
    func run(task: Task, toolchain: String) {
238
        run(task: task, toolchain: toolchain, wmoHack: false)
Drew's avatar
Drew committed
239
    }
240

Drew's avatar
Drew committed
241
    func run(task: Task, toolchain: String, wmoHack : Bool = false) {
242

243
        //warn if we don't understand an option
244 245 246 247
        var knownOptions = Options.allOptions.map({$0.rawValue})
        for option in Task.Option.allOptions.map({$0.rawValue}) {
            knownOptions.append(option)
        }
248
        for key in task.allKeys {
249
            if !knownOptions.contains(key) {
Drew's avatar
Drew committed
250
                print("Warning: unknown option \(key) for task \(task.qualifiedName)")
251 252
            }
        }
253

Drew's avatar
Drew committed
254
        //create the working directory
255
        let workDirectory = Path(".atllbuild")
256

Drew's avatar
Drew committed
257 258 259 260
        //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
261
        if Process.arguments.contains("--clean") {
262 263
            let _ = try? FS.removeItem(path: workDirectory.appending("objects"))
            let _ = try? FS.removeItem(path: workDirectory.appending("llbuildtmp"))
Drew's avatar
Drew committed
264
        }
265
        let _ = try? FS.removeItem(path: workDirectory.appending("include"))
266

Drew's avatar
Drew committed
267 268


269 270 271 272
        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
273

Drew's avatar
Drew committed
274
        //parse arguments
Drew's avatar
Drew committed
275
        var linkWithProduct: [String] = []
Drew's avatar
Drew committed
276 277 278 279
        if let arr_ = task[Options.LinkWithProduct.rawValue] {
            guard let arr = arr_.vector else {
                fatalError("Non-vector link directive \(arr_)")
            }
Drew's avatar
Drew committed
280
            for product in arr {
Drew's avatar
Drew committed
281 282
                guard var p = product.string else { fatalError("non-string product \(product)") }
                if p.hasSuffix(".dynamic") {
283
                    p.replace(searchTerm: ".dynamic", replacement: Platform.targetPlatform.dynamicLibraryExtension)
Drew's avatar
Drew committed
284
                }
Drew's avatar
Drew committed
285 286 287
                linkWithProduct.append(p)
            }
        }
Drew's avatar
Drew committed
288

289
        let outputType: OutputType
Drew's avatar
Drew committed
290
        if task[Options.OutputType.rawValue]?.string == "static-library" {
291 292
            outputType = .StaticLibrary
        }
Drew's avatar
Drew committed
293
        else if task[Options.OutputType.rawValue]?.string == "executable" {
294 295
            outputType = .Executable
        }
Drew's avatar
Drew committed
296 297 298
        else if task[Options.OutputType.rawValue]?.string == "dynamic-library" {
            outputType = .DynamicLibrary
        }
299
        else {
300
            fatalError("Unknown \(Options.OutputType.rawValue) \(task["outputType"])")
301
        }
302

303
        var compileOptions: [String] = []
Drew's avatar
Drew committed
304
        if let opts = task[Options.CompileOptions.rawValue]?.vector {
305
            for o in opts {
306
                guard let os = o.string else { fatalError("Compile option \(o) is not a string") }
307 308 309
                compileOptions.append(os)
            }
        }
Drew's avatar
Drew committed
310 311 312 313

        if wmoHack {
            compileOptions.append("-whole-module-optimization")
        }
314

315
        if let includePaths = task[Options.IncludeWithUser.rawValue]?.vector {
Drew's avatar
Drew committed
316 317 318
            for path_s in includePaths {
                guard let path = path_s.string else { fatalError("Non-string path \(path_s)") }
                compileOptions.append("-I")
319
                compileOptions.append((userPath() + path).description)
Drew's avatar
Drew committed
320 321
            }
        }
Drew's avatar
Drew committed
322
        var linkOptions: [String] = []
Drew's avatar
Drew committed
323
        if let opts = task[Options.LinkOptions.rawValue]?.vector {
Drew's avatar
Drew committed
324 325 326 327 328
            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
329 330 331

        //check for modulemaps
        for product in linkWithProduct {
332
            let productName = product.split(character: ".")[0]
333
            let moduleMapPath = workDirectory + "products/\(productName).modulemap"
334
            if FS.fileExists(path: moduleMapPath) {
Drew's avatar
Drew committed
335 336 337 338
                /*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. */
339
                let pathName = workDirectory + "include/\(productName)"
340 341 342
                try! FS.createDirectory(path: pathName)
                try! FS.copyItem(from: moduleMapPath, to: pathName.appending("module.modulemap"))
                compileOptions.append(contentsOf: ["-I", pathName.description])
Drew's avatar
Drew committed
343 344 345
            }
        }

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

Drew's avatar
Drew committed
349
        //xctestify
Drew's avatar
Drew committed
350
        if task[Options.XCTestify.rawValue]?.bool == true {
351
            precondition(outputType == .Executable, "You must use :\(Options.OutputType.rawValue) executable with xctestify.")
Drew's avatar
Drew committed
352
            //inject platform-specific flags
Drew's avatar
Drew committed
353 354
            switch(Platform.targetPlatform) {
                case .OSX:
355 356
                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"])
Drew's avatar
Drew committed
357 358 359 360
                
                case .Linux:
                break
            }
361
        }
Drew's avatar
Drew committed
362
        if task[Options.XCTestStrict.rawValue]?.bool == true {
Drew's avatar
Drew committed
363 364 365
            switch Platform.targetPlatform {
                case .OSX:
                //inject XCTestCaseProvider.swift
366
                let xcTestCaseProviderPath = try! FS.temporaryDirectory(prefix: "XCTestCase")
Drew's avatar
Drew committed
367

368
                try! ATllbuild.xcTestCaseProvider.write(to: xcTestCaseProviderPath.appending("XCTestCaseProvider.swift"))
Drew's avatar
Drew committed
369 370 371 372 373
                sources.append(xcTestCaseProviderPath)
                
                case .Linux:
                break
            }
Drew's avatar
Drew committed
374
        }
Drew's avatar
Drew committed
375 376 377 378 379 380 381
        let moduleMap: ModuleMapType
        if task[Options.ModuleMap.rawValue]?.string == "synthesized" {
            moduleMap = .Synthesized
        }
        else {
            moduleMap = .None
        }
Drew's avatar
Drew committed
382

Drew's avatar
Drew committed
383
        guard let name = task[Options.Name.rawValue]?.string else { fatalError("No name for atllbuild task") }
384 385 386 387 388 389 390 391 392 393

        if task[Options.Framework.rawValue]?.bool == true {
            #if !os(OSX)
            fatalError("\(Options.Framework.rawValue) is not supported on this platform.")
            #endif
            linkOptions.append("-Xlinker")
            linkOptions.append("-install_name")
            linkOptions.append("-Xlinker")
            linkOptions.append("@rpath/\(name).framework/Versions/A/\(name)")
        }
394

395
        if let umbrellaHeader = task[Options.UmbrellaHeader.rawValue]?.string {
Drew's avatar
Drew committed
396
            precondition(moduleMap == .Synthesized, ":\(Options.ModuleMap.rawValue) \"synthesized\" must be used with the \(Options.UmbrellaHeader.rawValue) option")
397
            let s = synthesizeModuleMap(name: name, umbrellaHeader: "Umbrella.h")
398 399
            try! s.write(to: workDirectory + "include/module.modulemap")
            try! FS.copyItem(from: task.importedPath + umbrellaHeader, to: workDirectory + "include/Umbrella.h")
400
            compileOptions.append("-I")
401
            compileOptions.append(workDirectory.appending("include").description + "/")
402 403
            compileOptions.append("-import-underlying-module")
        }
404

Drew's avatar
Drew committed
405
        let bootstrapOnly: Bool
Drew's avatar
Drew committed
406

Drew's avatar
Drew committed
407
        if task[Options.BootstrapOnly.rawValue]?.bool == true {
Drew's avatar
Drew committed
408
            bootstrapOnly = true
Drew's avatar
Drew committed
409 410
            //update the build platform to be the one passed on the CLI
            Platform.buildPlatform = Platform.targetPlatform
Drew's avatar
Drew committed
411 412 413 414
        }
        else {
            bootstrapOnly = false
        }
415

Drew's avatar
Drew committed
416 417 418
        ///The next task will not be bootstrapped.
        defer { Platform.buildPlatform = Platform.hostPlatform }
        
419
        let sdk: Bool
Drew's avatar
Drew committed
420
        if task[Options.LinkSDK.rawValue]?.bool == false {
421 422 423
            sdk = false
        }
        else { sdk = true }
424

425
        let llbuildyamlpath : Path
Drew's avatar
Drew committed
426

Drew's avatar
Drew committed
427
        if let value = task[Options.llBuildYaml.rawValue]?.string {
428
            llbuildyamlpath = Path(value)
Drew's avatar
Drew committed
429 430
        }
        else {
431
            llbuildyamlpath = workDirectory.appending("llbuild.yaml")
Drew's avatar
Drew committed
432
        }
433
        let swiftCPath: Path
Drew's avatar
Drew committed
434
        if let c = task[Options.SwiftCPath.rawValue]?.string {
Drew's avatar
Drew committed
435
            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.")
436
            swiftCPath = Path(c)
Drew's avatar
Drew committed
437 438
        }
        else {
439
            swiftCPath = findToolPath(toolName: "swiftc", toolchain: toolchain)
Drew's avatar
Drew committed
440
        }
441 442

        let yaml = llbuildyaml(sources: sources, workdir: workDirectory, modulename: name, linkSDK: sdk, compileOptions: compileOptions, linkOptions: linkOptions, outputType: outputType, linkWithProduct: linkWithProduct, swiftCPath: swiftCPath)
443
        let _ = try? yaml.write(to: llbuildyamlpath)
Drew's avatar
Drew committed
444
        if bootstrapOnly { return }
Drew's avatar
Drew committed
445 446 447 448 449

        switch moduleMap {
                case .None:
                break
                case .Synthesized:
450
                let s = synthesizeModuleMap(name: name, umbrellaHeader: nil)
451
                try! s.write(to: workDirectory + "products/\(name).modulemap")
Drew's avatar
Drew committed
452
        }
453

Drew's avatar
Drew committed
454
        //SR-566
455
        let cmd = "\(findToolPath(toolName: "swift-build-tool",toolchain: toolchain)) -f \(llbuildyamlpath)"
456
        anarchySystem(cmd)
Drew's avatar
Drew committed
457
        if task[Options.PublishProduct.rawValue]?.bool == true {
458 459
            if !FS.isDirectory(path: Path("bin")) {
                try! FS.createDirectory(path: Path("bin"))
460
            }
461 462
            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"))
463 464
            switch outputType {
            case .Executable:
465
                try! FS.copyItem(from: workDirectory + "products/\(name)", to: Path("bin/\(name)"))
466
            case .StaticLibrary:
467
                try! FS.copyItem(from: workDirectory + "products/\(name).a", to: Path("bin/\(name).a"))
Drew's avatar
Drew committed
468
            case .DynamicLibrary:
469
                try! FS.copyItem(from: workDirectory + ("products/\(name)." + Platform.targetPlatform.dynamicLibraryExtension) , to: Path("bin/\(name)." + Platform.targetPlatform.dynamicLibraryExtension))
470
            }
Drew's avatar
Drew committed
471 472 473 474
            switch moduleMap {
                case .None:
                break
                case .Synthesized:
475
                try! FS.copyItem(from: workDirectory + "products/\(name).modulemap", to: Path("bin/\(name).modulemap"))
Drew's avatar
Drew committed
476
            }
477
        }
Drew's avatar
Drew committed
478 479 480

        if task[Options.WholeModuleOptimization.rawValue]?.bool == true && !wmoHack {
            print("Work around SR-881")
481
            run(task: task, toolchain: toolchain, wmoHack: true)
Drew's avatar
Drew committed
482 483
        }

484 485
    }
}