Ninja: Add explicit preprocessing step for Fortran

All Fortran sources need to be preprocessed before any source may be
compiled so that module dependencies can be (later) extracted.  Factor
out an explicit preprocessing step preceding compilation.  Use Ninja
depfile dependencies on the preprocessing step and then compile the
already-preprocessed source with a separate build statement that depends
explicitly only on the preprocessor output.  Later we will insert
dynamic discovery of module dependencies between these steps.
This commit is contained in:
Brad King 2016-09-21 15:38:53 -04:00
parent 9a77680eed
commit 39ebfc79e6
5 changed files with 179 additions and 1 deletions

View File

@ -1,6 +1,9 @@
include(Compiler/GNU)
__compiler_gnu(Fortran)
set(CMAKE_Fortran_PREPROCESS_SOURCE
"<CMAKE_Fortran_COMPILER> -cpp <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> -o <PREPROCESSED_SOURCE>")
set(CMAKE_Fortran_FORMAT_FIXED_FLAG "-ffixed-form")
set(CMAKE_Fortran_FORMAT_FREE_FLAG "-ffree-form")

View File

@ -7,3 +7,6 @@ set(CMAKE_Fortran_FORMAT_FREE_FLAG "-free")
set(CMAKE_Fortran_CREATE_PREPROCESSED_SOURCE "<CMAKE_Fortran_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> > <PREPROCESSED_SOURCE>")
set(CMAKE_Fortran_CREATE_ASSEMBLY_SOURCE "<CMAKE_Fortran_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -S <SOURCE> -o <ASSEMBLY_SOURCE>")
set(CMAKE_Fortran_PREPROCESS_SOURCE
"<CMAKE_Fortran_COMPILER> -fpp <DEFINES> <INCLUDES> <FLAGS> -E <SOURCE> > <PREPROCESSED_SOURCE>")

View File

@ -18,5 +18,8 @@ string(APPEND CMAKE_Fortran_FLAGS_RELWITHDEBINFO_INIT " -g -xO2 -DNDEBUG")
set(CMAKE_Fortran_MODDIR_FLAG "-moddir=")
set(CMAKE_Fortran_MODPATH_FLAG "-M")
set(CMAKE_Fortran_PREPROCESS_SOURCE
"<CMAKE_Fortran_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -F <SOURCE> -o <PREPROCESSED_SOURCE>")
set(CMAKE_Fortran_CREATE_PREPROCESSED_SOURCE "<CMAKE_Fortran_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -F <SOURCE> -o <PREPROCESSED_SOURCE>")
set(CMAKE_Fortran_CREATE_ASSEMBLY_SOURCE "<CMAKE_Fortran_COMPILER> <DEFINES> <INCLUDES> <FLAGS> -S <SOURCE> -o <ASSEMBLY_SOURCE>")

View File

@ -93,6 +93,19 @@ std::string cmNinjaTargetGenerator::LanguageCompilerRule(
cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName());
}
std::string cmNinjaTargetGenerator::LanguagePreprocessRule(
std::string const& lang) const
{
return lang + "_PREPROCESS__" +
cmGlobalNinjaGenerator::EncodeRuleName(this->GeneratorTarget->GetName());
}
bool cmNinjaTargetGenerator::NeedExplicitPreprocessing(
std::string const& lang) const
{
return lang == "Fortran";
}
std::string cmNinjaTargetGenerator::OrderDependsTargetForTarget()
{
return "cmake_order_depends_target_" + this->GetTargetName();
@ -229,6 +242,41 @@ std::string cmNinjaTargetGenerator::GetObjectFilePath(
return path;
}
std::string cmNinjaTargetGenerator::GetPreprocessedFilePath(
cmSourceFile const* source) const
{
// Choose an extension to compile already-preprocessed source.
std::string ppExt = source->GetExtension();
if (cmHasLiteralPrefix(ppExt, "F")) {
// Some Fortran compilers automatically enable preprocessing for
// upper-case extensions. Since the source is already preprocessed,
// use a lower-case extension.
ppExt = cmSystemTools::LowerCase(ppExt);
}
if (ppExt == "fpp") {
// Some Fortran compilers automatically enable preprocessing for
// the ".fpp" extension. Since the source is already preprocessed,
// use the ".f" extension.
ppExt = "f";
}
// Take the object file name and replace the extension.
std::string const& objName = this->GeneratorTarget->GetObjectName(source);
std::string const& objExt =
this->GetGlobalGenerator()->GetLanguageOutputExtension(*source);
assert(objName.size() >= objExt.size());
std::string const ppName =
objName.substr(0, objName.size() - objExt.size()) + "-pp." + ppExt;
std::string path = this->LocalGenerator->GetHomeRelativeOutputPath();
if (!path.empty())
path += "/";
path += this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
path += "/";
path += ppName;
return path;
}
std::string cmNinjaTargetGenerator::GetTargetOutputDir() const
{
std::string dir = this->GeneratorTarget->GetDirectory(this->GetConfigName());
@ -311,6 +359,9 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
vars.ObjectDir = "$OBJECT_DIR";
vars.ObjectFileDir = "$OBJECT_FILE_DIR";
// For some cases we do an explicit preprocessor invocation.
bool const explicitPP = this->NeedExplicitPreprocessing(lang);
cmMakefile* mf = this->GetMakefile();
std::string flags = "$FLAGS";
@ -331,7 +382,9 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
std::string deptype;
std::string depfile;
std::string cldeps;
if (this->NeedDepTypeMSVC(lang)) {
if (explicitPP) {
// The explicit preprocessing step will handle dependency scanning.
} else if (this->NeedDepTypeMSVC(lang)) {
deptype = "msvc";
depfile = "";
flags += " /showIncludes";
@ -371,6 +424,66 @@ void cmNinjaTargetGenerator::WriteCompileRule(const std::string& lang)
vars.Flags = flags.c_str();
vars.DependencyFile = depfile.c_str();
if (explicitPP) {
// Lookup the explicit preprocessing rule.
std::string const ppVar = "CMAKE_" + lang + "_PREPROCESS_SOURCE";
std::string const ppCmd =
this->GetMakefile()->GetRequiredDefinition(ppVar);
// Explicit preprocessing always uses a depfile.
std::string const ppDeptype = "gcc";
std::string const ppDepfile = "$DEP_FILE";
cmLocalGenerator::RuleVariables ppVars;
ppVars.RuleLauncher = vars.RuleLauncher;
ppVars.CMTarget = vars.CMTarget;
ppVars.Language = vars.Language;
ppVars.Object = "$out"; // for RULE_LAUNCH_COMPILE
ppVars.PreprocessedSource = "$out";
ppVars.DependencyFile = ppDepfile.c_str();
// Preprocessing uses the original source,
// compilation uses preprocessed output.
ppVars.Source = vars.Source;
vars.Source = "$in";
// Preprocessing and compilation use the same flags.
ppVars.Flags = vars.Flags;
// Move preprocessor definitions to the preprocessor rule.
ppVars.Defines = vars.Defines;
vars.Defines = "";
// Copy include directories to the preprocessor rule. The Fortran
// compilation rule still needs them for the INCLUDE directive.
ppVars.Includes = vars.Includes;
// Rule for preprocessing source file.
std::vector<std::string> ppCmds;
cmSystemTools::ExpandListArgument(ppCmd, ppCmds);
for (std::vector<std::string>::iterator i = ppCmds.begin();
i != ppCmds.end(); ++i) {
this->GetLocalGenerator()->ExpandRuleVariables(*i, ppVars);
}
std::string const ppCmdLine =
this->GetLocalGenerator()->BuildCommandLine(ppCmds);
// Write the rule for preprocessing file of the given language.
std::ostringstream ppComment;
ppComment << "Rule for preprocessing " << lang << " files.";
std::ostringstream ppDesc;
ppDesc << "Building " << lang << " preprocessed $out";
this->GetGlobalGenerator()->AddRule(this->LanguagePreprocessRule(lang),
ppCmdLine, ppDesc.str(),
ppComment.str(), ppDepfile, ppDeptype,
/*rspfile*/ "",
/*rspcontent*/ "",
/*restat*/ "",
/*generator*/ false);
}
// Rule for compiling object file.
const std::string cmdVar = std::string("CMAKE_") + lang + "_COMPILE_OBJECT";
std::string compileCmd = mf->GetRequiredDefinition(cmdVar);
@ -589,6 +702,57 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement(
orderOnlyDeps);
}
// For some cases we do an explicit preprocessor invocation.
bool const explicitPP = this->NeedExplicitPreprocessing(language);
if (explicitPP) {
std::string const ppComment;
std::string const ppRule = this->LanguagePreprocessRule(language);
cmNinjaDeps ppOutputs;
cmNinjaDeps ppImplicitOuts;
cmNinjaDeps ppExplicitDeps;
cmNinjaDeps ppImplicitDeps;
cmNinjaDeps ppOrderOnlyDeps;
cmNinjaVars ppVars;
std::string const ppFileName =
this->ConvertToNinjaPath(this->GetPreprocessedFilePath(source));
ppOutputs.push_back(ppFileName);
// Move compilation dependencies to the preprocessing build statement.
std::swap(ppExplicitDeps, explicitDeps);
std::swap(ppImplicitDeps, implicitDeps);
std::swap(ppOrderOnlyDeps, orderOnlyDeps);
std::swap(ppVars["IN_ABS"], vars["IN_ABS"]);
// The actual compilation will now use the preprocessed source.
explicitDeps.push_back(ppFileName);
// Preprocessing and compilation use the same flags.
ppVars["FLAGS"] = vars["FLAGS"];
// Move preprocessor definitions to the preprocessor build statement.
std::swap(ppVars["DEFINES"], vars["DEFINES"]);
// Copy include directories to the preprocessor build statement. The
// Fortran compilation build statement still needs them for the INCLUDE
// directive.
ppVars["INCLUDES"] = vars["INCLUDES"];
// Explicit preprocessing always uses a depfile.
ppVars["DEP_FILE"] =
cmGlobalNinjaGenerator::EncodeDepfileSpace(ppFileName + ".d");
// The actual compilation does not need a depfile because it
// depends on the already-preprocessed source.
vars.erase("DEP_FILE");
this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
ppVars);
this->GetGlobalGenerator()->WriteBuild(
this->GetBuildFileStream(), ppComment, ppRule, ppOutputs, ppImplicitOuts,
ppExplicitDeps, ppImplicitDeps, ppOrderOnlyDeps, ppVars);
}
EnsureParentDirectoryExists(objectFileName);
vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(

View File

@ -70,6 +70,8 @@ protected:
cmMakefile* GetMakefile() const { return this->Makefile; }
std::string LanguageCompilerRule(const std::string& lang) const;
std::string LanguagePreprocessRule(std::string const& lang) const;
bool NeedExplicitPreprocessing(std::string const& lang) const;
std::string OrderDependsTargetForTarget();
@ -107,6 +109,9 @@ protected:
/// @return the object file path for the given @a source.
std::string GetObjectFilePath(cmSourceFile const* source) const;
/// @return the preprocessed source file path for the given @a source.
std::string GetPreprocessedFilePath(cmSourceFile const* source) const;
/// @return the file path where the target named @a name is generated.
std::string GetTargetFilePath(const std::string& name) const;