|
|
|
/* 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 <iterator>
|
|
|
|
|
|
|
|
#include <cm3p/json/reader.h>
|
|
|
|
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
|
|
|
|
|
|
#include "cmQtAutoGen.h"
|
|
|
|
#include "cmStringAlgorithms.h"
|
|
|
|
#include "cmSystemTools.h"
|
|
|
|
#include "cmValue.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::FileRead(std::string& content,
|
|
|
|
std::string const& filename,
|
|
|
|
std::string* error)
|
|
|
|
{
|
|
|
|
content.clear();
|
|
|
|
if (!cmSystemTools::FileExists(filename, true)) {
|
|
|
|
if (error != nullptr) {
|
|
|
|
*error = "Not a file.";
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned long const length = cmSystemTools::FileLength(filename);
|
|
|
|
cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
|
|
|
|
|
|
|
|
// Use lambda to save destructor calls of ifs
|
|
|
|
return [&ifs, length, &content, error]() -> bool {
|
|
|
|
if (!ifs) {
|
|
|
|
if (error != nullptr) {
|
|
|
|
*error = "Opening the file for reading failed.";
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
content.reserve(length);
|
|
|
|
using IsIt = std::istreambuf_iterator<char>;
|
|
|
|
content.assign(IsIt{ ifs }, IsIt{});
|
|
|
|
if (!ifs) {
|
|
|
|
content.clear();
|
|
|
|
if (error != nullptr) {
|
|
|
|
*error = "Reading from the file failed.";
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}();
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|