diff --git a/changelog/d_versions_flag.dd b/changelog/d_versions_flag.dd new file mode 100644 index 0000000..0211131 --- /dev/null +++ b/changelog/d_versions_flag.dd @@ -0,0 +1,6 @@ +Exposed `--d-versions` CLI flag + +You can now specify `--d-versions=Xyz` to basically insert `version = Xyz;` into +all D source files. This is the same as specifying `versions` in your dub.sdl / +dub.json file, but from the CLI and unrelated to any build types or +configurations. diff --git a/scripts/fish-completion/dub.fish b/scripts/fish-completion/dub.fish index bda55ed..1ed8fb8 100644 --- a/scripts/fish-completion/dub.fish +++ b/scripts/fish-completion/dub.fish @@ -48,6 +48,7 @@ complete -c dub -n "contains '$cmd' (commandline -poc)" -s c -l config -r -d "Build configuration" complete -c dub -n "contains '$cmd' (commandline -poc)" -s a -l arch -r -d "Force architecture" complete -c dub -n "contains '$cmd' (commandline -poc)" -s d -l debug -r -d "Debug identifier" + complete -c dub -n "contains '$cmd' (commandline -poc)" -s d -l d-version -r -d "Version identifier" complete -c dub -n "contains '$cmd' (commandline -poc)" -l nodeps -d "No dependency check" complete -c dub -n "contains '$cmd' (commandline -poc)" -s b -l build -u -x -d "Build type" -a "debug plain release release-debug release-nobounds unittest profile profile-gc docs ddox cov cov-ctfe unittest-cov unittest-cov-ctfe syntax" complete -c dub -n "contains '$cmd' (commandline -poc)" -l build-mode -x -d "How compiler & linker are invoked" -a "separate allAtOnce singleFile" diff --git a/scripts/zsh-completion/_dub b/scripts/zsh-completion/_dub index 21457f0..4550cd8 100644 --- a/scripts/zsh-completion/_dub +++ b/scripts/zsh-completion/_dub @@ -153,6 +153,7 @@ '--compiler=[Specifies the compiler binary to use (can be a path)]:compiler:(dmd gdc ldc gdmd ldmd)' \ '(-a --arch)'{-a,--arch=}'[Force a different architecture (e.g. x86 or x86_64)]:architecture: ' \ '(-d --debug)*'{-d,--debug=}'[Define the specified debug version identifier when building]:Debug version: ' \ + '--d-version=[Define the specified version identifier when building]:Version identifier: ' \ '--nodeps[Do not resolve missing dependencies before building]' \ '--build-mode=[Specifies the way the compiler and linker are invoked]:build mode:("separate (default)" allAtOnce singleFile)' \ '--single[Treats the package name as a filename. The file must contain a package recipe comment]:file:_files -g "*.d"' \ diff --git a/source/dub/commandline.d b/source/dub/commandline.d index ca5a552..56edc9d 100644 --- a/source/dub/commandline.d +++ b/source/dub/commandline.d @@ -1041,6 +1041,7 @@ string m_compilerName; string m_arch; string[] m_debugVersions; + string[] m_dVersions; string[] m_overrideConfigs; GeneratorSettings baseSettings; string m_defaultConfig; @@ -1071,7 +1072,12 @@ "Force a different architecture (e.g. x86 or x86_64)" ]); args.getopt("d|debug", &m_debugVersions, [ - "Define the specified debug version identifier when building - can be used multiple times" + "Define the specified `debug` version identifier when building - can be used multiple times" + ]); + args.getopt("d-version", &m_dVersions, [ + "Define the specified `version` identifier when building - can be used multiple times.", + "Use sparingly, with great power comes great responsibility! For commonly used or combined versions " + ~ "and versions that dependees should be able to use, create configurations in your package." ]); args.getopt("nodeps", &m_nodeps, [ "Do not resolve missing dependencies before building" @@ -1114,6 +1120,7 @@ this.baseSettings.compiler = getCompiler(m_compilerName); this.baseSettings.platform = this.baseSettings.compiler.determinePlatform(this.baseSettings.buildSettings, m_compilerName, m_arch); this.baseSettings.buildSettings.addDebugVersions(m_debugVersions); + this.baseSettings.buildSettings.addVersions(m_dVersions); m_defaultConfig = null; enforce (loadSpecificPackage(dub, package_name, ver), "Failed to load package."); diff --git a/source/dub/compilers/buildsettings.d b/source/dub/compilers/buildsettings.d index 9d5652c..1966802 100644 --- a/source/dub/compilers/buildsettings.d +++ b/source/dub/compilers/buildsettings.d @@ -405,6 +405,7 @@ struct Flags (T) { import dub.internal.vibecompat.data.serialization : ignore; + import dub.internal.vibecompat.data.json : Json; @ignore BitFlags!T values; @@ -420,6 +421,39 @@ alias values this; + public Json toJson() const + { + import std.conv : to; + import std.traits : EnumMembers; + + auto json = Json.emptyArray; + + static foreach (em; EnumMembers!T) { + static if (em != 0) { + if (values & em) { + json ~= em.to!string; + } + } + } + + return json; + } + + public static Flags!T fromJson(Json json) + { + import std.conv : to; + import std.exception : enforce; + + BitFlags!T flags; + + enforce(json.type == Json.Type.array, "Should be an array"); + foreach (jval; json) { + flags |= jval.get!string.to!T; + } + + return Flags!T(flags); + } + /** * Reads a list of flags from a JSON/YAML document and converts them * to our internal representation. @@ -444,6 +478,16 @@ unittest { + import dub.internal.vibecompat.data.json; + + auto opts = Flags!BuildOption(BuildOption.debugMode | BuildOption.debugInfo | BuildOption.warningsAsErrors); + const str = serializeToJsonString(opts); + assert(str == `["debugMode","debugInfo","warningsAsErrors"]`); + assert(deserializeJson!(typeof(opts))(str) == opts); +} + +unittest +{ import dub.internal.configy.Read; static struct Config diff --git a/source/dub/dub.d b/source/dub/dub.d index 28ada24..87a78bc 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -1883,6 +1883,11 @@ * project directory, but this led to issues with packages stored on * read-only file system / location, and lingering artifacts scattered * through the file system. + * + * Dub writes in the cache directory some Json description files + * of the available artifacts. These files are intended to be read by + * 3rd party software (e.g. Meson). The default cache location specified + * in this function should therefore not change across future Dub versions. */ NativePath cache; diff --git a/source/dub/generators/build.d b/source/dub/generators/build.d index 8583eab..73fe18b 100644 --- a/source/dub/generators/build.d +++ b/source/dub/generators/build.d @@ -285,12 +285,68 @@ buildWithCompiler(settings, cbuildsettings); target_binary_path = getTargetPath(cbuildsettings, settings); - if (!settings.tempBuild) + if (!settings.tempBuild) { copyTargetFile(target_path, buildsettings, settings); + updateCacheDatabase(settings, cbuildsettings, pack, config, build_id, target_binary_path.toNativeString()); + } return false; } + private void updateCacheDatabase(GeneratorSettings settings, BuildSettings buildsettings, in Package pack, string config, + string build_id, string target_binary_path) + { + import dub.internal.vibecompat.data.json; + import core.time : seconds; + + // Generate a `db.json` in the package version cache directory. + // This is read by 3rd party software (e.g. Meson) in order to find + // relevant build artifacts in Dub's cache. + + enum jsonFileName = "db.json"; + enum lockFileName = "db.lock"; + + const pkgCacheDir = packageCache(settings.cache, pack); + auto lock = lockFile((pkgCacheDir ~ lockFileName).toNativeString(), 3.seconds); + + const dbPath = pkgCacheDir ~ jsonFileName; + const dbPathStr = dbPath.toNativeString(); + Json db; + if (exists(dbPathStr)) { + const text = stripUTF8Bom(cast(string)readFile(dbPath)); + db = parseJsonString(text, dbPathStr); + enforce(db.type == Json.Type.array, "Expected a JSON array in " ~ dbPathStr); + } + else { + db = Json.emptyArray; + } + + foreach_reverse (entry; db) { + if (entry["buildId"].get!string == build_id) { + // duplicate + return; + } + } + + Json entry = Json.emptyObject; + + entry["architecture"] = serializeToJson(settings.platform.architecture); + entry["buildId"] = build_id; + entry["buildType"] = settings.buildType; + entry["compiler"] = settings.platform.compiler; + entry["compilerBinary"] = settings.platform.compilerBinary; + entry["compilerVersion"] = settings.platform.compilerVersion; + entry["configuration"] = config; + entry["package"] = pack.name; + entry["platform"] = serializeToJson(settings.platform.platform); + entry["targetBinaryPath"] = target_binary_path; + entry["version"] = pack.version_.toString(); + + db ~= entry; + + writeFile(dbPath, representation(db.toPrettyString())); + } + private void performRDMDBuild(GeneratorSettings settings, ref BuildSettings buildsettings, in Package pack, string config, out NativePath target_path) { auto cwd = settings.toolWorkingDirectory; diff --git a/source/dub/generators/generator.d b/source/dub/generators/generator.d index 8ff77d0..5816058 100644 --- a/source/dub/generators/generator.d +++ b/source/dub/generators/generator.d @@ -771,9 +771,15 @@ /** * Compute and returns the path were artifacts are stored for a given package * - * Artifacts are usually stored in: + * Artifacts are stored in: * `$DUB_HOME/cache/$PKG_NAME/$PKG_VERSION[/+$SUB_PKG_NAME]/` * Note that the leading `+` in the sub-package name is to avoid any ambiguity. + * + * Dub writes in the returned path a Json description file of the available + * artifacts in this cache location. This Json file is read by 3rd party + * software (e.g. Meson). Returned path should therefore not change across + * future Dub versions. + * * Build artifacts are usually stored in a sub-folder named "build", * as their names are based on user-supplied values. * @@ -957,7 +963,8 @@ } if (generate_binary) { - ensureDirectory(NativePath(buildsettings.targetPath)); + if (!settings.tempBuild) + ensureDirectory(NativePath(buildsettings.targetPath)); if (buildsettings.copyFiles.length) { void copyFolderRec(NativePath folder, NativePath dstfolder) diff --git a/test/d-versions.sh b/test/d-versions.sh new file mode 100755 index 0000000..f9e7da8 --- /dev/null +++ b/test/d-versions.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +. $(dirname "${BASH_SOURCE[0]}")/common.sh +cd ${CURR_DIR}/d-versions +${DUB} build --d-version=FromCli1 --d-version=FromCli2 diff --git a/test/d-versions/.gitignore b/test/d-versions/.gitignore new file mode 100644 index 0000000..d4b51c6 --- /dev/null +++ b/test/d-versions/.gitignore @@ -0,0 +1 @@ +d-versions diff --git a/test/d-versions/.no_build b/test/d-versions/.no_build new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/d-versions/.no_build diff --git a/test/d-versions/.no_run b/test/d-versions/.no_run new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/d-versions/.no_run diff --git a/test/d-versions/.no_test b/test/d-versions/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/d-versions/.no_test diff --git a/test/d-versions/dub.sdl b/test/d-versions/dub.sdl new file mode 100644 index 0000000..28fc0c9 --- /dev/null +++ b/test/d-versions/dub.sdl @@ -0,0 +1 @@ +name "d-versions" diff --git a/test/d-versions/source/app.d b/test/d-versions/source/app.d new file mode 100644 index 0000000..d9bd605 --- /dev/null +++ b/test/d-versions/source/app.d @@ -0,0 +1,16 @@ +version (FromCli1) + enum has1 = true; +else + enum has1 = false; + +version (FromCli2) + enum has2 = true; +else + enum has2 = false; + +static assert(has1); +static assert(has2); + +void main() +{ +} diff --git a/test/pr2642-cache-db/.gitignore b/test/pr2642-cache-db/.gitignore new file mode 100644 index 0000000..da01cd6 --- /dev/null +++ b/test/pr2642-cache-db/.gitignore @@ -0,0 +1,2 @@ +dubhome/ +pr2642-cache-db diff --git a/test/pr2642-cache-db/.no_test b/test/pr2642-cache-db/.no_test new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/pr2642-cache-db/.no_test diff --git a/test/pr2642-cache-db/dub.sdl b/test/pr2642-cache-db/dub.sdl new file mode 100644 index 0000000..f9fca89 --- /dev/null +++ b/test/pr2642-cache-db/dub.sdl @@ -0,0 +1,2 @@ +name "pr2642-cache-db"; +targetType "executable"; diff --git a/test/pr2642-cache-db/source/test_cache_db.d b/test/pr2642-cache-db/source/test_cache_db.d new file mode 100644 index 0000000..28a2438 --- /dev/null +++ b/test/pr2642-cache-db/source/test_cache_db.d @@ -0,0 +1,108 @@ +module test_cache_db; + +import std.path; +import std.file; +import std.process; +import std.stdio; +import std.json; + +void main() +{ + const dubhome = __FILE_FULL_PATH__.dirName().dirName().buildNormalizedPath("dubhome"); + if (exists(dubhome)) + { + rmdirRecurse(dubhome); + } + + const string[string] env = [ + "DUB_HOME": dubhome, + ]; + const fetchProgram = [ + environment["DUB"], + "fetch", + "gitcompatibledubpackage@1.0.4", + ]; + auto dubFetch = spawnProcess(fetchProgram, stdin, stdout, stderr, env); + wait(dubFetch); + + const buildProgramLib = [ + environment["DUB"], + "build", + "--build=debug", + "--config=lib", + "gitcompatibledubpackage@1.0.4", + ]; + auto dubBuild = spawnProcess(buildProgramLib, stdin, stdout, stderr, env); + wait(dubBuild); + + const buildProgramExe = [ + environment["DUB"], + "build", + "--build=debug", + "--config=exe", + "gitcompatibledubpackage@1.0.4", + ]; + dubBuild = spawnProcess(buildProgramExe, stdin, stdout, stderr, env); + wait(dubBuild); + + scope (success) + { + // leave dubhome in the tree for analysis in case of failure + rmdirRecurse(dubhome); + } + + const buildDbPath = buildNormalizedPath(dubhome, "cache", "gitcompatibledubpackage", "1.0.4", "db.json"); + assert(exists(buildDbPath), buildDbPath ~ " should exist"); + const buildDbStr = readText(buildDbPath); + auto json = parseJSON(buildDbStr); + assert(json.type == JSONType.array, "build db should be an array"); + assert(json.array.length == 2, "build db should have 2 entries"); + + auto db = json.array[0].object; + + void assertArray(string field) + { + assert(field in db, "db.json should have an array field " ~ field); + assert(db[field].type == JSONType.array, "expected field " ~ field ~ " to be an array"); + } + + void assertString(string field, string value = null) + { + assert(field in db, "db.json should have an string field " ~ field); + assert(db[field].type == JSONType.string, "expected field " ~ field ~ " to be a string"); + if (value) + assert(db[field].str == value, "expected field " ~ field ~ " to equal " ~ value); + } + + assertArray("architecture"); + assertString("buildId"); + assertString("buildType", "debug"); + assertString("compiler"); + assertString("compilerBinary"); + assertString("compilerVersion"); + assertString("configuration", "lib"); + assertString("package", "gitcompatibledubpackage"); + assertArray("platform"); + assertString("targetBinaryPath"); + assertString("version", "1.0.4"); + + auto binName = db["targetBinaryPath"].str; + assert(isFile(binName), "expected " ~ binName ~ " to be a file."); + + db = json.array[1].object; + + assertArray("architecture"); + assertString("buildId"); + assertString("buildType", "debug"); + assertString("compiler"); + assertString("compilerBinary"); + assertString("compilerVersion"); + assertString("configuration", "exe"); + assertString("package", "gitcompatibledubpackage"); + assertArray("platform"); + assertString("targetBinaryPath"); + assertString("version", "1.0.4"); + + binName = db["targetBinaryPath"].str; + assert(isFile(binName), "expected " ~ binName ~ " to be a file."); +}