diff --git a/changelog/toolchain_requirements.dd b/changelog/toolchain_requirements.dd index b72fb79..d27f8bb 100644 --- a/changelog/toolchain_requirements.dd +++ b/changelog/toolchain_requirements.dd @@ -8,7 +8,7 @@ { "toolchainRequirements": { "dub": "~>1.10", - "frontend": ">=2.068|<2.083" + "frontend": ">=2.068 <2.083" } } --- @@ -27,9 +27,11 @@ $(LI gdc) ) -Each can be assigned to one or more version specifications, separated by `|`. -For compilers, instead of a version specification, the keyword `no` can also be -used to indicate that the compiler should not be used for this package. +Each can contain a +$(LINK2 https://dub.pm/package-format-sdl.html#version-specs, version specification), +where DMD-like versions are supported in addition to SemVer versions. For +compilers, instead of a version specification, the keyword `no` can also be used +to indicate that the compiler should not be used for this package. Example scenario:$(BR) Package that needs DUB>=1.12, and that will only build with LDC>=1.10: diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index b087cd8..b6a96cc 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -8,6 +8,7 @@ module dub.compilers.compiler; public import dub.compilers.buildsettings; +public import dub.dependency : Dependency; public import dub.platform : BuildPlatform, matchesSpecification; import dub.internal.vibecompat.core.file; @@ -91,46 +92,6 @@ /// Convert linker flags to compiler format string[] lflagsToDFlags(in string[] lflags) const; - /// Get the dependency requirement string for this compiler - string toolchainRequirementString(const ref ToolchainRequirements tr); - - /// Check whether the compiler meet the compiler requirement specified - /// in the recipe. - bool checkCompilerRequirement(const ref BuildPlatform platform, const ref ToolchainRequirements tr); - - /// Check if the compiler is supported by the recipe - final bool checkCompilerSupported(const ref ToolchainRequirements tr) - { - const str = toolchainRequirementString(tr); - return str != ToolchainRequirements.noKwd; - } - - /// Check whether the compiler meet the frontend requirement specified - /// in the recipe. - final bool checkFrontendRequirement(const ref BuildPlatform platform, const ref ToolchainRequirements tr) - { - import std.typecons : Yes; - - return checkRequirement(tr.frontend, platform.frontendVersionString, Yes.dmdVer); - } - - /// Check that a particular tool version matches with a given requirement - final bool checkRequirement(const string requirement, const string toolVer, const Flag!"dmdVer" dmdVer) - { - import dub.compilers.utils : dmdLikeVersionToSemverLike; - import dub.dependency : Dependency, Version; - import std.algorithm : all, map, splitter; - - if (!requirement.length) return true; // no requirement - - const ver = Version(dmdVer ? dmdLikeVersionToSemverLike(toolVer) : toolVer); - - return requirement - .splitter('|') - .map!(r => Dependency(dmdVer ? dmdLikeVersionToSemverLike(r) : r)) - .all!(r => r.matches(ver)); - } - /** Runs a tool and provides common boilerplate code. This method should be used by `Compiler` implementations to invoke the diff --git a/source/dub/compilers/dmd.d b/source/dub/compilers/dmd.d index d362413..ac0a459 100644 --- a/source/dub/compilers/dmd.d +++ b/source/dub/compilers/dmd.d @@ -265,18 +265,6 @@ return lflags.map!(f => "-L"~f)().array(); } - string toolchainRequirementString(const ref ToolchainRequirements tr) - { - return tr.dmd; - } - - bool checkCompilerRequirement(const ref BuildPlatform platform, const ref ToolchainRequirements tr) - { - import std.typecons : Yes; - - return checkRequirement(tr.dmd, platform.compilerVersion, Yes.dmdVer); - } - private auto escapeArgs(in string[] args) { return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s); diff --git a/source/dub/compilers/gdc.d b/source/dub/compilers/gdc.d index dc4d118..53225e3 100644 --- a/source/dub/compilers/gdc.d +++ b/source/dub/compilers/gdc.d @@ -256,18 +256,6 @@ return dflags; } - - final string toolchainRequirementString(const ref ToolchainRequirements tr) - { - return tr.gdc; - } - - final bool checkCompilerRequirement(const ref BuildPlatform platform, const ref ToolchainRequirements tr) - { - import std.typecons : No; - - return checkRequirement(tr.gdc, platform.compilerVersion, No.dmdVer); - } } private string extractTarget(const string[] args) { auto i = args.countUntil("-o"); return i >= 0 ? args[i+1] : null; } diff --git a/source/dub/compilers/ldc.d b/source/dub/compilers/ldc.d index ae3ed22..4522825 100644 --- a/source/dub/compilers/ldc.d +++ b/source/dub/compilers/ldc.d @@ -233,18 +233,6 @@ return lflags.map!(s => "-L="~s)().array(); } - final string toolchainRequirementString(const ref ToolchainRequirements tr) - { - return tr.ldc; - } - - final bool checkCompilerRequirement(const ref BuildPlatform platform, const ref ToolchainRequirements tr) - { - import std.typecons : No; - - return checkRequirement(tr.ldc, platform.compilerVersion, No.dmdVer); - } - private auto escapeArgs(in string[] args) { return args.map!(s => s.canFind(' ') ? "\""~s~"\"" : s); diff --git a/source/dub/compilers/utils.d b/source/dub/compilers/utils.d index fc60a35..03b3c33 100644 --- a/source/dub/compilers/utils.d +++ b/source/dub/compilers/utils.d @@ -247,7 +247,7 @@ Returns: A Semver compliant string */ -string dmdLikeVersionToSemverLike(string ver) +package(dub) string dmdLikeVersionToSemverLike(string ver) { import std.algorithm : countUntil, joiner, map, skipOver, splitter; import std.array : join, split; diff --git a/source/dub/dependency.d b/source/dub/dependency.d index cd6c01d..da1b552 100644 --- a/source/dub/dependency.d +++ b/source/dub/dependency.d @@ -138,6 +138,8 @@ */ @property void versionSpec(string ves) { + static import std.string; + enforce(ves.length > 0); string orig = ves; @@ -200,6 +202,8 @@ /// ditto @property string versionSpec() const { + static import std.string; + string r; if (this == invalid) return "invalid"; diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index c88535d..b3b239c 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -50,47 +50,13 @@ void checkPkgRequirements(const(Package) pkg) { - import std.format : format; - const tr = pkg.recipe.toolchainRequirements; - - enforce ( - dcl.checkCompilerSupported(tr), - format( - "Installed %s-%s is not supported by %s. Supported compiler(s):\n%s", - dcl.name, settings.platform.compilerVersion, pkg.name, - tr.supportedCompilers.map!((string[2] cs) { - auto str = " - " ~ cs[0]; - if (cs[1].length) str ~= ": "~cs[1]; - return str; - }).join("\n") - ) - ); - - enforce( - dcl.checkCompilerRequirement(settings.platform, tr), - format( - "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~ - "Please consider upgrading your installation.", - dcl.name, settings.platform.compilerVersion, - pkg.name, dcl.name, dcl.toolchainRequirementString( tr ) - ) - ); - enforce( - dcl.checkFrontendRequirement(settings.platform, tr), - format( - "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~ - "Please consider upgrading your installation.", - dcl.name, settings.platform.compilerVersion, - settings.platform.frontendVersionString, pkg.name, tr.frontend - ) - ); + tr.checkPlatform(settings.platform, pkg.name); } checkPkgRequirements(m_project.rootPackage); - foreach (pkg; m_project.dependencies) { + foreach (pkg; m_project.dependencies) checkPkgRequirements(pkg); - } auto root_ti = targets[m_project.rootPackage.name]; diff --git a/source/dub/package_.d b/source/dub/package_.d index 1c1858e..88e3765 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -551,15 +551,13 @@ // Left as package until the final API for this has been found package auto getAllDependenciesRange() const { - return this.recipe.buildSettings.dependencies.byKeyValue - .map!(bs => PackageDependency(bs.key, bs.value)) - .chain( - this.recipe.configurations - .map!(c => c.buildSettings.dependencies.byKeyValue - .map!(bs => PackageDependency(bs.key, bs.value)) - ) - .joiner() - ); + return + chain( + only(this.recipe.buildSettings.dependencies.byKeyValue), + this.recipe.configurations.map!(c => c.buildSettings.dependencies.byKeyValue) + ) + .joiner() + .map!(d => PackageDependency(d.key, d.value)); } @@ -643,23 +641,21 @@ import dub.version_ : dubVersion; import std.exception : enforce; - if (m_info.toolchainRequirements.dub.length) { - const dep = Dependency(m_info.toolchainRequirements.dub); + const dep = m_info.toolchainRequirements.dub; - static assert(dubVersion.length); - static if (dubVersion[0] == 'v') { - enum dv = dubVersion[1 .. $]; - } - else { - enum dv = dubVersion; - } - static assert(isValidVersion(dv)); - - enforce(dep.matches(dv), - "dub-"~dv~" does not comply with toolchainRequirements.dub " ~ - "specification: "~m_info.toolchainRequirements.dub~ - "\nPlease consider upgrading your DUB installation"); + static assert(dubVersion.length); + static if (dubVersion[0] == 'v') { + enum dv = dubVersion[1 .. $]; } + else { + enum dv = dubVersion; + } + static assert(isValidVersion(dv)); + + enforce(dep.matches(dv), + "dub-" ~ dv ~ " does not comply with toolchainRequirements.dub " + ~ "specification: " ~ m_info.toolchainRequirements.dub.toString() + ~ "\nPlease consider upgrading your DUB installation"); } private void fillWithDefaults() diff --git a/source/dub/recipe/json.d b/source/dub/recipe/json.d index c1fd9a1..354a1e6 100644 --- a/source/dub/recipe/json.d +++ b/source/dub/recipe/json.d @@ -106,7 +106,7 @@ types[name] = settings.toJson(); ret["buildTypes"] = types; } - if (recipe.hasToolchainRequirements) { + if (!recipe.toolchainRequirements.empty) { ret["toolchainRequirements"] = recipe.toolchainRequirements.toJson(); } if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson(); @@ -300,46 +300,17 @@ private void parseJson(ref ToolchainRequirements tr, Json json) { - foreach (string name, value; json) { - switch (name) { - case "dub": - tr.dub = value.get!string(); - break; - case "frontend": - tr.frontend = value.get!string(); - break; - case "dmd": - tr.dmd = value.get!string(); - break; - case "ldc": - tr.ldc = value.get!string(); - break; - case "gdc": - tr.gdc = value.get!string(); - break; - default: - break; - } - } + foreach (string name, value; json) + tr.addRequirement(name, value.get!string); } private Json toJson(in ref ToolchainRequirements tr) { auto ret = Json.emptyObject; - if (tr.dub.length) { - ret["dub"] = serializeToJson(tr.dub); - } - if (tr.frontend.length) { - ret["frontend"] = serializeToJson(tr.frontend); - } - if (tr.dmd.length) { - ret["dmd"] = serializeToJson(tr.dmd); - } - if (tr.ldc.length) { - ret["ldc"] = serializeToJson(tr.ldc); - } - if (tr.gdc.length) { - ret["gdc"] = serializeToJson(tr.gdc); - } + if (tr.dub != Dependency.any) ret["dub"] = serializeToJson(tr.dub); + if (tr.frontend != Dependency.any) ret["frontend"] = serializeToJson(tr.frontend); + if (tr.dmd != Dependency.any) ret["dmd"] = serializeToJson(tr.dmd); + if (tr.ldc != Dependency.any) ret["ldc"] = serializeToJson(tr.ldc); + if (tr.gdc != Dependency.any) ret["gdc"] = serializeToJson(tr.gdc); return ret; } diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d index f1d3c3e..5a73030 100644 --- a/source/dub/recipe/packagerecipe.d +++ b/source/dub/recipe/packagerecipe.d @@ -98,17 +98,6 @@ throw new Exception("Unknown configuration: "~name); } - bool hasToolchainRequirements() const - { - import std.algorithm : any; - import std.range : only; - - with (toolchainRequirements) { - return only(dub, frontend, dmd, ldc, gdc) - .any!(r => r.length != 0); - } - } - /** Clones the package recipe recursively. */ PackageRecipe clone() const { return .clone(this); } @@ -123,28 +112,39 @@ /// Describes minimal toolchain requirements struct ToolchainRequirements { - /// DUB version requirement - string dub; - /// D front-end version requirement - string frontend; - /// DMD version requirement - string dmd; - /// LDC version requirement - string ldc; - /// GDC version requirement - string gdc; + import std.typecons : Tuple, tuple; - package(dub) enum noKwd = "no"; - /// Get the list of supported compilers. - /// Returns: array of couples of compiler name and compiler requirement - @property string[2][] supportedCompilers() const + /// DUB version requirement + Dependency dub = Dependency.any; + /// D front-end version requirement + Dependency frontend = Dependency.any; + /// DMD version requirement + Dependency dmd = Dependency.any; + /// LDC version requirement + Dependency ldc = Dependency.any; + /// GDC version requirement + Dependency gdc = Dependency.any; + + /** Get the list of supported compilers. + + Returns: + An array of couples of compiler name and compiler requirement + */ + @property Tuple!(string, Dependency)[] supportedCompilers() const { - string[2][] res; - if (dmd != noKwd) res ~= [ "dmd", dmd ]; - if (ldc != noKwd) res ~= [ "ldc", ldc ]; - if (gdc != noKwd) res ~= [ "gdc", gdc ]; + Tuple!(string, Dependency)[] res; + if (dmd != Dependency.invalid) res ~= Tuple!(string, Dependency)("dmd", dmd); + if (ldc != Dependency.invalid) res ~= Tuple!(string, Dependency)("ldc", ldc); + if (gdc != Dependency.invalid) res ~= Tuple!(string, Dependency)("gdc", gdc); return res; } + + bool empty() + const { + import std.algorithm.searching : all; + return only(dub, frontend, dmd, ldc, gdc) + .all!(r => r == Dependency.any); + } } @@ -330,6 +330,103 @@ } } +package(dub) void checkPlatform(in ref ToolchainRequirements tr, BuildPlatform platform, string package_name) +{ + import dub.compilers.utils : dmdLikeVersionToSemverLike; + import std.algorithm.iteration : map; + import std.format : format; + + string compilerver; + Dependency compilerspec; + + switch (platform.compiler) { + default: + compilerspec = Dependency.any; + compilerver = "0.0.0"; + break; + case "dmd": + compilerspec = tr.dmd; + compilerver = platform.compilerVersion.length + ? dmdLikeVersionToSemverLike(platform.compilerVersion) + : "0.0.0"; + break; + case "ldc": + compilerspec = tr.ldc; + compilerver = platform.compilerVersion; + if (!compilerver.length) compilerver = "0.0.0"; + break; + case "gdc": + compilerspec = tr.gdc; + compilerver = platform.compilerVersion; + if (!compilerver.length) compilerver = "0.0.0"; + break; + } + + enforce(compilerspec != Dependency.invalid, + format( + "Installed %s %s is not supported by %s. Supported compiler(s):\n%s", + platform.compiler, platform.compilerVersion, package_name, + tr.supportedCompilers.map!((cs) { + auto str = " - " ~ cs[0]; + if (cs[1] != Dependency.any) str ~= ": " ~ cs[1].toString(); + return str; + }).join("\n") + ) + ); + + enforce(compilerspec.matches(compilerver), + format( + "Installed %s-%s does not comply with %s compiler requirement: %s %s\n" ~ + "Please consider upgrading your installation.", + platform.compiler, platform.compilerVersion, + package_name, platform.compiler, compilerspec + ) + ); + + enforce(tr.frontend.matches(dmdLikeVersionToSemverLike(platform.frontendVersionString)), + format( + "Installed %s-%s with frontend %s does not comply with %s frontend requirement: %s\n" ~ + "Please consider upgrading your installation.", + platform.compiler, platform.compilerVersion, + platform.frontendVersionString, package_name, tr.frontend + ) + ); +} + +package bool addRequirement(ref ToolchainRequirements req, string name, string value) +{ + switch (name) { + default: return false; + case "dub": req.dub = parseDependency(value); break; + case "frontend": req.frontend = parseDMDDependency(value); break; + case "ldc": req.ldc = parseDependency(value); break; + case "gdc": req.gdc = parseDependency(value); break; + case "dmd": req.dmd = parseDMDDependency(value); break; + } + return true; +} + +private static Dependency parseDependency(string dep) +{ + if (dep == "no") return Dependency.invalid; + return Dependency(dep); +} + +private static Dependency parseDMDDependency(string dep) +{ + import dub.compilers.utils : dmdLikeVersionToSemverLike; + import dub.dependency : Dependency; + import std.algorithm : map, splitter; + import std.array : join; + + if (dep == "no") return Dependency.invalid; + return dep + .splitter(' ') + .map!(r => dmdLikeVersionToSemverLike(r)) + .join(' ') + .Dependency; +} + private T clone(T)(ref const(T) val) { import std.traits : isSomeString, isDynamicArray, isAssociativeArray, isBasicType, ValueType; diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index d3aaf14..09e8b8d 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -104,7 +104,7 @@ t.add(settings.toSDL()); ret.add(t); } - if (recipe.hasToolchainRequirements) { + if (!recipe.toolchainRequirements.empty) { ret.add(toSDL(recipe.toolchainRequirements)); } if (recipe.ddoxFilterArgs.length) @@ -280,47 +280,18 @@ private void parseToolchainRequirements(ref ToolchainRequirements tr, Tag tag) { - foreach (attr; tag.attributes) { - switch (attr.name) { - case "dub": - tr.dub = attr.value.get!string(); - break; - case "frontend": - tr.frontend = attr.value.get!string(); - break; - case "dmd": - tr.dmd = attr.value.get!string(); - break; - case "ldc": - tr.ldc = attr.value.get!string(); - break; - case "gdc": - tr.gdc = attr.value.get!string(); - break; - default: - break; - } - } + foreach (attr; tag.attributes) + tr.addRequirement(attr.name, attr.value.get!string); } private Tag toSDL(const ref ToolchainRequirements tr) { Attribute[] attrs; - if (tr.dub.length) { - attrs ~= new Attribute("dub", Value(tr.dub)); - } - if (tr.frontend.length) { - attrs ~= new Attribute("frontend", Value(tr.frontend)); - } - if (tr.dmd.length) { - attrs ~= new Attribute("dmd", Value(tr.dmd)); - } - if (tr.ldc.length) { - attrs ~= new Attribute("ldc", Value(tr.ldc)); - } - if (tr.gdc.length) { - attrs ~= new Attribute("gdc", Value(tr.gdc)); - } + if (tr.dub != Dependency.any) attrs ~= new Attribute("dub", Value(tr.dub.toString())); + if (tr.frontend != Dependency.any) attrs ~= new Attribute("frontend", Value(tr.frontend.toString())); + if (tr.dmd != Dependency.any) attrs ~= new Attribute("dmd", Value(tr.dmd.toString())); + if (tr.ldc != Dependency.any) attrs ~= new Attribute("ldc", Value(tr.ldc.toString())); + if (tr.gdc != Dependency.any) attrs ~= new Attribute("gdc", Value(tr.gdc.toString())); return new Tag(null, "toolchainRequirements", null, attrs); } @@ -510,11 +481,11 @@ assert(rec.buildTypes.length == 2); assert(rec.buildTypes["debug"].dflags == ["": ["-g", "-debug"]]); assert(rec.buildTypes["release"].dflags == ["": ["-release", "-O"]]); - assert(rec.toolchainRequirements.dub == "~>1.11.0"); - assert(rec.toolchainRequirements.frontend is null); - assert(rec.toolchainRequirements.dmd == "~>2.082"); - assert(rec.toolchainRequirements.ldc is null); - assert(rec.toolchainRequirements.gdc is null); + assert(rec.toolchainRequirements.dub == Dependency("~>1.11.0")); + assert(rec.toolchainRequirements.frontend == Dependency.any); + assert(rec.toolchainRequirements.dmd == Dependency("~>2.82.0")); + assert(rec.toolchainRequirements.ldc == Dependency.any); + assert(rec.toolchainRequirements.gdc == Dependency.any); assert(rec.ddoxFilterArgs == ["-arg1", "-arg2", "-arg3"], rec.ddoxFilterArgs.to!string); assert(rec.ddoxTool == "ddoxtool"); assert(rec.buildSettings.dependencies.length == 2); diff --git a/test/issue1531-toolchain-requirements.sh b/test/issue1531-toolchain-requirements.sh index 77bf5f4..c384198 100755 --- a/test/issue1531-toolchain-requirements.sh +++ b/test/issue1531-toolchain-requirements.sh @@ -91,7 +91,7 @@ test_cl_req_fail ">$DC_VER" test_cl_req_pass "<=$DC_VER" test_cl_req_fail "<$DC_VER" -test_cl_req_pass ">=$DC_VER|<$(($DC_VER_MAJ + 1))$DC_VER_REM" +test_cl_req_pass ">=$DC_VER <$(($DC_VER_MAJ + 1))$DC_VER_REM" test_cl_req_pass "~>$DC_VER" test_cl_req_fail "~>$(($DC_VER_MAJ + 1))$DC_VER_REM" test_cl_req_fail no