| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmSourceFile.h" |
| |
| #include <utility> |
| |
| #include <cm/string_view> |
| #include <cmext/string_view> |
| |
| #include "cmGlobalGenerator.h" |
| #include "cmList.h" |
| #include "cmListFileCache.h" |
| #include "cmMakefile.h" |
| #include "cmMessageType.h" |
| #include "cmPolicies.h" |
| #include "cmProperty.h" |
| #include "cmState.h" |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| #include "cmValue.h" |
| #include "cmake.h" |
| |
| cmSourceFile::cmSourceFile(cmMakefile* mf, const std::string& name, |
| bool generated, cmSourceFileLocationKind kind) |
| : Location(mf, name, (!generated) ? kind : cmSourceFileLocationKind::Known) |
| { |
| if (generated) { |
| this->MarkAsGenerated(); |
| } |
| } |
| |
| std::string const& cmSourceFile::GetExtension() const |
| { |
| return this->Extension; |
| } |
| |
| const std::string propTRUE = "1"; |
| const std::string propFALSE = "0"; |
| const std::string cmSourceFile::propLANGUAGE = "LANGUAGE"; |
| const std::string cmSourceFile::propLOCATION = "LOCATION"; |
| const std::string cmSourceFile::propGENERATED = "GENERATED"; |
| const std::string cmSourceFile::propCOMPILE_DEFINITIONS = |
| "COMPILE_DEFINITIONS"; |
| const std::string cmSourceFile::propCOMPILE_OPTIONS = "COMPILE_OPTIONS"; |
| const std::string cmSourceFile::propINCLUDE_DIRECTORIES = |
| "INCLUDE_DIRECTORIES"; |
| |
| void cmSourceFile::SetObjectLibrary(std::string const& objlib) |
| { |
| this->ObjectLibrary = objlib; |
| } |
| |
| std::string cmSourceFile::GetObjectLibrary() const |
| { |
| return this->ObjectLibrary; |
| } |
| |
| std::string const& cmSourceFile::GetOrDetermineLanguage() |
| { |
| // If the language was set explicitly by the user then use it. |
| if (cmValue lang = this->GetProperty(propLANGUAGE)) { |
| // Assign to member in order to return a reference. |
| this->Language = *lang; |
| return this->Language; |
| } |
| |
| // Perform computation needed to get the language if necessary. |
| if (this->Language.empty()) { |
| // If a known extension is given or a known full path is given then trust |
| // that the current extension is sufficient to determine the language. This |
| // will fail only if the user specifies a full path to the source but |
| // leaves off the extension, which is kind of weird. |
| if (this->FullPath.empty() && this->Location.ExtensionIsAmbiguous() && |
| this->Location.DirectoryIsAmbiguous()) { |
| // Finalize the file location to get the extension and set the language. |
| this->ResolveFullPath(); |
| } else { |
| // Use the known extension to get the language if possible. |
| std::string ext = |
| cmSystemTools::GetFilenameLastExtension(this->Location.GetName()); |
| this->CheckLanguage(ext); |
| } |
| } |
| |
| // Use the language determined from the file extension. |
| return this->Language; |
| } |
| |
| std::string cmSourceFile::GetLanguage() const |
| { |
| // If the language was set explicitly by the user then use it. |
| if (cmValue lang = this->GetProperty(propLANGUAGE)) { |
| return *lang; |
| } |
| |
| // Use the language determined from the file extension. |
| return this->Language; |
| } |
| |
| cmSourceFileLocation const& cmSourceFile::GetLocation() const |
| { |
| return this->Location; |
| } |
| |
| std::string const& cmSourceFile::ResolveFullPath(std::string* error, |
| std::string* cmp0115Warning) |
| { |
| if (this->FullPath.empty()) { |
| if (this->FindFullPath(error, cmp0115Warning)) { |
| this->CheckExtension(); |
| } |
| } |
| return this->FullPath; |
| } |
| |
| std::string const& cmSourceFile::GetFullPath() const |
| { |
| return this->FullPath; |
| } |
| |
| bool cmSourceFile::FindFullPath(std::string* error, |
| std::string* cmp0115Warning) |
| { |
| // If the file is generated compute the location without checking on disk. |
| // Note: We also check for a locally set GENERATED property, because |
| // it might have been set before policy CMP0118 (or CMP0163) was set |
| // to NEW. |
| if (this->GetIsGenerated(CheckScope::GlobalAndLocal)) { |
| // The file is either already a full path or is relative to the |
| // build directory for the target. |
| this->Location.DirectoryUseBinary(); |
| this->FullPath = this->Location.GetFullPath(); |
| this->FindFullPathFailed = false; |
| return true; |
| } |
| |
| // If this method has already failed once do not try again. |
| if (this->FindFullPathFailed) { |
| return false; |
| } |
| |
| // The file is not generated. It must exist on disk. |
| cmMakefile const* makefile = this->Location.GetMakefile(); |
| // Location path |
| std::string const& lPath = this->Location.GetFullPath(); |
| // List of extension lists |
| std::vector<std::string> exts = |
| makefile->GetCMakeInstance()->GetAllExtensions(); |
| auto cmp0115 = makefile->GetPolicyStatus(cmPolicies::CMP0115); |
| auto cmp0163 = makefile->GetPolicyStatus(cmPolicies::CMP0163); |
| auto cmp0118 = makefile->GetPolicyStatus(cmPolicies::CMP0118); |
| bool const cmp0163new = |
| cmp0163 != cmPolicies::OLD && cmp0163 != cmPolicies::WARN; |
| bool const cmp0118new = |
| cmp0163new || (cmp0118 != cmPolicies::OLD && cmp0118 != cmPolicies::WARN); |
| |
| // Tries to find the file in a given directory |
| auto findInDir = [this, &exts, &lPath, cmp0115, cmp0115Warning, cmp0118new, |
| makefile](std::string const& dir) -> bool { |
| // Compute full path |
| std::string const fullPath = cmSystemTools::CollapseFullPath(lPath, dir); |
| // Try full path |
| // Is this file globally marked as generated? Then mark so locally. |
| if (cmp0118new && |
| makefile->GetGlobalGenerator()->IsGeneratedFile(fullPath)) { |
| this->IsGenerated = true; |
| } |
| if (this->IsGenerated || cmSystemTools::FileExists(fullPath)) { |
| this->FullPath = fullPath; |
| return true; |
| } |
| // This has to be an if statement due to a bug in Oracle Developer Studio. |
| // See https://community.oracle.com/tech/developers/discussion/4476246/ |
| // for details. |
| if (cmp0115 == cmPolicies::OLD || cmp0115 == cmPolicies::WARN) { |
| // Try full path with extension |
| for (std::string const& ext : exts) { |
| if (!ext.empty()) { |
| std::string extPath = cmStrCat(fullPath, '.', ext); |
| // Is this file globally marked as generated? Then mark so locally. |
| if (cmp0118new && |
| makefile->GetGlobalGenerator()->IsGeneratedFile(extPath)) { |
| this->IsGenerated = true; |
| } |
| if (this->IsGenerated || cmSystemTools::FileExists(extPath)) { |
| this->FullPath = extPath; |
| if (cmp0115 == cmPolicies::WARN) { |
| std::string warning = |
| cmStrCat(cmPolicies::GetPolicyWarning(cmPolicies::CMP0115), |
| "\nFile:\n ", extPath); |
| if (cmp0115Warning) { |
| *cmp0115Warning = std::move(warning); |
| } else { |
| makefile->GetCMakeInstance()->IssueMessage( |
| MessageType::AUTHOR_WARNING, warning); |
| } |
| } |
| return true; |
| } |
| } |
| } |
| } |
| // File not found |
| return false; |
| }; |
| |
| // Try to find the file in various directories |
| if (this->Location.DirectoryIsAmbiguous()) { |
| if (findInDir(makefile->GetCurrentSourceDirectory()) || |
| findInDir(makefile->GetCurrentBinaryDirectory())) { |
| return true; |
| } |
| } else { |
| if (findInDir({})) { |
| return true; |
| } |
| } |
| |
| // Compose error |
| std::string err = cmStrCat("Cannot find source file:\n ", lPath); |
| switch (cmp0115) { |
| case cmPolicies::OLD: |
| case cmPolicies::WARN: |
| err = cmStrCat(err, "\nTried extensions"); |
| for (auto const& ext : exts) { |
| err = cmStrCat(err, " .", ext); |
| } |
| break; |
| case cmPolicies::REQUIRED_IF_USED: |
| case cmPolicies::REQUIRED_ALWAYS: |
| case cmPolicies::NEW: |
| break; |
| } |
| if (lPath == "FILE_SET"_s) { |
| err += "\nHint: the FILE_SET keyword may only appear after a visibility " |
| "specifier or another FILE_SET within the target_sources() " |
| "command."; |
| } |
| if (error != nullptr) { |
| *error = std::move(err); |
| } else { |
| makefile->IssueMessage(MessageType::FATAL_ERROR, err); |
| } |
| this->FindFullPathFailed = true; |
| |
| // File not found |
| return false; |
| } |
| |
| void cmSourceFile::CheckExtension() |
| { |
| // Compute the extension. |
| std::string realExt = |
| cmSystemTools::GetFilenameLastExtension(this->FullPath); |
| if (!realExt.empty()) { |
| // Store the extension without the leading '.'. |
| this->Extension = realExt.substr(1); |
| } |
| |
| // Look for object files. |
| if (this->Extension == "obj" || this->Extension == "o" || |
| this->Extension == "lo") { |
| this->SetProperty("EXTERNAL_OBJECT", "1"); |
| } |
| |
| // Try to identify the source file language from the extension. |
| if (this->Language.empty()) { |
| this->CheckLanguage(this->Extension); |
| } |
| } |
| |
| void cmSourceFile::CheckLanguage(std::string const& ext) |
| { |
| // Try to identify the source file language from the extension. |
| cmMakefile const* mf = this->Location.GetMakefile(); |
| cmGlobalGenerator* gg = mf->GetGlobalGenerator(); |
| std::string l = gg->GetLanguageFromExtension(ext.c_str()); |
| if (!l.empty()) { |
| this->Language = l; |
| } |
| } |
| |
| bool cmSourceFile::Matches(cmSourceFileLocation const& loc) |
| { |
| return this->Location.Matches(loc); |
| } |
| |
| void cmSourceFile::SetProperty(const std::string& prop, cmValue value) |
| { |
| if (prop == propINCLUDE_DIRECTORIES) { |
| this->IncludeDirectories.clear(); |
| if (value) { |
| cmListFileBacktrace lfbt = this->Location.GetMakefile()->GetBacktrace(); |
| this->IncludeDirectories.emplace_back(value, lfbt); |
| } |
| } else if (prop == propCOMPILE_OPTIONS) { |
| this->CompileOptions.clear(); |
| if (value) { |
| cmListFileBacktrace lfbt = this->Location.GetMakefile()->GetBacktrace(); |
| this->CompileOptions.emplace_back(value, lfbt); |
| } |
| } else if (prop == propCOMPILE_DEFINITIONS) { |
| this->CompileDefinitions.clear(); |
| if (value) { |
| cmListFileBacktrace lfbt = this->Location.GetMakefile()->GetBacktrace(); |
| this->CompileDefinitions.emplace_back(value, lfbt); |
| } |
| } else { |
| this->Properties.SetProperty(prop, value); |
| } |
| } |
| |
| void cmSourceFile::AppendProperty(const std::string& prop, |
| const std::string& value, bool asString) |
| { |
| if (prop == propINCLUDE_DIRECTORIES) { |
| if (!value.empty()) { |
| cmListFileBacktrace lfbt = this->Location.GetMakefile()->GetBacktrace(); |
| this->IncludeDirectories.emplace_back(value, lfbt); |
| } |
| } else if (prop == propCOMPILE_OPTIONS) { |
| if (!value.empty()) { |
| cmListFileBacktrace lfbt = this->Location.GetMakefile()->GetBacktrace(); |
| this->CompileOptions.emplace_back(value, lfbt); |
| } |
| } else if (prop == propCOMPILE_DEFINITIONS) { |
| if (!value.empty()) { |
| cmListFileBacktrace lfbt = this->Location.GetMakefile()->GetBacktrace(); |
| this->CompileDefinitions.emplace_back(value, lfbt); |
| } |
| } else { |
| this->Properties.AppendProperty(prop, value, asString); |
| } |
| } |
| |
| cmValue cmSourceFile::GetPropertyForUser(const std::string& prop) |
| { |
| // This method is a consequence of design history and backwards |
| // compatibility. GetProperty is (and should be) a const method. |
| // Computed properties should not be stored back in the property map |
| // but instead reference information already known. If they need to |
| // cache information in a mutable ivar to provide the return string |
| // safely then so be it. |
| // |
| // The LOCATION property is particularly problematic. The CMake |
| // language has very loose restrictions on the names that will match |
| // a given source file (for historical reasons). Implementing |
| // lookups correctly with such loose naming requires the |
| // cmSourceFileLocation class to commit to a particular full path to |
| // the source file as late as possible. If the users requests the |
| // LOCATION property we must commit now. |
| if (prop == propLOCATION) { |
| // Commit to a location. |
| this->ResolveFullPath(); |
| } |
| |
| // Similarly, LANGUAGE can be determined by the file extension |
| // if it is requested by the user. |
| if (prop == propLANGUAGE) { |
| // The pointer is valid until `this->Language` is modified. |
| return cmValue(this->GetOrDetermineLanguage()); |
| } |
| |
| // Special handling for GENERATED property. |
| if (prop == propGENERATED) { |
| // We need to check policy CMP0163 and CMP0118 in order to determine if we |
| // need to possibly consider the value of a locally set GENERATED property, |
| // too. |
| auto cmp0163 = |
| this->Location.GetMakefile()->GetPolicyStatus(cmPolicies::CMP0163); |
| auto cmp0118 = |
| this->Location.GetMakefile()->GetPolicyStatus(cmPolicies::CMP0118); |
| bool const cmp0163new = |
| cmp0163 != cmPolicies::OLD && cmp0163 != cmPolicies::WARN; |
| bool const cmp0118new = cmp0163new || |
| (cmp0118 != cmPolicies::OLD && cmp0118 != cmPolicies::WARN); |
| if (this->GetIsGenerated((!cmp0118new) ? CheckScope::GlobalAndLocal |
| : CheckScope::Global)) { |
| return cmValue(propTRUE); |
| } |
| return cmValue(propFALSE); |
| } |
| |
| // Perform the normal property lookup. |
| return this->GetProperty(prop); |
| } |
| |
| cmValue cmSourceFile::GetProperty(const std::string& prop) const |
| { |
| // Check for computed properties. |
| if (prop == propLOCATION) { |
| if (this->FullPath.empty()) { |
| return nullptr; |
| } |
| return cmValue(this->FullPath); |
| } |
| |
| // Check for the properties with backtraces. |
| if (prop == propINCLUDE_DIRECTORIES) { |
| if (this->IncludeDirectories.empty()) { |
| return nullptr; |
| } |
| |
| static std::string output; |
| output = cmList::to_string(this->IncludeDirectories); |
| return cmValue(output); |
| } |
| |
| if (prop == propCOMPILE_OPTIONS) { |
| if (this->CompileOptions.empty()) { |
| return nullptr; |
| } |
| |
| static std::string output; |
| output = cmList::to_string(this->CompileOptions); |
| return cmValue(output); |
| } |
| |
| if (prop == propCOMPILE_DEFINITIONS) { |
| if (this->CompileDefinitions.empty()) { |
| return nullptr; |
| } |
| |
| static std::string output; |
| output = cmList::to_string(this->CompileDefinitions); |
| return cmValue(output); |
| } |
| |
| cmValue retVal = this->Properties.GetPropertyValue(prop); |
| if (!retVal) { |
| cmMakefile const* mf = this->Location.GetMakefile(); |
| const bool chain = |
| mf->GetState()->IsPropertyChained(prop, cmProperty::SOURCE_FILE); |
| if (chain) { |
| return mf->GetProperty(prop, chain); |
| } |
| return nullptr; |
| } |
| |
| return retVal; |
| } |
| |
| const std::string& cmSourceFile::GetSafeProperty(const std::string& prop) const |
| { |
| cmValue ret = this->GetProperty(prop); |
| if (ret) { |
| return *ret; |
| } |
| |
| static std::string const s_empty; |
| return s_empty; |
| } |
| |
| bool cmSourceFile::GetPropertyAsBool(const std::string& prop) const |
| { |
| return this->GetProperty(prop).IsOn(); |
| } |
| |
| void cmSourceFile::MarkAsGenerated() |
| { |
| this->IsGenerated = true; |
| const auto& mf = *this->Location.GetMakefile(); |
| mf.GetGlobalGenerator()->MarkAsGeneratedFile(this->ResolveFullPath()); |
| } |
| |
| bool cmSourceFile::GetIsGenerated(CheckScope checkScope) const |
| { |
| if (this->IsGenerated) { |
| // Globally marked as generated! |
| return true; |
| } |
| if (checkScope == CheckScope::GlobalAndLocal) { |
| // Check locally stored properties. |
| return this->GetPropertyAsBool(propGENERATED); |
| } |
| return false; |
| } |
| |
| void cmSourceFile::SetProperties(cmPropertyMap properties) |
| { |
| this->Properties = std::move(properties); |
| } |
| |
| cmCustomCommand* cmSourceFile::GetCustomCommand() const |
| { |
| return this->CustomCommand.get(); |
| } |
| |
| void cmSourceFile::SetCustomCommand(std::unique_ptr<cmCustomCommand> cc) |
| { |
| this->CustomCommand = std::move(cc); |
| } |