Files
cmake/Source/cmQtAutoRcc.cxx
T
Joerg Bornemann d05bb5e3c7 Autogen: Fix AUTOGEN_BETTER_GRAPH_MULTI_CONFIG with VS generators
Use config-specific filenames instead of subdirectories to avoid object
file path mismatches in Visual Studio OBJECT libraries. The new paths
are consistent with how AUTOMOC generates .cpp files.

Add QtAutogen test RccObjectLibrary that fails without this patch.

Adjust the GlobalAutogenTarget test to the new paths.

Fixes: #26977
2026-01-29 08:30:57 +01:00

565 lines
18 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmQtAutoRcc.h"
#include <algorithm>
#include <string>
#include <vector>
#include <cmext/algorithm>
#include "cmCryptoHash.h"
#include "cmDuration.h"
#include "cmFileLock.h"
#include "cmFileLockResult.h"
#include "cmFileTime.h"
#include "cmProcessOutput.h"
#include "cmQtAutoGenerator.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
namespace {
/** \class cmQtAutoRccT
* \brief AUTORCC generator
*/
class cmQtAutoRccT : public cmQtAutoGenerator
{
public:
cmQtAutoRccT();
~cmQtAutoRccT() override;
cmQtAutoRccT(cmQtAutoRccT const&) = delete;
cmQtAutoRccT& operator=(cmQtAutoRccT const&) = delete;
private:
// -- Utility
bool IsMultiConfig() const { return this->MultiConfig_; }
std::string MultiConfigOutput() const;
// -- Abstract processing interface
bool InitFromInfo(InfoT const& info) override;
bool Process() override;
// -- Settings file
bool SettingsFileRead();
bool SettingsFileWrite();
// -- Tests
bool TestQrcRccFiles(bool& generate);
bool TestResources(bool& generate);
bool TestInfoFile();
// -- Generation
bool GenerateRcc();
bool GenerateWrapper();
// -- Config settings
bool MultiConfig_ = false;
bool CrossConfig_ = false;
bool UseBetterGraph_ = false;
std::string Generator_;
// -- Directories
std::string AutogenBuildDir_;
std::string IncludeDir_;
// -- Qt environment
std::string RccExecutable_;
cmFileTime RccExecutableTime_;
std::vector<std::string> RccListOptions_;
// -- Job
std::string LockFile_;
cmFileLock LockFileLock_;
std::string QrcFile_;
std::string QrcFileName_;
std::string QrcFileDir_;
cmFileTime QrcFileTime_;
std::string RccPathChecksum_;
std::string RccFileName_;
std::string RccFileOutput_;
std::string RccFilePublic_;
cmFileTime RccFileTime_;
std::string Reason;
std::vector<std::string> Options_;
std::vector<std::string> Inputs_;
// -- Settings file
std::string SettingsFile_;
std::string SettingsString_;
bool SettingsChanged_ = false;
bool BuildFileChanged_ = false;
};
cmQtAutoRccT::cmQtAutoRccT()
: cmQtAutoGenerator(GenT::RCC)
{
}
cmQtAutoRccT::~cmQtAutoRccT() = default;
bool cmQtAutoRccT::InitFromInfo(InfoT const& info)
{
// -- Required settings
if (!info.GetBool("MULTI_CONFIG", this->MultiConfig_, true) ||
!info.GetString("GENERATOR", this->Generator_, true) ||
!info.GetBool("CROSS_CONFIG", this->CrossConfig_, true) ||
!info.GetBool("USE_BETTER_GRAPH", this->UseBetterGraph_, true) ||
!info.GetString("BUILD_DIR", this->AutogenBuildDir_, true) ||
!info.GetStringConfig("INCLUDE_DIR", this->IncludeDir_, true) ||
!info.GetArrayConfig("RCC_LIST_OPTIONS", this->RccListOptions_, false) ||
!info.GetString("LOCK_FILE", this->LockFile_, true) ||
!info.GetStringConfig("SETTINGS_FILE", this->SettingsFile_, true) ||
!info.GetString("SOURCE", this->QrcFile_, true) ||
!info.GetString("OUTPUT_CHECKSUM", this->RccPathChecksum_, true) ||
!info.GetString("OUTPUT_NAME", this->RccFileName_, true) ||
!info.GetArray("OPTIONS", this->Options_, false)) {
return false;
}
// Expand $<CONFIG> in RccFileName if present (for better graph multi-config)
if (this->IsMultiConfig()) {
cmSystemTools::ReplaceString(this->RccFileName_, "$<CONFIG>",
this->InfoConfig());
}
if (this->UseBetterGraph_) {
if (!info.GetArrayConfig("INPUTS", this->Inputs_, false)) {
return false;
}
if (this->CrossConfig_) {
std::string const rccExecutableWithConfig =
"RCC_EXECUTABLE_" + this->ExecutableConfig();
if (!info.GetString(rccExecutableWithConfig, this->RccExecutable_,
true)) {
return false;
}
} else {
if (!info.GetStringConfig("RCC_EXECUTABLE", this->RccExecutable_,
true)) {
return false;
}
}
} else {
if (!info.GetString("RCC_EXECUTABLE", this->RccExecutable_, true) ||
!info.GetArray("RCC_LIST_OPTIONS", this->RccListOptions_, false) ||
!info.GetArray("INPUTS", this->Inputs_, false)) {
return false;
}
}
// -- Derive information
this->QrcFileName_ = cmSystemTools::GetFilenameName(this->QrcFile_);
this->QrcFileDir_ = cmSystemTools::GetFilenamePath(this->QrcFile_);
// For better graph multi-config, OUTPUT_NAME already includes config suffix
this->RccFilePublic_ =
cmStrCat(this->AutogenBuildDir_, '/', this->RccPathChecksum_, '/',
this->RccFileName_);
// rcc output file name
if (this->IsMultiConfig()) {
this->RccFileOutput_ =
cmStrCat(this->IncludeDir_, '/', this->MultiConfigOutput());
} else {
this->RccFileOutput_ = this->RccFilePublic_;
}
// -- Checks
if (!this->RccExecutableTime_.Load(this->RccExecutable_)) {
return info.LogError(cmStrCat("The rcc executable ",
this->MessagePath(this->RccExecutable_),
" does not exist."));
}
return true;
}
bool cmQtAutoRccT::Process()
{
if (!this->SettingsFileRead()) {
return false;
}
// Test if the rcc output needs to be regenerated
bool generate = false;
if (!this->TestQrcRccFiles(generate)) {
return false;
}
if (!generate && !this->TestResources(generate)) {
return false;
}
// Generate on demand
if (generate) {
if (!this->GenerateRcc()) {
return false;
}
} else {
// Test if the info file is newer than the output file
if (!this->TestInfoFile()) {
return false;
}
}
if (!this->GenerateWrapper()) {
return false;
}
return this->SettingsFileWrite();
}
std::string cmQtAutoRccT::MultiConfigOutput() const
{
return cmStrCat(this->RccPathChecksum_, '/',
AppendFilenameSuffix(this->RccFileName_, "_CMAKE_"));
}
bool cmQtAutoRccT::SettingsFileRead()
{
// Compose current settings strings
{
cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
auto cha = [&cryptoHash](cm::string_view value) {
cryptoHash.Append(value);
cryptoHash.Append(";");
};
cha(this->RccExecutable_);
std::for_each(this->RccListOptions_.begin(), this->RccListOptions_.end(),
cha);
cha(this->QrcFile_);
cha(this->RccPathChecksum_);
cha(this->RccFileName_);
std::for_each(this->Options_.begin(), this->Options_.end(), cha);
std::for_each(this->Inputs_.begin(), this->Inputs_.end(), cha);
this->SettingsString_ = cryptoHash.FinalizeHex();
}
// Make sure the settings file exists
if (!cmSystemTools::FileExists(this->SettingsFile_, true)) {
// Touch the settings file to make sure it exists
if (!cmSystemTools::Touch(this->SettingsFile_, true)) {
this->Log().Error(GenT::RCC,
cmStrCat("Touching the settings file ",
this->MessagePath(this->SettingsFile_),
" failed."));
return false;
}
}
// Lock the lock file
{
// Make sure the lock file exists
if (!cmSystemTools::FileExists(this->LockFile_, true)) {
if (!cmSystemTools::Touch(this->LockFile_, true)) {
this->Log().Error(GenT::RCC,
cmStrCat("Touching the lock file ",
this->MessagePath(this->LockFile_),
" failed."));
return false;
}
}
// Lock the lock file
cmFileLockResult lockResult = this->LockFileLock_.Lock(
this->LockFile_, static_cast<unsigned long>(-1));
if (!lockResult.IsOk()) {
this->Log().Error(GenT::RCC,
cmStrCat("Locking of the lock file ",
this->MessagePath(this->LockFile_),
" failed.\n", lockResult.GetOutputMessage()));
return false;
}
}
// Read old settings
{
std::string content;
if (FileRead(content, this->SettingsFile_)) {
this->SettingsChanged_ =
(this->SettingsString_ != SettingsFind(content, "rcc"));
// In case any setting changed clear the old settings file.
// This triggers a full rebuild on the next run if the current
// build is aborted before writing the current settings in the end.
if (this->SettingsChanged_) {
std::string error;
if (!FileWrite(this->SettingsFile_, "", &error)) {
this->Log().Error(GenT::RCC,
cmStrCat("Clearing of the settings file ",
this->MessagePath(this->SettingsFile_),
" failed.\n", error));
return false;
}
}
} else {
this->SettingsChanged_ = true;
}
}
return true;
}
bool cmQtAutoRccT::SettingsFileWrite()
{
// Only write if any setting changed
if (this->SettingsChanged_) {
if (this->Log().Verbose()) {
this->Log().Info(GenT::RCC,
"Writing settings file " +
this->MessagePath(this->SettingsFile_));
}
// Write settings file
std::string content = cmStrCat("rcc:", this->SettingsString_, '\n');
std::string error;
if (!FileWrite(this->SettingsFile_, content, &error)) {
this->Log().Error(GenT::RCC,
cmStrCat("Writing of the settings file ",
this->MessagePath(this->SettingsFile_),
" failed.\n", error));
// Remove old settings file to trigger a full rebuild on the next run
cmSystemTools::RemoveFile(this->SettingsFile_);
return false;
}
}
// Unlock the lock file
this->LockFileLock_.Release();
return true;
}
/// Do basic checks if rcc generation is required
bool cmQtAutoRccT::TestQrcRccFiles(bool& generate)
{
// Test if the rcc input file exists
if (!this->QrcFileTime_.Load(this->QrcFile_)) {
this->Log().Error(GenT::RCC,
cmStrCat("The resources file ",
this->MessagePath(this->QrcFile_),
" does not exist"));
return false;
}
// Test if the rcc output file exists
if (!this->RccFileTime_.Load(this->RccFileOutput_)) {
if (this->Log().Verbose()) {
this->Reason =
cmStrCat("Generating ", this->MessagePath(this->RccFileOutput_),
", because it doesn't exist, from ",
this->MessagePath(this->QrcFile_));
}
generate = true;
return true;
}
// Test if the settings changed
if (this->SettingsChanged_) {
if (this->Log().Verbose()) {
this->Reason =
cmStrCat("Generating ", this->MessagePath(this->RccFileOutput_),
", because the rcc settings changed, from ",
this->MessagePath(this->QrcFile_));
}
generate = true;
return true;
}
// Test if the rcc output file is older than the .qrc file
if (this->RccFileTime_.Older(this->QrcFileTime_)) {
if (this->Log().Verbose()) {
this->Reason = cmStrCat(
"Generating ", this->MessagePath(this->RccFileOutput_),
", because it is older than ", this->MessagePath(this->QrcFile_),
", from ", this->MessagePath(this->QrcFile_));
}
generate = true;
return true;
}
// Test if the rcc output file is older than the rcc executable
if (this->RccFileTime_.Older(this->RccExecutableTime_)) {
if (this->Log().Verbose()) {
this->Reason =
cmStrCat("Generating ", this->MessagePath(this->RccFileOutput_),
", because it is older than the rcc executable, from ",
this->MessagePath(this->QrcFile_));
}
generate = true;
return true;
}
return true;
}
bool cmQtAutoRccT::TestResources(bool& generate)
{
// Read resource files list
if (this->Inputs_.empty()) {
std::string error;
RccLister const lister(this->RccExecutable_, this->RccListOptions_);
if (!lister.list(this->QrcFile_, this->Inputs_, error,
this->Log().Verbose())) {
this->Log().Error(GenT::RCC,
cmStrCat("Listing of ",
this->MessagePath(this->QrcFile_),
" failed.\n", error));
return false;
}
}
// Check if any resource file is newer than the rcc output file
for (std::string const& resFile : this->Inputs_) {
// Check if the resource file exists
cmFileTime fileTime;
if (!fileTime.Load(resFile)) {
this->Log().Error(GenT::RCC,
cmStrCat("The resource file ",
this->MessagePath(resFile), " listed in ",
this->MessagePath(this->QrcFile_),
" does not exist."));
return false;
}
// Check if the resource file is newer than the rcc output file
if (this->RccFileTime_.Older(fileTime)) {
if (this->Log().Verbose()) {
this->Reason =
cmStrCat("Generating ", this->MessagePath(this->RccFileOutput_),
", because it is older than ", this->MessagePath(resFile),
", from ", this->MessagePath(this->QrcFile_));
}
generate = true;
break;
}
}
return true;
}
bool cmQtAutoRccT::TestInfoFile()
{
// Test if the rcc output file is older than the info file
if (this->RccFileTime_.Older(this->InfoFileTime())) {
if (this->Log().Verbose()) {
this->Log().Info(GenT::RCC,
cmStrCat("Touching ",
this->MessagePath(this->RccFileOutput_),
" because it is older than ",
this->MessagePath(this->InfoFile())));
}
// Touch build file
if (!cmSystemTools::Touch(this->RccFileOutput_, false)) {
this->Log().Error(GenT::RCC,
cmStrCat("Touching ",
this->MessagePath(this->RccFileOutput_),
" failed."));
return false;
}
this->BuildFileChanged_ = true;
}
return true;
}
bool cmQtAutoRccT::GenerateRcc()
{
// Make parent directory
if (!MakeParentDirectory(this->RccFileOutput_)) {
this->Log().Error(GenT::RCC,
cmStrCat("Could not create parent directory of ",
this->MessagePath(this->RccFileOutput_)));
return false;
}
// Compose rcc command
std::vector<std::string> cmd;
cmd.push_back(this->RccExecutable_);
cm::append(cmd, this->Options_);
cmd.emplace_back("-o");
cmd.push_back(this->RccFileOutput_);
cmd.push_back(this->QrcFile_);
// Log reason and command
if (this->Log().Verbose()) {
this->Log().Info(GenT::RCC,
cmStrCat(this->Reason,
cmHasSuffix(this->Reason, '\n') ? "" : "\n",
QuotedCommand(cmd), '\n'));
}
std::string rccStdOut;
std::string rccStdErr;
int retVal = 0;
bool result = cmSystemTools::RunSingleCommand(
cmd, &rccStdOut, &rccStdErr, &retVal, this->AutogenBuildDir_.c_str(),
cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto);
if (!result || (retVal != 0)) {
// rcc process failed
this->Log().ErrorCommand(GenT::RCC,
cmStrCat("The rcc process failed to compile\n ",
this->MessagePath(this->QrcFile_),
"\ninto\n ",
this->MessagePath(this->RccFileOutput_)),
cmd, rccStdOut + rccStdErr);
cmSystemTools::RemoveFile(this->RccFileOutput_);
return false;
}
// rcc process success
// Print rcc output
if (!rccStdOut.empty()) {
this->Log().Info(GenT::RCC, rccStdOut);
}
this->BuildFileChanged_ = true;
return true;
}
bool cmQtAutoRccT::GenerateWrapper()
{
// Generate a wrapper source file on demand
if (this->IsMultiConfig()) {
// Wrapper file content
std::string content =
cmStrCat("// This is an autogenerated configuration wrapper file.\n"
"// Changes will be overwritten.\n"
"#include <",
this->MultiConfigOutput(), ">\n");
// Compare with existing file content
bool fileDiffers = true;
{
std::string oldContents;
if (FileRead(oldContents, this->RccFilePublic_)) {
fileDiffers = (oldContents != content);
}
}
if (fileDiffers) {
// Write new wrapper file
if (this->Log().Verbose()) {
this->Log().Info(GenT::RCC,
cmStrCat("Generating RCC wrapper file ",
this->MessagePath(this->RccFilePublic_)));
}
std::string error;
if (!FileWrite(this->RccFilePublic_, content, &error)) {
this->Log().Error(GenT::RCC,
cmStrCat("Generating RCC wrapper file ",
this->MessagePath(this->RccFilePublic_),
" failed.\n", error));
return false;
}
} else if (this->BuildFileChanged_) {
// Just touch the wrapper file
if (this->Log().Verbose()) {
this->Log().Info(GenT::RCC,
cmStrCat("Touching RCC wrapper file ",
this->MessagePath(this->RccFilePublic_)));
}
if (!cmSystemTools::Touch(this->RccFilePublic_, false)) {
this->Log().Error(GenT::RCC,
cmStrCat("Touching RCC wrapper file ",
this->MessagePath(this->RccFilePublic_),
" failed."));
return false;
}
}
}
return true;
}
} // End of unnamed namespace
bool cmQtAutoRcc(cm::string_view infoFile, cm::string_view config,
cm::string_view executableConfig)
{
return cmQtAutoRccT().Run(infoFile, config, executableConfig);
}