/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 | } |