Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmDependsFortran.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmDependsFortran.h"
4
5
#include <cassert>
6
#include <cstdlib>
7
#include <iostream>
8
#include <map>
9
#include <utility>
10
11
#include "cmsys/FStream.hxx"
12
13
#include "cmFortranParser.h" /* Interface to parser object.  */
14
#include "cmGeneratedFileStream.h"
15
#include "cmGlobalUnixMakefileGenerator3.h"
16
#include "cmList.h"
17
#include "cmLocalUnixMakefileGenerator3.h"
18
#include "cmMakefile.h"
19
#include "cmOutputConverter.h"
20
#include "cmStringAlgorithms.h"
21
#include "cmSystemTools.h"
22
#include "cmValue.h"
23
24
// TODO: Test compiler for the case of the mod file.  Some always
25
// use lower case and some always use upper case.  I do not know if any
26
// use the case from the source code.
27
28
static void cmFortranModuleAppendUpperLower(std::string const& mod,
29
                                            std::string& mod_upper,
30
                                            std::string& mod_lower)
31
0
{
32
0
  std::string::size_type ext_len = 0;
33
0
  if (cmHasLiteralSuffix(mod, ".mod") || cmHasLiteralSuffix(mod, ".sub")) {
34
0
    ext_len = 4;
35
0
  } else if (cmHasLiteralSuffix(mod, ".smod")) {
36
0
    ext_len = 5;
37
0
  }
38
0
  std::string const& name = mod.substr(0, mod.size() - ext_len);
39
0
  std::string const& ext = mod.substr(mod.size() - ext_len);
40
0
  mod_upper =
41
0
    cmStrCat(std::move(mod_upper), cmSystemTools::UpperCase(name), ext);
42
0
  mod_lower += mod;
43
0
}
44
45
class cmDependsFortranInternals
46
{
47
public:
48
  // The set of modules provided by this target.
49
  std::set<std::string> TargetProvides;
50
51
  // Map modules required by this target to locations.
52
  using TargetRequiresMap = std::map<std::string, std::string>;
53
  TargetRequiresMap TargetRequires;
54
55
  // Information about each object file.
56
  using ObjectInfoMap = std::map<std::string, cmFortranSourceInfo>;
57
  ObjectInfoMap ObjectInfo;
58
59
  cmFortranSourceInfo& CreateObjectInfo(std::string const& obj,
60
                                        std::string const& src)
61
0
  {
62
0
    auto i = this->ObjectInfo.find(obj);
63
0
    if (i == this->ObjectInfo.end()) {
64
0
      std::map<std::string, cmFortranSourceInfo>::value_type entry(
65
0
        obj, cmFortranSourceInfo());
66
0
      i = this->ObjectInfo.insert(entry).first;
67
0
      i->second.Source = src;
68
0
    }
69
0
    return i->second;
70
0
  }
71
};
72
73
0
cmDependsFortran::cmDependsFortran() = default;
74
75
cmDependsFortran::cmDependsFortran(cmLocalUnixMakefileGenerator3* lg)
76
0
  : cmDepends(lg)
77
0
  , Internal(new cmDependsFortranInternals)
78
0
{
79
  // Configure the include file search path.
80
0
  this->SetIncludePathFromLanguage("Fortran");
81
82
  // Get the list of definitions.
83
0
  cmMakefile* mf = this->LocalGenerator->GetMakefile();
84
0
  cmList definitions{ mf->GetDefinition("CMAKE_TARGET_DEFINITIONS_Fortran") };
85
86
  // translate i.e. FOO=BAR to FOO and add it to the list of defined
87
  // preprocessor symbols
88
0
  for (std::string def : definitions) {
89
0
    std::string::size_type assignment = def.find('=');
90
0
    if (assignment != std::string::npos) {
91
0
      def.resize(assignment);
92
0
    }
93
0
    this->PPDefinitions.insert(def);
94
0
  }
95
96
0
  this->CompilerId = mf->GetSafeDefinition("CMAKE_Fortran_COMPILER_ID");
97
0
  this->SModSep = mf->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_SEP");
98
0
  this->SModExt = mf->GetSafeDefinition("CMAKE_Fortran_SUBMODULE_EXT");
99
0
}
100
101
0
cmDependsFortran::~cmDependsFortran() = default;
102
103
bool cmDependsFortran::WriteDependencies(std::set<std::string> const& sources,
104
                                         std::string const& obj,
105
                                         std::ostream& /*makeDepends*/,
106
                                         std::ostream& /*internalDepends*/)
107
0
{
108
  // Make sure this is a scanning instance.
109
0
  if (sources.empty() || sources.begin()->empty()) {
110
0
    cmSystemTools::Error("Cannot scan dependencies without a source file.");
111
0
    return false;
112
0
  }
113
0
  if (obj.empty()) {
114
0
    cmSystemTools::Error("Cannot scan dependencies without an object file.");
115
0
    return false;
116
0
  }
117
118
0
  cmFortranCompiler fc;
119
0
  fc.Id = this->CompilerId;
120
0
  fc.SModSep = this->SModSep;
121
0
  fc.SModExt = this->SModExt;
122
123
0
  bool okay = true;
124
0
  for (std::string const& src : sources) {
125
    // Get the information object for this source.
126
0
    cmFortranSourceInfo& info = this->Internal->CreateObjectInfo(obj, src);
127
128
    // Create the parser object. The constructor takes info by reference,
129
    // so we may look into the resulting objects later.
130
0
    cmFortranParser parser(fc, this->IncludePath, this->PPDefinitions, info);
131
132
    // Push on the starting file.
133
0
    cmFortranParser_FilePush(&parser, src.c_str());
134
135
    // Parse the translation unit.
136
0
    if (cmFortran_yyparse(parser.Scanner) != 0) {
137
      // Failed to parse the file.  Report failure to write dependencies.
138
0
      okay = false;
139
      /* clang-format off */
140
0
      std::cerr <<
141
0
        "warning: failed to parse dependencies from Fortran source "
142
0
        "'" << src << "': " << parser.Error << std::endl
143
0
        ;
144
      /* clang-format on */
145
0
    }
146
0
  }
147
0
  return okay;
148
0
}
149
150
bool cmDependsFortran::Finalize(std::ostream& makeDepends,
151
                                std::ostream& internalDepends)
152
0
{
153
  // Prepare the module search process.
154
0
  if (!this->LocateModules()) {
155
0
    return false;
156
0
  }
157
158
  // Get the directory in which stamp files will be stored.
159
0
  std::string const& stamp_dir = this->TargetDirectory;
160
161
  // Get the directory in which module files will be created.
162
0
  cmMakefile* mf = this->LocalGenerator->GetMakefile();
163
0
  std::string mod_dir =
164
0
    mf->GetSafeDefinition("CMAKE_Fortran_TARGET_MODULE_DIR");
165
0
  if (mod_dir.empty()) {
166
0
    mod_dir = this->LocalGenerator->GetCurrentBinaryDirectory();
167
0
  }
168
169
  // ATTENTION Before 4.0 the property name was misspelled.
170
  // Check the correct name first and than the old name.
171
0
  bool building_intrinsics =
172
0
    !mf->GetSafeDefinition("CMAKE_Fortran_TARGET_BUILDING_INTRINSIC_MODULES")
173
0
       .empty() ||
174
0
    !mf->GetSafeDefinition("CMAKE_Fortran_TARGET_BUILDING_INSTRINSIC_MODULES")
175
0
       .empty();
176
177
  // Actually write dependencies to the streams.
178
0
  using ObjectInfoMap = cmDependsFortranInternals::ObjectInfoMap;
179
0
  ObjectInfoMap const& objInfo = this->Internal->ObjectInfo;
180
0
  for (auto const& i : objInfo) {
181
0
    if (!this->WriteDependenciesReal(i.first, i.second, mod_dir, stamp_dir,
182
0
                                     makeDepends, internalDepends,
183
0
                                     building_intrinsics)) {
184
0
      return false;
185
0
    }
186
0
  }
187
188
  // Store the list of modules provided by this target.
189
0
  std::string fiName = cmStrCat(this->TargetDirectory, "/fortran.internal");
190
0
  cmGeneratedFileStream fiStream(fiName);
191
0
  fiStream << "# The fortran modules provided by this target.\n";
192
0
  fiStream << "provides\n";
193
0
  std::set<std::string> const& provides = this->Internal->TargetProvides;
194
0
  for (std::string const& i : provides) {
195
0
    fiStream << ' ' << i << '\n';
196
0
  }
197
198
  // Create a script to clean the modules.
199
0
  if (!provides.empty()) {
200
0
    std::string fcName =
201
0
      cmStrCat(this->TargetDirectory, "/cmake_clean_Fortran.cmake");
202
0
    cmGeneratedFileStream fcStream(fcName);
203
0
    fcStream << "# Remove fortran modules provided by this target.\n";
204
0
    fcStream << "FILE(REMOVE";
205
0
    for (std::string const& i : provides) {
206
0
      std::string mod_upper = cmStrCat(mod_dir, '/');
207
0
      std::string mod_lower = cmStrCat(mod_dir, '/');
208
0
      cmFortranModuleAppendUpperLower(i, mod_upper, mod_lower);
209
0
      std::string stamp = cmStrCat(stamp_dir, '/', i, ".stamp");
210
0
      fcStream << "\n"
211
0
                  "  \""
212
0
               << this->LocalGenerator->MaybeRelativeToCurBinDir(mod_lower)
213
0
               << "\"\n"
214
0
                  "  \""
215
0
               << this->LocalGenerator->MaybeRelativeToCurBinDir(mod_upper)
216
0
               << "\"\n"
217
0
                  "  \""
218
0
               << this->LocalGenerator->MaybeRelativeToCurBinDir(stamp)
219
0
               << "\"\n";
220
0
    }
221
0
    fcStream << "  )\n";
222
0
  }
223
0
  return true;
224
0
}
225
226
bool cmDependsFortran::LocateModules()
227
0
{
228
  // Collect the set of modules provided and required by all sources.
229
0
  using ObjectInfoMap = cmDependsFortranInternals::ObjectInfoMap;
230
0
  ObjectInfoMap const& objInfo = this->Internal->ObjectInfo;
231
0
  for (auto const& infoI : objInfo) {
232
0
    cmFortranSourceInfo const& info = infoI.second;
233
    // Include this module in the set provided by this target.
234
0
    this->Internal->TargetProvides.insert(info.Provides.begin(),
235
0
                                          info.Provides.end());
236
237
0
    for (std::string const& r : info.Requires) {
238
0
      this->Internal->TargetRequires[r].clear();
239
0
    }
240
0
  }
241
242
  // Short-circuit for simple targets.
243
0
  if (this->Internal->TargetRequires.empty()) {
244
0
    return true;
245
0
  }
246
247
  // Match modules provided by this target to those it requires.
248
0
  this->MatchLocalModules();
249
250
  // Load information about other targets.
251
0
  cmMakefile* mf = this->LocalGenerator->GetMakefile();
252
0
  cmList infoFiles{ mf->GetDefinition(
253
0
    "CMAKE_Fortran_TARGET_LINKED_INFO_FILES") };
254
0
  for (auto const& i : infoFiles) {
255
0
    std::string targetDir = cmSystemTools::GetFilenamePath(i);
256
0
    std::string fname = targetDir + "/fortran.internal";
257
0
    cmsys::ifstream fin(fname.c_str());
258
0
    if (!fin) {
259
0
      cmSystemTools::Error(cmStrCat("-E cmake_depends failed to open ", fname,
260
0
                                    " for module information"));
261
0
      return false;
262
0
    }
263
0
    this->MatchRemoteModules(fin, targetDir);
264
0
  }
265
266
  // TODO: Use `CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES` to handle cases
267
  // described in #25425. Note that because Makefiles generators do not
268
  // implement relaxed object compilation as described in #15555, the issues
269
  // never actually cause build failures; only incremental build incorrectness.
270
271
0
  return true;
272
0
}
273
274
void cmDependsFortran::MatchLocalModules()
275
0
{
276
0
  std::string const& stampDir = this->TargetDirectory;
277
0
  std::set<std::string> const& provides = this->Internal->TargetProvides;
278
0
  for (std::string const& i : provides) {
279
0
    this->ConsiderModule(i, stampDir);
280
0
  }
281
0
}
282
283
void cmDependsFortran::MatchRemoteModules(std::istream& fin,
284
                                          std::string const& stampDir)
285
0
{
286
0
  std::string line;
287
0
  bool doing_provides = false;
288
0
  while (cmSystemTools::GetLineFromStream(fin, line)) {
289
    // Ignore comments and empty lines.
290
0
    if (line.empty() || line[0] == '#' || line[0] == '\r') {
291
0
      continue;
292
0
    }
293
294
0
    if (line[0] == ' ') {
295
0
      if (doing_provides) {
296
0
        std::string mod = line;
297
0
        if (!cmHasLiteralSuffix(mod, ".mod") &&
298
0
            !cmHasLiteralSuffix(mod, ".smod") &&
299
0
            !cmHasLiteralSuffix(mod, ".sub")) {
300
          // Support fortran.internal files left by older versions of CMake.
301
          // They do not include the ".mod" extension.
302
0
          mod += ".mod";
303
0
        }
304
0
        this->ConsiderModule(mod.substr(1), stampDir);
305
0
      }
306
0
    } else if (line == "provides") {
307
0
      doing_provides = true;
308
0
    } else {
309
0
      doing_provides = false;
310
0
    }
311
0
  }
312
0
}
313
314
void cmDependsFortran::ConsiderModule(std::string const& name,
315
                                      std::string const& stampDir)
316
0
{
317
  // Locate each required module.
318
0
  auto required = this->Internal->TargetRequires.find(name);
319
0
  if (required != this->Internal->TargetRequires.end() &&
320
0
      required->second.empty()) {
321
    // The module is provided by a CMake target.  It will have a stamp file.
322
0
    std::string stampFile = cmStrCat(stampDir, '/', name, ".stamp");
323
0
    required->second = stampFile;
324
0
  }
325
0
}
326
327
bool cmDependsFortran::WriteDependenciesReal(std::string const& obj,
328
                                             cmFortranSourceInfo const& info,
329
                                             std::string const& mod_dir,
330
                                             std::string const& stamp_dir,
331
                                             std::ostream& makeDepends,
332
                                             std::ostream& internalDepends,
333
                                             bool buildingIntrinsics)
334
0
{
335
  // Get the source file for this object.
336
0
  std::string const& src = info.Source;
337
338
  // Write the include dependencies to the output stream.
339
0
  std::string obj_i = this->LocalGenerator->MaybeRelativeToTopBinDir(obj);
340
0
  std::string obj_m = cmSystemTools::ConvertToOutputPath(obj_i);
341
0
  internalDepends << obj_i << "\n " << src << '\n';
342
0
  if (!info.Includes.empty()) {
343
0
    auto const& lineContinue = static_cast<cmGlobalUnixMakefileGenerator3*>(
344
0
                                 this->LocalGenerator->GetGlobalGenerator())
345
0
                                 ->LineContinueDirective;
346
0
    bool supportLongLineDepend = static_cast<cmGlobalUnixMakefileGenerator3*>(
347
0
                                   this->LocalGenerator->GetGlobalGenerator())
348
0
                                   ->SupportsLongLineDependencies();
349
0
    if (supportLongLineDepend) {
350
0
      makeDepends << obj_m << ':';
351
0
    }
352
0
    for (std::string const& i : info.Includes) {
353
0
      std::string dependee = cmSystemTools::ConvertToOutputPath(
354
0
        this->LocalGenerator->MaybeRelativeToTopBinDir(i));
355
0
      if (supportLongLineDepend) {
356
0
        makeDepends << ' ' << lineContinue << ' ' << dependee;
357
0
      } else {
358
0
        makeDepends << obj_m << ": " << dependee << '\n';
359
0
      }
360
0
      internalDepends << ' ' << i << '\n';
361
0
    }
362
0
    makeDepends << '\n';
363
0
  }
364
365
0
  std::set<std::string> req = info.Requires;
366
0
  if (buildingIntrinsics) {
367
0
    req.insert(info.Intrinsics.begin(), info.Intrinsics.end());
368
0
  }
369
370
  // Write module requirements to the output stream.
371
0
  for (std::string const& i : req) {
372
    // Require only modules not provided in the same source.
373
0
    if (info.Provides.find(i) != info.Provides.cend()) {
374
0
      continue;
375
0
    }
376
377
    // The object file should depend on timestamped files for the
378
    // modules it uses.
379
0
    auto required = this->Internal->TargetRequires.find(i);
380
0
    if (required == this->Internal->TargetRequires.end()) {
381
0
      abort();
382
0
    }
383
0
    if (!required->second.empty()) {
384
      // This module is known.  Depend on its timestamp file.
385
0
      std::string stampFile = cmSystemTools::ConvertToOutputPath(
386
0
        this->LocalGenerator->MaybeRelativeToTopBinDir(required->second));
387
0
      makeDepends << obj_m << ": " << stampFile << '\n';
388
0
    } else {
389
      // This module is not known to CMake.  Try to locate it where
390
      // the compiler will and depend on that.
391
0
      std::string module;
392
0
      if (this->FindModule(i, module)) {
393
0
        module = cmSystemTools::ConvertToOutputPath(
394
0
          this->LocalGenerator->MaybeRelativeToTopBinDir(module));
395
0
        makeDepends << obj_m << ": " << module << '\n';
396
0
      }
397
0
    }
398
0
  }
399
400
  // If any modules are provided then they must be converted to stamp files.
401
0
  if (!info.Provides.empty()) {
402
    // Create a target to copy the module after the object file
403
    // changes.
404
0
    for (std::string const& i : info.Provides) {
405
      // Include this module in the set provided by this target.
406
0
      this->Internal->TargetProvides.insert(i);
407
408
      // Always use lower case for the mod stamp file name.  The
409
      // cmake_copy_f90_mod will call back to this class, which will
410
      // try various cases for the real mod file name.
411
0
      std::string modFile = cmStrCat(mod_dir, '/', i);
412
0
      modFile = this->LocalGenerator->ConvertToOutputFormat(
413
0
        this->LocalGenerator->MaybeRelativeToTopBinDir(modFile),
414
0
        cmOutputConverter::SHELL);
415
0
      std::string stampFile = cmStrCat(stamp_dir, '/', i, ".stamp");
416
0
      stampFile = this->LocalGenerator->MaybeRelativeToTopBinDir(stampFile);
417
0
      std::string const stampFileForShell =
418
0
        this->LocalGenerator->ConvertToOutputFormat(stampFile,
419
0
                                                    cmOutputConverter::SHELL);
420
0
      std::string const stampFileForMake =
421
0
        cmSystemTools::ConvertToOutputPath(stampFile);
422
423
0
      makeDepends << obj_m << ".provides.build"
424
0
                  << ": " << stampFileForMake << '\n';
425
      // Note that when cmake_copy_f90_mod finds that a module file
426
      // and the corresponding stamp file have no differences, the stamp
427
      // file is not updated. In such case the stamp file will be always
428
      // older than its prerequisite and trigger cmake_copy_f90_mod
429
      // on each new build. This is expected behavior for incremental
430
      // builds and can not be changed without performing recursive make
431
      // calls that would considerably slow down the building process.
432
0
      makeDepends << stampFileForMake << ": " << obj_m << '\n';
433
0
      makeDepends << "\t$(CMAKE_COMMAND) -E cmake_copy_f90_mod " << modFile
434
0
                  << ' ' << stampFileForShell;
435
0
      cmMakefile* mf = this->LocalGenerator->GetMakefile();
436
0
      cmValue cid = mf->GetDefinition("CMAKE_Fortran_COMPILER_ID");
437
0
      if (cmNonempty(cid)) {
438
0
        makeDepends << ' ' << *cid;
439
0
      }
440
0
      makeDepends << '\n';
441
0
    }
442
0
    makeDepends << obj_m << ".provides.build:\n";
443
    // After copying the modules update the timestamp file.
444
0
    makeDepends << "\t$(CMAKE_COMMAND) -E touch " << obj_m
445
0
                << ".provides.build\n";
446
447
    // Make sure the module timestamp rule is evaluated by the time
448
    // the target finishes building.
449
0
    std::string driver = cmStrCat(this->TargetDirectory, "/build");
450
0
    driver = cmSystemTools::ConvertToOutputPath(
451
0
      this->LocalGenerator->MaybeRelativeToTopBinDir(driver));
452
0
    makeDepends << driver << ": " << obj_m << ".provides.build\n";
453
0
  }
454
455
0
  return true;
456
0
}
457
458
bool cmDependsFortran::FindModule(std::string const& name, std::string& module)
459
0
{
460
  // Construct possible names for the module file.
461
0
  std::string mod_upper;
462
0
  std::string mod_lower;
463
0
  cmFortranModuleAppendUpperLower(name, mod_upper, mod_lower);
464
465
  // Search the include path for the module.
466
0
  std::string fullName;
467
0
  for (std::string const& ip : this->IncludePath) {
468
    // Try the lower-case name.
469
0
    fullName = cmStrCat(ip, '/', mod_lower);
470
0
    if (cmSystemTools::FileExists(fullName, true)) {
471
0
      module = fullName;
472
0
      return true;
473
0
    }
474
475
    // Try the upper-case name.
476
0
    fullName = cmStrCat(ip, '/', mod_upper);
477
0
    if (cmSystemTools::FileExists(fullName, true)) {
478
0
      module = fullName;
479
0
      return true;
480
0
    }
481
0
  }
482
0
  return false;
483
0
}
484
485
bool cmDependsFortran::CopyModule(std::vector<std::string> const& args)
486
0
{
487
  // Implements
488
  //
489
  //   $(CMAKE_COMMAND) -E cmake_copy_f90_mod input.mod output.mod.stamp
490
  //                                          [compiler-id]
491
  //
492
  // Note that the case of the .mod file depends on the compiler.  In
493
  // the future this copy could also account for the fact that some
494
  // compilers include a timestamp in the .mod file so it changes even
495
  // when the interface described in the module does not.
496
497
0
  std::string mod = args[2];
498
0
  std::string const& stamp = args[3];
499
0
  std::string compilerId;
500
0
  if (args.size() >= 5) {
501
0
    compilerId = args[4];
502
0
  }
503
0
  if (!cmHasLiteralSuffix(mod, ".mod") && !cmHasLiteralSuffix(mod, ".smod") &&
504
0
      !cmHasLiteralSuffix(mod, ".sub")) {
505
    // Support depend.make files left by older versions of CMake.
506
    // They do not include the ".mod" extension.
507
0
    mod += ".mod";
508
0
  }
509
0
  std::string mod_dir = cmSystemTools::GetFilenamePath(mod);
510
0
  if (!mod_dir.empty()) {
511
0
    mod_dir += "/";
512
0
  }
513
0
  std::string mod_upper = mod_dir;
514
0
  std::string mod_lower = mod_dir;
515
0
  cmFortranModuleAppendUpperLower(cmSystemTools::GetFilenameName(mod),
516
0
                                  mod_upper, mod_lower);
517
0
  if (cmSystemTools::FileExists(mod_upper, true)) {
518
0
    if (cmDependsFortran::ModulesDiffer(mod_upper, stamp, compilerId)) {
519
0
      if (!cmSystemTools::CopyFileAlways(mod_upper, stamp)) {
520
0
        std::cerr << "Error copying Fortran module from \"" << mod_upper
521
0
                  << "\" to \"" << stamp << "\".\n";
522
0
        return false;
523
0
      }
524
0
    }
525
0
    return true;
526
0
  }
527
0
  if (cmSystemTools::FileExists(mod_lower, true)) {
528
0
    if (cmDependsFortran::ModulesDiffer(mod_lower, stamp, compilerId)) {
529
0
      if (!cmSystemTools::CopyFileAlways(mod_lower, stamp)) {
530
0
        std::cerr << "Error copying Fortran module from \"" << mod_lower
531
0
                  << "\" to \"" << stamp << "\".\n";
532
0
        return false;
533
0
      }
534
0
    }
535
0
    return true;
536
0
  }
537
538
0
  std::cerr << "Error copying Fortran module \"" << args[2] << "\".  Tried \""
539
0
            << mod_upper << "\" and \"" << mod_lower << "\".\n";
540
0
  return false;
541
0
}
542
543
// Helper function to look for a short sequence in a stream.  If this
544
// is later used for longer sequences it should be re-written using an
545
// efficient string search algorithm such as Boyer-Moore.
546
static bool cmFortranStreamContainsSequence(std::istream& ifs, char const* seq,
547
                                            int len)
548
0
{
549
0
  assert(len > 0);
550
551
0
  int cur = 0;
552
0
  while (cur < len) {
553
    // Get the next character.
554
0
    int token = ifs.get();
555
0
    if (!ifs) {
556
0
      return false;
557
0
    }
558
559
    // Check the character.
560
0
    if (token == static_cast<int>(seq[cur])) {
561
0
      ++cur;
562
0
    } else {
563
      // Assume the sequence has no repeating subsequence.
564
0
      cur = 0;
565
0
    }
566
0
  }
567
568
  // The entire sequence was matched.
569
0
  return true;
570
0
}
571
572
// Helper function to compare the remaining content in two streams.
573
static bool cmFortranStreamsDiffer(std::istream& ifs1, std::istream& ifs2)
574
0
{
575
  // Compare the remaining content.
576
0
  for (;;) {
577
0
    int ifs1_c = ifs1.get();
578
0
    int ifs2_c = ifs2.get();
579
0
    if (!ifs1 && !ifs2) {
580
      // We have reached the end of both streams simultaneously.
581
      // The streams are identical.
582
0
      return false;
583
0
    }
584
585
0
    if (!ifs1 || !ifs2 || ifs1_c != ifs2_c) {
586
      // We have reached the end of one stream before the other or
587
      // found differing content.  The streams are different.
588
0
      break;
589
0
    }
590
0
  }
591
592
0
  return true;
593
0
}
594
595
bool cmDependsFortran::ModulesDiffer(std::string const& modFile,
596
                                     std::string const& stampFile,
597
                                     std::string const& compilerId)
598
0
{
599
  /*
600
  gnu >= 4.9:
601
    A mod file is an ascii file compressed with gzip.
602
    Compiling twice produces identical modules.
603
604
  gnu < 4.9:
605
    A mod file is an ascii file.
606
    <bar.mod>
607
    FORTRAN module created from /path/to/foo.f90 on Sun Dec 30 22:47:58 2007
608
    If you edit this, you'll get what you deserve.
609
    ...
610
    </bar.mod>
611
    As you can see the first line contains the date.
612
613
  intel:
614
    A mod file is a binary file.
615
    However, looking into both generated bar.mod files with a hex editor
616
    shows that they differ only before a sequence linefeed-zero (0x0A 0x00)
617
    which is located some bytes in front of the absolute path to the source
618
    file.
619
620
  sun:
621
    A mod file is a binary file.  Compiling twice produces identical modules.
622
623
  others:
624
    TODO ...
625
  */
626
627
  /* Compilers which do _not_ produce different mod content when the same
628
   * source is compiled twice
629
   *   -SunPro
630
   */
631
0
  if (compilerId == "SunPro") {
632
0
    return cmSystemTools::FilesDiffer(modFile, stampFile);
633
0
  }
634
635
#if defined(_WIN32) || defined(__CYGWIN__)
636
  cmsys::ifstream finModFile(modFile.c_str(), std::ios::in | std::ios::binary);
637
  cmsys::ifstream finStampFile(stampFile.c_str(),
638
                               std::ios::in | std::ios::binary);
639
#else
640
0
  cmsys::ifstream finModFile(modFile.c_str());
641
0
  cmsys::ifstream finStampFile(stampFile.c_str());
642
0
#endif
643
0
  if (!finModFile || !finStampFile) {
644
    // At least one of the files does not exist.  The modules differ.
645
0
    return true;
646
0
  }
647
648
  /* Compilers which _do_ produce different mod content when the same
649
   * source is compiled twice
650
   *   -GNU
651
   *   -Intel
652
   *
653
   * Eat the stream content until all recompile only related changes
654
   * are left behind.
655
   */
656
0
  if (compilerId == "GNU") {
657
    // GNU Fortran 4.9 and later compress .mod files with gzip
658
    // but also do not include a date so we can fall through to
659
    // compare them without skipping any prefix.
660
0
    unsigned char hdr[2];
661
0
    bool okay = !finModFile.read(reinterpret_cast<char*>(hdr), 2).fail();
662
0
    finModFile.seekg(0);
663
0
    if (!okay || hdr[0] != 0x1f || hdr[1] != 0x8b) {
664
0
      char const seq[1] = { '\n' };
665
0
      int const seqlen = 1;
666
667
0
      if (!cmFortranStreamContainsSequence(finModFile, seq, seqlen)) {
668
        // The module is of unexpected format.  Assume it is different.
669
0
        std::cerr << compilerId << " fortran module " << modFile
670
0
                  << " has unexpected format." << std::endl;
671
0
        return true;
672
0
      }
673
674
0
      if (!cmFortranStreamContainsSequence(finStampFile, seq, seqlen)) {
675
        // The stamp must differ if the sequence is not contained.
676
0
        return true;
677
0
      }
678
0
    }
679
0
  } else if (compilerId == "Intel" || compilerId == "IntelLLVM") {
680
0
    char const seq[2] = { '\n', '\0' };
681
0
    int const seqlen = 2;
682
683
    // Skip the leading byte which appears to be a version number.
684
    // We do not need to check for an error because the sequence search
685
    // below will fail in that case.
686
0
    finModFile.get();
687
0
    finStampFile.get();
688
689
0
    if (!cmFortranStreamContainsSequence(finModFile, seq, seqlen)) {
690
      // The module is of unexpected format.  Assume it is different.
691
0
      std::cerr << compilerId << " fortran module " << modFile
692
0
                << " has unexpected format." << std::endl;
693
0
      return true;
694
0
    }
695
696
0
    if (!cmFortranStreamContainsSequence(finStampFile, seq, seqlen)) {
697
      // The stamp must differ if the sequence is not contained.
698
0
      return true;
699
0
    }
700
0
  }
701
702
  // Compare the remaining content.  If no compiler id matched above,
703
  // including the case none was given, this will compare the whole
704
  // content.
705
0
  return cmFortranStreamsDiffer(finModFile, finStampFile);
706
0
}