Build.pm 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606
  1. # ------------------------------------------------------------------------------
  2. # NAME
  3. # Fcm::Build
  4. #
  5. # DESCRIPTION
  6. # This is the top level class for the FCM build system.
  7. #
  8. # COPYRIGHT
  9. # (C) Crown copyright Met Office. All rights reserved.
  10. # For further details please refer to the file COPYRIGHT.txt
  11. # which you should have received as part of this distribution.
  12. # ------------------------------------------------------------------------------
  13. use strict;
  14. use warnings;
  15. package Fcm::Build;
  16. use base qw(Fcm::ConfigSystem);
  17. use Carp qw{croak} ;
  18. use Cwd qw{cwd} ;
  19. use Fcm::BuildSrc ;
  20. use Fcm::BuildTask ;
  21. use Fcm::Config ;
  22. use Fcm::Dest ;
  23. use Fcm::CfgLine ;
  24. use Fcm::Timer qw{timestamp_command} ;
  25. use Fcm::Util qw{expand_tilde run_command touch_file w_report};
  26. use File::Basename qw{dirname} ;
  27. use File::Spec ;
  28. use List::Util qw{first} ;
  29. use Text::ParseWords qw{shellwords} ;
  30. # List of scalar property methods for this class
  31. my @scalar_properties = (
  32. 'name', # name of this build
  33. 'target', # targets of this build
  34. );
  35. # List of hash property methods for this class
  36. my @hash_properties = (
  37. 'srcpkg', # source packages of this build
  38. 'dummysrcpkg', # dummy for handling package inheritance with file extension
  39. );
  40. # List of compare_setting_X methods
  41. my @compare_setting_methods = (
  42. 'compare_setting_bld_blockdata', # program executable blockdata dependency
  43. 'compare_setting_bld_dep', # custom dependency setting
  44. 'compare_setting_bld_dep_excl', # exclude dependency setting
  45. 'compare_setting_bld_dep_n', # no dependency check
  46. 'compare_setting_bld_dep_pp', # custom PP dependency setting
  47. 'compare_setting_bld_dep_exe', # program executable extra dependency
  48. 'compare_setting_bld_exe_name', # program executable rename
  49. 'compare_setting_bld_pp', # PP flags
  50. 'compare_setting_infile_ext', # input file extension
  51. 'compare_setting_outfile_ext', # output file extension
  52. 'compare_setting_tool', # build tool settings
  53. );
  54. my $DELIMITER_LIST = $Fcm::Config::DELIMITER_LIST;
  55. # ------------------------------------------------------------------------------
  56. # SYNOPSIS
  57. # $obj = Fcm::Build->new;
  58. #
  59. # DESCRIPTION
  60. # This method constructs a new instance of the Fcm::Build class.
  61. # ------------------------------------------------------------------------------
  62. sub new {
  63. my $this = shift;
  64. my %args = @_;
  65. my $class = ref $this || $this;
  66. my $self = Fcm::ConfigSystem->new (%args);
  67. $self->{$_} = undef for (@scalar_properties);
  68. $self->{$_} = {} for (@hash_properties);
  69. bless $self, $class;
  70. # List of sub-methods for parse_cfg
  71. push @{ $self->cfg_methods }, (qw/target source tool dep misc/);
  72. # Optional prefix in configuration declaration
  73. $self->cfg_prefix ($self->setting (qw/CFG_LABEL BDECLARE/));
  74. # System type
  75. $self->type ('bld');
  76. return $self;
  77. }
  78. # ------------------------------------------------------------------------------
  79. # SYNOPSIS
  80. # $value = $obj->X;
  81. # $obj->X ($value);
  82. #
  83. # DESCRIPTION
  84. # Details of these properties are explained in @scalar_properties.
  85. # ------------------------------------------------------------------------------
  86. for my $name (@scalar_properties) {
  87. no strict 'refs';
  88. *$name = sub {
  89. my $self = shift;
  90. # Argument specified, set property to specified argument
  91. if (@_) {
  92. $self->{$name} = $_[0];
  93. }
  94. # Default value for property
  95. if (not defined $self->{$name}) {
  96. if ($name eq 'target') {
  97. # Reference to an array
  98. $self->{$name} = [];
  99. } elsif ($name eq 'name') {
  100. # Empty string
  101. $self->{$name} = '';
  102. }
  103. }
  104. return $self->{$name};
  105. }
  106. }
  107. # ------------------------------------------------------------------------------
  108. # SYNOPSIS
  109. # %hash = %{ $obj->X () };
  110. # $obj->X (\%hash);
  111. #
  112. # $value = $obj->X ($index);
  113. # $obj->X ($index, $value);
  114. #
  115. # DESCRIPTION
  116. # Details of these properties are explained in @hash_properties.
  117. #
  118. # If no argument is set, this method returns a hash containing a list of
  119. # objects. If an argument is set and it is a reference to a hash, the objects
  120. # are replaced by the the specified hash.
  121. #
  122. # If a scalar argument is specified, this method returns a reference to an
  123. # object, if the indexed object exists or undef if the indexed object does
  124. # not exist. If a second argument is set, the $index element of the hash will
  125. # be set to the value of the argument.
  126. # ------------------------------------------------------------------------------
  127. for my $name (@hash_properties) {
  128. no strict 'refs';
  129. *$name = sub {
  130. my ($self, $arg1, $arg2) = @_;
  131. # Ensure property is defined as a reference to a hash
  132. $self->{$name} = {} if not defined ($self->{$name});
  133. # Argument 1 can be a reference to a hash or a scalar index
  134. my ($index, %hash);
  135. if (defined $arg1) {
  136. if (ref ($arg1) eq 'HASH') {
  137. %hash = %$arg1;
  138. } else {
  139. $index = $arg1;
  140. }
  141. }
  142. if (defined $index) {
  143. # A scalar index is defined, set and/or return the value of an element
  144. $self->{$name}{$index} = $arg2 if defined $arg2;
  145. return (
  146. exists $self->{$name}{$index} ? $self->{$name}{$index} : undef
  147. );
  148. } else {
  149. # A scalar index is not defined, set and/or return the hash
  150. $self->{$name} = \%hash if defined $arg1;
  151. return $self->{$name};
  152. }
  153. }
  154. }
  155. # ------------------------------------------------------------------------------
  156. # SYNOPSIS
  157. # ($rc, $new_lines) = $self->X ($old_lines);
  158. #
  159. # DESCRIPTION
  160. # This method compares current settings with those in the cache, where X is
  161. # one of @compare_setting_methods.
  162. #
  163. # If setting has changed:
  164. # * For bld_blockdata, bld_dep_ext and bld_exe_name, it sets the re-generate
  165. # make-rule flag to true.
  166. # * For bld_dep_excl, in a standalone build, the method will remove the
  167. # dependency cache files for affected sub-packages. It returns an error if
  168. # the current build inherits from previous builds.
  169. # * For bld_pp, it updates the PP setting for affected sub-packages.
  170. # * For infile_ext, in a standalone build, the method will remove all the
  171. # sub-package cache files and trigger a re-build by removing most
  172. # sub-directories created by the previous build. It returns an error if the
  173. # current build inherits from previous builds.
  174. # * For outfile_ext, in a standalone build, the method will remove all the
  175. # sub-package dependency cache files. It returns an error if the current
  176. # build inherits from previous builds.
  177. # * For tool, it updates the "flags" files for any changed tools.
  178. # ------------------------------------------------------------------------------
  179. for my $name (@compare_setting_methods) {
  180. no strict 'refs';
  181. *$name = sub {
  182. my ($self, $old_lines) = @_;
  183. (my $prefix = uc ($name)) =~ s/^COMPARE_SETTING_//;
  184. my ($changed, $new_lines) =
  185. $self->compare_setting_in_config ($prefix, $old_lines);
  186. my $rc = scalar (keys %$changed);
  187. if ($rc and $old_lines) {
  188. $self->srcpkg ('')->is_updated (1);
  189. if ($name =~ /^compare_setting_bld_dep(?:_excl|_n|_pp)?$/) {
  190. # Mark affected packages as being updated
  191. for my $key (keys %$changed) {
  192. for my $pkg (values %{ $self->srcpkg }) {
  193. next unless $pkg->is_in_package ($key);
  194. $pkg->is_updated (1);
  195. }
  196. }
  197. } elsif ($name eq 'compare_setting_bld_pp') {
  198. # Mark affected packages as being updated
  199. for my $key (keys %$changed) {
  200. for my $pkg (values %{ $self->srcpkg }) {
  201. next unless $pkg->is_in_package ($key);
  202. next unless $self->srcpkg ($key)->is_type_any (
  203. keys %{ $self->setting ('BLD_TYPE_DEP_PP') }
  204. ); # Is a type requiring pre-processing
  205. $pkg->is_updated (1);
  206. }
  207. }
  208. } elsif ($name eq 'compare_setting_infile_ext') {
  209. # Re-set input file type if necessary
  210. for my $key (keys %$changed) {
  211. for my $pkg (values %{ $self->srcpkg }) {
  212. next unless $pkg->src and $pkg->ext and $key eq $pkg->ext;
  213. $pkg->type (undef);
  214. }
  215. }
  216. # Mark affected packages as being updated
  217. for my $pkg (values %{ $self->srcpkg }) {
  218. $pkg->is_updated (1);
  219. }
  220. } elsif ($name eq 'compare_setting_outfile_ext') {
  221. # Mark affected packages as being updated
  222. for my $pkg (values %{ $self->srcpkg }) {
  223. $pkg->is_updated (1);
  224. }
  225. } elsif ($name eq 'compare_setting_tool') {
  226. # Update the "flags" files for changed tools
  227. for my $name (sort keys %$changed) {
  228. my ($tool, @names) = split /__/, $name;
  229. my $pkg = join ('__', @names);
  230. my @srcpkgs = $self->srcpkg ($pkg)
  231. ? ($self->srcpkg ($pkg))
  232. : @{ $self->dummysrcpkg ($pkg)->children };
  233. for my $srcpkg (@srcpkgs) {
  234. my $file = File::Spec->catfile (
  235. $self->dest->flagsdir, $srcpkg->flagsbase ($tool)
  236. );
  237. &touch_file ($file) or croak $file, ': cannot update, abort';
  238. print $file, ': updated', "\n" if $self->verbose > 2;
  239. }
  240. }
  241. }
  242. }
  243. return ($rc, $new_lines);
  244. }
  245. }
  246. # ------------------------------------------------------------------------------
  247. # SYNOPSIS
  248. # ($rc, $new_lines) = $self->compare_setting_dependency ($old_lines, $flag);
  249. #
  250. # DESCRIPTION
  251. # This method uses the previous settings to determine the dependencies of
  252. # current source files.
  253. # ------------------------------------------------------------------------------
  254. sub compare_setting_dependency {
  255. my ($self, $old_lines, $flag) = @_;
  256. my $prefix = $flag ? 'DEP_PP' : 'DEP';
  257. my $method = $flag ? 'ppdep' : 'dep';
  258. my $rc = 0;
  259. my $new_lines = [];
  260. # Separate old lines
  261. my %old;
  262. if ($old_lines) {
  263. for my $line (@$old_lines) {
  264. next unless $line->label_starts_with ($prefix);
  265. $old{$line->label_from_field (1)} = $line;
  266. }
  267. }
  268. # Go through each source to see if the cache is up to date
  269. my $count = 0;
  270. my %mtime;
  271. for my $srcpkg (values %{ $self->srcpkg }) {
  272. next unless $srcpkg->cursrc and $srcpkg->type;
  273. my $key = $srcpkg->pkgname;
  274. my $out_of_date = $srcpkg->is_updated;
  275. # Check modification time of cache and source file if not out of date
  276. if (exists $old{$key}) {
  277. if (not $out_of_date) {
  278. $mtime{$old{$key}->src} = (stat ($old{$key}->src))[9]
  279. if not exists ($mtime{$old{$key}->src});
  280. $out_of_date = 1 if $mtime{$old{$key}->src} < $srcpkg->curmtime;
  281. }
  282. }
  283. else {
  284. $out_of_date = 1;
  285. }
  286. if ($out_of_date) {
  287. # Re-scan dependency
  288. $srcpkg->is_updated(1);
  289. my ($source_is_read, $dep_hash_ref) = $srcpkg->get_dep($flag);
  290. if ($source_is_read) {
  291. $count++;
  292. }
  293. $srcpkg->$method($dep_hash_ref);
  294. $rc = 1;
  295. }
  296. else {
  297. # Use cached dependency
  298. my ($progname, %hash) = split (
  299. /$Fcm::Config::DELIMITER_PATTERN/, $old{$key}->value
  300. );
  301. $srcpkg->progname ($progname) if $progname and not $flag;
  302. $srcpkg->$method (\%hash);
  303. }
  304. # New lines values: progname[::dependency-name::type][...]
  305. my @value = ((defined $srcpkg->progname ? $srcpkg->progname : ''));
  306. for my $name (sort keys %{ $srcpkg->$method }) {
  307. push @value, $name, $srcpkg->$method ($name);
  308. }
  309. push @$new_lines, Fcm::CfgLine->new (
  310. LABEL => $prefix . $Fcm::Config::DELIMITER . $key,
  311. VALUE => join ($Fcm::Config::DELIMITER, @value),
  312. );
  313. }
  314. print 'No. of file', ($count > 1 ? 's' : ''), ' scanned for',
  315. ($flag ? ' PP': ''), ' dependency: ', $count, "\n"
  316. if $self->verbose and $count;
  317. return ($rc, $new_lines);
  318. }
  319. # ------------------------------------------------------------------------------
  320. # SYNOPSIS
  321. # ($rc, $new_lines) = $self->compare_setting_srcpkg ($old_lines);
  322. #
  323. # DESCRIPTION
  324. # This method uses the previous settings to determine the type of current
  325. # source files.
  326. # ------------------------------------------------------------------------------
  327. sub compare_setting_srcpkg {
  328. my ($self, $old_lines) = @_;
  329. my $prefix = 'SRCPKG';
  330. # Get relevant items from old lines, stripping out $prefix
  331. my %old;
  332. if ($old_lines) {
  333. for my $line (@$old_lines) {
  334. next unless $line->label_starts_with ($prefix);
  335. $old{$line->label_from_field (1)} = $line;
  336. }
  337. }
  338. # Check for change, use previous setting if exist
  339. my $out_of_date = 0;
  340. my %mtime;
  341. for my $key (keys %{ $self->srcpkg }) {
  342. if (exists $old{$key}) {
  343. next unless $self->srcpkg ($key)->cursrc;
  344. my $type = defined $self->setting ('BLD_TYPE', $key)
  345. ? $self->setting ('BLD_TYPE', $key) : $old{$key}->value;
  346. $self->srcpkg ($key)->type ($type);
  347. if ($type ne $old{$key}->value) {
  348. $self->srcpkg ($key)->is_updated (1);
  349. $out_of_date = 1;
  350. }
  351. if (not $self->srcpkg ($key)->is_updated) {
  352. $mtime{$old{$key}->src} = (stat ($old{$key}->src))[9]
  353. if not exists ($mtime{$old{$key}->src});
  354. $self->srcpkg ($key)->is_updated (1)
  355. if $mtime{$old{$key}->src} < $self->srcpkg ($key)->curmtime;
  356. }
  357. } else {
  358. $self->srcpkg ($key)->is_updated (1);
  359. $out_of_date = 1;
  360. }
  361. }
  362. # Check for deleted keys
  363. for my $key (keys %old) {
  364. next if $self->srcpkg ($key);
  365. $out_of_date = 1;
  366. }
  367. # Return reference to an array of new lines
  368. my $new_lines = [];
  369. for my $key (keys %{ $self->srcpkg }) {
  370. push @$new_lines, Fcm::CfgLine->new (
  371. LABEL => $prefix . $Fcm::Config::DELIMITER . $key,
  372. VALUE => $self->srcpkg ($key)->type,
  373. );
  374. }
  375. return ($out_of_date, $new_lines);
  376. }
  377. # ------------------------------------------------------------------------------
  378. # SYNOPSIS
  379. # ($rc, $new_lines) = $self->compare_setting_target ($old_lines);
  380. #
  381. # DESCRIPTION
  382. # This method compare the previous target settings with current ones.
  383. # ------------------------------------------------------------------------------
  384. sub compare_setting_target {
  385. my ($self, $old_lines) = @_;
  386. my $prefix = 'TARGET';
  387. my $old;
  388. if ($old_lines) {
  389. for my $line (@$old_lines) {
  390. next unless $line->label_starts_with ($prefix);
  391. $old = $line->value;
  392. last;
  393. }
  394. }
  395. my $new = join (' ', sort @{ $self->target });
  396. return (
  397. (defined ($old) ? $old ne $new : 1),
  398. [Fcm::CfgLine->new (LABEL => $prefix, VALUE => $new)],
  399. );
  400. }
  401. # ------------------------------------------------------------------------------
  402. # SYNOPSIS
  403. # $rc = $self->invoke_fortran_interface_generator ();
  404. #
  405. # DESCRIPTION
  406. # This method invokes the Fortran interface generator for all Fortran free
  407. # format source files. It returns true on success.
  408. # ------------------------------------------------------------------------------
  409. sub invoke_fortran_interface_generator {
  410. my $self = shift;
  411. my $pdoneext = $self->setting (qw/OUTFILE_EXT PDONE/);
  412. # Set up build task to generate interface files for all selected Fortran 9x
  413. # sources
  414. my %task = ();
  415. SRC_FILE:
  416. for my $srcfile (values %{ $self->srcpkg }) {
  417. if (!defined($srcfile->interfacebase())) {
  418. next SRC_FILE;
  419. }
  420. my $target = $srcfile->interfacebase . $pdoneext;
  421. $task{$target} = Fcm::BuildTask->new (
  422. TARGET => $target,
  423. TARGETPATH => $self->dest->donepath,
  424. SRCFILE => $srcfile,
  425. DEPENDENCY => [$srcfile->flagsbase ('GENINTERFACE')],
  426. ACTIONTYPE => 'GENINTERFACE',
  427. );
  428. # Set up build tasks for each source file/package flags file for interface
  429. # generator tool
  430. for my $i (1 .. @{ $srcfile->pkgnames }) {
  431. my $target = $srcfile->flagsbase ('GENINTERFACE', -$i);
  432. my $depend = $i < @{ $srcfile->pkgnames }
  433. ? $srcfile->flagsbase ('GENINTERFACE', -$i - 1)
  434. : undef;
  435. $task{$target} = Fcm::BuildTask->new (
  436. TARGET => $target,
  437. TARGETPATH => $self->dest->flagspath,
  438. DEPENDENCY => [defined ($depend) ? $depend : ()],
  439. ACTIONTYPE => 'UPDATE',
  440. ) if not exists $task{$target};
  441. }
  442. }
  443. # Set up build task to update the flags file for interface generator tool
  444. $task{$self->srcpkg ('')->flagsbase ('GENINTERFACE')} = Fcm::BuildTask->new (
  445. TARGET => $self->srcpkg ('')->flagsbase ('GENINTERFACE'),
  446. TARGETPATH => $self->dest->flagspath,
  447. ACTIONTYPE => 'UPDATE',
  448. );
  449. my $count = 0;
  450. # Performs task
  451. for my $task (values %task) {
  452. next unless $task->actiontype eq 'GENINTERFACE';
  453. my $rc = $task->action (TASKLIST => \%task);
  454. $count++ if $rc;
  455. }
  456. print 'No. of generated Fortran interface', ($count > 1 ? 's' : ''), ': ',
  457. $count, "\n"
  458. if $self->verbose and $count;
  459. return 1;
  460. }
  461. # ------------------------------------------------------------------------------
  462. # SYNOPSIS
  463. # $rc = $self->invoke_make (%args);
  464. #
  465. # DESCRIPTION
  466. # This method invokes the make stage of the build system. It returns true on
  467. # success.
  468. #
  469. # ARGUMENTS
  470. # ARCHIVE - If set to "true", invoke the "archive" mode. Most build files and
  471. # directories created by this build will be archived using the
  472. # "tar" command. If not set, the default is not to invoke the
  473. # "archive" mode.
  474. # JOBS - Specify number of jobs that can be handled by "make". If set, the
  475. # value must be a natural integer. If not set, the default value is
  476. # 1 (i.e. run "make" in serial mode).
  477. # TARGETS - Specify targets to be built. If set, these targets will be built
  478. # instead of the ones specified in the build configuration file.
  479. # ------------------------------------------------------------------------------
  480. sub invoke_make {
  481. my ($self, %args) = @_;
  482. $args{TARGETS} ||= ['all'];
  483. $args{JOBS} ||= 1;
  484. my @command = (
  485. $self->setting(qw/TOOL MAKE/),
  486. shellwords($self->setting(qw/TOOL MAKEFLAGS/)),
  487. # -f Makefile
  488. ($self->setting(qw/TOOL MAKE_FILE/), $self->dest()->bldmakefile()),
  489. # -j N
  490. ($args{JOBS} ? ($self->setting(qw/TOOL MAKE_JOB/), $args{JOBS}) : ()),
  491. # -s
  492. ($self->verbose() >= 3 ? $self->setting(qw/TOOL MAKE_SILENT/) : ()),
  493. @{$args{TARGETS}}
  494. );
  495. my $old_cwd = $self->_chdir($self->dest()->rootdir());
  496. run_command(
  497. \@command, ERROR => 'warn', RC => \my($code), TIME => $self->verbose() >= 3,
  498. );
  499. $self->_chdir($old_cwd);
  500. my $rc = !$code;
  501. if ($rc && $args{ARCHIVE}) {
  502. $rc = $self->dest()->archive();
  503. }
  504. $rc &&= $self->dest()->create_bldrunenvsh();
  505. while (my ($key, $source) = each(%{$self->srcpkg()})) {
  506. $rc &&= defined($source->write_lib_dep_excl());
  507. }
  508. return $rc;
  509. }
  510. # ------------------------------------------------------------------------------
  511. # SYNOPSIS
  512. # $rc = $self->invoke_pre_process ();
  513. #
  514. # DESCRIPTION
  515. # This method invokes the pre-process stage of the build system. It
  516. # returns true on success.
  517. # ------------------------------------------------------------------------------
  518. sub invoke_pre_process {
  519. my $self = shift;
  520. # Check whether pre-processing is necessary
  521. my $invoke = 0;
  522. for (values %{ $self->srcpkg }) {
  523. next unless $_->get_setting ('BLD_PP');
  524. $invoke = 1;
  525. last;
  526. }
  527. return 1 unless $invoke;
  528. # Scan header dependency
  529. my $rc = $self->compare_setting (
  530. METHOD_LIST => ['compare_setting_dependency'],
  531. METHOD_ARGS => ['BLD_TYPE_DEP_PP'],
  532. CACHEBASE => $self->setting ('CACHE_DEP_PP'),
  533. );
  534. return $rc if not $rc;
  535. my %task = ();
  536. my $pdoneext = $self->setting (qw/OUTFILE_EXT PDONE/);
  537. # Set up tasks for each source file
  538. for my $srcfile (values %{ $self->srcpkg }) {
  539. if ($srcfile->is_type_all (qw/CPP INCLUDE/)) {
  540. # Set up a copy build task for each include file
  541. $task{$srcfile->base} = Fcm::BuildTask->new (
  542. TARGET => $srcfile->base,
  543. TARGETPATH => $self->dest->incpath,
  544. SRCFILE => $srcfile,
  545. DEPENDENCY => [keys %{ $srcfile->ppdep }],
  546. ACTIONTYPE => 'COPY',
  547. );
  548. } elsif ($srcfile->lang ('TOOL_SRC_PP')) {
  549. next unless $srcfile->get_setting ('BLD_PP');
  550. # Set up a PP build task for each source file
  551. my $target = $srcfile->base . $pdoneext;
  552. # Issue warning for duplicated tasks
  553. if (exists $task{$target}) {
  554. w_report 'WARNING: ', $target, ': unable to create task for: ',
  555. $srcfile->src, ': task already exists for: ',
  556. $task{$target}->srcfile->src;
  557. next;
  558. }
  559. $task{$target} = Fcm::BuildTask->new (
  560. TARGET => $target,
  561. TARGETPATH => $self->dest->donepath,
  562. SRCFILE => $srcfile,
  563. DEPENDENCY => [$srcfile->flagsbase ('PPKEYS'), keys %{ $srcfile->ppdep }],
  564. ACTIONTYPE => 'PP',
  565. );
  566. # Set up update ppkeys/flags build tasks for each source file/package
  567. my $ppkeys = $self->setting (
  568. 'TOOL_SRC_PP', $srcfile->lang ('TOOL_SRC_PP'), 'PPKEYS'
  569. );
  570. for my $i (1 .. @{ $srcfile->pkgnames }) {
  571. my $target = $srcfile->flagsbase ($ppkeys, -$i);
  572. my $depend = $i < @{ $srcfile->pkgnames }
  573. ? $srcfile->flagsbase ($ppkeys, -$i - 1)
  574. : undef;
  575. $task{$target} = Fcm::BuildTask->new (
  576. TARGET => $target,
  577. TARGETPATH => $self->dest->flagspath,
  578. DEPENDENCY => [defined ($depend) ? $depend : ()],
  579. ACTIONTYPE => 'UPDATE',
  580. ) if not exists $task{$target};
  581. }
  582. }
  583. }
  584. # Set up update global ppkeys build tasks
  585. for my $lang (keys %{ $self->setting ('TOOL_SRC_PP') }) {
  586. my $target = $self->srcpkg ('')->flagsbase (
  587. $self->setting ('TOOL_SRC_PP', $lang, 'PPKEYS')
  588. );
  589. $task{$target} = Fcm::BuildTask->new (
  590. TARGET => $target,
  591. TARGETPATH => $self->dest->flagspath,
  592. ACTIONTYPE => 'UPDATE',
  593. );
  594. }
  595. # Build all PP tasks
  596. my $count = 0;
  597. for my $task (values %task) {
  598. next unless $task->actiontype eq 'PP';
  599. my $rc = $task->action (TASKLIST => \%task);
  600. $task->srcfile->is_updated ($rc);
  601. $count++ if $rc;
  602. }
  603. print 'No. of pre-processed file', ($count > 1 ? 's' : ''), ': ', $count, "\n"
  604. if $self->verbose and $count;
  605. return 1;
  606. }
  607. # ------------------------------------------------------------------------------
  608. # SYNOPSIS
  609. # $rc = $self->invoke_scan_dependency ();
  610. #
  611. # DESCRIPTION
  612. # This method invokes the scan dependency stage of the build system. It
  613. # returns true on success.
  614. # ------------------------------------------------------------------------------
  615. sub invoke_scan_dependency {
  616. my $self = shift;
  617. # Scan/retrieve dependency
  618. # ----------------------------------------------------------------------------
  619. my $rc = $self->compare_setting (
  620. METHOD_LIST => ['compare_setting_dependency'],
  621. CACHEBASE => $self->setting ('CACHE_DEP'),
  622. );
  623. # Check whether make file is out of date
  624. # ----------------------------------------------------------------------------
  625. my $out_of_date = not -r $self->dest->bldmakefile;
  626. if ($rc and not $out_of_date) {
  627. for (qw/CACHE CACHE_DEP/) {
  628. my $cache_mtime = (stat (File::Spec->catfile (
  629. $self->dest->cachedir, $self->setting ($_),
  630. )))[9];
  631. my $mfile_mtime = (stat ($self->dest->bldmakefile))[9];
  632. next if not defined $cache_mtime;
  633. next if $cache_mtime < $mfile_mtime;
  634. $out_of_date = 1;
  635. last;
  636. }
  637. }
  638. if ($rc and not $out_of_date) {
  639. for (values %{ $self->srcpkg }) {
  640. next unless $_->is_updated;
  641. $out_of_date = 1;
  642. last;
  643. }
  644. }
  645. if ($rc and $out_of_date) {
  646. # Write Makefile
  647. # --------------------------------------------------------------------------
  648. # Register non-word package name
  649. my $unusual = 0;
  650. for my $key (sort keys %{ $self->srcpkg }) {
  651. next if $self->srcpkg ($key)->src;
  652. next if $key =~ /^\w*$/;
  653. $self->setting (
  654. ['FCM_PCK_OBJECTS', $key], 'FCM_PCK_OBJECTS' . $unusual++,
  655. );
  656. }
  657. # Write different parts in the Makefile
  658. my $makefile = '# Automatic Makefile' . "\n\n";
  659. $makefile .= 'FCM_BLD_NAME = ' . $self->name . "\n" if $self->name;
  660. $makefile .= 'FCM_BLD_CFG = ' . $self->cfg->actual_src . "\n";
  661. $makefile .= 'export FCM_VERBOSE ?= ' . $self->verbose . "\n\n";
  662. $makefile .= $self->dest->write_rules;
  663. $makefile .= $self->_write_makefile_perl5lib;
  664. $makefile .= $self->_write_makefile_tool;
  665. $makefile .= $self->_write_makefile_vpath;
  666. $makefile .= $self->_write_makefile_target;
  667. # Write rules for each source package
  668. # Ensure that container packages come before files - this allows $(OBJECTS)
  669. # and its dependent variables to expand correctly
  670. my @srcpkg = sort {
  671. if ($self->srcpkg ($a)->libbase and $self->srcpkg ($b)->libbase) {
  672. $b cmp $a;
  673. } elsif ($self->srcpkg ($a)->libbase) {
  674. -1;
  675. } elsif ($self->srcpkg ($b)->libbase) {
  676. 1;
  677. } else {
  678. $a cmp $b;
  679. }
  680. } keys %{ $self->srcpkg };
  681. for (@srcpkg) {
  682. $makefile .= $self->srcpkg ($_)->write_rules if $self->srcpkg ($_)->rules;
  683. }
  684. $makefile .= '# EOF' . "\n";
  685. # Update Makefile
  686. open OUT, '>', $self->dest->bldmakefile
  687. or croak $self->dest->bldmakefile, ': cannot open (', $!, '), abort';
  688. print OUT $makefile;
  689. close OUT
  690. or croak $self->dest->bldmakefile, ': cannot close (', $!, '), abort';
  691. print $self->dest->bldmakefile, ': updated', "\n" if $self->verbose;
  692. # Check for duplicated targets
  693. # --------------------------------------------------------------------------
  694. # Get list of types that cannot have duplicated targets
  695. my @no_duplicated_target_types = split (
  696. /$DELIMITER_LIST/,
  697. $self->setting ('BLD_TYPE_NO_DUPLICATED_TARGET'),
  698. );
  699. my %targets;
  700. for my $name (sort keys %{ $self->srcpkg }) {
  701. next unless $self->srcpkg ($name)->rules;
  702. for my $key (sort keys %{ $self->srcpkg ($name)->rules }) {
  703. if (exists $targets{$key}) {
  704. # Duplicated target: warning for most file types
  705. my $status = 'WARNING';
  706. # Duplicated target: error for the following file types
  707. if (@no_duplicated_target_types and
  708. $self-srcpkg ($name)->is_type_any (@no_duplicated_target_types) and
  709. $targets{$key}->is_type_any (@no_duplicated_target_types)) {
  710. $status = 'ERROR';
  711. $rc = 0;
  712. }
  713. # Report the warning/error
  714. w_report $status, ': ', $key, ': duplicated targets for building:';
  715. w_report ' ', $targets{$key}->src;
  716. w_report ' ', $self->srcpkg ($name)->src;
  717. } else {
  718. $targets{$key} = $self->srcpkg ($name);
  719. }
  720. }
  721. }
  722. }
  723. return $rc;
  724. }
  725. # ------------------------------------------------------------------------------
  726. # SYNOPSIS
  727. # $rc = $self->invoke_setup_build ();
  728. #
  729. # DESCRIPTION
  730. # This method invokes the setup_build stage of the build system. It returns
  731. # true on success.
  732. # ------------------------------------------------------------------------------
  733. sub invoke_setup_build {
  734. my $self = shift;
  735. my $rc = 1;
  736. # Extract archived sub-directories if necessary
  737. $rc = $self->dest->dearchive if $rc;
  738. # Compare cache
  739. $rc = $self->compare_setting (METHOD_LIST => [
  740. 'compare_setting_target', # targets
  741. 'compare_setting_srcpkg', # source package type
  742. @compare_setting_methods,
  743. ]) if $rc;
  744. # Set up runtime dependency scan patterns
  745. my %dep_pattern = %{ $self->setting ('BLD_DEP_PATTERN') };
  746. for my $key (keys %dep_pattern) {
  747. my $pattern = $dep_pattern{$key};
  748. while ($pattern =~ /##([\w:]+)##/g) {
  749. my $match = $1;
  750. my $val = $self->setting (split (/$Fcm::Config::DELIMITER/, $match));
  751. last unless defined $val;
  752. $val =~ s/\./\\./;
  753. $pattern =~ s/##$match##/$val/;
  754. }
  755. $self->setting (['BLD_DEP_PATTERN', $key], $pattern)
  756. unless $pattern eq $dep_pattern{$key};
  757. }
  758. return $rc;
  759. }
  760. # ------------------------------------------------------------------------------
  761. # SYNOPSIS
  762. # $rc = $self->invoke_system (%args);
  763. #
  764. # DESCRIPTION
  765. # This method invokes the build system. It returns true on success. See also
  766. # the header for invoke_make for further information on arguments.
  767. #
  768. # ARGUMENTS
  769. # STAGE - If set, it should be an integer number or a recognised keyword or
  770. # abbreviation. If set, the build is performed up to the named stage.
  771. # If not set, the default is to perform all stages of the build.
  772. # Allowed values are:
  773. # 1, setup or s
  774. # 2, pre_process or pp
  775. # 3, generate_dependency or gd
  776. # 4, generate_interface or gi
  777. # 5, all, a, make or m
  778. # ------------------------------------------------------------------------------
  779. sub invoke_system {
  780. my $self = shift;
  781. my %args = @_;
  782. # Parse arguments
  783. # ----------------------------------------------------------------------------
  784. # Default: run all 5 stages
  785. my $stage = (exists $args{STAGE} and $args{STAGE}) ? $args{STAGE} : 5;
  786. # Resolve named stages
  787. if ($stage !~ /^\d$/) {
  788. my %stagenames = (
  789. 'S(?:ETUP)?' => 1,
  790. 'P(?:RE)?_?P(?:ROCESS)?' => 2,
  791. 'G(?:ENERATE)?_?D(?:ENPENDENCY)?' => 3,
  792. 'G(?:ENERATE)?_?I(?:NTERFACE)?' => 4,
  793. '(?:A(?:LL)|M(?:AKE)?)' => 5,
  794. );
  795. # Does it match a recognised stage?
  796. for my $name (keys %stagenames) {
  797. next unless $stage =~ /$name/i;
  798. $stage = $stagenames{$name};
  799. last;
  800. }
  801. # Specified stage name not recognised, default to 5
  802. if ($stage !~ /^\d$/) {
  803. w_report 'WARNING: ', $stage, ': invalid build stage, default to 5.';
  804. $stage = 5;
  805. }
  806. }
  807. # Run the method associated with each stage
  808. # ----------------------------------------------------------------------------
  809. my $rc = 1;
  810. my @stages = (
  811. ['Setup build' , 'invoke_setup_build'],
  812. ['Pre-process' , 'invoke_pre_process'],
  813. ['Scan dependency' , 'invoke_scan_dependency'],
  814. ['Generate Fortran interface', 'invoke_fortran_interface_generator'],
  815. ['Make' , 'invoke_make'],
  816. );
  817. for my $i (1 .. 5) {
  818. last if (not $rc) or $i > $stage;
  819. my ($name, $method) = @{ $stages[$i - 1] };
  820. $rc = $self->invoke_stage ($name, $method, %args) if $rc and $stage >= $i;
  821. }
  822. return $rc;
  823. }
  824. # ------------------------------------------------------------------------------
  825. # SYNOPSIS
  826. # $rc = $self->parse_cfg_dep (\@cfg_lines);
  827. #
  828. # DESCRIPTION
  829. # This method parses the dependency settings in the @cfg_lines.
  830. # ------------------------------------------------------------------------------
  831. sub parse_cfg_dep {
  832. my ($self, $cfg_lines) = @_;
  833. my $rc = 1;
  834. # EXCL_DEP, EXE_DEP and BLOCKDATA declarations
  835. # ----------------------------------------------------------------------------
  836. for my $name (qw/BLD_BLOCKDATA BLD_DEP BLD_DEP_EXCL BLD_DEP_EXE/) {
  837. for my $line (grep {$_->slabel_starts_with_cfg ($name)} @$cfg_lines) {
  838. # Separate label into a list, delimited by double-colon, remove 1st field
  839. my @flds = $line->slabel_fields;
  840. shift @flds;
  841. if ($name =~ /^(?:BLD_DEP|BLD_DEP_EXCL|BLD_DEP_PP)$/) {
  842. # BLD_DEP_*: label fields may contain sub-package
  843. my $pk = @flds ? join ('__', @flds) : '';
  844. # Check whether sub-package is valid
  845. if ($pk and not ($self->srcpkg ($pk) or $self->dummysrcpkg ($pk))) {
  846. $line->error ($line->label . ': invalid sub-package in declaration.');
  847. $rc = 0;
  848. next;
  849. }
  850. # Setting is stored in an array reference
  851. $self->setting ([$name, $pk], [])
  852. if not defined $self->setting ($name, $pk);
  853. # Add current declaration to the array if necessary
  854. my $list = $self->setting ($name, $pk);
  855. my $value = $name eq 'BLD_DEP_EXCL' ? uc ($line->value) : $line->value;
  856. push @$list, $value if not grep {$_ eq $value} @$list;
  857. } else {
  858. # EXE_DEP and BLOCKDATA: label field may be an executable target
  859. my $target = @flds ? $flds[0] : '';
  860. # The value contains a list of objects and/or sub-package names
  861. my @deps = split /\s+/, $line->value;
  862. if (not @deps) {
  863. if ($name eq 'BLD_BLOCKDATA') {
  864. # The objects containing a BLOCKDATA program unit must be declared
  865. $line->error ($line->label . ': value not set.');
  866. $rc = 0;
  867. next;
  868. } else {
  869. # If $value is a null string, target(s) depends on all objects
  870. push @deps, '';
  871. }
  872. }
  873. for my $dep (@deps) {
  874. $dep =~ s/$Fcm::Config::DELIMITER_PATTERN/__/g;
  875. }
  876. $self->setting ([$name, $target], join (' ', sort @deps));
  877. }
  878. $line->parsed (1);
  879. }
  880. }
  881. return $rc;
  882. }
  883. # ------------------------------------------------------------------------------
  884. # SYNOPSIS
  885. # $rc = $self->parse_cfg_dest (\@cfg_lines);
  886. #
  887. # DESCRIPTION
  888. # This method parses the build destination settings in the @cfg_lines.
  889. # ------------------------------------------------------------------------------
  890. sub parse_cfg_dest {
  891. my ($self, $cfg_lines) = @_;
  892. my $rc = $self->SUPER::parse_cfg_dest ($cfg_lines);
  893. # Set up search paths
  894. for my $name (@Fcm::Dest::paths) {
  895. (my $label = uc ($name)) =~ s/PATH//;
  896. $self->setting (['PATH', $label], $self->dest->$name);
  897. }
  898. return $rc;
  899. }
  900. # ------------------------------------------------------------------------------
  901. # SYNOPSIS
  902. # $rc = $self->parse_cfg_misc (\@cfg_lines);
  903. #
  904. # DESCRIPTION
  905. # This method parses misc build settings in the @cfg_lines.
  906. # ------------------------------------------------------------------------------
  907. sub parse_cfg_misc {
  908. my ($self, $cfg_lines_ref) = @_;
  909. my $rc = 1;
  910. my %item_of = (
  911. BLD_DEP_N => [\&_parse_cfg_misc_dep_n , 1 ], # boolean
  912. BLD_EXE_NAME => [\&_parse_cfg_misc_exe_name ],
  913. BLD_LIB => [\&_parse_cfg_misc_dep_n ],
  914. BLD_PP => [\&_parse_cfg_misc_dep_n , 1 ], # boolean
  915. BLD_TYPE => [\&_parse_cfg_misc_dep_n ],
  916. INFILE_EXT => [\&_parse_cfg_misc_file_ext, 0, 1], # uc($value)
  917. OUTFILE_EXT => [\&_parse_cfg_misc_file_ext, 1, 0], # uc($ns)
  918. );
  919. while (my ($key, $item) = each(%item_of)) {
  920. my ($handler, @extra_arguments) = @{$item};
  921. for my $line (@{$cfg_lines_ref}) {
  922. if ($line->slabel_starts_with_cfg($key)) {
  923. if ($handler->($self, $key, $line, @extra_arguments)) {
  924. $line->parsed(1);
  925. }
  926. else {
  927. $rc = 0;
  928. }
  929. }
  930. }
  931. }
  932. return $rc;
  933. }
  934. # ------------------------------------------------------------------------------
  935. # parse_cfg_misc: handler of BLD_EXE_NAME or similar.
  936. sub _parse_cfg_misc_exe_name {
  937. my ($self, $key, $line) = @_;
  938. my ($prefix, $name, @fields) = $line->slabel_fields();
  939. if (!$name || @fields) {
  940. $line->error(sprintf('%s: expects a single label name field.', $key));
  941. return 0;
  942. }
  943. $self->setting([$key, $name], $line->value());
  944. return 1;
  945. }
  946. # ------------------------------------------------------------------------------
  947. # parse_cfg_misc: handler of BLD_DEP_N or similar.
  948. sub _parse_cfg_misc_dep_n {
  949. my ($self, $key, $line, $value_is_boolean) = @_;
  950. my ($prefix, @fields) = $line->slabel_fields();
  951. my $ns = @fields ? join(q{__}, @fields) : q{};
  952. if ($ns && !$self->srcpkg($ns) && !$self->dummysrcpkg($ns)) {
  953. $line->error($line->label() . ': invalid sub-package in declaration.');
  954. return 0;
  955. }
  956. my @srcpkgs
  957. = $self->dummysrcpkg($ns) ? @{$self->dummysrcpkg($ns)->children()}
  958. : $self->srcpkg($ns)
  959. ;
  960. my $value = $value_is_boolean ? $line->bvalue() : $line->value();
  961. for my $srcpkg (@srcpkgs) {
  962. $self->setting([$key, $srcpkg->pkgname()], $value);
  963. }
  964. return 1;
  965. }
  966. # ------------------------------------------------------------------------------
  967. # parse_cfg_misc: handler of INFILE_EXT/OUTFILE_EXT or similar.
  968. sub _parse_cfg_misc_file_ext {
  969. my ($self, $key, $line, $ns_in_uc, $value_in_uc) = @_;
  970. my ($prefix, $ns) = $line->slabel_fields();
  971. my $value = $value_in_uc ? uc($line->value()) : $line->value();
  972. $self->setting([$key, ($ns_in_uc ? uc($ns) : $ns)], $value);
  973. return 1;
  974. }
  975. # ------------------------------------------------------------------------------
  976. # SYNOPSIS
  977. # $rc = $self->parse_cfg_source (\@cfg_lines);
  978. #
  979. # DESCRIPTION
  980. # This method parses the source package settings in the @cfg_lines.
  981. # ------------------------------------------------------------------------------
  982. sub parse_cfg_source {
  983. my ($self, $cfg_lines) = @_;
  984. my $rc = 1;
  985. my %src = ();
  986. # Automatic source directory search?
  987. # ----------------------------------------------------------------------------
  988. my $search = 1;
  989. for my $line (grep {$_->slabel_starts_with_cfg ('SEARCH_SRC')} @$cfg_lines) {
  990. $search = $line->bvalue;
  991. $line->parsed (1);
  992. }
  993. # Search src/ sub-directory if necessary
  994. %src = %{ $self->dest->get_source_files } if $search;
  995. # SRC declarations
  996. # ----------------------------------------------------------------------------
  997. for my $line (grep {$_->slabel_starts_with_cfg ('FILE')} @$cfg_lines) {
  998. # Expand ~ notation and path relative to srcdir of destination
  999. my $value = $line->value;
  1000. $value = File::Spec->rel2abs (&expand_tilde ($value), $self->dest->srcdir);
  1001. if (not -r $value) {
  1002. $line->error ($value . ': source does not exist or is not readable.');
  1003. next;
  1004. }
  1005. # Package name
  1006. my @names = $line->slabel_fields;
  1007. shift @names;
  1008. # If package name not set, determine using the path if possible
  1009. if (not @names) {
  1010. my $package = $self->dest->get_pkgname_of_path ($value);
  1011. @names = @$package if defined $package;
  1012. }
  1013. if (not @names) {
  1014. $line->error ($self->cfglabel ('FILE') .
  1015. ': package not specified/cannot be determined.');
  1016. next;
  1017. }
  1018. $src{join ('__', @names)} = $value;
  1019. $line->parsed (1);
  1020. }
  1021. # For directories, get non-recursive file listing, and add to %src
  1022. # ----------------------------------------------------------------------------
  1023. for my $key (keys %src) {
  1024. next unless -d $src{$key};
  1025. opendir DIR, $src{$key} or die $src{$key}, ': cannot read directory';
  1026. while (my $base = readdir 'DIR') {
  1027. next if $base =~ /^\./;
  1028. my $file = File::Spec->catfile ($src{$key}, $base);
  1029. next unless -f $file and -r $file;
  1030. my $name = join ('__', ($key, $base));
  1031. $src{$name} = $file unless exists $src{$name};
  1032. }
  1033. closedir DIR;
  1034. delete $src{$key};
  1035. }
  1036. # Set up source packages
  1037. # ----------------------------------------------------------------------------
  1038. my %pkg = ();
  1039. for my $name (keys %src) {
  1040. $pkg{$name} = Fcm::BuildSrc->new (PKGNAME => $name, SRC => $src{$name});
  1041. }
  1042. # INHERIT::SRC declarations
  1043. # ----------------------------------------------------------------------------
  1044. my %can_inherit = ();
  1045. for my $line (
  1046. grep {$_->slabel_starts_with_cfg(qw/INHERIT FILE/)} @{$cfg_lines}
  1047. ) {
  1048. my ($key1, $key2, @ns) = $line->slabel_fields();
  1049. $can_inherit{join('__', @ns)} = $line->bvalue();
  1050. $line->parsed(1);
  1051. }
  1052. # Inherit packages, if it is OK to do so
  1053. for my $inherited_build (reverse(@{$self->inherit()})) {
  1054. SRCPKG:
  1055. while (my ($key, $srcpkg) = each(%{$inherited_build->srcpkg()})) {
  1056. if (exists($pkg{$key}) || !$srcpkg->src()) {
  1057. next SRCPKG;
  1058. }
  1059. my $known_key = first {exists($can_inherit{$_})} @{$srcpkg->pkgnames()};
  1060. if (defined($known_key) && !$can_inherit{$known_key}) {
  1061. next SRCPKG;
  1062. }
  1063. $pkg{$key} = $srcpkg;
  1064. }
  1065. }
  1066. # Get list of intermediate "packages"
  1067. # ----------------------------------------------------------------------------
  1068. for my $name (keys %pkg) {
  1069. # Name of current package
  1070. my @names = split /__/, $name;
  1071. my $cur = $name;
  1072. while ($cur) {
  1073. # Name of parent package
  1074. pop @names;
  1075. my $parent = @names ? join ('__', @names) : '';
  1076. # If parent package does not exist, create it
  1077. $pkg{$parent} = Fcm::BuildSrc->new (PKGNAME => $parent)
  1078. unless exists $pkg{$parent};
  1079. # Current package is a child of the parent package
  1080. push @{ $pkg{$parent}->children }, $pkg{$cur}
  1081. unless grep {$_->pkgname eq $cur} @{ $pkg{$parent}->children };
  1082. # Go up a package
  1083. $cur = $parent;
  1084. }
  1085. }
  1086. $self->srcpkg (\%pkg);
  1087. # Dummy: e.g. "foo/bar/baz.egg" belongs to the "foo/bar/baz" dummy.
  1088. # ----------------------------------------------------------------------------
  1089. for my $name (keys %pkg) {
  1090. (my $dname = $name) =~ s/\.\w+$//;
  1091. next if $dname eq $name;
  1092. next if $self->srcpkg ($dname);
  1093. $self->dummysrcpkg ($dname, Fcm::BuildSrc->new (PKGNAME => $dname))
  1094. unless $self->dummysrcpkg ($dname);
  1095. push @{ $self->dummysrcpkg ($dname)->children }, $pkg{$name};
  1096. }
  1097. # Make sure a package is defined
  1098. # ----------------------------------------------------------------------------
  1099. if (not %{$self->srcpkg}) {
  1100. w_report 'ERROR: ', $self->cfg->actual_src, ': no source file to build.';
  1101. $rc = 0;
  1102. }
  1103. return $rc;
  1104. }
  1105. # ------------------------------------------------------------------------------
  1106. # SYNOPSIS
  1107. # $rc = $self->parse_cfg_target (\@cfg_lines);
  1108. #
  1109. # DESCRIPTION
  1110. # This method parses the target settings in the @cfg_lines.
  1111. # ------------------------------------------------------------------------------
  1112. sub parse_cfg_target {
  1113. my ($self, $cfg_lines) = @_;
  1114. # NAME declaraions
  1115. # ----------------------------------------------------------------------------
  1116. for my $line (grep {$_->slabel_starts_with_cfg ('NAME')} @$cfg_lines) {
  1117. $self->name ($line->value);
  1118. $line->parsed (1);
  1119. }
  1120. # TARGET declarations
  1121. # ----------------------------------------------------------------------------
  1122. for my $line (grep {$_->slabel_starts_with_cfg ('TARGET')} @$cfg_lines) {
  1123. # Value is a space delimited list
  1124. push @{ $self->target }, split (/\s+/, $line->value);
  1125. $line->parsed (1);
  1126. }
  1127. # INHERIT::TARGET declarations
  1128. # ----------------------------------------------------------------------------
  1129. # By default, do not inherit target
  1130. my $inherit_flag = 0;
  1131. for (grep {$_->slabel_starts_with_cfg (qw/INHERIT TARGET/)} @$cfg_lines) {
  1132. $inherit_flag = $_->bvalue;
  1133. $_->parsed (1);
  1134. }
  1135. # Inherit targets from inherited build, if $inherit_flag is set to true
  1136. # ----------------------------------------------------------------------------
  1137. if ($inherit_flag) {
  1138. for my $use (reverse @{ $self->inherit }) {
  1139. unshift @{ $self->target }, @{ $use->target };
  1140. }
  1141. }
  1142. return 1;
  1143. }
  1144. # ------------------------------------------------------------------------------
  1145. # SYNOPSIS
  1146. # $rc = $self->parse_cfg_tool (\@cfg_lines);
  1147. #
  1148. # DESCRIPTION
  1149. # This method parses the tool settings in the @cfg_lines.
  1150. # ------------------------------------------------------------------------------
  1151. sub parse_cfg_tool {
  1152. my ($self, $cfg_lines) = @_;
  1153. my $rc = 1;
  1154. my %tools = %{ $self->setting ('TOOL') };
  1155. my @package_tools = split(/$DELIMITER_LIST/, $self->setting('TOOL_PACKAGE'));
  1156. # TOOL declaration
  1157. # ----------------------------------------------------------------------------
  1158. for my $line (grep {$_->slabel_starts_with_cfg ('TOOL')} @$cfg_lines) {
  1159. # Separate label into a list, delimited by double-colon, remove TOOL
  1160. my @flds = $line->slabel_fields;
  1161. shift @flds;
  1162. # Check that there is a field after TOOL
  1163. if (not @flds) {
  1164. $line->error ('TOOL: not followed by a valid label.');
  1165. $rc = 0;
  1166. next;
  1167. }
  1168. # The first field is the tool iteself, identified in uppercase
  1169. $flds[0] = uc ($flds[0]);
  1170. # Check that the tool is recognised
  1171. if (not exists $tools{$flds[0]}) {
  1172. $line->error ($flds[0] . ': not a valid TOOL.');
  1173. $rc = 0;
  1174. next;
  1175. }
  1176. # Check sub-package declaration
  1177. if (@flds > 1 and not grep {$_ eq $flds[0]} @package_tools) {
  1178. $line->error ($flds[0] . ': sub-package not accepted with this TOOL.');
  1179. $rc = 0;
  1180. next;
  1181. }
  1182. # Name of declared package
  1183. my $pk = join ('__', @flds[1 .. $#flds]);
  1184. # Check whether package exists
  1185. if (not ($self->srcpkg ($pk) or $self->dummysrcpkg ($pk))) {
  1186. $line->error ($line->label . ': invalid sub-package in declaration.');
  1187. $rc = 0;
  1188. next;
  1189. }
  1190. $self->setting (['TOOL', join ('__', @flds)], $line->value);
  1191. $line->parsed (1);
  1192. }
  1193. return $rc;
  1194. }
  1195. # ------------------------------------------------------------------------------
  1196. # SYNOPSIS
  1197. # $string = $self->_write_makefile_perl5lib ();
  1198. #
  1199. # DESCRIPTION
  1200. # This method returns a makefile $string for defining $PERL5LIB.
  1201. # ------------------------------------------------------------------------------
  1202. sub _write_makefile_perl5lib {
  1203. my $self = shift;
  1204. my $classpath = File::Spec->catfile (split (/::/, ref ($self))) . '.pm';
  1205. my $libdir = dirname (dirname ($INC{$classpath}));
  1206. my @libpath = split (/:/, (exists $ENV{PERL5LIB} ? $ENV{PERL5LIB} : ''));
  1207. my $string = ((grep {$_ eq $libdir} @libpath)
  1208. ? ''
  1209. : 'export PERL5LIB := ' . $libdir .
  1210. (exists $ENV{PERL5LIB} ? ':$(PERL5LIB)' : '') . "\n\n");
  1211. return $string;
  1212. }
  1213. # ------------------------------------------------------------------------------
  1214. # SYNOPSIS
  1215. # $string = $self->_write_makefile_target ();
  1216. #
  1217. # DESCRIPTION
  1218. # This method returns a makefile $string for defining the default targets.
  1219. # ------------------------------------------------------------------------------
  1220. sub _write_makefile_target {
  1221. my $self = shift;
  1222. # Targets of the build
  1223. # ----------------------------------------------------------------------------
  1224. my @targets = @{ $self->target };
  1225. if (not @targets) {
  1226. # Build targets not specified by user, default to building all main programs
  1227. my @programs = ();
  1228. # Get all main programs from all packages
  1229. for my $pkg (values %{ $self->srcpkg }) {
  1230. push @programs, $pkg->exebase if $pkg->exebase;
  1231. }
  1232. @programs = sort (@programs);
  1233. if (@programs) {
  1234. # Build main programs, if there are any
  1235. @targets = @programs;
  1236. } else {
  1237. # No main program in source tree, build the default library
  1238. @targets = ($self->srcpkg ('')->libbase);
  1239. }
  1240. }
  1241. my $return = 'FCM_BLD_TARGETS = ' . join (' ', @targets) . "\n\n";
  1242. # Default targets
  1243. $return .= '.PHONY : all' . "\n\n";
  1244. $return .= 'all : $(FCM_BLD_TARGETS)' . "\n\n";
  1245. # Targets for copy dummy
  1246. $return .= sprintf("%s:\n\ttouch \$@\n\n", $self->setting(qw/BLD_CPDUMMY/));
  1247. return $return;
  1248. }
  1249. # ------------------------------------------------------------------------------
  1250. # SYNOPSIS
  1251. # $string = $self->_write_makefile_tool ();
  1252. #
  1253. # DESCRIPTION
  1254. # This method returns a makefile $string for defining the build tools.
  1255. # ------------------------------------------------------------------------------
  1256. sub _write_makefile_tool {
  1257. my $self = shift;
  1258. # List of build tools
  1259. my $tool = $self->setting ('TOOL');
  1260. # List of tools local to FCM, (will not be exported)
  1261. my %localtool = map {($_, 1)} split ( # map into a hash table
  1262. /$DELIMITER_LIST/, $self->setting ('TOOL_LOCAL'),
  1263. );
  1264. # Export required tools
  1265. my $count = 0;
  1266. my $return = '';
  1267. for my $name (sort keys %$tool) {
  1268. # Ignore local tools
  1269. next if exists $localtool{(split (/__/, $name))[0]};
  1270. if ($name =~ /^\w+$/) {
  1271. # Tools with normal name, just export it as an environment variable
  1272. $return .= 'export ' . $name . ' = ' . $tool->{$name} . "\n";
  1273. } else {
  1274. # Tools with unusual characters, export using a label/value pair
  1275. $return .= 'export FCM_UNUSUAL_TOOL_LABEL' . $count . ' = ' . $name . "\n";
  1276. $return .= 'export FCM_UNUSUAL_TOOL_VALUE' . $count . ' = ' .
  1277. $tool->{$name} . "\n";
  1278. $count++;
  1279. }
  1280. }
  1281. $return .= "\n";
  1282. return $return;
  1283. }
  1284. # ------------------------------------------------------------------------------
  1285. # SYNOPSIS
  1286. # $string = $self->_write_makefile_vpath ();
  1287. #
  1288. # DESCRIPTION
  1289. # This method returns a makefile $string for defining vpath directives.
  1290. # ------------------------------------------------------------------------------
  1291. sub _write_makefile_vpath {
  1292. my $self = shift();
  1293. my $FMT = 'vpath %%%s $(FCM_%sPATH)';
  1294. my %SETTING_OF = %{$self->setting('BLD_VPATH')};
  1295. my %EXT_OF = %{$self->setting('OUTFILE_EXT')};
  1296. # Note: each setting can be either an empty string or a comma-separated list
  1297. # of output file extension keys.
  1298. join(
  1299. "\n",
  1300. (
  1301. map
  1302. {
  1303. my $key = $_;
  1304. my @types = split(qr{$DELIMITER_LIST}msx, $SETTING_OF{$key});
  1305. @types ? (map {sprintf($FMT, $EXT_OF{$_}, $key)} sort @types)
  1306. : sprintf($FMT, q{}, $key)
  1307. ;
  1308. }
  1309. sort keys(%SETTING_OF)
  1310. ),
  1311. ) . "\n\n";
  1312. }
  1313. # Wraps chdir. Returns the old working directory.
  1314. sub _chdir {
  1315. my ($self, $path) = @_;
  1316. if ($self->verbose() >= 3) {
  1317. printf("cd %s\n", $path);
  1318. }
  1319. my $old_cwd = cwd();
  1320. chdir($path) || croak(sprintf("%s: cannot change directory ($!)\n", $path));
  1321. $old_cwd;
  1322. }
  1323. # ------------------------------------------------------------------------------
  1324. 1;
  1325. __END__