1 module dbuild.util; 2 3 import std.digest : isDigest; 4 import std.traits : isIntegral; 5 import core.time : dur, Duration; 6 7 void feedDigestData(D)(ref D digest, in string s) 8 if (isDigest!D) 9 { 10 digest.put(cast(ubyte[])s); 11 digest.put(0); 12 } 13 14 void feedDigestData(D)(ref D digest, in string[] ss) 15 if (isDigest!D) 16 { 17 import std.bitmanip : nativeToLittleEndian; 18 19 digest.put(nativeToLittleEndian(cast(uint)ss.length)); 20 foreach (s; ss) { 21 digest.put(cast(ubyte[])s); 22 digest.put(0); 23 } 24 } 25 26 void feedDigestData(D, V)(ref D digest, in V val) 27 if (isDigest!D && (is(V == enum) || isIntegral!V)) 28 { 29 import std.bitmanip : nativeToLittleEndian; 30 31 digest.put(nativeToLittleEndian(val)); 32 digest.put(0); 33 } 34 35 bool checkMd5(in string path, in string md5) 36 in { assert(md5.length); } 37 body { 38 import std.digest : LetterCase, toHexString; 39 import std.digest.md : md5Of; 40 import std.uni : toLower; 41 import std.stdio : File; 42 43 ubyte[1024] buf = void; 44 auto f = File(path, "rb"); 45 return md5Of(f.byChunk(buf[])).toHexString!(LetterCase.lower)() == md5.toLower(); 46 } 47 48 /** 49 Obtain a lock for a file at the given path. If the file cannot be locked 50 within the given duration, an exception is thrown. The file will be created 51 if it does not yet exist. Deleting the file is not safe as another process 52 could create a new file with the same name. 53 The returned lock will get unlocked upon destruction. 54 55 Params: 56 path = path to file that gets locked 57 timeout = duration after which locking failed 58 Returns: 59 The locked file or an Exception on timeout. 60 */ 61 auto lockFile(string path, Duration timeout=dur!"msecs"(500)) 62 { 63 import core.thread : Thread; 64 import std.algorithm : move; 65 import std.datetime : Clock; 66 import std.exception : enforce; 67 import std.stdio : File; 68 69 // Just a wrapper to hide (and destruct) the locked File. 70 static struct LockFile 71 { 72 // The Lock can't be unlinked as someone could try to lock an already 73 // opened fd while a new file with the same name gets created. 74 // Exclusive filesystem locks (O_EXCL, mkdir) could be deleted but 75 // aren't automatically freed when a process terminates, see dub#1149. 76 private File f; 77 } 78 79 auto file = File(path, "w"); 80 auto t0 = Clock.currTime(); 81 auto duration = dur!"msecs"(1); 82 while (true) 83 { 84 if (file.tryLock()) 85 return LockFile(move(file)); 86 enforce(Clock.currTime() - t0 < timeout, "Failed to lock '"~path~"'."); 87 if (duration < dur!"msecs"(1024)) // exponentially increase sleep time 88 duration *= 2; 89 Thread.sleep(duration); 90 } 91 } 92 93 void runCommand(in string[] command, string workDir = null, bool quiet = false, string[string] env = null) 94 { 95 runCommands((&command)[0 .. 1], workDir, quiet, env); 96 } 97 98 void runCommands(in string[][] commands, string workDir = null, bool quiet = false, string[string] env = null) 99 { 100 import std.conv : to; 101 import std.exception : enforce; 102 import std.process : Config, escapeShellCommand, Pid, spawnProcess, wait; 103 import std.stdio : stdin, stdout, stderr, File; 104 105 version(Windows) enum nullFile = "NUL"; 106 else version(Posix) enum nullFile = "/dev/null"; 107 else static assert(0); 108 109 auto childStdout = stdout; 110 auto childStderr = stderr; 111 auto config = Config.retainStdout | Config.retainStderr; 112 113 if (quiet) { 114 childStdout = File(nullFile, "w"); 115 } 116 117 foreach(cmd; commands){ 118 if (!quiet) { 119 stdout.writeln("running ", cmd.commandRep); 120 } 121 auto pid = spawnProcess(cmd, stdin, childStdout, childStderr, env, config, workDir); 122 auto exitcode = pid.wait(); 123 enforce(exitcode == 0, "Command failed with exit code " 124 ~ to!string(exitcode) ~ ": " ~ cmd.commandRep); 125 } 126 } 127 128 private @property string commandRep(in string[] cmd) 129 { 130 import std.array : join; 131 132 return cmd.join(" "); 133 } 134 135 /// environment variable path separator 136 version(Posix) enum envPathSep = ':'; 137 else version(Windows) enum envPathSep = ';'; 138 else static assert(false); 139 140 /// Search for filename in the envPath variable content which can 141 /// contain multiple paths separated with sep depending on platform. 142 /// Returns: null if the file can't be found. 143 string searchInEnvPath(in string envPath, in string filename, in char sep=envPathSep) 144 { 145 import std.algorithm : splitter; 146 import std.file : exists; 147 import std.path : buildPath; 148 149 foreach (dir; splitter(envPath, sep)) { 150 const filePath = buildPath(dir, filename); 151 if (exists(filePath)) return filePath; 152 } 153 return null; 154 } 155 156 /// Search for filename pattern in the envPath variable content which can 157 /// contain multiple paths separated with sep depending on platform. 158 /// Returns: array of matching file names 159 string[] searchPatternInEnvPath(in string envPath, in string pattern, in char sep=envPathSep) 160 { 161 import std.algorithm : map, splitter; 162 import std.array : array; 163 import std.file : dirEntries, exists, isDir, SpanMode; 164 165 string[] res = []; 166 167 foreach (dir; splitter(envPath, sep)) { 168 if (!exists(dir) || !isDir(dir)) continue; 169 res ~= dirEntries(dir, pattern, SpanMode.shallow) 170 .map!(de => de.name) 171 .array; 172 } 173 return res; 174 } 175 176 string searchExecutable(in string exe) 177 { 178 import std.process : environment; 179 version(Windows) { 180 import std.algorithm : endsWith; 181 const efn = exe.endsWith(".exe") ? exe : exe ~ ".exe"; 182 } 183 else { 184 const efn = exe; 185 } 186 187 return searchInEnvPath(environment["PATH"], efn); 188 } 189 190 /// return the path of a file in a temp dir location 191 /// with a unique name. fnFmt should be a file name (without directory) 192 /// containing "%s" for use with std.format. It is used to insert a unique 193 /// random string in the path. 194 string tempFilePath(string fnFmt) 195 { 196 import std.ascii : letters; 197 import std.conv : to; 198 import std.file : tempDir; 199 import std.format : format; 200 import std.path : buildPath; 201 import std.random : randomSample; 202 import std.utf : byCodeUnit; 203 204 // random id with 20 letters 205 const id = letters.byCodeUnit.randomSample(20).to!string; 206 return tempDir.buildPath(format(fnFmt, id)); 207 } 208 209 /// Install file to target. 210 /// target's directory is recursively created if does not exist. 211 /// if a target exists and is newer or have same date than file, install is not performed 212 void installCopy(in string file, in string target) 213 { 214 import std.exception : enforce; 215 import std.path : dirName; 216 import std.file : copy, exists, mkdirRecurse, isDir, timeLastModified; 217 218 enforce(exists(file), "attempt to install non-existing file"); 219 220 const directory = dirName(target); 221 222 if (exists(target) && timeLastModified(target) >= timeLastModified(file)) { 223 return; 224 } 225 enforce( 226 !exists(directory) || isDir(directory), 227 directory ~ " exists but is not a directory" 228 ); 229 230 mkdirRecurse(directory); 231 copy(file, target); 232 }