You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openoffice.apache.org by af...@apache.org on 2013/12/04 09:51:31 UTC
svn commit: r1547732 [4/4] - in /openoffice/trunk/main:
instsetoo_native/data/ instsetoo_native/util/ solenv/bin/
solenv/bin/modules/installer/ solenv/bin/modules/installer/patch/
solenv/bin/modules/installer/windows/
Added: openoffice/trunk/main/solenv/bin/patch_tool.pl
URL: http://svn.apache.org/viewvc/openoffice/trunk/main/solenv/bin/patch_tool.pl?rev=1547732&view=auto
==============================================================================
--- openoffice/trunk/main/solenv/bin/patch_tool.pl (added)
+++ openoffice/trunk/main/solenv/bin/patch_tool.pl Wed Dec 4 08:51:30 2013
@@ -0,0 +1,1571 @@
+#!/usr/bin/perl -w
+
+#**************************************************************
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#**************************************************************
+
+use Getopt::Long;
+use Pod::Usage;
+use File::Path;
+use File::Spec;
+use File::Basename;
+use XML::LibXML;
+use Digest;
+use Archive::Zip;
+use Archive::Extract;
+
+use installer::ziplist;
+use installer::logger;
+use installer::windows::msiglobal;
+use installer::patch::Msi;
+use installer::patch::ReleasesList;
+use installer::patch::Version;
+
+use strict;
+
+
+=head1 NAME
+
+ patch_tool.pl - Create Windows MSI patches.
+
+=head1 SYNOPSIS
+
+ patch_tool.pl command [options]
+
+ Commands:
+ create create patches
+ apply apply patches
+
+ Options:
+ -p|--product-name <product-name>
+ The product name, eg Apache_OpenOffice
+ -o|--output-path <path>
+ Path to the instsetoo_native platform output tree
+ -d|--data-path <path>
+ Path to the data directory that is expected to be under version control.
+ --source-version <major>.<minor>.<micro>
+ The version that is to be patched.
+ --target-version <major>.<minor>.<micro>
+ The version after the patch has been applied.
+
+=head1 DESCRIPTION
+
+ Creates windows MSP patch files, one for each relevant language.
+ Patches convert an installed OpenOffice to the target version.
+
+ Required data are:
+ Installation sets of the source versions
+ Taken from ext_sources/
+ Downloaded from archive.apache.org on demand
+
+ Installation set of the target version
+ This is expected to be the current version.
+
+=cut
+
+# my $ImageFamily = "MNPapps";
+# The ImageFamily name has to have 1-8 alphanumeric characters.
+my $ImageFamily = "AOO";
+my $SourceImageName = "Source";
+my $TargetImageName = "Target";
+
+
+
+sub ProcessCommandline ()
+{
+ my $arguments = {
+ 'product-name' => undef,
+ 'output-path' => undef,
+ 'data-path' => undef,
+ 'lst-file' => undef,
+ 'source-version' => undef,
+ 'target-version' => undef};
+
+ if ( ! GetOptions(
+ "product-name=s", \$arguments->{'product-name'},
+ "output-path=s", \$arguments->{'output-path'},
+ "data-path=s" => \$arguments->{'data-path'},
+ "lst-file=s" => \$arguments->{'lst-file'},
+ "source-version:s" => \$arguments->{'source-version'},
+ "target-version:s" => \$arguments->{'target-version'}
+ ))
+ {
+ pod2usage(2);
+ }
+
+ # Only the command should be left in @ARGV.
+ pod2usage(2) unless scalar @ARGV == 1;
+ $arguments->{'command'} = shift @ARGV;
+
+ # At the moment we only support patches on windows. When this
+ # is extended in the future we need the package format as an
+ # argument.
+ $arguments->{'package-format'} = "msi";
+
+ return $arguments;
+}
+
+
+
+
+sub GetSourceMsiPath ($$)
+{
+ my ($context, $language) = @_;
+ my $unpacked_path = File::Spec->catfile(
+ $context->{'output-path'},
+ $context->{'product-name'},
+ $context->{'package-format'},
+ installer::patch::Version::ArrayToDirectoryName(
+ installer::patch::Version::StringToNumberArray(
+ $context->{'source-version'})),
+ $language);
+}
+
+
+
+
+sub GetTargetMsiPath ($$)
+{
+ my ($context, $language) = @_;
+ return File::Spec->catfile(
+ $context->{'output-path'},
+ $context->{'product-name'},
+ $context->{'package-format'},
+ "install",
+ $language);
+}
+
+
+
+sub ProvideInstallationSets ($$)
+{
+ my ($context, $language) = @_;
+
+ # Assume that the target installation set is located in the output tree.
+ my $target_path = GetTargetMsiPath($context, $language);
+ if ( ! -d $target_path)
+ {
+ installer::logger::PrintError("can not find target installation set at '%s'\n", $target_path);
+ return 0;
+ }
+ my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'});
+ my $target_msi_file = File::Spec->catfile(
+ $target_path,
+ sprintf("openoffice%d%d%d.msi", $target_version[0], $target_version[1], $target_version[2]));
+ if ( ! -f $target_msi_file)
+ {
+ installer::logger::PrintError("can not find target msi file at '%s'\n", $target_msi_file);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+
+
+sub GetLanguages ()
+{
+ # The set of languages is taken from the WITH_LANG environment variable.
+ # If that is missing or is empty then the default 'en-US' is used instead.
+ my @languages = ("en-US");
+ my $with_lang = $ENV{'WITH_LANG'};
+ if (defined $with_lang && $with_lang ne "")
+ {
+ @languages = split(/\s+/, $with_lang);
+ }
+ return @languages;
+}
+
+
+
+
+sub FindValidLanguages ($$$)
+{
+ my ($context, $release_data, $languages) = @_;
+
+ my @valid_languages = ();
+ foreach my $language (@$languages)
+ {
+ if ( ! ProvideInstallationSets($context, $language))
+ {
+ installer::logger::PrintError(" '%s' has no target installation set\n", $language);
+ }
+ elsif ( ! defined $release_data->{$language})
+ {
+ installer::logger::PrintError(" '%s' is not a released language for version %s\n",
+ $language,
+ $context->{'source-version'});
+ }
+ else
+ {
+ push @valid_languages, $language;
+ }
+ }
+
+ return @valid_languages;
+}
+
+
+
+
+sub ProvideSourceInstallationSet ($$$)
+{
+ my ($context, $language, $release_data) = @_;
+
+ my $url = $release_data->{$language}->{'URL'};
+ $url =~ /^(.*)\/([^\/]*)$/;
+ my ($location, $basename) = ($1,$2);
+
+ my $ext_sources_path = $ENV{'TARFILE_LOCATION'};
+ if ( ! -d $ext_sources_path)
+ {
+ installer::logger::PrintError("Can not determine the path to ext_sources/.\n");
+ installer::logger::PrintError("Maybe SOURCE_ROOT_DIR has not been correctly set in the environment?");
+ return 0;
+ }
+
+ # We need the unpacked installation set in <platform>/<product>/<package>/<source-version>,
+ # eg wntmsci12.pro/Apache_OpenOffice/msi/v-4-0-0.
+ my $unpacked_path = GetSourceMsiPath($context, $language);
+ if ( ! -d $unpacked_path)
+ {
+ # Make sure that the downloadable installation set (.exe) is present in ext_sources/.
+ my $filename = File::Spec->catfile($ext_sources_path, $basename);
+ if ( -f $filename)
+ {
+ PrintInfo("%s is already present in ext_sources/. Nothing to do\n", $basename);
+ }
+ else
+ {
+ return 0 if ! installer::patch::Download($language, $release_data, $location, $basename, $filename);
+ return 0 if ! -f $filename;
+ }
+
+ # Unpack the installation set.
+ if ( -d $unpacked_path)
+ {
+ # Take the existence of the destination path as proof that the
+ # installation set was successfully unpacked before.
+ }
+ else
+ {
+ installer::patch::InstallationSet::Unpack($filename, $unpacked_path);
+ }
+ }
+}
+
+
+
+
+# Find the source and target version between which the patch will be
+# created. Typically the target version is the current version and
+# the source version is the version of the previous release.
+sub DetermineVersions ($$)
+{
+ my ($context, $variables) = @_;
+
+ if (defined $context->{'source-version'} && defined $context->{'target-version'})
+ {
+ # Both source and target version have been specified on the
+ # command line. There remains nothing to be be done.
+ return;
+ }
+
+ if ( ! defined $context->{'target-version'})
+ {
+ # Use the current version as target version.
+ $context->{'target-version'} = $variables->{PRODUCTVERSION};
+ }
+
+ my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'});
+ shift @target_version;
+ my $is_target_version_major = 1;
+ foreach my $number (@target_version)
+ {
+ $is_target_version_major = 0 if ($number ne "0");
+ }
+ if ($is_target_version_major)
+ {
+ installer::logger::PrintError("can not create patch where target version is a new major version (%s)\n",
+ $context->{'target-version'});
+ die;
+ }
+
+ if ( ! defined $context->{'source-version'})
+ {
+ my $releases = installer::patch::ReleasesList::Instance();
+
+ # Search for target release in the list of previous releases.
+ # If it is found, use the previous version as source version.
+ # Otherwise use the last released version.
+ my $last_release = undef;
+ foreach my $release (@{$releases->{'releases'}})
+ {
+ last if ($release eq $context->{'target-version'});
+ $last_release = $release;
+ }
+ $context->{'source-version'} = $last_release;
+ }
+}
+
+
+
+
+=head2 CheckUpgradeCode($source_msi, $target_msi)
+
+ The 'UpgradeCode' values in the 'Property' table differs from source to target
+
+=cut
+sub CheckUpgradeCode($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ my $source_upgrade_code = $source_msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value");
+ my $target_upgrade_code = $target_msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value");
+
+ if ($source_upgrade_code eq $target_upgrade_code)
+ {
+ $installer::logger::Info->printf("Error: The UpgradeCode properties have to differ but are both '%s'\n",
+ $source_upgrade_code);
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: UpgradeCode values are identical\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckProductCode($source_msi, $target_msi)
+
+ The 'ProductCode' values in the 'Property' tables remain the same.
+
+=cut
+sub CheckProductCode($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ my $source_product_code = $source_msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value");
+ my $target_product_code = $target_msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value");
+
+ if ($source_product_code ne $target_product_code)
+ {
+ $installer::logger::Info->printf("Error: The ProductCode properties have to remain the same but are\n");
+ $installer::logger::Info->printf(" '%s' and '%s'\n",
+ $source_product_code,
+ $target_product_code);
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: ProductCode properties differ\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckBuildIdCode($source_msi, $target_msi)
+
+ The 'PRODUCTBUILDID' values in the 'Property' tables (not the AOO build ids) differ and the
+ target value is higher than the source value.
+
+=cut
+sub CheckBuildIdCode($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ my $source_build_id = $source_msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value");
+ my $target_build_id = $target_msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value");
+
+ if ($source_build_id >= $target_build_id)
+ {
+ $installer::logger::Info->printf(
+ "Error: The PRODUCTBUILDID properties have to increase but are '%s' and '%s'\n",
+ $source_build_id,
+ $target_build_id);
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: source build id is lower than target build id\n");
+ return 1;
+ }
+}
+
+
+
+
+sub CheckProductName ($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ my $source_product_name = $source_msi->GetTable("Property")->GetValue("Property", "DEFINEDPRODUCT", "Value");
+ my $target_product_name = $target_msi->GetTable("Property")->GetValue("Property", "DEFINEDPRODUCT", "Value");
+
+ if ($source_product_name ne $target_product_name)
+ {
+ $installer::logger::Info->printf("Error: product names of are not identical:\n");
+ $installer::logger::Info->printf(" %s != %s\n", $source_product_name, $target_product_name);
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: product names are identical\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckRemovedFiles($source_msi, $target_msi)
+
+ Files and components must not be deleted.
+
+=cut
+sub CheckRemovedFiles($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ # Get the 'File' tables.
+ my $source_file_table = $source_msi->GetTable("File");
+ my $target_file_table = $target_msi->GetTable("File");
+
+ # Create data structures for fast lookup.
+ my @source_files = map {$_->GetValue("File")} @{$source_file_table->GetAllRows()};
+ my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()};
+
+ # Search for removed files (files in source that are missing from target).
+ my $removed_file_count = 0;
+ foreach my $uniquename (@source_files)
+ {
+ if ( ! defined $target_file_map{$uniquename})
+ {
+ ++$removed_file_count;
+ }
+ }
+
+ if ($removed_file_count > 0)
+ {
+ $installer::logger::Info->printf("Error: %d files have been removed\n", $removed_file_count);
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: no files have been removed\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckNewFiles($source_msi, $target_msi)
+
+ New files have to be in new components.
+
+=cut
+sub CheckNewFiles($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ # Get the 'File' tables.
+ my $source_file_table = $source_msi->GetTable("File");
+ my $target_file_table = $target_msi->GetTable("File");
+
+ # Create data structures for fast lookup.
+ my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()};
+ my @target_files = map {$_->GetValue("File")} @{$target_file_table->GetAllRows()};
+
+ # Search for added files (files in target that where not in source).
+ my $added_file_count = 0;
+ foreach my $uniquename (@target_files)
+ {
+ if ( ! defined $source_file_map{$uniquename})
+ {
+ ++$added_file_count;
+ }
+ }
+
+ if ($added_file_count > 0)
+ {
+ $installer::logger::Info->printf("Warning: %d files have been added\n", $added_file_count);
+
+ $installer::logger::Info->printf("Check for new files being part of new components is not yet implemented\n");
+
+ return 1;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: no files have been added\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckComponentSets($source_msi, $target_msi)
+
+ Components must not be removed but can be added.
+ Features of added components have also to be new.
+
+=cut
+sub CheckComponentSets($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ # Get the 'Component' tables.
+ my $source_component_table = $source_msi->GetTable("Component");
+ my $target_component_table = $target_msi->GetTable("Component");
+
+ # Create data structures for fast lookup.
+ my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
+ my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
+
+ # Check that no component has been removed.
+ my @removed_components = ();
+ foreach my $componentname (keys %source_component_map)
+ {
+ if ( ! defined $target_component_map{$componentname})
+ {
+ push @removed_components, $componentname;
+ }
+ }
+ if (scalar @removed_components > 0)
+ {
+ # There are removed components.
+
+ # Check if any of them is not a registry component.
+ my $is_file_component_removed = 0;
+ foreach my $componentname (@removed_components)
+ {
+ if ($componentname !~ /^registry/)
+ {
+ $is_file_component_removed = 1;
+ }
+ }
+ if ($is_file_component_removed)
+ {
+ $installer::logger::Info->printf(
+ "Error: %d components have been removed, some of them are file components:\n",
+ scalar @removed_components);
+ $installer::logger::Info->printf(" %s\n", join(", ", @removed_components));
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf(
+ "Error: %d components have been removed, all of them are registry components:\n",
+ scalar @removed_components);
+ return 0;
+ }
+ }
+
+ # Check that added components belong to new features.
+ my @added_components = ();
+ foreach my $componentname (keys %target_component_map)
+ {
+ if ( ! defined $source_component_map{$componentname})
+ {
+ push @added_components, $componentname;
+ }
+ }
+
+ if (scalar @added_components > 0)
+ {
+ # Check if any of them is not a registry component.
+ my $is_file_component_removed = 0;
+ foreach my $componentname (@removed_components)
+ {
+ if ($componentname !~ /^registry/)
+ {
+ $is_file_component_removed = 1;
+ }
+ }
+
+ if ($is_file_component_removed)
+ {
+ $installer::logger::Info->printf(
+ "Warning: %d components have been addded\n",
+ scalar @added_components);
+ $installer::logger::Info->printf(
+ "Test for new components belonging to new features has not yet been implemented\n");
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf(
+ "Warning: %d components have been addded, all of them registry components\n",
+ scalar @added_components);
+ }
+ }
+
+ $installer::logger::Info->printf("OK: component sets in source and target are compatible\n");
+ return 1;
+}
+
+
+
+
+=head2 CheckComponent($source_msi, $target_msi)
+
+ In the 'Component' table the 'ComponentId' and 'Component' values
+ for corresponding componts in the source and target release have
+ to be identical.
+
+=cut
+sub CheckComponentValues($$$)
+{
+ my ($source_msi, $target_msi, $variables) = @_;
+
+ # Get the 'Component' tables.
+ my $source_component_table = $source_msi->GetTable("Component");
+ my $target_component_table = $target_msi->GetTable("Component");
+
+ # Create data structures for fast lookup.
+ my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
+ my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
+
+ my @differences = ();
+ my $comparison_count = 0;
+ while (my ($componentname, $source_component_row) = each %source_component_map)
+ {
+ my $target_component_row = $target_component_map{$componentname};
+ if (defined $target_component_row)
+ {
+ ++$comparison_count;
+ if ($source_component_row->GetValue("ComponentId") ne $target_component_row->GetValue("ComponentId"))
+ {
+ push @differences, [
+ $componentname,
+ $source_component_row->GetValue("ComponentId"),
+ $target_component_row->GetValue("ComponentId"),
+ $target_component_row->GetValue("Component"),
+ ];
+ }
+ }
+ }
+
+ if (scalar @differences > 0)
+ {
+ $installer::logger::Info->printf(
+ "Error: there are %d components with different 'ComponentId' values after %d comparisons.\n",
+ scalar @differences,
+ $comparison_count);
+ foreach my $item (@differences)
+ {
+ $installer::logger::Info->printf("%s %s\n", $item->[1], $item->[2]);
+ }
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: components in source and target are identical\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckFileSequence($source_msi, $target_msi)
+
+ In the 'File' table the 'Sequence' numbers for corresponding files has to be identical.
+
+=cut
+sub CheckFileSequence($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ # Get the 'File' tables.
+ my $source_file_table = $source_msi->GetTable("File");
+ my $target_file_table = $target_msi->GetTable("File");
+
+ # Create temporary data structures for fast access.
+ my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()};
+ my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()};
+
+ # Search files with mismatching sequence numbers.
+ my @mismatching_files = ();
+ while (my ($uniquename,$source_file_row) = each %source_file_map)
+ {
+ my $target_file_row = $target_file_map{$uniquename};
+ if (defined $target_file_row)
+ {
+ if ($source_file_row->GetValue('Sequence') ne $target_file_row->GetValue('Sequence'))
+ {
+ push @mismatching_files, [
+ $uniquename,
+ $source_file_row,
+ $target_file_row
+ ];
+ }
+ }
+ }
+
+ if (scalar @mismatching_files > 0)
+ {
+ $installer::logger::Info->printf("Error: there are %d files with mismatching 'Sequence' numbers\n",
+ scalar @mismatching_files);
+ foreach my $item (@mismatching_files)
+ {
+ $installer::logger::Info->printf(" %s: %d != %d\n",
+ $item->[0],
+ $item->[1]->GetValue("Sequence"),
+ $item->[2]->GetValue("Sequence"));
+ }
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: all files have matching 'Sequence' numbers\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckFileSequenceUnique($source_msi, $target_msi)
+
+ In the 'File' table the 'Sequence' values have to be unique.
+
+=cut
+sub CheckFileSequenceUnique($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ # Get the 'File' tables.
+ my $target_file_table = $target_msi->GetTable("File");
+
+ my %sequence_numbers = ();
+ my $collision_count = 0;
+ foreach my $row (@{$target_file_table->GetAllRows()})
+ {
+ my $sequence_number = $row->GetValue("Sequence");
+ if (defined $sequence_numbers{$sequence_number})
+ {
+ ++$collision_count;
+ }
+ else
+ {
+ $sequence_numbers{$sequence_number} = 1;
+ }
+ }
+
+ if ($collision_count > 0)
+ {
+ $installer::logger::Info->printf("Error: there are %d collisions ofn the sequence numbers\n",
+ $collision_count);
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: sequence numbers are unique\n");
+ return 1;
+ }
+}
+
+
+
+
+=head2 CheckFileSequenceHoles ($target_msi)
+
+ Check the sequence numbers of the target msi if the n files use numbers 1..n or if there are holes.
+ Holes are reported as warnings.
+
+=cut
+sub CheckFileSequenceHoles ($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ my $target_file_table = $target_msi->GetTable("File");
+ my %sequence_numbers = map {$_->GetValue("Sequence") => $_} @{$target_file_table->GetAllRows()};
+ my @sorted_sequence_numbers = sort {$a <=> $b} keys %sequence_numbers;
+ my $expected_next_sequence_number = 1;
+ my @holes = ();
+ foreach my $sequence_number (@sorted_sequence_numbers)
+ {
+ if ($sequence_number != $expected_next_sequence_number)
+ {
+ push @holes, [$expected_next_sequence_number, $sequence_number-1];
+ }
+ $expected_next_sequence_number = $sequence_number+1;
+ }
+ if (scalar @holes > 0)
+ {
+ $installer::logger::Info->printf("Warning: sequence numbers have %d holes\n");
+ foreach my $hole (@holes)
+ {
+ if ($hole->[0] != $hole->[1])
+ {
+ $installer::logger::Info->printf(" %d\n", $hole->[0]);
+ }
+ else
+ {
+ $installer::logger::Info->printf(" %d -> %d\n", $hole->[0], $hole->[1]);
+ }
+ }
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: there are no holes in the sequence numbers\n");
+ }
+ return 1;
+}
+
+
+
+
+=head2 CheckRegistryItems($source_msi, $target_msi)
+
+ In the 'Registry' table the 'Component_' and 'Key' values must not
+ depend on the version number (beyond the unchanging major
+ version).
+
+ 'Value' values must only depend on the major version number to
+ avoid duplicate entries in the start menu.
+
+ Violations are reported as warnings for now.
+
+=cut
+sub CheckRegistryItems($$$)
+{
+ my ($source_msi, $target_msi, $product_name) = @_;
+
+ # Get the registry tables.
+ my $source_registry_table = $source_msi->GetTable("Registry");
+ my $target_registry_table = $target_msi->GetTable("Registry");
+
+ my $registry_index = $target_registry_table->GetColumnIndex("Registry");
+ my $component_index = $target_registry_table->GetColumnIndex("Component_");
+
+ # Create temporary data structures for fast access.
+ my %source_registry_map = map {$_->GetValue($registry_index) => $_} @{$source_registry_table->GetAllRows()};
+ my %target_registry_map = map {$_->GetValue($registry_index) => $_} @{$target_registry_table->GetAllRows()};
+
+ # Prepare version numbers to search.
+ my $source_version_number = $source_msi->{'version'};
+ my $source_version_nodots = installer::patch::Version::ArrayToNoDotName(
+ installer::patch::Version::StringToNumberArray($source_version_number));
+ my $source_component_pattern = lc($product_name).$source_version_nodots;
+ my $target_version_number = $target_msi->{'version'};
+ my $target_version_nodots = installer::patch::Version::ArrayToNoDotName(
+ installer::patch::Version::StringToNumberArray($target_version_number));
+ my $target_component_pattern = lc($product_name).$target_version_nodots;
+
+ foreach my $source_row (values %source_registry_map)
+ {
+ my $target_row = $target_registry_map{$source_row->GetValue($registry_index)};
+ if ( ! defined $target_row)
+ {
+ $installer::logger::Info->printf("Error: sets of registry entries differs\n");
+ return 1;
+ }
+
+ my $source_component_name = $source_row->GetValue($component_index);
+ my $target_component_name = $source_row->GetValue($component_index);
+
+ }
+
+ $installer::logger::Info->printf("OK: registry items are OK\n");
+ return 1;
+}
+
+
+
+
+=head2
+
+ Component->KeyPath must not change. (see component.pm/get_component_keypath)
+
+=cut
+sub CheckComponentKeyPath ($$)
+{
+ my ($source_msi, $target_msi) = @_;
+
+ # Get the registry tables.
+ my $source_component_table = $source_msi->GetTable("Component");
+ my $target_component_table = $target_msi->GetTable("Component");
+
+ # Create temporary data structures for fast access.
+ my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
+ my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
+
+ my @mismatches = ();
+ while (my ($componentname, $source_component_row) = each %source_component_map)
+ {
+ my $target_component_row = $target_component_map{$componentname};
+ if (defined $target_component_row)
+ {
+ my $source_keypath = $source_component_row->GetValue("KeyPath");
+ my $target_keypath = $target_component_row->GetValue("KeyPath");
+ if ($source_keypath ne $target_keypath)
+ {
+ push @mismatches, [$componentname, $source_keypath, $target_keypath];
+ }
+ }
+ }
+
+ if (scalar @mismatches > 0)
+ {
+ $installer::logger::Info->printf(
+ "Error: there are %d mismatches in the 'KeyPath' column of the 'Component' table\n",
+ scalar @mismatches);
+
+ foreach my $item (@mismatches)
+ {
+ $installer::logger::Info->printf(
+ " %s: %s != %s\n",
+ $item->[0],
+ $item->[1],
+ $item->[2]);
+ }
+
+ return 0;
+ }
+ else
+ {
+ $installer::logger::Info->printf(
+ "OK: no mismatches in the 'KeyPath' column of the 'Component' table\n");
+ return 1;
+ }
+}
+
+
+
+
+sub Check ($$$$)
+{
+ my ($source_msi, $target_msi, $variables, $product_name) = @_;
+
+ $installer::logger::Info->printf("checking if source and target releases are compatable\n");
+ $installer::logger::Info->increase_indentation();
+
+ my $result = 1;
+
+ $result &&= CheckUpgradeCode($source_msi, $target_msi);
+ $result &&= CheckProductCode($source_msi, $target_msi);
+ $result &&= CheckBuildIdCode($source_msi, $target_msi);
+ $result &&= CheckProductName($source_msi, $target_msi);
+ $result &&= CheckRemovedFiles($source_msi, $target_msi);
+ $result &&= CheckNewFiles($source_msi, $target_msi);
+ $result &&= CheckComponentSets($source_msi, $target_msi);
+ $result &&= CheckComponentValues($source_msi, $target_msi, $variables);
+ $result &&= CheckFileSequence($source_msi, $target_msi);
+ $result &&= CheckFileSequenceUnique($source_msi, $target_msi);
+ $result &&= CheckFileSequenceHoles($source_msi, $target_msi);
+ $result &&= CheckRegistryItems($source_msi, $target_msi, $product_name);
+ $result &&= CheckComponentKeyPath($source_msi, $target_msi);
+
+ $installer::logger::Info->decrease_indentation();
+
+ return $result;
+}
+
+
+
+
+=head2 FindPcpTemplate ()
+
+ The template.pcp file is part of the Windows SDK.
+
+=cut
+sub FindPcpTemplate ()
+{
+ my $psdk_home = $ENV{'PSDK_HOME'};
+ if ( ! defined $psdk_home)
+ {
+ $installer::logger::Info->printf("Error: the PSDK_HOME environment variable is not set.\n");
+ $installer::logger::Info->printf(" did you load the AOO build environment?\n");
+ $installer::logger::Info->printf(" you may want to use the --with-psdk-home configure option\n");
+ return undef;
+ }
+ if ( ! -d $psdk_home)
+ {
+ $installer::logger::Info->printf(
+ "Error: the PSDK_HOME environment variable does not point to a valid directory: %s\n",
+ $psdk_home);
+ return undef;
+ }
+
+ my $schema_path = File::Spec->catfile($psdk_home, "Bin", "msitools", "Schemas", "MSI");
+ if ( ! -d $schema_path)
+ {
+ $installer::logger::Info->printf("Error: Can not locate the msi template folder in the Windows SDK\n");
+ $installer::logger::Info->printf(" %s\n", $schema_path);
+ $installer::logger::Info->printf(" Is the Windows SDK properly installed?\n");
+ return undef;
+ }
+
+ my $schema_filename = File::Spec->catfile($schema_path, "template.pcp");
+ if ( ! -f $schema_filename)
+ {
+ $installer::logger::Info->printf("Error: Can not locate the pcp template at\n");
+ $installer::logger::Info->printf(" %s\n", $schema_filename);
+ $installer::logger::Info->printf(" Is the Windows SDK properly installed?\n");
+ return undef;
+ }
+
+ return $schema_filename;
+}
+
+
+
+
+sub SetupPcpPatchMetadataTable ($$$)
+{
+ my ($pcp, $source_msi, $target_msi) = @_;
+
+ # Determine values for eg product name and source and new version.
+ my $source_version = $source_msi->{'version'};
+ my $target_version = $target_msi->{'version'};
+
+ my $property_table = $target_msi->GetTable("Property");
+ my $display_product_name = $property_table->GetValue("Property", "DEFINEDPRODUCT", "Value");
+
+ # Set table.
+ my $table = $pcp->GetTable("PatchMetadata");
+ $table->SetRow(
+ "Company", "",
+ "*Property", "Description",
+ "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version)
+ );
+ $table->SetRow(
+ "Company", "",
+ "*Property", "DisplayName",
+ "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version)
+ );
+ $table->SetRow(
+ "Company", "",
+ "*Property", "ManufacturerName",
+ "Value", $property_table->GetValue("Property", "Manufacturer", "Value"),
+ );
+ $table->SetRow(
+ "Company", "",
+ "*Property", "MoreInfoURL",
+ "Value", $property_table->GetValue("Property", "ARPURLINFOABOUT", "Value")
+ );
+ $table->SetRow(
+ "Company", "",
+ "*Property", "TargetProductName",
+ "Value", $property_table->GetValue("Property", "ProductName", "Value")
+ );
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
+
+ $table->SetRow(
+ "Company", "",
+ "*Property", "CreationTimeUTC",
+ "Value", sprintf("%d/%d/%d %d:%02d", $mon+1,$mday,$year+1900,$hour,$min)
+ );
+}
+
+
+
+
+sub SetupPropertiesTable ($$)
+{
+ my ($pcp, $msp_filename) = @_;
+
+ my $table = $pcp->GetTable("Properties");
+
+ $table->SetRow(
+ "*Name", "PatchOutputPath",
+ "Value", installer::patch::Tools::ToWindowsPath($msp_filename)
+ );
+ # Request at least Windows installer 2.0.
+ # Version 2.0 allows us to omit some values from ImageFamilies table.
+ $table->SetRow(
+ "*Name", "MinimumRequiredMsiVersion",
+ "Value", 200
+ );
+ # Allow diffs for binary files.
+ $table->SetRow(
+ "*Name", "IncludeWholeFilesOnly",
+ "Value", 0
+ );
+
+ my $uuid = installer::windows::msiglobal::create_guid();
+ my $uuid_string = "{" . $uuid . "}";
+ $table->SetRow(
+ "*Name", "PatchGUID",
+ "Value", $uuid_string
+ );
+ $installer::logger::Info->printf("created new PatchGUID %s\n", $uuid_string);
+
+ # Prevent sequence table from being generated.
+ $table->SetRow(
+ "*Name", "SEQUENCE_DATA_GENERATION_DISABLED",
+ "Value", 1);
+}
+
+
+
+
+sub SetupImageFamiliesTable ($)
+{
+ my ($pcp) = @_;
+
+ $pcp->GetTable("ImageFamilies")->SetRow(
+ "Family", $ImageFamily,
+ "MediaSrcPropName", "",#"MNPSrcPropName",
+ "MediaDiskId", "",
+ "FileSequenceStart", "",
+ "DiskPrompt", "",
+ "VolumeLabel", "");
+}
+
+
+
+
+sub SetupUpgradedImagesTable ($$)
+{
+ my ($pcp, $target_msi_path) = @_;
+
+ my $msi_path = installer::patch::Tools::ToWindowsPath($target_msi_path);
+ $pcp->GetTable("UpgradedImages")->SetRow(
+ "Upgraded", $TargetImageName,
+ "MsiPath", $msi_path,
+ "PatchMsiPath", "",
+ "SymbolPaths", "",
+ "Family", $ImageFamily);
+}
+
+
+
+
+sub SetupTargetImagesTable ($$)
+{
+ my ($pcp, $source_msi_path) = @_;
+
+ $pcp->GetTable("TargetImages")->SetRow(
+ "Target", $SourceImageName,
+ "MsiPath", installer::patch::Tools::ToWindowsPath($source_msi_path),
+ "SymbolPaths", "",
+ "Upgraded", $TargetImageName,
+ "Order", 1,
+ "ProductValidateFlags", "",
+ "IgnoreMissingSrcFiles", 0);
+}
+
+
+
+
+sub SetAdditionalValues ($%)
+{
+ my ($pcp, %data) = @_;
+
+ while (my ($key,$value) = each(%data))
+ {
+ $key =~ /^([^\/]+)\/([^:]+):(.+)$/
+ || die("invalid key format");
+ my ($table_name, $key_column,$key_value) = ($1,$2,$3);
+ $value =~ /^([^:]+):(.*)$/
+ || die("invalid value format");
+ my ($value_column,$value_value) = ($1,$2);
+
+ my $table = $pcp->GetTable($table_name);
+ $table->SetRow(
+ "*".$key_column, $key_value,
+ $value_column, $value_value);
+ }
+}
+
+
+
+
+sub CreatePcp ($$$$$$%)
+{
+ my ($source_msi,
+ $target_msi,
+ $language,
+ $context,
+ $msp_path,
+ $pcp_schema_filename,
+ %additional_values) = @_;
+
+ # Create filenames.
+ my $pcp_filename = File::Spec->catfile($msp_path, "openoffice.pcp");
+ my $msp_filename = File::Spec->catfile($msp_path, "openoffice.msp");
+
+ # Setup msp path and filename.
+ unlink($pcp_filename) if -f $pcp_filename;
+ if ( ! File::Copy::copy($pcp_schema_filename, $pcp_filename))
+ {
+ $installer::logger::Info->printf("Error: could not create openoffice.pcp as copy of pcp schema\n");
+ $installer::logger::Info->printf(" %s\n", $pcp_schema_filename);
+ $installer::logger::Info->printf(" %s\n", $pcp_filename);
+ return undef;
+ }
+ my $pcp = installer::patch::Msi->new(
+ $pcp_filename,
+ undef,
+ undef,
+ $language,
+ $context->{'product-name'});
+
+ # Store some values in the pcp for easy reference in the msp creation.
+ $pcp->{'msp_filename'} = $msp_filename;
+
+ SetupPcpPatchMetadataTable($pcp, $source_msi, $target_msi);
+ SetupPropertiesTable($pcp, $msp_filename);
+ SetupImageFamiliesTable($pcp);
+ SetupUpgradedImagesTable($pcp, $target_msi->{'filename'});
+ SetupTargetImagesTable($pcp, $source_msi->{'filename'});
+
+ SetAdditionalValues(%additional_values);
+
+ $pcp->Commit();
+
+ # Remove the PatchSequence table to avoid MsiMsp error message:
+ # "Since MSI 3.0 will block installation of major upgrade patches with
+ # sequencing information, creation of such patches is blocked."
+ #$pcp->RemoveTable("PatchSequence");
+ # TODO: alternatively add property SEQUENCE_DATA_GENERATION_DISABLED to pcp Properties table.
+
+
+ $installer::logger::Info->printf("created pcp file at\n");
+ $installer::logger::Info->printf(" %s\n", $pcp->{'filename'});
+
+ return $pcp;
+}
+
+
+
+
+sub ShowLog ($$$$)
+{
+ my ($log_path, $log_filename, $log_basename, $new_title) = @_;
+
+ if ( -f $log_filename)
+ {
+ my $destination_path = File::Spec->catfile($log_path, $log_basename);
+ File::Path::make_path($destination_path) if ! -d $destination_path;
+ my $command = join(" ",
+ "wilogutl.exe",
+ "/q",
+ "/l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
+ "/o", "'".installer::patch::Tools::ToWindowsPath($destination_path)."'");
+ printf("running command $command\n");
+ my $response = qx($command);
+ printf("response is '%s'\n", $response);
+ my @candidates = glob($destination_path . "/Details*");
+ foreach my $candidate (@candidates)
+ {
+ next unless -f $candidate;
+ my $new_name = $candidate;
+ $new_name =~ s/Details.*$/$log_basename.html/;
+
+ # Rename the top-level html file and replace the title.
+ open my $in, "<", $candidate;
+ open my $out, ">", $new_name;
+ while (<$in>)
+ {
+ if (/^(.*\<title\>)([^<]+)(.*)$/)
+ {
+ print $out $1.$new_title.$3;
+ }
+ else
+ {
+ print $out $_;
+ }
+ }
+ close $in;
+ close $out;
+
+ my $URL = $new_name;
+ $URL =~ s/\/c\//c|\//;
+ $URL =~ s/^(.):/$1|/;
+ $URL = "file:///". $URL;
+ $installer::logger::Info->printf("open %s in your browser to see the log messages\n", $URL);
+ }
+ }
+ else
+ {
+ $installer::logger::Info->printf("Error: log file not found at %s\n", $log_filename);
+ }
+}
+
+
+
+
+sub CreateMsp ($)
+{
+ my ($pcp) = @_;
+
+ # Prepare log files.
+ my $log_path = File::Spec->catfile($pcp->{'path'}, "log");
+ my $log_basename = "msp";
+ my $log_filename = File::Spec->catfile($log_path, $log_basename.".log");
+ my $performance_log_basename = "performance";
+ my $performance_log_filename = File::Spec->catfile($log_path, $performance_log_basename.".log");
+ File::Path::make_path($log_path) if ! -d $log_path;
+ unlink($log_filename) if -f $log_filename;
+ unlink($performance_log_filename) if -f $performance_log_filename;
+
+ # Create the .msp patch file.
+ my $temporary_msimsp_path = File::Spec->catfile($pcp->{'path'}, "tmp");
+ if ( ! -d $temporary_msimsp_path)
+ {
+ File::Path::make_path($temporary_msimsp_path)
+ || die ("can not create temporary path ".$temporary_msimsp_path);
+ }
+ $installer::logger::Info->printf("running msimsp.exe, that will take a while\n");
+ my $command = join(" ",
+ "msimsp.exe",
+ "-s", "'".installer::patch::Tools::ToWindowsPath($pcp->{'filename'})."'",
+ "-p", "'".installer::patch::Tools::ToWindowsPath($pcp->{'msp_filename'})."'",
+ "-l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
+ "-f", "'".installer::patch::Tools::ToWindowsPath($temporary_msimsp_path)."'");
+# "-lp", MsiTools::ToEscapedWindowsPath($performance_log_filename),
+ $installer::logger::Info->printf("running command %s\n", $command);
+ my $response = qx($command);
+ $installer::logger::Info->printf("response of msimsp is %s\n", $response);
+ if ( ! -d $temporary_msimsp_path)
+ {
+ die("msimsp failed and deleted temporary path ".$temporary_msimsp_path);
+ }
+
+ # Show the log file that was created by the msimsp.exe command.
+ ShowLog($log_path, $log_filename, $log_basename, "msp creation");
+ ShowLog($log_path, $performance_log_filename, $performance_log_basename, "msp creation perf");
+}
+
+
+
+sub CreatePatch ($$)
+{
+ my ($context, $variables) = @_;
+
+ $installer::logger::Info->printf("patch will update product %s from %s to %s\n",
+ $context->{'product-name'},
+ $context->{'source-version'},
+ $context->{'target-version'});
+
+ # Locate the Pcp schema file early on to report any errors before the lengthy operations that follow.
+ my $pcp_schema_filename = FindPcpTemplate();
+ if ( ! defined $pcp_schema_filename)
+ {
+ exit(1);
+ }
+
+ my $release_data = installer::patch::ReleasesList::Instance()
+ ->{$context->{'source-version'}}
+ ->{$context->{'package-format'}};
+
+ # Create a patch for each language.
+ my @requested_languages = GetLanguages();
+ my @valid_languages = FindValidLanguages($context, $release_data, \@requested_languages);
+ $installer::logger::Info->printf("of the requested languages '%s' are valid: '%s'\n",
+ join("', '", @requested_languages),
+ join("', '", @valid_languages));
+ foreach my $language (@valid_languages)
+ {
+ $installer::logger::Info->printf("processing language '%s'\n", $language);
+ $installer::logger::Info->increase_indentation();
+
+ # Provide .msi and .cab files and unpacke .cab for the source release.
+ $installer::logger::Info->printf("locating source package (%s)\n", $context->{'source-version'});
+ $installer::logger::Info->increase_indentation();
+ if ( ! installer::patch::InstallationSet::ProvideUnpackedCab(
+ $context->{'source-version'},
+ 0,
+ $language,
+ "msi",
+ $context->{'product-name'}))
+ {
+ die "could not provide unpacked .cab file";
+ }
+ my $source_msi = installer::patch::Msi->FindAndCreate(
+ $context->{'source-version'},
+ 0,
+ $language,
+ $context->{'product-name'});
+ die unless $source_msi->IsValid();
+
+ $installer::logger::Info->decrease_indentation();
+
+ # Provide .msi and .cab files and unpacke .cab for the target release.
+ $installer::logger::Info->printf("locating target package (%s)\n", $context->{'target-version'});
+ $installer::logger::Info->increase_indentation();
+ if ( ! installer::patch::InstallationSet::ProvideUnpackedCab(
+ $context->{'target-version'},
+ 1,
+ $language,
+ "msi",
+ $context->{'product-name'}))
+ {
+ die;
+ }
+ my $target_msi = installer::patch::Msi->FindAndCreate(
+ $context->{'target-version'},
+ 0,
+ $language,
+ $context->{'product-name'});
+ die unless defined $target_msi;
+ die unless $target_msi->IsValid();
+ $installer::logger::Info->decrease_indentation();
+
+ # Trigger reading of tables.
+ foreach my $table_name (("File", "Component", "Registry"))
+ {
+ $source_msi->GetTable($table_name);
+ $target_msi->GetTable($table_name);
+ $installer::logger::Info->printf("read %s table (source and target\n", $table_name);
+ }
+
+ # Check if the source and target msis fullfil all necessary requirements.
+ if ( ! Check($source_msi, $target_msi, $variables, $context->{'product-name'}))
+ {
+ $installer::logger::Info->printf("Error: Source and target releases are not compatible.\n");
+ $installer::logger::Info->printf(" => Can not create patch.\n");
+ $installer::logger::Info->printf(" Did you create the target installation set with 'release=t' ?\n");
+ exit(1);
+ }
+ else
+ {
+ $installer::logger::Info->printf("OK: Source and target releases are compatible.\n");
+ }
+
+ # Provide the base path for creating .pcp and .mcp file.
+ my $msp_path = File::Spec->catfile(
+ $context->{'output-path'},
+ $context->{'product-name'},
+ "msp",
+ sprintf("%s_%s",
+ installer::patch::Version::ArrayToDirectoryName(
+ installer::patch::Version::StringToNumberArray(
+ $source_msi->{'version'})),
+ installer::patch::Version::ArrayToDirectoryName(
+ installer::patch::Version::StringToNumberArray(
+ $target_msi->{'version'}))),
+ $language
+ );
+ File::Path::make_path($msp_path) unless -d $msp_path;
+
+ # Create the .pcp file that drives the msimsp.exe command.
+ my $pcp = CreatePcp(
+ $source_msi,
+ $target_msi,
+ $language,
+ $context,
+ $msp_path,
+ $pcp_schema_filename,
+ "Properties/Name:DontRemoveTempFolderWhenFinished" => "Value:1",
+ "Properties/Name:SEQUENCE_DATA_GENERATION_DISABLED" => "Value:1",
+ "Properties/Name:TrustMsi" => "Value:0",
+ "Properties/Name:ProductName" => "Value:OOO341");
+
+ # Finally create the msp.
+ CreateMsp($pcp);
+
+ $installer::logger::Info->decrease_indentation();
+ }
+}
+
+
+
+
+sub ApplyPatch ($$)
+{
+ my ($context, $variables) = @_;
+
+ $installer::logger::Info->printf("will apply patches that update product %s from %s to %s\n",
+ $context->{'product-name'},
+ $context->{'source-version'},
+ $context->{'target-version'});
+ my @languages = GetLanguages();
+
+ my $source_version_dirname = installer::patch::Version::ArrayToDirectoryName(
+ installer::patch::Version::StringToNumberArray(
+ $context->{'source-version'}));
+ my $target_version_dirname = installer::patch::Version::ArrayToDirectoryName(
+ installer::patch::Version::StringToNumberArray(
+ $context->{'target-version'}));
+
+ foreach my $language (@languages)
+ {
+ my $msp_filename = File::Spec->catfile(
+ $context->{'output-path'},
+ $context->{'product-name'},
+ "msp",
+ $source_version_dirname . "_" . $target_version_dirname,
+ $language,
+ "openoffice.msp");
+ if ( ! -f $msp_filename)
+ {
+ $installer::logger::Info->printf("%s does not point to a valid file\n", $msp_filename);
+ next;
+ }
+
+ my $log_path = File::Spec->catfile(dirname($msp_filename), "log");
+ my $log_basename = "apply-msp";
+ my $log_filename = File::Spec->catfile($log_path, $log_basename.".log");
+
+ my $command = join(" ",
+ "msiexec.exe",
+ "/update", "'".installer::patch::Tools::ToWindowsPath($msp_filename)."'",
+ "/L*xv!", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
+ "REINSTALL=ALL",
+# "REINSTALLMODE=vomus",
+ "REINSTALLMODE=omus",
+ "MSIENFORCEUPGRADECOMPONENTRULES=1");
+
+ printf("executing command %s\n", $command);
+ my $response = qx($command);
+ Encode::from_to($response, "UTF16LE", "UTF8");
+ printf("response was '%s'\n", $response);
+
+ ShowLog($log_path, $log_filename, $log_basename, "msp application");
+ }
+}
+
+
+
+
+sub main ()
+{
+ installer::logger::SetupSimpleLogging(undef);
+ my $context = ProcessCommandline();
+ my ($variables, undef, undef) = installer::ziplist::read_openoffice_lst_file(
+ $context->{'lst-file'},
+ $context->{'product-name'},
+ undef);
+ DetermineVersions($context, $variables);
+
+ if ($context->{'command'} eq "create")
+ {
+ CreatePatch($context, $variables);
+ }
+ elsif ($context->{'command'} eq "apply")
+ {
+ ApplyPatch($context, $variables);
+ }
+}
+
+
+main();
Modified: openoffice/trunk/main/solenv/bin/release_prepare.pl
URL: http://svn.apache.org/viewvc/openoffice/trunk/main/solenv/bin/release_prepare.pl?rev=1547732&r1=1547731&r2=1547732&view=diff
==============================================================================
--- openoffice/trunk/main/solenv/bin/release_prepare.pl (original)
+++ openoffice/trunk/main/solenv/bin/release_prepare.pl Wed Dec 4 08:51:30 2013
@@ -32,6 +32,8 @@ use Getopt::Long;
use Pod::Usage;
use Digest;
+use Carp::Always;
+
use strict;
=head1 NAME
@@ -106,9 +108,9 @@ sub ProcessCommandline ()
-sub ProcessLanguage ($$$$)
+sub ProcessLanguage ($$$$$)
{
- my ($source_version, $language, $package_format, $product_name) = @_;
+ my ($version, $is_current_version, $language, $package_format, $product_name) = @_;
$installer::logger::Info->printf("%s\n", $language);
$installer::logger::Info->increase_indentation();
@@ -118,77 +120,12 @@ sub ProcessLanguage ($$$$)
# 2. unpack it to get access to .cab and .msi
# 3. unpack .cab so that msimsp.exe can be run
- # Create paths to unpacked contents of .exe and .cab and determine if they exist.
- # The existence of these paths is taken as flag whether the unpacking has already taken place.
- my $unpacked_exe_path = installer::patch::InstallationSet::GetUnpackedMsiPath(
- $source_version,
+ installer::patch::InstallationSet::ProvideUnpackedCab(
+ $version,
+ $is_current_version,
$language,
$package_format,
$product_name);
- my $unpacked_cab_path = installer::patch::InstallationSet::GetUnpackedCabPath(
- $source_version,
- $language,
- $package_format,
- $product_name);
- my $exe_is_unpacked = -d $unpacked_exe_path;
- my $cab_is_unpacked = -d $unpacked_cab_path;
-
- if ( ! $exe_is_unpacked)
- {
- # Interpret existence of path as proof that the installation
- # set and the cab file have been successfully unpacked.
- # Nothing to do.
- my $filename = installer::patch::InstallationSet::ProvideDownloadSet(
- $source_version,
- $language,
- $package_format);
- if (defined $filename)
- {
- if ( ! -d $unpacked_exe_path)
- {
- installer::patch::InstallationSet::UnpackExe($filename, $unpacked_exe_path);
- }
- }
- else
- {
- installer::logger::PrintError("could not provide .exe installation set at '%s'\n", $filename);
- }
- }
- else
- {
- $installer::logger::Info->printf("downloadable installation set has already been unpacked to '%s'\n",
- $unpacked_exe_path);
- }
-
- if ( ! $cab_is_unpacked)
- {
- my $cab_filename = File::Spec->catfile($unpacked_exe_path, "openoffice1.cab");
- if ( ! -f $cab_filename)
- {
- # Cab file does not exist.
- installer::logger::PrintError(
- "could not find .cab file at '%s'. Extraction of .exe seems to have failed.\n",
- $cab_filename);
- }
-
- # Unpack the cab file.
- my $msi = new installer::patch::Msi(
- $source_version,
- $language,
- $product_name);
-
- $installer::logger::Info->printf("unpacking cab file '%s' to '%s'\n",
- $cab_filename, $unpacked_cab_path);
- installer::patch::InstallationSet::UnpackCab(
- $cab_filename,
- $msi,
- $unpacked_cab_path);
- }
- else
- {
- $installer::logger::Info->printf("cab has already been unpacked to\n");
- $installer::logger::Info->printf(" %s\n", $unpacked_cab_path);
- }
$installer::logger::Info->decrease_indentation();
}
@@ -196,31 +133,53 @@ sub ProcessLanguage ($$$$)
-installer::logger::SetupSimpleLogging("c:/tmp/log");
+sub main ()
+{
+ installer::logger::SetupSimpleLogging();
-my $arguments = ProcessCommandline();
-$arguments->{'package-format'} = 'msi';
+ my $arguments = ProcessCommandline();
+ $arguments->{'package-format'} = 'msi';
-print "preparing release build\n";
-my ($variables, undef, undef)
- = installer::ziplist::read_openoffice_lst_file(
+ $installer::logger::Info->print("preparing release build\n");
+ my ($variables, undef, undef)
+ = installer::ziplist::read_openoffice_lst_file(
$arguments->{'lst-file'},
$arguments->{'product-name'},
undef);
-if ( ! defined $arguments->{'source-version'})
-{
- $arguments->{'source-version'} = $variables->{'PREVIOUS_VERSION'};
+ if ( ! defined $arguments->{'source-version'})
+ {
+ $arguments->{'source-version'} = $variables->{'PREVIOUS_VERSION'};
+ if ( ! defined $arguments->{'source-version'})
+ {
+ $arguments->{'source-version'} = installer::patch::ReleasesList::GetPreviousVersion(
+ $variables->{'PRODUCTVERSION'});
+ if ( ! defined $arguments->{'source-version'})
+ {
+ $installer::logger::Info->printf("ERROR: can not autodetect previous version\n");
+ $installer::logger::Info->printf(" please specify via 'PREVIOUS_VERSION' in %s\n",
+ $arguments->{'lst-file'});
+ $installer::logger::Info->printf(" or the --source-version commandline option\n");
+ exit(1);
+ }
+ }
+ }
+ my $current_version = $variables->{'PRODUCTVERSION'};
+ $installer::logger::Info->printf("data from '%s'\n", $arguments->{'lst-file'});
+ $installer::logger::Info->printf("name is '%s'\n", $arguments->{'product-name'});
+ $installer::logger::Info->printf("path is '%s'\n", $arguments->{'output-path'});
+ $installer::logger::Info->printf("source version is '%s'\n", $arguments->{'source-version'});
+ $installer::logger::Info->printf("target version is '%s'\n", $current_version);
+
+ foreach my $language (@{$arguments->{'languages'}})
+ {
+ ProcessLanguage(
+ $arguments->{'source-version'},
+ $arguments->{'source-version'} eq $current_version,
+ $language,
+ $arguments->{'package-format'},
+ $arguments->{'product-name'});
+ }
}
-$installer::logger::Info->printf(" reading data from '%s'\n", $arguments->{'lst-file'});
-$installer::logger::Info->printf(" product name is '%s'\n", $arguments->{'product-name'});
-$installer::logger::Info->printf(" output path is '%s'\n", $arguments->{'output-path'});
-$installer::logger::Info->printf(" source version is '%s'\n", $arguments->{'source-version'});
-foreach my $language (@{$arguments->{'languages'}})
-{
- ProcessLanguage(
- $arguments->{'source-version'},
- $language,
- $arguments->{'package-format'},
- $arguments->{'product-name'});
-}
+
+main();