QtAutogen: Regenerate qrc files if their input changes (#15074)

Get dependencies from the output of ``rcc --list`` if using
Qt 5.  Otherwise process the file in the same way as the
qt4_add_resources macro.

This does not work for RCC files which are generated.

The cmake_autogen build step is implemented as a PRE_BUILD step
of the target currently if possible, rather than a standalone
custom target.  This is to keep the number of (synthesized)
custom targets that appear in the UI low, but in some cases
it is necessary to fall back to a full custom target.

Fall back to a full custom target for the VS builds if autorcc
is used.
This commit is contained in:
Stephen Kelly 2014-09-17 02:42:30 +02:00
parent a29953180c
commit 6e1c359fe9
8 changed files with 345 additions and 8 deletions

View File

@ -1,5 +1,6 @@
set(AM_SOURCES @_cpp_files@ )
set(AM_RCC_SOURCES @_rcc_files@ )
set(AM_RCC_INPUTS @_qt_rcc_inputs@)
set(AM_SKIP_MOC @_skip_moc@ )
set(AM_SKIP_UIC @_skip_uic@ )
set(AM_HEADERS @_moc_headers@ )

View File

@ -166,6 +166,112 @@ static std::string getAutogenTargetDir(cmTarget const* target)
return targetDir;
}
std::string cmQtAutoGenerators::ListQt5RccInputs(cmSourceFile* sf,
cmTarget const* target,
std::vector<std::string>& depends)
{
std::string rccCommand = this->GetRccExecutable(target);
std::vector<std::string> qrcEntries;
std::vector<std::string> command;
command.push_back(rccCommand);
command.push_back("--list");
std::string absFile = cmsys::SystemTools::GetRealPath(
sf->GetFullPath().c_str());
command.push_back(absFile);
std::string output;
int retVal = 0;
bool result = cmSystemTools::RunSingleCommand(command, &output,
&retVal, 0,
cmSystemTools::OUTPUT_NONE);
if (!result || retVal)
{
std::cerr << "AUTOGEN: error: Rcc list process for " << sf->GetFullPath()
<< " failed:\n" << output << std::endl;
return std::string();
}
std::istringstream ostr(output);
std::string oline;
while(std::getline(ostr, oline))
{
if (oline.empty())
{
// The output of rcc --list contains many empty lines.
continue;
}
if (cmHasLiteralPrefix(oline, "RCC: Error in"))
{
static std::string searchString = "Cannot find file '";
std::string::size_type pos = oline.find(searchString);
if (pos == std::string::npos)
{
std::cerr << "AUTOGEN: error: Rcc lists unparsable output "
<< oline << std::endl;
return std::string();
}
pos += searchString.length();
std::string::size_type sz = oline.size() - pos - 1;
qrcEntries.push_back(oline.substr(pos, sz));
}
else
{
qrcEntries.push_back(oline);
}
}
depends.insert(depends.end(), qrcEntries.begin(), qrcEntries.end());
std::string entriesList;
const char* sep = "";
for(std::vector<std::string>::const_iterator it = qrcEntries.begin();
it != qrcEntries.end(); ++it)
{
entriesList += sep;
entriesList += *it;
sep = "@list_sep@";
}
return entriesList;
}
std::string cmQtAutoGenerators::ListQt4RccInputs(cmSourceFile* sf,
std::vector<std::string>& depends)
{
const std::string qrcContents = ReadAll(sf->GetFullPath());
cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
std::string entriesList;
const char* sep = "";
size_t offset = 0;
while (fileMatchRegex.find(qrcContents.c_str() + offset))
{
std::string qrcEntry = fileMatchRegex.match(1);
offset += qrcEntry.size();
cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
fileReplaceRegex.find(qrcEntry);
std::string tag = fileReplaceRegex.match(1);
qrcEntry = qrcEntry.substr(tag.size());
if (!cmSystemTools::FileIsFullPath(qrcEntry.c_str()))
{
qrcEntry = sf->GetLocation().GetDirectory() + "/" + qrcEntry;
}
entriesList += sep;
entriesList += qrcEntry;
sep = "@list_sep@";
depends.push_back(qrcEntry);
}
return entriesList;
}
bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target)
{
cmMakefile* makefile = target->GetMakefile();
@ -271,6 +377,61 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target)
}
}
}
#endif
std::vector<std::string> rcc_output;
if(makefile->GetLocalGenerator()->GetGlobalGenerator()->GetName() == "Ninja"
#if defined(_WIN32) && !defined(__CYGWIN__)
|| usePRE_BUILD
#endif
)
{
std::vector<cmSourceFile*> srcFiles;
target->GetConfigCommonSourceFiles(srcFiles);
for(std::vector<cmSourceFile*>::const_iterator fileIt = srcFiles.begin();
fileIt != srcFiles.end();
++fileIt)
{
cmSourceFile* sf = *fileIt;
std::string absFile = cmsys::SystemTools::GetRealPath(
sf->GetFullPath().c_str());
std::string ext = sf->GetExtension();
if (target->GetPropertyAsBool("AUTORCC"))
{
if (ext == "qrc"
&& !cmSystemTools::IsOn(sf->GetPropertyForUser("SKIP_AUTORCC")))
{
std::string basename = cmsys::SystemTools::
GetFilenameWithoutLastExtension(absFile);
std::string rcc_output_dir = target->GetSupportDirectory();
cmSystemTools::MakeDirectory(rcc_output_dir.c_str());
std::string rcc_output_file = rcc_output_dir;
rcc_output_file += "/qrc_" + basename + ".cpp";
rcc_output.push_back(rcc_output_file);
if (!cmSystemTools::IsOn(sf->GetPropertyForUser("GENERATED")))
{
if (qtMajorVersion == "5")
{
this->ListQt5RccInputs(sf, target, depends);
}
else
{
this->ListQt4RccInputs(sf, depends);
}
#if defined(_WIN32) && !defined(__CYGWIN__)
usePRE_BUILD = false;
#endif
}
}
}
}
}
#if defined(_WIN32) && !defined(__CYGWIN__)
if(usePRE_BUILD)
{
// Add the pre-build command directly to bypass the OBJECT_LIBRARY
@ -287,10 +448,29 @@ bool cmQtAutoGenerators::InitializeAutogenTarget(cmTarget* target)
else
#endif
{
cmTarget* autogenTarget = makefile->AddUtilityCommand(
cmTarget* autogenTarget = 0;
if (!rcc_output.empty())
{
makefile->AddCustomCommandToOutput(rcc_output, depends, "",
commandLines, 0,
workingDirectory.c_str(),
false, false);
cmCustomCommandLines no_commands;
autogenTarget = makefile->AddUtilityCommand(
autogenTargetName, true,
workingDirectory.c_str(), rcc_output,
no_commands, false, autogenComment.c_str());
}
else
{
autogenTarget = makefile->AddUtilityCommand(
autogenTargetName, true,
workingDirectory.c_str(), depends,
commandLines, false, autogenComment.c_str());
}
// Set target folder
const char* autogenFolder = makefile->GetCMakeInstance()->GetProperty(
"AUTOMOC_TARGETS_FOLDER");
@ -418,6 +598,8 @@ void cmQtAutoGenerators::SetupAutoGenerateTarget(cmTarget const* target)
inputFile += "/Modules/AutogenInfo.cmake.in";
std::string outputFile = targetDir;
outputFile += "/AutogenInfo.cmake";
makefile->AddDefinition("_qt_rcc_inputs",
makefile->GetDefinition("_qt_rcc_inputs_" + target->GetName()));
makefile->ConfigureFile(inputFile.c_str(), outputFile.c_str(),
false, true, false);
@ -869,9 +1051,12 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target)
std::vector<cmSourceFile*> srcFiles;
target->GetConfigCommonSourceFiles(srcFiles);
std::string qrcInputs;
const char* qrcInputsSep = "";
std::string rccFileFiles;
std::string rccFileOptions;
const char *sep = "";
const char *optionSep = "";
const char *qtVersion = makefile->GetDefinition("_target_qt_version");
@ -880,6 +1065,11 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target)
{
cmSystemTools::ExpandListArgument(opts, rccOptions);
}
std::string qtMajorVersion = makefile->GetSafeDefinition("QT_VERSION_MAJOR");
if (qtMajorVersion == "")
{
qtMajorVersion = makefile->GetSafeDefinition("Qt5Core_VERSION_MAJOR");
}
for(std::vector<cmSourceFile*>::const_iterator fileIt = srcFiles.begin();
fileIt != srcFiles.end();
@ -909,9 +1099,9 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target)
if (!rccOptions.empty())
{
rccFileFiles += sep;
rccFileFiles += optionSep;
rccFileFiles += absFile;
rccFileOptions += sep;
rccFileOptions += optionSep;
}
const char *listSep = "";
for(std::vector<std::string>::const_iterator it = rccOptions.begin();
@ -922,10 +1112,34 @@ void cmQtAutoGenerators::SetupAutoRccTarget(cmTarget const* target)
rccFileOptions += *it;
listSep = "@list_sep@";
}
sep = ";";
optionSep = ";";
std::vector<std::string> depends;
std::string entriesList;
if (!cmSystemTools::IsOn(sf->GetPropertyForUser("GENERATED")))
{
if (qtMajorVersion == "5")
{
entriesList = this->ListQt5RccInputs(sf, target, depends);
}
else
{
entriesList = this->ListQt4RccInputs(sf, depends);
}
if (entriesList.empty())
{
return;
}
}
qrcInputs += qrcInputsSep;
qrcInputs += entriesList;
qrcInputsSep = ";";
}
}
}
makefile->AddDefinition("_qt_rcc_inputs_" + target->GetName(),
cmLocalGenerator::EscapeForCMake(qrcInputs).c_str());
makefile->AddDefinition("_rcc_files",
cmLocalGenerator::EscapeForCMake(_rcc_files).c_str());
@ -1153,6 +1367,28 @@ bool cmQtAutoGenerators::ReadAutogenInfoFile(cmMakefile* makefile,
cmSystemTools::ReplaceString(*optionIt, "@list_sep@", ";");
this->RccOptions[*fileIt] = *optionIt;
}
const char *rccInputs = makefile->GetSafeDefinition("AM_RCC_INPUTS");
std::vector<std::string> rccInputLists;
cmSystemTools::ExpandListArgument(rccInputs, rccInputLists);
if (this->RccSources.size() != rccInputLists.size())
{
cmSystemTools::Error("Error processing file: ", filename.c_str());
return false;
}
for (std::vector<std::string>::iterator fileIt = this->RccSources.begin(),
inputIt = rccInputLists.begin();
fileIt != this->RccSources.end();
++fileIt, ++inputIt)
{
cmSystemTools::ReplaceString(*inputIt, "@list_sep@", ";");
std::vector<std::string> rccInputFiles;
cmSystemTools::ExpandListArgument(*inputIt, rccInputFiles);
this->RccInputs[*fileIt] = rccInputFiles;
}
}
this->CurrentCompileSettingsStr = this->MakeCompileSettingsString(makefile);
@ -2075,6 +2311,25 @@ bool cmQtAutoGenerators::GenerateUi(const std::string& realName,
return false;
}
bool cmQtAutoGenerators::InputFilesNewerThanQrc(const std::string& qrcFile,
const std::string& rccOutput)
{
std::vector<std::string> const& files = this->RccInputs[qrcFile];
for (std::vector<std::string>::const_iterator it = files.begin();
it != files.end(); ++it)
{
int inputNewerThanQrc = 0;
bool success = cmsys::SystemTools::FileTimeCompare(it->c_str(),
rccOutput.c_str(),
&inputNewerThanQrc);
if (!success || inputNewerThanQrc >= 0)
{
return true;
}
}
return false;
}
bool cmQtAutoGenerators::GenerateQrc()
{
for(std::vector<std::string>::const_iterator si = this->RccSources.begin();
@ -2097,10 +2352,14 @@ bool cmQtAutoGenerators::GenerateQrc()
+ ".dir/qrc_" + basename + ".cpp";
int sourceNewerThanQrc = 0;
bool success = cmsys::SystemTools::FileTimeCompare(*si,
rcc_output_file,
bool generateQrc = !cmsys::SystemTools::FileTimeCompare(*si,
rcc_output_file.c_str(),
&sourceNewerThanQrc);
if (this->GenerateAll || !success || sourceNewerThanQrc >= 0)
generateQrc = generateQrc || (sourceNewerThanQrc >= 0);
generateQrc = generateQrc || this->InputFilesNewerThanQrc(*si,
rcc_output_file);
if (this->GenerateAll || generateQrc)
{
std::map<std::string, std::string>::const_iterator optionIt
= this->RccOptions.find(*si);

View File

@ -88,6 +88,15 @@ private:
std::string GetRccExecutable(cmTarget const* target);
std::string ListQt5RccInputs(cmSourceFile* sf, cmTarget const* target,
std::vector<std::string>& depends);
std::string ListQt4RccInputs(cmSourceFile* sf,
std::vector<std::string>& depends);
bool InputFilesNewerThanQrc(const std::string& qrcFile,
const std::string& rccOutput);
std::string QtMajorVersion;
std::string Sources;
std::vector<std::string> RccSources;
@ -118,6 +127,7 @@ private:
std::vector<std::string> UicTargetOptions;
std::map<std::string, std::string> UicOptions;
std::map<std::string, std::string> RccOptions;
std::map<std::string, std::vector<std::string> > RccInputs;
bool Verbose;
bool ColorOutput;

View File

@ -113,3 +113,35 @@ set_target_properties(no_link_language PROPERTIES AUTOMOC TRUE)
qtx_wrap_cpp(uicOnlyMoc sub/uiconly.h)
add_executable(uiconly sub/uiconly.cpp ${uicOnlyMoc})
target_link_libraries(uiconly ${QT_LIBRARIES})
try_compile(RCC_DEPENDS
"${CMAKE_CURRENT_BINARY_DIR}/autorcc_depends"
"${CMAKE_CURRENT_SOURCE_DIR}/autorcc_depends"
autorcc_depends
CMAKE_FLAGS "-DQT_QMAKE_EXECUTABLE:FILEPATH=${QT_QMAKE_EXECUTABLE}" "-DQT_TEST_VERSION=${QT_TEST_VERSION}"
OUTPUT_VARIABLE output
)
if (NOT RCC_DEPENDS)
message(SEND_ERROR "Initial build of autorcc_depends failed. Output: ${output}")
endif()
file(STRINGS "${CMAKE_CURRENT_BINARY_DIR}/autorcc_depends/info_file.txt" qrc_files)
list(GET qrc_files 0 qrc_file1)
set(timeformat "%Y%j%H%M%S")
file(TIMESTAMP "${qrc_file1}" file1_before "${timeformat}")
execute_process(COMMAND "${CMAKE_COMMAND}" -E sleep 1) # Ensure that the timestamp will change.
execute_process(COMMAND "${CMAKE_COMMAND}" -E touch "${CMAKE_CURRENT_SOURCE_DIR}/autorcc_depends/res1_input.txt")
execute_process(COMMAND "${CMAKE_COMMAND}" --build .
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/autorcc_depends"
)
file(TIMESTAMP "${qrc_file1}" file1_step1 "${timeformat}")
if (NOT file1_step1 GREATER file1_before)
message(SEND_ERROR "file1 (${qrc_file1}) should have changed in the first step!")
endif()

View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 2.8)
project(autorcc_depends)
set(CMAKE_AUTORCC ON)
if (QT_TEST_VERSION STREQUAL 4)
find_package(Qt4 REQUIRED)
set(QT_CORE_TARGET Qt4::QtCore)
else()
if (NOT QT_TEST_VERSION STREQUAL 5)
message(SEND_ERROR "Invalid Qt version specified.")
endif()
find_package(Qt5Core REQUIRED)
set(QT_CORE_TARGET Qt5::Core)
endif()
add_executable(test_res1
test_res1.cpp
res1.qrc
)
target_link_libraries(test_res1 ${QT_CORE_TARGET})
add_custom_command(TARGET test_res1 POST_BUILD COMMAND
${CMAKE_COMMAND} -E echo "$<TARGET_FILE:test_res1>" > info_file.txt)

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>res1_input.txt</file>
</qresource>
</RCC>

View File

@ -0,0 +1 @@
Res1 input.

View File

@ -0,0 +1,5 @@
int main()
{
return 0;
}