diff --git a/changelog/addcommand.dd b/changelog/addcommand.dd index d3ee0bd..55e9e2e 100644 --- a/changelog/addcommand.dd +++ b/changelog/addcommand.dd @@ -1,11 +1,30 @@ Add Command -The `add` command adds a dependency to the dub.json/dub.sdl file. +The `add` command adds a dependency to the dub.json/dub.sdl recipe file. -Running `dub add vibe-d` is equivalent to manually adding the following line to dub.sdl (X.Y.Z represents the latest version): +Running `dub add vibe-d` queries the latest version for vibe-d from the +registry, then rewrites your recipe file with the new dependency added. + +dub.json: +------ + "dependencies": { + "vibe-d": "~>X.Y.Z" + } +------ + +dub.sdl: +------ dependency "vibe-d" version="~>X.Y.Z" +------ -Or the following in dub.json: -"dependencies": { - "vibe-d": "~>X.Y.Z" -} +It is also possible to add multiple packages at once and explicitly add a +simple $(LINK2 version specification,http://code.dlang.org/package-format?lang=json#version-specs) +for some of them. + +For example the command `dub add vibe-d='~>0.8.2' mir-algorithm=3.1.21` would +add the given 2 dependencies to the recipe file without querying the registry. + +Packages with and without version-specifier can be mixed in a single invocation. + +The can also be used to overwrite existing dependencies of the same name with +different version specifications. diff --git a/source/dub/commandline.d b/source/dub/commandline.d index 7a2a742..0f7e1dc 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -254,8 +254,7 @@ PackageSupplier ps = getRegistryPackageSupplier(urls.front); urls.popFront; if (!urls.empty) - ps = new FallbackPackageSupplier(ps, - urls.map!(u => getRegistryPackageSupplier(u)).array); + ps = new FallbackPackageSupplier(ps ~ urls.map!getRegistryPackageSupplier.array); return ps; }) .array; @@ -309,7 +308,7 @@ args.getopt("skip-registry", &skipRegistry, [ "Sets a mode for skipping the search on certain package registry types:", " none: Search all configured or default registries (default)", - " standard: Don't search the main registry (e.g. "~defaultRegistryURL~")", + " standard: Don't search the main registry (e.g. "~defaultRegistryURLs[0]~")", " configured: Skip all default and user configured registries", " all: Only search registries specified with --registry", ]); @@ -560,17 +559,9 @@ p.copyright = input("Copyright string", copyrightString); while (true) { - auto depname = input("Add dependency (leave empty to skip)", null); - if (!depname.length) break; - try { - auto ver = dub.getLatestVersion(depname); - auto dep = ver.isBranch ? Dependency(ver) : Dependency("~>" ~ ver.toString()); - p.buildSettings.dependencies[depname] = dep; - logInfo("Added dependency %s %s", depname, dep.versionSpec); - } catch (Exception e) { - logError("Could not find package '%s'.", depname); - logDebug("Full error: %s", e.toString().sanitize); - } + auto depspec = input("Add dependency (leave empty to skip)", null); + if (!depspec.length) break; + addDependency(dub, p, depspec); } } @@ -613,7 +604,7 @@ bool m_nodeps; bool m_forceRemove = false; bool m_single; - bool m_filterVersions = true; + bool m_filterVersions = false; } override void prepare(scope CommandArgs args) @@ -1155,12 +1146,13 @@ this() { this.name = "add"; - this.argumentsPattern = ""; + this.argumentsPattern = "[=] []"; this.description = "Adds dependencies to the package file."; this.helpText = [ "Adds as dependencies.", "", - "Running \"dub add \" is the same as adding to the \"dependencies\" section in dub.json/dub.sdl." + "Running \"dub add \" is the same as adding to the \"dependencies\" section in dub.json/dub.sdl.", + "If no version is specified for one of the packages, dub will query the registry for the latest version." ]; } @@ -1173,22 +1165,14 @@ enforceUsage(free_args.length != 0, "Expected one or more arguments."); enforceUsage(app_args.length == 0, "Unexpected application arguments."); - string filetype = existsFile(dub.rootPath ~ "dub.json") ? "json" : "sdl"; - foreach (depname; free_args) { - try { - auto ver = dub.getLatestVersion(depname); - auto dep = ver.isBranch ? Dependency(ver) : Dependency("~>" ~ ver.toString()); - auto pkg = readPackageRecipe(dub.rootPath ~ ("dub." ~ filetype)); + if (!loadCwdPackage(dub, true)) return 1; + auto recipe = dub.project.rootPackage.rawRecipe.clone; - pkg.buildSettings.dependencies[depname] = dep; - writePackageRecipe(dub.rootPath ~ ("dub." ~ filetype), pkg); - - logInfo("Added dependency %s %s", depname, dep.versionSpec); - } catch (Exception e) { - logError("Could not find package '%s'.", depname); - logDebug("Full error: %s", e.toString().sanitize); - } + foreach (depspec; free_args) { + if (!addDependency(dub, recipe, depspec)) + return 1; } + writePackageRecipe(dub.project.rootPackage.recipePath, recipe); return 0; } @@ -2151,3 +2135,27 @@ { logWarn("The '%s' Command was renamed to '%s'. Please update your scripts.", prev, curr); } + +private bool addDependency(Dub dub, ref PackageRecipe recipe, string depspec) +{ + Dependency dep; + // split = + auto parts = depspec.findSplit("="); + auto depname = parts[0]; + if (!parts[1].empty) + dep = Dependency(parts[2]); + else + { + try { + auto ver = dub.getLatestVersion(depname); + dep = ver.isBranch ? Dependency(ver) : Dependency("~>" ~ ver.toString()); + } catch (Exception e) { + logError("Could not find package '%s'.", depname); + logDebug("Full error: %s", e.toString().sanitize); + return false; + } + } + recipe.buildSettings.dependencies[depname] = dep; + logInfo("Adding dependency %s %s", depname, dep.versionSpec); + return true; +} diff --git a/source/dub/dub.d b/source/dub/dub.d index 92ca4c0..dff7a97 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -59,13 +59,12 @@ registerCompiler(new LDCCompiler); } -/// The URL to the official package registry. -enum defaultRegistryURL = "https://code.dlang.org/"; -enum fallbackRegistryURLs = [ - // fallback in case of HTTPS problems - "http://code.dlang.org/", +deprecated("use defaultRegistryURLs") enum defaultRegistryURL = defaultRegistryURLs[0]; + +/// The URL to the official package registry and it's default fallback registries. +enum defaultRegistryURLs = [ + "https://code.dlang.org/", "https://code-mirror.dlang.io/", - "https://code-mirror2.dlang.io/", "https://dub-registry.herokuapp.com/", ]; @@ -74,17 +73,12 @@ This will contain a single package supplier that points to the official package registry. - See_Also: `defaultRegistryURL` + See_Also: `defaultRegistryURLs` */ PackageSupplier[] defaultPackageSuppliers() { - logDiagnostic("Using dub registry url '%s'", defaultRegistryURL); - return [ - new FallbackPackageSupplier( - new RegistryPackageSupplier(URL(defaultRegistryURL)), - fallbackRegistryURLs.map!(x => cast(PackageSupplier) new RegistryPackageSupplier(URL(x))).array - ) - ]; + logDiagnostic("Using dub registry url '%s'", defaultRegistryURLs[0]); + return [new FallbackPackageSupplier(defaultRegistryURLs.map!getRegistryPackageSupplier.array)]; } /** Returns a registry package supplier according to protocol. diff --git a/source/dub/packagesuppliers/fallback.d b/source/dub/packagesuppliers/fallback.d index 7a691a9..39a61e6 100644 --- a/source/dub/packagesuppliers/fallback.d +++ b/source/dub/packagesuppliers/fallback.d @@ -5,20 +5,26 @@ package abstract class AbstractFallbackPackageSupplier : PackageSupplier { - protected PackageSupplier m_default; - protected PackageSupplier[] m_fallbacks; + protected import core.time : minutes; + protected import std.datetime : Clock, SysTime; - this(PackageSupplier default_, PackageSupplier[] fallbacks) + static struct Pair { PackageSupplier ps; SysTime failTime; } + protected Pair[] m_suppliers; + + this(PackageSupplier[] suppliers) { - m_default = default_; - m_fallbacks = fallbacks; + assert(suppliers.length); + m_suppliers.length = suppliers.length; + foreach (i, ps; suppliers) + m_suppliers[i].ps = ps; } override @property string description() { import std.algorithm.iteration : map; import std.format : format; - return format("%s (fallback %s)", m_default.description, m_fallbacks.map!(x => x.description)); + return format("%s (fallbacks %-(%s, %))", m_suppliers[0].ps.description, + m_suppliers[1 .. $].map!(pair => pair.ps.description)); } // Workaround https://issues.dlang.org/show_bug.cgi?id=2525 @@ -40,19 +46,35 @@ { import std.format : format; enum fallback = q{ - import std.range : back, dropBackOne; - import dub.internal.vibecompat.core.log : logDebug; - scope (failure) + import dub.internal.vibecompat.core.log : logDiagnostic; + + Exception firstEx; + try + return m_suppliers[0].ps.%1$s(args); + catch (Exception e) { - foreach (m_fallback; m_fallbacks.dropBackOne) - { - try - return m_fallback.%1$s(args); - catch(Exception) - logDebug("Package supplier %s failed. Trying next fallback.", m_fallback); - } - return m_fallbacks.back.%1$s(args); + logDiagnostic("Package supplier %%s failed with '%%s', trying fallbacks.", + m_suppliers[0].ps.description, e.msg); + firstEx = e; } - return m_default.%1$s(args); + + immutable now = Clock.currTime; + foreach (ref pair; m_suppliers[1 .. $]) + { + if (pair.failTime > now - 10.minutes) + continue; + try + { + scope (success) logDiagnostic("Fallback %%s succeeded", pair.ps.description); + return pair.ps.%1$s(args); + } + catch (Exception e) + { + pair.failTime = now; + logDiagnostic("Fallback package supplier %%s failed with '%%s'.", + pair.ps.description, e.msg); + } + } + throw firstEx; }.format(__traits(identifier, func)); } diff --git a/source/dub/version_.d b/source/dub/version_.d index 21e9f46..2acd960 100644 --- a/source/dub/version_.d +++ b/source/dub/version_.d @@ -1,2 +1,2 @@ module dub.version_; -enum dubVersion = "v1.13.0-beta.1"; +enum dubVersion = "v1.13.0-beta.2"; diff --git a/test/4-describe-data-2-dmd.sh b/test/4-describe-data-2-dmd.sh index 5120f1b..8d7d0ae 100755 --- a/test/4-describe-data-2-dmd.sh +++ b/test/4-describe-data-2-dmd.sh @@ -17,7 +17,7 @@ trap cleanup EXIT -if ! $DUB describe --compiler=${DC} \ +if ! $DUB describe --compiler=$DC --filter-versions \ --data=main-source-file \ --data=dflags,lflags \ --data=libs,linker-files \ diff --git a/test/feat663-search.sh b/test/feat663-search.sh index 435c8d8..21cea3e 100755 --- a/test/feat663-search.sh +++ b/test/feat663-search.sh @@ -7,9 +7,9 @@ if ${DUB} search nonexistent123456789package 2>/dev/null; then die $LINENO '`dub search nonexistent123456789package` succeeded' fi -if ! OUTPUT=$(${DUB} search dub -v 2>&1); then - die $LINENO '`dub search dub` failed' "$OUTPUT" +if ! OUTPUT=$(${DUB} search '"dub-registry"' -v 2>&1); then + die $LINENO '`dub search "dub-registry"` failed' "$OUTPUT" fi -if ! grep -q '^dub (.*)\s'<<<"$OUTPUT"; then - die $LINENO '`grep -q '"'"'^dub (.*)\s'"'"'` failed' "$OUTPUT" +if ! grep -q '^dub-registry (.*)\s'<<<"$OUTPUT"; then + die $LINENO '`grep -q '"'"'^dub-registry (.*)\s'"'"'` failed' "$OUTPUT" fi diff --git a/test/issue1574-addcommand.sh b/test/issue1574-addcommand.sh index c2fb1c7..002acb1 100755 --- a/test/issue1574-addcommand.sh +++ b/test/issue1574-addcommand.sh @@ -19,14 +19,18 @@ trap cleanup EXIT -$DUB init -n $tempDir +$DUB init --non-interactive --format=json $tempDir cd $tempDir echo "import gitcompatibledubpackage.subdir.file; void main(){}" > source/app.d - $DUB add gitcompatibledubpackage --skip-registry=all --registry=http://localhost:$PORT - -#if dub fails to compile, that means that the "import mir.math.common" did not work -if ! $DUB build; then - die "Add command failed" +grep -q '"gitcompatibledubpackage"\s*:\s*"~>1\.0\.4"' dub.json +$DUB add gitcompatibledubpackage=1.0.2 non-existing-issue1574-pkg='~>9.8.7' --skip-registry=all +grep -q '"gitcompatibledubpackage"\s*:\s*"1\.0\.2"' dub.json +grep -q '"non-existing-issue1574-pkg"\s*:\s*"~>9\.8\.7"' dub.json +if $DUB add foo=1.2.3 gitcompatibledubpackage='~>a.b.c' --skip-registry=all; then + die $LINENO 'Adding non-semver spec should error' +fi +if grep -q '"foo"' dub.json; then + die $LINENO 'Failing add command should not write recipe file' fi