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. */