diff --git a/source/dub/generators/ninja.d b/source/dub/generators/ninja.d index 6093c87535..79b8a756b5 100644 --- a/source/dub/generators/ninja.d +++ b/source/dub/generators/ninja.d @@ -9,11 +9,13 @@ module dub.generators.ninja; import dub.compilers.compiler; import dub.generators.generator; import dub.project; +import dub.internal.vibecompat.inet.path; import std.algorithm : map, startsWith; import std.array : join, replace; import std.path : stripExtension; import std.stdio : File; +import std.file : exists, thisExePath; class NinjaGenerator : ProjectGenerator { @@ -47,6 +49,22 @@ class NinjaGenerator : ProjectGenerator f.writeln(" description = Archiving $out"); f.writeln(); + const recipePath = m_project.rootPackage.recipePath.toNativeString(); + auto selectionsPath = (m_project.rootPackage.path ~ "dub.selections.json").toNativeString(); + + f.writeln("rule regen"); + f.writeln(" command = ", thisExePath(), " generate ninja"); + f.writeln(" generator = 1"); + f.writeln(" description = Regenerating build.ninja"); + f.writeln(); + + string[] regenInputs = [recipePath]; + if (exists(selectionsPath)) + regenInputs ~= selectionsPath; + + f.writeln("build build.ninja: regen ", regenInputs.map!(p => escapeNinjaPath(p)).join(" ")); + f.writeln(); + foreach (name, info; targets) { auto bs = info.buildSettings; @@ -63,13 +81,13 @@ class NinjaGenerator : ProjectGenerator if (extraFlags.length) parts ~= extraFlags; auto flags = parts.join(" "); - auto lflags = bs.lflags.join(" "); + auto lflags = bs.lflags.map!(f => escapeNinjaPath(f)).join(" "); string[] objs; foreach (src; bs.sourceFiles) { auto obj = objName(src); - f.writeln("build ", obj, ": dc ", src); + f.writeln("build ", obj, ": dc ", escapeNinjaPath(src)); if (flags.length) f.writeln(" flags = ", flags); objs ~= obj; @@ -103,6 +121,11 @@ class NinjaGenerator : ProjectGenerator } } + private static string escapeNinjaPath(string path) + { + return path.replace(":", "$:").replace(" ", "$ "); + } + private static string objName(string src) { return stripExtension(src) diff --git a/test/ninja-generator-sdl/dub.sdl b/test/ninja-generator-sdl/dub.sdl new file mode 100644 index 0000000000..1b8de08184 --- /dev/null +++ b/test/ninja-generator-sdl/dub.sdl @@ -0,0 +1,2 @@ +name "ninja-generator-sdl" +targetType "executable" diff --git a/test/ninja-generator-sdl/source/app.d b/test/ninja-generator-sdl/source/app.d new file mode 100644 index 0000000000..ab73b3a234 --- /dev/null +++ b/test/ninja-generator-sdl/source/app.d @@ -0,0 +1 @@ +void main() {} diff --git a/test/ninja-generator.script.d b/test/ninja-generator.script.d new file mode 100644 index 0000000000..ace962e72d --- /dev/null +++ b/test/ninja-generator.script.d @@ -0,0 +1,86 @@ +/+ dub.sdl: + name "ninja-generator-regen" + dependency "common" path="./common" + +/ + +module ninja_generator_regen; + +import std.process : environment, execute, Config; +import std.path : buildPath, dirName; +import std.file : readText, remove, mkdirRecurse, rmdirRecurse, write; +import std.algorithm : canFind; +import core.thread : Thread; +import core.time : seconds; + +import common; + +bool regenUpdatesImportPath(string projDir, string recipePath, string origRecipe, string newRecipe) +{ + const buildNinjaPath = buildPath(projDir, "build.ninja"); + const importDir = buildPath(projDir, "extra-imports"); + + mkdirRecurse(importDir); + scope(exit) rmdirRecurse(importDir); + + Thread.sleep(1.seconds); + write(recipePath, newRecipe); + scope(exit) write(recipePath, origRecipe); + + const result = execute(["ninja"], null, Config.none, size_t.max, projDir); + if (result.status != 0 || !result.output.canFind("Regenerating build.ninja")) + return false; + + return readText(buildNinjaPath).canFind("extra-imports"); +} + +int main() +{ + const dub = environment.get("DUB", buildPath(__FILE_FULL_PATH__.dirName.dirName, "bin", "dub")); + const dc = environment.get("DC", "dmd"); + const curr_dir = environment.get("CURR_DIR", buildPath(__FILE_FULL_PATH__.dirName)); + const projDir = buildPath(curr_dir, "ninja-generator"); + + if (execute([dub, "generate", "ninja", "--compiler", dc], null, Config.none, size_t.max, projDir).status) + die("dub generate ninja failed"); + + execute(["ninja", "-t", "clean"], null, Config.none, size_t.max, projDir); + if (execute(["ninja"], null, Config.none, size_t.max, projDir).status) + die("initial ninja build failed"); + + const jsonRecipePath = buildPath(projDir, "dub.json"); + const origJson = readText(jsonRecipePath); + const newJson = `{ + "name": "ninja-generator", + "targetType": "executable", + "importPaths": ["extra-imports"] +}`; + + if (!regenUpdatesImportPath(projDir, jsonRecipePath, origJson, newJson)) + die("build.ninja was not actually regenerated with the new import path after touching dub.json"); + + const sdlProjDir = buildPath(curr_dir, "ninja-generator-sdl"); + + if (execute([dub, "generate", "ninja", "--compiler", dc], null, Config.none, size_t.max, sdlProjDir).status) + die("dub generate ninja failed for dub.sdl project"); + + const sdlBuildNinja = buildPath(sdlProjDir, "build.ninja"); + if (!readText(sdlBuildNinja).canFind("regen") || !readText(sdlBuildNinja).canFind("dub.sdl")) + die("build.ninja regen edge missing dub.sdl dependency"); + + execute(["ninja", "-t", "clean"], null, Config.none, size_t.max, sdlProjDir); + if (execute(["ninja"], null, Config.none, size_t.max, sdlProjDir).status) + die("initial ninja build failed for dub.sdl project"); + + const sdlRecipePath = buildPath(sdlProjDir, "dub.sdl"); + const origSdl = readText(sdlRecipePath); + const newSdl = "name \"ninja-generator-sdl\"\ntargetType \"executable\"\nimportPaths \"extra-imports\"\n"; + + if (!regenUpdatesImportPath(sdlProjDir, sdlRecipePath, origSdl, newSdl)) + die("build.ninja was not actually regenerated with the new import path after touching dub.sdl"); + + execute(["ninja", "-t", "clean"], null, Config.none, size_t.max, sdlProjDir); + remove(sdlBuildNinja); + + log("PASS"); + return 0; +}