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