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