CMake/Source/cmQtAutoGenerator.cxx
Alexey Edelev e5ec0e52f4 AUTOUIC: Fix generating of dependency rules for UI header files
We could not rely on .ui files when generating the ninja rules
for the generated UI header files. .ui files might be added to the
target sources but never processed by AUTOUIC afterward, since UI
header files are never included in a source code. Instead of adding
dependency rules based on the .ui files, this approach scans
non-generated source files for includes of the UI header files,
as AUTOUIC does. This gives the consistent set of UI header files
at configure time, that could be used to generate byproducts rules
for the AUTOUIC. Also, the path to the generated UI header file depends
not on the .ui file location but on the include line is used in source
files.

Fixes: #16776
2021-07-23 15:37:31 +02:00

459 lines
13 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmQtAutoGenerator.h"
#include <cm3p/json/reader.h>
#include "cmsys/FStream.hxx"
#include "cmQtAutoGen.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
cmQtAutoGenerator::Logger::Logger()
{
// Initialize logger
{
std::string verbose;
if (cmSystemTools::GetEnv("VERBOSE", verbose) && !verbose.empty()) {
unsigned long iVerbose = 0;
if (cmStrToULong(verbose, &iVerbose)) {
this->SetVerbosity(static_cast<unsigned int>(iVerbose));
} else {
// Non numeric verbosity
this->SetVerbose(cmIsOn(verbose));
}
}
}
{
std::string colorEnv;
cmSystemTools::GetEnv("COLOR", colorEnv);
if (!colorEnv.empty()) {
this->SetColorOutput(cmIsOn(colorEnv));
} else {
this->SetColorOutput(true);
}
}
}
void cmQtAutoGenerator::Logger::RaiseVerbosity(unsigned int value)
{
if (this->Verbosity_ < value) {
this->Verbosity_ = value;
}
}
void cmQtAutoGenerator::Logger::SetColorOutput(bool value)
{
this->ColorOutput_ = value;
}
std::string cmQtAutoGenerator::Logger::HeadLine(cm::string_view title)
{
return cmStrCat(title, '\n', std::string(title.size(), '-'), '\n');
}
void cmQtAutoGenerator::Logger::Info(GenT genType,
cm::string_view message) const
{
std::string msg = cmStrCat(GeneratorName(genType), ": ", message,
cmHasSuffix(message, '\n') ? "" : "\n");
{
std::lock_guard<std::mutex> lock(this->Mutex_);
cmSystemTools::Stdout(msg);
}
}
void cmQtAutoGenerator::Logger::Warning(GenT genType,
cm::string_view message) const
{
std::string msg;
if (message.find('\n') == std::string::npos) {
// Single line message
msg = cmStrCat(GeneratorName(genType), " warning: ", message,
cmHasSuffix(message, '\n') ? "\n" : "\n\n");
} else {
// Multi line message
msg = cmStrCat(HeadLine(cmStrCat(GeneratorName(genType), " warning")),
message, cmHasSuffix(message, '\n') ? "\n" : "\n\n");
}
{
std::lock_guard<std::mutex> lock(this->Mutex_);
cmSystemTools::Stdout(msg);
}
}
void cmQtAutoGenerator::Logger::Error(GenT genType,
cm::string_view message) const
{
std::string msg =
cmStrCat('\n', HeadLine(cmStrCat(GeneratorName(genType), " error")),
message, cmHasSuffix(message, '\n') ? "\n" : "\n\n");
{
std::lock_guard<std::mutex> lock(this->Mutex_);
cmSystemTools::Stderr(msg);
}
}
void cmQtAutoGenerator::Logger::ErrorCommand(
GenT genType, cm::string_view message,
std::vector<std::string> const& command, std::string const& output) const
{
std::string msg = cmStrCat(
'\n', HeadLine(cmStrCat(GeneratorName(genType), " subprocess error")),
message, cmHasSuffix(message, '\n') ? "\n" : "\n\n");
msg += cmStrCat(HeadLine("Command"), QuotedCommand(command), "\n\n");
msg += cmStrCat(HeadLine("Output"), output,
cmHasSuffix(output, '\n') ? "\n" : "\n\n");
{
std::lock_guard<std::mutex> lock(this->Mutex_);
cmSystemTools::Stderr(msg);
}
}
bool cmQtAutoGenerator::MakeParentDirectory(std::string const& filename)
{
bool success = true;
std::string const dirName = cmSystemTools::GetFilenamePath(filename);
if (!dirName.empty()) {
success = static_cast<bool>(cmSystemTools::MakeDirectory(dirName));
}
return success;
}
bool cmQtAutoGenerator::FileWrite(std::string const& filename,
std::string const& content,
std::string* error)
{
// Make sure the parent directory exists
if (!cmQtAutoGenerator::MakeParentDirectory(filename)) {
if (error != nullptr) {
*error = "Could not create parent directory.";
}
return false;
}
cmsys::ofstream ofs;
ofs.open(filename.c_str(),
(std::ios::out | std::ios::binary | std::ios::trunc));
// Use lambda to save destructor calls of ofs
return [&ofs, &content, error]() -> bool {
if (!ofs) {
if (error != nullptr) {
*error = "Opening file for writing failed.";
}
return false;
}
ofs << content;
if (!ofs.good()) {
if (error != nullptr) {
*error = "File writing failed.";
}
return false;
}
return true;
}();
}
bool cmQtAutoGenerator::FileDiffers(std::string const& filename,
std::string const& content)
{
bool differs = true;
std::string oldContents;
if (FileRead(oldContents, filename) && (oldContents == content)) {
differs = false;
}
return differs;
}
cmQtAutoGenerator::cmQtAutoGenerator(GenT genType)
: GenType_(genType)
{
}
cmQtAutoGenerator::~cmQtAutoGenerator() = default;
bool cmQtAutoGenerator::InfoT::Read(std::istream& istr)
{
try {
istr >> this->Json_;
} catch (...) {
return false;
}
return true;
}
bool cmQtAutoGenerator::InfoT::GetJsonArray(std::vector<std::string>& list,
Json::Value const& jval)
{
Json::ArrayIndex const arraySize = jval.size();
if (arraySize == 0) {
return false;
}
bool picked = false;
list.reserve(list.size() + arraySize);
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
Json::Value const& ival = jval[ii];
if (ival.isString()) {
list.emplace_back(ival.asString());
picked = true;
}
}
return picked;
}
bool cmQtAutoGenerator::InfoT::GetJsonArray(
std::unordered_set<std::string>& list, Json::Value const& jval)
{
Json::ArrayIndex const arraySize = jval.size();
if (arraySize == 0) {
return false;
}
bool picked = false;
list.reserve(list.size() + arraySize);
for (Json::ArrayIndex ii = 0; ii != arraySize; ++ii) {
Json::Value const& ival = jval[ii];
if (ival.isString()) {
list.emplace(ival.asString());
picked = true;
}
}
return picked;
}
std::string cmQtAutoGenerator::InfoT::ConfigKey(cm::string_view key) const
{
return cmStrCat(key, '_', this->Gen_.InfoConfig());
}
bool cmQtAutoGenerator::InfoT::GetString(std::string const& key,
std::string& value,
bool required) const
{
Json::Value const& jval = this->Json_[key];
if (!jval.isString()) {
if (!jval.isNull() || required) {
return this->LogError(cmStrCat(key, " is not a string."));
}
} else {
value = jval.asString();
if (value.empty() && required) {
return this->LogError(cmStrCat(key, " is empty."));
}
}
return true;
}
bool cmQtAutoGenerator::InfoT::GetStringConfig(std::string const& key,
std::string& value,
bool required) const
{
{ // Try config
std::string const configKey = this->ConfigKey(key);
Json::Value const& jval = this->Json_[configKey];
if (!jval.isNull()) {
if (!jval.isString()) {
return this->LogError(cmStrCat(configKey, " is not a string."));
}
value = jval.asString();
if (required && value.empty()) {
return this->LogError(cmStrCat(configKey, " is empty."));
}
return true;
}
}
// Try plain
return this->GetString(key, value, required);
}
bool cmQtAutoGenerator::InfoT::GetBool(std::string const& key, bool& value,
bool required) const
{
Json::Value const& jval = this->Json_[key];
if (jval.isBool()) {
value = jval.asBool();
} else {
if (!jval.isNull() || required) {
return this->LogError(cmStrCat(key, " is not a boolean."));
}
}
return true;
}
bool cmQtAutoGenerator::InfoT::GetUInt(std::string const& key,
unsigned int& value,
bool required) const
{
Json::Value const& jval = this->Json_[key];
if (jval.isUInt()) {
value = jval.asUInt();
} else {
if (!jval.isNull() || required) {
return this->LogError(cmStrCat(key, " is not an unsigned integer."));
}
}
return true;
}
bool cmQtAutoGenerator::InfoT::GetArray(std::string const& key,
std::vector<std::string>& list,
bool required) const
{
Json::Value const& jval = this->Json_[key];
if (!jval.isArray()) {
if (!jval.isNull() || required) {
return this->LogError(cmStrCat(key, " is not an array."));
}
}
return GetJsonArray(list, jval) || !required;
}
bool cmQtAutoGenerator::InfoT::GetArray(std::string const& key,
std::unordered_set<std::string>& list,
bool required) const
{
Json::Value const& jval = this->Json_[key];
if (!jval.isArray()) {
if (!jval.isNull() || required) {
return this->LogError(cmStrCat(key, " is not an array."));
}
}
return GetJsonArray(list, jval) || !required;
}
bool cmQtAutoGenerator::InfoT::GetArrayConfig(std::string const& key,
std::vector<std::string>& list,
bool required) const
{
{ // Try config
std::string const configKey = this->ConfigKey(key);
Json::Value const& jval = this->Json_[configKey];
if (!jval.isNull()) {
if (!jval.isArray()) {
return this->LogError(cmStrCat(configKey, " is not an array string."));
}
if (!GetJsonArray(list, jval) && required) {
return this->LogError(cmStrCat(configKey, " is empty."));
}
return true;
}
}
// Try plain
return this->GetArray(key, list, required);
}
bool cmQtAutoGenerator::InfoT::LogError(GenT genType,
cm::string_view message) const
{
this->Gen_.Log().Error(genType,
cmStrCat("Info error in info file\n",
Quoted(this->Gen_.InfoFile()), ":\n",
message));
return false;
}
bool cmQtAutoGenerator::InfoT::LogError(cm::string_view message) const
{
return this->LogError(this->Gen_.GenType_, message);
}
std::string cmQtAutoGenerator::SettingsFind(cm::string_view content,
cm::string_view key)
{
cm::string_view res;
std::string const prefix = cmStrCat(key, ':');
cm::string_view::size_type pos = content.find(prefix);
if (pos != cm::string_view::npos) {
pos += prefix.size();
if (pos < content.size()) {
cm::string_view::size_type posE = content.find('\n', pos);
if ((posE != cm::string_view::npos) && (posE != pos)) {
res = content.substr(pos, posE - pos);
}
}
}
return std::string(res);
}
std::string cmQtAutoGenerator::MessagePath(cm::string_view path) const
{
std::string res;
if (cmHasPrefix(path, this->ProjectDirs().Source)) {
res = cmStrCat("SRC:", path.substr(this->ProjectDirs().Source.size()));
} else if (cmHasPrefix(path, this->ProjectDirs().Binary)) {
res = cmStrCat("BIN:", path.substr(this->ProjectDirs().Binary.size()));
} else {
res = std::string(path);
}
return cmQtAutoGen::Quoted(res);
}
bool cmQtAutoGenerator::Run(cm::string_view infoFile, cm::string_view config)
{
// Info config
this->InfoConfig_ = std::string(config);
// Info file
this->InfoFile_ = std::string(infoFile);
cmSystemTools::CollapseFullPath(this->InfoFile_);
this->InfoDir_ = cmSystemTools::GetFilenamePath(this->InfoFile_);
// Load info file time
if (!this->InfoFileTime_.Load(this->InfoFile_)) {
cmSystemTools::Stderr(cmStrCat("AutoGen: The info file ",
Quoted(this->InfoFile_),
" is not readable\n"));
return false;
}
{
InfoT info(*this);
// Read info file
{
cmsys::ifstream ifs(this->InfoFile_.c_str(),
(std::ios::in | std::ios::binary));
if (!ifs) {
this->Log().Error(
this->GenType_,
cmStrCat("Could not to open info file ", Quoted(this->InfoFile_)));
return false;
}
if (!info.Read(ifs)) {
this->Log().Error(
this->GenType_,
cmStrCat("Could not read info file ", Quoted(this->InfoFile_)));
return false;
}
}
// -- Read common info settings
{
unsigned int verbosity = 0;
// Info: setup project directories
if (!info.GetUInt("VERBOSITY", verbosity, false) ||
!info.GetString("CMAKE_SOURCE_DIR", this->ProjectDirs_.Source,
true) ||
!info.GetString("CMAKE_BINARY_DIR", this->ProjectDirs_.Binary,
true) ||
!info.GetString("CMAKE_CURRENT_SOURCE_DIR",
this->ProjectDirs_.CurrentSource, true) ||
!info.GetString("CMAKE_CURRENT_BINARY_DIR",
this->ProjectDirs_.CurrentBinary, true)) {
return false;
}
this->Logger_.RaiseVerbosity(verbosity);
}
// -- Call virtual init from info method.
if (!this->InitFromInfo(info)) {
return false;
}
}
// Call virtual process method.
return this->Process();
}