You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
525 lines
15 KiB
525 lines
15 KiB
/* 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);
|
|
}
|
|
}
|
|
}
|