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