/* 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 #include #include "cmAlgorithms.h" #include "cmGeneratorExpression.h" #include "cmMakefile.h" #include "cmSourceFile.h" #include "cmStringAlgorithms.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() = default; /** * The set of files on which this one depends. */ typedef std::set DependencySetType; DependencySetType DependencySet; /** * This flag indicates whether dependency checking has been * performed for this file. */ bool DependDone = false; /** * If this object corresponds to a cmSourceFile instance, this points * to it. */ const cmSourceFile* SourceFile = nullptr; /** * 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); } cmLBDepend(const cmLBDepend&) = delete; cmLBDepend& operator=(const cmLBDepend&) = delete; /** * 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 uniqueIncludes; std::vector orderedAndUniqueIncludes; for (auto const& target : this->Makefile->GetTargets()) { const char* incDirProp = target.second.GetProperty("INCLUDE_DIRECTORIES"); if (!incDirProp) { continue; } std::string incDirs = cmGeneratorExpression::Preprocess( incDirProp, cmGeneratorExpression::StripAllGeneratorExpressions); std::vector 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); 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); 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); } 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 std::string& path = info->FullPath; if (path.empty()) { 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.back() != '/') { 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)) { 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.back() != '/') { 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.back() != '/') { 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 IncludeDirectories; typedef std::map FileToPathMapType; typedef std::map DirectoryToFileToPathMapType; typedef std::map DependInformationMapType; DependInformationMapType DependInformationMap; DirectoryToFileToPathMapType DirectoryToFileToPathMap; }; // cmOutputRequiredFilesCommand bool cmOutputRequiredFilesCommand::InitialPass( std::vector 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 visited; this->ListDependencies(info, fout, &visited); fclose(fout); } return true; } void cmOutputRequiredFilesCommand::ListDependencies( cmDependInformation const* info, FILE* fout, std::set* 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); } } }