
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
459 lines
13 KiB
C++
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();
|
|
}
|