/** Generator for direct compiler builds. Copyright: © 2013-2013 rejectedsoftware e.K. License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Sönke Ludwig */ module dub.generators.build; import dub.compilers.compiler; import dub.compilers.utils; import dub.generators.generator; import dub.internal.utils; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.inet.path; import dub.internal.logging; import dub.package_; import dub.packagemanager; import dub.project; import std.algorithm; import std.array; import std.conv; import std.exception; import std.file; import std.process; import std.string; import std.encoding : sanitize; string getObjSuffix(const scope ref BuildPlatform platform) { return platform.isWindows() ? ".obj" : ".o"; } string computeBuildName(string config, in GeneratorSettings settings, const string[][] hashing...) { import std.digest.sha : SHA256; import std.base64 : Base64URL; SHA256 hash; hash.start(); void addHash(in string[] strings...) { foreach (s; strings) { hash.put(cast(ubyte[])s); hash.put(0); } hash.put(0); } foreach(strings; hashing) addHash(strings); addHash(settings.platform.platform); addHash(settings.platform.architecture); addHash(settings.platform.compiler); addHash(settings.platform.compilerVersion); if(settings.recipeName != "") addHash(settings.recipeName); const hashstr = Base64URL.encode(hash.finish()[0 .. $ / 2]).stripRight("="); if(settings.recipeName != "") { import std.path:stripExtension, baseName; string recipeName = settings.recipeName.baseName.stripExtension; return format("%s-%s-%s-%s", config, settings.buildType, recipeName, hashstr); } return format("%s-%s-%s", config, settings.buildType, hashstr); } class BuildGenerator : ProjectGenerator { private { PackageManager m_packageMan; NativePath[] m_temporaryFiles; } this(Project project) { super(project); m_packageMan = project.packageManager; } override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets) { import std.path : setExtension; scope (exit) cleanupTemporaries(); void checkPkgRequirements(const(Package) pkg) { const tr = pkg.recipe.toolchainRequirements; tr.checkPlatform(settings.platform, pkg.name); } checkPkgRequirements(m_project.rootPackage); foreach (pkg; m_project.dependencies) checkPkgRequirements(pkg); auto root_ti = targets[m_project.rootPackage.name]; const rootTT = root_ti.buildSettings.targetType; enforce(!(settings.rdmd && rootTT == TargetType.none), "Building package with target type \"none\" with rdmd is not supported yet."); logInfo("Starting", Color.light_green, "Performing \"%s\" build using %s for %-(%s, %).", settings.buildType.color(Color.magenta), settings.platform.compilerBinary, settings.platform.architecture); bool any_cached = false; NativePath[string] target_paths; NativePath[] dynamicLibDepsFilesToCopy; // to the root package output dir const copyDynamicLibDepsLinkerFiles = rootTT == TargetType.dynamicLibrary || rootTT == TargetType.none; const copyDynamicLibDepsRuntimeFiles = copyDynamicLibDepsLinkerFiles || rootTT == TargetType.executable; bool[string] visited; void buildTargetRec(string target) { if (target in visited) return; visited[target] = true; auto ti = targets[target]; foreach (dep; ti.dependencies) buildTargetRec(dep); NativePath[] additional_dep_files; auto bs = ti.buildSettings.dup; const tt = bs.targetType; foreach (ldep; ti.linkDependencies) { const ldepPath = target_paths[ldep].toNativeString(); const doLink = tt != TargetType.staticLibrary && !(bs.options & BuildOption.syntaxOnly); if (doLink && isLinkerFile(settings.platform, ldepPath)) bs.addSourceFiles(ldepPath); else additional_dep_files ~= target_paths[ldep]; if (targets[ldep].buildSettings.targetType == TargetType.dynamicLibrary) { // copy the .{dll,so,dylib} if (copyDynamicLibDepsRuntimeFiles) dynamicLibDepsFilesToCopy ~= NativePath(ldepPath); if (settings.platform.isWindows()) { // copy the accompanying .pdb if found if (copyDynamicLibDepsRuntimeFiles) { const pdb = ldepPath.setExtension(".pdb"); if (existsFile(pdb)) dynamicLibDepsFilesToCopy ~= NativePath(pdb); } const importLib = ldepPath.setExtension(".lib"); if (existsFile(importLib)) { // link dependee against the import lib if (doLink) bs.addSourceFiles(importLib); // and copy if (copyDynamicLibDepsLinkerFiles) dynamicLibDepsFilesToCopy ~= NativePath(importLib); } // copy the .exp file if found const exp = ldepPath.setExtension(".exp"); if (copyDynamicLibDepsLinkerFiles && existsFile(exp)) dynamicLibDepsFilesToCopy ~= NativePath(exp); } } } NativePath tpath; if (tt != TargetType.none) { if (buildTarget(settings, bs, ti.pack, ti.config, ti.packages, additional_dep_files, tpath)) any_cached = true; } target_paths[target] = tpath; } // build all targets if (settings.rdmd || rootTT == TargetType.staticLibrary) { // RDMD always builds everything at once and static libraries don't need their // dependencies to be built NativePath tpath; buildTarget(settings, root_ti.buildSettings.dup, m_project.rootPackage, root_ti.config, root_ti.packages, null, tpath); return; } buildTargetRec(m_project.rootPackage.name); if (dynamicLibDepsFilesToCopy.length) { const rootTargetPath = NativePath(root_ti.buildSettings.targetPath); ensureDirectory(rootTargetPath); foreach (src; dynamicLibDepsFilesToCopy) { logDiagnostic("Copying target from %s to %s", src.toNativeString(), rootTargetPath.toNativeString()); hardLinkFile(src, rootTargetPath ~ src.head, true); } } if (any_cached) { logInfo("Finished", Color.green, "To force a rebuild of up-to-date targets, run again with --force" ); } } override void performPostGenerateActions(GeneratorSettings settings, in TargetInfo[string] targets) { // run the generated executable auto buildsettings = targets[m_project.rootPackage.name].buildSettings.dup; if (settings.run && !(buildsettings.options & BuildOption.syntaxOnly)) { NativePath exe_file_path; if (m_tempTargetExecutablePath.empty) exe_file_path = getTargetPath(buildsettings, settings); else exe_file_path = m_tempTargetExecutablePath ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); runTarget(exe_file_path, buildsettings, settings.runArgs, settings); } } private bool buildTarget(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_path) { import std.path : absolutePath; auto cwd = settings.toolWorkingDirectory; bool generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); auto build_id = buildsettings.computeBuildID(config, settings); // make all paths relative to shrink the command line string makeRelative(string path) { return shrinkPath(NativePath(path), cwd); } foreach (ref f; buildsettings.sourceFiles) f = makeRelative(f); foreach (ref p; buildsettings.importPaths) p = makeRelative(p); foreach (ref p; buildsettings.cImportPaths) p = makeRelative(p); foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); // perform the actual build bool cached = false; if (settings.rdmd) performRDMDBuild(settings, buildsettings, pack, config, target_path); else if (!generate_binary) performDirectBuild(settings, buildsettings, pack, config, target_path); else cached = performCachedBuild(settings, buildsettings, pack, config, build_id, packages, additional_dep_files, target_path); // HACK: cleanup dummy doc files, we shouldn't specialize on buildType // here and the compiler shouldn't need dummy doc output. if (settings.buildType == "ddox") { if ("__dummy.html".exists) removeFile("__dummy.html"); if ("__dummy_docs".exists) rmdirRecurse("__dummy_docs"); } // run post-build commands if (!cached && buildsettings.postBuildCommands.length) { logInfo("Post-build", Color.light_green, "Running commands"); runBuildCommands(CommandType.postBuild, buildsettings.postBuildCommands, pack, m_project, settings, buildsettings, [["DUB_BUILD_PATH" : target_path is NativePath.init ? "" : target_path.parentPath.toNativeString.absolutePath(settings.toolWorkingDirectory.toNativeString)]]); } return cached; } private bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, string build_id, in Package[] packages, in NativePath[] additional_dep_files, out NativePath target_binary_path) { NativePath target_path; if (settings.tempBuild) { string packageName = pack.basePackage is null ? pack.name : pack.basePackage.name; m_tempTargetExecutablePath = target_path = getTempDir() ~ format(".dub/build/%s-%s/%s/", packageName, pack.version_, build_id); } else target_path = packageCache(settings.cache, pack) ~ "build/" ~ build_id; if (!settings.force && isUpToDate(target_path, buildsettings, settings, pack, packages, additional_dep_files)) { logInfo("Up-to-date", Color.green, "%s %s: target for configuration [%s] is up to date.", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); logDiagnostic("Using existing build in %s.", target_path.toNativeString()); target_binary_path = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); if (!settings.tempBuild) copyTargetFile(target_path, buildsettings, settings); return true; } if (!isWritableDir(target_path, true)) { if (!settings.tempBuild) logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path); performDirectBuild(settings, buildsettings, pack, config, target_path); return false; } logInfo("Building", Color.light_green, "%s %s: building configuration [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); if( buildsettings.preBuildCommands.length ){ logInfo("Pre-build", Color.light_green, "Running commands"); runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings); } // override target path auto cbuildsettings = buildsettings; cbuildsettings.targetPath = target_path.toNativeString(); buildWithCompiler(settings, cbuildsettings); target_binary_path = getTargetPath(cbuildsettings, settings); if (!settings.tempBuild) { copyTargetFile(target_path, buildsettings, settings); updateCacheDatabase(settings, cbuildsettings, pack, config, build_id, target_binary_path.toNativeString()); } return false; } private void updateCacheDatabase(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, string build_id, string target_binary_path) { import dub.internal.vibecompat.data.json; import core.time : seconds; // Generate a `db.json` in the package version cache directory. // This is read by 3rd party software (e.g. Meson) in order to find // relevant build artifacts in Dub's cache. enum jsonFileName = "db.json"; enum lockFileName = "db.lock"; const pkgCacheDir = packageCache(settings.cache, pack); auto lock = lockFile((pkgCacheDir ~ lockFileName).toNativeString(), 3.seconds); const dbPath = pkgCacheDir ~ jsonFileName; const dbPathStr = dbPath.toNativeString(); Json db; if (exists(dbPathStr)) { const text = stripUTF8Bom(cast(string)readFile(dbPath)); db = parseJsonString(text, dbPathStr); enforce(db.type == Json.Type.array, "Expected a JSON array in " ~ dbPathStr); } else { db = Json.emptyArray; } foreach_reverse (entry; db) { if (entry["buildId"].get!string == build_id) { // duplicate return; } } Json entry = Json.emptyObject; entry["architecture"] = serializeToJson(settings.platform.architecture); entry["buildId"] = build_id; entry["buildType"] = settings.buildType; entry["compiler"] = settings.platform.compiler; entry["compilerBinary"] = settings.platform.compilerBinary; entry["compilerVersion"] = settings.platform.compilerVersion; entry["configuration"] = config; entry["package"] = pack.name; entry["platform"] = serializeToJson(settings.platform.platform); entry["targetBinaryPath"] = target_binary_path; entry["version"] = pack.version_.toString(); db ~= entry; writeFile(dbPath, representation(db.toPrettyString())); } private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) { auto cwd = settings.toolWorkingDirectory; //Added check for existence of [AppNameInPackagejson].d //If exists, use that as the starting file. NativePath mainsrc; if (buildsettings.mainSourceFile.length) { mainsrc = NativePath(buildsettings.mainSourceFile); if (!mainsrc.absolute) mainsrc = pack.path ~ mainsrc; } else { mainsrc = getMainSourceFile(pack); logWarn(`Package has no "mainSourceFile" defined. Using best guess: %s`, mainsrc.relativeTo(pack.path).toNativeString()); } // do not pass all source files to RDMD, only the main source file buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(s => !s.endsWith(".d"))().array(); settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); auto generate_binary = !buildsettings.dflags.canFind("-o-"); // Create start script, which will be used by the calling bash/cmd script. // build "rdmd --force %DFLAGS% -I%~dp0..\source -Jviews -Isource @deps.txt %LIBS% source\app.d" ~ application arguments // or with "/" instead of "\" bool tmp_target = false; if (generate_binary) { if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; auto tmpdir = getTempDir()~".rdmd/source/"; buildsettings.targetPath = tmpdir.toNativeString(); buildsettings.targetName = rnd ~ buildsettings.targetName; m_temporaryFiles ~= tmpdir; tmp_target = true; } target_path = getTargetPath(buildsettings, settings); settings.compiler.setTarget(buildsettings, settings.platform); } logDiagnostic("Application output name is '%s'", settings.compiler.getTargetFileName(buildsettings, settings.platform)); string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary]; if (settings.force) flags ~= "--force"; flags ~= buildsettings.dflags; flags ~= mainsrc.relativeTo(cwd).toNativeString(); if (buildsettings.preBuildCommands.length){ logInfo("Pre-build", Color.light_green, "Running commands"); runCommands(buildsettings.preBuildCommands, null, cwd.toNativeString()); } logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); logInfo("Running rdmd..."); logDiagnostic("rdmd %s", join(flags, " ")); auto rdmd_pid = spawnProcess("rdmd" ~ flags, null, Config.none, cwd.toNativeString()); auto result = rdmd_pid.wait(); enforce(result == 0, "Build command failed with exit code "~to!string(result)); if (tmp_target) { m_temporaryFiles ~= target_path; foreach (f; buildsettings.copyFiles) m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head; } } private void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) { auto cwd = settings.toolWorkingDirectory; auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); // make file paths relative to shrink the command line foreach (ref f; buildsettings.sourceFiles) { auto fp = NativePath(f); if( fp.absolute ) fp = fp.relativeTo(cwd); f = fp.toNativeString(); } logInfo("Building", Color.light_green, "%s %s [%s]", pack.name.color(Mode.bold), pack.version_, config.color(Color.blue)); // make all target/import paths relative string makeRelative(string path) { auto p = NativePath(path); // storing in a separate temprary to work around #601 auto prel = p.absolute ? p.relativeTo(cwd) : p; return prel.toNativeString(); } buildsettings.targetPath = makeRelative(buildsettings.targetPath); foreach (ref p; buildsettings.importPaths) p = makeRelative(p); foreach (ref p; buildsettings.cImportPaths) p = makeRelative(p); foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); bool is_temp_target = false; if (generate_binary) { if (settings.tempBuild || (settings.run && !isWritableDir(NativePath(buildsettings.targetPath), true))) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)); auto tmppath = getTempDir()~("dub/"~rnd~"/"); buildsettings.targetPath = tmppath.toNativeString(); m_temporaryFiles ~= tmppath; is_temp_target = true; } target_path = getTargetPath(buildsettings, settings); } if( buildsettings.preBuildCommands.length ){ logInfo("Pre-build", Color.light_green, "Running commands"); runBuildCommands(CommandType.preBuild, buildsettings.preBuildCommands, pack, m_project, settings, buildsettings); } buildWithCompiler(settings, buildsettings); if (is_temp_target) { m_temporaryFiles ~= target_path; foreach (f; buildsettings.copyFiles) m_temporaryFiles ~= NativePath(buildsettings.targetPath).parentPath ~ NativePath(f).head; } } private void copyTargetFile(in NativePath build_path, in BuildSettings buildsettings, in GeneratorSettings settings) { ensureDirectory(NativePath(buildsettings.targetPath)); string[] filenames = [ settings.compiler.getTargetFileName(buildsettings, settings.platform) ]; // Windows: add .pdb (for executables and DLLs) and/or import .lib & .exp (for DLLs) if found if (settings.platform.isWindows()) { void addIfFound(string extension) { import std.path : setExtension; const candidate = filenames[0].setExtension(extension); if (existsFile(build_path ~ candidate)) filenames ~= candidate; } const tt = buildsettings.targetType; if (tt == TargetType.executable || tt == TargetType.dynamicLibrary) addIfFound(".pdb"); if (tt == TargetType.dynamicLibrary) { addIfFound(".lib"); addIfFound(".exp"); } } foreach (filename; filenames) { auto src = build_path ~ filename; logDiagnostic("Copying target from %s to %s", src.toNativeString(), buildsettings.targetPath); hardLinkFile(src, NativePath(buildsettings.targetPath) ~ filename, true); } } private bool isUpToDate(NativePath target_path, BuildSettings buildsettings, GeneratorSettings settings, in Package main_pack, in Package[] packages, in NativePath[] additional_dep_files) { import std.datetime; auto targetfile = target_path ~ settings.compiler.getTargetFileName(buildsettings, settings.platform); if (!existsFile(targetfile)) { logDiagnostic("Target '%s' doesn't exist, need rebuild.", targetfile.toNativeString()); return false; } auto targettime = getFileInfo(targetfile).timeModified; auto allfiles = appender!(string[]); allfiles ~= buildsettings.sourceFiles; allfiles ~= buildsettings.importFiles; allfiles ~= buildsettings.stringImportFiles; allfiles ~= buildsettings.extraDependencyFiles; // TODO: add library files foreach (p; packages) allfiles ~= (p.recipePath != NativePath.init ? p : p.basePackage).recipePath.toNativeString(); foreach (f; additional_dep_files) allfiles ~= f.toNativeString(); bool checkSelectedVersions = !settings.single; if (checkSelectedVersions && main_pack is m_project.rootPackage && m_project.rootPackage.getAllDependencies().length > 0) allfiles ~= (main_pack.path ~ SelectedVersions.defaultFile).toNativeString(); foreach (file; allfiles.data) { if (!existsFile(file)) { logDiagnostic("File %s doesn't exist, triggering rebuild.", file); return false; } auto ftime = getFileInfo(file).timeModified; if (ftime > Clock.currTime) logWarn("File '%s' was modified in the future. Please re-save.", file); if (ftime > targettime) { logDiagnostic("File '%s' modified, need rebuild.", file); return false; } } return true; } /// Output an unique name to represent the source file. /// Calls with path that resolve to the same file on the filesystem will return the same, /// unless they include different symbolic links (which are not resolved). deprecated("Use the overload taking in the current working directory") static string pathToObjName(const scope ref BuildPlatform platform, string path) { return pathToObjName(platform, path, getWorkingDirectory); } /// ditto static string pathToObjName(const scope ref BuildPlatform platform, string path, NativePath cwd) { import std.digest.crc : crc32Of; import std.path : buildNormalizedPath, dirSeparator, relativePath, stripDrive; if (path.endsWith(".d")) path = path[0 .. $-2]; auto ret = buildNormalizedPath(cwd.toNativeString(), path).replace(dirSeparator, "."); auto idx = ret.lastIndexOf('.'); const objSuffix = getObjSuffix(platform); return idx < 0 ? ret ~ objSuffix : format("%s_%(%02x%)%s", ret[idx+1 .. $], crc32Of(ret[0 .. idx]), objSuffix); } /// Compile a single source file (srcFile), and write the object to objName. static string compileUnit(string srcFile, string objName, BuildSettings bs, GeneratorSettings gs) { NativePath tempobj = NativePath(bs.targetPath)~objName; string objPath = tempobj.toNativeString(); bs.libs = null; bs.lflags = null; bs.sourceFiles = [ srcFile ]; bs.targetType = TargetType.object; gs.compiler.prepareBuildSettings(bs, gs.platform, BuildSetting.commandLine); gs.compiler.setTarget(bs, gs.platform, objPath); gs.compiler.invoke(bs, gs.platform, gs.compileCallback, gs.toolWorkingDirectory); return objPath; } private void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings) { auto generate_binary = !(buildsettings.options & BuildOption.syntaxOnly); auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; scope (failure) { logDiagnostic("FAIL %s %s %s" , buildsettings.targetPath, buildsettings.targetName, buildsettings.targetType); auto tpath = getTargetPath(buildsettings, settings); if (generate_binary && existsFile(tpath)) removeFile(tpath); } if (settings.buildMode == BuildMode.singleFile && generate_binary) { import std.parallelism, std.range : walkLength; auto lbuildsettings = buildsettings; auto srcs = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)); auto objs = new string[](srcs.walkLength); void compileSource(size_t i, string src) { logInfo("Compiling", Color.light_green, "%s", src); const objPath = pathToObjName(settings.platform, src, settings.toolWorkingDirectory); objs[i] = compileUnit(src, objPath, buildsettings, settings); } if (settings.parallelBuild) { foreach (i, src; srcs.parallel(1)) compileSource(i, src); } else { foreach (i, src; srcs.array) compileSource(i, src); } logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold)); lbuildsettings.sourceFiles = is_static_library ? [] : lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array; settings.compiler.setTarget(lbuildsettings, settings.platform); settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); settings.compiler.invokeLinker(lbuildsettings, settings.platform, objs, settings.linkCallback, settings.toolWorkingDirectory); // NOTE: separate compile/link is not yet enabled for GDC. } else if (generate_binary && (settings.buildMode == BuildMode.allAtOnce || settings.compiler.name == "gdc" || is_static_library)) { // don't include symbols of dependencies (will be included by the top level target) if (is_static_library) buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array; // setup for command line settings.compiler.setTarget(buildsettings, settings.platform); settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); // invoke the compiler settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback, settings.toolWorkingDirectory); } else { // determine path for the temporary object file string tempobjname = buildsettings.targetName ~ getObjSuffix(settings.platform); NativePath tempobj = NativePath(buildsettings.targetPath) ~ tempobjname; // setup linker command line auto lbuildsettings = buildsettings; lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => isLinkerFile(settings.platform, f)).array; if (generate_binary) settings.compiler.setTarget(lbuildsettings, settings.platform); settings.compiler.prepareBuildSettings(lbuildsettings, settings.platform, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles); // setup compiler command line buildsettings.libs = null; buildsettings.lflags = null; if (generate_binary) buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString()); buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !isLinkerFile(settings.platform, f)).array; settings.compiler.prepareBuildSettings(buildsettings, settings.platform, BuildSetting.commandLine); settings.compiler.invoke(buildsettings, settings.platform, settings.compileCallback, settings.toolWorkingDirectory); if (generate_binary) { if (settings.tempBuild) { logInfo("Linking", Color.light_green, "%s => %s", buildsettings.targetName.color(Mode.bold), buildsettings.getTargetPath(settings)); } else { logInfo("Linking", Color.light_green, "%s", buildsettings.targetName.color(Mode.bold)); } settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()], settings.linkCallback, settings.toolWorkingDirectory); } } } private void runTarget(NativePath exe_file_path, in BuildSettings buildsettings, string[] run_args, GeneratorSettings settings) { if (buildsettings.targetType == TargetType.executable) { auto cwd = settings.toolWorkingDirectory; auto runcwd = cwd; if (buildsettings.workingDirectory.length) { runcwd = NativePath(buildsettings.workingDirectory); if (!runcwd.absolute) runcwd = cwd ~ runcwd; } if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path; runPreRunCommands(m_project.rootPackage, m_project, settings, buildsettings); logInfo("Running", Color.green, "%s %s", exe_file_path.relativeTo(runcwd), run_args.join(" ")); string[string] env; foreach (aa; [buildsettings.environments, buildsettings.runEnvironments]) foreach (k, v; aa) env[k] = v; if (settings.runCallback) { auto res = execute([ exe_file_path.toNativeString() ] ~ run_args, env, Config.none, size_t.max, runcwd.toNativeString()); settings.runCallback(res.status, res.output); settings.targetExitStatus = res.status; runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings); } else { auto prg_pid = spawnProcess([ exe_file_path.toNativeString() ] ~ run_args, env, Config.none, runcwd.toNativeString()); auto result = prg_pid.wait(); settings.targetExitStatus = result; runPostRunCommands(m_project.rootPackage, m_project, settings, buildsettings); enforce(result == 0, "Program exited with code "~to!string(result)); } } else enforce(false, "Target is a library. Skipping execution."); } private void runPreRunCommands(in Package pack, in Project proj, in GeneratorSettings settings, in BuildSettings buildsettings) { if (buildsettings.preRunCommands.length) { logInfo("Pre-run", Color.light_green, "Running commands..."); runBuildCommands(CommandType.preRun, buildsettings.preRunCommands, pack, proj, settings, buildsettings); } } private void runPostRunCommands(in Package pack, in Project proj, in GeneratorSettings settings, in BuildSettings buildsettings) { if (buildsettings.postRunCommands.length) { logInfo("Post-run", Color.light_green, "Running commands..."); runBuildCommands(CommandType.postRun, buildsettings.postRunCommands, pack, proj, settings, buildsettings); } } private void cleanupTemporaries() { foreach_reverse (f; m_temporaryFiles) { try { if (f.endsWithSlash) rmdir(f.toNativeString()); else remove(f.toNativeString()); } catch (Exception e) { logWarn("Failed to remove temporary file '%s': %s", f.toNativeString(), e.msg); logDiagnostic("Full error: %s", e.toString().sanitize); } } m_temporaryFiles = null; } } /** * Provides a unique (per build) identifier * * When building a package, it is important to have a unique but stable * identifier to differentiate builds and allow their caching. * This function provides such an identifier. * Example: * ``` * application-debug-linux.posix-x86_64-dmd_v2.100.2-D80285212AEC1FF9855F18AD52C68B9EEB5C7690609C224575F920096FB1965B * ``` */ private string computeBuildID(in BuildSettings buildsettings, string config, GeneratorSettings settings) { const(string[])[] hashing = [ buildsettings.versions, buildsettings.debugVersions, buildsettings.dflags, buildsettings.lflags, buildsettings.stringImportPaths, buildsettings.importPaths, buildsettings.cImportPaths, settings.platform.architecture, [ (cast(uint)(buildsettings.options & ~BuildOption.color)).to!string, // exclude color option from id settings.platform.compilerBinary, settings.platform.compiler, settings.platform.compilerVersion ], ]; return computeBuildName(config, settings, hashing); } private NativePath getMainSourceFile(in Package prj) { foreach (f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"]) if (existsFile(prj.path ~ f)) return prj.path ~ f; return prj.path ~ "source/app.d"; } private NativePath getTargetPath(const scope ref BuildSettings bs, const scope ref GeneratorSettings settings) { return NativePath(bs.targetPath) ~ settings.compiler.getTargetFileName(bs, settings.platform); } private string shrinkPath(NativePath path, NativePath base) { auto orig = path.toNativeString(); if (!path.absolute) return orig; version (Windows) { // avoid relative paths starting with `..\`: https://github.com/dlang/dub/issues/2143 if (!path.startsWith(base)) return orig; } auto rel = path.relativeTo(base).toNativeString(); return rel.length < orig.length ? rel : orig; } unittest { assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo")) == NativePath("bar/baz").toNativeString()); version (Windows) assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("/foo/bar/baz").toNativeString()); else assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/foo/baz")) == NativePath("../bar/baz").toNativeString()); assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/")) == NativePath("/foo/bar/baz").toNativeString()); assert(shrinkPath(NativePath("/foo/bar/baz"), NativePath("/bar/baz")) == NativePath("/foo/bar/baz").toNativeString()); } unittest { // issue #1235 - pass no library files to compiler command line when building a static lib import dub.internal.vibecompat.data.json : parseJsonString; import dub.compilers.gdc : GDCCompiler; import dub.platform : determinePlatform; version (Windows) auto libfile = "bar.lib"; else auto libfile = "bar.a"; auto desc = parseJsonString(`{"name": "test", "targetType": "library", "sourceFiles": ["foo.d", "`~libfile~`"]}`); auto pack = new Package(desc, NativePath("/tmp/fooproject")); auto pman = new PackageManager(pack.path, NativePath("/tmp/foo/"), NativePath("/tmp/foo/"), false); auto prj = new Project(pman, pack); final static class TestCompiler : GDCCompiler { override void invoke(in BuildSettings settings, in BuildPlatform platform, void delegate(int, string) output_callback, NativePath cwd) { assert(!settings.dflags[].any!(f => f.canFind("bar"))); } override void invokeLinker(in BuildSettings settings, in BuildPlatform platform, string[] objects, void delegate(int, string) output_callback, NativePath cwd) { assert(false); } } GeneratorSettings settings; settings.platform = BuildPlatform(determinePlatform(), ["x86"], "gdc", "test", 2075); settings.compiler = new TestCompiler; settings.config = "library"; settings.buildType = "debug"; settings.tempBuild = true; auto gen = new BuildGenerator(prj); gen.generate(settings); }