diff --git a/changelog/toolchain_requirements.dd b/changelog/toolchain_requirements.dd new file mode 100644 index 0000000..b72fb79 --- /dev/null +++ b/changelog/toolchain_requirements.dd @@ -0,0 +1,52 @@ +`toolchainRequirements` recipe entry + +DUB now supports a new entry in `dub.json` or `dub.sdl` to restrict the versions of +DUB and of compilers supported by a package. + +dub.json: +--- +{ + "toolchainRequirements": { + "dub": "~>1.10", + "frontend": ">=2.068|<2.083" + } +} +--- + +dub.sdl: +--- +toolchainRequirements dub="~>1.10" frontend=">=2.068|<2.083" +--- + +Supported entries in `toolchainRequirements` are: +$(UL + $(LI dub) + $(LI frontend) + $(LI dmd) + $(LI ldc) + $(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. + +Example scenario:$(BR) +Package that needs DUB>=1.12, and that will only build with LDC>=1.10: + +dub.json: +--- +{ + "toolchainRequirements": { + "dub": ">=1.12", + "dmd": "no", + "gdc": "no", + "ldc": ">=1.10" + } +} +--- + +dub.sdl: +--- +toolchainRequirements dub=">=1.12" dmd="no" gdc="no" ldc=">=1.10" +--- diff --git a/source/dub/compilers/compiler.d b/source/dub/compilers/compiler.d index 2670bcf..b087cd8 100644 --- a/source/dub/compilers/compiler.d +++ b/source/dub/compilers/compiler.d @@ -14,12 +14,14 @@ import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.data.json; import dub.internal.vibecompat.inet.path; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; import std.conv; import std.exception; import std.process; +import std.typecons : Flag; /** Returns a compiler handler for a given binary name. @@ -89,6 +91,46 @@ /// 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 @@ -115,11 +157,24 @@ enforce(status == 0, format("%s failed with exit code %s.", args[0], status)); } - /// Compiles platform probe file with the specified compiler and parses its output. - protected final BuildPlatform probePlatform(string compiler_binary, string[] args, string arch_override) + /** Compiles platform probe file with the specified compiler and parses its output. + Params: + compiler_binary = binary to invoke compiler with + args = arguments for the probe compilation + arch_override = special handler for x86_mscoff + versionRes = array of regular expressions to scan the output + and find the compiler version. For each, the + version must be in capture index 1. The output + is scanned in multi-line mode (i.e. ^ will match any line start) + */ + protected final BuildPlatform probePlatform(string compiler_binary, string[] args, + string arch_override, string[] versionRes) { + import dub.compilers.utils : generatePlatformProbeFile, readPlatformJsonProbe; + import std.algorithm : filter, map; + import std.range : takeOne; + import std.regex : matchFirst, regex; import std.string : format; - import dub.compilers.utils : generatePlatformProbeFile, readPlatformProbe; auto fil = generatePlatformProbeFile(); @@ -127,12 +182,25 @@ enforce(result.status == 0, format("Failed to invoke the compiler %s to determine the build platform: %s", compiler_binary, result.output)); - auto build_platform = readPlatformProbe(result.output); + auto build_platform = readPlatformJsonProbe(result.output); build_platform.compilerBinary = compiler_binary; if (build_platform.compiler != this.name) { - logWarn(`The determined compiler type "%s" doesn't match the expected type "%s". This will probably result in build errors.`, - build_platform.compiler, this.name); + logWarn(`The determined compiler type "%s" doesn't match the expected type "%s". `~ + `This will probably result in build errors.`, build_platform.compiler, this.name); + } + + auto ver = versionRes + .map!(re => matchFirst(result.output, regex(re, "m"))) + .filter!(c => c.length > 1) + .map!(c => c[1]) + .takeOne(); + if (ver.empty) { + logWarn(`Could not probe the compiler version for "%s". ` ~ + `Toolchain requirements might be ineffective`, build_platform.compiler); + } + else { + build_platform.compilerVersion = ver.front; } // Hack: see #1059 diff --git a/source/dub/compilers/dmd.d b/source/dub/compilers/dmd.d index be04a79..d362413 100644 --- a/source/dub/compilers/dmd.d +++ b/source/dub/compilers/dmd.d @@ -12,6 +12,7 @@ import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; @@ -54,6 +55,32 @@ @property string name() const { return "dmd"; } + enum dmdVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + + unittest { + import std.regex : matchFirst, regex; + auto probe = ` +binary dmd +version v2.082.0 +config /etc/dmd.conf +`; + auto re = regex(dmdVersionRe, "m"); + auto c = matchFirst(probe, re); + assert(c && c.length > 1 && c[1] == "2.082.0"); + } + + unittest { + import std.regex : matchFirst, regex; + auto probe = ` +binary dmd +version v2.084.0-beta.1 +config /etc/dmd.conf +`; + auto re = regex(dmdVersionRe, "m"); + auto c = matchFirst(probe, re); + assert(c && c.length > 1 && c[1] == "2.084.0-beta.1"); + } + BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) { string[] arch_flags; @@ -66,7 +93,12 @@ } settings.addDFlags(arch_flags); - return probePlatform(compiler_binary, arch_flags ~ ["-quiet", "-c", "-o-"], arch_override); + return probePlatform( + compiler_binary, + arch_flags ~ ["-quiet", "-c", "-o-", "-v"], + arch_override, + [ dmdVersionRe ] + ); } void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const @@ -233,6 +265,18 @@ 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 3a79b7e..dc4d118 100644 --- a/source/dub/compilers/gdc.d +++ b/source/dub/compilers/gdc.d @@ -12,6 +12,7 @@ import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; @@ -53,6 +54,33 @@ @property string name() const { return "gdc"; } + enum gdcVersionRe1 = `GDC\s+(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + enum gdcVersionRe2 = `^GNU D \(.*?\) version (\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + enum gdcVersionRe3 = `^gcc version (\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + + unittest { + import std.algorithm : equal, map; + import std.range : only; + import std.regex : matchFirst, regex; + auto probe = ` +Target: x86_64-pc-linux-gnu +Thread model: posix +gcc version 8.2.1 20180831 (GDC 8.2.1 based on D v2.068.2 built with ISL 0.20 for Arch Linux) +GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +GNU D (GDC 8.2.1 based on D v2.068.2 built with ISL 0.20 for Arch Linux) version 8.2.1 20180831 (x86_64-pc-linux-gnu) +GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 +binary /usr/lib/gcc/x86_64-pc-linux-gnu/8.2.1/cc1d +version v2.068.2 +predefs GNU D_Version2 LittleEndian GNU_DWARF2_Exceptions GNU_StackGrowsDown GNU_InlineAsm D_LP64 D_PIC assert all X86_64 D_HardFloat Posix linux CRuntime_Glibc +`; + auto vers = only(gdcVersionRe1, gdcVersionRe2, gdcVersionRe3) + .map!(re => matchFirst(probe, regex(re, "m"))) + .filter!(c => c && c.length > 1) + .map!(c => c[1]); + + assert(vers.equal([ "8.2.1", "8.2.1", "8.2.1" ])); + } + BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) { string[] arch_flags; @@ -66,9 +94,12 @@ } settings.addDFlags(arch_flags); - return probePlatform(compiler_binary, - arch_flags ~ ["-S"], - arch_override); + return probePlatform( + compiler_binary, + arch_flags ~ ["-S", "-v"], + arch_override, + [ gdcVersionRe1, gdcVersionRe2, gdcVersionRe3 ] + ); } void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const @@ -225,6 +256,18 @@ 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 776bf4b..ae3ed22 100644 --- a/source/dub/compilers/ldc.d +++ b/source/dub/compilers/ldc.d @@ -12,6 +12,7 @@ import dub.internal.utils; import dub.internal.vibecompat.core.log; import dub.internal.vibecompat.inet.path; +import dub.recipe.packagerecipe : ToolchainRequirements; import std.algorithm; import std.array; @@ -54,6 +55,20 @@ @property string name() const { return "ldc"; } + enum ldcVersionRe = `^version\s+v?(\d+\.\d+\.\d+[A-Za-z0-9.+-]*)`; + + unittest { + import std.regex : matchFirst, regex; + auto probe = ` +binary /usr/bin/ldc2 +version 1.11.0 (DMD v2.081.2, LLVM 6.0.1) +config /etc/ldc2.conf (x86_64-pc-linux-gnu) +`; + auto re = regex(ldcVersionRe, "m"); + auto c = matchFirst(probe, re); + assert(c && c.length > 1 && c[1] == "1.11.0"); + } + BuildPlatform determinePlatform(ref BuildSettings settings, string compiler_binary, string arch_override) { string[] arch_flags; @@ -65,7 +80,12 @@ } settings.addDFlags(arch_flags); - return probePlatform(compiler_binary, arch_flags ~ ["-c", "-o-"], arch_override); + return probePlatform( + compiler_binary, + arch_flags ~ ["-c", "-o-", "-v"], + arch_override, + [ ldcVersionRe ] + ); } void prepareBuildSettings(ref BuildSettings settings, BuildSetting fields = BuildSetting.all) const @@ -213,6 +233,18 @@ 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 6e57867..fc60a35 100644 --- a/source/dub/compilers/utils.d +++ b/source/dub/compilers/utils.d @@ -237,6 +237,56 @@ if (got_preamble) logWarn(""); } +/** + Turn a DMD-like version (e.g. 2.082.1) into a SemVer-like version (e.g. 2.82.1). + The function accepts a dependency operator prefix and some text postfix. + Prefix and postfix are returned verbatim. + Params: + ver = version string, possibly with a dependency operator prefix and some + test postfix. + Returns: + A Semver compliant string +*/ +string dmdLikeVersionToSemverLike(string ver) +{ + import std.algorithm : countUntil, joiner, map, skipOver, splitter; + import std.array : join, split; + import std.ascii : isDigit; + import std.conv : text; + import std.exception : enforce; + import std.functional : not; + import std.range : padRight; + + const start = ver.countUntil!isDigit; + enforce(start != -1, "Invalid semver: "~ver); + const prefix = ver[0 .. start]; + ver = ver[start .. $]; + + const end = ver.countUntil!(c => !c.isDigit && c != '.'); + const postfix = end == -1 ? null : ver[end .. $]; + auto verStr = ver[0 .. $-postfix.length]; + + auto comps = verStr + .splitter(".") + .map!((a) { if (a.length > 1) a.skipOver("0"); return a;}) + .padRight("0", 3); + + return text(prefix, comps.joiner("."), postfix); +} + +/// +unittest { + assert(dmdLikeVersionToSemverLike("2.082.1") == "2.82.1"); + assert(dmdLikeVersionToSemverLike("2.082.0") == "2.82.0"); + assert(dmdLikeVersionToSemverLike("2.082") == "2.82.0"); + assert(dmdLikeVersionToSemverLike("~>2.082") == "~>2.82.0"); + assert(dmdLikeVersionToSemverLike("~>2.082-beta1") == "~>2.82.0-beta1"); + assert(dmdLikeVersionToSemverLike("2.4.6") == "2.4.6"); + assert(dmdLikeVersionToSemverLike("2.4.6-alpha12") == "2.4.6-alpha12"); +} + +private enum probeBeginMark = "__dub_probe_begin__"; +private enum probeEndMark = "__dub_probe_end__"; /** Generate a file that will give, at compile time, information about the compiler (architecture, frontend version...) @@ -264,6 +314,7 @@ return res; } + pragma(msg, } ~ `"` ~ probeBeginMark ~ `"` ~q{ ); pragma(msg, `{`); pragma(msg,` "compiler": "`~ determineCompiler() ~ `",`); pragma(msg, ` "frontendVersion": ` ~ toString!__VERSION__ ~ `,`); @@ -275,6 +326,7 @@ pragma(msg, ` ` ~ determineArchitecture().stringArray); pragma(msg, ` ],`); pragma(msg, `}`); + pragma(msg, } ~ `"` ~ probeEndMark ~ `"` ~ q{ ); string[] determinePlatform() } ~ '{' ~ platformCheck ~ '}' ~ q{ string[] determineArchitecture() } ~ '{' ~ archCheck ~ '}' ~ q{ @@ -288,11 +340,11 @@ } /** - Processes the output generated by compiling the platform probe file. + Processes the JSON output generated by compiling the platform probe file. See_Also: `generatePlatformProbeFile`. */ -BuildPlatform readPlatformProbe(string output) +BuildPlatform readPlatformJsonProbe(string output) { import std.algorithm : map; import std.array : array; @@ -300,11 +352,11 @@ import std.string; // work around possible additional output of the compiler - auto idx1 = output.indexOf("{"); - auto idx2 = output.lastIndexOf("}"); + auto idx1 = output.indexOf(probeBeginMark); + auto idx2 = output.lastIndexOf(probeEndMark); enforce(idx1 >= 0 && idx1 < idx2, "Unexpected platform information output - does not contain a JSON object."); - output = output[idx1 .. idx2+1]; + output = output[idx1+probeBeginMark.length .. idx2]; import dub.internal.vibecompat.data.json; auto json = parseJsonString(output); diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 2c639fd..c88535d 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -46,6 +46,52 @@ { scope (exit) cleanupTemporaries(); + auto dcl = settings.compiler; + + 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 + ) + ); + } + + checkPkgRequirements(m_project.rootPackage); + foreach (pkg; m_project.dependencies) { + checkPkgRequirements(pkg); + } + auto root_ti = targets[m_project.rootPackage.name]; enforce(!(settings.rdmd && root_ti.buildSettings.targetType == TargetType.none), diff --git a/source/dub/package_.d b/source/dub/package_.d index 5da9201..1c1858e 100644 --- a/source/dub/package_.d +++ b/source/dub/package_.d @@ -116,6 +116,7 @@ // use the given recipe as the basis m_info = recipe; + checkDubRequirements(); fillWithDefaults(); } @@ -635,6 +636,32 @@ return ret; } + private void checkDubRequirements() + { + import dub.dependency : Dependency; + import dub.semver : isValidVersion; + import dub.version_ : dubVersion; + import std.exception : enforce; + + if (m_info.toolchainRequirements.dub.length) { + const dep = Dependency(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"); + } + } + private void fillWithDefaults() { auto bs = &m_info.buildSettings; diff --git a/source/dub/platform.d b/source/dub/platform.d index 1d91982..88da7b2 100644 --- a/source/dub/platform.d +++ b/source/dub/platform.d @@ -234,4 +234,25 @@ string compilerBinary; /// Compiled frontend version (e.g. `2067` for frontend versions 2.067.x) int frontendVersion; + /// Compiler version e.g. "1.11.0" + string compilerVersion; + /// Frontend version string from frontendVersion + /// e.g: 2067 => "2.067" + string frontendVersionString() const + { + import std.format : format; + + const maj = frontendVersion / 1000; + const min = frontendVersion % 1000; + return format("%d.%03d", maj, min); + } + /// + unittest + { + BuildPlatform bp; + bp.frontendVersion = 2067; + assert(bp.frontendVersionString == "2.067"); + } } + + diff --git a/source/dub/recipe/json.d b/source/dub/recipe/json.d index d9645b8..c1fd9a1 100644 --- a/source/dub/recipe/json.d +++ b/source/dub/recipe/json.d @@ -41,6 +41,9 @@ recipe.buildTypes[name] = bs; } break; + case "toolchainRequirements": + recipe.toolchainRequirements.parseJson(value); + break; case "-ddoxFilterArgs": recipe.ddoxFilterArgs = deserializeJson!(string[])(value); break; case "-ddoxTool": recipe.ddoxTool = value.get!string; break; } @@ -103,6 +106,9 @@ types[name] = settings.toJson(); ret["buildTypes"] = types; } + if (recipe.hasToolchainRequirements) { + ret["toolchainRequirements"] = recipe.toolchainRequirements.toJson(); + } if (!recipe.ddoxFilterArgs.empty) ret["-ddoxFilterArgs"] = recipe.ddoxFilterArgs.serializeToJson(); if (!recipe.ddoxTool.empty) ret["-ddoxTool"] = recipe.ddoxTool; return ret; @@ -291,3 +297,49 @@ } return ret; } + +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; + } + } +} + +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); + } + return ret; +} diff --git a/source/dub/recipe/packagerecipe.d b/source/dub/recipe/packagerecipe.d index 946b369..f1d3c3e 100644 --- a/source/dub/recipe/packagerecipe.d +++ b/source/dub/recipe/packagerecipe.d @@ -86,6 +86,8 @@ ConfigurationInfo[] configurations; BuildSettingsTemplate[string] buildTypes; + ToolchainRequirements toolchainRequirements; + SubPackage[] subPackages; inout(ConfigurationInfo) getConfiguration(string name) @@ -96,6 +98,17 @@ 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); } @@ -107,6 +120,33 @@ PackageRecipe recipe; } +/// 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; + + 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 + { + string[2][] res; + if (dmd != noKwd) res ~= [ "dmd", dmd ]; + if (ldc != noKwd) res ~= [ "ldc", ldc ]; + if (gdc != noKwd) res ~= [ "gdc", gdc ]; + return res; + } +} + /// Bundles information about a build configuration. struct ConfigurationInfo { diff --git a/source/dub/recipe/sdl.d b/source/dub/recipe/sdl.d index da99f7f..d3aaf14 100644 --- a/source/dub/recipe/sdl.d +++ b/source/dub/recipe/sdl.d @@ -50,6 +50,9 @@ parseBuildSettings(n, bt, parent_name); recipe.buildTypes[name] = bt; break; + case "toolchainRequirements": + parseToolchainRequirements(recipe.toolchainRequirements, n); + break; case "x:ddoxFilterArgs": recipe.ddoxFilterArgs ~= n.stringArrayTagValue; break; case "x:ddoxTool": recipe.ddoxTool = n.stringTagValue; break; } @@ -101,6 +104,9 @@ t.add(settings.toSDL()); ret.add(t); } + if (recipe.hasToolchainRequirements) { + ret.add(toSDL(recipe.toolchainRequirements)); + } if (recipe.ddoxFilterArgs.length) ret.add(new Tag("x", "ddoxFilterArgs", recipe.ddoxFilterArgs.map!(a => Value(a)).array)); if (recipe.ddoxTool.length) ret.add(new Tag("x", "ddoxTool", [Value(recipe.ddoxTool)])); @@ -272,6 +278,52 @@ return ret; } +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; + } + } +} + +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)); + } + return new Tag(null, "toolchainRequirements", null, attrs); +} + private string expandPackageName(string name, string parent_name, Tag tag) { import std.algorithm : canFind; @@ -369,6 +421,7 @@ buildType "release" { dflags "-release" "-O" } +toolchainRequirements dub="~>1.11.0" dmd="~>2.082" x:ddoxFilterArgs "-arg1" "-arg2" x:ddoxFilterArgs "-arg3" x:ddoxTool "ddoxtool" @@ -457,6 +510,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.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 new file mode 100755 index 0000000..77bf5f4 --- /dev/null +++ b/test/issue1531-toolchain-requirements.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +set -e + +. $(dirname "${BASH_SOURCE[0]}")/common.sh + +cat << EOF | $DUB - || die "Did not pass without toolchainRequirements" +/+ dub.sdl: ++/ +void main() {} +EOF + +# pass test dub requirement given as $1 +function test_dub_req_pass { + cat << EOF | $DUB - || die "Did not pass requirement dub=\"$1\"" +/+ dub.sdl: + toolchainRequirements dub="$1" ++/ +void main() {} +EOF +} + +# fail test dub requirement given as $1 +function test_dub_req_fail { + ! cat << EOF | $DUB - || die "Did not pass requirement dub=\"$1\"" +/+ dub.sdl: + toolchainRequirements dub="$1" ++/ +void main() {} +EOF +} + +test_dub_req_pass ">=1.7.0" +test_dub_req_fail "~>0.9" +test_dub_req_fail "~>999.0" + +# extract compiler version +if [[ $DC == *ldc* ]] || [[ $DC == *ldmd* ]]; then + VER_REG='\((([[:digit:]]+)(\.[[:digit:]]+\.[[:digit:]]+[A-Za-z0-9.+-]*))\)' + DC_NAME=ldc +elif [[ $DC == *dmd* ]]; then + VER_REG='v(([[:digit:]]+)(\.[[:digit:]]+\.[[:digit:]]+[A-Za-z0-9.+-]*))' + DC_NAME=dmd +elif [[ $DC == *gdc* ]]; then + VER_REG='\) (([[:digit:]]+)(\.[[:digit:]]+\.[[:digit:]]+[A-Za-z0-9.+-]*))' + DC_NAME=gdc +else + die "Did not recognize compiler" +fi +if [[ $($DC --version) =~ $VER_REG ]]; then + DC_VER=${BASH_REMATCH[1]} + DC_VER_MAJ=${BASH_REMATCH[2]} + DC_VER_REM=${BASH_REMATCH[3]} + $DC --version + echo $DC version is $DC_VER +else + $DC --version + die "Could not extract compiler version" +fi + +# create test app directory +TMPDIR=$(mktemp -d /tmp/dubtest1531_XXXXXX) +mkdir -p $TMPDIR/source +cat << EOF > $TMPDIR/source/app.d +module dubtest1531; +void main() {} +EOF + +# write dub.sdl with compiler requirement given as $1 +function write_cl_req { + cat << EOF > $TMPDIR/dub.sdl +name "dubtest1531" +toolchainRequirements ${DC_NAME}="$1" +EOF +} + +# pass test compiler requirement given as $1 +function test_cl_req_pass { + write_cl_req $1 + $DUB --compiler=$DC --root=$TMPDIR || die "Did not pass with $DC_NAME=\"$1\"" +} + +# fail test compiler requirement given as $1 +function test_cl_req_fail { + write_cl_req $1 + ! $DUB --compiler=$DC --root=$TMPDIR || die "Did not fail with $DC_NAME=\"$1\"" +} + + +test_cl_req_pass "==$DC_VER" +test_cl_req_pass ">=$DC_VER" +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" +test_cl_req_fail "~>$(($DC_VER_MAJ + 1))$DC_VER_REM" +test_cl_req_fail no + +rm -rf $TMPDIR