BuildTask.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. # ------------------------------------------------------------------------------
  2. # NAME
  3. # Fcm::BuildTask
  4. #
  5. # DESCRIPTION
  6. # This class hosts information of a build task in 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. package Fcm::BuildTask;
  14. @ISA = qw(Fcm::Base);
  15. # Standard pragma
  16. use strict;
  17. use warnings;
  18. # Standard modules
  19. use Carp;
  20. use File::Compare;
  21. use File::Copy;
  22. use File::Basename;
  23. use File::Path;
  24. use File::Spec::Functions;
  25. # FCM component modules
  26. use Fcm::Base;
  27. use Fcm::Timer;
  28. use Fcm::Util;
  29. # List of property methods for this class
  30. my @scalar_properties = (
  31. 'actiontype', # type of action
  32. 'dependency', # list of dependencies for this target
  33. 'srcfile', # reference to input Fcm::BuildSrc instance
  34. 'output', # output file
  35. 'outputmtime', # output file modification time
  36. 'target', # target name for this task
  37. 'targetpath', # search path for the target
  38. );
  39. # ------------------------------------------------------------------------------
  40. # SYNOPSIS
  41. # $obj = Fcm::BuildTask->new (%args);
  42. #
  43. # DESCRIPTION
  44. # This method constructs a new instance of the Fcm::BuildTask class. See
  45. # above for allowed list of properties. (KEYS should be in uppercase.)
  46. # ------------------------------------------------------------------------------
  47. sub new {
  48. my $this = shift;
  49. my %args = @_;
  50. my $class = ref $this || $this;
  51. my $self = Fcm::Base->new (%args);
  52. bless $self, $class;
  53. for my $name (@scalar_properties) {
  54. $self->{$name} = exists $args{uc ($name)} ? $args{uc ($name)} : undef;
  55. }
  56. return $self;
  57. }
  58. # ------------------------------------------------------------------------------
  59. # SYNOPSIS
  60. # $value = $obj->X;
  61. # $obj->X ($value);
  62. #
  63. # DESCRIPTION
  64. # Details of these properties are explained in @scalar_properties.
  65. # ------------------------------------------------------------------------------
  66. for my $name (@scalar_properties) {
  67. no strict 'refs';
  68. *$name = sub {
  69. my $self = shift;
  70. # Argument specified, set property to specified argument
  71. if (@_) {
  72. $self->{$name} = $_[0];
  73. if ($name eq 'output') {
  74. $self->{outputmtime} = $_[0] ? (stat $_[0]) [9] : undef;
  75. }
  76. }
  77. # Default value for property
  78. if (not defined $self->{$name}) {
  79. if ($name eq 'dependency' or $name eq 'targetpath') {
  80. # Reference to an array
  81. $self->{$name} = [];
  82. }
  83. }
  84. return $self->{$name};
  85. }
  86. }
  87. # ------------------------------------------------------------------------------
  88. # SYNOPSIS
  89. # $rc = $obj->action (TASKLIST => \%tasklist);
  90. #
  91. # DESCRIPTION
  92. # This method performs the task action and sets the output accordingly. The
  93. # argument TASKLIST must be a reference to a hash containing the other tasks
  94. # of the build, which this task may depend on. The keys of the hash must the
  95. # name of the target names of the tasks, and the values of the hash must be
  96. # the references to the corresponding Fcm::BuildTask instances. The method
  97. # returns true if the task has been performed to create a new version of the
  98. # target.
  99. # ------------------------------------------------------------------------------
  100. sub action {
  101. my $self = shift;
  102. my %args = @_;
  103. my $tasklist = exists $args{TASKLIST} ? $args{TASKLIST} : {};
  104. return unless $self->actiontype;
  105. my $uptodate = 1;
  106. my $dep_uptodate = 1;
  107. # Check if dependencies are up to date
  108. # ----------------------------------------------------------------------------
  109. for my $depend (@{ $self->dependency }) {
  110. if (exists $tasklist->{$depend}) {
  111. if (not $tasklist->{$depend}->output) {
  112. # Dependency task output is not set, performs its task action
  113. if ($tasklist->{$depend}->action (TASKLIST => $tasklist)) {
  114. $uptodate = 0;
  115. $dep_uptodate = 0;
  116. }
  117. }
  118. } elsif ($self->verbose > 1) {
  119. w_report 'Warning: Task for "', $depend,
  120. '" does not exist, may be required by ', $self->target;
  121. }
  122. }
  123. # Check if the target exists in the search path
  124. # ----------------------------------------------------------------------------
  125. if (@{ $self->targetpath }) {
  126. my $output = find_file_in_path ($self->target, $self->targetpath);
  127. $self->output ($output) if $output;
  128. }
  129. # Target is out of date if it does not exist
  130. if ($uptodate) {
  131. $uptodate = 0 if not $self->output;
  132. }
  133. # Check if current target is older than its dependencies
  134. # ----------------------------------------------------------------------------
  135. if ($uptodate) {
  136. for my $depend (@{ $self->dependency }) {
  137. next unless exists $tasklist->{$depend};
  138. if ($tasklist->{$depend}->outputmtime > $self->outputmtime) {
  139. $uptodate = 0;
  140. $dep_uptodate = 0;
  141. }
  142. }
  143. if ($uptodate and ref $self->srcfile) {
  144. $uptodate = 0 if $self->srcfile->mtime > $self->outputmtime;
  145. }
  146. }
  147. if ($uptodate) {
  148. # Current target and its dependencies are up to date
  149. # --------------------------------------------------------------------------
  150. if ($self->actiontype eq 'PP') {
  151. # "done" file up to date, set name of pre-processed source file
  152. # ------------------------------------------------------------------------
  153. my $base = $self->srcfile->root . lc ($self->srcfile->ext);
  154. my @pknames = split '__', (@{ $self->srcfile->pkgnames })[-2];
  155. my @path = map {
  156. catfile ($_, @pknames);
  157. } @{ $self->setting (qw/PATH PPSRC/) };
  158. my $oldfile = find_file_in_path ($base, \@path);
  159. $self->srcfile->ppsrc ($oldfile);
  160. }
  161. } else {
  162. # Perform action is not up to date
  163. # --------------------------------------------------------------------------
  164. # (For GENINTERFACE and PP, perform action if "done" file not up to date)
  165. my $new_output = @{ $self->targetpath }
  166. ? catfile ($self->targetpath->[0], $self->target)
  167. : $self->target;
  168. # Create destination container directory if necessary
  169. my $destdir = dirname $new_output;
  170. if (not -d $destdir) {
  171. print 'Make directory: ', $destdir, "\n" if $self->verbose > 2;
  172. mkpath $destdir;
  173. }
  174. # List of actions
  175. if ($self->actiontype eq 'UPDATE') {
  176. # Action is UPDATE: Update file
  177. # ------------------------------------------------------------------------
  178. print 'Update: ', $new_output, "\n" if $self->verbose > 2;
  179. touch_file $new_output
  180. or croak 'Unable to update "', $new_output, '", abort';
  181. $self->output ($new_output);
  182. } elsif ($self->actiontype eq 'COPY') {
  183. # Action is COPY: copy file to destination if necessary
  184. # ------------------------------------------------------------------------
  185. my $copy_required = ($dep_uptodate and $self->output and -r $self->output)
  186. ? compare ($self->output, $self->srcfile->src)
  187. : 1;
  188. if ($copy_required) {
  189. # Set up copy command
  190. my $srcfile = $self->srcfile->src;
  191. my $destfile = catfile ($destdir, basename($srcfile));
  192. print 'Copy: ', $srcfile, "\n", ' to: ', $destfile, "\n"
  193. if $self->verbose > 2;
  194. &copy ($srcfile, $destfile)
  195. or die $srcfile, ': copy to ', $destfile, ' failed (', $!, '), abort';
  196. chmod (((stat ($srcfile))[2] & 07777), $destfile);
  197. $self->output ($new_output);
  198. } else {
  199. $uptodate = 1;
  200. }
  201. } elsif ($self->actiontype eq 'PP' or $self->actiontype eq 'GENINTERFACE') {
  202. # Action is PP or GENINTERFACE: process file
  203. # ------------------------------------------------------------------------
  204. my ($newlines, $base, @path);
  205. if ($self->actiontype eq 'PP') {
  206. # Invoke the pre-processor on the source file
  207. # ----------------------------------------------------------------------
  208. # Get lines in the pre-processed source
  209. $newlines = $self->srcfile->get_pre_process;
  210. $base = $self->srcfile->root . lc ($self->srcfile->ext);
  211. # Get search path for the existing pre-processed file
  212. my @pknames = split '__', (@{ $self->srcfile->pkgnames })[-2];
  213. @path = map {
  214. catfile ($_, @pknames);
  215. } @{ $self->setting (qw/PATH PPSRC/) };
  216. } else { # if ($self->actiontype eq 'GENINTERFACE')
  217. # Invoke the interface generator
  218. # ----------------------------------------------------------------------
  219. # Get new interface lines
  220. $newlines = $self->srcfile->get_fortran_interface;
  221. # Get search path for the existing interface file
  222. $base = $self->srcfile->interfacebase;
  223. @path = @{ $self->setting (qw/PATH INC/) },
  224. }
  225. # If pre-processed or interface file exists,
  226. # compare its content with new lines to see if it has been updated
  227. my $update_required = 1;
  228. my $oldfile = find_file_in_path ($base, \@path);
  229. if ($oldfile and -r $oldfile) {
  230. # Read old file
  231. open FILE, '<', $oldfile;
  232. my @oldlines = readline 'FILE';
  233. close FILE;
  234. # Compare old contents and new contents
  235. if (@oldlines eq @$newlines) {
  236. $update_required = grep {
  237. $oldlines[$_] ne $newlines->[$_];
  238. } (0 .. $#oldlines);
  239. }
  240. }
  241. if ($update_required) {
  242. # Update the pre-processed source or interface file
  243. # ----------------------------------------------------------------------
  244. # Determine container directory of the pre-processed or interface file
  245. my $newfile = @path ? catfile ($path[0], $base) : $base;
  246. # Create the container directory if necessary
  247. if (not -d $path[0]) {
  248. print 'Make directory: ', $path[0], "\n"
  249. if $self->verbose > 1;
  250. mkpath $path[0];
  251. }
  252. # Update the pre-processor or interface file
  253. open FILE, '>', $newfile
  254. or croak 'Cannot write to "', $newfile, '" (', $!, '), abort';
  255. print FILE @$newlines;
  256. close FILE
  257. or croak 'Cannot write to "', $newfile, '" (', $!, '), abort';
  258. print 'Generated: ', $newfile, "\n" if $self->verbose > 1;
  259. # Set the name of the pre-processed file
  260. $self->srcfile->ppsrc ($newfile) if $self->actiontype eq 'PP';
  261. } else {
  262. # Content in pre-processed source or interface file is up to date
  263. # ----------------------------------------------------------------------
  264. $uptodate = 1;
  265. # Set the name of the pre-processed file
  266. $self->srcfile->ppsrc ($oldfile) if $self->actiontype eq 'PP';
  267. }
  268. # Update the "done" file
  269. print 'Update: ', $new_output, "\n" if $self->verbose > 2;
  270. touch_file $new_output
  271. or croak 'Unable to update "', $new_output, '", abort';
  272. $self->output ($new_output);
  273. } else {
  274. carp 'Action type "', $self->actiontype, "' not supported";
  275. }
  276. }
  277. return not $uptodate;
  278. }
  279. # ------------------------------------------------------------------------------
  280. 1;
  281. __END__