Coverage Report

Created: 2026-03-12 06:35

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