/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmQtAutoGenInitializer.h"
#include "cmQtAutoGen.h"

#include "cmAlgorithms.h"
#include "cmCustomCommand.h"
#include "cmCustomCommandLines.h"
#include "cmDuration.h"
#include "cmFilePathChecksum.h"
#include "cmGeneratedFileStream.h"
#include "cmGeneratorTarget.h"
#include "cmGlobalGenerator.h"
#include "cmLinkItem.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmOutputConverter.h"
#include "cmPolicies.h"
#include "cmProcessOutput.h"
#include "cmSourceFile.h"
#include "cmSourceGroup.h"
#include "cmState.h"
#include "cmStateTypes.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmake.h"
#include "cmsys/FStream.hxx"
#include "cmsys/SystemInformation.hxx"

#include <algorithm>
#include <array>
#include <deque>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

static std::size_t
GetParallelCPUCount()
{
    static std::size_t count = 0;
    // Detect only on the first call
    if(count == 0)
    {
        cmsys::SystemInformation info;
        info.RunCPUCheck();
        count = info.GetNumberOfPhysicalCPU();
        count = std::max<std::size_t>(count, 1);
        count = std::min<std::size_t>(count, cmQtAutoGen::ParallelMax);
    }
    return count;
}

static bool
AddToSourceGroup(cmMakefile* makefile, std::string const& fileName,
                 cmQtAutoGen::GeneratorT genType)
{
    cmSourceGroup* sourceGroup = nullptr;
    // Acquire source group
    {
        std::string property;
        std::string groupName;
        {
            std::array<std::string, 2> props;
            // Use generator specific group name
            switch(genType)
            {
                case cmQtAutoGen::GeneratorT::MOC:
                    props[0] = "AUTOMOC_SOURCE_GROUP";
                    break;
                case cmQtAutoGen::GeneratorT::RCC:
                    props[0] = "AUTORCC_SOURCE_GROUP";
                    break;
                default:
                    props[0] = "AUTOGEN_SOURCE_GROUP";
                    break;
            }
            props[1] = "AUTOGEN_SOURCE_GROUP";
            for(std::string& prop : props)
            {
                const char* propName =
                    makefile->GetState()->GetGlobalProperty(prop);
                if((propName != nullptr) && (*propName != '\0'))
                {
                    groupName = propName;
                    property  = std::move(prop);
                    break;
                }
            }
        }
        // Generate a source group on demand
        if(!groupName.empty())
        {
            sourceGroup = makefile->GetOrCreateSourceGroup(groupName);
            if(sourceGroup == nullptr)
            {
                std::ostringstream ost;
                ost << cmQtAutoGen::GeneratorNameUpper(genType);
                ost << ": " << property;
                ost << ": Could not find or create the source group ";
                ost << cmQtAutoGen::Quoted(groupName);
                cmSystemTools::Error(ost.str().c_str());
                return false;
            }
        }
    }
    if(sourceGroup != nullptr)
    {
        sourceGroup->AddGroupFile(fileName);
    }
    return true;
}

static void
AddCleanFile(cmMakefile* makefile, std::string const& fileName)
{
    makefile->AppendProperty("ADDITIONAL_MAKE_CLEAN_FILES", fileName.c_str(),
                             false);
}

static std::string
FileProjectRelativePath(cmMakefile* makefile, std::string const& fileName)
{
    std::string res;
    {
        std::string pSource = cmSystemTools::RelativePath(
            makefile->GetCurrentSourceDirectory(), fileName);
        std::string pBinary = cmSystemTools::RelativePath(
            makefile->GetCurrentBinaryDirectory(), fileName);
        if(pSource.size() < pBinary.size())
        {
            res = std::move(pSource);
        } else if(pBinary.size() < fileName.size())
        {
            res = std::move(pBinary);
        } else
        {
            res = fileName;
        }
    }
    return res;
}

/* @brief Tests if targetDepend is a STATIC_LIBRARY and if any of its
 * recursive STATIC_LIBRARY dependencies depends on targetOrigin
 * (STATIC_LIBRARY cycle).
 */
static bool
StaticLibraryCycle(cmGeneratorTarget const* targetOrigin,
                   cmGeneratorTarget const* targetDepend,
                   std::string const&       config)
{
    bool cycle = false;
    if((targetOrigin->GetType() == cmStateEnums::STATIC_LIBRARY) &&
       (targetDepend->GetType() == cmStateEnums::STATIC_LIBRARY))
    {
        std::set<cmGeneratorTarget const*>   knownLibs;
        std::deque<cmGeneratorTarget const*> testLibs;

        // Insert initial static_library dependency
        knownLibs.insert(targetDepend);
        testLibs.push_back(targetDepend);

        while(!testLibs.empty())
        {
            cmGeneratorTarget const* testTarget = testLibs.front();
            testLibs.pop_front();
            // Check if the test target is the origin target (cycle)
            if(testTarget == targetOrigin)
            {
                cycle = true;
                break;
            }
            // Collect all static_library dependencies from the test target
            cmLinkImplementationLibraries const* libs =
                testTarget->GetLinkImplementationLibraries(config);
            if(libs != nullptr)
            {
                for(cmLinkItem const& item : libs->Libraries)
                {
                    cmGeneratorTarget const* depTarget = item.Target;
                    if((depTarget != nullptr) &&
                       (depTarget->GetType() == cmStateEnums::STATIC_LIBRARY) &&
                       knownLibs.insert(depTarget).second)
                    {
                        testLibs.push_back(depTarget);
                    }
                }
            }
        }
    }
    return cycle;
}

cmQtAutoGenInitializer::cmQtAutoGenInitializer(cmGeneratorTarget* target,
                                               bool mocEnabled, bool uicEnabled,
                                               bool                  rccEnabled,
                                               IntegerVersion const& qtVersion)
: Target(target)
, QtVersion(qtVersion)
{
    Moc.Enabled = mocEnabled;
    Uic.Enabled = uicEnabled;
    Rcc.Enabled = rccEnabled;
}

bool
cmQtAutoGenInitializer::InitCustomTargets()
{
    cmMakefile*        makefile  = this->Target->Target->GetMakefile();
    cmLocalGenerator*  localGen  = this->Target->GetLocalGenerator();
    cmGlobalGenerator* globalGen = localGen->GetGlobalGenerator();

    // Configurations
    this->MultiConfig   = globalGen->IsMultiConfig();
    this->ConfigDefault = makefile->GetConfigurations(this->ConfigsList);
    if(this->ConfigsList.empty())
    {
        this->ConfigsList.push_back(this->ConfigDefault);
    }

    // Verbosity
    this->Verbosity = makefile->GetSafeDefinition("CMAKE_AUTOGEN_VERBOSE");
    if(!this->Verbosity.empty())
    {
        unsigned long iVerb = 0;
        if(!cmSystemTools::StringToULong(this->Verbosity.c_str(), &iVerb))
        {
            // Non numeric verbosity
            this->Verbosity = cmSystemTools::IsOn(this->Verbosity) ? "1" : "0";
        }
    }

    // Targets FOLDER
    {
        const char* folder =
            makefile->GetState()->GetGlobalProperty("AUTOMOC_TARGETS_FOLDER");
        if(folder == nullptr)
        {
            folder = makefile->GetState()->GetGlobalProperty(
                "AUTOGEN_TARGETS_FOLDER");
        }
        // Inherit FOLDER property from target (#13688)
        if(folder == nullptr)
        {
            folder = this->Target->GetProperty("FOLDER");
        }
        if(folder != nullptr)
        {
            this->TargetsFolder = folder;
        }
    }

    // Common directories
    {
        // Collapsed current binary directory
        std::string const cbd = cmSystemTools::CollapseFullPath(
            std::string(), makefile->GetCurrentBinaryDirectory());

        // Info directory
        this->Dir.Info = cbd;
        this->Dir.Info +=
            makefile->GetCMakeInstance()->GetCMakeFilesDirectory();
        this->Dir.Info += '/';
        this->Dir.Info += this->Target->GetName();
        this->Dir.Info += "_autogen";
        this->Dir.Info += ".dir";
        cmSystemTools::ConvertToUnixSlashes(this->Dir.Info);

        // Build directory
        this->Dir.Build = this->Target->GetSafeProperty("AUTOGEN_BUILD_DIR");
        if(this->Dir.Build.empty())
        {
            this->Dir.Build = cbd;
            this->Dir.Build += '/';
            this->Dir.Build += this->Target->GetName();
            this->Dir.Build += "_autogen";
        }
        cmSystemTools::ConvertToUnixSlashes(this->Dir.Build);
        // Cleanup build directory
        AddCleanFile(makefile, this->Dir.Build);

        // Working directory
        this->Dir.Work = cbd;
        cmSystemTools::ConvertToUnixSlashes(this->Dir.Work);

        // Include directory
        this->Dir.Include = this->Dir.Build;
        this->Dir.Include += "/include";
        if(this->MultiConfig)
        {
            this->Dir.Include += "_$<CONFIG>";
        }
        // Per config include directories
        if(this->MultiConfig)
        {
            for(std::string const& cfg : this->ConfigsList)
            {
                std::string& dir = this->Dir.ConfigInclude[cfg];
                dir              = this->Dir.Build;
                dir += "/include_";
                dir += cfg;
            }
        }
    }

    // Moc, Uic and _autogen target settings
    if(this->Moc.Enabled || this->Uic.Enabled)
    {
        // Init moc specific settings
        if(this->Moc.Enabled && !InitMoc())
        {
            return false;
        }

        // Init uic specific settings
        if(this->Uic.Enabled && !InitUic())
        {
            return false;
        }

        // Autogen target name
        this->AutogenTarget.Name = this->Target->GetName();
        this->AutogenTarget.Name += "_autogen";

        // Autogen target parallel processing
        this->AutogenTarget.Parallel =
            this->Target->GetSafeProperty("AUTOGEN_PARALLEL");
        if(this->AutogenTarget.Parallel.empty() ||
           (this->AutogenTarget.Parallel == "AUTO"))
        {
            // Autodetect number of CPUs
            this->AutogenTarget.Parallel =
                std::to_string(GetParallelCPUCount());
        }

        // Autogen target info and settings files
        {
            this->AutogenTarget.InfoFile = this->Dir.Info;
            this->AutogenTarget.InfoFile += "/AutogenInfo.cmake";

            this->AutogenTarget.SettingsFile = this->Dir.Info;
            this->AutogenTarget.SettingsFile += "/AutogenOldSettings.txt";

            if(this->MultiConfig)
            {
                for(std::string const& cfg : this->ConfigsList)
                {
                    std::string& filename =
                        this->AutogenTarget.ConfigSettingsFile[cfg];
                    filename = AppendFilenameSuffix(
                        this->AutogenTarget.SettingsFile, "_" + cfg);
                    AddCleanFile(makefile, filename);
                }
            } else
            {
                AddCleanFile(makefile, this->AutogenTarget.SettingsFile);
            }
        }

        // Autogen target: Compute user defined dependencies
        {
            std::string const deps =
                this->Target->GetSafeProperty("AUTOGEN_TARGET_DEPENDS");
            if(!deps.empty())
            {
                std::vector<std::string> extraDeps;
                cmSystemTools::ExpandListArgument(deps, extraDeps);
                for(std::string const& depName : extraDeps)
                {
                    // Allow target and file dependencies
                    auto* depTarget = makefile->FindTargetToUse(depName);
                    if(depTarget != nullptr)
                    {
                        this->AutogenTarget.DependTargets.insert(depTarget);
                    } else
                    {
                        this->AutogenTarget.DependFiles.insert(depName);
                    }
                }
            }
        }
    }

    // Init rcc specific settings
    if(this->Rcc.Enabled && !InitRcc())
    {
        return false;
    }

    // Add autogen include directory to the origin target INCLUDE_DIRECTORIES
    if(this->Moc.Enabled || this->Uic.Enabled ||
       (this->Rcc.Enabled && this->MultiConfig))
    {
        this->Target->AddIncludeDirectory(this->Dir.Include, true);
    }

    // Scan files
    if(!this->InitScanFiles())
    {
        return false;
    }

    // Create autogen target
    if((this->Moc.Enabled || this->Uic.Enabled) && !this->InitAutogenTarget())
    {
        return false;
    }

    // Create rcc targets
    if(this->Rcc.Enabled && !this->InitRccTargets())
    {
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::InitMoc()
{
    cmMakefile*       makefile = this->Target->Target->GetMakefile();
    cmLocalGenerator* localGen = this->Target->GetLocalGenerator();

    // Mocs compilation file
    this->Moc.MocsCompilation = this->Dir.Build;
    this->Moc.MocsCompilation += "/mocs_compilation.cpp";

    // Moc predefs command
    if(this->Target->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") &&
       (this->QtVersion >= IntegerVersion(5, 8)))
    {
        this->Moc.PredefsCmd = makefile->GetSafeDefinition(
            "CMAKE_CXX_COMPILER_PREDEFINES_COMMAND");
    }

    // Moc includes
    {
        bool const appendImplicit = (this->QtVersion.Major == 5);
        auto       GetIncludeDirs = [this, localGen, appendImplicit](
                                  std::string const& cfg) -> std::string {
            // Get the include dirs for this target, without stripping the
            // implicit include dirs off, see
            // https://gitlab.kitware.com/cmake/cmake/issues/13667
            std::vector<std::string> dirs;
            localGen->GetIncludeDirectories(dirs, this->Target, "CXX", cfg,
                                            false, appendImplicit);
            return cmJoin(dirs, ";");
        };

        // Default configuration include directories
        this->Moc.Includes = GetIncludeDirs(this->ConfigDefault);
        // Other configuration settings
        if(this->MultiConfig)
        {
            for(std::string const& cfg : this->ConfigsList)
            {
                std::string dirs = GetIncludeDirs(cfg);
                if(dirs != this->Moc.Includes)
                {
                    this->Moc.ConfigIncludes[cfg] = std::move(dirs);
                }
            }
        }
    }

    // Moc compile definitions
    {
        auto GetCompileDefinitions =
            [this, localGen](std::string const& cfg) -> std::string {
            std::set<std::string> defines;
            localGen->AddCompileDefinitions(defines, this->Target, cfg, "CXX");
            return cmJoin(defines, ";");
        };

        // Default configuration defines
        this->Moc.Defines = GetCompileDefinitions(this->ConfigDefault);
        // Other configuration defines
        if(this->MultiConfig)
        {
            for(std::string const& cfg : this->ConfigsList)
            {
                std::string defines = GetCompileDefinitions(cfg);
                if(defines != this->Moc.Defines)
                {
                    this->Moc.ConfigDefines[cfg] = std::move(defines);
                }
            }
        }
    }

    // Moc executable
    if(!GetMocExecutable())
    {
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::InitUic()
{
    cmMakefile* makefile = this->Target->Target->GetMakefile();

    // Uic search paths
    {
        std::string const usp =
            this->Target->GetSafeProperty("AUTOUIC_SEARCH_PATHS");
        if(!usp.empty())
        {
            cmSystemTools::ExpandListArgument(usp, this->Uic.SearchPaths);
            std::string const& srcDir = makefile->GetCurrentSourceDirectory();
            for(std::string& path : this->Uic.SearchPaths)
            {
                path = cmSystemTools::CollapseFullPath(path, srcDir);
            }
        }
    }
    // Uic target options
    {
        auto UicGetOpts = [this](std::string const& cfg) -> std::string {
            std::vector<std::string> opts;
            this->Target->GetAutoUicOptions(opts, cfg);
            return cmJoin(opts, ";");
        };

        // Default settings
        this->Uic.Options = UicGetOpts(this->ConfigDefault);

        // Configuration specific settings
        if(this->MultiConfig)
        {
            for(std::string const& cfg : this->ConfigsList)
            {
                std::string options = UicGetOpts(cfg);
                if(options != this->Uic.Options)
                {
                    this->Uic.ConfigOptions[cfg] = std::move(options);
                }
            }
        }
    }
    // .ui files skip and options
    {
        std::string const uiExt = "ui";
        std::string       pathError;
        for(cmSourceFile* sf : makefile->GetSourceFiles())
        {
            // sf->GetExtension() is only valid after sf->GetFullPath() ...
            // Since we're iterating over source files that might be not in the
            // target we need to check for path errors (not existing files).
            std::string const& fPath = sf->GetFullPath(&pathError);
            if(!pathError.empty())
            {
                pathError.clear();
                continue;
            }
            if(sf->GetExtension() == uiExt)
            {
                std::string const absFile = cmSystemTools::GetRealPath(fPath);
                // Check if the .ui file should be skipped
                if(sf->GetPropertyAsBool("SKIP_AUTOUIC") ||
                   sf->GetPropertyAsBool("SKIP_AUTOGEN"))
                {
                    this->Uic.Skip.insert(absFile);
                }
                // Check if the .ui file has uic options
                std::string const uicOpts =
                    sf->GetSafeProperty("AUTOUIC_OPTIONS");
                if(!uicOpts.empty())
                {
                    // Check if file isn't skipped
                    if(this->Uic.Skip.count(absFile) == 0)
                    {
                        this->Uic.FileFiles.push_back(absFile);
                        std::vector<std::string> optsVec;
                        cmSystemTools::ExpandListArgument(uicOpts, optsVec);
                        this->Uic.FileOptions.push_back(std::move(optsVec));
                    }
                }
            }
        }
    }

    // Uic executable
    if(!GetUicExecutable())
    {
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::InitRcc()
{
    if(!GetRccExecutable())
    {
        return false;
    }
    return true;
}

bool
cmQtAutoGenInitializer::InitScanFiles()
{
    cmMakefile* makefile = this->Target->Target->GetMakefile();

    // Scan through target files
    {
        std::string const          qrcExt = "qrc";
        std::vector<cmSourceFile*> srcFiles;
        this->Target->GetConfigCommonSourceFiles(srcFiles);
        for(cmSourceFile* sf : srcFiles)
        {
            if(sf->GetPropertyAsBool("SKIP_AUTOGEN"))
            {
                continue;
            }
            // sf->GetExtension() is only valid after sf->GetFullPath() ...
            std::string const& fPath = sf->GetFullPath();
            std::string const& ext   = sf->GetExtension();
            // Register generated files that will be scanned by moc or uic
            if(this->Moc.Enabled || this->Uic.Enabled)
            {
                cmSystemTools::FileFormat const fileType =
                    cmSystemTools::GetFileFormat(ext.c_str());
                if((fileType == cmSystemTools::CXX_FILE_FORMAT) ||
                   (fileType == cmSystemTools::HEADER_FILE_FORMAT))
                {
                    std::string const absPath =
                        cmSystemTools::GetRealPath(fPath);
                    if((this->Moc.Enabled &&
                        !sf->GetPropertyAsBool("SKIP_AUTOMOC")) ||
                       (this->Uic.Enabled &&
                        !sf->GetPropertyAsBool("SKIP_AUTOUIC")))
                    {
                        // Register source
                        const bool generated =
                            sf->GetPropertyAsBool("GENERATED");
                        if(fileType == cmSystemTools::HEADER_FILE_FORMAT)
                        {
                            if(generated)
                            {
                                this->AutogenTarget.HeadersGenerated.push_back(
                                    absPath);
                            } else
                            {
                                this->AutogenTarget.Headers.push_back(absPath);
                            }
                        } else
                        {
                            if(generated)
                            {
                                this->AutogenTarget.SourcesGenerated.push_back(
                                    absPath);
                            } else
                            {
                                this->AutogenTarget.Sources.push_back(absPath);
                            }
                        }
                    }
                }
            }
            // Register rcc enabled files
            if(this->Rcc.Enabled && (ext == qrcExt) &&
               !sf->GetPropertyAsBool("SKIP_AUTORCC"))
            {
                // Register qrc file
                {
                    Qrc qrc;
                    qrc.QrcFile = cmSystemTools::GetRealPath(fPath);
                    qrc.QrcName =
                        cmSystemTools::GetFilenameWithoutLastExtension(
                            qrc.QrcFile);
                    qrc.Generated = sf->GetPropertyAsBool("GENERATED");
                    // RCC options
                    {
                        std::string const opts =
                            sf->GetSafeProperty("AUTORCC_OPTIONS");
                        if(!opts.empty())
                        {
                            cmSystemTools::ExpandListArgument(opts,
                                                              qrc.Options);
                        }
                    }
                    this->Rcc.Qrcs.push_back(std::move(qrc));
                }
            }
        }
    }
    // cmGeneratorTarget::GetConfigCommonSourceFiles computes the target's
    // sources meta data cache. Clear it so that OBJECT library targets that
    // are AUTOGEN initialized after this target get their added
    // mocs_compilation.cpp source acknowledged by this target.
    this->Target->ClearSourcesCache();

    if(this->Moc.Enabled || this->Uic.Enabled)
    {
        // Read skip files from makefile sources
        {
            std::string pathError;
            for(cmSourceFile* sf : makefile->GetSourceFiles())
            {
                // sf->GetExtension() is only valid after sf->GetFullPath() ...
                // Since we're iterating over source files that might be not in
                // the target we need to check for path errors (not existing
                // files).
                std::string const& fPath = sf->GetFullPath(&pathError);
                if(!pathError.empty())
                {
                    pathError.clear();
                    continue;
                }
                cmSystemTools::FileFormat const fileType =
                    cmSystemTools::GetFileFormat(sf->GetExtension().c_str());
                if(!(fileType == cmSystemTools::CXX_FILE_FORMAT) &&
                   !(fileType == cmSystemTools::HEADER_FILE_FORMAT))
                {
                    continue;
                }
                const bool skipAll = sf->GetPropertyAsBool("SKIP_AUTOGEN");
                const bool mocSkip =
                    this->Moc.Enabled &&
                    (skipAll || sf->GetPropertyAsBool("SKIP_AUTOMOC"));
                const bool uicSkip =
                    this->Uic.Enabled &&
                    (skipAll || sf->GetPropertyAsBool("SKIP_AUTOUIC"));
                if(mocSkip || uicSkip)
                {
                    std::string const absFile =
                        cmSystemTools::GetRealPath(fPath);
                    if(mocSkip)
                    {
                        this->Moc.Skip.insert(absFile);
                    }
                    if(uicSkip)
                    {
                        this->Uic.Skip.insert(absFile);
                    }
                }
            }
        }

        // Process GENERATED sources and headers
        if(!this->AutogenTarget.SourcesGenerated.empty() ||
           !this->AutogenTarget.HeadersGenerated.empty())
        {
            // Check status of policy CMP0071
            bool                           policyAccept = false;
            bool                           policyWarn   = false;
            cmPolicies::PolicyStatus const CMP0071_status =
                makefile->GetPolicyStatus(cmPolicies::CMP0071);
            switch(CMP0071_status)
            {
                case cmPolicies::WARN:
                    policyWarn = true;
                    CM_FALLTHROUGH;
                case cmPolicies::OLD:
                    // Ignore GENERATED file
                    break;
                case cmPolicies::REQUIRED_IF_USED:
                case cmPolicies::REQUIRED_ALWAYS:
                case cmPolicies::NEW:
                    // Process GENERATED file
                    policyAccept = true;
                    break;
            }

            if(policyAccept)
            {
                // Accept GENERATED sources
                for(std::string const& absFile :
                    this->AutogenTarget.HeadersGenerated)
                {
                    this->AutogenTarget.Headers.push_back(absFile);
                    this->AutogenTarget.DependFiles.insert(absFile);
                }
                for(std::string const& absFile :
                    this->AutogenTarget.SourcesGenerated)
                {
                    this->AutogenTarget.Sources.push_back(absFile);
                    this->AutogenTarget.DependFiles.insert(absFile);
                }
            } else
            {
                if(policyWarn)
                {
                    std::string msg;
                    msg += cmPolicies::GetPolicyWarning(cmPolicies::CMP0071);
                    msg += "\n";
                    std::string tools;
                    std::string property;
                    if(this->Moc.Enabled && this->Uic.Enabled)
                    {
                        tools    = "AUTOMOC and AUTOUIC";
                        property = "SKIP_AUTOGEN";
                    } else if(this->Moc.Enabled)
                    {
                        tools    = "AUTOMOC";
                        property = "SKIP_AUTOMOC";
                    } else if(this->Uic.Enabled)
                    {
                        tools    = "AUTOUIC";
                        property = "SKIP_AUTOUIC";
                    }
                    msg += "For compatibility, CMake is excluding the "
                           "GENERATED source "
                           "file(s):\n";
                    for(const std::string& absFile :
                        this->AutogenTarget.HeadersGenerated)
                    {
                        msg.append("  ").append(Quoted(absFile)).append("\n");
                    }
                    for(const std::string& absFile :
                        this->AutogenTarget.SourcesGenerated)
                    {
                        msg.append("  ").append(Quoted(absFile)).append("\n");
                    }
                    msg += "from processing by ";
                    msg += tools;
                    msg += ". If any of the files should be processed, set "
                           "CMP0071 to NEW. "
                           "If any of the files should not be processed, "
                           "explicitly exclude them by setting the source file "
                           "property ";
                    msg += property;
                    msg += ":\n  set_property(SOURCE file.h PROPERTY ";
                    msg += property;
                    msg += " ON)\n";
                    makefile->IssueMessage(cmake::AUTHOR_WARNING, msg);
                }
            }
        }
        // Sort headers and sources
        if(this->Moc.Enabled || this->Uic.Enabled)
        {
            std::sort(this->AutogenTarget.Headers.begin(),
                      this->AutogenTarget.Headers.end());
            std::sort(this->AutogenTarget.Sources.begin(),
                      this->AutogenTarget.Sources.end());
        }
    }

    // Process qrc files
    if(!this->Rcc.Qrcs.empty())
    {
        const bool QtV5 = (this->QtVersion.Major == 5);
        // Target rcc options
        std::vector<std::string> optionsTarget;
        cmSystemTools::ExpandListArgument(
            this->Target->GetSafeProperty("AUTORCC_OPTIONS"), optionsTarget);

        // Check if file name is unique
        for(Qrc& qrc : this->Rcc.Qrcs)
        {
            qrc.Unique = true;
            for(Qrc const& qrc2 : this->Rcc.Qrcs)
            {
                if((&qrc != &qrc2) && (qrc.QrcName == qrc2.QrcName))
                {
                    qrc.Unique = false;
                    break;
                }
            }
        }
        // Path checksum and file names
        {
            cmFilePathChecksum const fpathCheckSum(makefile);
            for(Qrc& qrc : this->Rcc.Qrcs)
            {
                qrc.PathChecksum = fpathCheckSum.getPart(qrc.QrcFile);
                // RCC output file name
                {
                    std::string rccFile = this->Dir.Build + "/";
                    rccFile += qrc.PathChecksum;
                    rccFile += "/qrc_";
                    rccFile += qrc.QrcName;
                    rccFile += ".cpp";
                    qrc.RccFile = std::move(rccFile);
                }
                {
                    std::string base = this->Dir.Info;
                    base += "/RCC";
                    base += qrc.QrcName;
                    if(!qrc.Unique)
                    {
                        base += qrc.PathChecksum;
                    }

                    qrc.LockFile = base;
                    qrc.LockFile += ".lock";

                    qrc.InfoFile = base;
                    qrc.InfoFile += "Info.cmake";

                    qrc.SettingsFile = base;
                    qrc.SettingsFile += "Settings.txt";

                    if(this->MultiConfig)
                    {
                        for(std::string const& cfg : this->ConfigsList)
                        {
                            qrc.ConfigSettingsFile[cfg] = AppendFilenameSuffix(
                                qrc.SettingsFile, "_" + cfg);
                        }
                    }
                }
            }
        }
        // RCC options
        for(Qrc& qrc : this->Rcc.Qrcs)
        {
            // Target options
            std::vector<std::string> opts = optionsTarget;
            // Merge computed "-name XYZ" option
            {
                std::string name = qrc.QrcName;
                // Replace '-' with '_'. The former is not valid for symbol
                // names.
                std::replace(name.begin(), name.end(), '-', '_');
                if(!qrc.Unique)
                {
                    name += "_";
                    name += qrc.PathChecksum;
                }
                std::vector<std::string> nameOpts;
                nameOpts.emplace_back("-name");
                nameOpts.emplace_back(std::move(name));
                RccMergeOptions(opts, nameOpts, QtV5);
            }
            // Merge file option
            RccMergeOptions(opts, qrc.Options, QtV5);
            qrc.Options = std::move(opts);
        }
        // RCC resources
        for(Qrc& qrc : this->Rcc.Qrcs)
        {
            if(!qrc.Generated)
            {
                std::string error;
                if(!RccListInputs(qrc.QrcFile, qrc.Resources, error))
                {
                    cmSystemTools::Error(error.c_str());
                    return false;
                }
            }
        }
    }

    return true;
}

bool
cmQtAutoGenInitializer::InitAutogenTarget()
{
    cmMakefile*        makefile  = this->Target->Target->GetMakefile();
    cmLocalGenerator*  localGen  = this->Target->GetLocalGenerator();
    cmGlobalGenerator* globalGen = localGen->GetGlobalGenerator();

    // Register info file as generated by CMake
    makefile->AddCMakeOutputFile(this->AutogenTarget.InfoFile);

    // Files provided by the autogen target
    std::vector<std::string> autogenProvides;
    if(this->Moc.Enabled)
    {
        this->AddGeneratedSource(this->Moc.MocsCompilation, GeneratorT::MOC);
        autogenProvides.push_back(this->Moc.MocsCompilation);
    }

    // Compose target comment
    std::string autogenComment;
    {
        std::string tools;
        if(this->Moc.Enabled)
        {
            tools += "MOC";
        }
        if(this->Uic.Enabled)
        {
            if(!tools.empty())
            {
                tools += " and ";
            }
            tools += "UIC";
        }
        autogenComment = "Automatic ";
        autogenComment += tools;
        autogenComment += " for target ";
        autogenComment += this->Target->GetName();
    }

    // Compose command lines
    cmCustomCommandLines commandLines;
    {
        cmCustomCommandLine currentLine;
        currentLine.push_back(cmSystemTools::GetCMakeCommand());
        currentLine.push_back("-E");
        currentLine.push_back("cmake_autogen");
        currentLine.push_back(this->AutogenTarget.InfoFile);
        currentLine.push_back("$<CONFIGURATION>");
        commandLines.push_back(std::move(currentLine));
    }

    // Use PRE_BUILD on demand
    bool usePRE_BUILD = false;
    if(globalGen->GetName().find("Visual Studio") != std::string::npos)
    {
        // Under VS use a PRE_BUILD event instead of a separate target to
        // reduce the number of targets loaded into the IDE.
        // This also works around a VS 11 bug that may skip updating the target:
        //  https://connect.microsoft.com/VisualStudio/feedback/details/769495
        usePRE_BUILD = true;
    }
    // Disable PRE_BUILD in some cases
    if(usePRE_BUILD)
    {
        // Cannot use PRE_BUILD with file depends
        if(!this->AutogenTarget.DependFiles.empty())
        {
            usePRE_BUILD = false;
        }
    }
    // Create the autogen target/command
    if(usePRE_BUILD)
    {
        // Add additional autogen target dependencies to origin target
        for(cmTarget* depTarget : this->AutogenTarget.DependTargets)
        {
            this->Target->Target->AddUtility(depTarget->GetName(), makefile);
        }

        // Add the pre-build command directly to bypass the OBJECT_LIBRARY
        // rejection in cmMakefile::AddCustomCommandToTarget because we know
        // PRE_BUILD will work for an OBJECT_LIBRARY in this specific case.
        //
        // PRE_BUILD does not support file dependencies!
        const std::vector<std::string> no_output;
        const std::vector<std::string> no_deps;
        cmCustomCommand cc(makefile, no_output, autogenProvides, no_deps,
                           commandLines, autogenComment.c_str(),
                           this->Dir.Work.c_str());
        cc.SetEscapeOldStyle(false);
        cc.SetEscapeAllowMakeVars(true);
        this->Target->Target->AddPreBuildCommand(cc);
    } else
    {
        // Add link library target dependencies to the autogen target
        // dependencies
        {
            // add_dependencies/addUtility do not support generator expressions.
            // We depend only on the libraries found in all configs therefore.
            std::map<cmGeneratorTarget const*, std::size_t> commonTargets;
            for(std::string const& config : this->ConfigsList)
            {
                cmLinkImplementationLibraries const* libs =
                    this->Target->GetLinkImplementationLibraries(config);
                if(libs != nullptr)
                {
                    for(cmLinkItem const& item : libs->Libraries)
                    {
                        cmGeneratorTarget const* libTarget = item.Target;
                        if((libTarget != nullptr) &&
                           !StaticLibraryCycle(this->Target, libTarget, config))
                        {
                            // Increment target config count
                            commonTargets[libTarget]++;
                        }
                    }
                }
            }
            for(auto const& item : commonTargets)
            {
                if(item.second == this->ConfigsList.size())
                {
                    this->AutogenTarget.DependTargets.insert(
                        item.first->Target);
                }
            }
        }

        // Create autogen target
        cmTarget* autogenTarget = makefile->AddUtilityCommand(
            this->AutogenTarget.Name, cmMakefile::TargetOrigin::Generator, true,
            this->Dir.Work.c_str(), /*byproducts=*/autogenProvides,
            std::vector<std::string>(this->AutogenTarget.DependFiles.begin(),
                                     this->AutogenTarget.DependFiles.end()),
            commandLines, false, autogenComment.c_str());
        // Create autogen generator target
        localGen->AddGeneratorTarget(
            new cmGeneratorTarget(autogenTarget, localGen));

        // Forward origin utilities to autogen target
        for(std::string const& depName : this->Target->Target->GetUtilities())
        {
            autogenTarget->AddUtility(depName, makefile);
        }
        // Add additional autogen target dependencies to autogen target
        for(cmTarget* depTarget : this->AutogenTarget.DependTargets)
        {
            autogenTarget->AddUtility(depTarget->GetName(), makefile);
        }

        // Set FOLDER property in autogen target
        if(!this->TargetsFolder.empty())
        {
            autogenTarget->SetProperty("FOLDER", this->TargetsFolder.c_str());
        }

        // Add autogen target to the origin target dependencies
        this->Target->Target->AddUtility(this->AutogenTarget.Name, makefile);
    }

    return true;
}

bool
cmQtAutoGenInitializer::InitRccTargets()
{
    cmMakefile*       makefile = this->Target->Target->GetMakefile();
    cmLocalGenerator* localGen = this->Target->GetLocalGenerator();

    for(Qrc const& qrc : this->Rcc.Qrcs)
    {
        // Register info file as generated by CMake
        makefile->AddCMakeOutputFile(qrc.InfoFile);
        // Register file at target
        this->AddGeneratedSource(qrc.RccFile, GeneratorT::RCC);

        std::vector<std::string> ccOutput;
        ccOutput.push_back(qrc.RccFile);

        cmCustomCommandLines commandLines;
        if(this->MultiConfig)
        {
            // Build for all configurations
            for(std::string const& config : this->ConfigsList)
            {
                cmCustomCommandLine currentLine;
                currentLine.push_back(cmSystemTools::GetCMakeCommand());
                currentLine.push_back("-E");
                currentLine.push_back("cmake_autorcc");
                currentLine.push_back(qrc.InfoFile);
                currentLine.push_back(config);
                commandLines.push_back(std::move(currentLine));
            }
        } else
        {
            cmCustomCommandLine currentLine;
            currentLine.push_back(cmSystemTools::GetCMakeCommand());
            currentLine.push_back("-E");
            currentLine.push_back("cmake_autorcc");
            currentLine.push_back(qrc.InfoFile);
            currentLine.push_back("$<CONFIG>");
            commandLines.push_back(std::move(currentLine));
        }
        std::string ccComment = "Automatic RCC for ";
        ccComment += FileProjectRelativePath(makefile, qrc.QrcFile);

        if(qrc.Generated)
        {
            // Create custom rcc target
            std::string ccName;
            {
                ccName = this->Target->GetName();
                ccName += "_arcc_";
                ccName += qrc.QrcName;
                if(!qrc.Unique)
                {
                    ccName += "_";
                    ccName += qrc.PathChecksum;
                }
                std::vector<std::string> ccDepends;
                // Add the .qrc and info file to the custom target dependencies
                ccDepends.push_back(qrc.QrcFile);
                ccDepends.push_back(qrc.InfoFile);

                cmTarget* autoRccTarget = makefile->AddUtilityCommand(
                    ccName, cmMakefile::TargetOrigin::Generator, true,
                    this->Dir.Work.c_str(), ccOutput, ccDepends, commandLines,
                    false, ccComment.c_str());
                // Create autogen generator target
                localGen->AddGeneratorTarget(
                    new cmGeneratorTarget(autoRccTarget, localGen));

                // Set FOLDER property in autogen target
                if(!this->TargetsFolder.empty())
                {
                    autoRccTarget->SetProperty("FOLDER",
                                               this->TargetsFolder.c_str());
                }
            }
            // Add autogen target to the origin target dependencies
            this->Target->Target->AddUtility(ccName, makefile);
        } else
        {
            // Create custom rcc command
            {
                std::vector<std::string> ccByproducts;
                std::vector<std::string> ccDepends;
                // Add the .qrc and info file to the custom command dependencies
                ccDepends.push_back(qrc.QrcFile);
                ccDepends.push_back(qrc.InfoFile);

                // Add the resource files to the dependencies
                for(std::string const& fileName : qrc.Resources)
                {
                    // Add resource file to the custom command dependencies
                    ccDepends.push_back(fileName);
                }
                makefile->AddCustomCommandToOutput(
                    ccOutput, ccByproducts, ccDepends,
                    /*main_dependency*/ std::string(), commandLines,
                    ccComment.c_str(), this->Dir.Work.c_str());
            }
            // Reconfigure when .qrc file changes
            makefile->AddCMakeDependFile(qrc.QrcFile);
        }
    }

    return true;
}

bool
cmQtAutoGenInitializer::SetupCustomTargets()
{
    // Create info directory on demand
    if(!cmSystemTools::MakeDirectory(this->Dir.Info))
    {
        std::string emsg = ("AutoGen: Could not create directory: ");
        emsg += Quoted(this->Dir.Info);
        cmSystemTools::Error(emsg.c_str());
        return false;
    }

    // Generate autogen target info file
    if(this->Moc.Enabled || this->Uic.Enabled)
    {
        // Write autogen target info files
        if(!this->SetupWriteAutogenInfo())
        {
            return false;
        }
    }

    // Write AUTORCC info files
    if(this->Rcc.Enabled && !this->SetupWriteRccInfo())
    {
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::SetupWriteAutogenInfo()
{
    cmMakefile* makefile = this->Target->Target->GetMakefile();

    cmGeneratedFileStream ofs;
    ofs.SetCopyIfDifferent(true);
    ofs.Open(this->AutogenTarget.InfoFile, false, true);
    if(ofs)
    {
        // Utility lambdas
        auto CWrite = [&ofs](const char* key, std::string const& value) {
            ofs << "set(" << key << " "
                << cmOutputConverter::EscapeForCMake(value) << ")\n";
        };
        auto CWriteUInt = [&ofs](const char* key, unsigned int value) {
            ofs << "set(" << key << " " << value << ")\n";
        };
        auto CWriteList = [&CWrite](const char*                     key,
                                    std::vector<std::string> const& list) {
            CWrite(key, cmJoin(list, ";"));
        };
        auto CWriteNestedLists =
            [&CWrite](const char*                                  key,
                      std::vector<std::vector<std::string>> const& lists) {
                std::vector<std::string> seplist;
                for(const std::vector<std::string>& list : lists)
                {
                    std::string blist = "{";
                    blist += cmJoin(list, ";");
                    blist += "}";
                    seplist.push_back(std::move(blist));
                }
                CWrite(key, cmJoin(seplist, cmQtAutoGen::ListSep));
            };
        auto CWriteSet = [&CWrite](const char*                  key,
                                   std::set<std::string> const& list) {
            CWrite(key, cmJoin(list, ";"));
        };
        auto CWriteMap = [&ofs](const char*                               key,
                                std::map<std::string, std::string> const& map) {
            for(auto const& item : map)
            {
                ofs << "set(" << key << "_" << item.first << " "
                    << cmOutputConverter::EscapeForCMake(item.second) << ")\n";
            }
        };
        auto MfDef = [makefile](const char* key) {
            return makefile->GetSafeDefinition(key);
        };

        // Write
        ofs << "# Meta\n";
        CWrite("AM_MULTI_CONFIG", this->MultiConfig ? "TRUE" : "FALSE");
        CWrite("AM_PARALLEL", this->AutogenTarget.Parallel);
        CWrite("AM_VERBOSITY", this->Verbosity);

        ofs << "# Directories\n";
        CWrite("AM_CMAKE_SOURCE_DIR", MfDef("CMAKE_SOURCE_DIR"));
        CWrite("AM_CMAKE_BINARY_DIR", MfDef("CMAKE_BINARY_DIR"));
        CWrite("AM_CMAKE_CURRENT_SOURCE_DIR",
               MfDef("CMAKE_CURRENT_SOURCE_DIR"));
        CWrite("AM_CMAKE_CURRENT_BINARY_DIR",
               MfDef("CMAKE_CURRENT_BINARY_DIR"));
        CWrite("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE",
               MfDef("CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"));
        CWrite("AM_BUILD_DIR", this->Dir.Build);
        CWrite("AM_INCLUDE_DIR", this->Dir.Include);
        CWriteMap("AM_INCLUDE_DIR", this->Dir.ConfigInclude);

        ofs << "# Files\n";
        CWriteList("AM_SOURCES", this->AutogenTarget.Sources);
        CWriteList("AM_HEADERS", this->AutogenTarget.Headers);
        CWrite("AM_SETTINGS_FILE", this->AutogenTarget.SettingsFile);
        CWriteMap("AM_SETTINGS_FILE", this->AutogenTarget.ConfigSettingsFile);

        ofs << "# Qt\n";
        CWriteUInt("AM_QT_VERSION_MAJOR", this->QtVersion.Major);
        CWrite("AM_QT_MOC_EXECUTABLE", this->Moc.Executable);
        CWrite("AM_QT_UIC_EXECUTABLE", this->Uic.Executable);

        if(this->Moc.Enabled)
        {
            ofs << "# MOC settings\n";
            CWriteSet("AM_MOC_SKIP", this->Moc.Skip);
            CWrite("AM_MOC_DEFINITIONS", this->Moc.Defines);
            CWriteMap("AM_MOC_DEFINITIONS", this->Moc.ConfigDefines);
            CWrite("AM_MOC_INCLUDES", this->Moc.Includes);
            CWriteMap("AM_MOC_INCLUDES", this->Moc.ConfigIncludes);
            CWrite("AM_MOC_OPTIONS",
                   this->Target->GetSafeProperty("AUTOMOC_MOC_OPTIONS"));
            CWrite("AM_MOC_RELAXED_MODE", MfDef("CMAKE_AUTOMOC_RELAXED_MODE"));
            CWrite("AM_MOC_MACRO_NAMES",
                   this->Target->GetSafeProperty("AUTOMOC_MACRO_NAMES"));
            CWrite("AM_MOC_DEPEND_FILTERS",
                   this->Target->GetSafeProperty("AUTOMOC_DEPEND_FILTERS"));
            CWrite("AM_MOC_PREDEFS_CMD", this->Moc.PredefsCmd);
        }

        if(this->Uic.Enabled)
        {
            ofs << "# UIC settings\n";
            CWriteSet("AM_UIC_SKIP", this->Uic.Skip);
            CWrite("AM_UIC_TARGET_OPTIONS", this->Uic.Options);
            CWriteMap("AM_UIC_TARGET_OPTIONS", this->Uic.ConfigOptions);
            CWriteList("AM_UIC_OPTIONS_FILES", this->Uic.FileFiles);
            CWriteNestedLists("AM_UIC_OPTIONS_OPTIONS", this->Uic.FileOptions);
            CWriteList("AM_UIC_SEARCH_PATHS", this->Uic.SearchPaths);
        }
    } else
    {
        std::string err = "AutoGen: Could not write file ";
        err += this->AutogenTarget.InfoFile;
        cmSystemTools::Error(err.c_str());
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::SetupWriteRccInfo()
{
    for(Qrc const& qrc : this->Rcc.Qrcs)
    {
        cmGeneratedFileStream ofs;
        ofs.SetCopyIfDifferent(true);
        ofs.Open(qrc.InfoFile, false, true);
        if(ofs)
        {
            // Utility lambdas
            auto CWrite = [&ofs](const char* key, std::string const& value) {
                ofs << "set(" << key << " "
                    << cmOutputConverter::EscapeForCMake(value) << ")\n";
            };
            auto CWriteMap =
                [&ofs](const char*                               key,
                       std::map<std::string, std::string> const& map) {
                    for(auto const& item : map)
                    {
                        ofs << "set(" << key << "_" << item.first << " "
                            << cmOutputConverter::EscapeForCMake(item.second)
                            << ")\n";
                    }
                };

            // Write
            ofs << "# Configurations\n";
            CWrite("ARCC_MULTI_CONFIG", this->MultiConfig ? "TRUE" : "FALSE");
            CWrite("ARCC_VERBOSITY", this->Verbosity);
            ofs << "# Settings file\n";
            CWrite("ARCC_SETTINGS_FILE", qrc.SettingsFile);
            CWriteMap("ARCC_SETTINGS_FILE", qrc.ConfigSettingsFile);

            ofs << "# Directories\n";
            CWrite("ARCC_BUILD_DIR", this->Dir.Build);
            CWrite("ARCC_INCLUDE_DIR", this->Dir.Include);
            CWriteMap("ARCC_INCLUDE_DIR", this->Dir.ConfigInclude);

            ofs << "# Rcc executable\n";
            CWrite("ARCC_RCC_EXECUTABLE", this->Rcc.Executable);
            CWrite("ARCC_RCC_LIST_OPTIONS", cmJoin(this->Rcc.ListOptions, ";"));

            ofs << "# Rcc job\n";
            CWrite("ARCC_LOCK_FILE", qrc.LockFile);
            CWrite("ARCC_SOURCE", qrc.QrcFile);
            CWrite("ARCC_OUTPUT_CHECKSUM", qrc.PathChecksum);
            CWrite("ARCC_OUTPUT_NAME",
                   cmSystemTools::GetFilenameName(qrc.RccFile));
            CWrite("ARCC_OPTIONS", cmJoin(qrc.Options, ";"));
            CWrite("ARCC_INPUTS", cmJoin(qrc.Resources, ";"));
        } else
        {
            std::string err = "AutoRcc: Could not write file ";
            err += qrc.InfoFile;
            cmSystemTools::Error(err.c_str());
            return false;
        }
    }

    return true;
}

void
cmQtAutoGenInitializer::AddGeneratedSource(std::string const& filename,
                                           GeneratorT         genType)
{
    // Register source file in makefile
    cmMakefile* makefile = this->Target->Target->GetMakefile();
    {
        cmSourceFile* gFile = makefile->GetOrCreateSource(filename, true);
        gFile->SetProperty("GENERATED", "1");
        gFile->SetProperty("SKIP_AUTOGEN", "On");
    }

    // Add source file to source group
    AddToSourceGroup(makefile, filename, genType);

    // Add source file to target
    this->Target->AddSource(filename);
}

cmQtAutoGenInitializer::IntegerVersion
cmQtAutoGenInitializer::GetQtVersion(cmGeneratorTarget const* target)
{
    cmQtAutoGenInitializer::IntegerVersion res;
    cmMakefile* makefile = target->Target->GetMakefile();

    // -- Major version
    std::string qtMajor = makefile->GetSafeDefinition("QT_VERSION_MAJOR");
    if(qtMajor.empty())
    {
        qtMajor = makefile->GetSafeDefinition("Qt5Core_VERSION_MAJOR");
    }
    {
        const char* targetQtVersion =
            target->GetLinkInterfaceDependentStringProperty("QT_MAJOR_VERSION",
                                                            "");
        if(targetQtVersion != nullptr)
        {
            qtMajor = targetQtVersion;
        }
    }

    // -- Minor version
    std::string qtMinor;
    if(!qtMajor.empty())
    {
        if(qtMajor == "5")
        {
            qtMinor = makefile->GetSafeDefinition("Qt5Core_VERSION_MINOR");
        }
        if(qtMinor.empty())
        {
            qtMinor = makefile->GetSafeDefinition("QT_VERSION_MINOR");
        }
        {
            const char* targetQtVersion =
                target->GetLinkInterfaceDependentStringProperty(
                    "QT_MINOR_VERSION", "");
            if(targetQtVersion != nullptr)
            {
                qtMinor = targetQtVersion;
            }
        }
    }

    // -- Convert to integer
    if(!qtMajor.empty() && !qtMinor.empty())
    {
        unsigned long majorUL(0);
        unsigned long minorUL(0);
        if(cmSystemTools::StringToULong(qtMajor.c_str(), &majorUL) &&
           cmSystemTools::StringToULong(qtMinor.c_str(), &minorUL))
        {
            res.Major = static_cast<unsigned int>(majorUL);
            res.Minor = static_cast<unsigned int>(minorUL);
        }
    }

    return res;
}

bool
cmQtAutoGenInitializer::GetMocExecutable()
{
    std::string err;

    // Find moc executable
    {
        std::string targetName;
        if(this->QtVersion.Major == 5)
        {
            targetName = "Qt5::moc";
        } else if(this->QtVersion.Major == 4)
        {
            targetName = "Qt4::moc";
        } else
        {
            err = "The AUTOMOC feature supports only Qt 4 and Qt 5";
        }
        if(!targetName.empty())
        {
            cmLocalGenerator*  localGen = this->Target->GetLocalGenerator();
            cmGeneratorTarget* tgt =
                localGen->FindGeneratorTargetToUse(targetName);
            if(tgt != nullptr)
            {
                this->Moc.Executable = tgt->ImportedGetLocation("");
            } else
            {
                err = "Could not find target " + targetName;
            }
        }
    }

    // Test moc command
    if(err.empty())
    {
        if(cmSystemTools::FileExists(this->Moc.Executable, true))
        {
            std::vector<std::string> command;
            command.push_back(this->Moc.Executable);
            command.push_back("-h");
            std::string stdOut;
            std::string stdErr;
            int         retVal = 0;
            bool        result = cmSystemTools::RunSingleCommand(
                command, &stdOut, &stdErr, &retVal, nullptr,
                cmSystemTools::OUTPUT_NONE, cmDuration::zero(),
                cmProcessOutput::Auto);
            if(!result)
            {
                err = "The moc test command failed: ";
                err += QuotedCommand(command);
            }
        } else
        {
            err = "The moc executable ";
            err += Quoted(this->Moc.Executable);
            err += " does not exist";
        }
    }

    // Print error
    if(!err.empty())
    {
        std::string msg = "AutoMoc (";
        msg += this->Target->GetName();
        msg += "): ";
        msg += err;
        cmSystemTools::Error(msg.c_str());
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::GetUicExecutable()
{
    std::string err;

    // Find uic executable
    {
        std::string targetName;
        if(this->QtVersion.Major == 5)
        {
            targetName = "Qt5::uic";
        } else if(this->QtVersion.Major == 4)
        {
            targetName = "Qt4::uic";
        } else
        {
            err = "The AUTOUIC feature supports only Qt 4 and Qt 5";
        }
        if(!targetName.empty())
        {
            cmLocalGenerator*  localGen = this->Target->GetLocalGenerator();
            cmGeneratorTarget* tgt =
                localGen->FindGeneratorTargetToUse(targetName);
            if(tgt != nullptr)
            {
                this->Uic.Executable = tgt->ImportedGetLocation("");
            } else
            {
                if(this->QtVersion.Major == 5)
                {
                    // Project does not use Qt5Widgets, but has AUTOUIC ON
                    // anyway
                } else
                {
                    err = "Could not find target " + targetName;
                }
            }
        }
    }

    // Test uic command
    if(err.empty() && !this->Uic.Executable.empty())
    {
        if(cmSystemTools::FileExists(this->Uic.Executable, true))
        {
            std::vector<std::string> command;
            command.push_back(this->Uic.Executable);
            command.push_back("-h");
            std::string stdOut;
            std::string stdErr;
            int         retVal = 0;
            bool        result = cmSystemTools::RunSingleCommand(
                command, &stdOut, &stdErr, &retVal, nullptr,
                cmSystemTools::OUTPUT_NONE, cmDuration::zero(),
                cmProcessOutput::Auto);
            if(!result)
            {
                err = "The uic test command failed: ";
                err += QuotedCommand(command);
            }
        } else
        {
            err = "The uic executable ";
            err += Quoted(this->Uic.Executable);
            err += " does not exist";
        }
    }

    // Print error
    if(!err.empty())
    {
        std::string msg = "AutoUic (";
        msg += this->Target->GetName();
        msg += "): ";
        msg += err;
        cmSystemTools::Error(msg.c_str());
        return false;
    }

    return true;
}

bool
cmQtAutoGenInitializer::GetRccExecutable()
{
    std::string err;

    // Find rcc executable
    {
        std::string targetName;
        if(this->QtVersion.Major == 5)
        {
            targetName = "Qt5::rcc";
        } else if(this->QtVersion.Major == 4)
        {
            targetName = "Qt4::rcc";
        } else
        {
            err = "The AUTORCC feature supports only Qt 4 and Qt 5";
        }
        if(!targetName.empty())
        {
            cmLocalGenerator*  localGen = this->Target->GetLocalGenerator();
            cmGeneratorTarget* tgt =
                localGen->FindGeneratorTargetToUse(targetName);
            if(tgt != nullptr)
            {
                this->Rcc.Executable = tgt->ImportedGetLocation("");
            } else
            {
                err = "Could not find target " + targetName;
            }
        }
    }

    // Test rcc command
    if(err.empty())
    {
        if(cmSystemTools::FileExists(this->Rcc.Executable, true))
        {
            std::vector<std::string> command;
            command.push_back(this->Rcc.Executable);
            command.push_back("-h");
            std::string stdOut;
            std::string stdErr;
            int         retVal = 0;
            bool        result = cmSystemTools::RunSingleCommand(
                command, &stdOut, &stdErr, &retVal, nullptr,
                cmSystemTools::OUTPUT_NONE, cmDuration::zero(),
                cmProcessOutput::Auto);
            if(result)
            {
                // Detect if rcc supports (-)-list
                if(this->QtVersion.Major == 5)
                {
                    if(stdOut.find("--list") != std::string::npos)
                    {
                        this->Rcc.ListOptions.push_back("--list");
                    } else
                    {
                        this->Rcc.ListOptions.push_back("-list");
                    }
                }
            } else
            {
                err = "The rcc test command failed: ";
                err += QuotedCommand(command);
            }
        } else
        {
            err = "The rcc executable ";
            err += Quoted(this->Rcc.Executable);
            err += " does not exist";
        }
    }

    // Print error
    if(!err.empty())
    {
        std::string msg = "AutoRcc (";
        msg += this->Target->GetName();
        msg += "): ";
        msg += err;
        cmSystemTools::Error(msg.c_str());
        return false;
    }

    return true;
}

/// @brief Reads the resource files list from from a .qrc file
/// @arg fileName Must be the absolute path of the .qrc file
/// @return True if the rcc file was successfully read
bool
cmQtAutoGenInitializer::RccListInputs(std::string const&        fileName,
                                      std::vector<std::string>& files,
                                      std::string&              error)
{
    if(!cmSystemTools::FileExists(fileName))
    {
        error = "rcc resource file does not exist:\n  ";
        error += Quoted(fileName);
        error += "\n";
        return false;
    }
    if(!this->Rcc.ListOptions.empty())
    {
        // Use rcc for file listing
        if(this->Rcc.Executable.empty())
        {
            error = "rcc executable not available";
            return false;
        }

        // Run rcc list command in the directory of the qrc file with the
        // pathless
        // qrc file name argument. This way rcc prints relative paths.
        // This avoids issues on Windows when the qrc file is in a path that
        // contains non-ASCII characters.
        std::string const fileDir = cmSystemTools::GetFilenamePath(fileName);
        std::string const fileNameName =
            cmSystemTools::GetFilenameName(fileName);

        bool        result = false;
        int         retVal = 0;
        std::string rccStdOut;
        std::string rccStdErr;
        {
            std::vector<std::string> cmd;
            cmd.push_back(this->Rcc.Executable);
            cmd.insert(cmd.end(), this->Rcc.ListOptions.begin(),
                       this->Rcc.ListOptions.end());
            cmd.push_back(fileNameName);
            result = cmSystemTools::RunSingleCommand(
                cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
                cmSystemTools::OUTPUT_NONE, cmDuration::zero(),
                cmProcessOutput::Auto);
        }
        if(!result || retVal)
        {
            error = "rcc list process failed for:\n  ";
            error += Quoted(fileName);
            error += "\n";
            error += rccStdOut;
            error += "\n";
            error += rccStdErr;
            error += "\n";
            return false;
        }
        if(!RccListParseOutput(rccStdOut, rccStdErr, files, error))
        {
            return false;
        }
    } else
    {
        // We can't use rcc for the file listing.
        // Read the qrc file content into string and parse it.
        {
            std::string qrcContents;
            {
                cmsys::ifstream ifs(fileName.c_str());
                if(ifs)
                {
                    std::ostringstream osst;
                    osst << ifs.rdbuf();
                    qrcContents = osst.str();
                } else
                {
                    error = "rcc file not readable:\n  ";
                    error += Quoted(fileName);
                    error += "\n";
                    return false;
                }
            }
            // Parse string content
            RccListParseContent(qrcContents, files);
        }
    }

    // Convert relative paths to absolute paths
    RccListConvertFullPath(cmSystemTools::GetFilenamePath(fileName), files);
    return true;
}
