/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmTargetSourcesCommand.h" #include #include #include #include #include "cmArgumentParser.h" #include "cmArgumentParserTypes.h" #include "cmExperimental.h" #include "cmFileSet.h" #include "cmGeneratorExpression.h" #include "cmList.h" #include "cmListFileCache.h" #include "cmMakefile.h" #include "cmMessageType.h" #include "cmPolicies.h" #include "cmStateTypes.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" #include "cmTargetPropCommandBase.h" namespace { struct FileSetArgs { std::string Type; std::string FileSet; ArgumentParser::MaybeEmpty> BaseDirs; ArgumentParser::MaybeEmpty> Files; }; auto const FileSetArgsParser = cmArgumentParser() .Bind("TYPE"_s, &FileSetArgs::Type) .Bind("FILE_SET"_s, &FileSetArgs::FileSet) .Bind("BASE_DIRS"_s, &FileSetArgs::BaseDirs) .Bind("FILES"_s, &FileSetArgs::Files); struct FileSetsArgs { std::vector> FileSets; }; auto const FileSetsArgsParser = cmArgumentParser().Bind("FILE_SET"_s, &FileSetsArgs::FileSets); class TargetSourcesImpl : public cmTargetPropCommandBase { public: using cmTargetPropCommandBase::cmTargetPropCommandBase; protected: void HandleInterfaceContent(cmTarget* tgt, const std::vector& content, bool prepend, bool system) override { this->cmTargetPropCommandBase::HandleInterfaceContent( tgt, this->ConvertToAbsoluteContent(tgt, content, IsInterface::Yes, CheckCMP0076::Yes), prepend, system); } private: void HandleMissingTarget(const std::string& name) override { this->Makefile->IssueMessage( MessageType::FATAL_ERROR, cmStrCat("Cannot specify sources for target \"", name, "\" which is not built by this project.")); } bool HandleDirectContent(cmTarget* tgt, const std::vector& content, bool /*prepend*/, bool /*system*/) override { tgt->AppendProperty("SOURCES", this->Join(this->ConvertToAbsoluteContent( tgt, content, IsInterface::No, CheckCMP0076::Yes)), this->Makefile->GetBacktrace()); return true; // Successfully handled. } bool PopulateTargetProperties(const std::string& scope, const std::vector& content, bool prepend, bool system) override { if (!content.empty() && content.front() == "FILE_SET"_s) { return this->HandleFileSetMode(scope, content); } return this->cmTargetPropCommandBase::PopulateTargetProperties( scope, content, prepend, system); } std::string Join(const std::vector& content) override { return cmJoin(content, ";"); } enum class IsInterface { Yes, No, }; enum class CheckCMP0076 { Yes, No, }; std::vector ConvertToAbsoluteContent( cmTarget* tgt, const std::vector& content, IsInterface isInterfaceContent, CheckCMP0076 checkCmp0076); bool HandleFileSetMode(const std::string& scope, const std::vector& content); bool HandleOneFileSet(const std::string& scope, const std::vector& content); }; std::vector TargetSourcesImpl::ConvertToAbsoluteContent( cmTarget* tgt, const std::vector& content, IsInterface isInterfaceContent, CheckCMP0076 checkCmp0076) { // Skip conversion in case old behavior has been explicitly requested if (checkCmp0076 == CheckCMP0076::Yes && this->Makefile->GetPolicyStatus(cmPolicies::CMP0076) == cmPolicies::OLD) { return content; } bool changedPath = false; std::vector absoluteContent; absoluteContent.reserve(content.size()); for (std::string const& src : content) { std::string absoluteSrc; if (cmSystemTools::FileIsFullPath(src) || cmGeneratorExpression::Find(src) == 0 || (isInterfaceContent == IsInterface::No && (this->Makefile->GetCurrentSourceDirectory() == tgt->GetMakefile()->GetCurrentSourceDirectory()))) { absoluteSrc = src; } else { changedPath = true; absoluteSrc = cmStrCat(this->Makefile->GetCurrentSourceDirectory(), '/', src); } absoluteContent.push_back(absoluteSrc); } if (!changedPath) { return content; } bool issueMessage = true; bool useAbsoluteContent = false; std::ostringstream e; if (checkCmp0076 == CheckCMP0076::Yes) { switch (this->Makefile->GetPolicyStatus(cmPolicies::CMP0076)) { case cmPolicies::WARN: e << cmPolicies::GetPolicyWarning(cmPolicies::CMP0076) << "\n"; break; case cmPolicies::OLD: issueMessage = false; break; case cmPolicies::REQUIRED_ALWAYS: case cmPolicies::REQUIRED_IF_USED: this->Makefile->IssueMessage( MessageType::FATAL_ERROR, cmPolicies::GetRequiredPolicyError(cmPolicies::CMP0076)); break; case cmPolicies::NEW: { issueMessage = false; useAbsoluteContent = true; break; } } } else { issueMessage = false; useAbsoluteContent = true; } if (issueMessage) { if (isInterfaceContent == IsInterface::Yes) { e << "An interface source of target \"" << tgt->GetName() << "\" has a relative path."; } else { e << "A private source from a directory other than that of target \"" << tgt->GetName() << "\" has a relative path."; } this->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, e.str()); } return useAbsoluteContent ? absoluteContent : content; } bool TargetSourcesImpl::HandleFileSetMode( const std::string& scope, const std::vector& content) { auto args = FileSetsArgsParser.Parse(content, /*unparsedArguments=*/nullptr); for (auto& argList : args.FileSets) { argList.emplace(argList.begin(), "FILE_SET"_s); if (!this->HandleOneFileSet(scope, argList)) { return false; } } return true; } bool TargetSourcesImpl::HandleOneFileSet( const std::string& scope, const std::vector& content) { std::vector unparsed; auto args = FileSetArgsParser.Parse(content, &unparsed); if (!unparsed.empty()) { this->SetError( cmStrCat("Unrecognized keyword: \"", unparsed.front(), "\"")); return false; } if (args.FileSet.empty()) { this->SetError("FILE_SET must not be empty"); return false; } if (this->Target->GetType() == cmStateEnums::UTILITY) { this->SetError("FILE_SETs may not be added to custom targets"); return false; } if (this->Target->IsFrameworkOnApple()) { this->SetError("FILE_SETs may not be added to FRAMEWORK targets"); return false; } bool const isDefault = args.Type == args.FileSet || (args.Type.empty() && args.FileSet[0] >= 'A' && args.FileSet[0] <= 'Z'); std::string type = isDefault ? args.FileSet : args.Type; cmFileSetVisibility visibility = cmFileSetVisibilityFromName(scope, this->Makefile); auto fileSet = this->Target->GetOrCreateFileSet(args.FileSet, type, visibility); if (fileSet.second) { if (!isDefault) { if (!cmFileSet::IsValidName(args.FileSet)) { this->SetError("Non-default file set name must contain only letters, " "numbers, and underscores, and must not start with a " "capital letter or underscore"); return false; } } if (type.empty()) { this->SetError("Must specify a TYPE when creating file set"); return false; } bool const supportCxx20FileSetTypes = cmExperimental::HasSupportEnabled( *this->Makefile, cmExperimental::Feature::CxxModuleCMakeApi); if (supportCxx20FileSetTypes) { if (type != "HEADERS"_s && type != "CXX_MODULES"_s && type != "CXX_MODULE_HEADER_UNITS"_s) { this->SetError( R"(File set TYPE may only be "HEADERS", "CXX_MODULES", or "CXX_MODULE_HEADER_UNITS")"); return false; } if (cmFileSetVisibilityIsForInterface(visibility) && !cmFileSetVisibilityIsForSelf(visibility) && !this->Target->IsImported()) { if (type == "CXX_MODULES"_s || type == "CXX_MODULE_HEADER_UNITS"_s) { this->SetError( R"(File set TYPEs "CXX_MODULES" and "CXX_MODULE_HEADER_UNITS" may not have "INTERFACE" visibility)"); return false; } } } else { if (type != "HEADERS"_s) { this->SetError("File set TYPE may only be \"HEADERS\""); return false; } } if (args.BaseDirs.empty()) { args.BaseDirs.emplace_back(this->Makefile->GetCurrentSourceDirectory()); } } else { type = fileSet.first->GetType(); if (!args.Type.empty() && args.Type != type) { this->SetError(cmStrCat( "Type \"", args.Type, "\" for file set \"", fileSet.first->GetName(), "\" does not match original type \"", type, "\"")); return false; } if (visibility != fileSet.first->GetVisibility()) { this->SetError( cmStrCat("Scope ", scope, " for file set \"", args.FileSet, "\" does not match original scope ", cmFileSetVisibilityToName(fileSet.first->GetVisibility()))); return false; } } auto files = this->Join(this->ConvertToAbsoluteContent( this->Target, args.Files, IsInterface::Yes, CheckCMP0076::No)); if (!files.empty()) { fileSet.first->AddFileEntry( BT(files, this->Makefile->GetBacktrace())); } auto baseDirectories = this->Join(this->ConvertToAbsoluteContent( this->Target, args.BaseDirs, IsInterface::Yes, CheckCMP0076::No)); if (!baseDirectories.empty()) { fileSet.first->AddDirectoryEntry( BT(baseDirectories, this->Makefile->GetBacktrace())); if (type == "HEADERS"_s || type == "CXX_MODULE_HEADER_UNITS"_s) { for (auto const& dir : cmList{ baseDirectories }) { auto interfaceDirectoriesGenex = cmStrCat("$"); if (cmFileSetVisibilityIsForSelf(visibility)) { this->Target->AppendProperty("INCLUDE_DIRECTORIES", interfaceDirectoriesGenex, this->Makefile->GetBacktrace()); } if (cmFileSetVisibilityIsForInterface(visibility)) { this->Target->AppendProperty("INTERFACE_INCLUDE_DIRECTORIES", interfaceDirectoriesGenex, this->Makefile->GetBacktrace()); } } } } return true; } } // namespace bool cmTargetSourcesCommand(std::vector const& args, cmExecutionStatus& status) { return TargetSourcesImpl(status).HandleArguments(args, "SOURCES"); }