1 /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
2 file Copyright.txt or https://cmake.org/licensing for details. */
3 #include "cmDependsFortran.h"
12 #include "cmsys/FStream.hxx"
14 #include "cmFortranParser.h" /* Interface to parser object. */
15 #include "cmGeneratedFileStream.h"
16 #include "cmGlobalUnixMakefileGenerator3.h"
18 #include "cmLocalUnixMakefileGenerator3.h"
19 #include "cmMakefile.h"
20 #include "cmOutputConverter.h"
21 #include "cmStringAlgorithms.h"
22 #include "cmSystemTools.h"
25 // TODO: Test compiler for the case of the mod file. Some always
26 // use lower case and some always use upper case. I do not know if any
27 // use the case from the source code.
29 static void cmFortranModuleAppendUpperLower(std::string
const& mod
,
30 std::string
& mod_upper
,
31 std::string
& mod_lower
)
33 std::string::size_type ext_len
= 0;
34 if (cmHasLiteralSuffix(mod
, ".mod") || cmHasLiteralSuffix(mod
, ".sub")) {
36 } else if (cmHasLiteralSuffix(mod
, ".smod")) {
39 std::string
const& name
= mod
.substr(0, mod
.size() - ext_len
);
40 std::string
const& ext
= mod
.substr(mod
.size() - ext_len
);
41 mod_upper
+= cmSystemTools::UpperCase(name
) + ext
;
45 class cmDependsFortranInternals
48 // The set of modules provided by this target.
49 std::set
<std::string
> TargetProvides
;
51 // Map modules required by this target to locations.
52 using TargetRequiresMap
= std::map
<std::string
, std::string
>;
53 TargetRequiresMap TargetRequires
;
55 // Information about each object file.
56 using ObjectInfoMap
= std::map
<std::string
, cmFortranSourceInfo
>;
57 ObjectInfoMap ObjectInfo
;
59 cmFortranSourceInfo
& CreateObjectInfo(const std::string
& obj
,
60 const std::string
& src
)
62 auto i
= this->ObjectInfo
.find(obj
);
63 if (i
== this->ObjectInfo
.end()) {
64 std::map
<std::string
, cmFortranSourceInfo
>::value_type
entry(
65 obj
, cmFortranSourceInfo());
66 i
= this->ObjectInfo
.insert(entry
).first
;
67 i
->second
.Source
= src
;
73 cmDependsFortran::cmDependsFortran() = default;
75 cmDependsFortran::cmDependsFortran(cmLocalUnixMakefileGenerator3
* lg
)
77 , Internal(new cmDependsFortranInternals
)
79 // Configure the include file search path.
80 this->SetIncludePathFromLanguage("Fortran");
82 // Get the list of definitions.
83 cmMakefile
* mf
= this->LocalGenerator
->GetMakefile();
84 cmList definitions
{ mf
->GetDefinition("CMAKE_TARGET_DEFINITIONS_Fortran") };
86 // translate i.e. FOO=BAR to FOO and add it to the list of defined
87 // preprocessor symbols
88 for (std::string def
: definitions
) {
89 std::string::size_type assignment
= def
.find('=');
90 if (assignment
!= std::string::npos
) {
91 def
= def
.substr(0, assignment
);
93 this->PPDefinitions
.insert(def
);
96 this->CompilerId
= mf
->GetSafeDefinition("CMAKE_Fortran_COMPILER_ID");
97 this->SModSep
= mf
->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP");
98 this->SModExt
= mf
->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT");
101 cmDependsFortran::~cmDependsFortran() = default;
103 bool cmDependsFortran::WriteDependencies(const std::set
<std::string
>& sources
,
104 const std::string
& obj
,
105 std::ostream
& /*makeDepends*/,
106 std::ostream
& /*internalDepends*/)
108 // Make sure this is a scanning instance.
109 if (sources
.empty() || sources
.begin()->empty()) {
110 cmSystemTools::Error("Cannot scan dependencies without a source file.");
114 cmSystemTools::Error("Cannot scan dependencies without an object file.");
118 cmFortranCompiler fc
;
119 fc
.Id
= this->CompilerId
;
120 fc
.SModSep
= this->SModSep
;
121 fc
.SModExt
= this->SModExt
;
124 for (std::string
const& src
: sources
) {
125 // Get the information object for this source.
126 cmFortranSourceInfo
& info
= this->Internal
->CreateObjectInfo(obj
, src
);
128 // Create the parser object. The constructor takes info by reference,
129 // so we may look into the resulting objects later.
130 cmFortranParser
parser(fc
, this->IncludePath
, this->PPDefinitions
, info
);
132 // Push on the starting file.
133 cmFortranParser_FilePush(&parser
, src
.c_str());
135 // Parse the translation unit.
136 if (cmFortran_yyparse(parser
.Scanner
) != 0) {
137 // Failed to parse the file. Report failure to write dependencies.
139 /* clang-format off */
141 "warning: failed to parse dependencies from Fortran source "
142 "'" << src
<< "': " << parser
.Error
<< std::endl
144 /* clang-format on */
150 bool cmDependsFortran::Finalize(std::ostream
& makeDepends
,
151 std::ostream
& internalDepends
)
153 // Prepare the module search process.
154 if (!this->LocateModules()) {
158 // Get the directory in which stamp files will be stored.
159 const std::string
& stamp_dir
= this->TargetDirectory
;
161 // Get the directory in which module files will be created.
162 cmMakefile
* mf
= this->LocalGenerator
->GetMakefile();
163 std::string mod_dir
=
164 mf
->GetSafeDefinition("CMAKE_Fortran_TARGET_MODULE_DIR");
165 if (mod_dir
.empty()) {
166 mod_dir
= this->LocalGenerator
->GetCurrentBinaryDirectory();
169 bool building_intrinsics
=
170 !mf
->GetSafeDefinition("CMAKE_Fortran_TARGET_BUILDING_INSTRINSIC_MODULES")
173 // Actually write dependencies to the streams.
174 using ObjectInfoMap
= cmDependsFortranInternals::ObjectInfoMap
;
175 ObjectInfoMap
const& objInfo
= this->Internal
->ObjectInfo
;
176 for (auto const& i
: objInfo
) {
177 if (!this->WriteDependenciesReal(i
.first
, i
.second
, mod_dir
, stamp_dir
,
178 makeDepends
, internalDepends
,
179 building_intrinsics
)) {
184 // Store the list of modules provided by this target.
185 std::string fiName
= cmStrCat(this->TargetDirectory
, "/fortran.internal");
186 cmGeneratedFileStream
fiStream(fiName
);
187 fiStream
<< "# The fortran modules provided by this target.\n";
188 fiStream
<< "provides\n";
189 std::set
<std::string
> const& provides
= this->Internal
->TargetProvides
;
190 for (std::string
const& i
: provides
) {
191 fiStream
<< ' ' << i
<< '\n';
194 // Create a script to clean the modules.
195 if (!provides
.empty()) {
197 cmStrCat(this->TargetDirectory
, "/cmake_clean_Fortran.cmake");
198 cmGeneratedFileStream
fcStream(fcName
);
199 fcStream
<< "# Remove fortran modules provided by this target.\n";
200 fcStream
<< "FILE(REMOVE";
201 for (std::string
const& i
: provides
) {
202 std::string mod_upper
= cmStrCat(mod_dir
, '/');
203 std::string mod_lower
= cmStrCat(mod_dir
, '/');
204 cmFortranModuleAppendUpperLower(i
, mod_upper
, mod_lower
);
205 std::string stamp
= cmStrCat(stamp_dir
, '/', i
, ".stamp");
208 << this->LocalGenerator
->MaybeRelativeToCurBinDir(mod_lower
)
211 << this->LocalGenerator
->MaybeRelativeToCurBinDir(mod_upper
)
214 << this->LocalGenerator
->MaybeRelativeToCurBinDir(stamp
)
222 bool cmDependsFortran::LocateModules()
224 // Collect the set of modules provided and required by all sources.
225 using ObjectInfoMap
= cmDependsFortranInternals::ObjectInfoMap
;
226 ObjectInfoMap
const& objInfo
= this->Internal
->ObjectInfo
;
227 for (auto const& infoI
: objInfo
) {
228 cmFortranSourceInfo
const& info
= infoI
.second
;
229 // Include this module in the set provided by this target.
230 this->Internal
->TargetProvides
.insert(info
.Provides
.begin(),
231 info
.Provides
.end());
233 for (std::string
const& r
: info
.Requires
) {
234 this->Internal
->TargetRequires
[r
].clear();
238 // Short-circuit for simple targets.
239 if (this->Internal
->TargetRequires
.empty()) {
243 // Match modules provided by this target to those it requires.
244 this->MatchLocalModules();
246 // Load information about other targets.
247 cmMakefile
* mf
= this->LocalGenerator
->GetMakefile();
248 cmList infoFiles
{ mf
->GetDefinition(
249 "CMAKE_Fortran_TARGET_LINKED_INFO_FILES") };
250 for (auto const& i
: infoFiles
) {
251 std::string targetDir
= cmSystemTools::GetFilenamePath(i
);
252 std::string fname
= targetDir
+ "/fortran.internal";
253 cmsys::ifstream
fin(fname
.c_str());
255 cmSystemTools::Error(cmStrCat("-E cmake_depends failed to open ", fname
,
256 " for module information"));
259 this->MatchRemoteModules(fin
, targetDir
);
262 // TODO: Use `CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES` to handle cases
263 // described in #25425. Note that because Makefiles generators do not
264 // implement relaxed object compilation as described in #15555, the issues
265 // never actually cause build failures; only incremental build incorrectness.
270 void cmDependsFortran::MatchLocalModules()
272 std::string
const& stampDir
= this->TargetDirectory
;
273 std::set
<std::string
> const& provides
= this->Internal
->TargetProvides
;
274 for (std::string
const& i
: provides
) {
275 this->ConsiderModule(i
, stampDir
);
279 void cmDependsFortran::MatchRemoteModules(std::istream
& fin
,
280 const std::string
& stampDir
)
283 bool doing_provides
= false;
284 while (cmSystemTools::GetLineFromStream(fin
, line
)) {
285 // Ignore comments and empty lines.
286 if (line
.empty() || line
[0] == '#' || line
[0] == '\r') {
290 if (line
[0] == ' ') {
291 if (doing_provides
) {
292 std::string mod
= line
;
293 if (!cmHasLiteralSuffix(mod
, ".mod") &&
294 !cmHasLiteralSuffix(mod
, ".smod") &&
295 !cmHasLiteralSuffix(mod
, ".sub")) {
296 // Support fortran.internal files left by older versions of CMake.
297 // They do not include the ".mod" extension.
300 this->ConsiderModule(mod
.substr(1), stampDir
);
302 } else if (line
== "provides") {
303 doing_provides
= true;
305 doing_provides
= false;
310 void cmDependsFortran::ConsiderModule(const std::string
& name
,
311 const std::string
& stampDir
)
313 // Locate each required module.
314 auto required
= this->Internal
->TargetRequires
.find(name
);
315 if (required
!= this->Internal
->TargetRequires
.end() &&
316 required
->second
.empty()) {
317 // The module is provided by a CMake target. It will have a stamp file.
318 std::string stampFile
= cmStrCat(stampDir
, '/', name
, ".stamp");
319 required
->second
= stampFile
;
323 bool cmDependsFortran::WriteDependenciesReal(std::string
const& obj
,
324 cmFortranSourceInfo
const& info
,
325 std::string
const& mod_dir
,
326 std::string
const& stamp_dir
,
327 std::ostream
& makeDepends
,
328 std::ostream
& internalDepends
,
329 bool buildingIntrinsics
)
331 // Get the source file for this object.
332 std::string
const& src
= info
.Source
;
334 // Write the include dependencies to the output stream.
335 std::string obj_i
= this->LocalGenerator
->MaybeRelativeToTopBinDir(obj
);
336 std::string obj_m
= cmSystemTools::ConvertToOutputPath(obj_i
);
337 internalDepends
<< obj_i
<< "\n " << src
<< '\n';
338 if (!info
.Includes
.empty()) {
339 const auto& lineContinue
= static_cast<cmGlobalUnixMakefileGenerator3
*>(
340 this->LocalGenerator
->GetGlobalGenerator())
341 ->LineContinueDirective
;
342 bool supportLongLineDepend
= static_cast<cmGlobalUnixMakefileGenerator3
*>(
343 this->LocalGenerator
->GetGlobalGenerator())
344 ->SupportsLongLineDependencies();
345 if (supportLongLineDepend
) {
346 makeDepends
<< obj_m
<< ':';
348 for (std::string
const& i
: info
.Includes
) {
349 std::string dependee
= cmSystemTools::ConvertToOutputPath(
350 this->LocalGenerator
->MaybeRelativeToTopBinDir(i
));
351 if (supportLongLineDepend
) {
352 makeDepends
<< ' ' << lineContinue
<< ' ' << dependee
;
354 makeDepends
<< obj_m
<< ": " << dependee
<< '\n';
356 internalDepends
<< ' ' << i
<< '\n';
361 std::set
<std::string
> req
= info
.Requires
;
362 if (buildingIntrinsics
) {
363 req
.insert(info
.Intrinsics
.begin(), info
.Intrinsics
.end());
366 // Write module requirements to the output stream.
367 for (std::string
const& i
: req
) {
368 // Require only modules not provided in the same source.
369 if (info
.Provides
.find(i
) != info
.Provides
.cend()) {
373 // The object file should depend on timestamped files for the
375 auto required
= this->Internal
->TargetRequires
.find(i
);
376 if (required
== this->Internal
->TargetRequires
.end()) {
379 if (!required
->second
.empty()) {
380 // This module is known. Depend on its timestamp file.
381 std::string stampFile
= cmSystemTools::ConvertToOutputPath(
382 this->LocalGenerator
->MaybeRelativeToTopBinDir(required
->second
));
383 makeDepends
<< obj_m
<< ": " << stampFile
<< '\n';
385 // This module is not known to CMake. Try to locate it where
386 // the compiler will and depend on that.
388 if (this->FindModule(i
, module
)) {
389 module
= cmSystemTools::ConvertToOutputPath(
390 this->LocalGenerator
->MaybeRelativeToTopBinDir(module
));
391 makeDepends
<< obj_m
<< ": " << module
<< '\n';
396 // If any modules are provided then they must be converted to stamp files.
397 if (!info
.Provides
.empty()) {
398 // Create a target to copy the module after the object file
400 for (std::string
const& i
: info
.Provides
) {
401 // Include this module in the set provided by this target.
402 this->Internal
->TargetProvides
.insert(i
);
404 // Always use lower case for the mod stamp file name. The
405 // cmake_copy_f90_mod will call back to this class, which will
406 // try various cases for the real mod file name.
407 std::string modFile
= cmStrCat(mod_dir
, '/', i
);
408 modFile
= this->LocalGenerator
->ConvertToOutputFormat(
409 this->LocalGenerator
->MaybeRelativeToTopBinDir(modFile
),
410 cmOutputConverter::SHELL
);
411 std::string stampFile
= cmStrCat(stamp_dir
, '/', i
, ".stamp");
412 stampFile
= this->LocalGenerator
->MaybeRelativeToTopBinDir(stampFile
);
413 std::string
const stampFileForShell
=
414 this->LocalGenerator
->ConvertToOutputFormat(stampFile
,
415 cmOutputConverter::SHELL
);
416 std::string
const stampFileForMake
=
417 cmSystemTools::ConvertToOutputPath(stampFile
);
419 makeDepends
<< obj_m
<< ".provides.build"
420 << ": " << stampFileForMake
<< '\n';
421 // Note that when cmake_copy_f90_mod finds that a module file
422 // and the corresponding stamp file have no differences, the stamp
423 // file is not updated. In such case the stamp file will be always
424 // older than its prerequisite and trigger cmake_copy_f90_mod
425 // on each new build. This is expected behavior for incremental
426 // builds and can not be changed without performing recursive make
427 // calls that would considerably slow down the building process.
428 makeDepends
<< stampFileForMake
<< ": " << obj_m
<< '\n';
429 makeDepends
<< "\t$(CMAKE_COMMAND) -E cmake_copy_f90_mod " << modFile
430 << ' ' << stampFileForShell
;
431 cmMakefile
* mf
= this->LocalGenerator
->GetMakefile();
432 cmValue cid
= mf
->GetDefinition("CMAKE_Fortran_COMPILER_ID");
433 if (cmNonempty(cid
)) {
434 makeDepends
<< ' ' << *cid
;
438 makeDepends
<< obj_m
<< ".provides.build:\n";
439 // After copying the modules update the timestamp file.
440 makeDepends
<< "\t$(CMAKE_COMMAND) -E touch " << obj_m
441 << ".provides.build\n";
443 // Make sure the module timestamp rule is evaluated by the time
444 // the target finishes building.
445 std::string driver
= cmStrCat(this->TargetDirectory
, "/build");
446 driver
= cmSystemTools::ConvertToOutputPath(
447 this->LocalGenerator
->MaybeRelativeToTopBinDir(driver
));
448 makeDepends
<< driver
<< ": " << obj_m
<< ".provides.build\n";
454 bool cmDependsFortran::FindModule(std::string
const& name
, std::string
& module
)
456 // Construct possible names for the module file.
457 std::string mod_upper
;
458 std::string mod_lower
;
459 cmFortranModuleAppendUpperLower(name
, mod_upper
, mod_lower
);
461 // Search the include path for the module.
462 std::string fullName
;
463 for (std::string
const& ip
: this->IncludePath
) {
464 // Try the lower-case name.
465 fullName
= cmStrCat(ip
, '/', mod_lower
);
466 if (cmSystemTools::FileExists(fullName
, true)) {
471 // Try the upper-case name.
472 fullName
= cmStrCat(ip
, '/', mod_upper
);
473 if (cmSystemTools::FileExists(fullName
, true)) {
481 bool cmDependsFortran::CopyModule(const std::vector
<std::string
>& args
)
485 // $(CMAKE_COMMAND) -E cmake_copy_f90_mod input.mod output.mod.stamp
488 // Note that the case of the .mod file depends on the compiler. In
489 // the future this copy could also account for the fact that some
490 // compilers include a timestamp in the .mod file so it changes even
491 // when the interface described in the module does not.
493 std::string mod
= args
[2];
494 std::string
const& stamp
= args
[3];
495 std::string compilerId
;
496 if (args
.size() >= 5) {
497 compilerId
= args
[4];
499 if (!cmHasLiteralSuffix(mod
, ".mod") && !cmHasLiteralSuffix(mod
, ".smod") &&
500 !cmHasLiteralSuffix(mod
, ".sub")) {
501 // Support depend.make files left by older versions of CMake.
502 // They do not include the ".mod" extension.
505 std::string mod_dir
= cmSystemTools::GetFilenamePath(mod
);
506 if (!mod_dir
.empty()) {
509 std::string mod_upper
= mod_dir
;
510 std::string mod_lower
= mod_dir
;
511 cmFortranModuleAppendUpperLower(cmSystemTools::GetFilenameName(mod
),
512 mod_upper
, mod_lower
);
513 if (cmSystemTools::FileExists(mod_upper
, true)) {
514 if (cmDependsFortran::ModulesDiffer(mod_upper
, stamp
, compilerId
)) {
515 if (!cmSystemTools::CopyFileAlways(mod_upper
, stamp
)) {
516 std::cerr
<< "Error copying Fortran module from \"" << mod_upper
517 << "\" to \"" << stamp
<< "\".\n";
523 if (cmSystemTools::FileExists(mod_lower
, true)) {
524 if (cmDependsFortran::ModulesDiffer(mod_lower
, stamp
, compilerId
)) {
525 if (!cmSystemTools::CopyFileAlways(mod_lower
, stamp
)) {
526 std::cerr
<< "Error copying Fortran module from \"" << mod_lower
527 << "\" to \"" << stamp
<< "\".\n";
534 std::cerr
<< "Error copying Fortran module \"" << args
[2] << "\". Tried \""
535 << mod_upper
<< "\" and \"" << mod_lower
<< "\".\n";
539 // Helper function to look for a short sequence in a stream. If this
540 // is later used for longer sequences it should be re-written using an
541 // efficient string search algorithm such as Boyer-Moore.
542 static bool cmFortranStreamContainsSequence(std::istream
& ifs
, const char* seq
,
549 // Get the next character.
550 int token
= ifs
.get();
555 // Check the character.
556 if (token
== static_cast<int>(seq
[cur
])) {
559 // Assume the sequence has no repeating subsequence.
564 // The entire sequence was matched.
568 // Helper function to compare the remaining content in two streams.
569 static bool cmFortranStreamsDiffer(std::istream
& ifs1
, std::istream
& ifs2
)
571 // Compare the remaining content.
573 int ifs1_c
= ifs1
.get();
574 int ifs2_c
= ifs2
.get();
575 if (!ifs1
&& !ifs2
) {
576 // We have reached the end of both streams simultaneously.
577 // The streams are identical.
581 if (!ifs1
|| !ifs2
|| ifs1_c
!= ifs2_c
) {
582 // We have reached the end of one stream before the other or
583 // found differing content. The streams are different.
591 bool cmDependsFortran::ModulesDiffer(const std::string
& modFile
,
592 const std::string
& stampFile
,
593 const std::string
& compilerId
)
597 A mod file is an ascii file compressed with gzip.
598 Compiling twice produces identical modules.
601 A mod file is an ascii file.
603 FORTRAN module created from /path/to/foo.f90 on Sun Dec 30 22:47:58 2007
604 If you edit this, you'll get what you deserve.
607 As you can see the first line contains the date.
610 A mod file is a binary file.
611 However, looking into both generated bar.mod files with a hex editor
612 shows that they differ only before a sequence linefeed-zero (0x0A 0x00)
613 which is located some bytes in front of the absolute path to the source
617 A mod file is a binary file. Compiling twice produces identical modules.
623 /* Compilers which do _not_ produce different mod content when the same
624 * source is compiled twice
627 if (compilerId
== "SunPro") {
628 return cmSystemTools::FilesDiffer(modFile
, stampFile
);
631 #if defined(_WIN32) || defined(__CYGWIN__)
632 cmsys::ifstream
finModFile(modFile
.c_str(), std::ios::in
| std::ios::binary
);
633 cmsys::ifstream
finStampFile(stampFile
.c_str(),
634 std::ios::in
| std::ios::binary
);
636 cmsys::ifstream
finModFile(modFile
.c_str());
637 cmsys::ifstream
finStampFile(stampFile
.c_str());
639 if (!finModFile
|| !finStampFile
) {
640 // At least one of the files does not exist. The modules differ.
644 /* Compilers which _do_ produce different mod content when the same
645 * source is compiled twice
649 * Eat the stream content until all recompile only related changes
652 if (compilerId
== "GNU") {
653 // GNU Fortran 4.9 and later compress .mod files with gzip
654 // but also do not include a date so we can fall through to
655 // compare them without skipping any prefix.
656 unsigned char hdr
[2];
657 bool okay
= !finModFile
.read(reinterpret_cast<char*>(hdr
), 2).fail();
659 if (!okay
|| hdr
[0] != 0x1f || hdr
[1] != 0x8b) {
660 const char seq
[1] = { '\n' };
661 const int seqlen
= 1;
663 if (!cmFortranStreamContainsSequence(finModFile
, seq
, seqlen
)) {
664 // The module is of unexpected format. Assume it is different.
665 std::cerr
<< compilerId
<< " fortran module " << modFile
666 << " has unexpected format." << std::endl
;
670 if (!cmFortranStreamContainsSequence(finStampFile
, seq
, seqlen
)) {
671 // The stamp must differ if the sequence is not contained.
675 } else if (compilerId
== "Intel" || compilerId
== "IntelLLVM") {
676 const char seq
[2] = { '\n', '\0' };
677 const int seqlen
= 2;
679 // Skip the leading byte which appears to be a version number.
680 // We do not need to check for an error because the sequence search
681 // below will fail in that case.
685 if (!cmFortranStreamContainsSequence(finModFile
, seq
, seqlen
)) {
686 // The module is of unexpected format. Assume it is different.
687 std::cerr
<< compilerId
<< " fortran module " << modFile
688 << " has unexpected format." << std::endl
;
692 if (!cmFortranStreamContainsSequence(finStampFile
, seq
, seqlen
)) {
693 // The stamp must differ if the sequence is not contained.
698 // Compare the remaining content. If no compiler id matched above,
699 // including the case none was given, this will compare the whole
701 return cmFortranStreamsDiffer(finModFile
, finStampFile
);