fcm_internal 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #!/usr/bin/env perl
  2. #-------------------------------------------------------------------------------
  3. # (C) Crown copyright Met Office. All rights reserved.
  4. # For further details please refer to the file COPYRIGHT.txt
  5. # which you should have received as part of this distribution.
  6. #-------------------------------------------------------------------------------
  7. use strict;
  8. use warnings;
  9. use Fcm::Timer qw{timestamp_command};
  10. # Function declarations
  11. sub catfile;
  12. sub basename;
  13. sub dirname;
  14. # ------------------------------------------------------------------------------
  15. # Module level variables
  16. my %unusual_tool_name = ();
  17. # ------------------------------------------------------------------------------
  18. MAIN: {
  19. # Name of program
  20. my $this = basename $0;
  21. # Arguments
  22. my $subcommand = shift @ARGV;
  23. my ($function, $type) = split /:/, $subcommand;
  24. my ($srcpackage, $src, $target, $requirepp, @objects, @blockdata);
  25. if ($function eq 'archive') {
  26. ($target, @objects) = @ARGV;
  27. } elsif ($function eq 'load') {
  28. ($srcpackage, $src, $target, @blockdata) = @ARGV;
  29. } else {
  30. ($srcpackage, $src, $target, $requirepp) = @ARGV;
  31. }
  32. # Set up hash reference for all the required information
  33. my %info = (
  34. SRCPACKAGE => $srcpackage,
  35. SRC => $src,
  36. TYPE => $type,
  37. TARGET => $target,
  38. REQUIREPP => $requirepp,
  39. OBJECTS => \@objects,
  40. BLOCKDATA => \@blockdata,
  41. );
  42. # Get list of unusual tools
  43. my $i = 0;
  44. while (my $label = &get_env ('FCM_UNUSUAL_TOOL_LABEL' . $i)) {
  45. my $value = &get_env ('FCM_UNUSUAL_TOOL_VALUE' . $i);
  46. $unusual_tool_name{$label} = $value;
  47. $i++;
  48. }
  49. # Invoke the action
  50. my $rc = 0;
  51. if ($function eq 'compile') {
  52. $rc = &compile (\%info);
  53. } elsif ($function eq 'load') {
  54. $rc = &load (\%info);
  55. } elsif ($function eq 'archive') {
  56. $rc = &archive (\%info);
  57. } else {
  58. print STDERR $this, ': incorrect usage, abort';
  59. $rc = 1;
  60. }
  61. # Throw error if action failed
  62. if ($rc) {
  63. print STDERR $this, ' ', $function, ' failed (', $rc, ')', "\n";
  64. exit 1;
  65. } else {
  66. exit;
  67. }
  68. }
  69. # ------------------------------------------------------------------------------
  70. # SYNOPSIS
  71. # $rc = &compile (\%info);
  72. #
  73. # DESCRIPTION
  74. # This method invokes the correct compiler with the correct options to
  75. # compile the source file into the required target. The argument $info is a
  76. # hash reference set up in MAIN. The following environment variables are
  77. # used, where * is the source file type (F for Fortran, and C for C/C++):
  78. #
  79. # *C - compiler command
  80. # *C_OUTPUT - *C option to specify the name of the output file
  81. # *C_DEFINE - *C option to declare a pre-processor def
  82. # *C_INCLUDE - *C option to declare an include directory
  83. # *C_MODSEARCH- *C option to declare a module search directory
  84. # *C_COMPILE - *C option to ask the compiler to perform compile only
  85. # *CFLAGS - *C user options
  86. # *PPKEYS - list of pre-processor defs (may have sub-package suffix)
  87. # FCM_VERBOSE - verbose level
  88. # FCM_OBJDIR - destination directory of object file
  89. # FCM_TMPDIR - temporary destination directory of object file
  90. # ------------------------------------------------------------------------------
  91. sub compile {
  92. my $info = shift;
  93. # Verbose mode
  94. my $verbose = &get_env ('FCM_VERBOSE');
  95. $verbose = 1 unless defined ($verbose);
  96. my @command = ();
  97. # Guess file type for backward compatibility
  98. my $type = $info->{TYPE} ? $info->{TYPE} : &guess_file_type ($info->{SRC});
  99. # Compiler
  100. push @command, &get_env ($type . 'C', 1);
  101. # Compile output target (typical -o option)
  102. push @command, &get_env ($type . 'C_OUTPUT', 1), $info->{TARGET};
  103. # Pre-processor definition macros
  104. if ($info->{REQUIREPP}) {
  105. my @ppkeys = split /\s+/, &select_flags ($info, $type . 'PPKEYS');
  106. my $defopt = &get_env ($type . 'C_DEFINE', 1);
  107. push @command, (map {$defopt . $_} @ppkeys);
  108. }
  109. # Include search path
  110. my $incopt = &get_env ($type . 'C_INCLUDE', 1);
  111. my @incpath = split /:/, &get_env ('FCM_INCPATH');
  112. push @command, (map {$incopt . $_} @incpath);
  113. # Compiled module search path
  114. my $modopt = &get_env ($type . 'C_MODSEARCH');
  115. if ($modopt) {
  116. push @command, (map {$modopt . $_} @incpath);
  117. }
  118. # Other compiler flags
  119. my $flags = &select_flags ($info, $type . 'FLAGS');
  120. push @command, $flags if $flags;
  121. my $compile_only = &get_env ($type . 'C_COMPILE');
  122. if ($flags !~ /(?:^|\s)$compile_only\b/) {
  123. push @command, &get_env ($type . 'C_COMPILE');
  124. }
  125. # Name of source file
  126. push @command, $info->{SRC};
  127. # Execute command
  128. my $objdir = &get_env ('FCM_OBJDIR', 1);
  129. my $tmpdir = &get_env ('FCM_TMPDIR', 1);
  130. chdir $tmpdir;
  131. my $command = join ' ', @command;
  132. if ($verbose > 1) {
  133. print 'cd ', $tmpdir, "\n";
  134. print &timestamp_command ($command, 'Start');
  135. } elsif ($verbose) {
  136. print $command, "\n";
  137. }
  138. my $rc = system $command;
  139. print &timestamp_command ($command, 'End ') if $verbose > 1;
  140. # Move temporary output to correct location on success
  141. # Otherwise, remove temporary output
  142. if ($rc) { # error
  143. unlink $info->{TARGET};
  144. } else { # success
  145. print 'mv ', $info->{TARGET}, ' ', $objdir, "\n" if $verbose > 1;
  146. rename $info->{TARGET}, &catfile ($objdir, $info->{TARGET});
  147. }
  148. # Move any Fortran module definition files to the INC directory
  149. my @modfiles = <*.mod *.MOD>;
  150. for my $file (@modfiles) {
  151. rename $file, &catfile ($incpath[0], $file);
  152. }
  153. return $rc;
  154. }
  155. # ------------------------------------------------------------------------------
  156. # SYNOPSIS
  157. # $rc = &load (\%info);
  158. #
  159. # DESCRIPTION
  160. # This method invokes the correct loader with the correct options to link
  161. # the main program object into an executable. The argument $info is a hash
  162. # reference set up in MAIN. The following environment variables are used:
  163. #
  164. # LD - * linker command
  165. # LD_OUTPUT - LD option to specify the name of the output file
  166. # LD_LIBSEARCH - LD option to declare a directory in the library search path
  167. # LD_LIBLINK - LD option to declare an object library
  168. # LDFLAGS - LD user options
  169. # FCM_VERBOSE - verbose level
  170. # FCM_LIBDIR - destination directory of object libraries
  171. # FCM_OBJDIR - destination directory of object files
  172. # FCM_BINDIR - destination directory of executable file
  173. # FCM_TMPDIR - temporary destination directory of executable file
  174. #
  175. # * If LD is not set, it will attempt to guess the file type and use the
  176. # compiler as the linker.
  177. # ------------------------------------------------------------------------------
  178. sub load {
  179. my $info = shift;
  180. my $rc = 0;
  181. # Verbose mode
  182. my $verbose = &get_env ('FCM_VERBOSE');
  183. $verbose = 1 unless defined ($verbose);
  184. # Create temporary object library
  185. (my $name = $info->{TARGET}) =~ s/\.\S+$//;
  186. my $libname = '__fcm__' . $name;
  187. my $lib = 'lib' . $libname . '.a';
  188. my $libfile = catfile (&get_env ('FCM_LIBDIR', 1), $lib);
  189. $rc = &archive ({TARGET => $lib});
  190. unless ($rc) {
  191. my @command = ();
  192. # Linker
  193. my $ld = &select_flags ($info, 'LD');
  194. if (not $ld) {
  195. # Guess file type for backward compatibility
  196. my $type = $info->{TYPE} ? $info->{TYPE} : &guess_file_type ($info->{SRC});
  197. $ld = &get_env ($type . 'C', 1);
  198. }
  199. push @command, $ld;
  200. # Linker output target (typical -o option)
  201. push @command, &get_env ('LD_OUTPUT', 1), $info->{TARGET};
  202. # Name of main object file
  203. my $mainobj = (basename ($info->{SRC}) eq $info->{SRC})
  204. ? catfile (&get_env ('FCM_OBJDIR'), $info->{SRC})
  205. : $info->{SRC};
  206. push @command, $mainobj;
  207. # Link with Fortran BLOCKDATA objects if necessary
  208. if (@{ $info->{BLOCKDATA} }) {
  209. my @blockdata = @{ $info->{BLOCKDATA} };
  210. my @objpath = split /:/, &get_env ('FCM_OBJPATH');
  211. # Search each BLOCKDATA object file from the object search path
  212. for my $file (@blockdata) {
  213. for my $dir (@objpath) {
  214. my $full = catfile ($dir, $file);
  215. if (-r $full) {
  216. $file = $full;
  217. last;
  218. }
  219. }
  220. push @command, $file;
  221. }
  222. }
  223. # Library search path
  224. my $libopt = &get_env ('LD_LIBSEARCH', 1);
  225. my @libpath = split /:/, &get_env ('FCM_LIBPATH');
  226. push @command, (map {$libopt . $_} @libpath);
  227. # Link with temporary object library if it exists
  228. push @command, &get_env ('LD_LIBLINK', 1) . $libname if -f $libfile;
  229. # Other linker flags
  230. my $flags = &select_flags ($info, 'LDFLAGS');
  231. push @command, $flags;
  232. # Execute command
  233. my $tmpdir = &get_env ('FCM_TMPDIR', 1);
  234. my $bindir = &get_env ('FCM_BINDIR', 1);
  235. chdir $tmpdir;
  236. my $command = join ' ', @command;
  237. if ($verbose > 1) {
  238. print 'cd ', $tmpdir, "\n";
  239. print &timestamp_command ($command, 'Start');
  240. } elsif ($verbose) {
  241. print $command, "\n";
  242. }
  243. $rc = system $command;
  244. print &timestamp_command ($command, 'End ') if $verbose > 1;
  245. # Move temporary output to correct location on success
  246. # Otherwise, remove temporary output
  247. if ($rc) { # error
  248. unlink $info->{TARGET};
  249. } else { # success
  250. print 'mv ', $info->{TARGET}, ' ', $bindir, "\n" if $verbose > 1;
  251. rename $info->{TARGET}, &catfile ($bindir, $info->{TARGET});
  252. }
  253. }
  254. # Remove the temporary object library
  255. unlink $libfile if -f $libfile;
  256. return $rc;
  257. }
  258. # ------------------------------------------------------------------------------
  259. # SYNOPSIS
  260. # $rc = &archive (\%info);
  261. #
  262. # DESCRIPTION
  263. # This method invokes the library archiver to create an object library. The
  264. # argument $info is a hash reference set up in MAIN. The following
  265. # environment variables are used:
  266. #
  267. # AR - archiver command
  268. # ARFLAGS - AR options to update/create an object library
  269. # FCM_VERBOSE - verbose level
  270. # FCM_LIBDIR - destination directory of object libraries
  271. # FCM_OBJPATH - search path of object files
  272. # FCM_OBJDIR - destination directory of object files
  273. # FCM_TMPDIR - temporary destination directory of executable file
  274. # ------------------------------------------------------------------------------
  275. sub archive {
  276. my $info = shift;
  277. my $rc = 0;
  278. # Verbose mode
  279. my $verbose = &get_env ('FCM_VERBOSE');
  280. $verbose = 1 unless defined ($verbose);
  281. # Set up the archive command
  282. my $lib = &basename ($info->{TARGET});
  283. my $tmplib = &catfile (&get_env ('FCM_TMPDIR', 1), $lib);
  284. my @ar_cmd = ();
  285. push @ar_cmd, (&get_env ('AR', 1), &get_env ('ARFLAGS', 1));
  286. push @ar_cmd, $tmplib;
  287. # Get object directories and their files
  288. my %objdir;
  289. if (exists $info->{OBJECTS}) {
  290. # List of objects set in the argument, sort into directory/file list
  291. for my $name (@{ $info->{OBJECTS} }) {
  292. my $dir = (&dirname ($name) eq '.')
  293. ? &get_env ('FCM_OBJDIR', 1) : &dirname ($name);
  294. $objdir{$dir}{&basename ($name)} = 1;
  295. }
  296. } else {
  297. # Objects not listed in argument, search object path for all files
  298. my @objpath = split /:/, &get_env ('FCM_OBJPATH', 1);
  299. my %objbase = ();
  300. # Get registered objects into a hash (keys = objects, values = 1)
  301. my %objects = map {($_, 1)} split (/\s+/, &get_env ('OBJECTS'));
  302. # Seach object path for all files
  303. for my $dir (@objpath) {
  304. next unless -d $dir;
  305. chdir $dir;
  306. # Use all files from each directory in the object search path
  307. for ((glob ('*'))) {
  308. next unless exists $objects{$_}; # consider registered objects only
  309. $objdir{$dir}{$_} = 1 unless exists $objbase{$_};
  310. $objbase{$_} = 1;
  311. }
  312. }
  313. }
  314. for my $dir (sort keys %objdir) {
  315. next unless -d $dir;
  316. # Go to each object directory and executes the library archive command
  317. chdir $dir;
  318. my $command = join ' ', (@ar_cmd, sort keys %{ $objdir{$dir} });
  319. if ($verbose > 1) {
  320. print 'cd ', $dir, "\n";
  321. print &timestamp_command ($command, 'Start');
  322. } else {
  323. print $command, "\n" if exists $info->{OBJECTS};
  324. }
  325. $rc = system $command;
  326. print &timestamp_command ($command, 'End ')
  327. if $verbose > 1;
  328. last if $rc;
  329. }
  330. # Move temporary output to correct location on success
  331. # Otherwise, remove temporary output
  332. if ($rc) { # error
  333. unlink $tmplib;
  334. } else { # success
  335. my $libdir = &get_env ('FCM_LIBDIR', 1);
  336. print 'mv ', $tmplib, ' ', $libdir, "\n" if $verbose > 1;
  337. rename $tmplib, &catfile ($libdir, $lib);
  338. }
  339. return $rc;
  340. }
  341. # ------------------------------------------------------------------------------
  342. # SYNOPSIS
  343. # $type = &guess_file_type ($filename);
  344. #
  345. # DESCRIPTION
  346. # This function attempts to guess the file type by looking at the extension
  347. # of the $filename. Only C and Fortran at the moment.
  348. # ------------------------------------------------------------------------------
  349. sub guess_file_type {
  350. return (($_[0] =~ /\.c(\w+)?$/i) ? 'C' : 'F');
  351. }
  352. # ------------------------------------------------------------------------------
  353. # SYNOPSIS
  354. # $flags = &select_flags (\%info, $set);
  355. #
  356. # DESCRIPTION
  357. # This function selects the correct compiler/linker flags for the current
  358. # sub-package from the environment variable prefix $set. The argument $info
  359. # is a hash reference set up in MAIN.
  360. # ------------------------------------------------------------------------------
  361. sub select_flags {
  362. my ($info, $set) = @_;
  363. my $srcbase = &basename ($info->{SRC});
  364. my @names = ($set);
  365. push @names, split (/__/, $info->{SRCPACKAGE} . '__' . $srcbase);
  366. my $string = '';
  367. for my $i (reverse (0 .. $#names)) {
  368. my $var = &get_env (join ('__', (@names[0 .. $i])));
  369. $var = &get_env (join ('__', (@names[0 .. $i])))
  370. if (not defined ($var)) and $i and $names[-1] =~ s/\.[^\.]+$//;
  371. next unless defined $var;
  372. $string = $var;
  373. last;
  374. }
  375. return $string;
  376. }
  377. # ------------------------------------------------------------------------------
  378. # SYNOPSIS
  379. # $variable = &get_env ($name);
  380. # $variable = &get_env ($name, $compulsory);
  381. #
  382. # DESCRIPTION
  383. # This internal method gets a variable from $ENV{$name}. If $compulsory is
  384. # set to true, it throws an error if the variable is a not set or is an empty
  385. # string. Otherwise, it returns C<undef> if the variable is not set.
  386. # ------------------------------------------------------------------------------
  387. sub get_env {
  388. (my $name, my $compulsory) = @_;
  389. my $string;
  390. if ($name =~ /^\w+$/) {
  391. # $name contains only word characters, variable is exported normally
  392. die 'The environment variable "', $name, '" must be set, abort'
  393. if $compulsory and not exists $ENV{$name};
  394. $string = exists $ENV{$name} ? $ENV{$name} : undef;
  395. } else {
  396. # $name contains unusual characters
  397. die 'The environment variable "', $name, '" must be set, abort'
  398. if $compulsory and not exists $unusual_tool_name{$name};
  399. $string = exists $unusual_tool_name{$name}
  400. ? $unusual_tool_name{$name} : undef;
  401. }
  402. return $string;
  403. }
  404. # ------------------------------------------------------------------------------
  405. # SYNOPSIS
  406. # $path = &catfile (@paths);
  407. #
  408. # DESCRIPTION
  409. # This is a local implementation of what is in the File::Spec module.
  410. # ------------------------------------------------------------------------------
  411. sub catfile {
  412. my @names = split (m!/!, join ('/', @_));
  413. my $path = shift @names;
  414. for my $name (@names) {
  415. $path .= '/' . $name if $name;
  416. }
  417. return $path;
  418. }
  419. # ------------------------------------------------------------------------------
  420. # SYNOPSIS
  421. # $basename = &basename ($path);
  422. #
  423. # DESCRIPTION
  424. # This is a local implementation of what is in the File::Basename module.
  425. # ------------------------------------------------------------------------------
  426. sub basename {
  427. my $name = $_[0];
  428. $name =~ s{/*$}{}; # remove trailing slashes
  429. if ($name =~ m#.*/([^/]+)$#) {
  430. return $1;
  431. } else {
  432. return $name;
  433. }
  434. }
  435. # ------------------------------------------------------------------------------
  436. # SYNOPSIS
  437. # $dirname = &dirname ($path);
  438. #
  439. # DESCRIPTION
  440. # This is a local implementation of what is in the File::Basename module.
  441. # ------------------------------------------------------------------------------
  442. sub dirname {
  443. my $name = $_[0];
  444. if ($name =~ m#^/+$#) {
  445. return '/'; # dirname of root is root
  446. } else {
  447. $name =~ s{/*$}{}; # remove trailing slashes
  448. if ($name =~ m#^(.*)/[^/]+$#) {
  449. my $dir = $1;
  450. $dir =~ s{/*$}{}; # remove trailing slashes
  451. return $dir;
  452. } else {
  453. return '.';
  454. }
  455. }
  456. }
  457. # ------------------------------------------------------------------------------
  458. __END__
  459. =head1 NAME
  460. fcm_internal
  461. =head1 SYNOPSIS
  462. fcm_internal SUBCOMMAND ARGS
  463. =head1 DESCRIPTION
  464. The fcm_internal command is a frontend for some of the internal commands of
  465. the FCM build system. The subcommand can be "compile", "load" or "archive"
  466. for invoking the compiler, loader and library archiver respectively. If
  467. "compile" or "load" is specified, it can be suffixed with ":TYPE" to
  468. specify the nature of the source file. If TYPE is not specified, it is set
  469. to C if the file extension begins with ".c". For all other file types, it
  470. is set to F (for Fortran source). For compile and load, the other arguments
  471. are 1) the name of the container package of the source file, 2) the path to
  472. the source file and 3) the target name after compiling or loading the
  473. source file. For compile, the 4th argument is a flag to indicate whether
  474. pre-processing is required for compiling the source file. For load, the
  475. 4th and the rest of the arguments is a list of object files that cannot be
  476. archived into the temporary load library and must be linked into the target
  477. through the linker command. (E.g. Fortran BLOCKDATA program units must be
  478. linked this way.) If archive is specified, the first argument should be the
  479. name of the library archive target and the rest should be the object files
  480. to be included in the archive. This command is invoked via the build system
  481. and should never be called directly by the user.
  482. =head1 COPYRIGHT
  483. (C) Crown copyright Met Office. All rights reserved.
  484. =cut