1 module dls.bootstrap; 2 3 import std.file : exists, isFile, mkdirRecurse, remove; 4 import std.format : format; 5 import std.path : buildNormalizedPath; 6 7 immutable repoBase = import("repo.txt"); 8 immutable apiEndpoint = format!"https://api.github.com/repos/%s/dls/%%s"(repoBase); 9 10 version (Windows) 11 { 12 immutable os = "windows"; 13 } 14 else version (OSX) 15 { 16 immutable os = "osx"; 17 } 18 else version (linux) 19 { 20 immutable os = "linux"; 21 } 22 else 23 { 24 immutable os = "none"; 25 } 26 27 version (X86_64) 28 { 29 immutable arch = "x86_64"; 30 } 31 else version (X86) 32 { 33 immutable string arch; 34 } 35 else 36 { 37 immutable arch = "none"; 38 } 39 40 version (Windows) 41 { 42 immutable dlsExecutable = "dls.exe"; 43 } 44 else 45 { 46 immutable dlsExecutable = "dls"; 47 } 48 49 private immutable string dlsArchiveName; 50 private immutable string dlsDirName = "dls-%s"; 51 private string downloadUrl; 52 private string downloadVersion; 53 private string[] archiveMemberPaths; 54 55 shared static this() 56 { 57 version (X86) 58 { 59 import core.cpuid : isX86_64; 60 61 arch = isX86_64 ? "x86_64" : "x86"; 62 } 63 64 dlsArchiveName = format("dls-%%s.%s.%s.zip", os, arch); 65 } 66 67 @property bool canDownloadDls() 68 { 69 import std.algorithm : min; 70 import std.json : JSONException, parseJSON; 71 import std.net.curl : get; 72 73 try 74 { 75 const releases = parseJSON(get(format!apiEndpoint("releases"))).array; 76 77 foreach (release; releases[0 .. min($, 2)]) 78 { 79 foreach (asset; release["assets"].array) 80 { 81 if (asset["name"].str == format(dlsArchiveName, release["tag_name"].str)) 82 { 83 downloadUrl = asset["browser_download_url"].str; 84 downloadVersion = release["tag_name"].str; 85 return true; 86 } 87 } 88 } 89 } 90 catch (Exception e) 91 { 92 // The download URL couldn't be retrieved 93 } 94 95 return false; 96 } 97 98 void downloadDls(in void function(size_t size) totalSizeCallback = null, 99 in void function(size_t size) chunkSizeCallback = null, 100 in void function() extractCallback = null) 101 { 102 import std.array : appender; 103 import std.net.curl : HTTP; 104 import std.file : rmdirRecurse, write; 105 import std.zip : ZipArchive; 106 107 if (downloadUrl.length > 0 || canDownloadDls) 108 { 109 const dlsDir = buildNormalizedPath(dubBinDir, format(dlsDirName, downloadVersion)); 110 auto request = HTTP(downloadUrl); 111 auto archiveData = appender!(ubyte[]); 112 113 if (exists(dlsDir)) 114 { 115 if (isFile(dlsDir)) 116 { 117 remove(dlsDir); 118 } 119 else 120 { 121 rmdirRecurse(dlsDir); 122 } 123 } 124 125 mkdirRecurse(dlsDir); 126 127 request.onReceive = (in ubyte[] data) { 128 archiveData ~= data; 129 return data.length; 130 }; 131 132 request.onProgress = (size_t dlTotal, size_t dlNow, size_t ulTotal, size_t ulNow) { 133 import core.time : msecs; 134 import std.datetime.stopwatch : StopWatch; 135 136 static bool started; 137 static StopWatch watch; 138 139 if (!started && dlTotal > 0) 140 { 141 started = true; 142 watch.start(); 143 144 if (totalSizeCallback !is null) 145 { 146 totalSizeCallback(dlTotal); 147 } 148 } 149 150 if (started && chunkSizeCallback !is null && dlNow > 0 && watch.peek() >= 500.msecs) 151 { 152 watch.reset(); 153 chunkSizeCallback(dlNow); 154 } 155 156 return 0; 157 }; 158 159 request.perform(); 160 161 if (extractCallback !is null) 162 { 163 extractCallback(); 164 } 165 166 auto archive = new ZipArchive(archiveData.data); 167 archiveMemberPaths = []; 168 169 foreach (name, member; archive.directory) 170 { 171 const memberPath = buildNormalizedPath(dlsDir, name); 172 write(memberPath, archive.expand(member)); 173 archiveMemberPaths ~= memberPath; 174 175 version (Posix) 176 { 177 import core.sys.posix.sys.stat : chmod; 178 import std.conv : octal; 179 import std.string : toStringz; 180 181 if (name == dlsExecutable) 182 { 183 chmod(memberPath.toStringz(), octal!755); 184 } 185 } 186 } 187 } 188 else 189 { 190 throw new UpgradeFailedException("Cannot download DLS"); 191 } 192 } 193 194 void buildDls(in string dlsDir, in string[] additionalArgs = []) 195 { 196 import std.process : Config, execute; 197 198 auto cmdLine = ["dub", "build", "--build=release"] ~ additionalArgs; 199 200 version (Windows) 201 { 202 cmdLine ~= ["--compiler=dmd", "--arch=x86_mscoff"]; 203 } 204 205 const result = execute(cmdLine, null, Config.none, size_t.max, dlsDir); 206 207 if (result.status != 0) 208 { 209 throw new UpgradeFailedException("Build failed: " ~ result.output); 210 } 211 212 archiveMemberPaths = [buildNormalizedPath(dlsDir, dlsExecutable)]; 213 } 214 215 string linkDls() 216 { 217 import std.file : FileException; 218 import std.path : baseName; 219 import std.string : endsWith; 220 221 string dlsLinkPath; 222 223 foreach (memberPath; archiveMemberPaths) 224 { 225 if (!isFile(memberPath)) 226 { 227 throw new FileException(format!"%s doesn't exist"(memberPath)); 228 } 229 230 const linkPath = buildNormalizedPath(dubBinDir, baseName(memberPath)); 231 232 mkdirRecurse(dubBinDir); 233 234 if (exists(linkPath)) 235 { 236 remove(linkPath); 237 } 238 239 if (memberPath.endsWith(dlsExecutable)) 240 { 241 dlsLinkPath = linkPath; 242 } 243 } 244 245 version (Windows) 246 { 247 import std.algorithm : joiner, map; 248 import std.conv : to; 249 import std.file : FileException; 250 import std.format : format; 251 import std.process : Config, execute; 252 253 string[] mklinks; 254 255 foreach (memberPath; archiveMemberPaths) 256 { 257 mklinks ~= format("mklink %s %s", buildNormalizedPath(dubBinDir, 258 baseName(memberPath)), memberPath); 259 } 260 261 const mklinkCommand = mklinks.joiner(" & ").to!string; 262 const command = [ 263 "powershell.exe", 264 format!"Start-Process -FilePath cmd.exe -ArgumentList '/c %s' -Verb runas"( 265 mklinkCommand) 266 ]; 267 const result = execute(command); 268 269 if (result.status != 0) 270 { 271 throw new FileException("Symlink failed: " ~ result.output); 272 } 273 } 274 else version (Posix) 275 { 276 import std.file : symlink; 277 278 foreach (memberPath; archiveMemberPaths) 279 { 280 const linkPath = buildNormalizedPath(dubBinDir, baseName(memberPath)); 281 symlink(memberPath, linkPath); 282 } 283 } 284 else 285 { 286 static assert(false, "Platform not suported"); 287 } 288 289 return dlsLinkPath; 290 } 291 292 @property string dubBinDir() 293 { 294 import std.process : environment; 295 296 version (Windows) 297 { 298 const dubDirPath = environment["LOCALAPPDATA"]; 299 const dubDirName = "dub"; 300 } 301 else 302 { 303 const dubDirPath = environment["HOME"]; 304 const dubDirName = ".dub"; 305 } 306 307 return buildNormalizedPath(dubDirPath, dubDirName, "packages", ".bin"); 308 } 309 310 class UpgradeFailedException : Exception 311 { 312 this(in string message) 313 { 314 super(message); 315 } 316 }