diff --git a/source/dub/commandline.d b/source/dub/commandline.d index 70f836b..da81b20 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -19,7 +19,7 @@ import dub.packagesupplier; import dub.platform : determineCompiler; import dub.project; -import dub.internal.utils : getDUBVersion; +import dub.internal.utils : getDUBVersion, getClosestMatch; import std.algorithm; import std.array; @@ -381,7 +381,14 @@ m_defaultConfig = null; enforce (loadSpecificPackage(dub, package_name), "Failed to load package."); - enforce(m_buildConfig.length == 0 || dub.configurations.canFind(m_buildConfig), "Unknown build configuration: "~m_buildConfig); + if (m_buildConfig.length != 0 && !dub.configurations.canFind(m_buildConfig)) + { + string msg = "Unknown build configuration: "~m_buildConfig; + enum distance = 3; + if (auto match = dub.configurations.getClosestMatch(m_buildConfig, distance)) + msg ~= ". Did you mean '" ~ match ~ "'?"; + enforce(0, msg); + } if (m_buildType.length == 0) { if (environment.get("DFLAGS")) m_buildType = "$DFLAGS"; diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index 1802b10..cab722c 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -72,7 +72,7 @@ {["-cov"], "Call dub with --build=cov or --build=unittest-cox"}, {["-profile"], "Call dub with --build=profile"}, {["-version="], `Use "versions" to specify version constants in a compiler independent way`}, - {["-debug=", `Use "debugVersions" to specify version constants in a compiler independent way`]}, + {["-debug="], `Use "debugVersions" to specify version constants in a compiler independent way`}, {["-I"], `Use "importPaths" to specify import paths in a compiler independent way`}, {["-J"], `Use "stringImportPaths" to specify import paths in a compiler independent way`}, {["-m32", "-m64"], `Use --arch=x86/--arch=x86_64 to specify the target architecture`} diff --git a/source/dub/dub.d b/source/dub/dub.d index f58cc92..518ab92 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -481,11 +481,11 @@ if(version_.empty && packages.length > 1) { logError("Cannot remove package '" ~ package_id ~ "', there are multiple possibilities at location\n" ~ "'" ~ to!string(location_) ~ "'."); - logError("Retrieved versions:"); + logError("Available versions:"); foreach(pack; packages) - logError("%s", pack.vers); - throw new Exception("Please specify a individual version or use the wildcard identifier '" - ~ RemoveVersionWildcard ~ "' (without quotes)."); + logError(" %s", pack.vers); + throw new Exception("Please specify a individual version using --version=... or use the" + ~ " wildcard --version=" ~ RemoveVersionWildcard ~ " to remove all versions."); } logDebug("Removing %s packages.", packages.length); diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index a5b1d0d..06545d5 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -96,39 +96,40 @@ foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p); // perform the actual build + bool cached = false; if (settings.rdmd) performRDMDBuild(settings, buildsettings, pack, config); else if (settings.direct || !generate_binary) performDirectBuild(settings, buildsettings, pack, config); - else performCachedBuild(settings, buildsettings, pack, config, build_id, packages); + else cached = performCachedBuild(settings, buildsettings, pack, config, build_id, packages); // run post-build commands - if (buildsettings.postBuildCommands.length) { + if (!cached && buildsettings.postBuildCommands.length) { logInfo("Running post-build commands..."); runBuildCommands(buildsettings.postBuildCommands, buildsettings); } } - void performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, string build_id, in Package[] packages) + bool performCachedBuild(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, string build_id, in Package[] packages) { auto cwd = Path(getcwd()); auto target_path = pack.path ~ format(".dub/build/%s/", build_id); if (!settings.force && isUpToDate(target_path, buildsettings, settings.platform, pack, packages)) { - logInfo("Target %s (%s) is up to date. Use --force to rebuild.", pack.name, pack.vers); + logInfo("Target %s %s is up to date. Use --force to rebuild.", pack.name, pack.vers); logDiagnostic("Using existing build in %s.", target_path.toNativeString()); copyTargetFile(target_path, buildsettings, settings.platform); - return; + return true; } 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, pack, config); - return; + return false; } // determine basic build properties auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); - logInfo("Building %s configuration \"%s\", build type %s.", pack.name, config, settings.buildType); + logInfo("Building %s %s configuration \"%s\", build type %s.", pack.name, pack.vers, config, settings.buildType); if( buildsettings.preBuildCommands.length ){ logInfo("Running pre-build commands..."); @@ -141,6 +142,8 @@ buildWithCompiler(settings, cbuildsettings); copyTargetFile(target_path, buildsettings, settings.platform); + + return false; } void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config) diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index b89a8c2..11968da 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -56,7 +56,8 @@ string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config); foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { - auto buildsettings = pack.getBuildSettings(settings.platform, configs[pack.name]); + BuildSettings buildsettings; + buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); prepareGeneration(pack.name, buildsettings); } @@ -69,7 +70,8 @@ generateTargets(settings, targets); foreach (pack; m_project.getTopologicalPackageList(true, null, configs)) { - auto buildsettings = pack.getBuildSettings(settings.platform, configs[pack.name]); + BuildSettings buildsettings; + buildsettings.processVars(m_project, pack, pack.getBuildSettings(settings.platform, configs[pack.name]), true); bool generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly); finalizeGeneration(pack.name, buildsettings, pack.path, Path(bs.targetPath), generate_binary); } @@ -117,7 +119,7 @@ // start to build up the build settings BuildSettings buildsettings = settings.buildSettings.dup; - processVars(buildsettings, pack.path.toNativeString(), shallowbs, true); + processVars(buildsettings, m_project, pack, shallowbs, true); // remove any mainSourceFile from library builds if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) { diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index e402ded..65273a5 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -1,6 +1,6 @@ /** ... - + Copyright: © 2012 Matthias Dondorff License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. Authors: Matthias Dondorff @@ -208,4 +208,20 @@ default: return false; } return true; -} \ No newline at end of file +} + +/** + Get the closest match of $(D input) in the $(D array), where $(D distance) + is the maximum levenshtein distance allowed between the compared strings. + Returns $(D null) if no closest match is found. +*/ +string getClosestMatch(string[] array, string input, size_t distance) +{ + import std.algorithm : countUntil, map, levenshteinDistance; + import std.uni : toUpper; + + auto distMap = array.map!(elem => + levenshteinDistance!((a, b) => toUpper(a) == toUpper(b))(elem, input)); + auto idx = distMap.countUntil!(a => a <= distance); + return (idx == -1) ? null : array[idx]; +} diff --git a/source/dub/project.d b/source/dub/project.d index bbdc4cd..6568556 100644 --- a/source/dub/project.d +++ b/source/dub/project.d @@ -53,7 +53,7 @@ json.name = "unknown"; pack = new Package(json, project_path); } else { - pack = package_manager.getOrLoadPackage(project_path); + pack = package_manager.getOrLoadPackage(project_path); } this(package_manager, pack); @@ -103,7 +103,7 @@ /// List of retrieved dependency Packages @property const(Package[]) dependencies() const { return m_dependencies; } - + /// Main package. @property inout(Package) rootPackage() inout { return m_rootPackage; } @@ -115,7 +115,7 @@ int delegate(int delegate(ref const Package)) getTopologicalPackageList(bool children_first = false, in Package root_package = null, string[string] configs = null) const { const(Package) rootpack = root_package ? root_package : m_rootPackage; - + int iterator(int delegate(ref const Package) del) { int ret = 0; @@ -147,7 +147,7 @@ perform_rec(rootpack); return ret; } - + return &iterator; } @@ -256,7 +256,7 @@ @property string[] configurations() const { return m_rootPackage.configurations; } - /// Returns a map with the configuration for all packages in the dependency tree. + /// Returns a map with the configuration for all packages in the dependency tree. string[string] getPackageConfigs(in BuildPlatform platform, string config, bool allow_non_library = true) const { struct Vertex { string pack, config; } @@ -431,7 +431,7 @@ /** * Fills dst with values from this project. * - * dst gets initialized according to the given platform and config. + * dst gets initialized according to the given platform and config. * * Params: * dst = The BuildSettings struct to fill with data. @@ -450,12 +450,12 @@ assert(pkg.name in configs, "Missing configuration for "~pkg.name); logDebug("Gathering build settings for %s (%s)", pkg.name, configs[pkg.name]); - + auto psettings = pkg.getBuildSettings(platform, configs[pkg.name]); if (psettings.targetType != TargetType.none) { if (shallow && pkg !is m_rootPackage) psettings.sourceFiles = null; - processVars(dst, pkg_path, psettings); + processVars(dst, this, pkg, psettings); if (psettings.importPaths.empty) logWarn(`Package %s (configuration "%s") defines no import paths, use {"importPaths": [...]} or the default package directory structure to fix this.`, pkg.name, configs[pkg.name]); if (psettings.mainSourceFile.empty && pkg is m_rootPackage && psettings.targetType == TargetType.executable) @@ -470,9 +470,9 @@ dst.targetPath = psettings.targetPath; dst.targetName = psettings.targetName; if (!psettings.workingDirectory.empty) - dst.workingDirectory = processVars(psettings.workingDirectory, pkg_path, true); + dst.workingDirectory = processVars(psettings.workingDirectory, this, pkg, true); if (psettings.mainSourceFile.length) - dst.mainSourceFile = processVars(psettings.mainSourceFile, pkg_path, true); + dst.mainSourceFile = processVars(psettings.mainSourceFile, this, pkg, true); } } @@ -489,7 +489,7 @@ if (usedefflags) { BuildSettings btsettings; m_rootPackage.addBuildTypeSettings(btsettings, platform, build_type); - processVars(dst, m_rootPackage.path.toNativeString(), btsettings); + processVars(dst, this, m_rootPackage, btsettings); } } @@ -564,7 +564,7 @@ } catch(Exception t) return true; } else return false; } - + private void markUpToDate(string packageId) { logDebug("markUpToDate(%s)", packageId); Json create(ref Json json, string object) { @@ -661,7 +661,7 @@ /// Indicates where a package has been or should be placed to. enum PlacementLocation { - /// Packages retrived with 'local' will be placed in the current folder + /// Packages retrived with 'local' will be placed in the current folder /// using the package name as destination. local, /// Packages with 'userWide' will be placed in a folder accessible by @@ -675,50 +675,51 @@ /// The default placement location of fetched packages. Can be changed by --local or --system. auto defaultPlacementLocation = PlacementLocation.userWide; -void processVars(ref BuildSettings dst, string project_path, BuildSettings settings, bool include_target_settings = false) +void processVars(ref BuildSettings dst, in Project project, in Package pack, BuildSettings settings, bool include_target_settings = false) + { - dst.addDFlags(processVars(project_path, settings.dflags)); - dst.addLFlags(processVars(project_path, settings.lflags)); - dst.addLibs(processVars(project_path, settings.libs)); - dst.addSourceFiles(processVars(project_path, settings.sourceFiles, true)); - dst.addImportFiles(processVars(project_path, settings.importFiles, true)); - dst.addStringImportFiles(processVars(project_path, settings.stringImportFiles, true)); - dst.addCopyFiles(processVars(project_path, settings.copyFiles, true)); - dst.addVersions(processVars(project_path, settings.versions)); - dst.addDebugVersions(processVars(project_path, settings.debugVersions)); - 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)); + dst.addDFlags(processVars(project, pack, settings.dflags)); + dst.addLFlags(processVars(project, pack, settings.lflags)); + dst.addLibs(processVars(project, pack, settings.libs)); + dst.addSourceFiles(processVars(project, pack, settings.sourceFiles, true)); + dst.addImportFiles(processVars(project, pack, settings.importFiles, true)); + dst.addStringImportFiles(processVars(project, pack, settings.stringImportFiles, true)); + dst.addCopyFiles(processVars(project, pack, settings.copyFiles, true)); + dst.addVersions(processVars(project, pack, settings.versions)); + dst.addDebugVersions(processVars(project, pack, settings.debugVersions)); + dst.addImportPaths(processVars(project, pack, settings.importPaths, true)); + dst.addStringImportPaths(processVars(project, pack, settings.stringImportPaths, true)); + dst.addPreGenerateCommands(processVars(project, pack, settings.preGenerateCommands)); + dst.addPostGenerateCommands(processVars(project, pack, settings.postGenerateCommands)); + dst.addPreBuildCommands(processVars(project, pack, settings.preBuildCommands)); + dst.addPostBuildCommands(processVars(project, pack, settings.postBuildCommands)); dst.addRequirements(settings.requirements); dst.addOptions(settings.options); if (include_target_settings) { dst.targetType = settings.targetType; - dst.targetPath = processVars(settings.targetPath, project_path, true); + dst.targetPath = processVars(settings.targetPath, project, pack, true); dst.targetName = settings.targetName; if (!settings.workingDirectory.empty) - dst.workingDirectory = processVars(settings.workingDirectory, project_path, true); + dst.workingDirectory = processVars(settings.workingDirectory, project, pack, true); if (settings.mainSourceFile.length) - dst.mainSourceFile = processVars(settings.mainSourceFile, project_path, true); + dst.mainSourceFile = processVars(settings.mainSourceFile, project, pack, true); } } -private string[] processVars(string project_path, string[] vars, bool are_paths = false) +private string[] processVars(in Project project, in Package pack, string[] vars, bool are_paths = false) { auto ret = appender!(string[])(); - processVars(ret, project_path, vars, are_paths); + processVars(ret, project, pack, vars, are_paths); return ret.data; } -private void processVars(ref Appender!(string[]) dst, string project_path, string[] vars, bool are_paths = false) +private void processVars(ref Appender!(string[]) dst, in Project project, in Package pack, string[] vars, bool are_paths = false) { - foreach (var; vars) dst.put(processVars(var, project_path, are_paths)); + foreach (var; vars) dst.put(processVars(var, project, pack, are_paths)); } -private string processVars(string var, string project_path, bool is_path) +private string processVars(string var, in Project project, in Package pack, bool is_path) { auto idx = std.string.indexOf(var, '$'); if (idx >= 0) { @@ -737,10 +738,7 @@ auto varname = var[0 .. idx2]; var = var[idx2 .. $]; - string env_variable; - if( varname == "PACKAGE_DIR" ) vres.put(project_path); - else if( (env_variable = environment.get(varname)) != null) vres.put(env_variable); - else enforce(false, "Invalid variable: "~varname); + vres.put(getVariable(varname, project, pack)); } idx = std.string.indexOf(var, '$'); } @@ -750,19 +748,35 @@ if (is_path) { auto p = Path(var); if (!p.absolute) { - logDebug("Fixing relative path: %s ~ %s", project_path, p.toNativeString()); - p = Path(project_path) ~ p; - } - return p.toNativeString(); + logDebug("Fixing relative path: %s ~ %s", pack.path.toNativeString(), p.toNativeString()); + return (pack.path ~ p).toNativeString(); + } else return p.toNativeString(); } else return var; } +private string getVariable(string name, in Project project, in Package pack) +{ + if (name == "PACKAGE_DIR") return pack.path.toNativeString(); + if (name == "ROOT_PACKAGE_DIR") return project.rootPackage.path.toNativeString(); + + if (name.endsWith("_PACKAGE_DIR")) { + auto pname = name[0 .. $-12]; + foreach (prj; project.getTopologicalPackageList()) + if (prj.name.toUpper().replace("-", "_") == pname) + return prj.path.toNativeString(); + } + + if (auto envvar = environment.get(name)) return envvar; + + throw new Exception("Invalid variable: "~name); +} + private bool isIdentChar(dchar ch) { return ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '_'; } -string stripDlangSpecialChars(string s) +string stripDlangSpecialChars(string s) { import std.array; import std.uni; @@ -775,7 +789,7 @@ final class SelectedVersions { private struct Selected { Dependency dep; - //Dependency[string] packages; + //Dependency[string] packages; } private { enum FileVersion = 1;