From a209c10f1e40b30a02b0217f5229f3d43ca3957c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 3 Apr 2022 14:10:34 +0200 Subject: [PATCH] [wip] Utility: gather resource dependencies by executing corrade-rc. And not by reimplementing the conf file parsing with CMake. This will allow for much easier modifications to the resource file format, such as shorthand lists or directory globbing. TODO: but of course it would work only in projects using Corrade and not in Corrade itself! Because guess what, at the time I need to gather dependencies, I don't yet have the corrade-rc executable built. So I can't use it in execute_process(), but only later in add_custom_command(), which means it can't know the dependency tree until it actually starts building stuff and that's quite shit. TODO: there's a DEPFILE option which could solve this, but its support is sparse and mostly just in newer CMakes. So a no-go. TODO: or I would need to keep the nasty regex in there for when using corrade_add_resource() right inside Corrade, and switch to the clean option only in dependent projects, but that doesn't fix the problem of having to maintain two different parsers for the same thing. TODO: this also needs a changelog entry --- modules/UseCorrade.cmake | 24 ++++++------ src/Corrade/Utility/CMakeLists.txt | 1 + .../Utility/Implementation/ResourceCompile.h | 34 ++++++++++++++++ .../Utility/Test/ResourceCompileTest.cpp | 35 ++++++++++++++++- src/Corrade/Utility/rc.cpp | 39 +++++++++++++++++-- 5 files changed, 115 insertions(+), 18 deletions(-) diff --git a/modules/UseCorrade.cmake b/modules/UseCorrade.cmake index ca7bd5f1b..f8586fc21 100644 --- a/modules/UseCorrade.cmake +++ b/modules/UseCorrade.cmake @@ -506,19 +506,17 @@ function(corrade_add_resource name configurationFile) message(FATAL_ERROR "The Corrade::rc target, needed by corrade_add_resource() and corrade_add_static_plugin(), doesn't exist. Add the Utility / rc component to your find_package() or enable WITH_UTILITY / WITH_RC if you have Corrade as a CMake subproject.") endif() - # Parse dependencies from the file - set(dependencies ) - set(filenameRegex "^[ \t]*filename[ \t]*=[ \t]*\"?([^\"]+)\"?[ \t]*$") - get_filename_component(configurationFilePath ${configurationFile} PATH) - - file(STRINGS "${configurationFile}" files REGEX ${filenameRegex} ENCODING UTF-8) - foreach(file ${files}) - string(REGEX REPLACE ${filenameRegex} "\\1" filename "${file}") - if(NOT IS_ABSOLUTE "${filename}" AND configurationFilePath) - set(filename "${configurationFilePath}/${filename}") - endif() - list(APPEND dependencies "${filename}") - endforeach() + # Get dependency information for proper incremental rebuilds + execute_process( + # TODO yeah can't use a target name here, and of course what would this + # do if the corrade-rc binary isn't even built yet?! + COMMAND Corrade::rc ${name} --dependencies ${configurationFile} + RESULT_VARIABLE dependencies + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + message(STATUS "eyy" ${dependencies}) + # In the unlikely case where there would be ; in the filenames themselves + string(REGEX REPLACE ";" "\\\\;" dependencies "${dependencies}") + string(REGEX REPLACE "\n" ";" dependencies "${dependencies}") # Force IDEs display also the resource files in project view add_custom_target(${name}-dependencies SOURCES ${dependencies}) diff --git a/src/Corrade/Utility/CMakeLists.txt b/src/Corrade/Utility/CMakeLists.txt index 43a99521c..2d525c38f 100644 --- a/src/Corrade/Utility/CMakeLists.txt +++ b/src/Corrade/Utility/CMakeLists.txt @@ -212,6 +212,7 @@ if(NOT CMAKE_CROSSCOMPILING) Debug.cpp Configuration.cpp ConfigurationGroup.cpp + ConfigurationValue.cpp Format.cpp Path.cpp String.cpp diff --git a/src/Corrade/Utility/Implementation/ResourceCompile.h b/src/Corrade/Utility/Implementation/ResourceCompile.h index 45669d137..7a8fd2728 100644 --- a/src/Corrade/Utility/Implementation/ResourceCompile.h +++ b/src/Corrade/Utility/Implementation/ResourceCompile.h @@ -32,6 +32,8 @@ #include #include +#include "Corrade/Containers/Array.h" +#include "Corrade/Containers/GrowableArray.h" #include "Corrade/Containers/Optional.h" #include "Corrade/Containers/Pair.h" #include "Corrade/Utility/Configuration.h" @@ -248,6 +250,38 @@ std::string resourceCompileFrom(const std::string& name, const std::string& conf return resourceCompile(name, group, fileData); } +Containers::Optional> resourceDependencies(const Containers::StringView configurationFile) { + if(!Path::exists(configurationFile)) { + Error() << " Error: file" << configurationFile << "does not exist"; + return {}; + } + + const Containers::StringView path = Path::split(configurationFile).first(); + const Configuration conf(configurationFile, Configuration::Flag::ReadOnly); + + /* A subset of what's done in resourceCompileFrom(), keep in sync */ + std::vector files = conf.groups("file"); + Containers::Array out; + arrayReserve(out, files.size()); + for(const auto file: files) { + const Containers::StringView filename = file->value("filename"); + + /* This would mean printing a whole directory on the output with + unforeseeable consequences for unsuspecting build systems, so fail + in that case */ + if(!filename) { + Error() << " Error: filename of file" << out.size() + 1 << "in group" << conf.value("group") << "is empty"; + return {}; + } + + /* Absolute paths to keep it as simple as possible for the consumers */ + arrayAppend(out, Path::join(path, filename)); + } + + /* GCC 4.8 and Clang 3.8 need extra help here */ + return Containers::optional(std::move(out)); +} + }}}} #endif diff --git a/src/Corrade/Utility/Test/ResourceCompileTest.cpp b/src/Corrade/Utility/Test/ResourceCompileTest.cpp index 434e48120..8524cb5bc 100644 --- a/src/Corrade/Utility/Test/ResourceCompileTest.cpp +++ b/src/Corrade/Utility/Test/ResourceCompileTest.cpp @@ -116,6 +116,13 @@ void ResourceCompileTest::compileFrom() { CORRADE_COMPARE_AS(Implementation::resourceCompileFrom("ResourceTestData", conf), Path::join(RESOURCE_TEST_DIR, "compiled.cpp"), TestSuite::Compare::StringToFile); + + Containers::Optional> dependencies = Implementation::resourceDependencies(conf); + CORRADE_VERIFY(dependencies); + CORRADE_COMPARE_AS(*dependencies, Containers::arrayView({ + Path::join(RESOURCE_TEST_DIR, "../ResourceTestFiles/predisposition.bin"), + Path::join(RESOURCE_TEST_DIR, "consequence.bin") + }), TestSuite::Compare::Container); } void ResourceCompileTest::compileFromNothing() { @@ -123,6 +130,11 @@ void ResourceCompileTest::compileFromNothing() { CORRADE_COMPARE_AS(Implementation::resourceCompileFrom("ResourceTestNothingData", conf), Path::join(RESOURCE_TEST_DIR, "compiled-nothing.cpp"), TestSuite::Compare::StringToFile); + + Containers::Optional> dependencies = Implementation::resourceDependencies(conf); + CORRADE_VERIFY(dependencies); + CORRADE_COMPARE_AS(*dependencies, Containers::arrayView({ + }), TestSuite::Compare::Container); } void ResourceCompileTest::compileFromUtf8Filenames() { @@ -130,18 +142,35 @@ void ResourceCompileTest::compileFromUtf8Filenames() { CORRADE_COMPARE_AS(Implementation::resourceCompileFrom("ResourceTestUtf8Data", conf), Path::join(RESOURCE_TEST_DIR, "compiled-unicode.cpp"), TestSuite::Compare::StringToFile); + + Containers::Optional> dependencies = Implementation::resourceDependencies(conf); + CORRADE_VERIFY(dependencies); + CORRADE_COMPARE_AS(*dependencies, Containers::arrayView({ + Path::join(RESOURCE_TEST_DIR, "hýždě.bin") + }), TestSuite::Compare::Container); } void ResourceCompileTest::compileFromNonexistentResource() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(Implementation::resourceCompileFrom("ResourceTestData", "nonexistent.conf").empty()); - CORRADE_COMPARE(out.str(), " Error: file nonexistent.conf does not exist\n"); + CORRADE_VERIFY(!Implementation::resourceDependencies("nonexistent.conf")); + CORRADE_COMPARE(out.str(), + " Error: file nonexistent.conf does not exist\n" + " Error: file nonexistent.conf does not exist\n"); } void ResourceCompileTest::compileFromNonexistentFile() { Containers::String conf = Path::join(RESOURCE_TEST_DIR, "resources-nonexistent.conf"); + /* In this case the file existence is not checked, as the file could an + output of another buildsystem job */ + Containers::Optional> dependencies = Implementation::resourceDependencies(conf); + CORRADE_VERIFY(dependencies); + CORRADE_COMPARE_AS(*dependencies, Containers::arrayView({ + "/nonexistent.dat" + }), TestSuite::Compare::Container); + std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(Implementation::resourceCompileFrom("ResourceTestData", conf).empty()); @@ -152,6 +181,8 @@ void ResourceCompileTest::compileFromNonexistentFile() { } void ResourceCompileTest::compileFromEmptyGroup() { + /* Group name has no effect on dependency lists, not testing */ + std::ostringstream out; Error redirectError{&out}; @@ -175,6 +206,8 @@ void ResourceCompileTest::compileFromEmptyFilename() { } void ResourceCompileTest::compileFromEmptyAlias() { + /* Alias name has no effect on dependency lists, not testing */ + std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(Implementation::resourceCompileFrom("ResourceTestData", diff --git a/src/Corrade/Utility/rc.cpp b/src/Corrade/Utility/rc.cpp index e616cef50..968aa52fb 100644 --- a/src/Corrade/Utility/rc.cpp +++ b/src/Corrade/Utility/rc.cpp @@ -66,8 +66,16 @@ corrade-rc [-h|--help] [--] name resources.conf output.cpp - `name` --- exported symbol name - `resources.conf` --- resource configuration file (see @ref Utility::Resource for format description) -- `output.cpp` --- output file +- `output.cpp` --- output file; ignored if `--dependencies` is present +- `--dependencies` --- print a list of files the compiled resource is made of + and exit - `-h`, `--help` --- display this help message and exit + +The `--dependencies` option is meant to be used by build systems to track +transitive dependencies. It prints one file path per line with the path to the +`resources.conf` prepended -- if it's passed as an absolute path, the printed +paths are absolute, if not then relative. The configuration file itself is not +a part of the printed list. */ } @@ -79,11 +87,34 @@ int main(int argc, char** argv) { Utility::Arguments args; args.addArgument("name").setHelp("name", "exported symbol name") .addArgument("conf").setHelp("conf", "resource configuration file", "resources.conf") - .addArgument("output").setHelp("output", "output file", "output.cpp") - .setCommand("corrade-rc") - .setGlobalHelp("Corrade resource compiler.") + .addArgument("output").setHelp("output", "output file; ignored if --dependencies is present", "output.cpp") + .addBooleanOption("dependencies").setHelp("dependencies", "print a list of files the compiled resource is made of and exit") + .setParseErrorCallback([](const Utility::Arguments& args, Utility::Arguments::ParseError error, const std::string& key) { + /* If --info is passed, we don't need the output argument */ + if(error == Utility::Arguments::ParseError::MissingArgument && + key == "output" && args.isSet("dependencies")) return true; + + /* Handle all other errors as usual */ + return false; + }) + .setGlobalHelp(R"(Corrade resource compiler. + +The --dependencies option is meant to be used by build systems to track +transitive dependencies. It prints one file path per line with the path to the +resources.conf prepended -- if it's passed as an absolute path, the printed +paths are absolute, if not then relative. The configuration file itself is not +a part of the printed list.)") .parse(argc, argv); + /* Print dependencies and exit, if requested */ + if(args.isSet("dependencies")) { + Containers::Optional> dependencies = Utility::Implementation::resourceDependencies(args.value("conf")); + if(!dependencies) return 4; + for(const Containers::String& filename: *dependencies) + Utility::Debug{} << filename; + return 0; + } + /* Remove previous output file. Only if it exists, to not print an error message when compiling for the first time. If it fails, die as well -- we'd not succeed after either. */