Package.swift 7.5 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.
14
import Foundation
15

16 17 18
final public class Task {
    public var key: String = ""
    public var dependencies: [String] = []
19
    public var tool: String = "atllbuild"
20
    public var importedPath: String ///the directory at which the task was imported.  This includes a trailing /.
Drew's avatar
Drew committed
21 22

    var mixins: [String] = [] ///The mixins we should apply to this task
23
    
24 25
    private var kvp: [String:ParseValue]

26
    init?(value: ParseValue, name: String, importedPath: String) {
27
        guard let kvp = value.map else { return nil }
28
        self.importedPath = importedPath.pathWithTrailingSlash
29 30 31
        self.kvp = kvp
        self.key = name
        self.tool = kvp["tool"]?.string ?? self.tool
Drew's avatar
Drew committed
32 33 34 35 36 37 38 39
        if let mixins = kvp["mixins"]?.vector {
            for mixin in mixins {
                guard let str = mixin.string else {
                    fatalError("Non-string mixin \(mixin)")
                }
                self.mixins.append(str)
            }
        }
40

41 42
        if let values = kvp["dependencies"]?.vector {
            for value in values {
43
                if let dep = value.string { self.dependencies.append(dep) }
44 45 46
            }
        }
    }
47 48 49 50
    
    public subscript(key: String) -> ParseValue? {
        return kvp[key]
    }
51 52
}

53
final public class Package {
54
    // The required properties.
55
    public var name: String
56 57 58 59
    
    // The optional properties. All optional properties must have a default value.
    public var version: String = ""
    public var tasks: [String:Task] = [:]
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79

    /**Calculate the pruned dependency graph for the given task
- returns: A list of tasks in a reasonable order to be processed. */
    public func prunedDependencyGraph(task: Task) -> [Task] {
        var pruned : [Task] = []
        if let dependencies = task["dependencies"]?.vector {
            for next in dependencies {
                guard let depName = next.string else { fatalError("Non-string dependency \(next)")}
                guard let nextTask = tasks[depName] else { fatalError("Can't find so-called task \(depName)")}
                let nextGraph = prunedDependencyGraph(nextTask)
                for nextItem in nextGraph {
                    let filteredTasks = pruned.filter() {$0.key == nextItem.key}
                    if filteredTasks.count >= 1 { continue }
                    pruned.append(nextItem)
                }
            }
        }
        pruned.append(task)
        return pruned
    }
80 81 82 83
    
    public init(name: String) {
        self.name = name
    }
84
    
Drew's avatar
Drew committed
85
    public convenience init?(filepath: String, configurations: [String: String] = [:]) {
86 87 88 89
        guard let parser = Parser(filepath: filepath) else { return nil }
        
        do {
            let result = try parser.parse()
Drew's avatar
Drew committed
90
            let basepath = filepath.toNSString.stringByDeletingLastPathComponent
91
            self.init(type: result, configurations: configurations, pathOnDisk:basepath)
92 93 94 95 96 97 98
        }
        catch {
            print("error: \(error)")
            return nil
        }
    }
    
99
    public init?(type: ParseType, configurations: [String: String], pathOnDisk: String) {
100 101
        if type.name != "package" { return nil }
        
102
        if let value = type.properties["name"]?.string { self.name = value }
103 104 105 106
        else {
            print("ERROR: No name specified for the package.")
            return nil
        }
107
        if let value = type.properties["version"]?.string { self.version = value }
108 109 110

        if let parsedTasks = type.properties["tasks"]?.map {
            for (key, value) in parsedTasks {
111
                if let task = Task(value: value, name: key, importedPath: pathOnDisk) {
112 113 114 115
                    self.tasks[key] = task
                }
            }
        }
Drew's avatar
Drew committed
116 117

        //swap in configurations
Drew's avatar
Drew committed
118
        var usedConfigurations : [String] = []
Drew's avatar
Drew committed
119
        for requestedConfiguration in configurations.keys {
Drew's avatar
Drew committed
120 121 122 123 124 125 126 127 128 129 130 131
            for (taskname, task) in self.tasks {
                if let configurationSpecs = task.kvp["configurations"]?.map {
                    if let activeConfiguration = configurationSpecs[requestedConfiguration]?.vector {
                        for mixin in activeConfiguration {
                            guard let str = mixin.string else {
                                fatalError("Non-string mixin \(mixin)")
                            }
                            task.mixins.append(str)
                            usedConfigurations.append(requestedConfiguration)
                        }
                    }
                }
Drew's avatar
Drew committed
132
            }
Drew's avatar
Drew committed
133 134 135 136 137
        }
        //warn about unused configurations
        for requestedConfiguration in configurations.keys {
            if !usedConfigurations.contains(requestedConfiguration) {
                print("Warning: configuration \(requestedConfiguration) had no effect.")
Drew's avatar
Drew committed
138
            }
Drew's avatar
Drew committed
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
        }
        //swap in mixins
        if let mixins = type.properties["mixins"]?.map {
            for (name, task) in self.tasks {
                for mixinName in task.mixins {
                    guard let mixin = mixins[mixinName]?.map else {
                        fatalError("Can't find mixin named \(mixinName) in \(mixins)")
                    }
                    for (optionName, optionValue) in mixin {
                        guard let vectorValue = optionValue.vector else {
                            fatalError("Unsupported non-vector type \(optionValue)")
                        }
                        guard let existingValue = task[optionName]?.vector else {
                            fatalError("Can't mixin to \(task.key)[\(optionName)]")
                        }

                        guard let optionValueVec = optionValue.vector else {
                            fatalError("Non-vector option value \(optionValue)")
                        }
                        var newValue = existingValue
                        newValue.appendContentsOf(optionValueVec)
                        task.kvp[optionName] = ParseValue.Vector(newValue)
Drew's avatar
Drew committed
161 162 163 164
                    }
                }
            }
        }
165 166 167 168 169

        //load imported tasks
        if let imports = type.properties["import"]?.vector {
            for importFile in imports {
                guard let importFileString = importFile.string else { fatalError("Non-string import \(importFile)")}
Drew's avatar
Drew committed
170 171
                let adjustedImportPath = (pathOnDisk.pathWithTrailingSlash + importFileString).toNSString.stringByDeletingLastPathComponent.pathWithTrailingSlash
                let adjustedFileName = importFileString.toNSString.lastPathComponent
172 173
                guard let remotePackage = Package(filepath: adjustedImportPath + adjustedFileName, configurations: configurations) else {
                    fatalError("Can't load remote package \(adjustedImportPath + adjustedFileName)")
174 175
                }
                for task in remotePackage.tasks.keys {
176
                    remotePackage.tasks[task]!.importedPath = adjustedImportPath
177 178 179 180
                    self.tasks["\(remotePackage.name).\(task)"] = remotePackage.tasks[task]
                }
            }
        }
181
    }
182
}