diff --git a/build-files.txt b/build-files.txt index 1b0cb9b..b0b37fc 100644 --- a/build-files.txt +++ b/build-files.txt @@ -14,7 +14,6 @@ source/dub/generators/build.d source/dub/generators/generator.d source/dub/generators/monod.d -source/dub/generators/rdmd.d source/dub/generators/visuald.d source/dub/internal/utils.d source/dub/internal/vibecompat/core/file.d diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index dda7bb1..3695dd4 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -29,38 +29,168 @@ class BuildGenerator : ProjectGenerator { private { Project m_project; - PackageManager m_pkgMgr; + PackageManager m_packageMan; + Path[] m_temporaryFiles; } + + bool useRDMD = false; this(Project app, PackageManager mgr) { m_project = app; - m_pkgMgr = mgr; + m_packageMan = mgr; } - + void generateProject(GeneratorSettings settings) { + scope(exit) cleanupTemporaries(); + auto cwd = Path(getcwd()); + if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform); auto buildsettings = settings.buildSettings; m_project.addBuildSettings(buildsettings, settings.platform, settings.config, null, settings.buildType == "ddox"); m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType); + // make all paths relative to shrink the command line + string makeRelative(string path) { auto p = Path(path); if (p.absolute) p = p.relativeTo(cwd); return p.toNativeString(); } + foreach (ref f; buildsettings.sourceFiles) f = makeRelative(f); + foreach (ref p; buildsettings.importPaths) p = makeRelative(p); + foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); + + // perform the actual build + if (this.useRDMD) performRDMDBuild(settings, buildsettings); + else if (settings.direct) performDirectBuild(settings, buildsettings); + else performCachedBuild(settings, buildsettings); + + // run post-build commands + if (buildsettings.postBuildCommands.length) { + logInfo("Running post-build commands..."); + runBuildCommands(buildsettings.postBuildCommands, buildsettings); + } + + // run the generated executable + if (!(buildsettings.options & BuildOptions.syntaxOnly) && settings.run) { + auto exe_file_path = Path(buildsettings.targetPath) ~ getTargetFileName(buildsettings, settings.platform); + runTarget(exe_file_path, buildsettings, settings.runArgs); + } + } + + void performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings) + { + auto cwd = Path(getcwd()); + auto build_id = computeBuildID(settings); + auto target_path = m_project.mainPackage.path ~ format(".dub/build/%s/", build_id); + + if (isUpToDate(target_path, buildsettings, settings.platform)) { + logInfo("Target is up to date. Skipping build."); + copyTargetFile(target_path, buildsettings, settings.platform); + return; + } + + if (!isWritableDir(target_path, true)) { + logInfo("Build directory %s is not writable. Falling back to direct build in the system's temp folder.", target_path.relativeTo(cwd).toNativeString()); + performDirectBuild(settings, buildsettings); + return; + } + + // determine basic build properties + auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); + + // run pre-/post-generate commands and copy "copyFiles" + prepareGeneration(buildsettings); + finalizeGeneration(buildsettings, generate_binary); + + logInfo("Building configuration \""~settings.config~"\", build type "~settings.buildType); + + if( buildsettings.preBuildCommands.length ){ + logInfo("Running pre-build commands..."); + runBuildCommands(buildsettings.preBuildCommands, buildsettings); + } + + // override target path + auto cbuildsettings = buildsettings; + cbuildsettings.targetPath = target_path.relativeTo(cwd).toNativeString(); + buildWithCompiler(settings, cbuildsettings); + + copyTargetFile(target_path, buildsettings, settings.platform); + } + + void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings) + { + //Added check for existance of [AppNameInPackagejson].d + //If exists, use that as the starting file. + auto mainsrc = getMainSourceFile(m_project); + + // 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, 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 "\" + Path exe_file_path; + bool tmp_target = false; + if (generate_binary) { + if (settings.run && !isWritableDir(Path(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; + } + exe_file_path = Path(buildsettings.targetPath) ~ getTargetFileName(buildsettings, settings.platform); + settings.compiler.setTarget(buildsettings, settings.platform); + } + + logDiagnostic("Application output name is '%s'", getTargetFileName(buildsettings, settings.platform)); + + string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary]; + flags ~= buildsettings.dflags; + flags ~= (mainsrc).toNativeString(); + + prepareGeneration(buildsettings); + finalizeGeneration(buildsettings, generate_binary); + + if (buildsettings.preBuildCommands.length){ + logInfo("Running pre-build commands..."); + runCommands(buildsettings.preBuildCommands); + } + + logInfo("Building configuration "~settings.config~", build type "~settings.buildType); + + logInfo("Running rdmd..."); + logDiagnostic("rdmd %s", join(flags, " ")); + auto rdmd_pid = spawnProcess("rdmd" ~ flags); + auto result = rdmd_pid.wait(); + enforce(result == 0, "Build command failed with exit code "~to!string(result)); + + if (tmp_target) { + m_temporaryFiles ~= exe_file_path; + foreach (f; buildsettings.copyFiles) + m_temporaryFiles ~= Path(buildsettings.targetPath).parentPath ~ Path(f).head; + } + } + + void performDirectBuild(GeneratorSettings settings, ref BuildSettings buildsettings) + { + auto cwd = Path(getcwd()); + auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; // make file paths relative to shrink the command line - foreach(ref f; buildsettings.sourceFiles){ + foreach (ref f; buildsettings.sourceFiles) { auto fp = Path(f); - if( fp.absolute ) fp = fp.relativeTo(Path(getcwd())); + if( fp.absolute ) fp = fp.relativeTo(cwd); f = fp.toNativeString(); } - // find the temp directory - auto tmp = getTempDir(); - - if( settings.config.length ) logInfo("Building configuration \""~settings.config~"\", build type "~settings.buildType); - else logInfo("Building default configuration, build type "~settings.buildType); + logInfo("Building configuration \""~settings.config~"\", build type "~settings.buildType); prepareGeneration(buildsettings); @@ -80,12 +210,14 @@ if (settings.run && !isWritableDir(Path(buildsettings.targetPath), true)) { import std.random; auto rnd = to!string(uniform(uint.min, uint.max)); - buildsettings.targetPath = (tmp~"dub/"~rnd).toNativeString(); + auto tmppath = getTempDir()~("dub/"~rnd~"/"); + buildsettings.targetPath = tmppath.toNativeString(); + m_temporaryFiles ~= tmppath; is_temp_target = true; } exe_file_path = Path(buildsettings.targetPath) ~ getTargetFileName(buildsettings, settings.platform); + settings.compiler.setTarget(buildsettings, settings.platform); } - logDiagnostic("Application output name is '%s'", exe_file_path.toNativeString()); finalizeGeneration(buildsettings, generate_binary); @@ -94,15 +226,65 @@ runBuildCommands(buildsettings.preBuildCommands, buildsettings); } - // assure that we clean up after ourselves - Path[] cleanup_files; - scope (exit) { - foreach (f; cleanup_files) - if (existsFile(f)) - remove(f.toNativeString()); - if (is_temp_target) - rmdirRecurse(buildsettings.targetPath); + buildWithCompiler(settings, buildsettings); + + if (is_temp_target) { + m_temporaryFiles ~= exe_file_path; + foreach (f; buildsettings.copyFiles) + m_temporaryFiles ~= Path(buildsettings.targetPath).parentPath ~ Path(f).head; } + } + + private string computeBuildID(GeneratorSettings settings) + { + import std.digest.digest; + import std.digest.sha; + SHA1 hash; + hash.start(); + // ... + auto hashstr = hash.finish().toHexString().idup; + + return format("%s-%s-%s-%s-%s", settings.config, settings.buildType, + settings.platform.architecture.join("."), + settings.platform.compilerBinary, hashstr); + } + + private void copyTargetFile(Path build_path, BuildSettings buildsettings, BuildPlatform platform) + { + auto filename = getTargetFileName(buildsettings, platform); + auto src = build_path ~ filename; + copyFile(src, Path(buildsettings.targetPath) ~ filename, true); + } + + private bool isUpToDate(Path target_path, BuildSettings buildsettings, BuildPlatform platform) + { + import std.datetime; + + auto targetfile = target_path ~ getTargetFileName(buildsettings, platform); + if (!existsFile(targetfile)) return false; + auto targettime = getFileInfo(targetfile).timeModified; + + auto allfiles = appender!(string[]); + allfiles ~= buildsettings.sourceFiles; + allfiles ~= buildsettings.importFiles; + allfiles ~= buildsettings.stringImportFiles; + foreach (p; m_project.getTopologicalPackageList()) + allfiles ~= p.packageInfoFile.toNativeString(); + + foreach (file; allfiles.data) { + auto ftime = getFileInfo(file).timeModified; + if (ftime > Clock.currTime) + logWarn("File '%s' was modified in the future. Please re-save."); + if (ftime > targettime) + return false; + } + return true; + } + + void buildWithCompiler(GeneratorSettings settings, BuildSettings buildsettings) + { + auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); + auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library; /* NOTE: for DMD experimental separate compile/link is used, but this is not yet implemented @@ -111,17 +293,16 @@ */ if (settings.platform.compilerBinary != "dmd" || !generate_binary || is_static_library) { // setup for command line - if( generate_binary ) settings.compiler.setTarget(buildsettings, settings.platform); settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); // invoke the compiler logInfo("Running %s...", settings.platform.compilerBinary); - if (generate_binary && is_temp_target) cleanup_files ~= exe_file_path; settings.compiler.invoke(buildsettings, settings.platform); } else { // determine path for the temporary object file - version(Windows) enum tempobjname = "temp.obj"; - else enum tempobjname = "temp.o"; + string tempobjname = buildsettings.targetName; + version(Windows) tempobjname ~= ".obj"; + else tempobjname ~= ".o"; Path tempobj = Path(buildsettings.targetPath) ~ tempobjname; if (buildsettings.targetType == TargetType.dynamicLibrary) @@ -143,33 +324,49 @@ settings.compiler.invoke(buildsettings, settings.platform); logInfo("Linking..."); - if (is_temp_target) cleanup_files ~= exe_file_path; settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()]); } - - // run post-build commands - if( buildsettings.postBuildCommands.length ){ - logInfo("Running post-build commands..."); - runBuildCommands(buildsettings.postBuildCommands, buildsettings); - } - - // copy files and run the executable - if (generate_binary && settings.run) { - if (buildsettings.targetType == TargetType.executable) { - auto runcwd = cwd; - if (buildsettings.workingDirectory.length) { - runcwd = cwd ~ buildsettings.workingDirectory; - logDiagnostic("Switching to %s", runcwd.toNativeString()); - chdir(runcwd.toNativeString()); - } - scope(exit) chdir(cwd.toNativeString()); - if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path; - exe_file_path = exe_file_path.relativeTo(runcwd); - logInfo("Running %s %s", exe_file_path.toNativeString(), settings.runArgs.join(" ")); - auto prg_pid = spawnProcess(exe_file_path.toNativeString() ~ settings.runArgs); - auto result = prg_pid.wait(); - enforce(result == 0, "Program exited with code "~to!string(result)); - } else logInfo("Target is a library. Skipping execution."); - } } + + void runTarget(Path exe_file_path, BuildSettings buildsettings, string[] run_args) + { + if (buildsettings.targetType == TargetType.executable) { + auto cwd = Path(getcwd()); + auto runcwd = cwd; + if (buildsettings.workingDirectory.length) { + runcwd = cwd ~ buildsettings.workingDirectory; + logDiagnostic("Switching to %s", runcwd.toNativeString()); + chdir(runcwd.toNativeString()); + } + scope(exit) chdir(cwd.toNativeString()); + if (!exe_file_path.absolute) exe_file_path = cwd ~ exe_file_path; + exe_file_path = exe_file_path.relativeTo(runcwd); + logInfo("Running %s %s", exe_file_path.toNativeString(), run_args.join(" ")); + auto prg_pid = spawnProcess(exe_file_path.toNativeString() ~ run_args); + auto result = prg_pid.wait(); + enforce(result == 0, "Program exited with code "~to!string(result)); + } else logInfo("Target is a library. Skipping execution."); + } + + 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; + } +} + +private Path getMainSourceFile(in Project prj) +{ + foreach( f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"]) + if( exists(f) ) + return Path(f); + return Path("source/app.d"); } diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index afeed84..360d5d0 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -10,7 +10,6 @@ import dub.compilers.compiler; import dub.generators.build; import dub.generators.monod; -import dub.generators.rdmd; import dub.generators.visuald; import dub.internal.vibecompat.core.file; import dub.internal.vibecompat.core.log; @@ -35,14 +34,14 @@ struct GeneratorSettings { BuildPlatform platform; - string config; Compiler compiler; + string config; + string buildType; BuildSettings buildSettings; - // only used for generator "rdmd" - bool run; + // only used for generator "build" + bool run, force, direct, clean; string[] runArgs; - string buildType; } @@ -62,7 +61,9 @@ return new BuildGenerator(app, mgr); case "rdmd": logDebug("Creating rdmd generator."); - return new RdmdGenerator(app, mgr); + auto ret = new BuildGenerator(app, mgr); + ret.useRDMD = true; + return ret; case "mono-d": logDebug("Creating MonoD generator."); return new MonoDGenerator(app, mgr); diff --git a/source/dub/generators/rdmd.d b/source/dub/generators/rdmd.d deleted file mode 100644 index 2ee3bee..0000000 --- a/source/dub/generators/rdmd.d +++ /dev/null @@ -1,134 +0,0 @@ -/** - Generator for direct RDMD 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.rdmd; - -import dub.compilers.compiler; -import dub.generators.generator; -import dub.internal.utils; -import dub.internal.vibecompat.core.file; -import dub.internal.vibecompat.core.log; -import dub.internal.vibecompat.inet.path; -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; - - -class RdmdGenerator : ProjectGenerator { - private { - Project m_project; - PackageManager m_pkgMgr; - } - - this(Project app, PackageManager mgr) - { - m_project = app; - m_pkgMgr = mgr; - } - - void generateProject(GeneratorSettings settings) - { - //Added check for existance of [AppNameInPackagejson].d - //If exists, use that as the starting file. - auto mainsrc = getMainSourceFile(m_project); - - auto buildsettings = settings.buildSettings; - m_project.addBuildSettings(buildsettings, settings.platform, settings.config); - m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType); - // 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, 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 "\" - Path run_exe_file; - bool tmp_target = false; - if (generate_binary) { - if (settings.run && !isWritableDir(Path(buildsettings.targetPath), true)) { - import std.random; - auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; - buildsettings.targetPath = (getTempDir()~".rdmd/source/").toNativeString(); - buildsettings.targetName = rnd ~ buildsettings.targetName; - tmp_target = true; - } - run_exe_file = Path(buildsettings.targetPath) ~ getTargetFileName(buildsettings, settings.platform); - settings.compiler.setTarget(buildsettings, settings.platform); - } - - logDiagnostic("Application output name is '%s'", getTargetFileName(buildsettings, settings.platform)); - - string[] flags = ["--build-only", "--compiler="~settings.platform.compilerBinary]; - flags ~= buildsettings.dflags; - flags ~= (mainsrc).toNativeString(); - - prepareGeneration(buildsettings); - finalizeGeneration(buildsettings, generate_binary); - - if( buildsettings.preBuildCommands.length ){ - logInfo("Running pre-build commands..."); - runCommands(buildsettings.preBuildCommands); - } - - if( settings.config.length ) logInfo("Building configuration "~settings.config~", build type "~settings.buildType); - else logInfo("Building default configuration, build type "~settings.buildType); - - logInfo("Running rdmd..."); - logDiagnostic("rdmd %s", join(flags, " ")); - auto rdmd_pid = spawnProcess("rdmd" ~ flags); - auto result = rdmd_pid.wait(); - enforce(result == 0, "Build command failed with exit code "~to!string(result)); - - if( buildsettings.postBuildCommands.length ){ - logInfo("Running post-build commands..."); - runCommands(buildsettings.postBuildCommands); - } - - if (generate_binary && settings.run) { - if (buildsettings.targetType == TargetType.executable) { - auto cwd = Path(getcwd()); - auto runcwd = cwd; - if (buildsettings.workingDirectory.length) - runcwd = cwd ~ buildsettings.workingDirectory; - logDiagnostic("Switching to %s", runcwd.toNativeString()); - chdir(runcwd.toNativeString()); - scope(exit) chdir(cwd.toNativeString()); - - if (!run_exe_file.absolute) run_exe_file = cwd ~ run_exe_file; - run_exe_file = run_exe_file.relativeTo(runcwd); - - logInfo("Running %s...", run_exe_file.toNativeString()); - auto prg_pid = spawnProcess(run_exe_file.toNativeString() ~ settings.runArgs); - result = prg_pid.wait(); - if (tmp_target) { - remove(run_exe_file.toNativeString()); - foreach( f; buildsettings.copyFiles ) - remove((run_exe_file.parentPath ~ Path(f).head).toNativeString()); - } - enforce(result == 0, "Program exited with code "~to!string(result)); - } else logInfo("Target is a library. Skipping execution."); - } - } -} - -private Path getMainSourceFile(in Project prj) -{ - foreach( f; ["source/app.d", "src/app.d", "source/"~prj.name~".d", "src/"~prj.name~".d"]) - if( exists(f) ) - return Path(f); - return Path("source/app.d"); -}