1#!/usr/bin/env perl 2 3#************************************************************** 4# 5# Licensed to the Apache Software Foundation (ASF) under one 6# or more contributor license agreements. See the NOTICE file 7# distributed with this work for additional information 8# regarding copyright ownership. The ASF licenses this file 9# to you under the Apache License, Version 2.0 (the 10# "License"); you may not use this file except in compliance 11# with the License. You may obtain a copy of the License at 12# 13# http://www.apache.org/licenses/LICENSE-2.0 14# 15# Unless required by applicable law or agreed to in writing, 16# software distributed under the License is distributed on an 17# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18# KIND, either express or implied. See the License for the 19# specific language governing permissions and limitations 20# under the License. 21# 22#************************************************************** 23 24=head1 NAME 25 26 download_external_libraries.pl - Load missing tarballs specified in main/external_libs.lst. 27 28=head1 SYNOPSIS 29 30 For downloading external libraries (typically from the main/bootstrap script): 31 32 download_external_libraries(<data-file-name>); 33 34=head1 DESCRIPTION 35 36 The contents of the main/external_libs.lst file are used to determine the 37 external library tarballs that are missing from ext_sources/. 38 39 Individual libraries can be ignored depending on the values of environment variables. 40 41 Format of the main/external_libs.lst file: 42 43 The file is line based. 44 Comments start with a # and go to the end of the line and are ignored. 45 Lines that are empty or contain only spaces and/or comments are ignored. 46 47 All other lines can have one of two forms: 48 - A variable definition of the form <name>=<value>. 49 - A conditional block start in the form "if (<expression>)" 50 51 Variables defined in a conditional block are only visible in this block and 52 replace the definition of global variables and variables earlier in the same 53 block. 54 Some variables have special names: 55 - MD5 is the expected MD5 checksum of the library tarball. 56 - SHA1 is the expected SHA1 checksum of the library tarball. 57 - URL1 to URL9 specify from where to download the tarball. The urls are tried in order. 58 The first successful download (download completed and checksum match) stops the iteration. 59 60 Expressions are explained below in the comment of EvaluateExpression(). 61 62 A library is only regarded if its conditional expression evaluates to 1. 63 64 Example: 65 66 DefaultSite=http://some-internet-site.org 67 if ( true ) 68 MD5 = 0123456789abcdef0123456789abcdef 69 name = library-1.0.tar.gz 70 URL1 = http://some-other-internet-site.org/another-name.tgz 71 URL2 = $(DefaultSite)$(MD5)-$(name) 72 73 This tries to load a library first from some-other-internet-site.org and if 74 that fails from some-internet-site.org. The library is stored as $(MD5)-$(name) 75 even when it is loaded as another-name.tgz. 76 77=cut 78 79 80use strict; 81 82use File::Spec; 83use File::Path; 84use File::Basename; 85use Digest::MD5; 86use Digest::SHA; 87use URI; 88use LWP::UserAgent; 89 90my $Debug = 1; 91 92my $LocalEnvironment = undef; 93my $GlobalEnvironment = {}; 94my @Missing = (); 95 96 97 98 99=head3 ProcessDataFile 100 101 Read the data file, typically named main/external_libs.lst, find the external 102 library tarballs that are not yet present in ext_sources/ and download them. 103 104=cut 105sub ProcessDataFile ($) 106{ 107 my $filename = shift; 108 109 my $destination = $ENV{'TARFILE_LOCATION'}; 110 111 die "can not open data file $filename" if ! -e $filename; 112 113 my $current_selector_value = 1; 114 my @URLHeads = (); 115 my @download_requests = (); 116 117 open my $in, $filename; 118 while (my $line = <$in>) 119 { 120 # Remove leading and trailing space and comments 121 $line =~ s/^\s+//; 122 $line =~ s/\s+$//; 123 $line =~ s/\s*#.*$//; 124 125 # Ignore empty lines. 126 next if $line eq ""; 127 128 # An "if" statement starts a new block. 129 if ($line =~ /^\s*if\s*\(\s*(.*?)\s*\)\s*$/) 130 { 131 ProcessLastBlock(); 132 133 $LocalEnvironment = { 'selector' => $1 }; 134 } 135 136 # Lines of the form name = value define a local variable. 137 elsif ($line =~ /^\s*(\S+)\s*=\s*(.*?)\s*$/) 138 { 139 if (defined $LocalEnvironment) 140 { 141 $LocalEnvironment->{$1} = $2; 142 } 143 else 144 { 145 $GlobalEnvironment->{$1} = $2; 146 } 147 } 148 else 149 { 150 die "can not parse line $line\n"; 151 } 152 } 153 154 ProcessLastBlock(); 155 156 Download(\@download_requests, \@URLHeads); 157} 158 159 160 161 162=head3 ProcessLastBlock 163 164 Process the last definition of an external library. 165 If there is not last block, true for the first "if" statement, then the call is ignored. 166 167=cut 168sub ProcessLastBlock () 169{ 170 # Return if no block is defined. 171 return if ! defined $LocalEnvironment; 172 173 # Ignore the block if the selector does not match. 174 if ( ! EvaluateExpression(SubstituteVariables($LocalEnvironment->{'selector'}))) 175 { 176 printf("ignoring %s because its prerequisites are not fulfilled\n", GetValue('name')); 177 } 178 else 179 { 180 my $name = GetValue('name'); 181 my $checksum = GetChecksum(); 182 183 if ( ! IsPresent($name, $checksum)) 184 { 185 AddDownloadRequest($name, $checksum); 186 } 187 } 188} 189 190 191 192 193=head3 AddDownloadRequest($name, $checksum) 194 195 Add a request for downloading the library $name to @Missing. 196 Collect all available URL[1-9] variables as source URLs. 197 198=cut 199sub AddDownloadRequest ($$) 200{ 201 my ($name, $checksum) = @_; 202 203 print "adding download request for $name\n"; 204 205 my $urls = []; 206 my $url = GetValue('URL'); 207 push @$urls, SubstituteVariables($url) if (defined $url); 208 for (my $i=1; $i<10; ++$i) 209 { 210 $url = GetValue('URL'.$i); 211 next if ! defined $url; 212 push @$urls, SubstituteVariables($url); 213 } 214 215 push @Missing, [$name, $checksum, $urls]; 216} 217 218 219 220 221=head3 GetChecksum() 222 223 When either MD5 or SHA1 are variables in the current scope then return 224 a reference to a hash with two entries: 225 'type' is either 'MD5' or 'SHA1', the type or algorithm of the checksum, 226 'value' is the actual checksum 227 Otherwise undef is returned. 228 229=cut 230sub GetChecksum() 231{ 232 my $checksum = GetValue("MD5"); 233 if (defined $checksum && $checksum ne "") 234 { 235 return { 'type' => 'MD5', 'value' => $checksum }; 236 } 237 elsif (defined ($checksum=GetValue("SHA1")) && $checksum ne "") 238 { 239 return { 'type' => 'SHA1', 'value' => $checksum }; 240 } 241 else 242 { 243 return undef; 244 } 245} 246 247 248 249 250=head3 GetValue($variable_name) 251 252 Return the value of the variable with name $variable_name from the local 253 environment or, if not defined there, the global environment. 254 255=cut 256sub GetValue ($) 257{ 258 my $variable_name = shift; 259 260 my $candidate = $LocalEnvironment->{$variable_name}; 261 return $candidate if defined $candidate; 262 263 return $GlobalEnvironment->{$variable_name}; 264} 265 266 267 268=head3 SubstituteVariables($text) 269 270 Replace all references to variables in $text with the respective variable values. 271 This is done repeatedly until no variable reference remains. 272 273=cut 274sub SubstituteVariables ($) 275{ 276 my $text = shift; 277 278 my $infinite_recursion_guard = 100; 279 while ($text =~ /^(.*?)\$\(([^)]+)\)(.*)$/) 280 { 281 my ($head,$name,$tail) = ($1,$2,$3); 282 my $value = GetValue($name); 283 die "can not evaluate variable $name" if ! defined $value; 284 $text = $head.$value.$tail; 285 286 die "(probably) detected an infinite recursion in variable definitions" if --$infinite_recursion_guard<=0; 287 } 288 289 return $text; 290} 291 292 293 294 295=head3 EvaluateExpression($expression) 296 297 Evaluate the $expression of an "if" statement to either 0 or 1. It can 298 be a single term (see EvaluateTerm for a description), or several terms 299 separated by either all ||s or &&s. A term can also be an expression 300 enclosed in parantheses. 301 302=cut 303sub EvaluateExpression ($) 304{ 305 my $expression = shift; 306 307 # Evaluate sub expressions enclosed in parantheses. 308 while ($expression =~ /^(.*)\(([^\(\)]+)\)(.*)$/) 309 { 310 $expression = $1 . (EvaluateExpression($2) ? " true " : " false ") . $3; 311 } 312 313 if ($expression =~ /&&/ && $expression =~ /\|\|/) 314 { 315 die "expression can contain either && or || but not both at the same time"; 316 } 317 elsif ($expression =~ /&&/) 318 { 319 foreach my $term (split (/\s*&&\s*/,$expression)) 320 { 321 return 0 if ! EvaluateTerm($term); 322 } 323 return 1; 324 } 325 elsif ($expression =~ /\|\|/) 326 { 327 foreach my $term (split (/\s*\|\|\s*/,$expression)) 328 { 329 return 1 if EvaluateTerm($term); 330 } 331 return 0; 332 } 333 else 334 { 335 return EvaluateTerm($expression); 336 } 337} 338 339 340 341 342=head3 EvaluateTerm($term) 343 344 Evaluate the $term to either 0 or 1. 345 A term is either the literal "true", which evaluates to 1, or an expression 346 of the form NAME=VALUE or NAME!=VALUE. NAME is the name of an environment 347 variable and VALUE any string. VALUE may be empty. 348 349=cut 350sub EvaluateTerm ($) 351{ 352 my $term = shift; 353 354 if ($term =~ /^\s*([a-zA-Z_0-9]+)\s*(==|!=)\s*(.*)\s*$/) 355 { 356 my ($variable_name, $operator, $given_value) = ($1,$2,$3); 357 my $variable_value = $ENV{$variable_name}; 358 $variable_value = "" if ! defined $variable_value; 359 360 if ($operator eq "==") 361 { 362 return $variable_value eq $given_value; 363 } 364 elsif ($operator eq "!=") 365 { 366 return $variable_value ne $given_value; 367 } 368 else 369 { 370 die "unknown operator in term $term"; 371 } 372 } 373 elsif ($term =~ /^\s*true\s*$/i) 374 { 375 return 1; 376 } 377 elsif ($term =~ /^\s*false\s*$/i) 378 { 379 return 0; 380 } 381 else 382 { 383 die "term $term is not of the form <environment-variable> (=|==) <value>"; 384 } 385} 386 387 388 389 390=head IsPresent($name, $given_checksum) 391 392 Check if an external library tar ball with the basename $name already 393 exists in the target directory TARFILE_LOCATION. The basename is 394 prefixed with the MD5 or SHA1 checksum. 395 If the file exists then its checksum is compared to the given one. 396 397=cut 398sub IsPresent ($$) 399{ 400 my ($name, $given_checksum) = @_; 401 402 my $filename = File::Spec->catfile($ENV{'TARFILE_LOCATION'}, $given_checksum->{'value'}."-".$name); 403 return 0 unless -f $filename; 404 405 # File exists. Check if its checksum is correct. 406 my $checksum; 407 if ( ! defined $given_checksum) 408 { 409 print "no checksum given, can not verify\n"; 410 return 1; 411 } 412 elsif ($given_checksum->{'type'} eq "MD5") 413 { 414 my $md5 = Digest::MD5->new(); 415 open my $in, $filename; 416 $md5->addfile($in); 417 $checksum = $md5->hexdigest(); 418 } 419 elsif ($given_checksum->{'type'} eq "SHA1") 420 { 421 my $sha1 = Digest::SHA->new("1"); 422 open my $in, $filename; 423 $sha1->addfile($in); 424 $checksum = $sha1->hexdigest(); 425 } 426 else 427 { 428 die "unsupported checksum type (not MD5 or SHA1)"; 429 } 430 431 if ($given_checksum->{'value'} ne $checksum) 432 { 433 # Checksum does not match. Delete the file. 434 print "$name exists, but checksum does not match => deleting\n"; 435 unlink($filename); 436 return 0; 437 } 438 else 439 { 440 printf("%s exists, %s checksum is OK\n", $name, $given_checksum->{'type'}); 441 return 1; 442 } 443} 444 445 446 447 448=head3 Download 449 450 Download a set of files specified by @Missing. 451 452 For http URLs there may be an optional checksum. If it is present then downloaded 453 files that do not match that checksum lead to abortion of the current process. 454 Files that have already been downloaded are not downloaded again. 455 456=cut 457sub Download () 458{ 459 my $download_path = $ENV{'TARFILE_LOCATION'}; 460 461 if (scalar @Missing > 0) 462 { 463 printf("downloading %d missing tar ball%s to %s\n", 464 scalar @Missing, scalar @Missing>0 ? "s" : "", 465 $download_path); 466 } 467 else 468 { 469 print "all external libraries present\n"; 470 return; 471 } 472 473 # Download the missing files. 474 my $all_downloaded = 1; 475 for my $item (@Missing) 476 { 477 my ($name, $checksum, $urls) = @$item; 478 479 my $downloaded = 0; 480 foreach my $url (@$urls) 481 { 482 $downloaded = DownloadFile( 483 defined $checksum 484 ? $checksum->{'value'}."-".$name 485 : $name, 486 $url, 487 $checksum); 488 last if $downloaded 489 } 490 $all_downloaded &&= $downloaded; 491 } 492 die "some needed files could not be downloaded!" if !$all_downloaded; 493} 494 495 496 497 498=head3 DownloadFile($name,$URL,$checksum) 499 500 Download a single external library tarball. It origin is given by $URL. 501 Its destination is $(TARFILE_LOCATION)/$checksum-$name. 502 503=cut 504sub DownloadFile ($$$) 505{ 506 my $name = shift; 507 my $URL = shift; 508 my $checksum = shift; 509 510 if (defined $checksum) 511 { 512 system( 513 $ENV{'JAVAINTERPRETER'}, 514 "-cp", 515 File::Spec->catfile( 516 File::Spec->catfile($ENV{'SOLARENV'}, $ENV{'INPATH'}), 517 "class"), 518 "AOOJavaDownloader", 519 $name, 520 $URL, 521 $checksum->{'type'}, 522 $checksum->{'value'}); 523 } 524 else 525 { 526 system( 527 $ENV{'JAVAINTERPRETER'}, 528 "-cp", 529 File::Spec->catfile( 530 File::Spec->catfile($ENV{'SOLARENV'}, $ENV{'INPATH'}), 531 "class"), 532 "AOOJavaDownloader", 533 $name, 534 $URL); 535 } 536 537 my $rc = $? >> 8; 538 if ($rc == 0) 539 { 540 return 1; 541 } 542 elsif ($rc == 1) 543 { 544 return 0; 545 } 546 else 547 { 548 exit $rc; 549 } 550} 551 552 553 554 555=head3 CheckDownloadDestination () 556 557 Make sure that the download destination $TARFILE_LOCATION does exist. If 558 not, then the directory is created. 559 560=cut 561sub CheckDownloadDestination () 562{ 563 my $destination = $ENV{'TARFILE_LOCATION'}; 564 die "ERROR: no destination defined! please set TARFILE_LOCATION!" if ($destination eq ""); 565 566 if ( ! -d $destination) 567 { 568 File::Path::make_path($destination); 569 die "ERROR: can't create \$TARFILE_LOCATION" if ! -d $destination; 570 } 571} 572 573 574 575 576=head3 ProvideSpecialTarball ($url,$name,$name_converter) 577 578 A few tarballs need special handling. That is done here. 579 580=cut 581sub ProvideSpecialTarball ($$$) 582{ 583 my $url = shift; 584 my $name = shift; 585 my $name_converter = shift; 586 587 return unless defined $url && $url ne ""; 588 589 # See if we can find the executable. 590 my ($SOLARENV,$OUTPATH,$EXEEXT) = ($ENV{'SOLARENV'},$ENV{'OUTPATH'},$ENV{'EXEEXT'}); 591 $SOLARENV = "" unless defined $SOLARENV; 592 $OUTPATH = "" unless defined $OUTPATH; 593 $EXEEXT = "" unless defined $EXEEXT; 594 if (-x File::Spec->catfile($SOLARENV, $OUTPATH, "bin", $name.$EXEEXT)) 595 { 596 print "found $name executable\n"; 597 return; 598 } 599 600 # Download the source from the URL. 601 my $basename = basename(URI->new($url)->path()); 602 die unless defined $basename; 603 604 if (defined $name_converter) 605 { 606 $basename = &{$name_converter}($basename); 607 } 608 609 # Has the source tar ball already been downloaded? 610 my @candidates = glob(File::Spec->catfile($ENV{'TARFILE_LOCATION'}, "*-" . $basename)); 611 if (scalar @candidates > 0) 612 { 613 # Yes. 614 print "$basename exists\n"; 615 return; 616 } 617 else 618 { 619 # No, download it. 620 print "downloading $basename\n"; 621 DownloadFile($basename, $url, undef); 622 } 623} 624 625 626 627 628 629# The main() functionality. 630 631die "usage: $0 <data-file-name>" if scalar @ARGV != 1; 632my $data_file = $ARGV[0]; 633CheckDownloadDestination(); 634ProcessDataFile($data_file); 635ProvideSpecialTarball($ENV{'DMAKE_URL'}, "dmake", undef); 636ProvideSpecialTarball( 637 $ENV{'EPM_URL'}, 638 "epm", 639 sub{$_[0]=~s/-source//; return $_[0]}); 640