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