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