diff --git a/.github/scripts/check-deps-and-qt.sh b/.github/scripts/check-deps-and-qt.sh index 9f68db9..30d64fe 100644 --- a/.github/scripts/check-deps-and-qt.sh +++ b/.github/scripts/check-deps-and-qt.sh @@ -132,12 +132,25 @@ qt_consumer() { cd "$ROOT/tests/qt-consumer" rm -rf target mkdir -p target/ci - "$MCPP" build 2>&1 | tee target/ci/build.log + "$MCPP" build -v 2>&1 | tee target/ci/build.log + # lupdate writes the `.ts` in the package root and lrelease reads it after; + # neither claims the package's directory as a construction output. + local lu lr + lu=$(grep -n '/lupdate[^ ]* ' target/ci/build.log | head -1 | cut -d: -f1) + lr=$(grep -n '/lrelease[^ ]* ' target/ci/build.log | head -1 | cut -d: -f1) + [ -n "$lu" ] && [ -n "$lr" ] && [ "$lu" -lt "$lr" ] || fail "lupdate did not run before lrelease" + ! grep -q 'output_dir' target/ci/build.log || fail "the build reports a construction directory over the package's sources" + echo "ok: lupdate updated the .ts before lrelease read it" "$MCPP" run | tee target/ci/run.log grep -qE "^qt-consumer: signal 42, resource 'greetings from rcc', translation 'hallo', Qt 6\." target/ci/run.log || fail "moc, rcc or lrelease did not reach the program" find target -name 'qt_consumer_de.qm' | grep -q . || fail "no .qm was produced" echo "ok: moc (header and inline), rcc and lrelease reached the program" + "$MCPP" build --profile dev -v > target/ci/second-build.log 2>&1 || + { cat target/ci/second-build.log; fail "the second build failed"; } + ! grep -qE '/(lupdate|lrelease)[^ ]* ' target/ci/second-build.log || + fail "a second build with nothing changed re-ran lupdate or lrelease" + echo "ok: a second build with nothing changed ran neither lupdate nor lrelease" } qt_widgets_consumer() { diff --git a/README.md b/README.md index ef01523..369e107 100644 --- a/README.md +++ b/README.md @@ -852,7 +852,7 @@ CI measures the rest: ```toml [build-dependencies.mcpp] -plugins = { version = "0.13.0", features = ["deps-vcpkg"], host-module = true, tools = ["mcpp-deps"] } +plugins = { version = "0.13.1", features = ["deps-vcpkg"], host-module = true, tools = ["mcpp-deps"] } ``` ```cpp @@ -944,7 +944,7 @@ default unless `cache_args` names another. ```toml [build-dependencies.mcpp] -plugins = { version = "0.13.0", features = ["rules-qt-xim"], host-module = true } +plugins = { version = "0.13.1", features = ["rules-qt-xim"], host-module = true } [build] sources = ["src/*.cpp", "res/*.qrc", "i18n/*.ts", "ui/*.ui"] @@ -974,7 +974,7 @@ int main() { | `private_modules` | modules whose private headers are included | | `moc`, `moc_headers` | `moc_scan::project_headers` (default) scans the package's headers by content; `moc_scan::listed` takes `moc_headers` only | | `forms`, `resources` | `.ui` and `.qrc` files beside those in `[build] sources` | -| `i18n` | `.ts` files beside those in `[build] sources`; `update_sources` runs `lupdate` as a `prepare` action, whose output directory is the `.ts` files', before `lrelease`; `tr_function_alias`; `deploy_to` (default `translations`); `out_dir`, where `lrelease` writes (default `/qt/translations`) | +| `i18n` | `.ts` files beside those in `[build] sources`; `update_sources` runs `lupdate` before `lrelease`, as an action whose output is the `.ts` file it rewrites; `tr_function_alias`; `deploy_to` (default `translations`); `out_dir`, where `lrelease` writes (default `/qt/translations`) | | `deploy_plugins` | plugin directories placed beside the program; default `platforms` | | `deploy_software_gl` | Windows: `opengl32sw.dll` and `d3dcompiler_47.dll` beside the program | | `root`, `extra_roots` | an SDK, and further prefixes | diff --git a/mcpp.toml b/mcpp.toml index 4b768b3..b512b0c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] name = "plugins" namespace = "mcpp" -version = "0.13.0" +version = "0.13.1" description = "Official mcpp build plugins: rule packages under mcpp.rules.*, build-time utilities under mcpp.tools.*, each member selected by a feature" license = "Apache-2.0" authors = ["mcpp-community"] @@ -653,7 +653,7 @@ main = "tools/embed_main.cpp" # it by a release. # # [build-dependencies.mcpp] -# plugins = { version = "0.13.0", features = ["deps-vcpkg"], +# plugins = { version = "0.13.1", features = ["deps-vcpkg"], # host-module = true, tools = ["mcpp-deps"] } [targets.mcpp-deps] kind = "bin" diff --git a/rules/qt.cppm b/rules/qt.cppm index 79ed13c..aff30a1 100644 --- a/rules/qt.cppm +++ b/rules/qt.cppm @@ -34,8 +34,9 @@ // declares nothing, so the plan succeeds and the build is where it fails. // // `lupdate` REWRITES SOURCES, so it is off unless `translations::update_sources` -// asks for it; then it is a `prepare` action whose stamp `lrelease` waits for, -// the order Qt's Visual Studio integration runs them in. +// asks for it; then it is an action whose output is the `.ts` file itself, and +// `lrelease` reads that file, the order Qt's Visual Studio integration runs +// them in. module; #include @@ -447,7 +448,7 @@ inline bool compile(options opt = {}) { const std::string desc = "MOC " + in.filename().string(); mcpp::action a; a.id = id.c_str(); - a.role = "source"; + a.role = mcpp::roles::source; a.description = desc.c_str(); a.depfile = dep.c_str(); a.arg(moc.c_str()).arg(src.c_str()).arg("-o").arg(out.c_str()) @@ -477,7 +478,7 @@ inline bool compile(options opt = {}) { const std::string desc = "UIC " + fs::path(f).filename().string(); mcpp::action a; a.id = id.c_str(); - a.role = "source"; + a.role = mcpp::roles::source; a.description = desc.c_str(); a.arg(uic.c_str()).arg(in.c_str()).arg("-o").arg(out.c_str()) .input(in.c_str()).output(out.c_str()).submit(); @@ -505,7 +506,7 @@ inline bool compile(options opt = {}) { mcpp::rerun_if_changed(in.c_str()); mcpp::action a; a.id = id.c_str(); - a.role = "source"; + a.role = mcpp::roles::source; a.description = desc.c_str(); a.arg(rcc.c_str()).arg("--name").arg(stem.c_str()).arg(in.c_str()).arg("-o").arg(out.c_str()) .input(in.c_str()); @@ -545,37 +546,29 @@ inline bool compile(options opt = {}) { const fs::path qmDir = opt.i18n.out_dir.empty() ? gen / "translations" : detail::absolute_from_root(opt.i18n.out_dir); const std::string qm = generic(qmDir / (stem + ".qm")); - std::string stamp; if (opt.i18n.update_sources) { - stamp = generic(gen / (stem + ".lupdate.stamp")); const std::string id = "qt:lupdate:" + stem; const std::string desc = "LUPDATE " + file.filename().string(); mcpp::action u; u.id = id.c_str(); - // Construction, not validation (SPEC-007 R3.4): lupdate - // rewrites the `.ts` files in their directory, which lrelease - // reads, so it is a `prepare` whose output directory is theirs. - u.role = mcpp::roles::prepare; + // The file lupdate writes is named before it runs, so the action + // names it as its output (SPEC-007 R3.2) and needs neither a + // stamp nor a `prepare` directory. `lrelease` takes the same file + // as its input, which orders the two. + u.role = mcpp::roles::source; u.description = desc.c_str(); u.arg(lupdate.c_str()).arg("-silent").arg("-extensions").arg("cpp,h,hpp,ixx,cppm"); for (auto const& a : opt.i18n.tr_function_alias) u.arg("-tr-function-alias").arg(a.c_str()); for (auto const& s : sources) u.arg(s.c_str()).input(s.c_str()); - // The stamp is written by mcpp when lupdate succeeds (a - // prepare's command need not write its own); `lrelease` takes it as an - // input, so it reads the `.ts` lupdate has rewritten. - // `output_dir` keeps the pointer it is given, unlike `arg()`, - // so the string is a local that outlives `submit()`. - const std::string tsDir = generic(file.parent_path()); - u.arg("-ts").arg(in.c_str()).output(stamp.c_str()).output_dir(tsDir.c_str()).submit(); + u.arg("-ts").arg(in.c_str()).output(in.c_str()).submit(); } const std::string id = "qt:lrelease:" + stem; const std::string desc = "LRELEASE " + file.filename().string(); mcpp::action r; r.id = id.c_str(); - r.role = "source"; + r.role = mcpp::roles::source; r.description = desc.c_str(); r.arg(lrelease.c_str()).arg("-silent").arg(in.c_str()).arg("-qm").arg(qm.c_str()).input(in.c_str()); - if (!stamp.empty()) r.input(stamp.c_str()); r.output(qm.c_str()).submit(); mcpp::deploy(qm.c_str(), opt.i18n.deploy_to.c_str()); } diff --git a/src/plugins.cppm b/src/plugins.cppm index 1c5302d..3e084f1 100644 --- a/src/plugins.cppm +++ b/src/plugins.cppm @@ -49,7 +49,7 @@ export namespace mcpp::plugins { // // One package, one version: the number lives in mcpp.toml, and the CI step // `the collection states its own version` compares the two. -inline constexpr std::string_view version = "0.13.0"; +inline constexpr std::string_view version = "0.13.1"; } // namespace mcpp::plugins diff --git a/tests/qt-consumer/build.mcpp b/tests/qt-consumer/build.mcpp index cda072a..fe2f62d 100644 --- a/tests/qt-consumer/build.mcpp +++ b/tests/qt-consumer/build.mcpp @@ -7,5 +7,8 @@ int main() { mcpp::rules::qt::options o; o.modules = { "Core" }; o.deploy_plugins = {}; + // lupdate brings the `.ts` up to date with src/ before lrelease reads it. + // The committed file is lupdate's own output, so a build leaves it as is. + o.i18n.update_sources = true; return mcpp::rules::qt::compile(o) ? 0 : 1; } diff --git a/tests/qt-consumer/mcpp.toml b/tests/qt-consumer/mcpp.toml index 7e60890..e818f4b 100644 --- a/tests/qt-consumer/mcpp.toml +++ b/tests/qt-consumer/mcpp.toml @@ -8,9 +8,12 @@ # src/counter.h declares a Q_OBJECT class -> moc # src/main.cpp includes "main.moc" -> moc, inline # res/app.qrc names res/greeting.txt -> rcc -# i18n/qt_consumer_de.ts -> lrelease, deployed +# qt_consumer_de.ts updated from src/ by lupdate -> lrelease, deployed # under translations/ # +# The `.ts` sits in the package root, as it does in GalTranslPP, so lupdate +# writes one file there and claims no directory. +# # and the program prints what each produced: a signal delivered to a slot, a # resource read through `:/`, and a string translated by the `.qm` it loaded # from beside itself. `widgets/` is the second fixture, for Qt Widgets, `.ui` @@ -39,7 +42,7 @@ plugins = { path = "../..", features = ["rules-qt-xim"], host-module = true } # on the MSVC ABI the static CRT; a PE image resolves its imports per DLL, so # Qt's DLLs keep their own CRT. cxx_runtime = "toolchain-coupled" -sources = ["src/*.cpp", "res/*.qrc", "i18n/*.ts"] +sources = ["src/*.cpp", "res/*.qrc", "*.ts"] [targets.qt-consumer] kind = "bin" diff --git a/tests/qt-consumer/i18n/qt_consumer_de.ts b/tests/qt-consumer/qt_consumer_de.ts similarity index 81% rename from tests/qt-consumer/i18n/qt_consumer_de.ts rename to tests/qt-consumer/qt_consumer_de.ts index 45ed799..08e26d3 100644 --- a/tests/qt-consumer/i18n/qt_consumer_de.ts +++ b/tests/qt-consumer/qt_consumer_de.ts @@ -4,6 +4,7 @@ main + hello hallo