1 /** 2 Generator for direct compiler builds. 3 4 Copyright: © 2013-2013 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.generators.build; 9 10 import dub.compilers.compiler; 11 import dub.compilers.utils; 12 import dub.generators.generator; 13 import dub.internal.utils; 14 import dub.internal.vibecompat.core.file; 15 import dub.internal.vibecompat.core.log; 16 import dub.internal.vibecompat.inet.path; 17 import dub.package_; 18 import dub.packagemanager; 19 import dub.project; 20 21 import std.algorithm; 22 import std.array; 23 import std.conv; 24 import std.exception; 25 import std.file; 26 import std.process; 27 import std.string; 28 import std.encoding : sanitize; 29 30 string getObjSuffix(const scope ref BuildPlatform platform) 31 { 32 return platform.platform.canFind("windows") ? ".obj" : ".o"; 33 } 34 35 string computeBuildName(string config, GeneratorSettings settings, const string[][] hashing...) 36 { 37 import std.digest; 38 import std.digest.md; 39 40 MD5 hash; 41 hash.start(); 42 void addHash(in string[] strings...) { foreach (s; strings) { hash.put(cast(ubyte[])s); hash.put(0); } hash.put(0); } 43 foreach(strings; hashing) 44 addHash(strings); 45 auto hashstr = hash.finish().toHexString().idup; 46 47 return format("%s-%s-%s-%s-%s_v%s-%s", config, settings.buildType, 48 settings.platform.platform.join("."), 49 settings.platform.architecture.join("."), 50 settings.platform.compiler, settings.platform.compilerVersion, hashstr); 51 } 52 53 class BuildGenerator : ProjectGenerator { 54 private { 55 PackageManager m_packageMan; 56 NativePath[] m_temporaryFiles; 57 } 58 59 this(Project project) 60 { 61 super(project); 62 m_packageMan = project.packageManager; 63 } 64 65 override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) 66 { 67 scope (exit) cleanupTemporaries(); 68 69 void checkPkgRequirements(const(Package) pkg) 70 { 71 const tr = pkg.recipe.toolchainRequirements; 72 tr.checkPlatform(settings.platform, pkg.name); 73 } 74 75 checkPkgRequirements(m_project.rootPackage); 76 foreach (pkg; m_project.dependencies) 77 checkPkgRequirements(pkg); 78 79 auto root_ti = targets[m_project.rootPackage.name]; 80 81 enforce(!(settings.rdmd && root_ti.buildSettings.targetType == TargetType.none), 82 "Building package with target type \"none\" with rdmd is not supported yet."); 83 84 logInfo("Performing \"%s\" build using %s for %-(%s, %).", 85 settings.buildType, settings.platform.compilerBinary, settings.platform.architecture); 86 87 bool any_cached = false; 88 89 NativePath[string] target_paths; 90 91 bool[string] visited; 92 void buildTargetRec(string target) 93 { 94 if (target in visited) return; 95 visited[target] = true; 96 97 auto ti = targets[target]; 98 99 foreach (dep; ti.dependencies) 100 buildTargetRec(dep); 101 102 NativePath[] additional_dep_files; 103 auto bs = ti.buildSettings.dup; 104 foreach (ldep; ti.linkDependencies) { 105 if (bs.targetType != TargetType.staticLibrary && !(bs.options & BuildOption.syntaxOnly)) { 106 bs.addSourceFiles(target_paths[ldep].toNativeString()); 107 } else { 108 additional_dep_files ~= target_paths[ldep]; 109 } 110 } 111 NativePath tpath; 112 if (bs.targetType != TargetType.none) 113 if (buildTarget(settings, bs, ti.pack, ti.config, ti.packages, additional_dep_files, tpath)) 114 any_cached = true; 115 target_paths[target] = tpath; 116 } 117 118 // build all targets 119 if (settings.rdmd || root_ti.buildSettings.targetType == TargetType.staticLibrary) { 120 // RDMD always builds everything at once and static libraries don't need their 121 // dependencies to be built 122 NativePath tpath; 123 buildTarget(settings, root_ti.buildSettings.dup, m_project.rootPackage, root_ti.config, root_ti.packages, null, tpath); 124 } else { 125 buildTargetRec(m_project.rootPackage.name); 126 127 if (any_cached) { 128 logInfo("To force a rebuild of up-to-date targets, run again with --force."); 129 } 130 } 131 } 132 133 override void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) 134 { 135 // run the generated executable 136 auto buildsettings = targets[m_project.rootPackage.name].buildSettings.dup; 137 if (settings.run && !(buildsettings.options & BuildOption.syntaxOnly)) { 138 NativePath exe_file_path; 139 if (m_tempTargetExecutablePath.empty) 140 exe_file_path = getTargetPath(buildsettings, settings); 141 else 142 exe_file_path = m_tempTargetExecutablePath ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); 143 runTarget(exe_file_path, buildsettings, settings.runArgs, settings); 144 } 145 } 146 147 private bool buildTarget(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_path) 148 { 149 import std.path : absolutePath; 150 151 auto cwd = NativePath(getcwd()); 152 bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 153 154 auto build_id = computeBuildID(config, buildsettings, settings); 155 156 // make all paths relative to shrink the command line 157 string makeRelative(string path) { return shrinkPath(NativePath(path), cwd); } 158 foreach (ref f; buildsettings.sourceFiles) f = makeRelative(f); 159 foreach (ref p; buildsettings.importPaths) p = makeRelative(p); 160 foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); 161 162 // perform the actual build 163 bool cached = false; 164 if (settings.rdmd) performRDMDBuild(settings, buildsettings, pack, config, target_path); 165 else if (settings.direct || !generate_binary) performDirectBuild(settings, buildsettings, pack, config, target_path); 166 else cached = performCachedBuild(settings, buildsettings, pack, config, build_id, packages, additional_dep_files, target_path); 167 168 // HACK: cleanup dummy doc files, we shouldn't specialize on buildType 169 // here and the compiler shouldn't need dummy doc output. 170 if (settings.buildType == "ddox") { 171 if ("__dummy.html".exists) 172 removeFile("__dummy.html"); 173 if ("__dummy_docs".exists) 174 rmdirRecurse("__dummy_docs"); 175 } 176 177 // run post-build commands 178 if (!cached && buildsettings.postBuildCommands.length) { 179 logInfo("Running post-build commands..."); 180 runBuildCommands(CommandType.postBuild, buildsettings.postBuildCommands, pack, m_project, settings, buildsettings, 181 [["DUB_BUILD_PATH" : target_path.parentPath.toNativeString.absolutePath]]); 182 } 183 184 return cached; 185 } 186 187 private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, 188 string build_id, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_binary_path) 189 { 190 auto cwd = NativePath(getcwd()); 191 192 NativePath target_path; 193 if (settings.tempBuild) { 194 string packageName = pack.basePackage is null ? pack.name : pack.basePackage.name; 195 m_tempTargetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", packageName, pack.version_, build_id); 196 } 197 else target_path = pack.path ~ format(".dub/build/%s/", build_id); 198 199 if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) { 200 logInfo("%s %s: target for configuration \"%s\" is up to date.", pack.name, pack.version_, config); 201 logDiagnostic("Using existing build in %s.", target_path.toNativeString()); 202 target_binary_path = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); 203 if (!settings.tempBuild) 204 copyTargetFile(target_path, buildsettings, settings); 205 return true; 206 } 207 208 if (!isWritableDir(target_path, true)) { 209 if (!settings.tempBuild) 210 logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString()); 211 performDirectBuild(settings, buildsettings, pack, config, target_path); 212 return false; 213 } 214 215 logInfo("%s %s: building configuration \"%s\"...", pack.name, pack.version_, config); 216 217 if( buildsettings.preBuildCommands.length ){ 218 logInfo("Running pre-build commands..."); 219 runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings); 220 } 221 222 // override target path 223 auto cbuildsettings = buildsettings; 224 cbuildsettings.targetPath = shrinkPath(target_path, cwd); 225 buildWithCompiler(settings, cbuildsettings); 226 target_binary_path = getTargetPath(cbuildsettings, settings); 227 228 if (!settings.tempBuild) 229 copyTargetFile(target_path, buildsettings, settings); 230 231 return false; 232 } 233 234 private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) 235 { 236 auto cwd = NativePath(getcwd()); 237 //Added check for existence of [AppNameInPackagejson].d 238 //If exists, use that as the starting file. 239 NativePath mainsrc; 240 if (buildsettings.mainSourceFile.length) { 241 mainsrc = NativePath(buildsettings.mainSourceFile); 242 if (!mainsrc.absolute) mainsrc = pack.path ~ mainsrc; 243 } else { 244 mainsrc = getMainSourceFile(pack); 245 logWarn(`Package has no "mainSourceFile" defined. Using best guess: %s`, mainsrc.relativeTo(pack.path).toNativeString()); 246 } 247 248 // do not pass all source files to RDMD, only the main source file 249 buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(s => !s.endsWith(".d"))().array(); 250 settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); 251 252 auto generate_binary = !buildsettings.dflags.canFind("-o-"); 253 254 // Create start script, which will be used by the calling bash/cmd script. 255 // build "rdmd --force %DFLAGS% -I%~dp0..\source -Jviews -Isource @deps.txt %LIBS% source\app.d" ~ application arguments 256 // or with "/" instead of "\" 257 bool tmp_target = false; 258 if (generate_binary) { 259 if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) { 260 import std.random; 261 auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; 262 auto tmpdir = getTempDir()~".rdmd/source/"; 263 buildsettings.targetPath = tmpdir.toNativeString(); 264 buildsettings.targetName = rnd ~ buildsettings.targetName; 265 m_temporaryFiles ~= tmpdir; 266 tmp_target = true; 267 } 268 target_path = getTargetPath(buildsettings, settings); 269 settings.compiler.setTarget(buildsettings, settings.platform); 270 } 271 272 logDiagnostic("Application output name is '%s'", settings.compiler.getTargetFileName(buildsettings, settings.platform)); 273 274 string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary]; 275 if (settings.force) flags ~= "--force"; 276 flags ~= buildsettings.dflags; 277 flags ~= mainsrc.relativeTo(cwd).toNativeString(); 278 279 if (buildsettings.preBuildCommands.length){ 280 logInfo("Running pre-build commands..."); 281 runCommands(buildsettings.preBuildCommands); 282 } 283 284 logInfo("%s %s: building configuration \"%s\"...", pack.name, pack.version_, config); 285 286 logInfo("Running rdmd..."); 287 logDiagnostic("rdmd %s", join(flags, " ")); 288 auto rdmd_pid = spawnProcess("rdmd" ~ flags); 289 auto result = rdmd_pid.wait(); 290 enforce(result == 0, "Build command failed with exit code "~to!string(result)); 291 292 if (tmp_target) { 293 m_temporaryFiles ~= target_path; 294 foreach (f; buildsettings.copyFiles) 295 m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head; 296 } 297 } 298 299 private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) 300 { 301 auto cwd = NativePath(getcwd()); 302 auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 303 304 // make file paths relative to shrink the command line 305 foreach (ref f; buildsettings.sourceFiles) { 306 auto fp = NativePath(f); 307 if( fp.absolute ) fp = fp.relativeTo(cwd); 308 f = fp.toNativeString(); 309 } 310 311 logInfo("%s %s: building configuration \"%s\"...", pack.name, pack.version_, config); 312 313 // make all target/import paths relative 314 string makeRelative(string path) { 315 auto p = NativePath(path); 316 // storing in a separate temprary to work around #601 317 auto prel = p.absolute ? p.relativeTo(cwd) : p; 318 return prel.toNativeString(); 319 } 320 buildsettings.targetPath = makeRelative(buildsettings.targetPath); 321 foreach (ref p; buildsettings.importPaths) p = makeRelative(p); 322 foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); 323 324 bool is_temp_target = false; 325 if (generate_binary) { 326 if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) { 327 import std.random; 328 auto rnd = to!string(uniform(uint.min, uint.max)); 329 auto tmppath = getTempDir()~("dub/"~rnd~"/"); 330 buildsettings.targetPath = tmppath.toNativeString(); 331 m_temporaryFiles ~= tmppath; 332 is_temp_target = true; 333 } 334 target_path = getTargetPath(buildsettings, settings); 335 } 336 337 if( buildsettings.preBuildCommands.length ){ 338 logInfo("Running pre-build commands..."); 339 runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings); 340 } 341 342 buildWithCompiler(settings, buildsettings); 343 344 if (is_temp_target) { 345 m_temporaryFiles ~= target_path; 346 foreach (f; buildsettings.copyFiles) 347 m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head; 348 } 349 } 350 351 private string computeBuildID(string config, in BuildSettings buildsettings, GeneratorSettings settings) 352 { 353 const(string[])[] hashing = [ 354 buildsettings.versions, 355 buildsettings.debugVersions, 356 buildsettings.dflags, 357 buildsettings.lflags, 358 buildsettings.stringImportPaths, 359 buildsettings.importPaths, 360 settings.platform.architecture, 361 [ 362 (cast(uint)buildsettings.options).to!string, 363 settings.platform.compilerBinary, 364 settings.platform.compiler, 365 settings.platform.compilerVersion, 366 ], 367 ]; 368 369 return computeBuildName(config, settings, hashing); 370 } 371 372 private void copyTargetFile(NativePath build_path, BuildSettings buildsettings, GeneratorSettings settings) 373 { 374 if (!existsFile(NativePath(buildsettings.targetPath))) 375 mkdirRecurse(buildsettings.targetPath); 376 377 string[] filenames = [ 378 settings.compiler.getTargetFileName(buildsettings, settings.platform) 379 ]; 380 381 // Windows: add .pdb if found 382 const tt = buildsettings.targetType; 383 if ((tt == TargetType.executable || tt == TargetType.dynamicLibrary) && 384 settings.platform.platform.canFind("windows")) 385 { 386 import std.path : setExtension; 387 const pdbFilename = filenames[0].setExtension(".pdb"); 388 if (existsFile(build_path ~ pdbFilename)) 389 filenames ~= pdbFilename; 390 } 391 392 foreach (filename; filenames) 393 { 394 auto src = build_path ~ filename; 395 logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath); 396 hardLinkFile(src, NativePath(buildsettings.targetPath) ~ filename, true); 397 } 398 } 399 400 private bool isUpToDate(NativePath target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in NativePath[] additional_dep_files) 401 { 402 import std.datetime; 403 404 auto targetfile = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); 405 if (!existsFile(targetfile)) { 406 logDiagnostic("Target '%s' doesn't exist, need rebuild.", targetfile.toNativeString()); 407 return false; 408 } 409 auto targettime = getFileInfo(targetfile).timeModified; 410 411 auto allfiles = appender!(string[]); 412 allfiles ~= buildsettings.sourceFiles; 413 allfiles ~= buildsettings.importFiles; 414 allfiles ~= buildsettings.stringImportFiles; 415 allfiles ~= buildsettings.extraDependencyFiles; 416 // TODO: add library files 417 foreach (p; packages) 418 allfiles ~= (p.recipePath != NativePath.init ? p : p.basePackage).recipePath.toNativeString(); 419 foreach (f; additional_dep_files) allfiles ~= f.toNativeString(); 420 bool checkSelectedVersions = !settings.single; 421 if (checkSelectedVersions && main_pack is m_project.rootPackage && m_project.rootPackage.getAllDependencies().length > 0) 422 allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString(); 423 424 foreach (file; allfiles.data) { 425 if (!existsFile(file)) { 426 logDiagnostic("File %s doesn't exist, triggering rebuild.", file); 427 return false; 428 } 429 auto ftime = getFileInfo(file).timeModified; 430 if (ftime > Clock.currTime) 431 logWarn("File '%s' was modified in the future. Please re-save.", file); 432 if (ftime > targettime) { 433 logDiagnostic("File '%s' modified, need rebuild.", file); 434 return false; 435 } 436 } 437 return true; 438 } 439 440 /// Output an unique name to represent the source file. 441 /// Calls with path that resolve to the same file on the filesystem will return the same, 442 /// unless they include different symbolic links (which are not resolved). 443 444 static string pathToObjName(const scope ref BuildPlatform platform, string path) 445 { 446 import std.digest.crc : crc32Of; 447 import std.path : buildNormalizedPath, dirSeparator, relativePath, stripDrive; 448 if (path.endsWith(".d")) path = path[0 .. $-2]; 449 auto ret = buildNormalizedPath(getcwd(), path).replace(dirSeparator, "."); 450 auto idx = ret.lastIndexOf('.'); 451 const objSuffix = getObjSuffix(platform); 452 return idx < 0 ? ret ~ objSuffix : format("%s_%(%02x%)%s", ret[idx+1 .. $], crc32Of(ret[0 .. idx]), objSuffix); 453 } 454 455 /// Compile a single source file (srcFile), and write the object to objName. 456 static string compileUnit(string srcFile, string objName, BuildSettings bs, GeneratorSettings gs) { 457 NativePath tempobj = NativePath(bs.targetPath)~objName; 458 string objPath = tempobj.toNativeString(); 459 bs.libs = null; 460 bs.lflags = null; 461 bs.sourceFiles = [ srcFile ]; 462 bs.targetType = TargetType.object; 463 gs.compiler.prepareBuildSettings(bs, gs.platform, BuildSetting.commandLine); 464 gs.compiler.setTarget(bs, gs.platform, objPath); 465 gs.compiler.invoke(bs, gs.platform, gs.compileCallback); 466 return objPath; 467 } 468 469 private void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings) 470 { 471 auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); 472 auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; 473 474 scope (failure) { 475 logDiagnostic("FAIL %s %s %s" , buildsettings.targetPath, buildsettings.targetName, buildsettings.targetType); 476 auto tpath = getTargetPath(buildsettings, settings); 477 if (generate_binary && existsFile(tpath)) 478 removeFile(tpath); 479 } 480 if (settings.buildMode == BuildMode.singleFile && generate_binary) { 481 import std.parallelism, std.range : walkLength; 482 483 auto lbuildsettings = buildsettings; 484 auto srcs = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)); 485 auto objs = new string[](srcs.walkLength); 486 487 void compileSource(size_t i, string src) { 488 logInfo("Compiling %s...", src); 489 const objPath = pathToObjName(settings.platform, src); 490 objs[i] = compileUnit(src, objPath, buildsettings, settings); 491 } 492 493 if (settings.parallelBuild) { 494 foreach (i, src; srcs.parallel(1)) compileSource(i, src); 495 } else { 496 foreach (i, src; srcs.array) compileSource(i, src); 497 } 498 499 logInfo("Linking..."); 500 lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array; 501 settings.compiler.setTarget(lbuildsettings, settings.platform); 502 settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); 503 settings.compiler.invokeLinker(lbuildsettings, settings.platform, objs, settings.linkCallback); 504 505 // NOTE: separate compile/link is not yet enabled for GDC. 506 } else if (generate_binary && (settings.buildMode == BuildMode.allAtOnce || settings.compiler.name == "gdc" || is_static_library)) { 507 // don't include symbols of dependencies (will be included by the top level target) 508 if (is_static_library) buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array; 509 510 // setup for command line 511 settings.compiler.setTarget(buildsettings, settings.platform); 512 settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); 513 514 // invoke the compiler 515 settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); 516 } else { 517 // determine path for the temporary object file 518 string tempobjname = buildsettings.targetName ~ getObjSuffix(settings.platform); 519 NativePath tempobj = NativePath(buildsettings.targetPath) ~ tempobjname; 520 521 // setup linker command line 522 auto lbuildsettings = buildsettings; 523 lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array; 524 if (generate_binary) settings.compiler.setTarget(lbuildsettings, settings.platform); 525 settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); 526 527 // setup compiler command line 528 buildsettings.libs = null; 529 buildsettings.lflags = null; 530 if (generate_binary) buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString()); 531 buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array; 532 533 settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); 534 535 settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback); 536 537 if (generate_binary) { 538 logInfo("Linking..."); 539 settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback); 540 } 541 } 542 } 543 544 private void runTarget(NativePath exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings) 545 { 546 if (buildsettings.targetType == TargetType.executable) { 547 auto cwd = NativePath(getcwd()); 548 auto runcwd = cwd; 549 if (buildsettings.workingDirectory.length) { 550 runcwd = NativePath(buildsettings.workingDirectory); 551 if (!runcwd.absolute) runcwd = cwd ~ runcwd; 552 } 553 if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path; 554 runPreRunCommands(m_project.rootPackage, m_project, settings, buildsettings); 555 logInfo("Running %s %s", exe_file_path.relativeTo(runcwd), run_args.join(" ")); 556 string[string] env; 557 foreach (aa; [buildsettings.environments, buildsettings.runEnvironments]) 558 foreach (k, v; aa) 559 env[k] = v; 560 if (settings.runCallback) { 561 auto res = execute([ exe_file_path.toNativeString() ] ~ run_args, 562 env, Config.none, size_t.max, runcwd.toNativeString()); 563 settings.runCallback(res.status, res.output); 564 settings.targetExitStatus = res.status; 565 runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings); 566 } else { 567 auto prg_pid = spawnProcess([ exe_file_path.toNativeString() ] ~ run_args, 568 env, Config.none, runcwd.toNativeString()); 569 auto result = prg_pid.wait(); 570 settings.targetExitStatus = result; 571 runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings); 572 enforce(result == 0, "Program exited with code "~to!string(result)); 573 } 574 } else 575 enforce(false, "Target is a library. Skipping execution."); 576 } 577 578 private void runPreRunCommands(in Package pack, in Project proj, in GeneratorSettings settings, 579 in BuildSettings buildsettings) 580 { 581 if (buildsettings.preRunCommands.length) { 582 logInfo("Running pre-run commands..."); 583 runBuildCommands(CommandType.preRun, buildsettings.preRunCommands, pack, proj, settings, buildsettings); 584 } 585 } 586 587 private void runPostRunCommands(in Package pack, in Project proj, in GeneratorSettings settings, 588 in BuildSettings buildsettings) 589 { 590 if (buildsettings.postRunCommands.length) { 591 logInfo("Running post-run commands..."); 592 runBuildCommands(CommandType.postRun, buildsettings.postRunCommands, pack, proj, settings, buildsettings); 593 } 594 } 595 596 private void cleanupTemporaries() 597 { 598 foreach_reverse (f; m_temporaryFiles) { 599 try { 600 if (f.endsWithSlash) rmdir(f.toNativeString()); 601 else remove(f.toNativeString()); 602 } catch (Exception e) { 603 logWarn("Failed to remove temporary file '%s': %s", f.toNativeString(), e.msg); 604 logDiagnostic("Full error: %s", e.toString().sanitize); 605 } 606 } 607 m_temporaryFiles = null; 608 } 609 } 610 611 private NativePath getMainSourceFile(in Package prj) 612 { 613 foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"]) 614 if (existsFile(prj.path ~ f)) 615 return prj.path ~ f; 616 return prj.path ~ "source/app.d"; 617 } 618 619 private NativePath getTargetPath(const scope ref BuildSettings bs, const scope ref GeneratorSettings settings) 620 { 621 return NativePath(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform); 622 } 623 624 private string shrinkPath(NativePath path, NativePath base) 625 { 626 auto orig = path.toNativeString(); 627 if (!path.absolute) return orig; 628 version (Windows) 629 { 630 // avoid relative paths starting with `..\`: https://github.com/dlang/dub/issues/2143 631 if (!path.startsWith(base)) return orig; 632 } 633 auto rel = path.relativeTo(base).toNativeString(); 634 return rel.length < orig.length ? rel : orig; 635 } 636 637 unittest { 638 assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo")) == NativePath("bar/baz").toNativeString()); 639 version (Windows) 640 assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("/foo/bar/baz").toNativeString()); 641 else 642 assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("../bar/baz").toNativeString()); 643 assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/")) == NativePath("/foo/bar/baz").toNativeString()); 644 assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/baz")) == NativePath("/foo/bar/baz").toNativeString()); 645 } 646 647 unittest { // issue #1235 - pass no library files to compiler command line when building a static lib 648 import dub.internal.vibecompat.data.json : parseJsonString; 649 import dub.compilers.gdc : GDCCompiler; 650 import dub.platform : determinePlatform; 651 652 version (Windows) auto libfile = "bar.lib"; 653 else auto libfile = "bar.a"; 654 655 auto desc = parseJsonString(`{"name": "test", "targetType": "library", "sourceFiles": ["foo.d", "`~libfile~`"]}`); 656 auto pack = new Package(desc, NativePath("/tmp/fooproject")); 657 auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false); 658 auto prj = new Project(pman, pack); 659 660 final static class TestCompiler : GDCCompiler { 661 override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback) { 662 assert(!settings.dflags[].any!(f => f.canFind("bar"))); 663 } 664 override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback) { 665 assert(false); 666 } 667 } 668 669 GeneratorSettings settings; 670 settings.platform = BuildPlatform(determinePlatform(), ["x86"], "gdc", "test", 2075); 671 settings.compiler = new TestCompiler; 672 settings.config = "library"; 673 settings.buildType = "debug"; 674 settings.tempBuild = true; 675 676 auto gen = new BuildGenerator(prj); 677 gen.generate(settings); 678 }