diff --git a/.github/workflows/d.yml b/.github/workflows/d.yml index 399f3ba8..2c6da637 100644 --- a/.github/workflows/d.yml +++ b/.github/workflows/d.yml @@ -10,8 +10,8 @@ jobs: os: [ubuntu-22.04, windows-2019] # Remember to change the compiler versions further below as well as here dc: - - dmd-2.104.1 - - ldc-1.32.2 + - dmd-2.105.0 + - ldc-1.33.0 runs-on: ${{ matrix.os }} steps: @@ -68,10 +68,8 @@ jobs: matrix: os: [ubuntu-22.04, macos-12, windows-2022] dc: - - dmd-2.103.1 - - dmd-2.100.0 - - ldc-1.32.2 - - ldc-1.30.0 + - dmd-2.105.0 + - ldc-1.33.0 runs-on: ${{ matrix.os }} defaults: run: diff --git a/payload/reggae/options.d b/payload/reggae/options.d index 53b2caf6..c05ef990 100644 --- a/payload/reggae/options.d +++ b/payload/reggae/options.d @@ -199,7 +199,11 @@ struct Options { } string[] reggaeFileDependencies() @safe const { - return [ranFromPath, reggaeFilePath] ~ getReggaeFileDependenciesDlang ~ dependencies; + import std.file: exists; + auto maybeReggaeFile = reggaeFilePath.exists + ? [reggaeFilePath] + : []; + return ranFromPath ~ maybeReggaeFile ~ getReggaeFileDependenciesDlang ~ dependencies; } bool isJsonBuild() @safe const { diff --git a/payload/reggae/rules/dub.d b/payload/reggae/rules/dub.d index 803bcbaa..57abfc2d 100644 --- a/payload/reggae/rules/dub.d +++ b/payload/reggae/rules/dub.d @@ -38,16 +38,35 @@ static if(isDubProject) { CompilationMode compilationMode = CompilationMode.options) () { - enum config = "default"; - enum dubInfo = configToDubInfo[config]; - enum targetName = dubInfo.targetName; - enum linkerFlags = dubInfo.mainLinkerFlags ~ linkerFlags.value; + import reggae.config: options; + + return dubDefaultTarget( + options, + configToDubInfo, + compilerFlags, + linkerFlags, + compilationMode + ); + } + + Target dubDefaultTarget(C)( + in imported!"reggae.options".Options options, + in C configToDubInfo, + CompilerFlags compilerFlags = CompilerFlags(), + LinkerFlags linkerFlags = LinkerFlags(), + CompilationMode compilationMode = CompilationMode.options) + { + const config = "default"; + const dubInfo = configToDubInfo[config]; + const targetName = dubInfo.targetName; + const allLinkerFlags = dubInfo.mainLinkerFlags ~ linkerFlags.value; return dubTarget( + options, targetName, dubInfo, compilerFlags.value, - linkerFlags, + allLinkerFlags, compilationMode, ); } @@ -79,7 +98,26 @@ static if(isDubProject) { CompilationMode compilationMode) () { + import reggae.config: options; + return dubTestTarget( + options, + configToDubInfo, + compilerFlags, + linkerFlags, + compilationMode + ); + } + + Target dubTestTarget(C) + (in imported!"reggae.options".Options options, + in C configToDubInfo, + CompilerFlags compilerFlags = CompilerFlags(), + LinkerFlags linkerFlags = LinkerFlags(), + CompilationMode compilationMode = CompilationMode.options) + { + import reggae.build : Target; import reggae.dub.info: TargetType, targetName; + import reggae.rules.dub: dubTarget; import std.exception : enforce; // No `dub test` config? Then it inherited some `targetType "none"`, and @@ -102,7 +140,8 @@ static if(isDubProject) { name = targetName(TargetType.executable, "ut"); } - return dubTarget(name, + return dubTarget(options, + name, dubInfo, compilerFlags.value, linkerFlags.value, @@ -120,8 +159,12 @@ static if(isDubProject) { ) () if(isCallable!objsFunction) { + import reggae.config: options; + const dubInfo = configToDubInfo[config.value]; - return dubTarget(dubInfo.targetName, + + return dubTarget(options, + dubInfo.targetName, dubInfo, compilerFlags.value, linkerFlags.value, @@ -139,17 +182,21 @@ static if(isDubProject) { ) () { - return dubTarget(targetName, - configToDubInfo[config.value], - compilerFlags.value, - linkerFlags.value, - compilationMode, - objsFunction(), - ); - } + import reggae.config: options; + return dubTarget( + options, + targetName, + configToDubInfo[config.value], + compilerFlags.value, + linkerFlags.value, + compilationMode, + objsFunction(), + ); + } Target dubTarget( + in imported!"reggae.options".Options options, in TargetName targetName, in DubInfo dubInfo, in string[] compilerFlags, @@ -159,7 +206,6 @@ static if(isDubProject) { in size_t startingIndex = 0, ) { - import reggae.config: options; import reggae.rules.common: staticLibraryTarget, link; import reggae.types: TargetName; import std.path: relativePath, buildPath; @@ -300,7 +346,7 @@ static if(isDubProject) { } - private Target[] objs(in TargetName targetName, + /*private*/ Target[] objs(in TargetName targetName, in DubInfo dubInfo, in string[] compilerFlags, in CompilationMode compilationMode, @@ -320,7 +366,7 @@ static if(isDubProject) { return allObjs; } - private string realName(in TargetName targetName, in DubInfo dubInfo) { + /*private*/ string realName(in TargetName targetName, in DubInfo dubInfo) { import std.path: buildPath; diff --git a/src/reggae/dub/interop/package.d b/src/reggae/dub/interop/package.d index 8b762354..dbf82b9b 100644 --- a/src/reggae/dub/interop/package.d +++ b/src/reggae/dub/interop/package.d @@ -8,9 +8,6 @@ import reggae.from; public import reggae.dub.interop.reggaefile; -private from!"reggae.dub.info".DubInfo[string] gDubInfos; - - void writeDubConfig(O)(ref O output, in from!"reggae.options".Options options, from!"std.stdio".File file) { @@ -27,34 +24,44 @@ void writeDubConfig(O)(ref O output, return; } - // must check for dub.selections.json before creating dub instance - const dubSelectionsJson = ensureDubSelectionsJson(output, options); - - auto dub = Dub(options); + auto dubInfos = dubInfos(output, options); - dubFetch(output, dub, options, dubSelectionsJson); + const targetType = dubInfos["default"].packages.length + ? dubInfos["default"].packages[0].targetType + : TargetType.sourceLibrary; file.writeln("import reggae.dub.info;"); file.writeln("enum isDubProject = true;"); - - output.log(" Getting dub build information"); - auto dubInfo = getDubInfo(output, dub, options); - output.log(" Got dub build information"); - - const targetType = dubInfo.packages.length - ? dubInfo.packages[0].targetType - : TargetType.sourceLibrary; - file.writeln(`const configToDubInfo = assocList([`); - const keys = () @trusted { return gDubInfos.keys; }(); - foreach(config; keys) { - file.writeln(` assocEntry("`, config, `", `, gDubInfos[config], `),`); + foreach(k, v; dubInfos) { + file.writeln(` assocEntry("`, k, `", `, v, `),`); } file.writeln(`]);`); + file.writeln; } +auto dubInfos(O)(ref O output, + in from!"reggae.options".Options options) { + import reggae.io: log; + import reggae.dub.info: TargetType; + import reggae.dub.interop.fetch: dubFetch; + import reggae.dub.interop.dublib: Dub; + + // must check for dub.selections.json before creating dub instance + const dubSelectionsJson = ensureDubSelectionsJson(output, options); + + auto dub = Dub(options); + + dubFetch(output, dub, options, dubSelectionsJson); + + output.log(" Getting dub build information"); + auto ret = getDubInfos(output, dub, options); + output.log(" Got dub build information"); + + return ret; +} private string ensureDubSelectionsJson (O) @@ -81,60 +88,57 @@ private string ensureDubSelectionsJson } - -private from!"reggae.dub.info".DubInfo getDubInfo +private from!"reggae.dub.info".DubInfo[string] getDubInfos (O) (ref O output, ref from!"reggae.dub.interop.dublib".Dub dub, in from!"reggae.options".Options options) { - import reggae.dub.interop: gDubInfos; import reggae.io: log; import reggae.path: buildPath; + import reggae.dub.info: DubInfo; import std.file: exists; import std.exception: enforce; - version(unittest) gDubInfos = null; - - if("default" !in gDubInfos) { - - enforce(buildPath(options.projectPath, "dub.selections.json").exists, - "Cannot find dub.selections.json"); - - auto settings = dub.getGeneratorSettings(options); - const configs = dubConfigurations(output, dub, options, settings); - const haveTestConfig = configs.test != ""; - bool atLeastOneConfigOk; - Exception dubInfoFailure; - - foreach(config; configs.configurations) { - const isTestConfig = haveTestConfig && config == configs.test; - try { - gDubInfos[config] = handleDubConfig(output, dub, options, settings, config, isTestConfig); - atLeastOneConfigOk = true; - } catch(Exception ex) { - output.log("ERROR: Could not get info for configuration ", config, ": ", ex.msg); - if(dubInfoFailure is null) dubInfoFailure = ex; - } + DubInfo[string] ret; + + enforce(buildPath(options.projectPath, "dub.selections.json").exists, + "Cannot find dub.selections.json"); + + auto settings = dub.getGeneratorSettings(options); + const configs = dubConfigurations(output, dub, options, settings); + const haveTestConfig = configs.test != ""; + bool atLeastOneConfigOk; + Exception dubInfoFailure; + + foreach(config; configs.configurations) { + const isTestConfig = haveTestConfig && config == configs.test; + try { + ret[config] = handleDubConfig(output, dub, options, settings, config, isTestConfig); + atLeastOneConfigOk = true; + } catch(Exception ex) { + output.log("ERROR: Could not get info for configuration ", config, ": ", ex.msg); + if(dubInfoFailure is null) dubInfoFailure = ex; } + } - if(!atLeastOneConfigOk) { - assert(dubInfoFailure !is null, - "Internal error: no configurations worked and no exception to throw"); - throw dubInfoFailure; - } + if(!atLeastOneConfigOk) { + assert(dubInfoFailure !is null, + "Internal error: no configurations worked and no exception to throw"); + throw dubInfoFailure; + } - gDubInfos["default"] = gDubInfos[configs.default_]; + ret["default"] = ret[configs.default_]; - // (additionally) expose the special `dub test` config as `unittest` config in the DSL (`configToDubInfo`) - // (for `dubTestTarget!()`, `dubConfigurationTarget!(Configuration("unittest"))` etc.) - if(haveTestConfig && configs.test != "unittest" && configs.test in gDubInfos) - gDubInfos["unittest"] = gDubInfos[configs.test]; - } + // (additionally) expose the special `dub test` config as `unittest` config in the DSL (`configToDubInfo`) + // (for `dubTestTarget!()`, `dubConfigurationTarget!(Configuration("unittest"))` etc.) + if(haveTestConfig && configs.test != "unittest" && configs.test in ret) + ret["unittest"] = ret[configs.test]; - return gDubInfos["default"]; + return ret; } + private from!"reggae.dub.interop.configurations".DubConfigurations dubConfigurations (O) diff --git a/src/reggae/dub/interop/reggaefile.d b/src/reggae/dub/interop/reggaefile.d index c8210d67..afae2d60 100644 --- a/src/reggae/dub/interop/reggaefile.d +++ b/src/reggae/dub/interop/reggaefile.d @@ -7,46 +7,28 @@ module reggae.dub.interop.reggaefile; import reggae.from; -void maybeCreateReggaefile(T)(auto ref T output, - in from!"reggae.options".Options options) -{ +auto defaultDubBuild(in from!"reggae.options".Options options) { + import reggae.build: Build; + import reggae.dub.interop: dubInfos; import std.file: exists; + import std.stdio: stdout; - if(options.isDubProject && !options.reggaeFilePath.exists) { - createReggaefile(output, options); - } -} - -// default build for a dub project when there is no reggaefile -private void createReggaefile(T)(auto ref T output, - in from!"reggae.options".Options options) -{ - import reggae.io: log; - import reggae.path: buildPath; - import std.stdio: File; - import std.string: replace; - - output.log("Creating reggaefile.d from dub information"); - auto file = File(buildPath(options.projectPath, "reggaefile.d"), "w"); - - static cleanup(in string str) { - return str.replace("\n ", "\n"); - } + if(!options.isDubProject || options.reggaeFilePath.exists) + return Build(); - const text = options.dubConfig == "" - ? standardDubReggaefile - : reducedDubReggaefile; + const configToDubInfo = dubInfos(stdout, options); - file.write(text.replace("\n ", "\n")); + return options.dubConfig == "" + ? standardDubBuild(options, configToDubInfo) + : reducedDubBuild(options, configToDubInfo); } +auto standardDubBuild(in from!"reggae.options".Options options, in ConfigToDubInfo configToDubInfo) { + import reggae.build: Build, Target, optional; + import reggae.rules.dub: dubDefaultTarget, dubTestTarget; -// This is the default reggaefile for dub projects if one is not found -private enum standardDubReggaefile = q{ - import reggae; - - alias buildTarget = dubDefaultTarget!(); // dub build - alias testTarget = dubTestTarget!(); // dub test + auto buildTarget = dubDefaultTarget(options, configToDubInfo); // dub build + auto testTarget = dubTestTarget(options, configToDubInfo); // dub test Target aliasTarget(string aliasName, alias target)() { import std.algorithm: canFind, map; @@ -71,16 +53,14 @@ private enum standardDubReggaefile = q{ // And a `ut` convenience alias for the `dub test` target. alias utTarget = aliasTarget!("ut", testTarget); - mixin build!(buildTarget, optional!testTarget, optional!defaultTarget, optional!utTarget); -}; - + return Build(buildTarget, optional(testTarget), optional(defaultTarget), optional(utTarget)); +} -// This is the default reggaefile if one is not found when --dub-config is used. This speeds -// up running reggae -private enum reducedDubReggaefile = q{ - import reggae; +auto reducedDubBuild(in from!"reggae.options".Options options, in ConfigToDubInfo configToDubInfo) { + import reggae.build: Build, Target, optional; + import reggae.rules.dub: dubDefaultTarget; - alias buildTarget = dubDefaultTarget!(); // dub build + auto buildTarget = dubDefaultTarget(options, configToDubInfo); // dub build Target aliasTarget(string aliasName, alias target)() { import std.algorithm: map; @@ -92,5 +72,7 @@ private enum reducedDubReggaefile = q{ // Add a `default` convenience alias for the `dub build` target. alias defaultTarget = aliasTarget!("default", buildTarget); - mixin build!(buildTarget, optional!defaultTarget); -}; + return Build(buildTarget, optional(defaultTarget)); +} + +private alias ConfigToDubInfo = from!"reggae.dub.info".DubInfo[string]; diff --git a/src/reggae/reggae.d b/src/reggae/reggae.d index 9c4ce9bf..e4059da7 100644 --- a/src/reggae/reggae.d +++ b/src/reggae/reggae.d @@ -30,10 +30,13 @@ import reggae.path: buildPath; version(minimal) { //empty stubs for minimal version of reggae - void maybeCreateReggaefile(T...)(T) {} void writeDubConfig(T...)(T) {} + auto defaultDubBuild() { + import reggae.build: Build; + return Build(); + } } else { - import reggae.dub.interop: writeDubConfig, maybeCreateReggaefile; + import reggae.dub.interop: writeDubConfig, defaultDubBuild; } mixin template reggaeGen(targets...) { @@ -68,6 +71,9 @@ void run(T)(auto ref T output, Options options) { enforce(options.projectPath != "", "A project path must be specified"); + // if there's no custom reggaefile, execute and exit early + if(dubBuild(options)) return; + // write out the library source files to be compiled/interpreted // with the user's build description writeSrcFiles(output, options); @@ -77,41 +83,52 @@ void run(T)(auto ref T output, Options options) { if(haveToReturn) return; } - maybeCreateReggaefile(output, options); createBuild(output, options); } //get JSON description of the build from a scripting language //and transform it into a build description //return true if no D files are present -bool jsonBuild(Options options) { +private bool jsonBuild(Options options) { + import reggae.json_build; + immutable jsonOutput = getJsonOutput(options); - return jsonBuild(options, jsonOutput); + auto build = jsonToBuild(options.projectPath, jsonOutput); + + return runtimeBuild(jsonToOptions(options, jsonOutput), build); } -//transform JSON description into a Build struct -//return true if no D files are present -bool jsonBuild(Options options, in string jsonOutput) { - enforce(options.backend != Backend.binary, "Binary backend not supported via JSON"); +// Call dub, get the build description, and generate the build now +private bool dubBuild(in Options options) { + import reggae.dub.interop.reggaefile: defaultDubBuild; + auto build = defaultDubBuild(options); + return runtimeBuild(options, build); +} + +private bool runtimeBuild(in Options options, imported!"reggae.build".Build build) { + import reggae.buildgen: doBuild; + import reggae.types: Backend; + import std.algorithm: among; + + enforce(options.backend != Backend.binary, "Binary backend not supported at runtime"); version(minimal) assert(0, "JSON builds not supported in minimal version"); else { - import reggae.json_build; import reggae.buildgen; import reggae.rules.common: Language; - auto build = jsonToBuild(options.projectPath, jsonOutput); - doBuild(build, jsonToOptions(options, jsonOutput)); + if(build == build.init) return false; + + doBuild(build, options); import reggae.buildgen:writeCompilationDB; if(!options.noCompilationDB) writeCompilationDB(build, options); - //true -> exit early - return !build.targets.canFind!(a => a.getLanguage == Language.D); } -} + return true; +} private string getJsonOutput(in Options options) @safe { const args = getJsonOutputArgs(options); diff --git a/tests/it/runtime/dependencies.d b/tests/it/runtime/dependencies.d index 434862fe..9031847c 100644 --- a/tests/it/runtime/dependencies.d +++ b/tests/it/runtime/dependencies.d @@ -43,7 +43,7 @@ version(DigitalMars) { version(DigitalMars) { version(linux) { - static foreach(backend; ["ninja", "make", "binary"]) { + static foreach(backend; ["ninja", "make"]) { @("change.compiler." ~ backend) @Tags(backend) diff --git a/tests/it/runtime/dub.d b/tests/it/runtime/dub.d index f5fa964b..1ca31261 100644 --- a/tests/it/runtime/dub.d +++ b/tests/it/runtime/dub.d @@ -16,7 +16,7 @@ unittest { with(immutable ReggaeSandbox("dub")) { shouldNotExist("reggaefile.d"); writelnUt("\n\nReggae output:\n\n", runReggae("-b", "ninja").lines.join("\n"), "-----\n"); - shouldExist("reggaefile.d"); + shouldNotExist("reggaefile.d"); auto output = ninja(["-v"]).shouldExecuteOk; version(Windows) { @@ -72,7 +72,7 @@ unittest { @("dependencies not on file system already no dub.selections.json") @Flaky -@Tags(["dub", "ninja"]) +@Tags("dub", "ninja", "online") unittest { import std.file: exists, rmdirRecurse; @@ -98,7 +98,7 @@ unittest { @("no main function but with unit tests") -@Tags(["dub", "ninja"]) +@Tags("dub", "ninja", "online") @Flaky unittest { import std.file: mkdirRecurse; diff --git a/tests/it/runtime/error_messages.d b/tests/it/runtime/error_messages.d index f6b9e49b..410497b9 100644 --- a/tests/it/runtime/error_messages.d +++ b/tests/it/runtime/error_messages.d @@ -11,7 +11,7 @@ import tests.it.runtime; else enum projectPath = "/non/existent"; - ReggaeSandbox().runReggae(["-b", "binary"], projectPath).shouldThrowWithMessage( + ReggaeSandbox().runReggae(["-b", "ninja"], projectPath).shouldThrowWithMessage( "Could not find " ~ buildPath(projectPath, "reggaefile.d") ); } @@ -21,7 +21,7 @@ import tests.it.runtime; with(immutable ReggaeSandbox()) { writeFile("foo.txt"); - runReggae("-b", "binary").shouldThrowWithMessage( + runReggae("-b", "ninja").shouldThrowWithMessage( "Could not find " ~ buildPath(testPath, "reggaefile.d")); } } @@ -32,22 +32,22 @@ import tests.it.runtime; writeFile("reggaefile.d"); writeFile("reggaefile.py"); - runReggae("-b", "binary").shouldThrowWithMessage( + runReggae("-b", "ninja").shouldThrowWithMessage( "Reggae builds may only use one language. Found: D, Python" ); writeFile("reggaefile.rb"); - runReggae("-b", "binary").shouldThrowWithMessage( + runReggae("-b", "ninja").shouldThrowWithMessage( "Reggae builds may only use one language. Found: D, Python, Ruby" ); writeFile("reggaefile.js"); - runReggae("-b", "binary").shouldThrowWithMessage( + runReggae("-b", "ninja").shouldThrowWithMessage( "Reggae builds may only use one language. Found: D, Python, Ruby, JavaScript" ); writeFile("reggaefile.lua"); - runReggae("-b", "binary").shouldThrowWithMessage( + runReggae("-b", "ninja").shouldThrowWithMessage( "Reggae builds may only use one language. Found: D, Python, Ruby, JavaScript, Lua" ); } diff --git a/tests/ut/json_build/rules.d b/tests/ut/json_build/rules.d index e8bc37d2..1b2fbbb6 100644 --- a/tests/ut/json_build/rules.d +++ b/tests/ut/json_build/rules.d @@ -128,7 +128,6 @@ unittest { auto options = jsonToOptions(defaultOptions, parseJSON(jsonString)); options.reggaeFileDependencies.shouldEqual( [thisExePath, - buildPath(projectPath, "reggaefile.d"), "/path/to/foo.py", "/other/path/bar.py"]); }