cmDependsFortran.cxx 25 KB

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