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();
