Commit 39ebfc79 authored by Brad King's avatar Brad King
Browse files

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.
parent 9a77680e
set(CMAKE_Fortran_FORMAT_FIXED_FLAG "-ffixed-form")
set(CMAKE_Fortran_FORMAT_FREE_FLAG "-ffree-form")
......@@ -7,3 +7,6 @@ set(CMAKE_Fortran_FORMAT_FREE_FLAG "-free")
......@@ -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")
......@@ -93,6 +93,19 @@ std::string cmNinjaTargetGenerator::LanguageCompilerRule(
std::string cmNinjaTargetGenerator::LanguagePreprocessRule(
std::string const& lang) const
return lang + "_PREPROCESS__" +
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 =
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 =
// 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 =
// 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";
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(
// 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 =
// 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.
// 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.
this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(),
this->GetBuildFileStream(), ppComment, ppRule, ppOutputs, ppImplicitOuts,
ppExplicitDeps, ppImplicitDeps, ppOrderOnlyDeps, ppVars);
vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat(
......@@ -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;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment