1#!/usr/bin/perl 2#************************************************************** 3# 4# Licensed to the Apache Software Foundation (ASF) under one 5# or more contributor license agreements. See the NOTICE file 6# distributed with this work for additional information 7# regarding copyright ownership. The ASF licenses this file 8# to you under the Apache License, Version 2.0 (the 9# "License"); you may not use this file except in compliance 10# with the License. You may obtain a copy of the License at 11# 12# http://www.apache.org/licenses/LICENSE-2.0 13# 14# Unless required by applicable law or agreed to in writing, 15# software distributed under the License is distributed on an 16# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17# KIND, either express or implied. See the License for the 18# specific language governing permissions and limitations 19# under the License. 20# 21#************************************************************** 22 23use strict; 24use warnings; 25use XML::LibXML; 26use open OUT => ":utf8"; 27use LWP::Simple; 28use Digest; 29use Digest::MD5; 30use Digest::SHA; 31use File::Temp; 32use File::Path; 33 34use Carp::always; 35 36=head1 NAME 37 38 build_release.pl - Tool for batch release builds and uploads and the creation of wiki pages that list install sets. 39 40=head1 SYNOPSIS 41 42 build_release.pl <command> {option} <release-description.xml> 43 44 comands: 45 build builds all install sets as requested by the XML file and supported by the platform. 46 build-missing 47 build only those install sets that have not been built earlier. 48 upload upload install sets to a local or remote (via ssh with public/private key) 49 directory structure. Uploads install sets that where build on other platforms. 50 wiki create a wiki (MediaWiki syntax) snippet that references all install sets at the upload 51 location. Includes install sets that where built and/or uploaded from other 52 platforms and machines. 53 options: 54 -j <count> maximum number of build processes 55 -k keep going if there are recoverable errors 56 -u <path> upload destination 57 -l check links on wiki page, write broken links as plain text 58 -ld check links on wiki page, mark broken links 59 -o <filename> filename of the output (wiki: wiki page, build: makefile) 60 -n <number> maximal number of upload tries, defaults to 5. 61 -d dry-run 62 63 Typical calls are: 64 build_release.pl build -j4 instsetoo_native/util/aoo-410-release.xml 65 for building the installation sets, language packs and patches for the 4.1 release. 66 67 build_release.pl upload -u me@server:path -n 3 instsetoo_native/util/aoo-410-release.xml 68 to upload the previously built installation sets etc. 69 70 build_release.pl wiki -o /tmp/wiki.txt instsetoo_native/util/aoo-410-release.xml 71 to create an updated wiki page with installation sets etc built at several 72 places and uploaded to several locations. 73 74 75=head1 XML file format 76 77The release description could look like this: 78 79<release 80 name="snapshot" 81 version="4.1.0"> 82 83 <language 84 id="ast" # As specified by 'configure --with-lang' 85 english-name="Asturian" 86 local-name="Asturianu" 87 /> 88 ... more languages 89 90 <platform 91 id="wntmsci12.pro" 92 display-name="Windows" 93 archive-platform="Win_x86" 94 word-size="32" 95 package-types="msi" 96 extension="exe" 97 /> 98 ... more platforms 99 100 <download 101 platform-id="wntmsci12.pro" 102 base-url="http://people.apache.org/~somebody/developer-snapshots/snapshot/win32" 103 /> 104 105 <package 106 id="openoffice" 107 target="openoffice" 108 display-name="Full Install" 109 archive-name="Apache_OpenOffice_%V_%P_install%T_%L.%E" 110 /> 111 112 <build 113 package-id="openoffice" 114 platform-list="all" 115 language-list="all" 116 /> 117 ... more build entries 118 119 <wiki> 120 <package-ref 121 package-id="openoffice" 122 language-list="all" 123 platform-list="all" 124 table="main" 125 /> 126 ... more packages 127 </wiki> 128 129</release> 130 131A single <release> tag contains any number of 132 133<language> id 134 The language id used internally by the build process, eg de, en-US 135 english-name 136 The english name of the language, eg german 137 local-name 138 The language name in that language, eg Deutsch 139 140 Each listed language is expected to have been passed to configure via --with-lang 141 The set of languages defines for which languages to 142 build installation sets, language packs etc. (build command) 143 upload installation sets, etc (upload command) 144 add rows in the wiki page (wiki command) 145 146<platform> id 147 The platform id that is used internally by the build process, eg wntmsci12.pro 148 Note that <p>.pro and <p> are treated as two different platforms. 149 display-name 150 Name which is printed in the wiki table. 151 archive-platform 152 Platform name as used in the name of the installation set, eg Win_x86 153 word-size 154 Bit size of the installation sets, etc, typically either 32 or 64 155 package-types 156 Semicolon separated list of package types, eg "msi" or "deb;rpm" 157 add-package-type-to-archive-name 158 For deb and rpm archives it is necessary to add the package type to the archive name. 159 extension 160 Extension of the archive name, eg "exe" or "tar.gz" 161 162 For the build command only those <platform> elements are used that match the platform on which this 163 script is run. 164 165<download> 166 platform-id 167 Reference to one of the <platform> elements and has to match the id attribute of that platform. 168 base-url 169 URL head to which the name of the downloadable installation set etc. is appended. 170 Eg. http://people.apache.org/~somebody/developer-snapshots/snapshot/win32 171 172 Defines one download source that is referenced in the wiki page. Multiple <download> elements 173 per platform are possible. Earlier entires are preferred over later ones. 174 175<package> 176 id 177 Internal name that is used to reference the package. 178 target 179 Target name recognized by instsetoo_native/util/makefile.mk, eg openoffice or oolanguagepack. 180 display-name 181 Name of the package that is shown in the wiki page, eg "Full Install" or "Langpack". 182 archive-name 183 Template of the archive name. 184 %V version 185 %P archive package name 186 %T package type 187 %L language 188 %E extension. 189 190 Defines a downloadable and distributable package, eg openoffice for the binary OpenOffice installation set. 191 192<build> target 193 platform-list 194 Semicolon separated list of platforms for which to build the target. 195 Ignores all platforms that don't match the platform on which this script is executed. 196 The special value 'all' is a shortcut for all platforms listed by <platform> elements. 197 language-list 198 Semicolon separated list of languages for which the build the target. 199 The special value 'all' is a shortcut for all languages listed by <language> elements. 200 201 Defines the sets of targets, plaforms and languages which are to be built. 202 203<wiki> 204 <package-ref> 205 package-id 206 The id of the referenced package. 207 platform-list 208 See <build> tag for explanation. 209 language-list 210 See <build> tag for explanation. 211 table 212 Specifies the wiki table into which to add the package lines. Can be "main" or "secondary". 213 214=cut 215 216 217 218my %EnUSBasedLanguages = ( 219 'ast' => 1 220 ); 221 222 223sub ProcessCommandline (@); 224sub PrintUsageAndExit (); 225sub Trim ($); 226sub ReadReleaseDescription ($$); 227sub ProcessBuildDescription ($$$); 228sub ProcessPlatformDescription ($$); 229sub ProcessDownloadDescription ($$); 230sub ProcessPackageDescription ($$); 231sub ProcessWikiPackageDescription ($$$); 232sub ProcessLanguageDescription ($$); 233sub PostprocessLanguageList ($$); 234sub PostprocessPlatformList ($$); 235sub CheckLanguageSet ($@); 236sub WriteMakefile ($$); 237sub Upload ($$); 238sub PrepareUploadArea ($@); 239sub UploadFilesViaRsync ($$@); 240sub CollectDownloadSets ($); 241sub ProvideChecksums ($@); 242sub IsOlderThan ($$); 243sub GetInstallationPackageName ($$$$); 244sub ResolveTemplate ($$$$$); 245sub GetCurrentPlatformDescriptor ($); 246sub Wiki ($$); 247sub GetTableList ($); 248sub GetPackagesForTable ($$); 249sub GetLanguagesForTable ($@); 250sub GetPlatformsForTable ($@); 251sub WriteDownloadLinks ($$$$$$$); 252sub FindDownload ($$$$$); 253sub CreateLink ($$$); 254sub CheckLink ($); 255sub SignFile ($$); 256 257sub ProcessCommandline (@) 258{ 259 my @arguments = @_; 260 261 my $command = undef; 262 my $description_filename = undef; 263 my $max_process_count = 1; 264 my $keep_going = 0; 265 my $upload_destination = undef; 266 my $check_links = 0; 267 my $mark_broken_links = 0; 268 my $output_filename = undef; 269 my $max_upload_count = 5; 270 my $build_only_missing = 0; 271 my $dry_run = 0; 272 273 my $error = 0; 274 while (scalar @arguments > 0) 275 { 276 my $argument = shift @arguments; 277 if ($argument =~ /^-/) 278 { 279 if ($argument eq "-j") 280 { 281 $max_process_count = shift @arguments; 282 } 283 elsif ($argument eq "-u") 284 { 285 $upload_destination = shift @arguments; 286 $upload_destination =~ s/(\\|\/)$//; 287 } 288 elsif ($argument eq "-k") 289 { 290 $keep_going = 1; 291 } 292 elsif ($argument eq "-l") 293 { 294 $check_links = 1; 295 } 296 elsif ($argument eq "-ld") 297 { 298 $check_links = 1; 299 $mark_broken_links = 1; 300 } 301 elsif ($argument eq "-o") 302 { 303 $output_filename = shift @arguments; 304 } 305 elsif ($argument eq "-n") 306 { 307 $max_upload_count = shift @arguments; 308 } 309 elsif ($argument eq "-d") 310 { 311 $dry_run = 1; 312 } 313 else 314 { 315 printf STDERR "unknown option $argument %s\n", $argument; 316 $error = 1; 317 } 318 } 319 elsif ( ! defined $command) 320 { 321 $command = $argument; 322 if ($command eq "build-missing") 323 { 324 $command = "build"; 325 $build_only_missing = 1; 326 } 327 elsif ($command !~ /^(build|build-missing|upload|wiki)$/) 328 { 329 printf STDERR "unknown command '%s'\n", $command; 330 $error = 1; 331 } 332 } 333 else 334 { 335 $description_filename = $argument; 336 if ( ! -f $description_filename) 337 { 338 print STDERR "can not open release description '%s'\n", $description_filename; 339 $error = 1; 340 } 341 } 342 } 343 344 if ( ! defined $description_filename) 345 { 346 $error = 1; 347 } 348 if (! defined $command) 349 { 350 printf STDERR "ERROR: no command\n"; 351 $error = 1; 352 } 353 elsif ($command =~ /^(wiki)$/) 354 { 355 if ( ! defined $output_filename) 356 { 357 printf STDERR "ERROR: no output filename\n", 358 $error = 1; 359 } 360 } 361 362 if ($error) 363 { 364 PrintUsageAndExit(); 365 } 366 367 return { 368 'command' => $command, 369 'filename' => $description_filename, 370 'max-process-count' => $max_process_count, 371 'keep-going' => $keep_going, 372 'upload-destination' => $upload_destination, 373 'check-links' => $check_links, 374 'mark-broken-links' => $mark_broken_links, 375 'output-filename' => $output_filename, 376 'max-upload-count' => $max_upload_count, 377 'build-only-missing' => $build_only_missing, 378 'dry-run' => $dry_run 379 }; 380} 381 382 383 384 385sub PrintUsageAndExit () 386{ 387 print STDERR "usage: $0 <command> {option} <release-description.xml>\n"; 388 print STDERR " comands:\n"; 389 print STDERR " build\n"; 390 print STDERR " build-missing\n"; 391 print STDERR " upload\n"; 392 print STDERR " wiki create a download page in MediaWiki syntax\n"; 393 print STDERR " options:\n"; 394 print STDERR " -j <count> maximum number of build processes\n"; 395 print STDERR " -k keep going if there are recoverable errors\n"; 396 print STDERR " -u <path> upload destination\n"; 397 print STDERR " -l check links on wiki page, write broken links as plain text\n"; 398 print STDERR " -ld check links on wiki page, mark broken links\n"; 399 print STDERR " -o <filename> filename of the output (wiki: wiki page, build: makefile)\n"; 400 print STDERR " -n <number> maximal number of upload tries, defaults to 5.\n"; 401 print STDERR " -d dry run\n"; 402 exit(1); 403} 404 405 406 407 408=head2 Trim ($text) 409 410 Remove leading and trailing space from the given string. 411 412=cut 413sub Trim ($) 414{ 415 my ($text) = @_; 416 $text =~ s/^\s+|\s+$//g; 417 return $text; 418} 419 420 421 422 423=head2 ReadReleaseDescription ($$) 424 425 Read the release description from $filename. 426 427=cut 428sub ReadReleaseDescription ($$) 429{ 430 my ($filename, $context) = @_; 431 432 my $document = XML::LibXML->load_xml('location' => $filename); 433 my $root = $document->documentElement(); 434 435 # Initialize the release description. 436 my $release = { 437 'name' => $root->getAttribute("name"), 438 'version' => $root->getAttribute("version"), 439 'previous-version' => $root->getAttribute("previous-version"), 440 'builds' => [], 441 'languages' => {}, 442 'language-ids' => [], 443 'platforms' => {}, 444 'downloads' => [], 445 'packages' => {}, 446 'platform-ids' => [], 447 'wiki-packages' => [] 448 }; 449 450 # Process the language descriptions. 451 for my $language_element ($root->getChildrenByTagName("language")) 452 { 453 my $language_descriptor = ProcessLanguageDescription($language_element, $context); 454 $release->{'languages'}->{$language_descriptor->{'id'}} = $language_descriptor; 455 push @{$release->{'language-ids'}}, $language_descriptor->{'id'}; 456 } 457 printf "%d languages\n", scalar keys %{$release->{'languages'}}; 458 459 # Process the platform descriptions. 460 for my $platform_element ($root->getChildrenByTagName("platform")) 461 { 462 my $platform_descriptor = ProcessPlatformDescription($platform_element, $context); 463 $release->{'platforms'}->{$platform_descriptor->{'id'}} = $platform_descriptor; 464 push @{$release->{'platform-ids'}}, $platform_descriptor->{'id'}; 465 } 466 printf "%d platforms\n", scalar keys %{$release->{'platforms'}}; 467 468 # Process the package descriptions. 469 for my $package_element ($root->getChildrenByTagName("package")) 470 { 471 my $package_descriptor = ProcessPackageDescription($package_element, $context); 472 $release->{'packages'}->{$package_descriptor->{'id'}} = $package_descriptor; 473 } 474 printf "%d packages\n", scalar keys %{$release->{'packages'}}; 475 476 # Platform specific the package descriptions. 477 for my $package_element ($root->getChildrenByTagName("platform-package")) 478 { 479 my $package_descriptor = ProcessPlatformPackageDescription($package_element, $context); 480 my $key = $package_descriptor->{'platform-id'} . "/" . $package_descriptor->{'package-id'}; 481 $release->{'platform-packages'}->{$key} = $package_descriptor; 482 } 483 printf "%d platform packages\n", scalar keys %{$release->{'platform-packages'}}; 484 485 # Process the download descriptions. 486 for my $download_element ($root->getChildrenByTagName("download")) 487 { 488 my $download_descriptor = ProcessDownloadDescription($download_element, $context); 489 push @{$release->{'downloads'}}, $download_descriptor; 490 } 491 printf "%d downloads\n", scalar @{$release->{'downloads'}}; 492 493 if ($context->{'command'} =~ /^(build|upload)$/) 494 { 495 # Process the build descriptions. 496 for my $build_element ($root->getChildrenByTagName("build")) 497 { 498 push @{$release->{'builds'}}, ProcessBuildDescription($build_element, $context, $release); 499 } 500 printf "%d build targets\n", scalar @{$release->{'builds'}}; 501 } 502 503 if ($context->{'command'} eq "wiki") 504 { 505 for my $wiki_element ($root->getChildrenByTagName("wiki")) 506 { 507 for my $wiki_package_element ($wiki_element->getChildrenByTagName("package-ref")) 508 { 509 my $wiki_package = ProcessWikiPackageDescription( 510 $wiki_package_element, 511 $context, 512 $release); 513 push @{$release->{'wiki-packages'}}, $wiki_package; 514 } 515 } 516 printf "%d wiki packages\n", scalar @{$release->{'wiki-packages'}}; 517 } 518 519 return $release; 520} 521 522 523 524 525=head ProcessBuildDescription ($build_element, $context, $release_descriptor) 526 527 Process one <build> element. 528 529 If its platform-list does not match the current platform then the <build> element is ignored. 530 531=cut 532sub ProcessBuildDescription ($$$) 533{ 534 my ($build_element, $context, $release_descriptor) = @_; 535 536 my $package_id = $build_element->getAttribute("package-id"); 537 my $languages = PostprocessLanguageList($build_element->getAttribute("language-list"), $release_descriptor); 538 my $platforms = PostprocessPlatformList($build_element->getAttribute("platform-list"), $release_descriptor); 539 540 # Check if the platform matches any for which the product shall be built. 541 my $current_platform = $ENV{'INPATH'}; 542 my $is_platform_match = 0; 543 foreach my $platform_id (@$platforms) 544 { 545 if ($platform_id eq $current_platform) 546 { 547 $is_platform_match=1; 548 last; 549 } 550 } 551 if ($is_platform_match) 552 { 553 printf "including build %s\n", $package_id; 554 } 555 else 556 { 557 printf "skipping build %s: no platform match\n", $package_id; 558 printf "none of the platforms %s matches %s\n", 559 join(", ", keys %{$release_descriptor->{'platforms'}}), 560 $current_platform; 561 return; 562 } 563 564 my @languages = CheckLanguageSet($context, @$languages); 565 566 return { 567 'package-id' => $package_id, 568 'platform-list' => $platforms, 569 'language-list' => \@languages 570 }; 571} 572 573 574 575 576 577=head2 ProcessPlatformDescription ($element, $context) 578 579 Process one <platform> element. 580 581 The corresponding platform descriptor is returned as a hash. 582 583=cut 584sub ProcessPlatformDescription ($$) 585{ 586 my ($element, $context) = @_; 587 588 my $descriptor = {}; 589 # Mandatory tags. 590 foreach my $id ("id", "display-name", "archive-platform", "word-size", "package-types") 591 { 592 $descriptor->{$id} = $element->getAttribute($id); 593 die "release/platform has no attribute $id" unless defined $descriptor->{$id}; 594 } 595 # Optional tags. 596 foreach my $id ("extension", "add-package-type-to-archive-name") 597 { 598 my $value = $element->getAttribute($id); 599 $descriptor->{$id} = $value if defined $value; 600 } 601 602 $descriptor->{'add-package-type-to-archive-name'} = 0 603 unless defined $descriptor->{'add-package-type-to-archive-name'}; 604 $descriptor->{'package-types'} = [split(/;/, $descriptor->{'package-types'})]; 605 606 return $descriptor; 607} 608 609 610 611 612=head2 ProcessDownloadDescription ($element, $context) 613 614 Process one <download> element. 615 616 The corresponding download descriptor is returned as a hash. 617 618=cut 619sub ProcessDownloadDescription ($$) 620{ 621 my ($element, $context) = @_; 622 623 my $descriptor = {}; 624 625 # Mandatory tags. 626 foreach my $id ("platform-id", "base-url") 627 { 628 $descriptor->{$id} = $element->getAttribute($id); 629 die "release/download has no attribute $id" unless defined $descriptor->{$id}; 630 } 631 632 return $descriptor; 633} 634 635 636 637 638=head2 ProcessPackageDescription ($element, $context) 639 640 Process one <package> element. 641 642 The corresponding package descriptor is returned as a hash. 643 644=cut 645sub ProcessPackageDescription ($$) 646{ 647 my ($element, $context) = @_; 648 649 my $descriptor = {}; 650 651 # Mandatory tags. 652 foreach my $id ("id", "target", "archive-path", "archive-name", "display-name") 653 { 654 $descriptor->{$id} = $element->getAttribute($id); 655 die "release/package has no attribute $id" unless defined $descriptor->{$id}; 656 die "release/package attribute $id is empty" unless $descriptor->{$id} !~ /^\s*$/; 657 } 658 # Optional tags. 659 foreach my $id ("link-tooltip", "link-URL", "download-extension") 660 { 661 my $value = $element->getAttribute($id); 662 $descriptor->{$id} = $value if defined $value; 663 } 664 665 return $descriptor; 666} 667 668 669 670 671=head2 ProcessPlatformPackageDescription ($element, $context) 672 673 Process one <platform-package> element. 674 675 The corresponding package descriptor is returned as a hash. 676 677=cut 678sub ProcessPlatformPackageDescription ($$) 679{ 680 my ($element, $context) = @_; 681 682 my $descriptor = {}; 683 684 # Mandatory tags. 685 foreach my $id ("platform-id", "package-id") 686 { 687 $descriptor->{$id} = $element->getAttribute($id); 688 die "release/package has no attribute $id" unless defined $descriptor->{$id}; 689 die "release/package attribute $id is empty" unless $descriptor->{$id} !~ /^\s*$/; 690 } 691 # Optional tags. 692 foreach my $id ("extension", "package-types") 693 { 694 my $value = $element->getAttribute($id); 695 $descriptor->{$id} = $value if defined $value; 696 } 697 if (defined $descriptor->{'package-types'}) 698 { 699 $descriptor->{'package-types'} = [split(/;/, $descriptor->{'package-types'})]; 700 } 701 702 return $descriptor; 703} 704 705 706 707 708=head2 ProcessWikiPackageDescription ($element, $context) 709 710 Process one <wiki><package-ref> element. 711 712 The corresponding descriptor is returned as a hash. 713 714=cut 715sub ProcessWikiPackageDescription ($$$) 716{ 717 my ($element, $context, $release_descriptor) = @_; 718 719 my $descriptor = {}; 720 # Mandatory tags. 721 foreach my $id ("package-id", "table") 722 { 723 $descriptor->{$id} = $element->getAttribute($id); 724 die "wiki/package-ref has no attribute $id" unless defined $descriptor->{$id}; 725 die "wiki/package-ref attribute $id is empty" unless $descriptor->{$id} !~ /^\s*$/; 726 } 727 728 $descriptor->{'language-list'} = PostprocessLanguageList( 729 $element->getAttribute("language-list"), 730 $release_descriptor); 731 $descriptor->{'platform-list'} = PostprocessPlatformList( 732 $element->getAttribute("platform-list"), 733 $release_descriptor); 734 735 $descriptor->{'languages'} = {map{$_=>1} @{$descriptor->{'language-list'}}}; 736 $descriptor->{'platforms'} = {map{$_=>1} @{$descriptor->{'platform-list'}}}; 737 738 return $descriptor; 739} 740 741 742 743 744=head2 ProcessLanguageDescription ($element, $context) 745 746 Process one <language> element. 747 748 The corresponding language descriptor is returned as a hash. 749 750=cut 751sub ProcessLanguageDescription ($$) 752{ 753 my ($element, $context) = @_; 754 755 my $descriptor = {}; 756 foreach my $id ("id", "english-name", "local-name") 757 { 758 $descriptor->{$id} = $element->getAttribute($id); 759 die "wiki/language has no attribute $id" unless defined $descriptor->{$id}; 760 } 761 762 return $descriptor; 763} 764 765 766 767 768=head2 PostprocessLanguageList ($language_list, $release_descriptor) 769 770 Process a language list that is given as 'language-list' attribute to some tags. 771 772 If the attribute is missing, ie $language_list is undef, or its value is "all", 773 then the returned list of languages is set to all languages defined via <language> elements. 774 775=cut 776sub PostprocessLanguageList ($$) 777{ 778 my ($language_list, $release_descriptor) = @_; 779 780 my @matching_languages = (); 781 if ( ! defined $language_list 782 || $language_list eq "all") 783 { 784 @matching_languages = sort keys %{$release_descriptor->{'languages'}}; 785 } 786 else 787 { 788 @matching_languages = split(/;/, $language_list); 789 } 790 791 return \@matching_languages; 792} 793 794 795 796 797=head2 PostprocessPlatformList ($platform_list, $release_descriptor) 798 799 Process a platform list that is given as 'platform-list' attribute to some tags. 800 801 If the attribute is missing, ie $platform_list is undef, or its value is "all", 802 then the returned list of platforms is set to all platforms defined via <platform> elements. 803 804=cut 805sub PostprocessPlatformList ($$) 806{ 807 my ($platform_list, $release_descriptor) = @_; 808 809 my @matching_platforms = (); 810 if ( ! defined $platform_list 811 || $platform_list eq "all") 812 { 813 @matching_platforms = sort keys %{$release_descriptor->{'platforms'}}; 814 } 815 else 816 { 817 @matching_platforms = split(/;/, $platform_list); 818 } 819 820 return \@matching_platforms; 821} 822 823 824 825 826=head2 CheckLanguageSet ($context, @languages) 827 828 Compare the given list of languages with the one defined by the 'WITH_LANG' environment variable. 829 830 This is to ensure that configure --with-lang was called with the same set of languages that are 831 listed by the <language> elements. 832 833=cut 834sub CheckLanguageSet ($@) 835{ 836 my ($context, @languages) = @_; 837 my %configured_languages = map{$_=>1} split(/\s+/, $ENV{'WITH_LANG'}); 838 839 my @missing_languages = (); 840 my @present_languages = (); 841 for my $language (@languages) 842 { 843 if (defined $configured_languages{$language}) 844 { 845 push @present_languages, $language; 846 } 847 else 848 { 849 push @missing_languages, $language; 850 } 851 } 852 853 if (scalar @missing_languages > 0) 854 { 855 my $message_head = $context->{'keep-going'} ? "WARNING" : "ERROR"; 856 printf STDERR "%s: there are languages that where not configured via --with-lang:\n", $message_head; 857 printf STDERR "%s: %s\n", $message_head, join(", ", @missing_languages); 858 if ($context->{'keep-going'}) 859 { 860 printf " available languages:\n"; 861 printf " %s\n", join(", ", @present_languages); 862 } 863 else 864 { 865 printf STDERR "ERROR: please rerun configure with --with-lang=\"%s\"\n", join(" ", @languages); 866 exit(1); 867 } 868 } 869 870 return @present_languages; 871} 872 873 874 875 876=head2 WriteMakefile ($release_descriptor, $context) 877 878 Write a makefile with all targets that match the <build> elements. 879 880 The use of a makefile allows us to use make to handle concurrent building. 881 882 When an output file was specified on the command line (option -o) then the 883 makefile is written to that file but make is not run. 884 885 When no output file was specified then the makefile is written to a temporary 886 file. Then make is run for this makefile. 887 888=cut 889sub WriteMakefile ($$) 890{ 891 my ($release_descriptor, $context) = @_; 892 893 my $filename = $context->{'output-filename'}; 894 if ( ! defined $filename) 895 { 896 $filename = File::Temp->new(); 897 } 898 899 # Collect the targets to make. 900 my @targets = (); 901 foreach my $build (@{$release_descriptor->{'builds'}}) 902 { 903 my $platform_descriptor = GetCurrentPlatformDescriptor($release_descriptor); 904 my $package_descriptor = $release_descriptor->{'packages'}->{$build->{'package-id'}}; 905 my $platform_package_descriptor = GetPlatformPackage( 906 $release_descriptor, 907 $platform_descriptor, 908 $package_descriptor); 909 910 foreach my $language_id (@{$build->{'language-list'}}) 911 { 912 foreach my $package_format (@{$platform_package_descriptor->{'package-types'}}) 913 { 914 my $full_target = sprintf("%s_%s.%s", 915 $package_descriptor->{'target'}, 916 $language_id, 917 $package_format); 918 if ($context->{'build-only-missing'}) 919 { 920 my ($archive_path, $archive_name) = GetInstallationPackageName( 921 $release_descriptor, 922 $platform_package_descriptor, 923 $package_format, 924 $language_id); 925 my $candidate = $archive_path . "/" . $archive_name; 926 if (-f $candidate) 927 { 928 printf "download set for %s already exists, skipping\n", $full_target; 929 next; 930 } 931 else 932 { 933 printf "%s %s\n", $archive_path, $archive_name; 934 } 935 } 936 push @targets, $full_target; 937 } 938 } 939 } 940 941 # Write the makefile. 942 open my $make, ">", $filename; 943 944 # Write dependencies of 'all' on the products in all languages. 945 print $make "all .PHONY : \\\n "; 946 printf $make "%s\n", join(" \\\n ", @targets); 947 printf $make "\n\n"; 948 949 if ($context->{'dry-run'}) 950 { 951 printf ("adding make fules for\n %s\n", join("\n ", @targets)); 952 } 953 954 # Write rules that chain dmake in instsetoo_native/util. 955 foreach my $target (@targets) 956 { 957 printf $make "%s :\n", $target; 958 printf $make "\tdmake \$@ release=t\n"; 959 } 960 close $make; 961 962 963 if ( ! defined $context->{'output-filename'}) 964 { 965 # Caller wants us to run make. 966 my $path = $ENV{'SRC_ROOT'} . "/instsetoo_native/util"; 967 my $command = sprintf("make -f \"%s\" -C \"%s\" -j%d", 968 $filename, 969 $path, 970 $context->{'max-process-count'}); 971 if ($context->{'dry-run'}) 972 { 973 printf "would run %s\n", $command; 974 } 975 else 976 { 977 printf "running %s\n", $command; 978 system($command); 979 } 980 } 981} 982 983 984 985 986sub Upload ($$) 987{ 988 my ($release_descriptor, $context) = @_; 989 990 if ( ! defined $context->{'upload-destination'}) 991 { 992 printf STDERR "ERROR: upload destination is missing\n"; 993 PrintUsageAndExit(); 994 } 995 996 my @download_sets = CollectDownloadSets($release_descriptor); 997 998 ProvideChecksums($context, @download_sets); 999 my $source_path = PrepareUploadArea($context, @download_sets); 1000 if ( ! defined $source_path) 1001 { 1002 exit(1); 1003 } 1004 if ( ! UploadFilesViaRsync($context, $source_path, @download_sets)) 1005 { 1006 exit(1); 1007 } 1008} 1009 1010 1011 1012 1013=head2 PrepareUploadArea ($context, @download_sets) 1014 1015 Create a temporary directory with the same sub directory strcuture that is requested in the upload location. 1016 The files that are to be uploaded are not copied but linked into this temporary directory tree. 1017 1018 Returns the name of the temporary directory. 1019 1020=cut 1021sub PrepareUploadArea ($@) 1022{ 1023 my ($context, @download_sets) = @_; 1024 1025 my $tmpdir = File::Temp->newdir(); 1026 foreach my $download_set (@download_sets) 1027 { 1028 foreach my $extension ("", ".md5", ".sha256", ".asc") 1029 { 1030 my $basename = sprintf("%s%s", $download_set->{'archive-name'}, $extension); 1031 my $source_path = $download_set->{'source-path'}; 1032 my $source = sprintf("%s/%s", $source_path, $basename); 1033 my $target_path = sprintf("%s/%s", $tmpdir, $download_set->{'destination-path'}); 1034 my $target = sprintf("%s/%s", $target_path, $basename); 1035 if ($context->{'dry-run'}) 1036 { 1037 printf "would create link for %s\n", $basename; 1038 printf " %s\n", $source_path; 1039 printf " to %s\n", $target_path; 1040 } 1041 else 1042 { 1043 mkpath($target_path); 1044 unlink $target if ( -f $target); 1045 my $result = symlink($source, $target); 1046 if ($result != 1) 1047 { 1048 printf "ERROR: can not created symbolic link to %s\n", $basename; 1049 printf " %s\n", $source; 1050 printf " -> %s\n", $target; 1051 return undef; 1052 } 1053 } 1054 } 1055 } 1056 1057 return $tmpdir; 1058} 1059 1060 1061 1062 1063sub UploadFilesViaRsync ($$@) 1064{ 1065 my ($context, $source_path, @download_sets) = @_; 1066 1067 1068 # Collect the rsync flags. 1069 my @rsync_options = ( 1070 "-L", # Copy linked files 1071 "-a", # Transfer the local attributes and modification times. 1072 "-c", # Use checksums to compare source and destination files. 1073 "--progress", # Show a progress indicator 1074 "--partial", # Try to resume a previously failed upload 1075 ); 1076 1077 # (Optional) Add flags for upload to ssh server 1078 my $upload_destination = $context->{'upload-destination'}; 1079 if ($upload_destination =~ /@/) 1080 { 1081 push @rsync_options, ("-e", "ssh"); 1082 } 1083 1084 # Set up the rsync command. 1085 my $command = sprintf("rsync %s \"%s/\" \"%s\"", 1086 join(" ", @rsync_options), 1087 $source_path, 1088 $upload_destination); 1089 printf "%s\n", $command; 1090 1091 if ($context->{'dry-run'}) 1092 { 1093 printf "would run %s up to %d times\n", $command, $context->{'max-upload-count'}; 1094 } 1095 else 1096 { 1097 # Run the command. If it fails, repeat a number of times. 1098 my $max_run_count = $context->{'max-upload-count'}; 1099 for (my $run_index=1; $run_index<=$max_run_count && scalar @download_sets>0; ++$run_index) 1100 { 1101 my $result = system($command); 1102 printf "%d %d\n", $result, $?; 1103 return 1 if $result == 0; 1104 } 1105 printf "ERROR: could not upload all files without error in %d runs\n", $max_run_count; 1106 return 0; 1107 } 1108} 1109 1110 1111 1112 1113sub CollectDownloadSets ($) 1114{ 1115 my ($release_descriptor) = @_; 1116 1117 my @download_sets = (); 1118 1119 foreach my $platform_descriptor (values %{$release_descriptor->{'platforms'}}) 1120 { 1121 my $platform_path = sprintf("%s/instsetoo_native/%s", 1122 $ENV{'SOLARSRC'}, 1123 $platform_descriptor->{'id'}); 1124 if ( ! -d $platform_path) 1125 { 1126 printf "ignoring missing %s\n", $platform_path; 1127 next; 1128 } 1129 for my $package_descriptor (values %{$release_descriptor->{'packages'}}) 1130 { 1131 my $platform_package_descriptor = GetPlatformPackage( 1132 $release_descriptor, 1133 $platform_descriptor, 1134 $package_descriptor); 1135 my @package_formats = @{$platform_descriptor->{'package-types'}}; 1136 for my $package_format (@package_formats) 1137 { 1138 for my $language_id (@{$release_descriptor->{'language-ids'}}) 1139 { 1140 my ($archive_path, $archive_name) = GetInstallationPackageName( 1141 $release_descriptor, 1142 $platform_package_descriptor, 1143 $package_format, 1144 $language_id); 1145 my $candidate = $archive_path."/".$archive_name; 1146 if ( ! -f $candidate) 1147 { 1148# printf STDERR "ERROR: can not find download set '%s'\n", $candidate; 1149 next; 1150 } 1151 printf "adding %s\n", $archive_name; 1152 my $download_set = { 1153 'source-path' => $archive_path, 1154 'archive-name' => $archive_name, 1155 'platform' => $platform_descriptor->{'archive-platform'}, 1156 'destination-path' => sprintf("developer-snapshots/%s/%s", 1157 $release_descriptor->{'name'}, 1158 $platform_descriptor->{'archive-platform'}) 1159 }; 1160 printf " %s\n", $download_set->{'destination-path'}; 1161 push @download_sets, $download_set; 1162 } 1163 } 1164 } 1165 } 1166 1167 return @download_sets; 1168} 1169 1170 1171 1172 1173=head2 ProvideChecksums ($context, @download_sets) 1174 1175 Create checksums in MD5 and SHA256 format and a gpg signature for the given download set. 1176 The checksums are not created when they already exists and are not older than the download set. 1177 1178=cut 1179sub ProvideChecksums ($@) 1180{ 1181 my ($context, @download_sets) = @_; 1182 1183 my @asc_requests = (); 1184 foreach my $download_set (@download_sets) 1185 { 1186 printf "%s\n", $download_set->{'archive-name'}; 1187 my $full_archive_name = $download_set->{'source-path'} . "/" . $download_set->{'archive-name'}; 1188 $full_archive_name = Trim(qx(cygpath -u "$full_archive_name")); 1189 1190 my $md5_filename = $full_archive_name . ".md5"; 1191 if ( ! -f $md5_filename || IsOlderThan($md5_filename, $full_archive_name)) 1192 { 1193 if ($context->{'dry-run'}) 1194 { 1195 printf " would create MD5\n"; 1196 } 1197 else 1198 { 1199 my $digest = Digest::MD5->new(); 1200 open my ($in), $full_archive_name; 1201 $digest->addfile($in); 1202 my $checksum = $digest->hexdigest(); 1203 close $in; 1204 1205 open my ($out), ">", $md5_filename; 1206 printf $out "%s *%s", $checksum, $download_set->{'archive-name'}; 1207 close $out; 1208 1209 printf " created MD5\n"; 1210 } 1211 } 1212 else 1213 { 1214 printf " MD5 already exists\n"; 1215 } 1216 1217 my $sha256_filename = $full_archive_name . ".sha256"; 1218 if ( ! -f $sha256_filename || IsOlderThan($sha256_filename, $full_archive_name)) 1219 { 1220 if ($context->{'dry-run'}) 1221 { 1222 printf " would create SHA256\n"; 1223 } 1224 else 1225 { 1226 my $digest = Digest::SHA->new("sha256"); 1227 open my ($in), $full_archive_name; 1228 $digest->addfile($in); 1229 my $checksum = $digest->hexdigest(); 1230 close $in; 1231 1232 open my ($out), ">", $sha256_filename; 1233 printf $out "%s *%s", $checksum, $download_set->{'archive-name'}; 1234 close $out; 1235 1236 printf " created SHA256\n"; 1237 } 1238 } 1239 else 1240 { 1241 printf " SHA256 already exists\n"; 1242 } 1243 1244 my $asc_filename = $full_archive_name . ".asc"; 1245 if ( ! -f $asc_filename || IsOlderThan($asc_filename, $full_archive_name)) 1246 { 1247 if ($context->{'dry-run'}) 1248 { 1249 printf " would create ASC\n"; 1250 } 1251 else 1252 { 1253 # gpg seems not to be able to sign more than one file at a time. 1254 # Password has to be provided every time. 1255 my $command = sprintf("gpg --armor --detach-sig \"%s\"", $full_archive_name); 1256 print $command; 1257 my $result = system($command); 1258 printf " created ASC\n"; 1259 } 1260 } 1261 else 1262 { 1263 printf " ASC already exists\n"; 1264 } 1265 } 1266} 1267 1268 1269 1270 1271=head2 IsOlderThan ($filename1, $filename2) 1272 1273 Return true (1) if the last modification date of $filename1 is older than (<) that of $filename2. 1274 1275=cut 1276sub IsOlderThan ($$) 1277{ 1278 my ($filename1, $filename2) = @_; 1279 1280 my @stat1 = stat $filename1; 1281 my @stat2 = stat $filename2; 1282 1283 return $stat1[9] < $stat2[9]; 1284} 1285 1286 1287 1288 1289sub GetInstallationPackageName ($$$$) 1290{ 1291 my ($release_descriptor, $platform_package_descriptor, $package_format, $language) = @_; 1292 1293 my $path = ResolveTemplate( 1294 $platform_package_descriptor->{'archive-path'}, 1295 $release_descriptor, 1296 $platform_package_descriptor, 1297 $package_format, 1298 $language); 1299 my $name = ResolveTemplate( 1300 $platform_package_descriptor->{'archive-name'}, 1301 $release_descriptor, 1302 $platform_package_descriptor, 1303 $package_format, 1304 $language); 1305 1306 return ($path, $name); 1307} 1308 1309 1310 1311 1312sub ResolveTemplate ($$$$$) 1313{ 1314 my ($template, $release_descriptor, $platform_package_descriptor, $package_format, $language) = @_; 1315 1316 my $archive_package_type = ""; 1317 if ($platform_package_descriptor->{'add-package-type-to-archive-name'} =~ /^(1|true|yes)$/i) 1318 { 1319 $archive_package_type = "-".$package_format; 1320 } 1321 my $full_language = $language; 1322 if ($EnUSBasedLanguages{$language}) 1323 { 1324 $full_language = "en-US_".$language; 1325 } 1326 my $extension = $platform_package_descriptor->{'download-extension'}; 1327 if ( ! defined $extension) 1328 { 1329 $extension = $platform_package_descriptor->{'extension'}; 1330 } 1331 1332 my $old_to_new_version_dash = sprintf( 1333 "v-%s_v-%s", 1334 $release_descriptor->{'previous-version'}, 1335 $release_descriptor->{'version'}); 1336 $old_to_new_version_dash =~ s/\./-/g; 1337 my $old_to_new_version_dots = sprintf( 1338 "%s-%s", 1339 $release_descriptor->{'previous-version'}, 1340 $release_descriptor->{'version'}); 1341 1342 1343 my $name = $template; 1344 1345 # Resolve %? template parameters. 1346 $name =~ s/%V/$release_descriptor->{'version'}/g; 1347 $name =~ s/%W/$old_to_new_version_dash/g; 1348 $name =~ s/%w/$old_to_new_version_dots/g; 1349 $name =~ s/%P/$platform_package_descriptor->{'archive-platform'}/g; 1350 $name =~ s/%t/$archive_package_type/g; 1351 $name =~ s/%T/$package_format/g; 1352 $name =~ s/%l/$full_language/g; 1353 $name =~ s/%L/$language/g; 1354 $name =~ s/%E/$extension/g; 1355 1356 # Resolve $name environment references. 1357 while ($name =~ /^(.*?)\$([a-zA-Z0-9_]+)(.*)$/) 1358 { 1359 $name = $1 . $ENV{$2} . $3; 1360 } 1361 1362 return $name; 1363} 1364 1365 1366 1367 1368sub GetCurrentPlatformDescriptor ($) 1369{ 1370 my ($release_descriptor) = @_; 1371 1372 my $platform_descriptor = $release_descriptor->{'platforms'}->{$ENV{'INPATH'}}; 1373 if ( ! defined $platform_descriptor) 1374 { 1375 printf STDERR "ERROR: platform '%s' is not supported\n", $ENV{'INPATH'}; 1376 } 1377 return $platform_descriptor; 1378} 1379 1380 1381 1382 1383sub GetPlatformPackage ($$$) 1384{ 1385 my ($release_descriptor, $platform_descriptor, $package_descriptor) = @_; 1386 my $key = sprintf("%s/%s", $platform_descriptor->{'id'}, $package_descriptor->{'id'}); 1387 1388 my $platform_package = $release_descriptor->{'platform-packages'}->{$key}; 1389 $platform_package = {} 1390 unless defined $platform_package; 1391 1392 my $joined_descriptor = { 1393 %$platform_descriptor, 1394 %$package_descriptor, 1395 %$platform_package, 1396 'id' => $key, 1397 'platform-id' => $platform_descriptor->{'id'}, 1398 'package-id' => $package_descriptor->{'id'} 1399 }; 1400 return $joined_descriptor; 1401} 1402 1403 1404 1405 1406sub Wiki ($$) 1407{ 1408 my ($release_descriptor, $context) = @_; 1409 1410 open my $out, ">", $context->{'output-filename'}; 1411 1412 my @table_list = GetTableList($release_descriptor); 1413 foreach my $table_name (@table_list) 1414 { 1415 my @table_packages = GetPackagesForTable($release_descriptor, $table_name); 1416 my @table_languages = GetLanguagesForTable($release_descriptor, @table_packages); 1417 my @table_platforms = GetPlatformsForTable($release_descriptor, @table_packages); 1418 1419 printf "packages: %s\n", join(", ", map {$_->{'package'}->{'display-name'}} @table_packages); 1420 printf "languages: %s\n", join(", ", map {$_->{'english-name'}} @table_languages); 1421 printf "platforms: %s\n", join(", ", map {$_->{'id'}} @table_platforms); 1422 1423 print $out "{| class=\"wikitable\"\n"; 1424 1425 # Write the table head. 1426 print $out "|-\n"; 1427 print $out "! colspan=\"2\" | Language<br>The names do not refer to countries\n"; 1428 print $out "! Type\n"; 1429 foreach my $platform_descriptor (@table_platforms) 1430 { 1431 foreach my $package_type (@{$platform_descriptor->{'package-types'}}) 1432 { 1433 printf $out "! %s<br>%s bit<br>%s\n", 1434 $platform_descriptor->{'display-name'}, 1435 $platform_descriptor->{'word-size'}, 1436 uc($package_type); 1437 } 1438 } 1439 1440 foreach my $language_descriptor (@table_languages) 1441 { 1442 if ($context->{'check-links'}) 1443 { 1444 $| = 1; 1445 printf "%-5s: ", $language_descriptor->{'id'}; 1446 } 1447 1448 print $out "|-\n"; 1449 printf $out "| rowspan=\"%d\" | %s\n", scalar @table_packages, $language_descriptor->{'english-name'}; 1450 printf $out "| rowspan=\"%d\" | %s\n", scalar @table_packages, $language_descriptor->{'local-name'}; 1451 1452 my $is_first = 1; 1453 foreach my $wiki_package_descriptor (@table_packages) 1454 { 1455 my $package_descriptor = $wiki_package_descriptor->{'package'}; 1456 1457 if ($is_first) 1458 { 1459 $is_first = 0; 1460 } 1461 else 1462 { 1463 printf $out "|-\n"; 1464 } 1465 1466 # Write the name of the package, e.g. Full Install or Langpack. 1467 if (defined $package_descriptor->{'link-URL'}) 1468 { 1469 printf $out "| [%s %s]\n", 1470 $package_descriptor->{'link-URL'}, 1471 $package_descriptor->{'display-name'}; 1472 } 1473 else 1474 { 1475 printf $out "| %s\n", $package_descriptor->{'display-name'}; 1476 } 1477 1478 foreach my $platform_descriptor (@table_platforms) 1479 { 1480 my $platform_package_descriptor = GetPlatformPackage( 1481 $release_descriptor, 1482 $platform_descriptor, 1483 $package_descriptor); 1484 1485 foreach my $package_type (@{$platform_package_descriptor->{'package-types'}}) 1486 { 1487 WriteDownloadLinks( 1488 $out, 1489 $release_descriptor, 1490 $context, 1491 $language_descriptor, 1492 $wiki_package_descriptor, 1493 $platform_package_descriptor, 1494 $package_type); 1495 } 1496 } 1497 } 1498 1499 if ($context->{'check-links'}) 1500 { 1501 printf "\n"; 1502 } 1503 } 1504 1505 print $out "|}\n"; 1506 } 1507 close $out; 1508} 1509 1510 1511 1512 1513sub GetTableList ($) 1514{ 1515 my ($release_descriptor) = @_; 1516 1517 my %seen_table_names = (); 1518 my @table_names = (); 1519 foreach my $wiki_package_descriptor (@{$release_descriptor->{'wiki-packages'}}) 1520 { 1521 my $table_name = $wiki_package_descriptor->{'table'}; 1522 if ( ! $seen_table_names{$table_name}) 1523 { 1524 push @table_names, $table_name; 1525 $seen_table_names{$table_name} = 1; 1526 } 1527 } 1528 return @table_names; 1529} 1530 1531 1532 1533 1534sub GetPackagesForTable ($$) 1535{ 1536 my ($release_descriptor, $table_name) = @_; 1537 1538 my @packages = (); 1539 foreach my $wiki_package_descriptor (@{$release_descriptor->{'wiki-packages'}}) 1540 { 1541 if ($wiki_package_descriptor->{'table'} eq $table_name) 1542 { 1543 my $package_descriptor = $release_descriptor->{'packages'}->{ 1544 $wiki_package_descriptor->{'package-id'}}; 1545 $wiki_package_descriptor->{'package'} = $package_descriptor; 1546 push @packages, $wiki_package_descriptor; 1547 } 1548 } 1549 return @packages; 1550} 1551 1552 1553 1554 1555sub GetLanguagesForTable ($@) 1556{ 1557 my ($release_descriptor, @packages) = @_; 1558 1559 # Find the languages that are reference by at least one package. 1560 my %matching_languages = (); 1561 foreach my $package_descriptor (@packages) 1562 { 1563 foreach my $language_id (@{$package_descriptor->{'language-list'}}) 1564 { 1565 $matching_languages{$language_id} = 1; 1566 } 1567 } 1568 1569 # Retrieve the language descriptors for the language ids. 1570 my @matching_language_descriptors = (); 1571 foreach my $language_id (@{$release_descriptor->{'language-ids'}}) 1572 { 1573 if (defined $matching_languages{$language_id}) 1574 { 1575 my $language_descriptor = $release_descriptor->{'languages'}->{$language_id}; 1576 if (defined $language_descriptor) 1577 { 1578 push @matching_language_descriptors, $language_descriptor; 1579 } 1580 } 1581 } 1582 1583 return @matching_language_descriptors; 1584} 1585 1586 1587 1588 1589sub GetPlatformsForTable ($@) 1590{ 1591 my ($release_descriptor, @packages) = @_; 1592 1593 # Find the platforms that are reference by at least one package. 1594 my %matching_platform_ids = (); 1595 foreach my $package_descriptor (@packages) 1596 { 1597 foreach my $platform_id (@{$package_descriptor->{'platform-list'}}) 1598 { 1599 $matching_platform_ids{$platform_id} = 1; 1600 } 1601 } 1602 1603 # Retrieve the platform descriptors for the plaform ids. 1604 my @matching_platform_descriptors = (); 1605 foreach my $platform_id (@{$release_descriptor->{'platform-ids'}}) 1606 { 1607 if ($matching_platform_ids{$platform_id}) 1608 { 1609 print $platform_id."\n"; 1610 push @matching_platform_descriptors, $release_descriptor->{'platforms'}->{$platform_id}; 1611 } 1612 } 1613 1614 return @matching_platform_descriptors; 1615} 1616 1617 1618 1619 1620my $bold_text_start = "<b>"; 1621my $bold_text_end = "</b>"; 1622my $small_text_start = "<span style=\"font-size:80%\">"; 1623my $small_text_end = "</span>"; 1624my $broken_link_start = "<span style=\"color:#FF0000\">"; 1625my $broken_link_end = "</span>"; 1626 1627 1628sub WriteDownloadLinks ($$$$$$$) 1629{ 1630 my ($out, 1631 $release_descriptor, 1632 $context, 1633 $language_descriptor, 1634 $wiki_package_descriptor, 1635 $platform_package_descriptor, 1636 $package_type) = @_; 1637 1638 # Check if the current language and platform match the package. 1639 my $platform_id = $platform_package_descriptor->{'platform-id'}; 1640 if (defined $wiki_package_descriptor->{'platforms'}->{$platform_id} 1641 && defined $wiki_package_descriptor->{'languages'}->{$language_descriptor->{'id'}}) 1642 { 1643 my $archive_package_name = ""; 1644 my $extension = $package_type; 1645 if (defined $platform_package_descriptor->{'extension'}) 1646 { 1647 $extension = $platform_package_descriptor->{'extension'}; 1648 } 1649 if (defined $platform_package_descriptor->{'download-extension'}) 1650 { 1651 $extension = $platform_package_descriptor->{'download-extension'}; 1652 } 1653 $archive_package_name = "-".$package_type if ($package_type =~ /deb|rpm/); 1654 1655 my ($archive_path, $archive_name) = GetInstallationPackageName( 1656 $release_descriptor, 1657 $platform_package_descriptor, 1658 $package_type, 1659 $language_descriptor->{'id'}); 1660 1661 printf $out "| align=\"center\" | "; 1662 my $download = FindDownload( 1663 $context, 1664 $release_descriptor, 1665 $platform_package_descriptor, 1666 $package_type, 1667 $archive_name); 1668 if (defined $download) 1669 { 1670 my $url = $download->{'base-url'} . "/". $archive_name; 1671 printf $out "%s%s%s<br><br>%s%s %s<br>%s%s", 1672 $bold_text_start, 1673 CreateLink($url, $extension, $context), 1674 $bold_text_end, 1675 $small_text_start, 1676 CreateLink($url.".asc", "ASC", $context), 1677 CreateLink($url.".md5", "MD5", $context), 1678 CreateLink($url.".sha256", "SHA256", $context), 1679 $small_text_end; 1680 } 1681 printf $out "\n"; 1682 } 1683 else 1684 { 1685 printf $out "|\n"; 1686 } 1687} 1688 1689 1690 1691 1692sub FindDownload ($$$$$) 1693{ 1694 my ($context, 1695 $release_descriptor, 1696 $platform_package_descriptor, 1697 $package_type, 1698 $archive_name) = @_; 1699 1700 foreach my $download (@{$release_descriptor->{'downloads'}}) 1701 { 1702 if ($download->{'platform-id'} eq $platform_package_descriptor->{'platform-id'}) 1703 { 1704 my $url = $download->{'base-url'} . "/". $archive_name; 1705 if ($context->{'check-links'}) 1706 { 1707 if (CheckLink($url)) 1708 { 1709 # URL points to an existing file. 1710 printf "+"; 1711 return $download; 1712 } 1713 else 1714 { 1715 # URL is broken. 1716 # Try the next download area for the platform. 1717 next; 1718 } 1719 } 1720 else 1721 { 1722 # Use the URL unchecked. If there is more than one download area for the platform then only 1723 # the first is ever used. 1724 printf "."; 1725 return $download; 1726 } 1727 } 1728 } 1729 1730 if ($context->{'check-links'}) 1731 { 1732 printf "-"; 1733 } 1734 1735 return undef; 1736} 1737 1738 1739 1740 1741sub CreateLink ($$$) 1742{ 1743 my ($url, $text, $context) = @_; 1744 1745 my $is_link_broken = 0; 1746 if ($context->{'check-links'}) 1747 { 1748 if (CheckLink($url)) 1749 { 1750 $is_link_broken = 0; 1751 printf "+"; 1752 } 1753 else 1754 { 1755 $is_link_broken = 1; 1756 printf "-"; 1757 } 1758 } 1759 else 1760 { 1761 printf "."; 1762 } 1763 1764 if ( ! $is_link_broken) 1765 { 1766 return sprintf ("[%s %s]", $url, $text); 1767 } 1768 elsif ($context->{'mark-broken-links'}) 1769 { 1770 return sprintf ("%sbroken%s[%s %s]", $broken_link_start, $broken_link_end, $url, $text); 1771 } 1772 else 1773 { 1774 return sprintf ("%s", $text); 1775 } 1776} 1777 1778 1779 1780 1781=head2 CheckLink ($url) 1782 1783 Check if the file referenced by $url can be downloaded. 1784 This is determined by downloading only the header. 1785 1786=cut 1787my $LastCheckedURL = ""; 1788my $LastCheckedResult = undef; 1789sub CheckLink ($) 1790{ 1791 my ($url) = @_; 1792 1793 if ($url ne $LastCheckedURL) 1794 { 1795 my $head = LWP::Simple::head($url); 1796 $LastCheckedURL = $url; 1797 $LastCheckedResult = !!$head; 1798 } 1799 1800 return $LastCheckedResult; 1801} 1802 1803 1804 1805 1806sub SignFile ($$) 1807{ 1808 my ($signature, $filename) = @_; 1809 1810 my $command = sprintf( 1811 "gpg --armor --output %s.asc --detach-sig %s", 1812 $filename, 1813 $filename); 1814} 1815 1816 1817 1818 1819my $context = ProcessCommandline(@ARGV); 1820my $release_descriptor = ReadReleaseDescription($context->{'filename'}, $context); 1821if ($context->{'command'} eq "build") 1822{ 1823 WriteMakefile($release_descriptor, $context); 1824} 1825elsif ($context->{'command'} eq "upload") 1826{ 1827 Upload($release_descriptor, $context); 1828} 1829elsif ($context->{'command'} eq "wiki") 1830{ 1831 Wiki($release_descriptor, $context); 1832} 1833