1 /* 2 zlib/libpng license 3 4 Copyright (c) 2023-2025 Matheus Catarino França <matheus-catarino@hotmail.com> 5 6 This software is provided 'as-is', without any express or implied warranty. 7 In no event will the authors be held liable for any damages arising from the 8 use of this software. 9 */ 10 module build; 11 12 import std; 13 14 // Dependency versions 15 enum emsdk_version = "4.0.10"; 16 enum imgui_version = "1.91.9b"; 17 18 void main(string[] args) @safe 19 { 20 21 static if (__VERSION__ < 2111) 22 { 23 static assert(false, "This project requires DMD-frontend 2.111.0 or newer"); 24 } 25 26 // Command-line options 27 struct Options 28 { 29 bool help, verbose, downloadEmsdk, downloadShdc; 30 string compiler, target = defaultTarget, optimize = "debug", linkExample, runExample, linkage = "static"; 31 SokolBackend backend; 32 bool useX11 = true, useWayland, useEgl, useLTO, withSokolImgui; 33 } 34 35 Options opts; 36 immutable sokolRoot = environment.get("SOKOL_ROOTPATH", getcwd); 37 immutable vendorPath = absolutePath(buildPath(sokolRoot, "vendor")); 38 immutable sokolSrcPath = absolutePath(buildPath(sokolRoot, "src", "sokol", "c")); 39 40 // Parse arguments 41 foreach (arg; args[1 .. $]) 42 with (opts) switch (arg) 43 { 44 case "--help": 45 help = true; 46 break; 47 case "--verbose": 48 verbose = true; 49 break; 50 case "--enable-wasm-lto": 51 useLTO = true; 52 break; 53 case "--download-emsdk": 54 downloadEmsdk = true; 55 break; 56 case "--download-sokol-tools": 57 downloadShdc = true; 58 break; 59 case "--with-sokol-imgui": 60 withSokolImgui = true; 61 break; 62 default: 63 if (arg.startsWith("--backend=")) 64 backend = arg[10 .. $].to!SokolBackend; 65 else if (arg.startsWith("--toolchain=")) 66 compiler = findProgram(arg[12 .. $]); 67 else if (arg.startsWith("--optimize=")) 68 optimize = arg[11 .. $]; 69 else if (arg.startsWith("--target=")) 70 target = arg[9 .. $]; 71 else if (arg.startsWith("--link=")) 72 linkExample = arg[7 .. $]; 73 else if (arg.startsWith("--run=")) 74 runExample = arg[6 .. $]; 75 else if (arg.startsWith("--linkage=")) 76 { 77 linkage = arg[10 .. $]; 78 if (!["static", "dynamic"].canFind(linkage)) 79 throw new Exception("Invalid linkage: use static or dynamic"); 80 } 81 else 82 throw new Exception("Unknown argument: " ~ arg); 83 break; 84 } 85 86 if (args.length < 2 || opts.help) 87 { 88 writeln("Usage: build [options]\nOptions:"); 89 writeln(" --help Show this help message"); 90 writeln(" --verbose Enable verbose output"); 91 writeln(" --backend=<backend> Select backend (d3d11, metal, glcore, gles3, wgpu)"); 92 writeln(" --toolchain=<compiler> Select C toolchain (e.g., gcc, clang, emcc)"); 93 writeln(" --optimize=<level> Select optimization level (debug, release, small)"); 94 writeln(" --target=<target> Select target (native, wasm, android)"); 95 writeln(" --enable-wasm-lto Enable Emscripten LTO"); 96 writeln( 97 " --linkage=<type> Specify library linkage (static or dynamic, default: static)"); 98 writeln(" --download-emsdk Download Emscripten SDK"); 99 writeln(" --download-sokol-tools Download sokol-tools"); 100 writeln(" --link=<example> Link WASM example (e.g., triangle)"); 101 writeln(" --run=<example> Run WASM example (e.g., triangle)"); 102 writeln(" --with-sokol-imgui Enable sokol_imgui integration"); 103 return; 104 } 105 106 if (opts.backend == SokolBackend._auto) 107 opts.backend = resolveSokolBackend(opts.backend, opts.target); 108 109 if (!opts.linkExample && !opts.runExample) 110 { 111 if (opts.target.canFind("wasm")) 112 opts.downloadEmsdk = true; 113 writeln("Configuration:"); 114 writeln(" Target: ", opts.target, ", Optimize: ", opts.optimize, ", Backend: ", opts 115 .backend); 116 writeln(" Linkage: ", opts.linkage); 117 writeln(" Download: Emscripten=", opts.downloadEmsdk, ", ImGui=", opts.withSokolImgui, ", Sokol-tools=", opts 118 .downloadShdc); 119 writeln(" Verbose: ", opts.verbose); 120 } 121 122 // Setup dependencies 123 if (opts.downloadEmsdk || opts.target.canFind("wasm")) 124 getEmSDK(vendorPath); 125 if (opts.withSokolImgui) 126 getIMGUI(vendorPath); 127 128 // Execute build steps 129 if (opts.downloadShdc) 130 buildShaders(vendorPath); 131 else if (opts.linkExample) 132 { 133 EmLinkOptions linkOpts = { 134 target: "wasm", 135 optimize: opts.optimize, 136 lib_main: buildPath("build", "lib" ~ opts.linkExample ~ ".a"), 137 vendor: vendorPath, 138 backend: opts.backend, 139 use_emmalloc: true, 140 release_use_lto: opts.useLTO, 141 use_imgui: opts.withSokolImgui, 142 use_filesystem: false, 143 shell_file_path: absolutePath(buildPath(sokolRoot, "src", "sokol", "web", "shell.html")), 144 extra_args: [ 145 "-L" ~ absolutePath(buildPath(sokolRoot, "build")), "-lsokol" 146 ], 147 verbose: opts.verbose 148 }; 149 emLinkStep(linkOpts); 150 } 151 else if (opts.runExample) 152 { 153 emRunStep(EmRunOptions(opts.runExample, vendorPath, opts.verbose)); 154 } 155 else 156 { 157 LibSokolOptions libOpts = { 158 target: opts.target, 159 optimize: opts.optimize, 160 toolchain: opts.compiler, 161 vendor: vendorPath, 162 sokolSrcPath: sokolSrcPath, 163 backend: opts.backend, 164 use_x11: opts.useX11, 165 use_wayland: opts.useWayland, 166 use_egl: opts.useEgl, 167 with_sokol_imgui: opts.withSokolImgui, 168 linkageStatic: opts.target.canFind("wasm") ? true : opts.linkage == "static", 169 verbose: opts.verbose 170 }; 171 //FIXME: enable in all targets 172 if (opts.target.canFind("wasm")) 173 buildLibSokol(libOpts); 174 } 175 } 176 177 // Dependency management 178 void getEmSDK(string vendor) @safe 179 { 180 downloadAndExtract("Emscripten SDK", vendor, "emsdk", 181 format("https://github.com/emscripten-core/emsdk/archive/refs/tags/%s.zip", emsdk_version), 182 (path) => emSdkSetupStep(path)); 183 } 184 185 void getIMGUI(string vendor) @safe 186 { 187 downloadAndExtract("ImGui", vendor, "imgui", 188 format("https://github.com/floooh/dcimgui/archive/refs/tags/v%s.zip", imgui_version)); 189 } 190 191 void buildShaders(string vendor) @safe 192 { 193 immutable shdcPath = getSHDC(vendor); 194 immutable shadersDir = "examples/shaders"; 195 immutable shaders = [ 196 "triangle", "bufferoffsets", "cube", "instancing", "mrt", 197 "noninterleaved", "offscreen", "quad", "shapes", "texcube", "blend" 198 ]; 199 200 version (OSX) 201 enum glsl = "glsl410"; 202 else 203 enum glsl = "glsl430"; 204 immutable slang = glsl ~ ":metal_macos:hlsl5:glsl300es:wgsl"; 205 206 version (Posix) 207 executeOrFail(["chmod", "+x", shdcPath], "Failed to set shader permissions", true); 208 209 foreach (shader; shaders) 210 executeOrFail([ 211 shdcPath, "-i", buildPath(shadersDir, shader ~ ".glsl"), 212 "-o", buildPath(shadersDir, shader ~ ".d"), "-l", slang, "-f", "sokol_d" 213 ], "Shader compilation failed for " ~ shader, true); 214 } 215 216 // Download and extract utility 217 void downloadAndExtract(string name, string vendor, string dir, string url, void delegate(string) @safe postExtract = null) @safe 218 { 219 writeln("Setting up ", name); 220 string path = absolutePath(buildPath(vendor, dir)); 221 string file = dir ~ ".zip"; 222 scope (exit) 223 if (exists(file)) 224 remove(file); 225 226 if (!exists(path)) 227 { 228 download(url, file); 229 extractZip(file, path); 230 } 231 if (postExtract) 232 postExtract(path); 233 } 234 235 // Core build structures 236 enum SokolBackend 237 { 238 _auto, 239 d3d11, 240 metal, 241 glcore, 242 gles3, 243 wgpu 244 } 245 246 struct LibSokolOptions 247 { 248 string target, optimize, toolchain, vendor, sokolSrcPath; 249 SokolBackend backend; 250 bool use_egl, use_x11 = true, use_wayland, with_sokol_imgui, linkageStatic, verbose; 251 } 252 253 struct EmLinkOptions 254 { 255 string target, optimize, lib_main, vendor, shell_file_path; 256 SokolBackend backend; 257 bool release_use_closure = true, release_use_lto, use_emmalloc, use_filesystem, use_imgui, verbose; 258 string[] extra_args; 259 } 260 261 struct EmRunOptions 262 { 263 string name, vendor; 264 bool verbose; 265 } 266 267 struct EmbuilderOptions 268 { 269 string port_name, vendor; 270 } 271 272 // Build Sokol and ImGui libraries 273 void buildLibSokol(LibSokolOptions opts) @safe 274 { 275 immutable buildDir = absolutePath("build"); 276 mkdirRecurse(buildDir); 277 278 // Compiler setup 279 string compiler = opts.toolchain ? opts.toolchain : defaultCompiler(opts.target); 280 string[] cflags = [ 281 "-DNDEBUG", "-DIMPL", 282 format("-DSOKOL_%s", resolveSokolBackend(opts.backend, opts.target).to!string.toUpper) 283 ]; 284 string[] lflags; 285 286 // Platform-specific flags 287 switch (opts.target) 288 { 289 case "darwin": 290 cflags ~= [ 291 "-ObjC", "-Wall", "-Wextra", "-Wno-unused-function", 292 "-Wno-return-type-c-linkage" 293 ]; 294 lflags ~= [ 295 "-framework", "Cocoa", "-framework", "QuartzCore", "-framework", 296 "Foundation", 297 "-framework", "MetalKit", "-framework", "Metal", "-framework", 298 "AudioToolbox" 299 ]; 300 break; 301 case "linux": 302 cflags ~= ["-Wall", "-Wextra", "-Wno-unused-function"]; 303 if (opts.use_egl) 304 cflags ~= "-DSOKOL_FORCE_EGL"; 305 if (!opts.use_x11) 306 cflags ~= "-DSOKOL_DISABLE_X11"; 307 if (!opts.use_wayland) 308 cflags ~= "-DSOKOL_DISABLE_WAYLAND"; 309 lflags ~= opts.use_wayland ? [ 310 "-lwayland-client", "-lwayland-egl", "-lwayland-cursor", "-lxkbcommon" 311 ] : []; 312 lflags ~= ["-lX11", "-lGL", "-lXi", "-lXcursor", "-lasound"]; 313 break; 314 case "windows": 315 cflags ~= ["/DNDEBUG", "/DIMPL", "/wd4190", "/O2"]; 316 lflags ~= ["dxgi.lib", "d3d11.lib"]; 317 break; 318 case "wasm": 319 cflags ~= ["-fPIE"]; 320 if (opts.backend == SokolBackend.wgpu) // add include path to find emdawnwebgpu <webgpu/webgpu.h> before Emscripten SDK webgpu.h 321 { 322 //dfmt off 323 EmbuilderOptions embopts = { 324 port_name: "emdawnwebgpu", 325 vendor: opts.vendor, 326 }; 327 //dfmt on 328 embuilderStep(embopts); 329 cflags ~= format("-I%s", buildPath(opts.vendor, "emsdk", "upstream", "emscripten", "cache", "ports", "emdawnwebgpu", "emdawnwebgpu_pkg", "webgpu", "include")); 330 } 331 compiler = buildPath(opts.vendor, "emsdk", "upstream", "emscripten", "emcc") ~ (isWindows ? ".bat" 332 : ""); 333 break; 334 default: 335 break; 336 } 337 338 // Optimization and dynamic library flags 339 cflags ~= opts.optimize == "debug" && !opts.target.canFind("windows") ? "-O0" : "-O2"; 340 if (!opts.linkageStatic && !opts.target.canFind("wasm")) 341 cflags ~= "-fPIC"; 342 343 // Compile Sokol sources 344 immutable sokolSources = [ 345 "sokol_log.c", "sokol_app.c", "sokol_gfx.c", "sokol_time.c", 346 "sokol_audio.c", 347 "sokol_gl.c", "sokol_debugtext.c", "sokol_shape.c", "sokol_glue.c", 348 "sokol_fetch.c", "sokol_memtrack.c" 349 ]; 350 auto sokolObjs = compileSources(sokolSources, buildDir, opts.sokolSrcPath, compiler, cflags, "sokol_", opts 351 .verbose); 352 353 // Create Sokol library 354 immutable sokolLib = buildPath(buildDir, opts.linkageStatic ? "libsokol.a" : (opts.target.canFind("darwin") ? "libsokol.dylib" : opts 355 .target.canFind("windows") ? "sokol.dll" : "libsokol.so")); 356 linkLibrary(sokolLib, sokolObjs, opts.target, opts.linkageStatic, opts.vendor, lflags, opts 357 .verbose); 358 sokolObjs.each!(obj => exists(obj) && remove(obj)); 359 360 // Handle ImGui 361 if (opts.with_sokol_imgui) 362 { 363 immutable imguiRoot = absolutePath(buildPath(opts.vendor, "imgui", "src")); 364 enforce(exists(imguiRoot), "ImGui source not found. Use --download-imgui."); 365 366 immutable imguiSources = [ 367 "cimgui.cpp", "imgui.cpp", "imgui_demo.cpp", "imgui_draw.cpp", 368 "imgui_tables.cpp", "imgui_widgets.cpp" 369 ]; 370 cflags ~= format("-I%s", imguiRoot); 371 372 //dfmt off 373 string imguiCompiler = opts.target.canFind("wasm") ? buildPath(opts.vendor, "emsdk", "upstream", "emscripten", "em++") ~ ( 374 isWindows ? ".bat" : "") : 375 compiler.canFind("clang") ? findProgram(compiler ~ "++") : 376 compiler.canFind("gcc") ? findProgram("g++") : compiler; 377 //dfmt on 378 379 // Compile ImGui sources 380 auto imguiObjs = compileSources(imguiSources, buildDir, imguiRoot, imguiCompiler, cflags ~ "-DNDEBUG", "imgui_", opts 381 .verbose); 382 383 // Compile sokol_imgui.c 384 immutable sokolImguiPath = buildPath(opts.sokolSrcPath, "sokol_imgui.c"); 385 enforce(exists(sokolImguiPath), "sokol_imgui.c not found"); 386 immutable sokolImguiObj = buildPath(buildDir, "sokol_imgui.o"); 387 compileSource(sokolImguiPath, sokolImguiObj, compiler, cflags, opts.verbose); 388 imguiObjs ~= sokolImguiObj; 389 390 // Create ImGui library 391 immutable imguiLib = buildPath(buildDir, opts.linkageStatic ? "libcimgui.a" : (opts.target.canFind("darwin") ? "libcimgui.dylib" : opts 392 .target.canFind("windows") ? "cimgui.dll" : "libcimgui.so")); 393 linkLibrary(imguiLib, imguiObjs, opts.target, opts.linkageStatic, opts.vendor, lflags, opts 394 .verbose); 395 imguiObjs.each!(obj => exists(obj) && remove(obj)); 396 } 397 } 398 399 // Compile a single source file 400 void compileSource(string srcPath, string objPath, string compiler, string[] cflags, bool verbose) @safe 401 { 402 enforce(exists(srcPath), format("Source file %s does not exist", srcPath)); 403 string[] cmd = [compiler] ~ cflags ~ ["-c", "-o", objPath, srcPath]; 404 if (verbose) 405 writeln("Executing: ", cmd.join(" ")); 406 auto result = executeShell(cmd.join(" ")); 407 if (verbose && result.output.length) 408 writeln("Output:\n", result.output); 409 enforce(result.status == 0, format("Failed to compile %s: %s", srcPath, result.output)); 410 } 411 412 // Compile multiple sources 413 string[] compileSources(const(string[]) sources, string buildDir, string srcRoot, string compiler, string[] cflags, string prefix, bool verbose) @safe 414 { 415 string[] objFiles; 416 foreach (src; sources) 417 { 418 immutable srcPath = buildPath(srcRoot, src); 419 immutable objPath = buildPath(buildDir, prefix ~ src.baseName ~ ".o"); 420 compileSource(srcPath, objPath, compiler, cflags, verbose); 421 objFiles ~= objPath; 422 } 423 return objFiles; 424 } 425 426 // Link objects into a static or dynamic library 427 void linkLibrary(string libPath, string[] objFiles, string target, bool linkageStatic, string vendor, string[] lflags, bool verbose) @safe 428 { 429 string arCmd = target.canFind("wasm") ? buildPath(vendor, "emsdk", "upstream", "emscripten", "emar") ~ ( 430 isWindows ? ".bat" : "") : isWindows ? "lib.exe" : "ar"; 431 string[] cmd; 432 433 if (!linkageStatic && !target.canFind("wasm")) 434 { 435 if (target.canFind("darwin")) 436 { 437 string linker = findProgram("clang"); 438 cmd = [linker, "-dynamiclib", "-o", libPath] ~ objFiles ~ lflags; 439 } 440 else if (target.canFind("windows")) 441 { 442 string linker = findProgram("cl"); 443 cmd = [linker, "/LD", format("/Fe:%s", libPath)] ~ objFiles ~ lflags; 444 } 445 else // Linux 446 { 447 string linker = findProgram("gcc"); 448 cmd = [linker, "-shared", "-o", libPath] ~ objFiles ~ lflags; 449 } 450 } 451 else if (isWindows && !target.canFind("wasm")) 452 { 453 cmd = [arCmd, "/nologo", format("/OUT:%s", libPath)] ~ objFiles; 454 } 455 else 456 { 457 cmd = [arCmd, "rcs", libPath] ~ objFiles; 458 } 459 460 if (verbose) 461 writeln("Executing: ", cmd.join(" ")); 462 auto result = executeShell(cmd.join(" ")); 463 if (verbose && result.output.length) 464 writeln("Output:\n", result.output); 465 enforce(result.status == 0, format("Failed to create %s: %s", libPath, result.output)); 466 } 467 468 // Link WASM executable 469 void emLinkStep(EmLinkOptions opts) @safe 470 { 471 string emcc = buildPath(opts.vendor, "emsdk", "upstream", "emscripten", opts.use_imgui ? "em++" 472 : "emcc") ~ (isWindows ? ".bat" : ""); 473 string[] cmd = [emcc]; 474 475 if (opts.use_imgui) 476 cmd ~= "-lcimgui"; 477 if (opts.optimize == "debug") 478 cmd ~= ["-gsource-map", "-sSAFE_HEAP=1", "-sSTACK_OVERFLOW_CHECK=1"]; 479 else 480 { 481 cmd ~= "-sASSERTIONS=0"; 482 cmd ~= opts.optimize == "small" ? "-Oz" : "-O3"; 483 if (opts.release_use_lto) 484 cmd ~= "-flto"; 485 if (opts.release_use_closure) 486 cmd ~= ["--closure", "1"]; 487 } 488 489 if (opts.backend == SokolBackend.wgpu) 490 cmd ~= "--use-port=emdawnwebgpu"; 491 if (opts.backend == SokolBackend.gles3) 492 cmd ~= "-sUSE_WEBGL2=1"; 493 if (!opts.use_filesystem) 494 cmd ~= "-sNO_FILESYSTEM=1"; 495 if (opts.use_emmalloc) 496 cmd ~= "-sMALLOC='emmalloc'"; 497 if (opts.shell_file_path) 498 cmd ~= "--shell-file=" ~ opts.shell_file_path; 499 500 cmd ~= ["-sSTACK_SIZE=512KB"] ~ opts.extra_args ~ opts.lib_main; 501 immutable baseName = opts.lib_main.baseName[3 .. $ - 2]; // Strip "lib" and ".a" 502 string outFile = buildPath("build", baseName ~ ".html"); 503 cmd ~= ["-o", outFile]; 504 505 if (opts.verbose) 506 writeln("Executing: ", cmd.join(" ")); 507 auto result = executeShell(cmd.join(" ")); 508 if (opts.verbose && result.output.length) 509 writeln("Output:\n", result.output); 510 enforce(result.status == 0, format("emcc failed: %s: %s", outFile, result.output)); 511 512 string webDir = "web"; 513 mkdirRecurse(webDir); 514 foreach (ext; [".html", ".wasm", ".js"]) 515 copy(buildPath("build", baseName ~ ext), buildPath(webDir, baseName ~ ext)); 516 rmdirRecurse(buildPath("build")); 517 } 518 519 // Run WASM executable 520 void emRunStep(EmRunOptions opts) @safe 521 { 522 string emrun = buildPath(opts.vendor, "emsdk", "upstream", "emscripten", "emrun") ~ ( 523 isWindows ? ".bat" : ""); 524 executeOrFail([emrun, buildPath("web", opts.name ~ ".html")], "emrun failed", opts.verbose); 525 } 526 527 // Setup Emscripten SDK 528 void emSdkSetupStep(string emsdk) @safe 529 { 530 if (!exists(buildPath(emsdk, ".emscripten"))) 531 { 532 immutable cmd = buildPath(emsdk, "emsdk") ~ (isWindows ? ".bat" : ""); 533 executeOrFail([!isWindows ? "bash " ~ cmd: cmd, "install", "latest"], "emsdk install failed", true); 534 executeOrFail([!isWindows ? "bash " ~ cmd: cmd, "activate", "latest"], "emsdk activate failed", true); 535 } 536 } 537 538 void embuilderStep(EmbuilderOptions opts) @safe 539 { 540 string embuilder = buildPath(opts.vendor, "emsdk", "upstream", "emscripten", "embuilder") ~ ( 541 isWindows ? ".bat" : ""); 542 string[] bFlags = ["build", opts.port_name]; 543 executeOrFail(embuilder ~ bFlags, "embuilder failed to build " ~ opts.port_name, true); 544 } 545 546 // Utility functions 547 string findProgram(string programName) @safe 548 { 549 foreach (path; environment.get("PATH").split(pathSeparator)) 550 { 551 string fullPath = buildPath(path, programName); 552 version (Windows) 553 fullPath ~= ".exe"; 554 if (exists(fullPath) && isFile(fullPath)) 555 return fullPath; 556 } 557 throw new Exception(format("Program '%s' not found in PATH", programName)); 558 } 559 560 string defaultCompiler(string target) @safe 561 { 562 if (target.canFind("wasm")) 563 return ""; 564 version (linux) 565 return findProgram("gcc"); 566 version (Windows) 567 return findProgram("cl"); 568 version (OSX) 569 return findProgram("clang"); 570 version (Android) 571 return findProgram("clang"); 572 throw new Exception("Unsupported platform"); 573 } 574 575 SokolBackend resolveSokolBackend(SokolBackend backend, string target) @safe 576 { 577 if (target.canFind("linux")) 578 return SokolBackend.glcore; 579 if (target.canFind("darwin")) 580 return SokolBackend.metal; 581 if (target.canFind("windows")) 582 return SokolBackend.d3d11; 583 if (target.canFind("wasm")) 584 return backend == SokolBackend.wgpu ? backend : SokolBackend.gles3; 585 if (target.canFind("android")) 586 return SokolBackend.gles3; 587 version (linux) 588 return SokolBackend.glcore; 589 version (Windows) 590 return SokolBackend.d3d11; 591 version (OSX) 592 return SokolBackend.metal; 593 return backend; 594 } 595 596 void executeOrFail(string[] cmd, string errorMsg, bool verbose) @safe 597 { 598 if (verbose) 599 writeln("Executing: ", cmd.join(" ")); 600 auto result = executeShell(cmd.join(" ")); 601 if (verbose && result.output.length) 602 writeln("Output:\n", result.output); 603 enforce(result.status == 0, format("%s: %s", errorMsg, result.output)); 604 } 605 606 bool isWindows() @safe 607 { 608 version (Windows) 609 return true; 610 return false; 611 } 612 613 // Download and extract functions 614 void download(string url, string fileName) @trusted 615 { 616 auto buf = appender!(ubyte[])(); 617 size_t contentLength; 618 auto http = HTTP(url); 619 http.onReceiveHeader((in k, in v) { 620 if (k == "content-length") 621 contentLength = to!size_t(v); 622 }); 623 624 int barWidth = 50; 625 http.onReceive((data) { 626 buf.put(data); 627 if (contentLength) 628 { 629 float progress = cast(float) buf.data.length / contentLength; 630 write("\r[", "=".replicate(cast(int)(barWidth * progress)), ">", " ".replicate( 631 barWidth - cast(int)(barWidth * progress)), "] ", 632 format("%d%%", cast(int)(progress * 100))); 633 stdout.flush(); 634 } 635 return data.length; 636 }); 637 638 http.perform(); 639 enforce(http.statusLine.code / 100 == 2 || http.statusLine.code == 302, format( 640 "HTTP request failed: %s", http.statusLine.code)); 641 std.file.write(fileName, buf.data); 642 writeln(); 643 } 644 645 void extractZip(string zipFile, string destination) @trusted 646 { 647 ZipArchive archive = new ZipArchive(read(zipFile)); 648 string prefix = archive.directory.keys.front[0 .. $ - archive.directory.keys.front.find("/") 649 .length + 1]; 650 651 if (exists(destination)) 652 rmdirRecurse(destination); 653 mkdirRecurse(destination); 654 655 foreach (name, am; archive.directory) 656 { 657 if (!am.expandedSize) 658 continue; 659 string path = buildPath(destination, chompPrefix(name, prefix)); 660 mkdirRecurse(dirName(path)); 661 std.file.write(path, archive.expand(am)); 662 } 663 } 664 665 string getSHDC(string vendor) @safe 666 { 667 string path = absolutePath(buildPath(vendor, "shdc")); 668 string file = "shdc.zip"; 669 scope (exit) 670 if (exists(file)) 671 remove(file); 672 673 if (!exists(path)) 674 { 675 download("https://github.com/floooh/sokol-tools-bin/archive/refs/heads/master.zip", file); 676 extractZip(file, path); 677 } 678 679 version (Windows) 680 immutable shdc = buildPath("bin", "win32", "sokol-shdc.exe"); 681 else version (linux) 682 immutable shdc = buildPath("bin", isAArch64 ? "linux_arm64" : "linux", "sokol-shdc"); 683 else version (OSX) 684 immutable shdc = buildPath("bin", isAArch64 ? "osx_arm64" : "osx", "sokol-shdc"); 685 else 686 throw new Exception("Unsupported platform for sokol-tools"); 687 688 return buildPath(path, shdc); 689 } 690 691 bool isAArch64() @safe 692 { 693 version (AArch64) 694 return true; 695 return false; 696 } 697 698 string defaultTarget() @safe 699 { 700 version (linux) 701 return "linux"; 702 version (Windows) 703 return "windows"; 704 version (OSX) 705 return "darwin"; 706 version (Android) 707 return "android"; 708 version (Emscripten) 709 return "wasm"; 710 throw new Exception("Unsupported platform"); 711 }