| 7318 |
dpurdie |
1 |
########################################################################
|
|
|
2 |
# COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED.
|
|
|
3 |
#
|
|
|
4 |
# Module name : JatsSignatureBuilder.pm
|
|
|
5 |
# Module type : JATS Utility
|
|
|
6 |
# Compiler(s) : Perl
|
|
|
7 |
# Environment(s): jats
|
|
|
8 |
#
|
|
|
9 |
# Description : Generate a PAckage signature
|
|
|
10 |
#
|
|
|
11 |
#
|
|
|
12 |
#......................................................................#
|
|
|
13 |
|
|
|
14 |
require 5.008_002;
|
|
|
15 |
use strict;
|
|
|
16 |
use warnings;
|
|
|
17 |
|
|
|
18 |
package JatsSignatureBuilder;
|
|
|
19 |
|
|
|
20 |
use JatsError;
|
|
|
21 |
use FileUtils;
|
|
|
22 |
use JatsVersionUtils;
|
|
|
23 |
use JatsEnv;
|
|
|
24 |
use JatsSystem;
|
|
|
25 |
use ArrayHashUtils;
|
|
|
26 |
use BuildName;
|
|
|
27 |
|
|
|
28 |
use Digest::SHA::PurePerl qw(sha1);
|
|
|
29 |
use IPC::Open3;
|
|
|
30 |
use File::Path;
|
|
|
31 |
|
|
|
32 |
#-------------------------------------------------------------------------------
|
|
|
33 |
# Function : GeneratePackageSignature
|
|
|
34 |
#
|
|
|
35 |
# Description : Generate a package 'signature' for this package
|
|
|
36 |
#
|
|
|
37 |
# The signature is used to bypass the entire Make processing in a sandbox
|
|
|
38 |
# If we can find a matching package in the package store then we don't
|
|
|
39 |
# need to 'make' this package.
|
|
|
40 |
#
|
|
|
41 |
# There are two scenarios:
|
|
|
42 |
# In a GIT enabled sandbox
|
|
|
43 |
# Without GIT
|
|
|
44 |
#
|
|
|
45 |
# In a GIT enabled sandbox the signature allows the use of a pre-built
|
|
|
46 |
# package - even if the package has been built on a different branch.
|
|
|
47 |
#
|
|
|
48 |
# The signature includes:
|
|
|
49 |
# The name of this package
|
|
|
50 |
# The GIT sha1 of the directory trees that contain this package
|
|
|
51 |
# The package signatures of all dependent packages
|
|
|
52 |
#
|
|
|
53 |
# In a Non-GIT enabled sandbox the package signature will be set such that
|
|
|
54 |
# the package will never be found in the package store and the package MUST
|
|
|
55 |
# be built within the sandbox.
|
|
|
56 |
#
|
|
|
57 |
# The hard part is determing the directory trees that contains this package
|
|
|
58 |
# Ideally this is a single dir-tree, but this cannot be enforced.
|
|
|
59 |
#
|
|
|
60 |
# Source directories have been gathered during makefile generation
|
|
|
61 |
#
|
|
|
62 |
# This suits most cases, but there are a few where the user needs
|
|
|
63 |
# to give JATS a hint. Use the AsdSrcDir directive to extend
|
|
|
64 |
# the signature paths to directories not under the build.pl
|
|
|
65 |
# or any makefile included by the build.pl
|
|
|
66 |
#
|
|
|
67 |
# The generated file will be held in the sandbox directory.
|
|
|
68 |
#
|
| 7319 |
dpurdie |
69 |
# Inputs : $pkgBase - Path to the package's build file
|
|
|
70 |
# $outPath - Base of path to create signature files
|
| 7318 |
dpurdie |
71 |
#
|
|
|
72 |
# Returns : The package signature
|
|
|
73 |
#
|
| 7319 |
dpurdie |
74 |
sub GeneratePackageSignature
|
| 7318 |
dpurdie |
75 |
{
|
| 7319 |
dpurdie |
76 |
my ($pkgBase, $outPath) = @_;
|
| 7318 |
dpurdie |
77 |
my %sigExcludeDirs;
|
|
|
78 |
my %sigExcludeFiles;
|
|
|
79 |
my $BuildSignatureSha1;
|
|
|
80 |
my $BuildSignature;
|
|
|
81 |
my @sigList;
|
|
|
82 |
my $sigText;
|
|
|
83 |
|
| 7319 |
dpurdie |
84 |
Error ("No directory specified") unless $pkgBase;
|
| 7320 |
dpurdie |
85 |
Debug("Build Directory: $pkgBase");
|
| 7319 |
dpurdie |
86 |
Error ("Not a directory: $pkgBase") unless -d $pkgBase;
|
| 7318 |
dpurdie |
87 |
|
| 7319 |
dpurdie |
88 |
my $parsedInfo = JatsParser::processBuild ($pkgBase);
|
|
|
89 |
#DebugDumpData("GeneratePackageSignature::parsedInfo", $parsedInfo);
|
|
|
90 |
Error ('BuildName not found') unless exists $parsedInfo->{BuildName};
|
|
|
91 |
|
| 7318 |
dpurdie |
92 |
#
|
|
|
93 |
# Determine the saved locations for the output files
|
|
|
94 |
#
|
|
|
95 |
mkpath ( $outPath ) unless -d $outPath;
|
|
|
96 |
my $signatureFile = CatPaths($outPath, 'Package.sig');
|
|
|
97 |
my $sigDebugFile = CatPaths($outPath, 'Package.dsig');
|
|
|
98 |
|
|
|
99 |
#
|
|
|
100 |
# Determine if this is a GIT enabled sandbox build
|
|
|
101 |
# Need a .git directory or file in the root of the sandbox
|
|
|
102 |
#
|
|
|
103 |
my $gitEnabled;
|
|
|
104 |
if ($::GBE_SANDBOX && -e CatPaths ($::GBE_SANDBOX, '.git') ) {
|
|
|
105 |
$gitEnabled = 1;
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
#
|
|
|
109 |
# Start generating the signature
|
|
|
110 |
# Include the package Name, Version and Project
|
|
|
111 |
#
|
|
|
112 |
$BuildSignatureSha1 = Digest::SHA::PurePerl->new;
|
|
|
113 |
$sigText = "PKGNAME: " . join (' ', @{$parsedInfo->{BuildName}} );
|
|
|
114 |
|
|
|
115 |
$BuildSignatureSha1->add( $sigText );
|
|
|
116 |
push @sigList, $sigText . ": " . $BuildSignatureSha1->clone->hexdigest;
|
|
|
117 |
|
|
|
118 |
#
|
|
|
119 |
# Include the signature of ALL dependent packages
|
|
|
120 |
# Ie: The package signature is a function of the source and its dependents
|
|
|
121 |
# Assume that we are starting with a sorted list
|
|
|
122 |
#
|
|
|
123 |
foreach my $tag ( @{$parsedInfo->{PkgList}} )
|
|
|
124 |
{
|
|
|
125 |
my ($pname, $pversion) = split ($;, $tag);
|
| 7319 |
dpurdie |
126 |
my $pkgSig = getPackageSignature($pname, $pversion);
|
| 7318 |
dpurdie |
127 |
$BuildSignatureSha1->add("PKGSIGNATURE: $pkgSig");
|
|
|
128 |
push @sigList, sprintf("PKGSIGNATURE: [%s %s] %s: %s", $pname, $pversion, $pkgSig , $BuildSignatureSha1->clone->hexdigest);
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
if ($gitEnabled)
|
|
|
132 |
{
|
|
|
133 |
#
|
|
|
134 |
# Include the sha1 of all 'git' tree items that form the complete source image
|
|
|
135 |
# Warn user if not all components are version controlled
|
|
|
136 |
#
|
|
|
137 |
my @relDirList = @{$parsedInfo->{DirList}};
|
|
|
138 |
my @cmdList = map { 'HEAD:' . $_ . '/' } @relDirList;
|
|
|
139 |
Debug3(" GIT CMD: " . "git rev-parse", @cmdList );
|
|
|
140 |
#DebugDumpData("parsedInfo",$parsedInfo);
|
|
|
141 |
#
|
|
|
142 |
# Generate a 'nice' array of display paths used
|
|
|
143 |
# The display path will be used simply to report the location in the debug of the package signature
|
|
|
144 |
# The display path is relative to the base of the sandbox
|
|
|
145 |
#
|
|
|
146 |
my @absDirList = map { RelPath(FullPath( $_ ),$::GBE_SANDBOX) } @relDirList;
|
|
|
147 |
|
|
|
148 |
#
|
|
|
149 |
# Callback function to process the output of the Git parse
|
|
|
150 |
# Expect one line for each HEAD: item
|
|
|
151 |
#
|
|
|
152 |
my $index = 0;
|
|
|
153 |
my @notControlled;
|
|
|
154 |
my $callback = sub {
|
|
|
155 |
my ($cdata, $gitShar) = @_;
|
|
|
156 |
$gitShar =~ s~\s+$~~;
|
|
|
157 |
Debug3(" GIT OUT: " . $gitShar );
|
|
|
158 |
if ($gitShar =~ m~^HEAD:(.*)~) {
|
|
|
159 |
push @notControlled, $1;
|
|
|
160 |
$gitShar = 'MSG: Not version controlled';
|
|
|
161 |
}
|
|
|
162 |
$BuildSignatureSha1->add($gitShar);
|
|
|
163 |
push @sigList, "PKGSRC: $absDirList[$index++]: $gitShar: " . $BuildSignatureSha1->clone->hexdigest;
|
|
|
164 |
return 0;
|
|
|
165 |
};
|
|
|
166 |
|
|
|
167 |
my $rv = GitCmd('rev-parse', @cmdList, { process => $callback } );
|
|
|
168 |
Debug2("GitCmd Result: $rv");
|
|
|
169 |
$BuildSignature = $BuildSignatureSha1->hexdigest;
|
|
|
170 |
|
|
|
171 |
if (@notControlled) {
|
|
|
172 |
Warning('The following paths are not version controlled:', @notControlled);
|
|
|
173 |
}
|
|
|
174 |
}
|
|
|
175 |
else
|
|
|
176 |
{
|
|
|
177 |
$BuildSignature = 'MSG: Sandbox is not git enabled';
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
Message("Signature: $BuildSignature");
|
|
|
181 |
push @sigList, "Signature: $BuildSignature";
|
|
|
182 |
FileCreate( $signatureFile, $BuildSignature );
|
|
|
183 |
FileCreate( $sigDebugFile, @sigList );
|
|
|
184 |
Debug0("sigDebugFile: $sigDebugFile");
|
|
|
185 |
|
|
|
186 |
return $BuildSignature;
|
|
|
187 |
}
|
|
|
188 |
|
|
|
189 |
#-------------------------------------------------------------------------------
|
| 7319 |
dpurdie |
190 |
# Function : getPackageSignature
|
|
|
191 |
#
|
|
|
192 |
# Description : Helper routine
|
|
|
193 |
# Given a package name and package version determine the package
|
|
|
194 |
# signature.
|
|
|
195 |
#
|
|
|
196 |
# Can used predetermined data or perform a package repo scan
|
|
|
197 |
#
|
|
|
198 |
# This version assumes that we are buildign within a jats sandbox
|
|
|
199 |
# Each packages signature file Package.sig is stored in the packages
|
|
|
200 |
# interface directory (at the moment).
|
|
|
201 |
#
|
|
|
202 |
# Process:
|
|
|
203 |
# Locate the packages interface directory - we have a link file to it
|
|
|
204 |
# Read in the PAckage Signature file
|
|
|
205 |
#
|
|
|
206 |
# Inputs : $pname - Package Name
|
|
|
207 |
# $pversion - Package Version
|
|
|
208 |
# $mode - Optional. true -> do not error if not found
|
|
|
209 |
#
|
|
|
210 |
# Returns : The package signature. Undefined if the package canot be found
|
|
|
211 |
#
|
|
|
212 |
sub getPackageSignature
|
|
|
213 |
{
|
|
|
214 |
my ($pname, $pversion, $mode ) = @_;
|
|
|
215 |
my $prj = '';
|
|
|
216 |
my $pkg;
|
|
|
217 |
my $version;
|
|
|
218 |
my $pkgSig;
|
|
|
219 |
|
|
|
220 |
#
|
|
|
221 |
# We are in a sandbox and expect to find a interface/Package.sig file
|
|
|
222 |
# This will allow us to locate the package in the package store
|
|
|
223 |
#
|
|
|
224 |
# If there is no interface/Package.sig, then the user must build (not make)
|
|
|
225 |
# the package in the sandbox.
|
|
|
226 |
#
|
|
|
227 |
# ie: the interface/Package.sig file allows us to use the package from package cache
|
|
|
228 |
# or indicates that the user has not yet built the package
|
|
|
229 |
#
|
|
|
230 |
# First locate the packages interface directory
|
|
|
231 |
# We have a nice link from the sandbox to assist in this
|
|
|
232 |
#
|
|
|
233 |
my ($pn, $pv, $ps ) = SplitPackage ($pname, $pversion );
|
|
|
234 |
$version = 'sandbox';
|
|
|
235 |
$prj = '.' . $ps if ( $ps );
|
|
|
236 |
$version .= $prj;
|
|
|
237 |
|
|
|
238 |
my $ifaceDir = CatPaths($::GBE_SANDBOX, 'sandbox_dpkg_archive', $pname, $version . '.int');
|
|
|
239 |
$ifaceDir = TagFileRead($ifaceDir);
|
|
|
240 |
$ifaceDir =~ s~\\~/~g;
|
|
|
241 |
$ifaceDir =~ s~GBE_SANDBOX/~$::GBE_SANDBOX/~;
|
|
|
242 |
my $pkgSigFile = CatPaths( $ifaceDir, 'Package.sig');
|
|
|
243 |
|
|
|
244 |
if ( -f $pkgSigFile)
|
|
|
245 |
{
|
|
|
246 |
#Debug0("$pname, $pversion --> $pkgSigFile");
|
|
|
247 |
$pkgSig = TagFileRead($pkgSigFile);
|
|
|
248 |
Error("Package signature invalid for $pname/$version", "Signature: $pkgSig")
|
|
|
249 |
if((length($pkgSig) != 40) && $pkgSig !~ m~^MSG:~) ;
|
|
|
250 |
}
|
|
|
251 |
else
|
|
|
252 |
{
|
|
|
253 |
Error("Package signature not found for $pname/$version", "You must 'build' the package before using it") unless $mode;
|
|
|
254 |
}
|
|
|
255 |
|
|
|
256 |
return $pkgSig;
|
|
|
257 |
}
|
|
|
258 |
|
|
|
259 |
#-------------------------------------------------------------------------------
|
| 7318 |
dpurdie |
260 |
# Function : GitCmd
|
|
|
261 |
#
|
|
|
262 |
# Description : Run a Git Command and capture/process the output
|
|
|
263 |
#
|
|
|
264 |
# Based on JatsSvnCore:SvnCmd
|
|
|
265 |
#
|
|
|
266 |
# Inputs : Command
|
|
|
267 |
# Command arguments
|
|
|
268 |
# Last argument may be a hash of options.
|
|
|
269 |
# nosavedata - Don't save the data
|
|
|
270 |
# process - Callback function
|
|
|
271 |
# printdata - Print data
|
|
|
272 |
# error - Error Message
|
|
|
273 |
# Used as first line of an Error call
|
|
|
274 |
#
|
|
|
275 |
# Returns : non-zero on errors detected
|
|
|
276 |
#
|
|
|
277 |
sub GitCmd
|
|
|
278 |
{
|
|
|
279 |
my $self; # Local storage
|
|
|
280 |
Debug ("GitCmd");
|
|
|
281 |
|
|
|
282 |
#
|
|
|
283 |
# Locate essential tools
|
|
|
284 |
#
|
|
|
285 |
our $GBE_SVN_PATH;
|
|
|
286 |
EnvImportOptional('GBE_GIT_PATH', '');
|
|
|
287 |
Debug ("GBE_GIT_PATH", $::GBE_GIT_PATH);
|
|
|
288 |
|
|
|
289 |
my $stdmux = LocateProgInPath ( 'stdmux');
|
|
|
290 |
my $git = LocateProgInPath ( 'git', '--All', '--Path=' . $::GBE_GIT_PATH );
|
|
|
291 |
|
|
|
292 |
#
|
|
|
293 |
# Extract arguments and options
|
|
|
294 |
# If last argument is a hash, then its a hash of options
|
|
|
295 |
#
|
|
|
296 |
my $opt;
|
|
|
297 |
$opt = pop @_
|
|
|
298 |
if (@_ > 0 and UNIVERSAL::isa($_[-1],'HASH'));
|
|
|
299 |
|
|
|
300 |
$self->{PRINTDATA} = $opt->{'printdata'} if ( exists $opt->{'printdata'} );
|
|
|
301 |
|
|
|
302 |
Verbose2 "GitCmd $git @_";
|
|
|
303 |
|
|
|
304 |
#
|
|
|
305 |
# Useful debugging
|
|
|
306 |
#
|
|
|
307 |
# $self->{LAST_CMD} = [$svn, @_];
|
|
|
308 |
|
|
|
309 |
#
|
|
|
310 |
# Reset command output data
|
|
|
311 |
#
|
|
|
312 |
$self->{ERROR_LIST} = [];
|
|
|
313 |
$self->{RESULT_LIST} = [];
|
|
|
314 |
# $self->{LAST_CMD} = \@_;
|
|
|
315 |
|
|
|
316 |
#
|
|
|
317 |
# Make use of a wrapper program to mux the STDERR and STDOUT into
|
|
|
318 |
# one stream (STDOUT). # This solves a lot of problems
|
|
|
319 |
#
|
|
|
320 |
# Do not use IO redirection of STDERR because as this will cause a
|
|
|
321 |
# shell (sh or cmd.exe) to be invoked and this makes it much
|
|
|
322 |
# harder to kill on all platforms.
|
|
|
323 |
#
|
|
|
324 |
# Use open3 as it allows the arguments to be passed
|
|
|
325 |
# directly without escaping and without any shell in the way
|
|
|
326 |
#
|
|
|
327 |
local (*CHLD_OUT, *CHLD_IN);
|
|
|
328 |
my $pid = open3( \*CHLD_IN, \*CHLD_OUT, '>&STDERR', $stdmux, $git, @_);
|
|
|
329 |
|
|
|
330 |
#
|
|
|
331 |
# Looks as though we always get a PID - even if the process dies
|
|
|
332 |
# straight away or can't be found. I suspect that open3 doesn't set
|
|
|
333 |
# $! anyway. I know it doesn't set $?
|
|
|
334 |
#
|
|
|
335 |
Debug ("Pid: $pid");
|
|
|
336 |
Error ("Can't run command: $!") unless $pid;
|
|
|
337 |
|
|
|
338 |
#
|
|
|
339 |
# Close the input handle
|
|
|
340 |
# We don't have anything to send to this program
|
|
|
341 |
#
|
|
|
342 |
close(CHLD_IN);
|
|
|
343 |
|
|
|
344 |
#
|
|
|
345 |
# Monitor the output from the utility
|
|
|
346 |
# Have used stdmux to multiplex stdout and stderr
|
|
|
347 |
#
|
|
|
348 |
# Note: IO::Select doesn't work on Windows :(
|
|
|
349 |
# Note: Open3 will cause blocking unless both streams are read
|
|
|
350 |
# Can't read both streams because IO::Select doesn't work
|
|
|
351 |
#
|
|
|
352 |
# Observation:
|
|
|
353 |
# svn puts errors to STDERR
|
|
|
354 |
# svn puts status to STDOUT
|
|
|
355 |
#
|
|
|
356 |
while (<CHLD_OUT>)
|
|
|
357 |
{
|
|
|
358 |
s~\s+$~~;
|
|
|
359 |
tr~\\/~/~;
|
|
|
360 |
|
|
|
361 |
|
|
|
362 |
Verbose3 ( "GitCmd:" . $_);
|
|
|
363 |
m~^STD(...):(.+)~;
|
|
|
364 |
my $data = $1 ? $2 : $_;
|
|
|
365 |
next unless ( $data );
|
|
|
366 |
|
|
|
367 |
if ( $1 && $1 eq 'ERR' )
|
|
|
368 |
{
|
|
|
369 |
#
|
|
|
370 |
# Process STDERR output
|
|
|
371 |
#
|
|
|
372 |
push @{$self->{ERROR_LIST}}, $data;
|
|
|
373 |
}
|
|
|
374 |
else
|
|
|
375 |
{
|
|
|
376 |
#
|
|
|
377 |
# Process STDOUT data
|
|
|
378 |
#
|
|
|
379 |
push @{$self->{RESULT_LIST}}, $data unless ($opt->{'nosavedata'});
|
|
|
380 |
|
|
|
381 |
#
|
|
|
382 |
# If the user has specified a processing function then pass each
|
|
|
383 |
# line to the specified function. A non-zero return will
|
|
|
384 |
# be taken as a signal to kill the command.
|
|
|
385 |
#
|
|
|
386 |
if ( exists ($opt->{'process'}) && $opt->{'process'}($self, $data) )
|
|
|
387 |
{
|
|
|
388 |
kill 9, $pid;
|
|
|
389 |
sleep(1);
|
|
|
390 |
last;
|
|
|
391 |
}
|
|
|
392 |
}
|
|
|
393 |
}
|
|
|
394 |
|
|
|
395 |
close(CHLD_OUT);
|
|
|
396 |
|
|
|
397 |
#
|
|
|
398 |
# MUST wait for the process
|
|
|
399 |
# Under Windows if this is not done then we eventually fill up some
|
|
|
400 |
# perl-internal structure and can't spawn anymore processes.
|
|
|
401 |
#
|
|
|
402 |
my $rv = waitpid ( $pid, 0);
|
|
|
403 |
|
|
|
404 |
#
|
|
|
405 |
# If an error condition was detected and the user has provided
|
|
|
406 |
# an error message, then display the error
|
|
|
407 |
#
|
|
|
408 |
# This simplifies the user error processing
|
|
|
409 |
#
|
|
|
410 |
if ( @{$self->{ERROR_LIST}} && $opt->{'error'} )
|
|
|
411 |
{
|
|
|
412 |
Error ( $opt->{'error'}, @{$self->{ERROR_LIST}} );
|
|
|
413 |
}
|
|
|
414 |
|
|
|
415 |
#
|
|
|
416 |
# Exit status has no meaning since open3 has been used
|
|
|
417 |
# This is because perl does not treat the opened process as a child
|
|
|
418 |
# Not too sure it makes any difference anyway
|
|
|
419 |
#
|
|
|
420 |
#
|
|
|
421 |
Debug ("Useless Exit Status: $rv");
|
|
|
422 |
my $result = @{$self->{ERROR_LIST}} ? 1 : 0;
|
|
|
423 |
Verbose3 ("Exit Code: $result");
|
|
|
424 |
|
|
|
425 |
return $result;
|
|
|
426 |
}
|
|
|
427 |
|
|
|
428 |
|
|
|
429 |
###############################################################################
|
|
|
430 |
# Internal Package
|
|
|
431 |
# Primarily to hide the use of the AUTOLOAD
|
|
|
432 |
# Which still doesn't behave as expected
|
|
|
433 |
# Have trouble with $self in AUTOLOAD. Its not appearig as an argument.
|
|
|
434 |
#
|
|
|
435 |
package JatsParser;
|
|
|
436 |
use strict;
|
|
|
437 |
use warnings;
|
|
|
438 |
|
|
|
439 |
my $currentClass;
|
|
|
440 |
our $ProjectBase;
|
|
|
441 |
our $ScmRoot;
|
|
|
442 |
|
|
|
443 |
#-------------------------------------------------------------------------------
|
|
|
444 |
# Function : JatsParser::processBuild
|
|
|
445 |
#
|
|
|
446 |
# Description : Process the build.pl file and associated makefile.pl's
|
|
|
447 |
# A static-ish method to do all of the hard work.
|
|
|
448 |
#
|
|
|
449 |
# Inputs : $buildPath - Path to the build file
|
|
|
450 |
#
|
|
|
451 |
# Returns : A few globals
|
|
|
452 |
#
|
|
|
453 |
sub processBuild
|
|
|
454 |
{
|
|
|
455 |
my ($baseDir) = @_;
|
|
|
456 |
my @AllSubDirs;
|
|
|
457 |
|
|
|
458 |
#
|
|
|
459 |
# Process the build.pl file
|
|
|
460 |
#
|
|
|
461 |
my $filename = ::CatPaths($baseDir, 'build.pl');
|
|
|
462 |
::Error ("Build file not found : $filename") unless -f $filename;
|
|
|
463 |
$baseDir = ::RelPath(::FullPath ($baseDir));
|
|
|
464 |
my $buildParser = newJatsParser();
|
|
|
465 |
$buildParser->parseFile($baseDir , 'build.pl');
|
|
|
466 |
|
|
|
467 |
#DebugDumpData("parser", $parser);
|
|
|
468 |
|
|
|
469 |
#
|
|
|
470 |
# If no source subdirs where specified in the build file then insert the default one
|
|
|
471 |
# This is the same action as perform by jats build
|
|
|
472 |
#
|
|
|
473 |
if ( ! defined $buildParser->{SubDirs}) {
|
|
|
474 |
push @{$buildParser->{SubDirs}}, ::CatPaths($baseDir, 'src');
|
|
|
475 |
}
|
|
|
476 |
|
|
|
477 |
#
|
|
|
478 |
# If the 'common' makefile exists then parse it as well
|
|
|
479 |
#
|
|
|
480 |
my $commonMakefile = ::CatPaths($baseDir, 'makefile.pl');
|
|
|
481 |
unless ( -f $commonMakefile) {
|
|
|
482 |
$commonMakefile = undef;
|
|
|
483 |
}
|
|
|
484 |
|
|
|
485 |
#
|
|
|
486 |
# Add the build path to the list of known subdirectories
|
|
|
487 |
#
|
|
|
488 |
@AllSubDirs = $baseDir;
|
|
|
489 |
|
|
|
490 |
#
|
|
|
491 |
# Process all subdirs
|
|
|
492 |
# Order is not important - in this case
|
|
|
493 |
#
|
|
|
494 |
my @SubDirs = @{$buildParser->{SubDirs}};
|
|
|
495 |
my $parser = newJatsParser($baseDir, $commonMakefile);
|
|
|
496 |
while (@SubDirs)
|
|
|
497 |
{
|
|
|
498 |
my $makeDir = ::CleanDirName(pop @SubDirs);
|
|
|
499 |
|
|
|
500 |
@{$parser->{SubDirs}} = ();
|
|
|
501 |
$parser->parseFile($makeDir, 'makefile.pl');
|
|
|
502 |
|
|
|
503 |
push @SubDirs, @{$parser->{SubDirs}} if (defined $parser->{SubDirs});
|
|
|
504 |
::UniquePush (\@AllSubDirs, $makeDir);
|
|
|
505 |
}
|
|
|
506 |
|
|
|
507 |
my @AllInclude = @{$parser->{Includes}} if defined $parser->{Includes};
|
|
|
508 |
|
|
|
509 |
#
|
|
|
510 |
# Generate a list of root directories used by the package
|
|
|
511 |
# ie: want top level directories only and not subdirectories
|
|
|
512 |
#
|
|
|
513 |
my @PackageDirs = generateMinDirList(@AllSubDirs, @AllInclude);
|
|
|
514 |
|
|
|
515 |
#
|
|
|
516 |
# Generate a list of all the external packages
|
|
|
517 |
# Don't sort the list. Order may be important
|
|
|
518 |
#
|
|
|
519 |
my @AllPackages = ();
|
|
|
520 |
push @AllPackages, @{$buildParser->{PkgList}} if (defined $buildParser->{PkgList});
|
|
|
521 |
|
|
|
522 |
#
|
|
|
523 |
# Prepare a structure to be returned
|
|
|
524 |
#
|
|
|
525 |
my $data;
|
|
|
526 |
$data->{BuildName} = $buildParser->{BuildName};
|
|
|
527 |
$data->{BaseDir} = $baseDir;
|
|
|
528 |
$data->{PkgList} = \@AllPackages;
|
|
|
529 |
$data->{DirList} = \@PackageDirs;
|
|
|
530 |
return $data;
|
|
|
531 |
}
|
|
|
532 |
|
|
|
533 |
#-------------------------------------------------------------------------------
|
|
|
534 |
# Function : generateMinDirList
|
|
|
535 |
#
|
|
|
536 |
# Description : Generate a list of root directories used by the package
|
|
|
537 |
# ie: want top level directories onyl and not subdirectories
|
|
|
538 |
#
|
|
|
539 |
#
|
|
|
540 |
# Inputs : A list of paths to process
|
|
|
541 |
#
|
|
|
542 |
# Returns : A list of processed paths
|
|
|
543 |
#
|
|
|
544 |
sub generateMinDirList
|
|
|
545 |
{
|
|
|
546 |
#
|
|
|
547 |
# Convert all to absolute paths
|
|
|
548 |
#
|
|
|
549 |
my @baseList;
|
|
|
550 |
foreach (@_) {
|
|
|
551 |
push @baseList, ::FullPath($_);
|
|
|
552 |
}
|
|
|
553 |
|
|
|
554 |
|
|
|
555 |
# Process the complete list to remove subdirectories
|
|
|
556 |
# Process is:
|
|
|
557 |
# Sort list. Will end up with shortest directories first, thus subdirs will follow parents
|
|
|
558 |
# Insert each item into a new list iff it is not a subdir of something already in the list
|
|
|
559 |
#
|
|
|
560 |
my @dirList = sort {uc($a) cmp uc($b)} @baseList;
|
|
|
561 |
|
|
|
562 |
my @newlist;
|
|
|
563 |
foreach my $newItem ( @dirList ) {
|
|
|
564 |
my $match = 0;
|
|
|
565 |
foreach my $item ( @newlist ) {
|
|
|
566 |
if (index ($newItem, $item) == 0) {
|
|
|
567 |
$match = 1;
|
|
|
568 |
last;
|
|
|
569 |
}
|
|
|
570 |
}
|
|
|
571 |
push @newlist, $newItem if (! $match);
|
|
|
572 |
}
|
|
|
573 |
|
|
|
574 |
#
|
|
|
575 |
# Convert back to relative paths
|
|
|
576 |
#
|
|
|
577 |
@baseList = ();
|
|
|
578 |
foreach ( @newlist ) {
|
|
|
579 |
push @baseList, ::RelPath($_);
|
|
|
580 |
}
|
|
|
581 |
|
|
|
582 |
return @baseList;
|
|
|
583 |
}
|
|
|
584 |
|
|
|
585 |
#-------------------------------------------------------------------------------
|
|
|
586 |
# Function : AUTOLOAD
|
|
|
587 |
#
|
|
|
588 |
# Description : Intercept and process user directives
|
|
|
589 |
# It does not attempt to distinguish between user errors and
|
|
|
590 |
# programming errors. It assumes that the program has been
|
|
|
591 |
# tested.
|
|
|
592 |
#
|
|
|
593 |
# Inputs : Original function arguments (captured)
|
|
|
594 |
#
|
|
|
595 |
#
|
|
|
596 |
our $AUTOLOAD;
|
|
|
597 |
sub AUTOLOAD
|
|
|
598 |
{
|
|
|
599 |
#
|
|
|
600 |
# Don't respond to class destruction
|
|
|
601 |
#
|
|
|
602 |
return if our $AUTOLOAD =~ /::DESTROY$/;
|
|
|
603 |
|
|
|
604 |
my $self = $currentClass;
|
|
|
605 |
my $type = ref ($self) || ::Error("$self is not an object");
|
|
|
606 |
|
|
|
607 |
my $args = ::JatsError::ArgsToString( \@_);
|
|
|
608 |
my $fname = $AUTOLOAD;
|
|
|
609 |
$fname =~ s~^\w+::~~;
|
|
|
610 |
my ($package, $filename, $line) = caller;
|
|
|
611 |
|
|
|
612 |
#
|
|
|
613 |
# If directive is inlined
|
|
|
614 |
# Replace it with the raw text of the directive
|
|
|
615 |
# Really only for display purposes
|
|
|
616 |
#
|
|
|
617 |
if ($fname eq 'If')
|
|
|
618 |
{
|
|
|
619 |
return $fname . '(' . join( ',', map { qq/"$_"/ } @_ ) . ')' ;
|
|
|
620 |
}
|
|
|
621 |
|
|
|
622 |
#
|
|
|
623 |
# Capture and process some directives
|
|
|
624 |
#
|
|
|
625 |
my %directives = ( AddIncDir => 1,
|
|
|
626 |
AddSrcDir => 1,
|
|
|
627 |
AddDir => 1,
|
|
|
628 |
AddLibDir => 1,
|
|
|
629 |
|
|
|
630 |
LinkPkgArchive => 2,
|
|
|
631 |
BuildPkgArchive => 2,
|
|
|
632 |
|
|
|
633 |
BuildName => 3,
|
|
|
634 |
|
|
|
635 |
SetProjectBase => 4,
|
|
|
636 |
|
|
|
637 |
SubDir => 5,
|
|
|
638 |
BuildSubDir => 5,
|
|
|
639 |
|
|
|
640 |
);
|
|
|
641 |
|
|
|
642 |
if ($directives{$fname})
|
|
|
643 |
{
|
|
|
644 |
# ::Debug0 ("Directive: $fname( $args );", "File: $filename, Line: $line, Mode: $directives{$fname}" );
|
|
|
645 |
|
|
|
646 |
# AddIncDir
|
|
|
647 |
# AddSrcDir
|
|
|
648 |
# AddLibDir
|
|
|
649 |
# AddDir
|
|
|
650 |
# Directives that specify directories that extend paths
|
|
|
651 |
#
|
|
|
652 |
if ($directives{$fname} == 1)
|
|
|
653 |
{
|
|
|
654 |
for (my $ii = 1; $ii < scalar @_; $ii++)
|
|
|
655 |
{
|
|
|
656 |
my $arg = $_[$ii];
|
|
|
657 |
next if ( $arg =~ m~^--~);
|
|
|
658 |
#::Debug0("Processing: $arg");
|
|
|
659 |
#::DebugDumpData("Self", $self);
|
|
|
660 |
#
|
|
|
661 |
# Skip if the path looks like it conatins keywords
|
|
|
662 |
# interface and local
|
|
|
663 |
#
|
|
|
664 |
if ($arg =~ m~/interface/~ || $arg =~ m~/local/~ ) {
|
|
|
665 |
$arg = '.'
|
|
|
666 |
}
|
|
|
667 |
|
|
|
668 |
my $dirtyPath = $arg;
|
|
|
669 |
$dirtyPath = join( '/', $self->{baseDir}, $arg) unless ($arg =~ m~^/~ || $arg =~ m~^\w:~) ;
|
|
|
670 |
#::Debug0("DirtyPath: $dirtyPath");
|
|
|
671 |
my $path = ::CleanPath($dirtyPath );
|
|
|
672 |
#::Debug0("CleanPath: $path");
|
|
|
673 |
::UniquePush (\@{$self->{Includes}}, $path);
|
|
|
674 |
::Error ("Included directory does not exist: $path") unless -d $path;
|
|
|
675 |
}
|
|
|
676 |
}
|
|
|
677 |
|
|
|
678 |
#
|
|
|
679 |
# LinkPkgArchive
|
|
|
680 |
# BuildPkgArchive
|
|
|
681 |
# Directives that define external packages
|
|
|
682 |
#
|
|
|
683 |
if ($directives{$fname} == 2) {
|
|
|
684 |
push @{$self->{PkgList}}, join($;, @_);
|
|
|
685 |
}
|
|
|
686 |
|
|
|
687 |
#
|
|
|
688 |
# BuildName
|
|
|
689 |
# Directive that specifies the Build Name
|
|
|
690 |
# Format into name, version, suffix
|
|
|
691 |
#
|
|
|
692 |
if ($directives{$fname} == 3) {
|
|
|
693 |
my $build_info = BuildName::parseBuildName( @_ );
|
|
|
694 |
$build_info->{BUILDNAME_PROJECT} = $build_info->{BUILDNAME_PROJECT} ? '.' . $build_info->{BUILDNAME_PROJECT} : '';
|
|
|
695 |
my @data = ($build_info->{BUILDNAME_PACKAGE}, $build_info->{BUILDNAME_VERSION}, $build_info->{BUILDNAME_PROJECT});
|
|
|
696 |
$self->{BuildName} = \@data;
|
|
|
697 |
}
|
|
|
698 |
|
|
|
699 |
#
|
|
|
700 |
# SetProjectBase
|
|
|
701 |
# Handle ProjectBase variable
|
|
|
702 |
# Only handle a subset as I want to deprecate this
|
|
|
703 |
# Handle ONLY one arg
|
|
|
704 |
# Must be either : --Up=nn, or a string ( ../.. );
|
|
|
705 |
#
|
|
|
706 |
if ($directives{$fname} == 4) {
|
|
|
707 |
if (scalar @_ > 1 ) {
|
|
|
708 |
:: Error ("Multiple arguments to SetProjectBase not supported");
|
|
|
709 |
}
|
|
|
710 |
|
|
|
711 |
my $dirString = $_[0];
|
|
|
712 |
if ($dirString =~ m~--Up=(\d+)~) {
|
|
|
713 |
my $count = $1;
|
|
|
714 |
$dirString = '/..' x $count;
|
|
|
715 |
}
|
|
|
716 |
my $newProjectBase = $self->{ProjectBase} . $dirString;
|
|
|
717 |
$newProjectBase = ::CleanPath($newProjectBase);
|
|
|
718 |
#::Debug0 ("SetProjectBase:" . $newProjectBase);
|
|
|
719 |
|
|
|
720 |
no strict;
|
|
|
721 |
no warnings 'all';
|
|
|
722 |
$ProjectBase = $newProjectBase;
|
|
|
723 |
#::Debug0 ("ProjectBase: $ProjectBase");
|
|
|
724 |
}
|
|
|
725 |
|
|
|
726 |
#
|
|
|
727 |
# SubDir
|
|
|
728 |
# BuildSubDir
|
|
|
729 |
# Directives that specify subdirectories to be included in the build
|
|
|
730 |
# Assume they are relative
|
|
|
731 |
#
|
|
|
732 |
if ($directives{$fname} == 5) {
|
|
|
733 |
foreach ( @_ ) {
|
|
|
734 |
push @{$self->{SubDirs}}, ::CatPaths($self->{baseDir}, $_ );
|
|
|
735 |
}
|
|
|
736 |
}
|
|
|
737 |
}
|
|
|
738 |
}
|
|
|
739 |
|
|
|
740 |
|
|
|
741 |
#-------------------------------------------------------------------------------
|
|
|
742 |
# Function : newJatsParser
|
|
|
743 |
#
|
|
|
744 |
# Description : New instance of a JatsParser object
|
|
|
745 |
#
|
|
|
746 |
# Inputs : $buildBase - Root of the build
|
|
|
747 |
# $commonFile - Common makefile to be prefixed to all
|
|
|
748 |
#
|
|
|
749 |
# Returns :
|
|
|
750 |
#
|
|
|
751 |
sub newJatsParser
|
|
|
752 |
{
|
|
|
753 |
my ($buildBase, $commonFile) = @_;
|
|
|
754 |
my $class = 'JatsParser';
|
|
|
755 |
my $self = {};
|
|
|
756 |
bless $self, $class;
|
|
|
757 |
|
|
|
758 |
#
|
|
|
759 |
# Init Data
|
|
|
760 |
#
|
|
|
761 |
$self->{baseDir} = '';
|
|
|
762 |
$self->{filename} = '';
|
|
|
763 |
$self->{SubDirs} = ();
|
|
|
764 |
$self->{PkgList} = ();
|
|
|
765 |
$self->{Includes} = ();
|
|
|
766 |
$self->{ScmRoot} = ::FullPath($buildBase) if defined $buildBase ;
|
|
|
767 |
$self->{ProjectBase} = $self->{ScmRoot};
|
|
|
768 |
$self->{Common} = $commonFile if defined $commonFile;
|
|
|
769 |
|
|
|
770 |
#
|
|
|
771 |
# Return class
|
|
|
772 |
#
|
|
|
773 |
return $self;
|
|
|
774 |
}
|
|
|
775 |
|
|
|
776 |
#-------------------------------------------------------------------------------
|
|
|
777 |
# Function : parseFile
|
|
|
778 |
#
|
|
|
779 |
# Description : Parse a build or makefile and return data
|
|
|
780 |
#
|
|
|
781 |
# Inputs : $baseDir - Base directory
|
|
|
782 |
# $filename - File to process
|
|
|
783 |
#
|
|
|
784 |
# Returns : stuff
|
|
|
785 |
#
|
|
|
786 |
sub parseFile
|
|
|
787 |
{
|
|
|
788 |
my ($self, $baseDir, $filename) = @_;
|
|
|
789 |
|
|
|
790 |
$currentClass = $self;
|
|
|
791 |
$self->{baseDir} = $baseDir;
|
|
|
792 |
$self->{filename} = $filename;
|
|
|
793 |
$filename = ::CatPaths($baseDir,$filename);
|
| 7320 |
dpurdie |
794 |
::Error("parseFile. File not found: $filename") unless -f $filename;
|
| 7318 |
dpurdie |
795 |
|
|
|
796 |
#
|
|
|
797 |
# Set Jats-global variables
|
|
|
798 |
# $ProjectBase
|
|
|
799 |
# $ScmRoot
|
|
|
800 |
#
|
|
|
801 |
no strict;
|
|
|
802 |
$ProjectBase = $self->{ProjectBase};
|
|
|
803 |
$ScmRoot = ::RelPath( $self->{ScmRoot}, ::FullPath($self->{baseDir}) ) if defined( $self->{ScmRoot});
|
|
|
804 |
|
|
|
805 |
local @ARGV;
|
|
|
806 |
$ARGV[1] = 'Dummy';
|
|
|
807 |
use strict;
|
|
|
808 |
|
|
|
809 |
#
|
|
|
810 |
# Create the code to be processed
|
|
|
811 |
# Join the common-makefile and the user-makefile
|
|
|
812 |
# Set Line numbers and filenames
|
|
|
813 |
#
|
|
|
814 |
my $commonCode = "#No Common Code\n";
|
|
|
815 |
$commonCode = slurpFile($self,$self->{Common}) if (exists $self->{Common});
|
|
|
816 |
my $code = slurpFile($self,$filename);
|
|
|
817 |
|
|
|
818 |
# ::Debug0("code:\n", $commonCode . $code);
|
|
|
819 |
|
|
|
820 |
#
|
|
|
821 |
# Evaluate the code
|
|
|
822 |
#
|
|
|
823 |
no strict;
|
|
|
824 |
no warnings 'all';
|
|
|
825 |
eval $commonCode . $code;
|
|
|
826 |
if ($@) {
|
|
|
827 |
::Error('Bad eval of Code:', $@);
|
|
|
828 |
::Debug0("Code", $code);
|
|
|
829 |
}
|
|
|
830 |
use strict;
|
|
|
831 |
use warnings;
|
|
|
832 |
|
|
|
833 |
# ::DebugDumpData("ParsedData", $self);
|
|
|
834 |
}
|
|
|
835 |
|
|
|
836 |
#-------------------------------------------------------------------------------
|
|
|
837 |
# Function : slurpFile
|
|
|
838 |
#
|
|
|
839 |
# Description : Read and entire (build / makefile ) into a string
|
|
|
840 |
# Clean it up a little bit
|
|
|
841 |
#
|
|
|
842 |
# Inputs : $file - File to process
|
|
|
843 |
#
|
|
|
844 |
# Returns : Entire file as a sinngle string
|
|
|
845 |
#
|
|
|
846 |
|
|
|
847 |
|
|
|
848 |
sub slurpFile
|
|
|
849 |
{
|
|
|
850 |
my ($self, $file) = @_;
|
|
|
851 |
local $/;
|
|
|
852 |
open my $fh, '<', $file or ::Error("Cannot open $file. $!" );
|
|
|
853 |
$/ = undef;
|
|
|
854 |
my $data = <$fh>;
|
|
|
855 |
close $fh;
|
|
|
856 |
|
|
|
857 |
#
|
|
|
858 |
# Remove ugly directives
|
|
|
859 |
# Messes with line numbers
|
|
|
860 |
#
|
|
|
861 |
$data =~ s~^\s*require.*~_JatsRequire();~gm;
|
|
|
862 |
$data =~ s~^\s*die.*~_JatsDie();~mg;
|
|
|
863 |
$data =~ s~^\s*unless.*~_JatsUnless();~mg;
|
|
|
864 |
|
|
|
865 |
$data =~ s~^\s*\$MAKELIB_PL\s+.*~_JatsDefine();~mg;
|
|
|
866 |
$data =~ s~^\s*\$BUILDLIB_PL\s+.*~_JatsDefine();~mg;
|
|
|
867 |
|
|
|
868 |
#
|
|
|
869 |
# Some old build files use some rubbish perl
|
|
|
870 |
#
|
|
|
871 |
# $data =~ s~^my~#my~mg;
|
|
|
872 |
|
|
|
873 |
#
|
|
|
874 |
# Put a nice header on the file for error reporting
|
|
|
875 |
#
|
|
|
876 |
my $absName = ::FullPath($file);
|
|
|
877 |
my $header = "#line 1 \"$absName\"\n" ;
|
|
|
878 |
|
|
|
879 |
return $header . $data;
|
|
|
880 |
}
|
|
|
881 |
1;
|