@@ -3,34 +3,48 @@ import Foundation
3
3
/// A simple build system
4
4
struct MiniMake {
5
5
/// Attributes of a task
6
- enum TaskAttribute {
6
+ enum TaskAttribute : String , Codable {
7
7
/// Task is phony, meaning it must be built even if its inputs are up to date
8
8
case phony
9
9
/// Don't print anything when building this task
10
10
case silent
11
11
}
12
- /// A task to build
13
- struct Task {
14
- /// Key of the task
15
- let key : TaskKey
16
- /// Display name of the task
17
- let displayName : String
12
+
13
+ /// Information about a task enough to capture build
14
+ /// graph changes
15
+ struct TaskInfo : Codable {
18
16
/// Input tasks not yet built
19
- var wants : Set < TaskKey >
17
+ let wants : [ TaskKey ]
20
18
/// Set of files that must be built before this task
21
19
let inputs : [ String ]
22
20
/// Output task name
23
21
let output : String
24
22
/// Attributes of the task
23
+ let attributes : [ TaskAttribute ]
24
+ }
25
+
26
+ /// A task to build
27
+ struct Task {
28
+ let info : TaskInfo
29
+ /// Input tasks not yet built
30
+ let wants : Set < TaskKey >
31
+ /// Attributes of the task
25
32
let attributes : Set < TaskAttribute >
33
+ /// Display name of the task
34
+ let displayName : String
35
+ /// Key of the task
36
+ let key : TaskKey
26
37
/// Build operation
27
38
let build : ( Task ) throws -> Void
28
39
/// Whether the task is done
29
40
var isDone : Bool
41
+
42
+ var inputs : [ String ] { self . info. inputs }
43
+ var output : String { self . info. output }
30
44
}
31
45
32
46
/// A task key
33
- struct TaskKey : Hashable , Comparable , CustomStringConvertible {
47
+ struct TaskKey : Codable , Hashable , Comparable , CustomStringConvertible {
34
48
let id : String
35
49
var description : String { self . id }
36
50
@@ -41,7 +55,9 @@ struct MiniMake {
41
55
static func < ( lhs: TaskKey , rhs: TaskKey ) -> Bool { lhs. id < rhs. id }
42
56
}
43
57
58
+ /// All tasks in the build system
44
59
private var tasks : [ TaskKey : Task ]
60
+ /// Whether to explain why tasks are built
45
61
private var shouldExplain : Bool
46
62
/// Current working directory at the time the build started
47
63
private let buildCwd : String
@@ -52,13 +68,26 @@ struct MiniMake {
52
68
self . buildCwd = FileManager . default. currentDirectoryPath
53
69
}
54
70
55
- mutating func addTask( inputFiles: [ String ] = [ ] , inputTasks: [ TaskKey ] = [ ] , output: String , attributes: Set < TaskAttribute > = [ ] , build: @escaping ( Task ) throws -> Void ) -> TaskKey {
71
+ /// Adds a task to the build system
72
+ mutating func addTask( inputFiles: [ String ] = [ ] , inputTasks: [ TaskKey ] = [ ] , output: String , attributes: [ TaskAttribute ] = [ ] , build: @escaping ( Task ) throws -> Void ) -> TaskKey {
56
73
let displayName = output. hasPrefix ( self . buildCwd) ? String ( output. dropFirst ( self . buildCwd. count + 1 ) ) : output
57
74
let taskKey = TaskKey ( id: output)
58
- self . tasks [ taskKey] = Task ( key: taskKey, displayName: displayName, wants: Set ( inputTasks) , inputs: inputFiles, output: output, attributes: attributes, build: build, isDone: false )
75
+ let info = TaskInfo ( wants: inputTasks, inputs: inputFiles, output: output, attributes: attributes)
76
+ self . tasks [ taskKey] = Task ( info: info, wants: Set ( inputTasks) , attributes: Set ( attributes) , displayName: displayName, key: taskKey, build: build, isDone: false )
59
77
return taskKey
60
78
}
61
79
80
+ /// Computes a stable fingerprint of the build graph
81
+ ///
82
+ /// This fingerprint must be stable across builds and must change
83
+ /// if the build graph changes in any way.
84
+ func computeFingerprint( root: TaskKey ) throws -> Data {
85
+ let encoder = JSONEncoder ( )
86
+ encoder. outputFormatting = . sortedKeys
87
+ let tasks = self . tasks. sorted { $0. key < $1. key } . map { $0. value. info }
88
+ return try encoder. encode ( tasks)
89
+ }
90
+
62
91
private func explain( _ message: @autoclosure ( ) -> String ) {
63
92
if self . shouldExplain {
64
93
print ( message ( ) )
@@ -100,7 +129,8 @@ struct MiniMake {
100
129
}
101
130
}
102
131
103
- private func computeTotalTasks( task: Task ) -> Int {
132
+ /// Computes the total number of tasks to build used for progress display
133
+ private func computeTotalTasksForDisplay( task: Task ) -> Int {
104
134
var visited = Set < TaskKey > ( )
105
135
func visit( task: Task ) -> Int {
106
136
guard !visited. contains ( task. key) else { return 0 }
@@ -114,6 +144,14 @@ struct MiniMake {
114
144
return visit ( task: task)
115
145
}
116
146
147
+ /// Cleans all outputs of all tasks
148
+ func cleanEverything( ) {
149
+ for task in self . tasks. values {
150
+ try ? FileManager . default. removeItem ( atPath: task. output)
151
+ }
152
+ }
153
+
154
+ /// Starts building
117
155
mutating func build( output: TaskKey ) throws {
118
156
/// Returns true if any of the task's inputs have a modification date later than the task's output
119
157
func shouldBuild( task: Task ) -> Bool {
@@ -143,7 +181,7 @@ struct MiniMake {
143
181
return shouldBuild
144
182
}
145
183
}
146
- var progressPrinter = ProgressPrinter ( total: self . computeTotalTasks ( task: self . tasks [ output] !) )
184
+ var progressPrinter = ProgressPrinter ( total: self . computeTotalTasksForDisplay ( task: self . tasks [ output] !) )
147
185
148
186
func runTask( taskKey: TaskKey ) throws {
149
187
guard var task = self . tasks [ taskKey] else {
0 commit comments