1 /** 2 * Copyright: Copyright Jason White, 2016 3 * License: MIT 4 * Authors: Jason White 5 */ 6 module button.task; 7 8 import button.command; 9 import button.resource; 10 import button.context; 11 import button.exceptions; 12 13 /** 14 * A task key must be unique. 15 */ 16 struct TaskKey 17 { 18 /** 19 * The commands to execute in sequential order. The first argument is the 20 * name of the executable. 21 */ 22 immutable(Command)[] commands; 23 24 /** 25 * The working directory for the commands, relative to the current working 26 * directory of the build system. If empty, the current working directory of 27 * the build system is used. 28 */ 29 string workingDirectory = ""; 30 31 this(immutable(Command)[] commands, string workingDirectory = "") 32 { 33 assert(commands.length, "A task must have >0 commands"); 34 35 this.commands = commands; 36 this.workingDirectory = workingDirectory; 37 } 38 39 /** 40 * Compares this key with another. 41 */ 42 int opCmp()(const auto ref typeof(this) that) const pure nothrow 43 { 44 import std.algorithm.comparison : cmp; 45 import std.path : filenameCmp; 46 47 if (immutable result = cmp(this.commands, that.commands)) 48 return result; 49 50 return filenameCmp(this.workingDirectory, that.workingDirectory); 51 } 52 53 /// Ditto 54 bool opEquals()(const auto ref typeof(this) that) const pure nothrow 55 { 56 return this.opCmp(that) == 0; 57 } 58 } 59 60 unittest 61 { 62 // Comparison 63 static assert(TaskKey([Command(["a", "b"])]) < TaskKey([Command(["a", "c"])])); 64 static assert(TaskKey([Command(["a", "c"])]) > TaskKey([Command(["a", "b"])])); 65 static assert(TaskKey([Command(["a", "b"])], "a") == TaskKey([Command(["a", "b"])], "a")); 66 static assert(TaskKey([Command(["a", "b"])], "a") != TaskKey([Command(["a", "b"])], "b")); 67 static assert(TaskKey([Command(["a", "b"])], "a") < TaskKey([Command(["a", "b"])], "b")); 68 } 69 70 unittest 71 { 72 import std.conv : to; 73 74 // Converting commands to a string. This is used to store/retrieve tasks in 75 // the database. 76 77 immutable t = TaskKey([ 78 Command(["foo", "bar"]), 79 Command(["baz"]), 80 ]); 81 82 assert(t.commands.to!string == `[["foo", "bar"], ["baz"]]`); 83 } 84 85 /** 86 * A representation of a task. 87 */ 88 struct Task 89 { 90 import std.datetime : SysTime; 91 92 TaskKey key; 93 94 alias key this; 95 96 /** 97 * Time this task was last executed. If this is SysTime.min, then it is 98 * taken to mean that the task has never been executed before. This is 99 * useful for knowing if a task with no dependencies needs to be executed. 100 */ 101 SysTime lastExecuted = SysTime.min; 102 103 /** 104 * Text to display when running the task. If this is null, the commands 105 * themselves will be displayed. This is useful for reducing the amount of 106 * noise that is displayed. 107 */ 108 string display; 109 110 /** 111 * The result of executing a task. 112 */ 113 struct Result 114 { 115 /** 116 * List of raw byte arrays of implicit inputs/outputs. There is one byte 117 * array per command. 118 */ 119 Resource[] inputs, outputs; 120 } 121 122 this(TaskKey key) 123 { 124 this.key = key; 125 } 126 127 this(immutable(Command)[] commands, string workDir = "", 128 string display = null, SysTime lastExecuted = SysTime.min) 129 { 130 assert(commands.length, "A task must have >0 commands"); 131 132 this.commands = commands; 133 this.display = display; 134 this.workingDirectory = workDir; 135 this.lastExecuted = lastExecuted; 136 } 137 138 /** 139 * Returns a string representation of the task. 140 * 141 * Since individual commands are in argv format, we format it into a string 142 * as one would enter into a shell. 143 */ 144 string toPrettyString(bool verbose = false) const pure 145 { 146 import std.array : join; 147 import std.algorithm.iteration : map; 148 149 if (display && !verbose) 150 return display; 151 152 // Just use the first command 153 return commands[0].toPrettyString; 154 } 155 156 /** 157 * Returns a short string representation of the task. 158 */ 159 @property string toPrettyShortString() const pure nothrow 160 { 161 if (display) 162 return display; 163 164 // Just use the first command 165 return commands[0].toPrettyShortString; 166 } 167 168 /** 169 * Compares this task with another. 170 */ 171 int opCmp()(const auto ref typeof(this) that) const pure nothrow 172 { 173 return this.key.opCmp(that.key); 174 } 175 176 /// Ditto 177 bool opEquals()(const auto ref typeof(this) that) const pure nothrow 178 { 179 return opCmp(that) == 0; 180 } 181 182 version (none) unittest 183 { 184 assert(Task([["a", "b"]]) < Task([["a", "c"]])); 185 assert(Task([["a", "b"]]) > Task([["a", "a"]])); 186 187 assert(Task([["a", "b"]]) < Task([["a", "c"]])); 188 assert(Task([["a", "b"]]) > Task([["a", "a"]])); 189 190 assert(Task([["a", "b"]]) == Task([["a", "b"]])); 191 assert(Task([["a", "b"]], "a") < Task([["a", "b"]], "b")); 192 assert(Task([["a", "b"]], "b") > Task([["a", "b"]], "a")); 193 assert(Task([["a", "b"]], "a") == Task([["a", "b"]], "a")); 194 } 195 }