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

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

Drew's avatar
Drew committed
18 19 20 21
#if os(Linux)
    import Glibc //need sleep
#endif

Drew's avatar
Drew committed
22 23
/**The ATllbuild tool builds a swift module via llbuild.
For more information on this tool, see `docs/attllbuild.md` */
Drew's avatar
Drew committed
24 25
final class ATllbuild : Tool {
    
26 27 28 29 30
    enum OutputType {
        case Executable
        case StaticLibrary
    }
    
31 32 33 34 35 36 37
    /**
     * 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
     */
Drew's avatar
Drew committed
38
    func llbuildyaml(sources: [String], workdir: String, modulename: String, linkSDK: Bool, compileOptions: [String], linkOptions: [String], outputType: OutputType, linkWithProduct:[String], swiftCPath: String) -> String {
39
        let productPath = workdir + "products/"
Drew's avatar
Drew committed
40 41
        //this format is largely undocumented, but I reverse-engineered it from SwiftPM.
        var yaml = "client:\n  name: swift-build\n\n"
Drew's avatar
Drew committed
42 43 44 45
        
        yaml += "tools: {}\n\n"

        
Drew's avatar
Drew committed
46
        yaml += "targets:\n"
Drew's avatar
Drew committed
47 48 49
        yaml += "  \"\": [<atllbuild>]\n"
        yaml += "  atllbuild: [<atllbuild>]\n"
        
Drew's avatar
Drew committed
50
        //this is the "compile" command
Drew's avatar
Drew committed
51 52 53 54
        
        yaml += "commands:\n"
        yaml += "  <atllbuild-swiftc>:\n"
        yaml += "     tool: swift-compiler\n"
Drew's avatar
Drew committed
55
        yaml += "     executable: \"\(swiftCPath)\"\n"
Drew's avatar
Drew committed
56 57 58 59 60
        yaml += "     inputs: \(sources)\n"
        yaml += "     sources: \(sources)\n"
        
        //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
Drew's avatar
Drew committed
61
            workdir + "objects/" + source.toNSString.lastPathComponent + ".o"
Drew's avatar
Drew committed
62 63
        }
        yaml += "     objects: \(objects)\n"
Drew's avatar
Drew committed
64 65 66 67
        //this crazy syntax is how llbuild specifies outputs
        var llbuild_outputs = ["<atllbuild-swiftc>"]
        llbuild_outputs.appendContentsOf(objects)
        yaml += "     outputs: \(llbuild_outputs)\n"
68 69 70 71 72 73 74
        
        switch(outputType) {
        case .Executable:
            break
        case .StaticLibrary:
            yaml += "     is-library: true\n" //I have no idea what the effect of this is, but swiftPM does it, so I'm including it.
        }
Drew's avatar
Drew committed
75 76
        
        yaml += "     module-name: \(modulename)\n"
77 78
        let swiftModulePath = "\(productPath + modulename).swiftmodule"
        yaml += "     module-output-path: \(swiftModulePath)\n"
Drew's avatar
Drew committed
79 80 81
        yaml += "     temps-path: \(workdir)/llbuildtmp\n"
        
        var args : [String] = []
Drew's avatar
Drew committed
82
        args.appendContentsOf(["-j8", "-D","ATBUILD","-I",workdir+"products/"])
83 84
        
        if linkSDK {
Drew's avatar
Drew committed
85
            #if os(OSX) //we don't have SDKPath on linux
86
            args.appendContentsOf(["-sdk", SDKPath])
Drew's avatar
Drew committed
87
            #endif
88
        }
Drew's avatar
Drew committed
89
        args.appendContentsOf(compileOptions)
Drew's avatar
Drew committed
90 91
        
        yaml += "     other-args: \(args)\n"
Drew's avatar
Drew committed
92 93 94
        
        //and this is the "link" command
        yaml += "  <atllbuild>:\n"
95 96 97 98 99 100
        switch(outputType) {
        case .Executable:
            yaml += "    tool: shell\n"
            //this crazy syntax is how sbt declares a dependency
            var llbuild_inputs = ["<atllbuild-swiftc>"]
            llbuild_inputs.appendContentsOf(objects)
Drew's avatar
Drew committed
101 102
            let builtProducts = linkWithProduct.map {workdir+"products/"+$0}
            llbuild_inputs.appendContentsOf(builtProducts)
103
            let executablePath = productPath+modulename
104
            yaml += "    inputs: \(llbuild_inputs)\n"
105
            yaml += "    outputs: [\"<atllbuild>\", \"\(executablePath)\"]\n"
106
            //and now we have the crazy 'args'
Drew's avatar
Drew committed
107
            args = [swiftCPath, "-o",executablePath]
108
            args.appendContentsOf(objects)
Drew's avatar
Drew committed
109
            args.appendContentsOf(builtProducts)
Drew's avatar
Drew committed
110
            args.appendContentsOf(linkOptions)
111
            yaml += "    args: \(args)\n"
112 113
            yaml += "    description: Linking executable \(executablePath)\n"
            return yaml
Drew's avatar
Drew committed
114

115 116 117 118 119 120
        
        case .StaticLibrary:
            yaml += "    tool: shell\n"
            var llbuild_inputs = ["<atllbuild-swiftc>"]
            llbuild_inputs.appendContentsOf(objects)
            yaml += "    inputs: \(llbuild_inputs)\n"
121
            let libPath = productPath + modulename + ".a"
122 123 124 125 126 127 128 129 130 131
            yaml += "    outputs: [\"<atllbuild>\", \"\(libPath)\"]\n"
            
            //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)\""
132
            return yaml
133
        }
134
     }
Drew's avatar
Drew committed
135
    
136
    func run(task: Task) {
Drew's avatar
Drew committed
137 138 139
        //create the working directory
        let workDirectory = ".atllbuild/"
        let manager = NSFileManager.defaultManager()
Drew's avatar
Drew committed
140 141 142 143 144 145 146 147 148
        
        //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.
        let _ = try? manager.removeItemAtPath(workDirectory + "/objects")
        let _ = try? manager.removeItemAtPath(workDirectory + "/llbuildtmp")
        let _ = try? manager.createDirectoryAtPath(workDirectory, withIntermediateDirectories: false, attributes: nil)
        let _ = try? manager.createDirectoryAtPath(workDirectory + "/products", withIntermediateDirectories: false, attributes: nil)
149
        let _ = try? manager.createDirectoryAtPath(workDirectory + "/objects", withIntermediateDirectories: false, attributes: nil)
Drew's avatar
Drew committed
150

Drew's avatar
Drew committed
151
        //parse arguments
Drew's avatar
Drew committed
152
        var linkWithProduct: [String] = []
153
        if let arr = task["linkWithProduct"]?.vector {
Drew's avatar
Drew committed
154
            for product in arr {
155
                guard let p = product.string else { fatalError("non-string product \(product)") }
Drew's avatar
Drew committed
156 157 158
                linkWithProduct.append(p)
            }
        }
159
        let outputType: OutputType
160
        if task["outputType"]?.string == "static-library" {
161 162
            outputType = .StaticLibrary
        }
163
        else if task["outputType"]?.string == "executable" {
164 165 166
            outputType = .Executable
        }
        else {
167
            fatalError("Unknown outputType \(task["outputType"])")
168 169
        }
        
Drew's avatar
Drew committed
170
        var compileOptions: [String] = []
171
        if let opts = task["compileOptions"]?.vector {
Drew's avatar
Drew committed
172
            for o in opts {
173
                guard let os = o.string else { fatalError("Compile option \(o) is not a string") }
Drew's avatar
Drew committed
174 175 176
                compileOptions.append(os)
            }
        }
Drew's avatar
Drew committed
177 178 179 180 181 182 183
        var linkOptions: [String] = []
        if let opts = task["linkOptions"]?.vector {
            for o in opts {
                guard let os = o.string else { fatalError("Link option \(o) is not a string") }
                linkOptions.append(os)
            }
        }
184
        guard let sourceDescriptions = task["source"]?.vector?.flatMap({$0.string}) else { fatalError("Can't find sources for atllbuild.") }
185
                let sources = collectSources(sourceDescriptions, task: task)
Drew's avatar
Drew committed
186

187
        guard let name = task["name"]?.string else { fatalError("No name for atllbuild task") }
Drew's avatar
Drew committed
188 189
        
        let bootstrapOnly: Bool
Drew's avatar
Drew committed
190

191
        if task["bootstrapOnly"]?.bool == true {
Drew's avatar
Drew committed
192 193 194 195 196 197
            bootstrapOnly = true
        }
        else {
            bootstrapOnly = false
        }
        
198
        let sdk: Bool
199
        if task["linkSDK"]?.bool == false {
200 201 202 203
            sdk = false
        }
        else { sdk = true }
        
Drew's avatar
Drew committed
204
        let llbuildyamlpath : String
Drew's avatar
Drew committed
205

206 207
        if let value = task["llbuildyaml"]?.string {
            llbuildyamlpath = value
Drew's avatar
Drew committed
208 209 210 211
        }
        else {
            llbuildyamlpath = workDirectory + "llbuild.yaml"
        }
Drew's avatar
Drew committed
212 213 214 215 216 217 218 219

        let swiftCPath: String
        if let c = task["swiftCPath"]?.string {
            swiftCPath = c
        }
        else {
            swiftCPath = SwiftCPath
        }
Drew's avatar
Drew committed
220
        
Drew's avatar
Drew committed
221
        let yaml = llbuildyaml(sources, workdir: workDirectory, modulename: name, linkSDK: sdk, compileOptions: compileOptions, linkOptions: linkOptions, outputType: outputType, linkWithProduct: linkWithProduct, swiftCPath: swiftCPath)
222
        let _ = try? yaml.writeToFile(llbuildyamlpath, atomically: false, encoding: NSUTF8StringEncoding)
Drew's avatar
Drew committed
223
        if bootstrapOnly { return }
Drew's avatar
Drew committed
224
        
Drew's avatar
Drew committed
225 226 227 228
        //SR-566
        let cmd = "\(SwiftBuildToolpath) -f \(llbuildyamlpath)"
        if system(cmd) != 0 {
            fatalError(cmd)
Drew's avatar
Drew committed
229
        }
Drew's avatar
Drew committed
230 231
    }
}