| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file Copyright.txt or https://cmake.org/licensing for details. */ |
| #include "cmOutputRequiredFilesCommand.h" |
| |
| #include "cmsys/FStream.hxx" |
| #include "cmsys/RegularExpression.hxx" |
| #include <map> |
| #include <utility> |
| |
| #include "cmAlgorithms.h" |
| #include "cmGeneratorExpression.h" |
| #include "cmMakefile.h" |
| #include "cmSourceFile.h" |
| #include "cmSystemTools.h" |
| #include "cmTarget.h" |
| |
| class cmExecutionStatus; |
| |
| /** \class cmDependInformation |
| * \brief Store dependency information for a single source file. |
| * |
| * This structure stores the depend information for a single source file. |
| */ |
| class cmDependInformation |
| { |
| public: |
| /** |
| * Construct with dependency generation marked not done; instance |
| * not placed in cmMakefile's list. |
| */ |
| cmDependInformation() |
| : DependDone(false) |
| , SourceFile(nullptr) |
| { |
| } |
| |
| /** |
| * The set of files on which this one depends. |
| */ |
| typedef std::set<cmDependInformation*> DependencySetType; |
| DependencySetType DependencySet; |
| |
| /** |
| * This flag indicates whether dependency checking has been |
| * performed for this file. |
| */ |
| bool DependDone; |
| |
| /** |
| * If this object corresponds to a cmSourceFile instance, this points |
| * to it. |
| */ |
| const cmSourceFile* SourceFile; |
| |
| /** |
| * Full path to this file. |
| */ |
| std::string FullPath; |
| |
| /** |
| * Full path not including file name. |
| */ |
| std::string PathOnly; |
| |
| /** |
| * Name used to #include this file. |
| */ |
| std::string IncludeName; |
| |
| /** |
| * This method adds the dependencies of another file to this one. |
| */ |
| void AddDependencies(cmDependInformation* info) |
| { |
| if (this != info) { |
| this->DependencySet.insert(info); |
| } |
| } |
| }; |
| |
| class cmLBDepend |
| { |
| public: |
| /** |
| * Construct the object with verbose turned off. |
| */ |
| cmLBDepend() |
| { |
| this->Verbose = false; |
| this->IncludeFileRegularExpression.compile("^.*$"); |
| this->ComplainFileRegularExpression.compile("^$"); |
| } |
| |
| /** |
| * Destructor. |
| */ |
| ~cmLBDepend() { cmDeleteAll(this->DependInformationMap); } |
| |
| /** |
| * Set the makefile that is used as a source of classes. |
| */ |
| void SetMakefile(cmMakefile* makefile) |
| { |
| this->Makefile = makefile; |
| |
| // Now extract the include file regular expression from the makefile. |
| this->IncludeFileRegularExpression.compile( |
| this->Makefile->GetIncludeRegularExpression()); |
| this->ComplainFileRegularExpression.compile( |
| this->Makefile->GetComplainRegularExpression()); |
| |
| // Now extract any include paths from the targets |
| std::set<std::string> uniqueIncludes; |
| std::vector<std::string> orderedAndUniqueIncludes; |
| cmTargets& targets = this->Makefile->GetTargets(); |
| for (auto const& target : targets) { |
| const char* incDirProp = |
| target.second.GetProperty("INCLUDE_DIRECTORIES"); |
| if (!incDirProp) { |
| continue; |
| } |
| |
| std::string incDirs = cmGeneratorExpression::Preprocess( |
| incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions); |
| |
| std::vector<std::string> includes; |
| cmSystemTools::ExpandListArgument(incDirs, includes); |
| |
| for (std::string& path : includes) { |
| this->Makefile->ExpandVariablesInString(path); |
| if (uniqueIncludes.insert(path).second) { |
| orderedAndUniqueIncludes.push_back(path); |
| } |
| } |
| } |
| |
| for (std::string const& inc : orderedAndUniqueIncludes) { |
| this->AddSearchPath(inc); |
| } |
| } |
| |
| /** |
| * Add a directory to the search path for include files. |
| */ |
| void AddSearchPath(const std::string& path) |
| { |
| this->IncludeDirectories.push_back(path); |
| } |
| |
| /** |
| * Generate dependencies for the file given. Returns a pointer to |
| * the cmDependInformation object for the file. |
| */ |
| const cmDependInformation* FindDependencies(const char* file) |
| { |
| cmDependInformation* info = this->GetDependInformation(file, nullptr); |
| this->GenerateDependInformation(info); |
| return info; |
| } |
| |
| protected: |
| /** |
| * Compute the depend information for this class. |
| */ |
| |
| void DependWalk(cmDependInformation* info) |
| { |
| cmsys::ifstream fin(info->FullPath.c_str()); |
| if (!fin) { |
| cmSystemTools::Error("error can not open ", info->FullPath.c_str()); |
| return; |
| } |
| |
| std::string line; |
| while (cmSystemTools::GetLineFromStream(fin, line)) { |
| if (cmHasLiteralPrefix(line, "#include")) { |
| // if it is an include line then create a string class |
| size_t qstart = line.find('\"', 8); |
| size_t qend; |
| // if a quote is not found look for a < |
| if (qstart == std::string::npos) { |
| qstart = line.find('<', 8); |
| // if a < is not found then move on |
| if (qstart == std::string::npos) { |
| cmSystemTools::Error("unknown include directive ", line.c_str()); |
| continue; |
| } |
| qend = line.find('>', qstart + 1); |
| } else { |
| qend = line.find('\"', qstart + 1); |
| } |
| // extract the file being included |
| std::string includeFile = line.substr(qstart + 1, qend - qstart - 1); |
| // see if the include matches the regular expression |
| if (!this->IncludeFileRegularExpression.find(includeFile)) { |
| if (this->Verbose) { |
| std::string message = "Skipping "; |
| message += includeFile; |
| message += " for file "; |
| message += info->FullPath; |
| cmSystemTools::Error(message.c_str(), nullptr); |
| } |
| continue; |
| } |
| |
| // Add this file and all its dependencies. |
| this->AddDependency(info, includeFile.c_str()); |
| /// add the cxx file if it exists |
| std::string cxxFile = includeFile; |
| std::string::size_type pos = cxxFile.rfind('.'); |
| if (pos != std::string::npos) { |
| std::string root = cxxFile.substr(0, pos); |
| cxxFile = root + ".cxx"; |
| bool found = false; |
| // try jumping to .cxx .cpp and .c in order |
| if (cmSystemTools::FileExists(cxxFile)) { |
| found = true; |
| } |
| for (std::string path : this->IncludeDirectories) { |
| path = path + "/"; |
| path = path + cxxFile; |
| if (cmSystemTools::FileExists(path)) { |
| found = true; |
| } |
| } |
| if (!found) { |
| cxxFile = root + ".cpp"; |
| if (cmSystemTools::FileExists(cxxFile)) { |
| found = true; |
| } |
| for (std::string path : this->IncludeDirectories) { |
| path = path + "/"; |
| path = path + cxxFile; |
| if (cmSystemTools::FileExists(path)) { |
| found = true; |
| } |
| } |
| } |
| if (!found) { |
| cxxFile = root + ".c"; |
| if (cmSystemTools::FileExists(cxxFile)) { |
| found = true; |
| } |
| for (std::string path : this->IncludeDirectories) { |
| path = path + "/"; |
| path = path + cxxFile; |
| if (cmSystemTools::FileExists(path)) { |
| found = true; |
| } |
| } |
| } |
| if (!found) { |
| cxxFile = root + ".txx"; |
| if (cmSystemTools::FileExists(cxxFile)) { |
| found = true; |
| } |
| for (std::string path : this->IncludeDirectories) { |
| path = path + "/"; |
| path = path + cxxFile; |
| if (cmSystemTools::FileExists(path)) { |
| found = true; |
| } |
| } |
| } |
| if (found) { |
| this->AddDependency(info, cxxFile.c_str()); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add a dependency. Possibly walk it for more dependencies. |
| */ |
| void AddDependency(cmDependInformation* info, const char* file) |
| { |
| cmDependInformation* dependInfo = |
| this->GetDependInformation(file, info->PathOnly.c_str()); |
| this->GenerateDependInformation(dependInfo); |
| info->AddDependencies(dependInfo); |
| } |
| |
| /** |
| * Fill in the given object with dependency information. If the |
| * information is already complete, nothing is done. |
| */ |
| void GenerateDependInformation(cmDependInformation* info) |
| { |
| // If dependencies are already done, stop now. |
| if (info->DependDone) { |
| return; |
| } |
| // Make sure we don't visit the same file more than once. |
| info->DependDone = true; |
| |
| const char* path = info->FullPath.c_str(); |
| if (!path) { |
| cmSystemTools::Error( |
| "Attempt to find dependencies for file without path!"); |
| return; |
| } |
| |
| bool found = false; |
| |
| // If the file exists, use it to find dependency information. |
| if (cmSystemTools::FileExists(path, true)) { |
| // Use the real file to find its dependencies. |
| this->DependWalk(info); |
| found = true; |
| } |
| |
| // See if the cmSourceFile for it has any files specified as |
| // dependency hints. |
| if (info->SourceFile != nullptr) { |
| |
| // Get the cmSourceFile corresponding to this. |
| const cmSourceFile& cFile = *(info->SourceFile); |
| // See if there are any hints for finding dependencies for the missing |
| // file. |
| if (!cFile.GetDepends().empty()) { |
| // Dependency hints have been given. Use them to begin the |
| // recursion. |
| for (std::string const& file : cFile.GetDepends()) { |
| this->AddDependency(info, file.c_str()); |
| } |
| |
| // Found dependency information. We are done. |
| found = true; |
| } |
| } |
| |
| if (!found) { |
| // Try to find the file amongst the sources |
| cmSourceFile* srcFile = this->Makefile->GetSource( |
| cmSystemTools::GetFilenameWithoutExtension(path)); |
| if (srcFile) { |
| if (srcFile->GetFullPath() == path) { |
| found = true; |
| } else { |
| // try to guess which include path to use |
| for (std::string incpath : this->IncludeDirectories) { |
| if (!incpath.empty() && incpath[incpath.size() - 1] != '/') { |
| incpath = incpath + "/"; |
| } |
| incpath = incpath + path; |
| if (srcFile->GetFullPath() == incpath) { |
| // set the path to the guessed path |
| info->FullPath = incpath; |
| found = true; |
| } |
| } |
| } |
| } |
| } |
| |
| if (!found) { |
| // Couldn't find any dependency information. |
| if (this->ComplainFileRegularExpression.find( |
| info->IncludeName.c_str())) { |
| cmSystemTools::Error("error cannot find dependencies for ", path); |
| } else { |
| // Destroy the name of the file so that it won't be output as a |
| // dependency. |
| info->FullPath.clear(); |
| } |
| } |
| } |
| |
| /** |
| * Get an instance of cmDependInformation corresponding to the given file |
| * name. |
| */ |
| cmDependInformation* GetDependInformation(const char* file, |
| const char* extraPath) |
| { |
| // Get the full path for the file so that lookup is unambiguous. |
| std::string fullPath = this->FullPath(file, extraPath); |
| |
| // Try to find the file's instance of cmDependInformation. |
| DependInformationMapType::const_iterator result = |
| this->DependInformationMap.find(fullPath); |
| if (result != this->DependInformationMap.end()) { |
| // Found an instance, return it. |
| return result->second; |
| } |
| // Didn't find an instance. Create a new one and save it. |
| cmDependInformation* info = new cmDependInformation; |
| info->FullPath = fullPath; |
| info->PathOnly = cmSystemTools::GetFilenamePath(fullPath); |
| info->IncludeName = file; |
| this->DependInformationMap[fullPath] = info; |
| return info; |
| } |
| |
| /** |
| * Find the full path name for the given file name. |
| * This uses the include directories. |
| * TODO: Cache path conversions to reduce FileExists calls. |
| */ |
| std::string FullPath(const char* fname, const char* extraPath) |
| { |
| DirectoryToFileToPathMapType::iterator m; |
| if (extraPath) { |
| m = this->DirectoryToFileToPathMap.find(extraPath); |
| } else { |
| m = this->DirectoryToFileToPathMap.find(""); |
| } |
| |
| if (m != this->DirectoryToFileToPathMap.end()) { |
| FileToPathMapType& map = m->second; |
| FileToPathMapType::iterator p = map.find(fname); |
| if (p != map.end()) { |
| return p->second; |
| } |
| } |
| |
| if (cmSystemTools::FileExists(fname, true)) { |
| std::string fp = cmSystemTools::CollapseFullPath(fname); |
| this->DirectoryToFileToPathMap[extraPath ? extraPath : ""][fname] = fp; |
| return fp; |
| } |
| |
| for (std::string path : this->IncludeDirectories) { |
| if (!path.empty() && path[path.size() - 1] != '/') { |
| path = path + "/"; |
| } |
| path = path + fname; |
| if (cmSystemTools::FileExists(path, true) && |
| !cmSystemTools::FileIsDirectory(path)) { |
| std::string fp = cmSystemTools::CollapseFullPath(path); |
| this->DirectoryToFileToPathMap[extraPath ? extraPath : ""][fname] = fp; |
| return fp; |
| } |
| } |
| |
| if (extraPath) { |
| std::string path = extraPath; |
| if (!path.empty() && path[path.size() - 1] != '/') { |
| path = path + "/"; |
| } |
| path = path + fname; |
| if (cmSystemTools::FileExists(path, true) && |
| !cmSystemTools::FileIsDirectory(path)) { |
| std::string fp = cmSystemTools::CollapseFullPath(path); |
| this->DirectoryToFileToPathMap[extraPath][fname] = fp; |
| return fp; |
| } |
| } |
| |
| // Couldn't find the file. |
| return std::string(fname); |
| } |
| |
| cmMakefile* Makefile; |
| bool Verbose; |
| cmsys::RegularExpression IncludeFileRegularExpression; |
| cmsys::RegularExpression ComplainFileRegularExpression; |
| std::vector<std::string> IncludeDirectories; |
| typedef std::map<std::string, std::string> FileToPathMapType; |
| typedef std::map<std::string, FileToPathMapType> |
| DirectoryToFileToPathMapType; |
| typedef std::map<std::string, cmDependInformation*> DependInformationMapType; |
| DependInformationMapType DependInformationMap; |
| DirectoryToFileToPathMapType DirectoryToFileToPathMap; |
| }; |
| |
| // cmOutputRequiredFilesCommand |
| bool cmOutputRequiredFilesCommand::InitialPass( |
| std::vector<std::string> const& args, cmExecutionStatus&) |
| { |
| if (args.size() != 2) { |
| this->SetError("called with incorrect number of arguments"); |
| return false; |
| } |
| |
| // store the arg for final pass |
| this->File = args[0]; |
| this->OutputFile = args[1]; |
| |
| // compute the list of files |
| cmLBDepend md; |
| md.SetMakefile(this->Makefile); |
| md.AddSearchPath(this->Makefile->GetCurrentSourceDirectory()); |
| // find the depends for a file |
| const cmDependInformation* info = md.FindDependencies(this->File.c_str()); |
| if (info) { |
| // write them out |
| FILE* fout = cmsys::SystemTools::Fopen(this->OutputFile, "w"); |
| if (!fout) { |
| std::string err = "Can not open output file: "; |
| err += this->OutputFile; |
| this->SetError(err); |
| return false; |
| } |
| std::set<cmDependInformation const*> visited; |
| this->ListDependencies(info, fout, &visited); |
| fclose(fout); |
| } |
| |
| return true; |
| } |
| |
| void cmOutputRequiredFilesCommand::ListDependencies( |
| cmDependInformation const* info, FILE* fout, |
| std::set<cmDependInformation const*>* visited) |
| { |
| // add info to the visited set |
| visited->insert(info); |
| // now recurse with info's dependencies |
| for (cmDependInformation* d : info->DependencySet) { |
| if (visited->find(d) == visited->end()) { |
| if (!info->FullPath.empty()) { |
| std::string tmp = d->FullPath; |
| std::string::size_type pos = tmp.rfind('.'); |
| if (pos != std::string::npos && (tmp.substr(pos) != ".h")) { |
| tmp = tmp.substr(0, pos); |
| fprintf(fout, "%s\n", d->FullPath.c_str()); |
| } |
| } |
| this->ListDependencies(d, fout, visited); |
| } |
| } |
| } |