diff --git a/source/app.d b/source/app.d index 6b300b1..9394dda 100644 --- a/source/app.d +++ b/source/app.d @@ -238,6 +238,7 @@ logDebug("Generating using %s", generator); dub.generateProject(generator, gensettings); + if( build_type == "ddox" ) dub.runDdox(); break; } @@ -308,7 +309,7 @@ remove-local Removes a local package directory list-locals Prints a list of all locals generate Generates project files using the specified generator: - visuald, mono-d, build, rdmd + visuald, mono-d, build, rdmd General options: --annotate Do not execute dependency installations, just print @@ -318,13 +319,16 @@ --vquiet No output Build/run options: - --build=NAME Specifies the type of build to perform. Valid names: - debug (default), release, unittest, profile, docs, - plain + --build=NAME Specifies the type of build to perform. Note that + setting the DFLAGS environment variable will override + the build type with custom flags. + Possible names: + debug (default), plain, release, unittest, profile, + docs, ddox --config=NAME Builds the specified configuration. Configurations can be defined in package.json --compiler=NAME Uses one of the supported compilers: - dmd (default), gcc, ldc, gdmd, ldmd + dmd (default), gcc, ldc, gdmd, ldmd --arch=NAME Force a different architecture (e.g. x86 or x86_64) --nodeps Do not check dependencies for 'run' or 'build' --print-builds Prints the list of available build types diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index 81ed9fc..d1710e7 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -13,6 +13,7 @@ import std.algorithm; import std.array; +import std.exception; import vibecompat.data.json; import vibecompat.inet.path; @@ -65,36 +66,68 @@ string[] dflags; string[] lflags; string[] libs; - string[] files; + string[] sourceFiles; string[] copyFiles; string[] versions; string[] importPaths; string[] stringImportPaths; + string[] preGenerateCommands; + string[] postGenerateCommands; + string[] preBuildCommands; + string[] postBuildCommands; void parse(in Json root, BuildPlatform platform) { - addDFlags(getPlatformField(root, "dflags", platform)); - addLFlags(getPlatformField(root, "lflags", platform)); - addLibs(getPlatformField(root, "libs", platform)); - addFiles(getPlatformField(root, "files", platform)); - addCopyFiles(getPlatformField(root, "copyFiles", platform)); - addVersions(getPlatformField(root, "versions", platform)); - addImportDirs(getPlatformField(root, "importPaths", platform)); - addStringImportDirs(getPlatformField(root, "stringImportPaths", platform)); + foreach(string name, value; root){ + auto components = name.split("-"); + if( !matchesPlatform(components[1 .. $], platform) ) + continue; + + const(string[]) entries(){ + enforce(value.type == Json.Type.Array, "Field "~name~" must be of type string[]."); + return value.get!(Json[]).map!(j => j.get!string).array(); + } + + switch(components[0]){ + default: break; + case "dflags": addDFlags(entries()); break; + case "lflags": addLFlags(entries()); break; + case "libs": addLibs(entries()); break; + case "sourceFiles": + case "files": addSourceFiles(entries()); break; + case "copyFiles": addCopyFiles(entries()); break; + case "versions": addVersions(entries()); break; + case "importPaths": addImportPaths(entries()); break; + case "stringImportPaths": addStringImportPaths(entries()); break; + case "preGenerateCommands": addPreGenerateCommands(entries()); break; + case "postGenerateCommands": addPostGenerateCommands(entries()); break; + case "preBuildCommands": addPreBuildCommands(entries()); break; + case "postBuildCommands": addPostBuildCommands(entries()); break; + } + } } - void addDFlags(string[] value...) { add(dflags, value); } - void addLFlags(string[] value...) { add(lflags, value); } - void addLibs(string[] value...) { add(libs, value); } - void addFiles(string[] value...) { add(files, value); } - void addCopyFiles(string[] value...) { add(copyFiles, value); } - void addVersions(string[] value...) { add(versions, value); } - void addImportDirs(string[] value...) { add(importPaths, value); } - void addStringImportDirs(string[] value...) { add(stringImportPaths, value); } + void addDFlags(in string[] value...) { add(dflags, value); } + void addLFlags(in string[] value...) { add(lflags, value); } + void addLibs(in string[] value...) { add(libs, value); } + void addSourceFiles(in string[] value...) { add(sourceFiles, value); } + void addCopyFiles(in string[] value...) { add(copyFiles, value); } + void addVersions(in string[] value...) { add(versions, value); } + void addImportPaths(in string[] value...) { add(importPaths, value); } + void addStringImportPaths(in string[] value...) { add(stringImportPaths, value); } + void addPreGenerateCommands(in string[] value...) { add(preGenerateCommands, value, false); } + void addPostGenerateCommands(in string[] value...) { add(postGenerateCommands, value, false); } + void addPreBuildCommands(in string[] value...) { add(preBuildCommands, value, false); } + void addPostBuildCommands(in string[] value...) { add(postBuildCommands, value, false); } // Adds vals to arr without adding duplicates. - private void add(ref string[] arr, string[] vals) + private void add(ref string[] arr, in string[] vals, bool no_duplicates = true) { + if( !no_duplicates ){ + arr ~= vals; + return; + } + foreach( v; vals ){ bool found = false; foreach( i; 0 .. arr.length ) @@ -106,16 +139,14 @@ } } - // Parses json and returns the values of the corresponding field - // by the platform. - private string[] getPlatformField(in Json json, string name, BuildPlatform platform) - const { - auto ret = appender!(string[])(); - foreach( suffix; getPlatformSuffixIterator(platform) ){ - foreach( j; json[name~suffix].opt!(Json[]) ) - ret.put(j.get!string); - } - return ret.data; + bool matchesPlatform(string[] platform_parts, BuildPlatform platform) + { + if( platform_parts.length == 0 ) return true; + // TODO: optimize + foreach( suffix; getPlatformSuffixIterator(platform) ) + if( suffix == "-"~platform_parts.join("-") ) + return true; + return false; } } @@ -133,7 +164,7 @@ dflags = 1<<0, lflags = 1<<1, libs = 1<<2, - files = 1<<3, + sourceFiles = 1<<3, copyFiles = 1<<4, versions = 1<<5, importPaths = 1<<6, @@ -141,7 +172,7 @@ none = 0, commandLine = dflags|copyFiles, commandLineSeparate = commandLine|lflags, - all = dflags|lflags|libs|files|copyFiles|versions|importPaths|stringImportPaths + all = dflags|lflags|libs|sourceFiles|copyFiles|versions|importPaths|stringImportPaths } private { diff --git a/source/dub/compilers/dmd.d b/source/dub/compilers/dmd.d index 4d0f1e3..fb3f6ff 100644 --- a/source/dub/compilers/dmd.d +++ b/source/dub/compilers/dmd.d @@ -56,7 +56,7 @@ } catch( Exception e ){ logDebug("pkg-config failed: %s", e.msg); logDebug("Falling back to direct -lxyz flags."); - version(Windows) settings.addFiles(settings.libs.map!(l => l~".lib")().array()); + version(Windows) settings.addSourceFiles(settings.libs.map!(l => l~".lib")().array()); else settings.addLFlags(settings.libs.map!(l => "-l"~l)().array()); } settings.libs = null; @@ -77,9 +77,9 @@ settings.stringImportPaths = null; } - if( !(fields & BuildSetting.files) ){ - settings.addDFlags(settings.files); - settings.files = null; + if( !(fields & BuildSetting.sourceFiles) ){ + settings.addDFlags(settings.sourceFiles); + settings.sourceFiles = null; } if( !(fields & BuildSetting.lflags) ){ diff --git a/source/dub/compilers/gdc.d b/source/dub/compilers/gdc.d index 39a47de..707c413 100644 --- a/source/dub/compilers/gdc.d +++ b/source/dub/compilers/gdc.d @@ -117,9 +117,9 @@ settings.stringImportPaths = null; } - if( !(fields & BuildSetting.files) ){ - settings.addDFlags(settings.files); - settings.files = null; + if( !(fields & BuildSetting.sourceFiles) ){ + settings.addDFlags(settings.sourceFiles); + settings.sourceFiles = null; } if( !(fields & BuildSetting.lflags) ){ diff --git a/source/dub/compilers/ldc.d b/source/dub/compilers/ldc.d index d94a67c..abfb71c 100644 --- a/source/dub/compilers/ldc.d +++ b/source/dub/compilers/ldc.d @@ -74,9 +74,9 @@ settings.stringImportPaths = null; } - if( !(fields & BuildSetting.files) ){ - settings.addDFlags(settings.files); - settings.files = null; + if( !(fields & BuildSetting.sourceFiles) ){ + settings.addDFlags(settings.sourceFiles); + settings.sourceFiles = null; } if( !(fields & BuildSetting.lflags) ){ diff --git a/source/dub/dub.d b/source/dub/dub.d index a9b2be8..0694fc8 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -95,18 +95,18 @@ void loadPackageFromCwd() { - m_root = m_cwd; + loadPackage(m_cwd); + } + + void loadPackage(Path path) + { + m_root = path; m_packageManager.projectPackagePath = m_root ~ ".dub/packages/"; m_project = new Project(m_packageManager, m_root); } string getDefaultConfiguration(BuildPlatform platform) const { return m_project.getDefaultConfiguration(platform); } - /// Lists all installed modules - void list() { - logInfo(m_project.info()); - } - /// Performs installation and uninstallation as necessary for /// the application. /// @param options bit combination of UpdateOptions @@ -170,25 +170,20 @@ m_project.createZip(zipFile); } - /// Prints some information to the log. - void info() { - logInfo("Status for %s", m_root); - logInfo("\n" ~ m_project.info()); - } /// Gets all installed packages as a "packageId" = "version" associative array string[string] installedPackages() const { return m_project.installedPackagesIDs(); } /// Installs the package matching the dependency into the application. - void install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.projectLocal) + Package install(string packageId, const Dependency dep, InstallLocation location = InstallLocation.projectLocal) { auto pinfo = m_packageSupplier.packageJson(packageId, dep); string ver = pinfo["version"].get!string; - if( m_packageManager.hasPackage(packageId, ver, location) ){ + if( auto pack = m_packageManager.getPackage(packageId, ver, location) ){ logInfo("Package %s %s (%s) is already installed with the latest version, skipping upgrade.", packageId, ver, location); - return; + return pack; } logInfo("Downloading %s %s...", packageId, ver); @@ -203,7 +198,7 @@ scope(exit) remove(sTempFile); logInfo("Installing %s %s...", packageId, ver); - m_packageManager.install(tempFile, pinfo, location); + return m_packageManager.install(tempFile, pinfo, location); } /// Uninstalls a given package from the list of installed modules. @@ -345,4 +340,43 @@ //Act smug to the user. logInfo("Successfully created an empty project in '"~path.toNativeString()~"'."); } + + void runDdox() + { + auto ddox_pack = m_packageManager.getBestPackage("ddox", ">=0.0.0"); + if( !ddox_pack ){ + logInfo("DDOX is not installed, performing user wide installation."); + ddox_pack = install("ddox", new Dependency(">=0.0.0"), InstallLocation.userWide); + } + + version(Windows) auto ddox_exe = "ddox.exe"; + else auto ddox_exe = "ddox"; + + if( !existsFile(ddox_pack.path~ddox_exe) ){ + logInfo("DDOX in %s is not built, performing build now.", ddox_pack.path.toNativeString()); + + auto ddox_dub = new Dub(m_packageSupplier); + ddox_dub.loadPackage(ddox_pack.path); + + GeneratorSettings settings; + settings.compilerBinary = "dmd"; + settings.compiler = getCompiler(settings.compilerBinary); + settings.platform = settings.compiler.determinePlatform(settings.buildSettings, settings.compilerBinary); + settings.buildType = "debug"; + ddox_dub.generateProject("build", settings); + + //runCommands(["cd "~ddox_pack.path.toNativeString()~" && dub build -v"]); + } + + auto p = ddox_pack.path; + p.endsWithSlash = true; + auto dub_path = p.toNativeString(); + + string[] commands; + commands ~= dub_path~"ddox filter --min-protection=Protected docs.json"; + commands ~= dub_path~"ddox generate-html docs.json docs"; + version(Windows) commands ~= "xcopy /S /D "~dub_path~"public\\* docs\\"; + else commands ~= "cp -r "~dub_path~"public/* docs/"; + runCommands(commands); + } } diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 7ed19b2..1d592e5 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -12,6 +12,7 @@ import dub.package_; import dub.packagemanager; import dub.project; +import dub.utils; import std.algorithm; import std.array; @@ -40,11 +41,11 @@ void generateProject(GeneratorSettings settings) { - //Added check for existance of [AppNameInPackagejson].d //If exists, use that as the starting file. auto outfile = getBinName(m_project); auto mainsrc = getMainSourceFile(m_project); + auto cwd = Path(getcwd()); logDebug("Application output name is '%s'", outfile); @@ -64,31 +65,35 @@ foreach(s; pack.sources){ if( pack !is m_project.mainPackage && s == Path("source/app.d") ) continue; - auto relpath = (pack.path ~ s).relativeTo(m_project.mainPackage.path); - buildsettings.addFiles(relpath.toNativeString()); + auto relpath = (pack.path ~ s).relativeTo(cwd); + buildsettings.addSourceFiles(relpath.toNativeString()); } } addPackageFiles(m_project.mainPackage); - foreach(dep; m_project.installedPackages) + foreach(dep; m_project.dependencies) addPackageFiles(dep); + auto generate_binary = !buildsettings.dflags.canFind("-o-"); + // setup for command line settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); Path run_exe_file; - if( !settings.run ){ - settings.compiler.setTarget(buildsettings, m_project.binaryPath~outfile); - } else { - import std.random; - auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; - auto tmp = environment.get("TEMP"); - if( !tmp.length ) tmp = environment.get("TMP"); - if( !tmp.length ){ - version(Posix) tmp = "/tmp"; - else tmp = "."; + if( generate_binary ){ + if( !settings.run ){ + settings.compiler.setTarget(buildsettings, m_project.binaryPath~outfile); + } else { + import std.random; + auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; + auto tmp = environment.get("TEMP"); + if( !tmp.length ) tmp = environment.get("TMP"); + if( !tmp.length ){ + version(Posix) tmp = "/tmp"; + else tmp = "."; + } + run_exe_file = Path(tmp~"/.rdmd/source/"~rnd~outfile); + settings.compiler.setTarget(buildsettings, run_exe_file); } - run_exe_file = Path(tmp~"/.rdmd/source/"~rnd~outfile); - settings.compiler.setTarget(buildsettings, run_exe_file); } string[] flags = buildsettings.dflags; @@ -96,30 +101,53 @@ if( settings.config.length ) logInfo("Building configuration "~settings.config~", build type "~settings.buildType); else logInfo("Building default configuration, build type "~settings.buildType); - logInfo("Running %s", settings.compilerBinary ~ " " ~ join(flags, " ")); + if( buildsettings.preGenerateCommands.length ){ + logInfo("Running pre-generate commands..."); + runCommands(buildsettings.preGenerateCommands); + } + + if( buildsettings.postGenerateCommands.length ){ + logInfo("Running post-generate commands..."); + runCommands(buildsettings.postGenerateCommands); + } + + if( buildsettings.preBuildCommands.length ){ + logInfo("Running pre-build commands..."); + runCommands(buildsettings.preBuildCommands); + } + + logInfo("Running %s...", settings.compilerBinary); + logDebug("%s %s", settings.compilerBinary, join(flags, " ")); auto compiler_pid = spawnProcess(settings.compilerBinary, flags); auto result = compiler_pid.wait(); enforce(result == 0, "Build command failed with exit code "~to!string(result)); - // TODO: move to a common place - this is not generator specific - if( buildsettings.copyFiles.length ){ - logInfo("Copying files..."); - foreach( f; buildsettings.copyFiles ){ - auto src = Path(f); - auto dst = (run_exe_file.empty ? m_project.binaryPath : run_exe_file.parentPath) ~ Path(f).head; - logDebug(" %s to %s", src.toNativeString(), dst.toNativeString()); - try copyFile(src, dst, true); - catch logWarn("Failed to copy to %s", dst.toNativeString()); - } + if( buildsettings.postBuildCommands.length ){ + logInfo("Running post-build commands..."); + runCommands(buildsettings.postBuildCommands); } - if( settings.run ){ - auto prg_pid = spawnProcess(run_exe_file.toNativeString(), settings.runArgs); - result = prg_pid.wait(); - 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)); + if( generate_binary ){ + // TODO: move to a common place - this is not generator specific + if( buildsettings.copyFiles.length ){ + logInfo("Copying files..."); + foreach( f; buildsettings.copyFiles ){ + auto src = Path(f); + auto dst = (run_exe_file.empty ? m_project.binaryPath : run_exe_file.parentPath) ~ Path(f).head; + logDebug(" %s to %s", src.toNativeString(), dst.toNativeString()); + try copyFile(src, dst, true); + catch logWarn("Failed to copy to %s", dst.toNativeString()); + } + } + + if( settings.run ){ + auto prg_pid = spawnProcess(run_exe_file.toNativeString(), settings.runArgs); + result = prg_pid.wait(); + 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)); + } } } } diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index e832d98..c64ead6 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -79,6 +79,7 @@ case "release": dst.addDFlags("-release", "-O", "-inline"); break; case "unittest": dst.addDFlags("-g", "-unittest"); break; case "profile": dst.addDFlags("-g", "-O", "-inline", "-profile"); break; - case "docs": dst.addDFlags("-c", "-o-", "-D", "-Dfdocs", "-Xfdocs.json"); break; + case "docs": dst.addDFlags("-c", "-o-", "-D", "-Dddocs"); break; + case "ddox": dst.addDFlags("-c", "-o-", "-D", "-Df__dummy.html", "-Xfdocs.json"); break; } } diff --git a/source/dub/generators/monod.d b/source/dub/generators/monod.d index e3db7e4..72b4cee 100644 --- a/source/dub/generators/monod.d +++ b/source/dub/generators/monod.d @@ -12,6 +12,7 @@ import dub.package_; import dub.packagemanager; import dub.project; +import dub.utils; import std.algorithm; import std.array; @@ -24,6 +25,8 @@ import vibecompat.core.log; +// TODO: handle pre/post build commands + class MonoDGenerator : ProjectGenerator { private { Project m_app; @@ -42,9 +45,21 @@ void generateProject(GeneratorSettings settings) { + auto buildsettings = settings.buildSettings; + + if( buildsettings.preGenerateCommands.length ){ + logInfo("Running pre-generate commands..."); + runCommands(buildsettings.preGenerateCommands); + } + logTrace("About to generate projects for %s, with %s direct dependencies.", m_app.mainPackage().name, m_app.mainPackage().dependencies().length); generateProjects(m_app.mainPackage(), settings); generateSolution(settings); + + if( buildsettings.postGenerateCommands.length ){ + logInfo("Running post-generate commands..."); + runCommands(buildsettings.postGenerateCommands); + } } private void generateSolution(GeneratorSettings settings) @@ -239,7 +254,7 @@ continue; generateSourceEntry(p.path ~s, p.path); } - foreach( s; buildsettings.files ) + foreach( s; buildsettings.sourceFiles ) generateSourceEntry(Path(s), p.path); } @@ -247,7 +262,7 @@ sln.put(" \n"); generateSources(pack); if( m_singleProject ) - foreach(dep; m_app.installedPackages) + foreach(dep; m_app.dependencies) generateSources(dep); sln.put(" \n"); sln.put(""); diff --git a/source/dub/generators/rdmd.d b/source/dub/generators/rdmd.d index 8602001..2d11159 100644 --- a/source/dub/generators/rdmd.d +++ b/source/dub/generators/rdmd.d @@ -12,7 +12,9 @@ import dub.package_; import dub.packagemanager; import dub.project; +import dub.utils; +import std.algorithm; import std.array; import std.conv; import std.exception; @@ -39,7 +41,6 @@ void generateProject(GeneratorSettings settings) { - //Added check for existance of [AppNameInPackagejson].d //If exists, use that as the starting file. auto outfile = getBinName(m_project); @@ -47,26 +48,6 @@ logDebug("Application output name is '%s'", outfile); - // 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 "\" - string[] flags = ["--force", "--build-only", "--compiler="~settings.compilerBinary]; - Path run_exe_file; - if( !settings.run ){ - flags ~= "-of"~(m_project.binaryPath~outfile).toNativeString(); - } else { - import std.random; - auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; - auto tmp = environment.get("TEMP"); - if( !tmp.length ) tmp = environment.get("TMP"); - if( !tmp.length ){ - version(Posix) tmp = "/tmp"; - else tmp = "."; - } - run_exe_file = Path(tmp~"/.rdmd/source/"~rnd~outfile); - flags ~= "-of"~run_exe_file.toNativeString(); - } - auto buildsettings = settings.buildSettings; m_project.addBuildSettings(buildsettings, settings.platform, settings.config); buildsettings.addDFlags(["-w"/*, "-property"*/]); @@ -78,37 +59,84 @@ addBuildTypeFlags(buildsettings, settings.buildType); } + 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 "\" + string[] flags = ["--force", "--build-only", "--compiler="~settings.compilerBinary]; + Path run_exe_file; + if( generate_binary ){ + if( !settings.run ){ + flags ~= "-of"~(m_project.binaryPath~outfile).toNativeString(); + } else { + import std.random; + auto rnd = to!string(uniform(uint.min, uint.max)) ~ "-"; + auto tmp = environment.get("TEMP"); + if( !tmp.length ) tmp = environment.get("TMP"); + if( !tmp.length ){ + version(Posix) tmp = "/tmp"; + else tmp = "."; + } + run_exe_file = Path(tmp~"/.rdmd/source/"~rnd~outfile); + flags ~= "-of"~run_exe_file.toNativeString(); + } + } + settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine); flags ~= buildsettings.dflags; flags ~= (mainsrc).toNativeString(); + if( buildsettings.preGenerateCommands.length ){ + logInfo("Running pre-generate commands..."); + runCommands(buildsettings.preGenerateCommands); + } + + if( buildsettings.postGenerateCommands.length ){ + logInfo("Running post-generate commands..."); + runCommands(buildsettings.postGenerateCommands); + } + + 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 %s", "rdmd " ~ join(flags, " ")); + logInfo("Running rdmd..."); + logDebug("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)); - // TODO: move to a common place - this is not generator specific - if( buildsettings.copyFiles.length ){ - logInfo("Copying files..."); - foreach( f; buildsettings.copyFiles ){ - auto src = Path(f); - auto dst = (run_exe_file.empty ? m_project.binaryPath : run_exe_file.parentPath) ~ Path(f).head; - logDebug(" %s to %s", src.toNativeString(), dst.toNativeString()); - try copyFile(src, dst, true); - catch logWarn("Failed to copy to %s", dst.toNativeString()); - } + if( buildsettings.postBuildCommands.length ){ + logInfo("Running post-build commands..."); + runCommands(buildsettings.postBuildCommands); } - if( settings.run ){ - auto prg_pid = spawnProcess(run_exe_file.toNativeString(), settings.runArgs); - result = prg_pid.wait(); - 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)); + if( generate_binary ){ + // TODO: move to a common place - this is not generator specific + if( buildsettings.copyFiles.length ){ + logInfo("Copying files..."); + foreach( f; buildsettings.copyFiles ){ + auto src = Path(f); + auto dst = (run_exe_file.empty ? m_project.binaryPath : run_exe_file.parentPath) ~ Path(f).head; + logDebug(" %s to %s", src.toNativeString(), dst.toNativeString()); + try copyFile(src, dst, true); + catch logWarn("Failed to copy to %s", dst.toNativeString()); + } + } + + if( settings.run ){ + auto prg_pid = spawnProcess(run_exe_file.toNativeString(), settings.runArgs); + result = prg_pid.wait(); + 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)); + } } } } diff --git a/source/dub/generators/visuald.d b/source/dub/generators/visuald.d index 6e52a52..38b5ee3 100644 --- a/source/dub/generators/visuald.d +++ b/source/dub/generators/visuald.d @@ -12,6 +12,7 @@ import dub.package_; import dub.packagemanager; import dub.project; +import dub.utils; import std.algorithm; import std.array; @@ -29,6 +30,9 @@ // Dubbing is developing dub... //version = DUBBING; +// TODO: handle pre/post build commands + + class VisualDGenerator : ProjectGenerator { private { Project m_app; @@ -42,10 +46,22 @@ } void generateProject(GeneratorSettings settings) { + auto buildsettings = settings.buildSettings; + + if( buildsettings.preGenerateCommands.length ){ + logInfo("Running pre-generate commands..."); + runCommands(buildsettings.preGenerateCommands); + } + logTrace("About to generate projects for %s, with %s direct dependencies.", m_app.mainPackage().name, m_app.mainPackage().dependencies().length); generateProjects(m_app.mainPackage(), settings); generateSolution(); logInfo("VisualD project generated."); + + if( buildsettings.postGenerateCommands.length ){ + logInfo("Running post-generate commands..."); + runCommands(buildsettings.postGenerateCommands); + } } private { @@ -286,7 +302,7 @@ // Add libraries, system libs need to be suffixed by ".lib". string linkLibs = join(map!(a => a~".lib")(getSettings!"libs"()), " "); - string addLinkFiles = join(getSettings!"files"(), " "); + string addLinkFiles = join(getSettings!"sourceFiles"(), " "); ret.formattedWrite(" %s", linkLibs ~ " " ~ addLinkFiles); diff --git a/source/dub/package_.d b/source/dub/package_.d index 03ebe04..f1d2b2b 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -124,7 +124,12 @@ @property const(Url) url() const { return Url.parse(cast(string)m_meta["url"]); } @property const(Dependency[string]) dependencies() const { return m_dependencies; } @property const(LocalPackageDef)[] localPackageDefs() const { return m_localPackageDefs; } - @property string binaryPath() const { return m_meta["binaryPath"].opt!string; } + @property Path binaryPath() + const { + auto p = m_meta["binaryPath"].opt!string; + if( !p.length ) return this.path; + return this.path ~ Path(p); + } @property string[] configurations() const { @@ -148,6 +153,9 @@ if( !pc ) return ret; ret.parse(*pc, platform); } + + // TODO: add all sources and "source"/"src" as import paths + // TODO: add "views" as string import path return ret; } @@ -190,12 +198,26 @@ spaths ~= map!(p => Path(p.get!string()))((*multipleSourcePaths)[]).array; } if (spaths.empty) { - spaths ~= Path("source"); + if( existsFile(path ~ "source") ) spaths ~= Path("source"); + else if( existsFile(path ~ "src") ) spaths ~= Path("src"); } return spaths; } + @property const(Path[]) appSources() + const { + Path[] ret; + if( auto as = "appSources" in m_meta ){ + foreach(src; *as) + ret ~= Path(src.get!string()); + } else { + if( existsFile(m_path ~ "source/app.d") ) ret ~= Path("source/app.d"); + else if( existsFile(m_path ~ ("source/"~name()~".d")) ) ret ~= Path("source/"~name()~".d"); + } + return ret; + } + /// TODO: what is the defaul configuration? string getDefaultConfiguration(BuildPlatform platform) const { diff --git a/source/dub/packagemanager.d b/source/dub/packagemanager.d index 5b177f5..129bff0 100644 --- a/source/dub/packagemanager.d +++ b/source/dub/packagemanager.d @@ -66,13 +66,13 @@ return null; } - bool hasPackage(string name, string ver, InstallLocation location) + Package getPackage(string name, string ver, InstallLocation location) { foreach(ep; getPackageIterator(name)){ if( ep.installLocation == location && ep.vers == ver ) - return true; + return ep; } - return false; + return null; } Package getBestPackage(string name, string version_spec) diff --git a/source/dub/project.d b/source/dub/project.d index 2726f6c..3478c88 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -54,10 +54,7 @@ reinit(); } - @property Path binaryPath() const - { - return m_main.binaryPath.length ? Path(m_main.binaryPath) : Path("./"); - } + @property Path binaryPath() const { return m_main.binaryPath; } /// Gathers information @property string info() @@ -81,20 +78,47 @@ } /// List of installed Packages - @property const(Package[]) installedPackages() const { return m_dependencies; } + @property const(Package[]) dependencies() const { return m_dependencies; } /// Main package. @property const (Package) mainPackage() const { return m_main; } + /** Allows iteration of the dependency tree in topological order (children first) + */ + @property int delegate(int delegate(ref const Package)) topologicalPackageList() + const { + int iterator(int delegate(ref const Package) del) + { + int ret = 0; + bool[const(Package)] visited; + void perform_rec(in Package p){ + if( p in visited ) return; + visited[p] = true; + + foreach(d; p.dependencies.byKey) + foreach(dp; m_dependencies) + if( dp.name == d ){ + perform_rec(dp); + if( ret ) return; + break; + } + + ret = del(p); + if( ret ) return; + } + perform_rec(m_main); + return ret; + } + return &iterator; + } + string getDefaultConfiguration(BuildPlatform platform) const { string ret; - foreach( p; m_dependencies ){ + foreach(p; topologicalPackageList){ auto c = p.getDefaultConfiguration(platform); if( c.length ) ret = c; } - auto c = m_main.getDefaultConfiguration(platform); - if( c ) ret = c; return ret; } @@ -108,6 +132,11 @@ /// Rereads the applications state. void reinit() { + scope(failure){ + logDebug("Failed to parse package.json. Assuming defaults."); + m_main = new Package(serializeToJson(["name": ""]), InstallLocation.local, m_root); + } + m_dependencies = null; m_main = null; m_packageManager.refresh(); @@ -174,25 +203,22 @@ void addImportPath(string path, bool src) { if( !exists(path) ) return; - if( src ) dst.addImportDirs([path]); - else dst.addStringImportDirs([path]); + if( src ) dst.addImportPaths([path]); + else dst.addStringImportPaths([path]); } - if( m_main ) processVars(dst, ".", m_main.getBuildSettings(platform, config)); - addImportPath("source", true); - addImportPath("views", false); - - foreach( pkg; m_dependencies ){ + foreach(pkg; this.topologicalPackageList){ processVars(dst, pkg.path.toNativeString(), pkg.getBuildSettings(platform, config)); addImportPath((pkg.path ~ "source").toNativeString(), true); addImportPath((pkg.path ~ "views").toNativeString(), false); } // add version identifiers for available packages - foreach(pack; this.installedPackages) + foreach(pack; this.dependencies) dst.addVersions(["Have_" ~ stripDlangSpecialChars(pack.name)]); } + /// Actions which can be performed to update the application. Action[] determineActions(PackageSupplier packageSupplier, int option) { scope(exit) writeDubJson(); @@ -518,11 +544,15 @@ dst.addDFlags(processVars(project_path, settings.dflags)); dst.addLFlags(processVars(project_path, settings.lflags)); dst.addLibs(processVars(project_path, settings.libs)); - dst.addFiles(processVars(project_path, settings.files, true)); + dst.addSourceFiles(processVars(project_path, settings.sourceFiles, true)); dst.addCopyFiles(processVars(project_path, settings.copyFiles, true)); dst.addVersions(processVars(project_path, settings.versions)); - dst.addImportDirs(processVars(project_path, settings.importPaths, true)); - dst.addStringImportDirs(processVars(project_path, settings.stringImportPaths, true)); + dst.addImportPaths(processVars(project_path, settings.importPaths, true)); + dst.addStringImportPaths(processVars(project_path, settings.stringImportPaths, true)); + dst.addPreGenerateCommands(processVars(project_path, settings.preGenerateCommands)); + dst.addPostGenerateCommands(processVars(project_path, settings.postGenerateCommands)); + dst.addPreBuildCommands(processVars(project_path, settings.preBuildCommands)); + dst.addPostBuildCommands(processVars(project_path, settings.postBuildCommands)); } private string[] processVars(string project_path, string[] vars, bool are_paths = false) diff --git a/source/dub/utils.d b/source/dub/utils.d index 8ea6c54..a054ebb 100644 --- a/source/dub/utils.d +++ b/source/dub/utils.d @@ -20,6 +20,7 @@ import std.zip; import std.typecons; import std.conv; +import stdx.process; package bool isEmptyDir(Path p) { @@ -70,3 +71,13 @@ return str[3 ..$]; return str; } + +void runCommands(string[] commands) +{ + foreach(cmd; commands){ + logDebug("Running %s", cmd); + auto pipes = pipeShell(cmd, Redirect.none); + auto exitcode = pipes.pid.wait(); + enforce(exitcode == 0, "Command failed with exit code "~to!string(exitcode)); + } +}