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