1 | # ------------------------------------------------------------------------------ |
---|
2 | # Copyright (C) 2006-2021 British Crown (Met Office) & Contributors. |
---|
3 | # |
---|
4 | # This file is part of FCM, tools for managing and building source code. |
---|
5 | # |
---|
6 | # FCM is free software: you can redistribute it and/or modify |
---|
7 | # it under the terms of the GNU General Public License as published by |
---|
8 | # the Free Software Foundation, either version 3 of the License, or |
---|
9 | # (at your option) any later version. |
---|
10 | # |
---|
11 | # FCM is distributed in the hope that it will be useful, |
---|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
14 | # GNU General Public License for more details. |
---|
15 | # |
---|
16 | # You should have received a copy of the GNU General Public License |
---|
17 | # along with FCM. If not, see <http://www.gnu.org/licenses/>. |
---|
18 | # ------------------------------------------------------------------------------ |
---|
19 | use strict; |
---|
20 | use warnings; |
---|
21 | |
---|
22 | # ------------------------------------------------------------------------------ |
---|
23 | package FCM::System::Make::Build::FileType::Fortran; |
---|
24 | use base qw{FCM::System::Make::Build::FileType}; |
---|
25 | |
---|
26 | use FCM::Context::Make::Build; # for FCM::Context::Make::Build::Target |
---|
27 | use FCM::System::Make::Build::Task::Compile::Fortran; |
---|
28 | use FCM::System::Make::Build::Task::ExtractInterface; |
---|
29 | use FCM::System::Make::Build::Task::Install; |
---|
30 | use FCM::System::Make::Build::Task::Link::Fortran; |
---|
31 | use File::Basename qw{basename}; |
---|
32 | use Text::Balanced qw{extract_bracketed extract_delimited}; |
---|
33 | |
---|
34 | # Recommended file extensions of this utility |
---|
35 | our $FILE_EXT = '.F .F90 .F95 .FOR .FTN .f .f90 .f95 .for .ftn .inc'; |
---|
36 | |
---|
37 | # List of Fortran intrinsic modules |
---|
38 | our @INTRINSIC_MODULES = qw{ |
---|
39 | ieee_arithmetic |
---|
40 | ieee_exceptions |
---|
41 | ieee_features |
---|
42 | iso_c_binding |
---|
43 | iso_fortran_env |
---|
44 | omp_lib |
---|
45 | omp_lib_kinds |
---|
46 | }; |
---|
47 | |
---|
48 | # Prefix for dependency name that is only applicable under OMP |
---|
49 | our $OMP_PREFIX = '!$'; |
---|
50 | |
---|
51 | # Regular expressions |
---|
52 | my $RE_FILE = qr{[\w\-+.]+}imsx; |
---|
53 | my $RE_NAME = qr{[A-Za-z]\w*}imsx; |
---|
54 | my $RE_SPEC = qr{ |
---|
55 | character|class|complex|double\s*complex|double\s*precision|integer| |
---|
56 | logical|procedure|real|type |
---|
57 | }imsx; |
---|
58 | my $RE_UNIT_BASE = qr{ |
---|
59 | block\s*data| |
---|
60 | module(?!\s*(?:function|subroutine|procedure)\s+)| |
---|
61 | program| |
---|
62 | }imsx; |
---|
63 | my $RE_UNIT_CALL = qr{subroutine|function}imsx; |
---|
64 | my %RE = ( |
---|
65 | DEP_O => qr{\A\s*!\s*depends\s*on\s*:\s*($RE_FILE)}imsx, |
---|
66 | DEP_USE => qr{\A\s*use\s+($RE_NAME)}imsx, |
---|
67 | DEP_SUBM => qr{\A\s*submodule\s+\(($RE_NAME)\)}imsx, |
---|
68 | INCLUDE => qr{\#?\s*include\s*}imsx, |
---|
69 | OMP_SENT => qr{\A(\s*!\$\s+)?(.*)\z}imsx, |
---|
70 | UNIT_ATTR => qr{\A\s*(?:(?:(?:impure\s+)?elemental|recursive|pure)\s+)+(.*)\z}imsx, |
---|
71 | UNIT_BASE => qr{\A\s*($RE_UNIT_BASE)\s+($RE_NAME)\b}imsx, |
---|
72 | UNIT_SUBM => qr{\A\s*(submodule)\s*\(($RE_NAME)\)\s*($RE_NAME)\b}imsx, |
---|
73 | UNIT_CALL => qr{\A\s*($RE_UNIT_CALL)\s+($RE_NAME)\b}imsx, |
---|
74 | UNIT_END => qr{\A\s*(end)(?:\s+($RE_NAME)(?:\s+($RE_NAME))?)?\b}imsx, |
---|
75 | UNIT_SPEC => qr{\A\s*$RE_SPEC\b(.*)\z}imsx, |
---|
76 | ); |
---|
77 | |
---|
78 | # Dependency types and extractors |
---|
79 | my %SOURCE_ANALYSE_DEP_OF = ( |
---|
80 | 'f.module' => \&_source_analyse_dep_module, |
---|
81 | 'include' => \&_source_analyse_dep_include, |
---|
82 | 'o' => sub { lc($_[0]) =~ $RE{DEP_O} }, # lc required for legacy |
---|
83 | 'o.special' => sub {}, |
---|
84 | ); |
---|
85 | # Alias |
---|
86 | my $TARGET = 'FCM::Context::Make::Build::Target'; |
---|
87 | # Classes for tasks used by targets of this file type |
---|
88 | my %TASK_CLASS_OF = ( |
---|
89 | 'compile' => 'FCM::System::Make::Build::Task::Compile::Fortran', |
---|
90 | 'compile+' => 'FCM::System::Make::Build::Task::Compile::Fortran::Extra', |
---|
91 | 'ext-iface' => 'FCM::System::Make::Build::Task::ExtractInterface', |
---|
92 | 'install' => 'FCM::System::Make::Build::Task::Install', |
---|
93 | 'link' => 'FCM::System::Make::Build::Task::Link::Fortran', |
---|
94 | ); |
---|
95 | # Property suffices of output file extensions |
---|
96 | my %TARGET_EXT_OF = ( |
---|
97 | 'bin' => '.exe', |
---|
98 | 'f90-interface' => '.interface', |
---|
99 | 'f90-mod' => '.mod', |
---|
100 | 'o' => '.o', |
---|
101 | ); |
---|
102 | |
---|
103 | sub new { |
---|
104 | my ($class, $attrib_ref) = @_; |
---|
105 | bless( |
---|
106 | FCM::System::Make::Build::FileType->new({ |
---|
107 | id => 'fortran', |
---|
108 | file_ext => $FILE_EXT, |
---|
109 | source_analyse_always => 1, |
---|
110 | source_analyse_dep_of => {%SOURCE_ANALYSE_DEP_OF}, |
---|
111 | source_analyse_more => \&_source_analyse_more, |
---|
112 | source_analyse_more_init => \&_source_analyse_more_init, |
---|
113 | source_to_targets => \&_source_to_targets, |
---|
114 | target_deps_filter => \&_target_deps_filter, |
---|
115 | target_file_ext_of => {%TARGET_EXT_OF}, |
---|
116 | target_file_name_option_of => {'f90-mod' => q{}}, |
---|
117 | task_class_of => {%TASK_CLASS_OF}, |
---|
118 | %{$attrib_ref}, |
---|
119 | }), |
---|
120 | $class, |
---|
121 | ); |
---|
122 | } |
---|
123 | |
---|
124 | sub _source_analyse_more { |
---|
125 | my ($line, $info_hash_ref, $state) = @_; |
---|
126 | |
---|
127 | # End Interface |
---|
128 | if ($state->{in_interface}) { |
---|
129 | if ($line =~ qr{\A\s*end\s*interface\b}imsx) { |
---|
130 | $state->{in_interface} = 0; |
---|
131 | } |
---|
132 | return 1; |
---|
133 | } |
---|
134 | |
---|
135 | # End Program Unit |
---|
136 | if (@{$state->{stack}} && $line =~ qr{\A\s*end\b}imsx) { |
---|
137 | my ($end, $type, $symbol) = lc($line) =~ $RE{UNIT_END}; |
---|
138 | if (!$end) { |
---|
139 | return 1; |
---|
140 | } |
---|
141 | my ($top_type, $top_symbol) = @{$state->{stack}->[-1]}; |
---|
142 | if (!$type |
---|
143 | || $top_type eq $type && (!$symbol || $top_symbol eq $symbol)) |
---|
144 | { |
---|
145 | pop(@{$state->{stack}}); |
---|
146 | if ($state->{in_contains} && !@{$state->{stack}}) { |
---|
147 | $state->{in_contains} = 0; |
---|
148 | } |
---|
149 | } |
---|
150 | return 1; |
---|
151 | } |
---|
152 | |
---|
153 | # Interface/Contains |
---|
154 | if ($line =~ qr{\A\s*contains\b}imsx) { |
---|
155 | $state->{'in_contains'} = 1; |
---|
156 | return 1; |
---|
157 | } |
---|
158 | if ($line =~ qr{\A\s*(?:abstract\s+)?interface\b}imsx) { |
---|
159 | $state->{'in_interface'} = 1; |
---|
160 | return 1; |
---|
161 | } |
---|
162 | |
---|
163 | # Program Unit |
---|
164 | my ($type, $symbol, @extras) = _process_prog_unit($line); |
---|
165 | if ($type) { |
---|
166 | if (!@{$state->{stack}}) { |
---|
167 | if ($type eq 'program') { |
---|
168 | $info_hash_ref->{main} = 1; |
---|
169 | } |
---|
170 | $info_hash_ref->{symbols} ||= []; |
---|
171 | push(@{$info_hash_ref->{symbols}}, [$type, $symbol, @extras]); |
---|
172 | } |
---|
173 | push(@{$state->{stack}}, [$type, $symbol]); |
---|
174 | return 1; |
---|
175 | } |
---|
176 | return; |
---|
177 | } |
---|
178 | |
---|
179 | sub _source_analyse_more_init { |
---|
180 | my ($info_ref, $state) = @_; |
---|
181 | %{$info_ref} = (main => 0, symbols => []); |
---|
182 | %{$state} = (in_contains => undef, in_interface => undef, stack => []); |
---|
183 | } |
---|
184 | |
---|
185 | # Reads information: extract an include dependency. |
---|
186 | sub _source_analyse_dep_include { |
---|
187 | my ($line) = @_; |
---|
188 | my ($omp_sentinel, $extracted); |
---|
189 | ($omp_sentinel, $line) = $line =~ $RE{OMP_SENT}; |
---|
190 | ($extracted) = extract_delimited($line, q{'"}, $RE{INCLUDE}); |
---|
191 | if (!$extracted) { |
---|
192 | return; |
---|
193 | } |
---|
194 | $extracted = substr($extracted, 1, length($extracted) - 2); |
---|
195 | if ($omp_sentinel) { |
---|
196 | $extracted = $OMP_PREFIX . $extracted; |
---|
197 | } |
---|
198 | $extracted; |
---|
199 | } |
---|
200 | |
---|
201 | # Reads information: extract a module dependency. |
---|
202 | sub _source_analyse_dep_module { |
---|
203 | my ($line) = @_; |
---|
204 | my ($omp_sentinel, $extracted, $can_analyse_more); |
---|
205 | ($omp_sentinel, $line) = $line =~ $RE{OMP_SENT}; |
---|
206 | ($extracted) = lc($line) =~ $RE{DEP_USE}; |
---|
207 | if (!$extracted) { |
---|
208 | ($extracted) = lc($line) =~ $RE{DEP_SUBM}; |
---|
209 | $can_analyse_more = 1; |
---|
210 | } |
---|
211 | if (!$extracted || grep {$_ eq $extracted} @INTRINSIC_MODULES) { |
---|
212 | return; |
---|
213 | } |
---|
214 | if ($omp_sentinel) { |
---|
215 | $extracted = $OMP_PREFIX . $extracted; |
---|
216 | } |
---|
217 | ($extracted, $can_analyse_more); |
---|
218 | } |
---|
219 | |
---|
220 | # Parse a statement for program unit header. Returns a list containing the type, |
---|
221 | # the symbol and the signature tokens of the program unit. |
---|
222 | sub _process_prog_unit { |
---|
223 | my ($string) = @_; |
---|
224 | my ($type, $symbol, $symbol_parent) = (q{}, q{}, q{}); |
---|
225 | ($type, $symbol) = lc($string) =~ $RE{UNIT_BASE}; |
---|
226 | if ($type) { |
---|
227 | $type =~ s{\s*}{}gmsx; |
---|
228 | return ($type, $symbol); |
---|
229 | } |
---|
230 | ($type, $symbol_parent, $symbol) = lc($string) =~ $RE{UNIT_SUBM}; |
---|
231 | if ($type) { |
---|
232 | return ($type, $symbol, $symbol_parent); |
---|
233 | } |
---|
234 | $string =~ s/$RE{UNIT_ATTR}/$1/; |
---|
235 | my ($match) = $string =~ $RE{UNIT_SPEC}; |
---|
236 | if ($match) { |
---|
237 | $string = $match; |
---|
238 | if ($string =~ qr{\A \s* \(}msx) { |
---|
239 | extract_bracketed($string); |
---|
240 | } |
---|
241 | elsif ($string =~ qr{\A \s* \*}msx) { |
---|
242 | $string =~ s{\A \s* \* \d+ \s*}{}msx; |
---|
243 | } |
---|
244 | } |
---|
245 | ($type, $symbol) = lc($string) =~ $RE{UNIT_CALL}; |
---|
246 | if (!$type) { |
---|
247 | return; |
---|
248 | } |
---|
249 | return (lc($type), lc($symbol)); |
---|
250 | } |
---|
251 | |
---|
252 | # Returns a list of targets for a given build source. |
---|
253 | sub _source_to_targets { |
---|
254 | my ($attrib_ref, $source, $ext_hash_ref, $option_hash_ref) = @_; |
---|
255 | my $key = basename($source->get_path()); |
---|
256 | my $TARGET_OF = sub { |
---|
257 | my ($symbol, $type) = @_; |
---|
258 | if (exists($option_hash_ref->{$type})) { |
---|
259 | my $is_upper = index($option_hash_ref->{$type}, 'case=upper') >= 0; |
---|
260 | $symbol = $is_upper ? uc($symbol) : lc($symbol); |
---|
261 | } |
---|
262 | $symbol . $ext_hash_ref->{$type}; |
---|
263 | }; |
---|
264 | my @deps = map { |
---|
265 | my ($k, $type) = @{$_}; |
---|
266 | my $ext = $attrib_ref->{util}->file_ext($k); |
---|
267 | $type eq 'f.module' ? [$TARGET_OF->($k, 'f90-mod'), 'include', 1] |
---|
268 | : $type eq 'o' && !$ext ? [$TARGET_OF->($k, 'o'), $type] |
---|
269 | : [$k, $type] |
---|
270 | } @{$source->get_deps()}; |
---|
271 | # All source files can be used as include files |
---|
272 | my @targets = ( |
---|
273 | $TARGET->new( |
---|
274 | { category => $TARGET->CT_INCLUDE, |
---|
275 | deps => [@deps], |
---|
276 | dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE}, |
---|
277 | key => $key, |
---|
278 | status_of => {'include' => $TARGET->ST_UNKNOWN}, |
---|
279 | task => 'install', |
---|
280 | } |
---|
281 | ), |
---|
282 | ); |
---|
283 | my ($ext, $root) = $attrib_ref->{util}->file_ext($key); |
---|
284 | # @{$symbols_ref} contains a list of [$type, $symbol, $symbol_parent] |
---|
285 | # where $type is the program unit type |
---|
286 | # $symbol is the program unit symbol |
---|
287 | # $symbol_parent is the parent program unit symbol, e.g. name of |
---|
288 | # parent module of a submodule |
---|
289 | my $symbols_ref = $source->get_info_of()->{symbols}; |
---|
290 | # FIXME: hard code the handling of "*.inc" files as include files |
---|
291 | if (!defined($symbols_ref) || !@{$symbols_ref} || $ext eq 'inc') { |
---|
292 | return @targets; |
---|
293 | } |
---|
294 | my $key_of_o = $TARGET_OF->($symbols_ref->[0][1], 'o'); |
---|
295 | my @keys_of_mod; |
---|
296 | for (grep {$_->[0] eq 'module'} @{$symbols_ref}) { |
---|
297 | my ($type, $symbol) = @{$_}; |
---|
298 | my $key_of_mod = $TARGET_OF->($symbol, 'f90-mod'); |
---|
299 | my @include_deps = grep {$_->[1] eq 'include'} @deps; |
---|
300 | push( |
---|
301 | @targets, |
---|
302 | $TARGET->new( |
---|
303 | { category => $TARGET->CT_INCLUDE, |
---|
304 | deps => [[$key_of_o, 'o']], |
---|
305 | dep_policy_of => { |
---|
306 | 'include' => $TARGET->POLICY_CAPTURE, |
---|
307 | 'o' => $TARGET->POLICY_FILTER_IMMEDIATE, |
---|
308 | }, |
---|
309 | key => $key_of_mod, |
---|
310 | task => 'compile+', |
---|
311 | } |
---|
312 | ) |
---|
313 | ); |
---|
314 | push(@keys_of_mod, $key_of_mod); |
---|
315 | } |
---|
316 | my @symbol_parents = map { |
---|
317 | scalar(@{$_}) > 2 ? $TARGET_OF->($_->[2], 'o') : (); |
---|
318 | } @{$symbols_ref}; |
---|
319 | push( |
---|
320 | @targets, |
---|
321 | $TARGET->new( |
---|
322 | { category => $TARGET->CT_O, |
---|
323 | deps => [@deps], |
---|
324 | dep_policy_of => {'include' => $TARGET->POLICY_CAPTURE}, |
---|
325 | info_of => {paths => [], parents => \@symbol_parents}, |
---|
326 | key => $key_of_o, |
---|
327 | task => 'compile', |
---|
328 | triggers => \@keys_of_mod, |
---|
329 | } |
---|
330 | ), |
---|
331 | ); |
---|
332 | if (grep {$_->[0] eq 'subroutine' || $_->[0] eq 'function'} @{$symbols_ref}) { |
---|
333 | my $target_key = $root . $ext_hash_ref->{'f90-interface'}; |
---|
334 | push( |
---|
335 | @targets, |
---|
336 | $TARGET->new( |
---|
337 | { category => $TARGET->CT_INCLUDE, |
---|
338 | deps => [[$key_of_o, 'o'], grep {exists($_->[2])} @deps], |
---|
339 | dep_policy_of => { |
---|
340 | 'include' => $TARGET->POLICY_FILTER_IMMEDIATE, |
---|
341 | }, |
---|
342 | key => $target_key, |
---|
343 | task => 'ext-iface', |
---|
344 | } |
---|
345 | ) |
---|
346 | ); |
---|
347 | } |
---|
348 | if ($source->get_info_of()->{main}) { |
---|
349 | my @link_deps = grep {$_->[1] eq 'o' || $_->[1] eq 'o.special'} @deps; |
---|
350 | push( |
---|
351 | @targets, |
---|
352 | $TARGET->new( |
---|
353 | { category => $TARGET->CT_BIN, |
---|
354 | deps => [[$key_of_o, 'o'], @link_deps], |
---|
355 | dep_policy_of => { |
---|
356 | 'o' => $TARGET->POLICY_CAPTURE, |
---|
357 | 'o.special' => $TARGET->POLICY_CAPTURE, |
---|
358 | }, |
---|
359 | info_of => { |
---|
360 | paths => [], deps => {o => [], 'o.special' => []}, |
---|
361 | }, |
---|
362 | key => $root . $ext_hash_ref->{bin}, |
---|
363 | task => 'link', |
---|
364 | } |
---|
365 | ) |
---|
366 | ); |
---|
367 | } |
---|
368 | return @targets; |
---|
369 | } |
---|
370 | |
---|
371 | # If target's fc.flag-omp property is empty, remove !$OMP dependencies. |
---|
372 | # Otherwise, remove !$OMP sentinels from the dependencies. |
---|
373 | sub _target_deps_filter { |
---|
374 | my ($attrib_ref, $target) = @_; |
---|
375 | if ($target->get_prop_of()->{'fc.flag-omp'}) { |
---|
376 | for my $dep_ref (@{$target->get_deps()}) { |
---|
377 | if (index($dep_ref->[0], $OMP_PREFIX) == 0) { |
---|
378 | substr($dep_ref->[0], 0, length($OMP_PREFIX), q{}); |
---|
379 | } |
---|
380 | } |
---|
381 | } |
---|
382 | else { |
---|
383 | $target->set_deps( |
---|
384 | [grep {index($_->[0], $OMP_PREFIX) == -1} @{$target->get_deps()}], |
---|
385 | ); |
---|
386 | } |
---|
387 | } |
---|
388 | |
---|
389 | # ------------------------------------------------------------------------------ |
---|
390 | 1; |
---|
391 | __END__ |
---|
392 | |
---|
393 | =head1 NAME |
---|
394 | |
---|
395 | FCM::System::Make::Build::FileType::Fortran |
---|
396 | |
---|
397 | =head1 SYNOPSIS |
---|
398 | |
---|
399 | use FCM::System::Make::Build::FileType::Fortran; |
---|
400 | my $file_type_util = FCM::System::Make::Build::FileType::Fortran->new(); |
---|
401 | |
---|
402 | $file_type_util->source_analyse($source); |
---|
403 | |
---|
404 | my @targets = $file_type_util->source_to_targets($m_ctx, $ctx, $source); |
---|
405 | |
---|
406 | =head1 DESCRIPTION |
---|
407 | |
---|
408 | A wrapper of |
---|
409 | L<FCM::System::Make::Build::FileType|FCM::System::Make::Build::FileType> with |
---|
410 | configurations to work with Fortran source files. |
---|
411 | |
---|
412 | =head1 TODO |
---|
413 | |
---|
414 | Combine the code with FCM::System::Make::Build::Task::ExtractInterface. |
---|
415 | |
---|
416 | =head1 COPYRIGHT |
---|
417 | |
---|
418 | Copyright (C) 2006-2021 British Crown (Met Office) & Contributors. |
---|
419 | |
---|
420 | =cut |
---|