1#!/usr/bin/perl -w 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 24use Getopt::Long; 25use Pod::Usage; 26use File::Path; 27use File::Spec; 28use File::Basename; 29use XML::LibXML; 30use Digest; 31use Archive::Zip; 32use Archive::Extract; 33 34use installer::ziplist; 35use installer::logger; 36use installer::windows::msiglobal; 37use installer::patch::Msi; 38use installer::patch::ReleasesList; 39use installer::patch::Version; 40 41use strict; 42 43 44=head1 NAME 45 46 patch_tool.pl - Create Windows MSI patches. 47 48=head1 SYNOPSIS 49 50 patch_tool.pl command [options] 51 52 Commands: 53 create create patches 54 apply apply patches 55 56 Options: 57 -p|--product-name <product-name> 58 The product name, eg Apache_OpenOffice 59 -o|--output-path <path> 60 Path to the instsetoo_native platform output tree 61 -d|--data-path <path> 62 Path to the data directory that is expected to be under version control. 63 --source-version <major>.<minor>.<micro> 64 The version that is to be patched. 65 --target-version <major>.<minor>.<micro> 66 The version after the patch has been applied. 67 68=head1 DESCRIPTION 69 70 Creates windows MSP patch files, one for each relevant language. 71 Patches convert an installed OpenOffice to the target version. 72 73 Required data are: 74 Installation sets of the source versions 75 Taken from ext_sources/ 76 Downloaded from archive.apache.org on demand 77 78 Installation set of the target version 79 This is expected to be the current version. 80 81=cut 82 83# my $ImageFamily = "MNPapps"; 84# The ImageFamily name has to have 1-8 alphanumeric characters. 85my $ImageFamily = "AOO"; 86my $SourceImageName = "Source"; 87my $TargetImageName = "Target"; 88 89 90 91sub ProcessCommandline () 92{ 93 my $arguments = { 94 'product-name' => undef, 95 'output-path' => undef, 96 'data-path' => undef, 97 'lst-file' => undef, 98 'source-version' => undef, 99 'target-version' => undef}; 100 101 if ( ! GetOptions( 102 "product-name=s", \$arguments->{'product-name'}, 103 "output-path=s", \$arguments->{'output-path'}, 104 "data-path=s" => \$arguments->{'data-path'}, 105 "lst-file=s" => \$arguments->{'lst-file'}, 106 "source-version:s" => \$arguments->{'source-version'}, 107 "target-version:s" => \$arguments->{'target-version'} 108 )) 109 { 110 pod2usage(2); 111 } 112 113 # Only the command should be left in @ARGV. 114 pod2usage(2) unless scalar @ARGV == 1; 115 $arguments->{'command'} = shift @ARGV; 116 117 # At the moment we only support patches on windows. When this 118 # is extended in the future we need the package format as an 119 # argument. 120 $arguments->{'package-format'} = "msi"; 121 122 return $arguments; 123} 124 125 126 127 128sub GetSourceMsiPath ($$) 129{ 130 my ($context, $language) = @_; 131 my $unpacked_path = File::Spec->catfile( 132 $context->{'output-path'}, 133 $context->{'product-name'}, 134 $context->{'package-format'}, 135 installer::patch::Version::ArrayToDirectoryName( 136 installer::patch::Version::StringToNumberArray( 137 $context->{'source-version'})), 138 $language); 139} 140 141 142 143 144sub GetTargetMsiPath ($$) 145{ 146 my ($context, $language) = @_; 147 return File::Spec->catfile( 148 $context->{'output-path'}, 149 $context->{'product-name'}, 150 $context->{'package-format'}, 151 "install", 152 $language); 153} 154 155 156 157sub ProvideInstallationSets ($$) 158{ 159 my ($context, $language) = @_; 160 161 # Assume that the target installation set is located in the output tree. 162 my $target_path = GetTargetMsiPath($context, $language); 163 if ( ! -d $target_path) 164 { 165 installer::logger::PrintError("can not find target installation set at '%s'\n", $target_path); 166 return 0; 167 } 168 my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'}); 169 my $target_msi_file = File::Spec->catfile( 170 $target_path, 171 sprintf("openoffice%d%d%d.msi", $target_version[0], $target_version[1], $target_version[2])); 172 if ( ! -f $target_msi_file) 173 { 174 installer::logger::PrintError("can not find target msi file at '%s'\n", $target_msi_file); 175 return 0; 176 } 177 178 return 1; 179} 180 181 182 183 184sub GetLanguages () 185{ 186 # The set of languages is taken from the WITH_LANG environment variable. 187 # If that is missing or is empty then the default 'en-US' is used instead. 188 my @languages = ("en-US"); 189 my $with_lang = $ENV{'WITH_LANG'}; 190 if (defined $with_lang && $with_lang ne "") 191 { 192 @languages = split(/\s+/, $with_lang); 193 } 194 return @languages; 195} 196 197 198 199 200sub FindValidLanguages ($$$) 201{ 202 my ($context, $release_data, $languages) = @_; 203 204 my @valid_languages = (); 205 foreach my $language (@$languages) 206 { 207 if ( ! ProvideInstallationSets($context, $language)) 208 { 209 installer::logger::PrintError(" '%s' has no target installation set\n", $language); 210 } 211 elsif ( ! defined $release_data->{$language}) 212 { 213 installer::logger::PrintError(" '%s' is not a released language for version %s\n", 214 $language, 215 $context->{'source-version'}); 216 } 217 else 218 { 219 push @valid_languages, $language; 220 } 221 } 222 223 return @valid_languages; 224} 225 226 227 228 229sub ProvideSourceInstallationSet ($$$) 230{ 231 my ($context, $language, $release_data) = @_; 232 233 my $url = $release_data->{$language}->{'URL'}; 234 $url =~ /^(.*)\/([^\/]*)$/; 235 my ($location, $basename) = ($1,$2); 236 237 my $ext_sources_path = $ENV{'TARFILE_LOCATION'}; 238 if ( ! -d $ext_sources_path) 239 { 240 installer::logger::PrintError("Can not determine the path to ext_sources/.\n"); 241 installer::logger::PrintError("Maybe SOURCE_ROOT_DIR has not been correctly set in the environment?"); 242 return 0; 243 } 244 245 # We need the unpacked installation set in <platform>/<product>/<package>/<source-version>, 246 # eg wntmsci12.pro/Apache_OpenOffice/msi/v-4-0-0. 247 my $unpacked_path = GetSourceMsiPath($context, $language); 248 if ( ! -d $unpacked_path) 249 { 250 # Make sure that the downloadable installation set (.exe) is present in ext_sources/. 251 my $filename = File::Spec->catfile($ext_sources_path, $basename); 252 if ( -f $filename) 253 { 254 PrintInfo("%s is already present in ext_sources/. Nothing to do\n", $basename); 255 } 256 else 257 { 258 return 0 if ! installer::patch::Download($language, $release_data, $location, $basename, $filename); 259 return 0 if ! -f $filename; 260 } 261 262 # Unpack the installation set. 263 if ( -d $unpacked_path) 264 { 265 # Take the existence of the destination path as proof that the 266 # installation set was successfully unpacked before. 267 } 268 else 269 { 270 installer::patch::InstallationSet::Unpack($filename, $unpacked_path); 271 } 272 } 273} 274 275 276 277 278# Find the source and target version between which the patch will be 279# created. Typically the target version is the current version and 280# the source version is the version of the previous release. 281sub DetermineVersions ($$) 282{ 283 my ($context, $variables) = @_; 284 285 if (defined $context->{'source-version'} && defined $context->{'target-version'}) 286 { 287 # Both source and target version have been specified on the 288 # command line. There remains nothing to be be done. 289 return; 290 } 291 292 if ( ! defined $context->{'target-version'}) 293 { 294 # Use the current version as target version. 295 $context->{'target-version'} = $variables->{PRODUCTVERSION}; 296 } 297 298 my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'}); 299 shift @target_version; 300 my $is_target_version_major = 1; 301 foreach my $number (@target_version) 302 { 303 $is_target_version_major = 0 if ($number ne "0"); 304 } 305 if ($is_target_version_major) 306 { 307 installer::logger::PrintError("can not create patch where target version is a new major version (%s)\n", 308 $context->{'target-version'}); 309 die; 310 } 311 312 if ( ! defined $context->{'source-version'}) 313 { 314 my $releases = installer::patch::ReleasesList::Instance(); 315 316 # Search for target release in the list of previous releases. 317 # If it is found, use the previous version as source version. 318 # Otherwise use the last released version. 319 my $last_release = undef; 320 foreach my $release (@{$releases->{'releases'}}) 321 { 322 last if ($release eq $context->{'target-version'}); 323 $last_release = $release; 324 } 325 $context->{'source-version'} = $last_release; 326 } 327} 328 329 330 331 332=head2 CheckUpgradeCode($source_msi, $target_msi) 333 334 The 'UpgradeCode' values in the 'Property' table differs from source to target 335 336=cut 337sub CheckUpgradeCode($$) 338{ 339 my ($source_msi, $target_msi) = @_; 340 341 my $source_upgrade_code = $source_msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value"); 342 my $target_upgrade_code = $target_msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value"); 343 344 if ($source_upgrade_code eq $target_upgrade_code) 345 { 346 $installer::logger::Info->printf("Error: The UpgradeCode properties have to differ but are both '%s'\n", 347 $source_upgrade_code); 348 return 0; 349 } 350 else 351 { 352 $installer::logger::Info->printf("OK: UpgradeCode values are identical\n"); 353 return 1; 354 } 355} 356 357 358 359 360=head2 CheckProductCode($source_msi, $target_msi) 361 362 The 'ProductCode' values in the 'Property' tables remain the same. 363 364=cut 365sub CheckProductCode($$) 366{ 367 my ($source_msi, $target_msi) = @_; 368 369 my $source_product_code = $source_msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value"); 370 my $target_product_code = $target_msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value"); 371 372 if ($source_product_code ne $target_product_code) 373 { 374 $installer::logger::Info->printf("Error: The ProductCode properties have to remain the same but are\n"); 375 $installer::logger::Info->printf(" '%s' and '%s'\n", 376 $source_product_code, 377 $target_product_code); 378 return 0; 379 } 380 else 381 { 382 $installer::logger::Info->printf("OK: ProductCode properties differ\n"); 383 return 1; 384 } 385} 386 387 388 389 390=head2 CheckBuildIdCode($source_msi, $target_msi) 391 392 The 'PRODUCTBUILDID' values in the 'Property' tables (not the AOO build ids) differ and the 393 target value is higher than the source value. 394 395=cut 396sub CheckBuildIdCode($$) 397{ 398 my ($source_msi, $target_msi) = @_; 399 400 my $source_build_id = $source_msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value"); 401 my $target_build_id = $target_msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value"); 402 403 if ($source_build_id >= $target_build_id) 404 { 405 $installer::logger::Info->printf( 406 "Error: The PRODUCTBUILDID properties have to increase but are '%s' and '%s'\n", 407 $source_build_id, 408 $target_build_id); 409 return 0; 410 } 411 else 412 { 413 $installer::logger::Info->printf("OK: source build id is lower than target build id\n"); 414 return 1; 415 } 416} 417 418 419 420 421sub CheckProductName ($$) 422{ 423 my ($source_msi, $target_msi) = @_; 424 425 my $source_product_name = $source_msi->GetTable("Property")->GetValue("Property", "DEFINEDPRODUCT", "Value"); 426 my $target_product_name = $target_msi->GetTable("Property")->GetValue("Property", "DEFINEDPRODUCT", "Value"); 427 428 if ($source_product_name ne $target_product_name) 429 { 430 $installer::logger::Info->printf("Error: product names of are not identical:\n"); 431 $installer::logger::Info->printf(" %s != %s\n", $source_product_name, $target_product_name); 432 return 0; 433 } 434 else 435 { 436 $installer::logger::Info->printf("OK: product names are identical\n"); 437 return 1; 438 } 439} 440 441 442 443 444=head2 CheckRemovedFiles($source_msi, $target_msi) 445 446 Files and components must not be deleted. 447 448=cut 449sub CheckRemovedFiles($$) 450{ 451 my ($source_msi, $target_msi) = @_; 452 453 # Get the 'File' tables. 454 my $source_file_table = $source_msi->GetTable("File"); 455 my $target_file_table = $target_msi->GetTable("File"); 456 457 # Create data structures for fast lookup. 458 my @source_files = map {$_->GetValue("File")} @{$source_file_table->GetAllRows()}; 459 my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()}; 460 461 # Search for removed files (files in source that are missing from target). 462 my $removed_file_count = 0; 463 foreach my $uniquename (@source_files) 464 { 465 if ( ! defined $target_file_map{$uniquename}) 466 { 467 ++$removed_file_count; 468 } 469 } 470 471 if ($removed_file_count > 0) 472 { 473 $installer::logger::Info->printf("Error: %d files have been removed\n", $removed_file_count); 474 return 0; 475 } 476 else 477 { 478 $installer::logger::Info->printf("OK: no files have been removed\n"); 479 return 1; 480 } 481} 482 483 484 485 486=head2 CheckNewFiles($source_msi, $target_msi) 487 488 New files have to be in new components. 489 490=cut 491sub CheckNewFiles($$) 492{ 493 my ($source_msi, $target_msi) = @_; 494 495 # Get the 'File' tables. 496 my $source_file_table = $source_msi->GetTable("File"); 497 my $target_file_table = $target_msi->GetTable("File"); 498 499 # Create data structures for fast lookup. 500 my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()}; 501 my @target_files = map {$_->GetValue("File")} @{$target_file_table->GetAllRows()}; 502 503 # Search for added files (files in target that where not in source). 504 my $added_file_count = 0; 505 foreach my $uniquename (@target_files) 506 { 507 if ( ! defined $source_file_map{$uniquename}) 508 { 509 ++$added_file_count; 510 } 511 } 512 513 if ($added_file_count > 0) 514 { 515 $installer::logger::Info->printf("Warning: %d files have been added\n", $added_file_count); 516 517 $installer::logger::Info->printf("Check for new files being part of new components is not yet implemented\n"); 518 519 return 1; 520 } 521 else 522 { 523 $installer::logger::Info->printf("OK: no files have been added\n"); 524 return 1; 525 } 526} 527 528 529 530 531=head2 CheckComponentSets($source_msi, $target_msi) 532 533 Components must not be removed but can be added. 534 Features of added components have also to be new. 535 536=cut 537sub CheckComponentSets($$) 538{ 539 my ($source_msi, $target_msi) = @_; 540 541 # Get the 'Component' tables. 542 my $source_component_table = $source_msi->GetTable("Component"); 543 my $target_component_table = $target_msi->GetTable("Component"); 544 545 # Create data structures for fast lookup. 546 my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()}; 547 my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()}; 548 549 # Check that no component has been removed. 550 my @removed_components = (); 551 foreach my $componentname (keys %source_component_map) 552 { 553 if ( ! defined $target_component_map{$componentname}) 554 { 555 push @removed_components, $componentname; 556 } 557 } 558 if (scalar @removed_components > 0) 559 { 560 # There are removed components. 561 562 # Check if any of them is not a registry component. 563 my $is_file_component_removed = 0; 564 foreach my $componentname (@removed_components) 565 { 566 if ($componentname !~ /^registry/) 567 { 568 $is_file_component_removed = 1; 569 } 570 } 571 if ($is_file_component_removed) 572 { 573 $installer::logger::Info->printf( 574 "Error: %d components have been removed, some of them are file components:\n", 575 scalar @removed_components); 576 $installer::logger::Info->printf(" %s\n", join(", ", @removed_components)); 577 return 0; 578 } 579 else 580 { 581 $installer::logger::Info->printf( 582 "Error: %d components have been removed, all of them are registry components:\n", 583 scalar @removed_components); 584 return 0; 585 } 586 } 587 588 # Check that added components belong to new features. 589 my @added_components = (); 590 foreach my $componentname (keys %target_component_map) 591 { 592 if ( ! defined $source_component_map{$componentname}) 593 { 594 push @added_components, $componentname; 595 } 596 } 597 598 if (scalar @added_components > 0) 599 { 600 # Check if any of them is not a registry component. 601 my $is_file_component_removed = 0; 602 foreach my $componentname (@removed_components) 603 { 604 if ($componentname !~ /^registry/) 605 { 606 $is_file_component_removed = 1; 607 } 608 } 609 610 if ($is_file_component_removed) 611 { 612 $installer::logger::Info->printf( 613 "Warning: %d components have been addded\n", 614 scalar @added_components); 615 $installer::logger::Info->printf( 616 "Test for new components belonging to new features has not yet been implemented\n"); 617 return 0; 618 } 619 else 620 { 621 $installer::logger::Info->printf( 622 "Warning: %d components have been addded, all of them registry components\n", 623 scalar @added_components); 624 } 625 } 626 627 $installer::logger::Info->printf("OK: component sets in source and target are compatible\n"); 628 return 1; 629} 630 631 632 633 634=head2 CheckComponent($source_msi, $target_msi) 635 636 In the 'Component' table the 'ComponentId' and 'Component' values 637 for corresponding componts in the source and target release have 638 to be identical. 639 640=cut 641sub CheckComponentValues($$$) 642{ 643 my ($source_msi, $target_msi, $variables) = @_; 644 645 # Get the 'Component' tables. 646 my $source_component_table = $source_msi->GetTable("Component"); 647 my $target_component_table = $target_msi->GetTable("Component"); 648 649 # Create data structures for fast lookup. 650 my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()}; 651 my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()}; 652 653 my @differences = (); 654 my $comparison_count = 0; 655 while (my ($componentname, $source_component_row) = each %source_component_map) 656 { 657 my $target_component_row = $target_component_map{$componentname}; 658 if (defined $target_component_row) 659 { 660 ++$comparison_count; 661 if ($source_component_row->GetValue("ComponentId") ne $target_component_row->GetValue("ComponentId")) 662 { 663 push @differences, [ 664 $componentname, 665 $source_component_row->GetValue("ComponentId"), 666 $target_component_row->GetValue("ComponentId"), 667 $target_component_row->GetValue("Component"), 668 ]; 669 } 670 } 671 } 672 673 if (scalar @differences > 0) 674 { 675 $installer::logger::Info->printf( 676 "Error: there are %d components with different 'ComponentId' values after %d comparisons.\n", 677 scalar @differences, 678 $comparison_count); 679 foreach my $item (@differences) 680 { 681 $installer::logger::Info->printf("%s %s\n", $item->[1], $item->[2]); 682 } 683 return 0; 684 } 685 else 686 { 687 $installer::logger::Info->printf("OK: components in source and target are identical\n"); 688 return 1; 689 } 690} 691 692 693 694 695=head2 CheckFileSequence($source_msi, $target_msi) 696 697 In the 'File' table the 'Sequence' numbers for corresponding files has to be identical. 698 699=cut 700sub CheckFileSequence($$) 701{ 702 my ($source_msi, $target_msi) = @_; 703 704 # Get the 'File' tables. 705 my $source_file_table = $source_msi->GetTable("File"); 706 my $target_file_table = $target_msi->GetTable("File"); 707 708 # Create temporary data structures for fast access. 709 my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()}; 710 my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()}; 711 712 # Search files with mismatching sequence numbers. 713 my @mismatching_files = (); 714 while (my ($uniquename,$source_file_row) = each %source_file_map) 715 { 716 my $target_file_row = $target_file_map{$uniquename}; 717 if (defined $target_file_row) 718 { 719 if ($source_file_row->GetValue('Sequence') ne $target_file_row->GetValue('Sequence')) 720 { 721 push @mismatching_files, [ 722 $uniquename, 723 $source_file_row, 724 $target_file_row 725 ]; 726 } 727 } 728 } 729 730 if (scalar @mismatching_files > 0) 731 { 732 $installer::logger::Info->printf("Error: there are %d files with mismatching 'Sequence' numbers\n", 733 scalar @mismatching_files); 734 foreach my $item (@mismatching_files) 735 { 736 $installer::logger::Info->printf(" %s: %d != %d\n", 737 $item->[0], 738 $item->[1]->GetValue("Sequence"), 739 $item->[2]->GetValue("Sequence")); 740 } 741 return 0; 742 } 743 else 744 { 745 $installer::logger::Info->printf("OK: all files have matching 'Sequence' numbers\n"); 746 return 1; 747 } 748} 749 750 751 752 753=head2 CheckFileSequenceUnique($source_msi, $target_msi) 754 755 In the 'File' table the 'Sequence' values have to be unique. 756 757=cut 758sub CheckFileSequenceUnique($$) 759{ 760 my ($source_msi, $target_msi) = @_; 761 762 # Get the 'File' tables. 763 my $target_file_table = $target_msi->GetTable("File"); 764 765 my %sequence_numbers = (); 766 my $collision_count = 0; 767 foreach my $row (@{$target_file_table->GetAllRows()}) 768 { 769 my $sequence_number = $row->GetValue("Sequence"); 770 if (defined $sequence_numbers{$sequence_number}) 771 { 772 ++$collision_count; 773 } 774 else 775 { 776 $sequence_numbers{$sequence_number} = 1; 777 } 778 } 779 780 if ($collision_count > 0) 781 { 782 $installer::logger::Info->printf("Error: there are %d collisions ofn the sequence numbers\n", 783 $collision_count); 784 return 0; 785 } 786 else 787 { 788 $installer::logger::Info->printf("OK: sequence numbers are unique\n"); 789 return 1; 790 } 791} 792 793 794 795 796=head2 CheckFileSequenceHoles ($target_msi) 797 798 Check the sequence numbers of the target msi if the n files use numbers 1..n or if there are holes. 799 Holes are reported as warnings. 800 801=cut 802sub CheckFileSequenceHoles ($$) 803{ 804 my ($source_msi, $target_msi) = @_; 805 806 my $target_file_table = $target_msi->GetTable("File"); 807 my %sequence_numbers = map {$_->GetValue("Sequence") => $_} @{$target_file_table->GetAllRows()}; 808 my @sorted_sequence_numbers = sort {$a <=> $b} keys %sequence_numbers; 809 my $expected_next_sequence_number = 1; 810 my @holes = (); 811 foreach my $sequence_number (@sorted_sequence_numbers) 812 { 813 if ($sequence_number != $expected_next_sequence_number) 814 { 815 push @holes, [$expected_next_sequence_number, $sequence_number-1]; 816 } 817 $expected_next_sequence_number = $sequence_number+1; 818 } 819 if (scalar @holes > 0) 820 { 821 $installer::logger::Info->printf("Warning: sequence numbers have %d holes\n"); 822 foreach my $hole (@holes) 823 { 824 if ($hole->[0] != $hole->[1]) 825 { 826 $installer::logger::Info->printf(" %d\n", $hole->[0]); 827 } 828 else 829 { 830 $installer::logger::Info->printf(" %d -> %d\n", $hole->[0], $hole->[1]); 831 } 832 } 833 } 834 else 835 { 836 $installer::logger::Info->printf("OK: there are no holes in the sequence numbers\n"); 837 } 838 return 1; 839} 840 841 842 843 844=head2 CheckRegistryItems($source_msi, $target_msi) 845 846 In the 'Registry' table the 'Component_' and 'Key' values must not 847 depend on the version number (beyond the unchanging major 848 version). 849 850 'Value' values must only depend on the major version number to 851 avoid duplicate entries in the start menu. 852 853 Violations are reported as warnings for now. 854 855=cut 856sub CheckRegistryItems($$$) 857{ 858 my ($source_msi, $target_msi, $product_name) = @_; 859 860 # Get the registry tables. 861 my $source_registry_table = $source_msi->GetTable("Registry"); 862 my $target_registry_table = $target_msi->GetTable("Registry"); 863 864 my $registry_index = $target_registry_table->GetColumnIndex("Registry"); 865 my $component_index = $target_registry_table->GetColumnIndex("Component_"); 866 867 # Create temporary data structures for fast access. 868 my %source_registry_map = map {$_->GetValue($registry_index) => $_} @{$source_registry_table->GetAllRows()}; 869 my %target_registry_map = map {$_->GetValue($registry_index) => $_} @{$target_registry_table->GetAllRows()}; 870 871 # Prepare version numbers to search. 872 my $source_version_number = $source_msi->{'version'}; 873 my $source_version_nodots = installer::patch::Version::ArrayToNoDotName( 874 installer::patch::Version::StringToNumberArray($source_version_number)); 875 my $source_component_pattern = lc($product_name).$source_version_nodots; 876 my $target_version_number = $target_msi->{'version'}; 877 my $target_version_nodots = installer::patch::Version::ArrayToNoDotName( 878 installer::patch::Version::StringToNumberArray($target_version_number)); 879 my $target_component_pattern = lc($product_name).$target_version_nodots; 880 881 foreach my $source_row (values %source_registry_map) 882 { 883 my $target_row = $target_registry_map{$source_row->GetValue($registry_index)}; 884 if ( ! defined $target_row) 885 { 886 $installer::logger::Info->printf("Error: sets of registry entries differs\n"); 887 return 1; 888 } 889 890 my $source_component_name = $source_row->GetValue($component_index); 891 my $target_component_name = $source_row->GetValue($component_index); 892 893 } 894 895 $installer::logger::Info->printf("OK: registry items are OK\n"); 896 return 1; 897} 898 899 900 901 902=head2 903 904 Component->KeyPath must not change. (see component.pm/get_component_keypath) 905 906=cut 907sub CheckComponentKeyPath ($$) 908{ 909 my ($source_msi, $target_msi) = @_; 910 911 # Get the registry tables. 912 my $source_component_table = $source_msi->GetTable("Component"); 913 my $target_component_table = $target_msi->GetTable("Component"); 914 915 # Create temporary data structures for fast access. 916 my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()}; 917 my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()}; 918 919 my @mismatches = (); 920 while (my ($componentname, $source_component_row) = each %source_component_map) 921 { 922 my $target_component_row = $target_component_map{$componentname}; 923 if (defined $target_component_row) 924 { 925 my $source_keypath = $source_component_row->GetValue("KeyPath"); 926 my $target_keypath = $target_component_row->GetValue("KeyPath"); 927 if ($source_keypath ne $target_keypath) 928 { 929 push @mismatches, [$componentname, $source_keypath, $target_keypath]; 930 } 931 } 932 } 933 934 if (scalar @mismatches > 0) 935 { 936 $installer::logger::Info->printf( 937 "Error: there are %d mismatches in the 'KeyPath' column of the 'Component' table\n", 938 scalar @mismatches); 939 940 foreach my $item (@mismatches) 941 { 942 $installer::logger::Info->printf( 943 " %s: %s != %s\n", 944 $item->[0], 945 $item->[1], 946 $item->[2]); 947 } 948 949 return 0; 950 } 951 else 952 { 953 $installer::logger::Info->printf( 954 "OK: no mismatches in the 'KeyPath' column of the 'Component' table\n"); 955 return 1; 956 } 957} 958 959 960 961 962sub Check ($$$$) 963{ 964 my ($source_msi, $target_msi, $variables, $product_name) = @_; 965 966 $installer::logger::Info->printf("checking if source and target releases are compatable\n"); 967 $installer::logger::Info->increase_indentation(); 968 969 my $result = 1; 970 971 $result &&= CheckUpgradeCode($source_msi, $target_msi); 972 $result &&= CheckProductCode($source_msi, $target_msi); 973 $result &&= CheckBuildIdCode($source_msi, $target_msi); 974 $result &&= CheckProductName($source_msi, $target_msi); 975 $result &&= CheckRemovedFiles($source_msi, $target_msi); 976 $result &&= CheckNewFiles($source_msi, $target_msi); 977 $result &&= CheckComponentSets($source_msi, $target_msi); 978 $result &&= CheckComponentValues($source_msi, $target_msi, $variables); 979 $result &&= CheckFileSequence($source_msi, $target_msi); 980 $result &&= CheckFileSequenceUnique($source_msi, $target_msi); 981 $result &&= CheckFileSequenceHoles($source_msi, $target_msi); 982 $result &&= CheckRegistryItems($source_msi, $target_msi, $product_name); 983 $result &&= CheckComponentKeyPath($source_msi, $target_msi); 984 985 $installer::logger::Info->decrease_indentation(); 986 987 return $result; 988} 989 990 991 992 993=head2 FindPcpTemplate () 994 995 The template.pcp file is part of the Windows SDK. 996 997=cut 998sub FindPcpTemplate () 999{ 1000 my $psdk_home = $ENV{'PSDK_HOME'}; 1001 if ( ! defined $psdk_home) 1002 { 1003 $installer::logger::Info->printf("Error: the PSDK_HOME environment variable is not set.\n"); 1004 $installer::logger::Info->printf(" did you load the AOO build environment?\n"); 1005 $installer::logger::Info->printf(" you may want to use the --with-psdk-home configure option\n"); 1006 return undef; 1007 } 1008 if ( ! -d $psdk_home) 1009 { 1010 $installer::logger::Info->printf( 1011 "Error: the PSDK_HOME environment variable does not point to a valid directory: %s\n", 1012 $psdk_home); 1013 return undef; 1014 } 1015 1016 my $schema_path = File::Spec->catfile($psdk_home, "Bin", "msitools", "Schemas", "MSI"); 1017 if ( ! -d $schema_path) 1018 { 1019 $installer::logger::Info->printf("Error: Can not locate the msi template folder in the Windows SDK\n"); 1020 $installer::logger::Info->printf(" %s\n", $schema_path); 1021 $installer::logger::Info->printf(" Is the Windows SDK properly installed?\n"); 1022 return undef; 1023 } 1024 1025 my $schema_filename = File::Spec->catfile($schema_path, "template.pcp"); 1026 if ( ! -f $schema_filename) 1027 { 1028 $installer::logger::Info->printf("Error: Can not locate the pcp template at\n"); 1029 $installer::logger::Info->printf(" %s\n", $schema_filename); 1030 $installer::logger::Info->printf(" Is the Windows SDK properly installed?\n"); 1031 return undef; 1032 } 1033 1034 return $schema_filename; 1035} 1036 1037 1038 1039 1040sub SetupPcpPatchMetadataTable ($$$) 1041{ 1042 my ($pcp, $source_msi, $target_msi) = @_; 1043 1044 # Determine values for eg product name and source and new version. 1045 my $source_version = $source_msi->{'version'}; 1046 my $target_version = $target_msi->{'version'}; 1047 1048 my $property_table = $target_msi->GetTable("Property"); 1049 my $display_product_name = $property_table->GetValue("Property", "DEFINEDPRODUCT", "Value"); 1050 1051 # Set table. 1052 my $table = $pcp->GetTable("PatchMetadata"); 1053 $table->SetRow( 1054 "Company", "", 1055 "*Property", "Description", 1056 "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version) 1057 ); 1058 $table->SetRow( 1059 "Company", "", 1060 "*Property", "DisplayName", 1061 "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version) 1062 ); 1063 $table->SetRow( 1064 "Company", "", 1065 "*Property", "ManufacturerName", 1066 "Value", $property_table->GetValue("Property", "Manufacturer", "Value"), 1067 ); 1068 $table->SetRow( 1069 "Company", "", 1070 "*Property", "MoreInfoURL", 1071 "Value", $property_table->GetValue("Property", "ARPURLINFOABOUT", "Value") 1072 ); 1073 $table->SetRow( 1074 "Company", "", 1075 "*Property", "TargetProductName", 1076 "Value", $property_table->GetValue("Property", "ProductName", "Value") 1077 ); 1078 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time); 1079 1080 $table->SetRow( 1081 "Company", "", 1082 "*Property", "CreationTimeUTC", 1083 "Value", sprintf("%d/%d/%d %d:%02d", $mon+1,$mday,$year+1900,$hour,$min) 1084 ); 1085} 1086 1087 1088 1089 1090sub SetupPropertiesTable ($$) 1091{ 1092 my ($pcp, $msp_filename) = @_; 1093 1094 my $table = $pcp->GetTable("Properties"); 1095 1096 $table->SetRow( 1097 "*Name", "PatchOutputPath", 1098 "Value", installer::patch::Tools::ToWindowsPath($msp_filename) 1099 ); 1100 # Request at least Windows installer 2.0. 1101 # Version 2.0 allows us to omit some values from ImageFamilies table. 1102 $table->SetRow( 1103 "*Name", "MinimumRequiredMsiVersion", 1104 "Value", 200 1105 ); 1106 # Allow diffs for binary files. 1107 $table->SetRow( 1108 "*Name", "IncludeWholeFilesOnly", 1109 "Value", 0 1110 ); 1111 1112 my $uuid = installer::windows::msiglobal::create_guid(); 1113 my $uuid_string = "{" . $uuid . "}"; 1114 $table->SetRow( 1115 "*Name", "PatchGUID", 1116 "Value", $uuid_string 1117 ); 1118 $installer::logger::Info->printf("created new PatchGUID %s\n", $uuid_string); 1119 1120 # Prevent sequence table from being generated. 1121 $table->SetRow( 1122 "*Name", "SEQUENCE_DATA_GENERATION_DISABLED", 1123 "Value", 1); 1124} 1125 1126 1127 1128 1129sub SetupImageFamiliesTable ($) 1130{ 1131 my ($pcp) = @_; 1132 1133 $pcp->GetTable("ImageFamilies")->SetRow( 1134 "Family", $ImageFamily, 1135 "MediaSrcPropName", "",#"MNPSrcPropName", 1136 "MediaDiskId", "", 1137 "FileSequenceStart", "", 1138 "DiskPrompt", "", 1139 "VolumeLabel", ""); 1140} 1141 1142 1143 1144 1145sub SetupUpgradedImagesTable ($$) 1146{ 1147 my ($pcp, $target_msi_path) = @_; 1148 1149 my $msi_path = installer::patch::Tools::ToWindowsPath($target_msi_path); 1150 $pcp->GetTable("UpgradedImages")->SetRow( 1151 "Upgraded", $TargetImageName, 1152 "MsiPath", $msi_path, 1153 "PatchMsiPath", "", 1154 "SymbolPaths", "", 1155 "Family", $ImageFamily); 1156} 1157 1158 1159 1160 1161sub SetupTargetImagesTable ($$) 1162{ 1163 my ($pcp, $source_msi_path) = @_; 1164 1165 $pcp->GetTable("TargetImages")->SetRow( 1166 "Target", $SourceImageName, 1167 "MsiPath", installer::patch::Tools::ToWindowsPath($source_msi_path), 1168 "SymbolPaths", "", 1169 "Upgraded", $TargetImageName, 1170 "Order", 1, 1171 "ProductValidateFlags", "", 1172 "IgnoreMissingSrcFiles", 0); 1173} 1174 1175 1176 1177 1178sub SetAdditionalValues ($%) 1179{ 1180 my ($pcp, %data) = @_; 1181 1182 while (my ($key,$value) = each(%data)) 1183 { 1184 $key =~ /^([^\/]+)\/([^:]+):(.+)$/ 1185 || die("invalid key format"); 1186 my ($table_name, $key_column,$key_value) = ($1,$2,$3); 1187 $value =~ /^([^:]+):(.*)$/ 1188 || die("invalid value format"); 1189 my ($value_column,$value_value) = ($1,$2); 1190 1191 my $table = $pcp->GetTable($table_name); 1192 $table->SetRow( 1193 "*".$key_column, $key_value, 1194 $value_column, $value_value); 1195 } 1196} 1197 1198 1199 1200 1201sub CreatePcp ($$$$$$%) 1202{ 1203 my ($source_msi, 1204 $target_msi, 1205 $language, 1206 $context, 1207 $msp_path, 1208 $pcp_schema_filename, 1209 %additional_values) = @_; 1210 1211 # Create filenames. 1212 my $pcp_filename = File::Spec->catfile($msp_path, "openoffice.pcp"); 1213 my $msp_filename = File::Spec->catfile($msp_path, "openoffice.msp"); 1214 1215 # Setup msp path and filename. 1216 unlink($pcp_filename) if -f $pcp_filename; 1217 if ( ! File::Copy::copy($pcp_schema_filename, $pcp_filename)) 1218 { 1219 $installer::logger::Info->printf("Error: could not create openoffice.pcp as copy of pcp schema\n"); 1220 $installer::logger::Info->printf(" %s\n", $pcp_schema_filename); 1221 $installer::logger::Info->printf(" %s\n", $pcp_filename); 1222 return undef; 1223 } 1224 my $pcp = installer::patch::Msi->new( 1225 $pcp_filename, 1226 undef, 1227 undef, 1228 $language, 1229 $context->{'product-name'}); 1230 1231 # Store some values in the pcp for easy reference in the msp creation. 1232 $pcp->{'msp_filename'} = $msp_filename; 1233 1234 SetupPcpPatchMetadataTable($pcp, $source_msi, $target_msi); 1235 SetupPropertiesTable($pcp, $msp_filename); 1236 SetupImageFamiliesTable($pcp); 1237 SetupUpgradedImagesTable($pcp, $target_msi->{'filename'}); 1238 SetupTargetImagesTable($pcp, $source_msi->{'filename'}); 1239 1240 SetAdditionalValues(%additional_values); 1241 1242 $pcp->Commit(); 1243 1244 # Remove the PatchSequence table to avoid MsiMsp error message: 1245 # "Since MSI 3.0 will block installation of major upgrade patches with 1246 # sequencing information, creation of such patches is blocked." 1247 #$pcp->RemoveTable("PatchSequence"); 1248 # TODO: alternatively add property SEQUENCE_DATA_GENERATION_DISABLED to pcp Properties table. 1249 1250 1251 $installer::logger::Info->printf("created pcp file at\n"); 1252 $installer::logger::Info->printf(" %s\n", $pcp->{'filename'}); 1253 1254 return $pcp; 1255} 1256 1257 1258 1259 1260sub ShowLog ($$$$) 1261{ 1262 my ($log_path, $log_filename, $log_basename, $new_title) = @_; 1263 1264 if ( -f $log_filename) 1265 { 1266 my $destination_path = File::Spec->catfile($log_path, $log_basename); 1267 File::Path::make_path($destination_path) if ! -d $destination_path; 1268 my $command = join(" ", 1269 "wilogutl.exe", 1270 "/q", 1271 "/l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'", 1272 "/o", "'".installer::patch::Tools::ToWindowsPath($destination_path)."'"); 1273 printf("running command $command\n"); 1274 my $response = qx($command); 1275 printf("response is '%s'\n", $response); 1276 my @candidates = glob($destination_path . "/Details*"); 1277 foreach my $candidate (@candidates) 1278 { 1279 next unless -f $candidate; 1280 my $new_name = $candidate; 1281 $new_name =~ s/Details.*$/$log_basename.html/; 1282 1283 # Rename the top-level html file and replace the title. 1284 open my $in, "<", $candidate; 1285 open my $out, ">", $new_name; 1286 while (<$in>) 1287 { 1288 if (/^(.*\<title\>)([^<]+)(.*)$/) 1289 { 1290 print $out $1.$new_title.$3; 1291 } 1292 else 1293 { 1294 print $out $_; 1295 } 1296 } 1297 close $in; 1298 close $out; 1299 1300 my $URL = $new_name; 1301 $URL =~ s/\/c\//c|\//; 1302 $URL =~ s/^(.):/$1|/; 1303 $URL = "file:///". $URL; 1304 $installer::logger::Info->printf("open %s in your browser to see the log messages\n", $URL); 1305 } 1306 } 1307 else 1308 { 1309 $installer::logger::Info->printf("Error: log file not found at %s\n", $log_filename); 1310 } 1311} 1312 1313 1314 1315 1316sub CreateMsp ($) 1317{ 1318 my ($pcp) = @_; 1319 1320 # Prepare log files. 1321 my $log_path = File::Spec->catfile($pcp->{'path'}, "log"); 1322 my $log_basename = "msp"; 1323 my $log_filename = File::Spec->catfile($log_path, $log_basename.".log"); 1324 my $performance_log_basename = "performance"; 1325 my $performance_log_filename = File::Spec->catfile($log_path, $performance_log_basename.".log"); 1326 File::Path::make_path($log_path) if ! -d $log_path; 1327 unlink($log_filename) if -f $log_filename; 1328 unlink($performance_log_filename) if -f $performance_log_filename; 1329 1330 # Create the .msp patch file. 1331 my $temporary_msimsp_path = File::Spec->catfile($pcp->{'path'}, "tmp"); 1332 if ( ! -d $temporary_msimsp_path) 1333 { 1334 File::Path::make_path($temporary_msimsp_path) 1335 || die ("can not create temporary path ".$temporary_msimsp_path); 1336 } 1337 $installer::logger::Info->printf("running msimsp.exe, that will take a while\n"); 1338 my $command = join(" ", 1339 "msimsp.exe", 1340 "-s", "'".installer::patch::Tools::ToWindowsPath($pcp->{'filename'})."'", 1341 "-p", "'".installer::patch::Tools::ToWindowsPath($pcp->{'msp_filename'})."'", 1342 "-l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'", 1343 "-f", "'".installer::patch::Tools::ToWindowsPath($temporary_msimsp_path)."'"); 1344# "-lp", MsiTools::ToEscapedWindowsPath($performance_log_filename), 1345 $installer::logger::Info->printf("running command %s\n", $command); 1346 my $response = qx($command); 1347 $installer::logger::Info->printf("response of msimsp is %s\n", $response); 1348 if ( ! -d $temporary_msimsp_path) 1349 { 1350 die("msimsp failed and deleted temporary path ".$temporary_msimsp_path); 1351 } 1352 1353 # Show the log file that was created by the msimsp.exe command. 1354 ShowLog($log_path, $log_filename, $log_basename, "msp creation"); 1355 ShowLog($log_path, $performance_log_filename, $performance_log_basename, "msp creation perf"); 1356} 1357 1358 1359 1360sub CreatePatch ($$) 1361{ 1362 my ($context, $variables) = @_; 1363 1364 $installer::logger::Info->printf("patch will update product %s from %s to %s\n", 1365 $context->{'product-name'}, 1366 $context->{'source-version'}, 1367 $context->{'target-version'}); 1368 1369 # Locate the Pcp schema file early on to report any errors before the lengthy operations that follow. 1370 my $pcp_schema_filename = FindPcpTemplate(); 1371 if ( ! defined $pcp_schema_filename) 1372 { 1373 exit(1); 1374 } 1375 1376 my $release_data = installer::patch::ReleasesList::Instance() 1377 ->{$context->{'source-version'}} 1378 ->{$context->{'package-format'}}; 1379 1380 # Create a patch for each language. 1381 my @requested_languages = GetLanguages(); 1382 my @valid_languages = FindValidLanguages($context, $release_data, \@requested_languages); 1383 $installer::logger::Info->printf("of the requested languages '%s' are valid: '%s'\n", 1384 join("', '", @requested_languages), 1385 join("', '", @valid_languages)); 1386 foreach my $language (@valid_languages) 1387 { 1388 $installer::logger::Info->printf("processing language '%s'\n", $language); 1389 $installer::logger::Info->increase_indentation(); 1390 1391 # Provide .msi and .cab files and unpacke .cab for the source release. 1392 $installer::logger::Info->printf("locating source package (%s)\n", $context->{'source-version'}); 1393 $installer::logger::Info->increase_indentation(); 1394 if ( ! installer::patch::InstallationSet::ProvideUnpackedCab( 1395 $context->{'source-version'}, 1396 0, 1397 $language, 1398 "msi", 1399 $context->{'product-name'})) 1400 { 1401 die "could not provide unpacked .cab file"; 1402 } 1403 my $source_msi = installer::patch::Msi->FindAndCreate( 1404 $context->{'source-version'}, 1405 0, 1406 $language, 1407 $context->{'product-name'}); 1408 die unless $source_msi->IsValid(); 1409 1410 $installer::logger::Info->decrease_indentation(); 1411 1412 # Provide .msi and .cab files and unpacke .cab for the target release. 1413 $installer::logger::Info->printf("locating target package (%s)\n", $context->{'target-version'}); 1414 $installer::logger::Info->increase_indentation(); 1415 if ( ! installer::patch::InstallationSet::ProvideUnpackedCab( 1416 $context->{'target-version'}, 1417 1, 1418 $language, 1419 "msi", 1420 $context->{'product-name'})) 1421 { 1422 die; 1423 } 1424 my $target_msi = installer::patch::Msi->FindAndCreate( 1425 $context->{'target-version'}, 1426 0, 1427 $language, 1428 $context->{'product-name'}); 1429 die unless defined $target_msi; 1430 die unless $target_msi->IsValid(); 1431 $installer::logger::Info->decrease_indentation(); 1432 1433 # Trigger reading of tables. 1434 foreach my $table_name (("File", "Component", "Registry")) 1435 { 1436 $source_msi->GetTable($table_name); 1437 $target_msi->GetTable($table_name); 1438 $installer::logger::Info->printf("read %s table (source and target\n", $table_name); 1439 } 1440 1441 # Check if the source and target msis fullfil all necessary requirements. 1442 if ( ! Check($source_msi, $target_msi, $variables, $context->{'product-name'})) 1443 { 1444 $installer::logger::Info->printf("Error: Source and target releases are not compatible.\n"); 1445 $installer::logger::Info->printf(" => Can not create patch.\n"); 1446 $installer::logger::Info->printf(" Did you create the target installation set with 'release=t' ?\n"); 1447 exit(1); 1448 } 1449 else 1450 { 1451 $installer::logger::Info->printf("OK: Source and target releases are compatible.\n"); 1452 } 1453 1454 # Provide the base path for creating .pcp and .mcp file. 1455 my $msp_path = File::Spec->catfile( 1456 $context->{'output-path'}, 1457 $context->{'product-name'}, 1458 "msp", 1459 sprintf("%s_%s", 1460 installer::patch::Version::ArrayToDirectoryName( 1461 installer::patch::Version::StringToNumberArray( 1462 $source_msi->{'version'})), 1463 installer::patch::Version::ArrayToDirectoryName( 1464 installer::patch::Version::StringToNumberArray( 1465 $target_msi->{'version'}))), 1466 $language 1467 ); 1468 File::Path::make_path($msp_path) unless -d $msp_path; 1469 1470 # Create the .pcp file that drives the msimsp.exe command. 1471 my $pcp = CreatePcp( 1472 $source_msi, 1473 $target_msi, 1474 $language, 1475 $context, 1476 $msp_path, 1477 $pcp_schema_filename, 1478 "Properties/Name:DontRemoveTempFolderWhenFinished" => "Value:1", 1479 "Properties/Name:SEQUENCE_DATA_GENERATION_DISABLED" => "Value:1", 1480 "Properties/Name:TrustMsi" => "Value:0", 1481 "Properties/Name:ProductName" => "Value:OOO341"); 1482 1483 # Finally create the msp. 1484 CreateMsp($pcp); 1485 1486 $installer::logger::Info->decrease_indentation(); 1487 } 1488} 1489 1490 1491 1492 1493sub ApplyPatch ($$) 1494{ 1495 my ($context, $variables) = @_; 1496 1497 $installer::logger::Info->printf("will apply patches that update product %s from %s to %s\n", 1498 $context->{'product-name'}, 1499 $context->{'source-version'}, 1500 $context->{'target-version'}); 1501 my @languages = GetLanguages(); 1502 1503 my $source_version_dirname = installer::patch::Version::ArrayToDirectoryName( 1504 installer::patch::Version::StringToNumberArray( 1505 $context->{'source-version'})); 1506 my $target_version_dirname = installer::patch::Version::ArrayToDirectoryName( 1507 installer::patch::Version::StringToNumberArray( 1508 $context->{'target-version'})); 1509 1510 foreach my $language (@languages) 1511 { 1512 my $msp_filename = File::Spec->catfile( 1513 $context->{'output-path'}, 1514 $context->{'product-name'}, 1515 "msp", 1516 $source_version_dirname . "_" . $target_version_dirname, 1517 $language, 1518 "openoffice.msp"); 1519 if ( ! -f $msp_filename) 1520 { 1521 $installer::logger::Info->printf("%s does not point to a valid file\n", $msp_filename); 1522 next; 1523 } 1524 1525 my $log_path = File::Spec->catfile(dirname($msp_filename), "log"); 1526 my $log_basename = "apply-msp"; 1527 my $log_filename = File::Spec->catfile($log_path, $log_basename.".log"); 1528 1529 my $command = join(" ", 1530 "msiexec.exe", 1531 "/update", "'".installer::patch::Tools::ToWindowsPath($msp_filename)."'", 1532 "/L*xv!", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'", 1533 "REINSTALL=ALL", 1534# "REINSTALLMODE=vomus", 1535 "REINSTALLMODE=omus", 1536 "MSIENFORCEUPGRADECOMPONENTRULES=1"); 1537 1538 printf("executing command %s\n", $command); 1539 my $response = qx($command); 1540 Encode::from_to($response, "UTF16LE", "UTF8"); 1541 printf("response was '%s'\n", $response); 1542 1543 ShowLog($log_path, $log_filename, $log_basename, "msp application"); 1544 } 1545} 1546 1547 1548 1549 1550sub main () 1551{ 1552 installer::logger::SetupSimpleLogging(undef); 1553 my $context = ProcessCommandline(); 1554 my ($variables, undef, undef) = installer::ziplist::read_openoffice_lst_file( 1555 $context->{'lst-file'}, 1556 $context->{'product-name'}, 1557 undef); 1558 DetermineVersions($context, $variables); 1559 1560 if ($context->{'command'} eq "create") 1561 { 1562 CreatePatch($context, $variables); 1563 } 1564 elsif ($context->{'command'} eq "apply") 1565 { 1566 ApplyPatch($context, $variables); 1567 } 1568} 1569 1570 1571main(); 1572