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(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 string searchExecutable(in string exe) 157 { 158 import std.process : environment; 159 version(Windows) { 160 import std.algorithm : endsWith; 161 const efn = exe.endsWith(".exe") ? exe : exe ~ ".exe"; 162 } 163 else { 164 const efn = exe; 165 } 166 167 return searchInEnvPath(environment["PATH"], efn); 168 } 169 170 /// return the path of a file in a temp dir location 171 /// with a unique name. fnFmt should be a file name (without directory) 172 /// containing "%s" for use with std.format. It is used to insert a unique 173 /// random string in the path. 174 string tempFilePath(string fnFmt) 175 { 176 import std.ascii : letters; 177 import std.conv : to; 178 import std.file : tempDir; 179 import std.format : format; 180 import std.path : buildPath; 181 import std.random : randomSample; 182 import std.utf : byCodeUnit; 183 184 // random id with 20 letters 185 const id = letters.byCodeUnit.randomSample(20).to!string; 186 return tempDir.buildPath(format(fnFmt, id)); 187 }