1 module served.commands.dcd_update; 2 3 import served.extension; 4 import served.io.git_build; 5 import served.io.http_wrap; 6 import served.types; 7 8 import workspaced.api; 9 import workspaced.coms; 10 11 import rm.rf; 12 13 import std.algorithm : endsWith; 14 import std.format : format; 15 import std.json : JSONValue; 16 import std.path : baseName, buildPath, chainPath, isAbsolute; 17 18 import fs = std.file; 19 import io = std.stdio; 20 21 version (ARM) 22 { 23 version = DCDFromSource; 24 } 25 version (Win32) 26 { 27 } 28 else version (Win64) 29 { 30 } 31 else version (linux) 32 { 33 } 34 else version (OSX) 35 { 36 } 37 else version = DCDFromSource; 38 39 version (DCDFromSource) 40 enum isDCDFromSource = true; 41 else 42 enum isDCDFromSource = false; 43 44 __gshared string dcdUpdateReason = null; 45 __gshared bool dcdUpdating; 46 @protocolNotification("served/updateDCD") 47 void updateDCD() 48 { 49 scope (exit) 50 { 51 dcdUpdating = false; 52 dcdUpdateReason = null; 53 } 54 55 if (dcdUpdateReason.length) 56 rpc.notifyMethod("coded/logInstall", "Installing DCD: " ~ dcdUpdateReason); 57 else 58 rpc.notifyMethod("coded/logInstall", "Installing DCD"); 59 60 string outputFolder = determineOutputFolder; 61 if (fs.exists(outputFolder)) 62 { 63 foreach (file; ["dcd", "DCD", "dcd-client", "dcd-server"]) 64 { 65 auto path = buildPath(outputFolder, file); 66 if (fs.exists(path)) 67 { 68 if (fs.isFile(path)) 69 fs.remove(path); 70 else 71 rmdirRecurseForce(path); 72 } 73 } 74 } 75 if (!fs.exists(outputFolder)) 76 fs.mkdirRecurse(outputFolder); 77 string ext = ""; 78 version (Windows) 79 ext = ".exe"; 80 string finalDestinationClient; 81 string finalDestinationServer; 82 83 bool success; 84 85 enum latestVersionBroken = false; 86 87 // the latest version that has downloads available 88 enum bundledDCDVersion = "v0.13.6"; 89 90 bool compileFromSource = false; 91 version (DCDFromSource) 92 compileFromSource = true; 93 else 94 { 95 version (Windows) 96 { 97 // needed to check for 64 bit process compatibility on 32 bit binaries because of WoW64 98 import core.sys.windows.windows : GetNativeSystemInfo, SYSTEM_INFO, 99 PROCESSOR_ARCHITECTURE_INTEL; 100 101 SYSTEM_INFO sysInfo; 102 GetNativeSystemInfo(&sysInfo); 103 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) // only 64 bit releases 104 compileFromSource = true; 105 } 106 107 if (!checkVersion(bundledDCDVersion, DCDComponent.latestKnownVersion)) 108 compileFromSource = true; 109 } 110 111 static if (latestVersionBroken) 112 compileFromSource = true; 113 114 string[] triedPaths; 115 116 if (compileFromSource) 117 { 118 success = compileDependency(outputFolder, "DCD", 119 "https://github.com/dlang-community/DCD.git", [ 120 [ 121 firstConfig.git.userPath, "submodule", "update", "--init", 122 "--recursive" 123 ], ["dub", "build", "--config=client"], 124 ["dub", "build", "--config=server"] 125 ]); 126 finalDestinationClient = buildPath(outputFolder, "DCD", "dcd-client" ~ ext); 127 if (!fs.exists(finalDestinationClient)) 128 finalDestinationClient = buildPath(outputFolder, "DCD", "bin", "dcd-client" ~ ext); 129 finalDestinationServer = buildPath(outputFolder, "DCD", "dcd-server" ~ ext); 130 if (!fs.exists(finalDestinationServer)) 131 finalDestinationServer = buildPath(outputFolder, "DCD", "bin", "dcd-server" ~ ext); 132 133 triedPaths = [ 134 "DCD/dcd-client" ~ ext, "DCD/dcd-server" ~ ext, "DCD/bin/dcd-client" ~ ext, 135 "DCD/bin/dcd-server" ~ ext 136 ]; 137 } 138 else 139 { 140 string url; 141 142 enum commonPrefix = "https://github.com/dlang-community/DCD/releases/download/" 143 ~ bundledDCDVersion ~ "/dcd-" ~ bundledDCDVersion; 144 145 version (Windows) 146 url = commonPrefix ~ "-windows-x86_64.zip"; 147 else version (linux) 148 url = commonPrefix ~ "-linux-x86_64.tar.gz"; 149 else version (OSX) 150 url = commonPrefix ~ "-osx-x86_64.tar.gz"; 151 else 152 static assert(false); 153 154 import std.process : pipeProcess, Redirect, Config, wait; 155 import std.zip : ZipArchive; 156 import core.thread : Fiber; 157 158 string destFile = buildPath(outputFolder, url.baseName); 159 160 try 161 { 162 rpc.notifyMethod("coded/logInstall", "Downloading from " ~ url ~ " to " ~ outputFolder); 163 164 if (fs.exists(destFile)) 165 rpc.notifyMethod("coded/logInstall", 166 "Zip file already exists! Trying to install existing zip."); 167 else 168 downloadFile(url, "Downloading DCD...", destFile); 169 170 rpc.notifyMethod("coded/logInstall", "Extracting download..."); 171 if (url.endsWith(".tar.gz")) 172 { 173 rpc.notifyMethod("coded/logInstall", "> tar xvfz " ~ url.baseName); 174 auto proc = pipeProcess(["tar", "xvfz", url.baseName], 175 Redirect.stdout | Redirect.stderrToStdout, null, Config.none, outputFolder); 176 foreach (line; proc.stdout.byLineCopy) 177 rpc.notifyMethod("coded/logInstall", line); 178 proc.pid.wait; 179 } 180 else if (url.endsWith(".zip")) 181 { 182 auto zip = new ZipArchive(fs.read(destFile)); 183 foreach (name, am; zip.directory) 184 { 185 if (name.isAbsolute) 186 name = "." ~ name; 187 zip.expand(am); 188 fs.write(chainPath(outputFolder, name), am.expandedData); 189 } 190 } 191 success = true; 192 193 finalDestinationClient = buildPath(outputFolder, "dcd-client" ~ ext); 194 finalDestinationServer = buildPath(outputFolder, "dcd-server" ~ ext); 195 196 if (!fs.exists(finalDestinationClient)) 197 finalDestinationClient = buildPath(outputFolder, "bin", "dcd-client" ~ ext); 198 if (!fs.exists(finalDestinationServer)) 199 finalDestinationServer = buildPath(outputFolder, "bin", "dcd-client" ~ ext); 200 201 triedPaths = [ 202 "dcd-client" ~ ext, "dcd-server" ~ ext, "bin/dcd-client" ~ ext, 203 "bin/dcd-server" ~ ext 204 ]; 205 } 206 catch (Exception e) 207 { 208 rpc.notifyMethod("coded/logInstall", "Failed installing: " ~ e.toString ~ "\n\n"); 209 rpc.notifyMethod("coded/logInstall", 210 "If you have troube downloading via code-d, try manually downloading the DCD archive and placing it in " 211 ~ destFile ~ "!"); 212 success = false; 213 } 214 } 215 216 if (success && (!fs.exists(finalDestinationClient) || !fs.exists(finalDestinationServer))) 217 { 218 rpc.notifyMethod("coded/logInstall", 219 "Successfully downloaded DCD, but could not find the executables."); 220 rpc.notifyMethod("coded/logInstall", 221 "Please open your user settings and insert the paths for dcd-client and dcd-server manually."); 222 rpc.notifyMethod("coded/logInstall", "Download base location: " ~ outputFolder); 223 rpc.notifyMethod("coded/logInstall", ""); 224 rpc.notifyMethod("coded/logInstall", format("Tried %(%s, %)", triedPaths)); 225 226 finalDestinationClient = "dcd-client"; 227 finalDestinationServer = "dcd-server"; 228 } 229 230 if (success) 231 { 232 dcdUpdating = false; 233 234 backend.globalConfiguration.set("dcd", "clientPath", finalDestinationClient); 235 backend.globalConfiguration.set("dcd", "serverPath", finalDestinationServer); 236 237 foreach (ref workspace; workspaces) 238 { 239 workspace.config.d.dcdClientPath = finalDestinationClient; 240 workspace.config.d.dcdServerPath = finalDestinationServer; 241 } 242 rpc.notifyMethod("coded/updateSetting", UpdateSettingParams("dcdClientPath", 243 JSONValue(finalDestinationClient), true)); 244 rpc.notifyMethod("coded/updateSetting", UpdateSettingParams("dcdServerPath", 245 JSONValue(finalDestinationServer), true)); 246 rpc.notifyMethod("coded/logInstall", "Successfully installed DCD"); 247 248 foreach (ref workspace; workspaces) 249 { 250 auto instance = backend.getInstance(workspace.folder.uri.uriToFile); 251 if (instance is null) 252 rpc.notifyMethod("coded/logInstall", 253 "Failed to find workspace to start DCD for " ~ workspace.folder.uri); 254 else 255 { 256 instance.config.set("dcd", "clientPath", finalDestinationClient); 257 instance.config.set("dcd", "serverPath", finalDestinationServer); 258 259 lazyStartDCDServer(instance, workspace.folder.uri); 260 } 261 } 262 } 263 }