xref: /AOO41X/main/solenv/bin/patch_tool.pl (revision d575d58faf6a1c4e48dda7b480be8cda57b8c8e5)
19f91b7e3SAndre Fischer#!/usr/bin/perl -w
29f91b7e3SAndre Fischer
39f91b7e3SAndre Fischer#**************************************************************
49f91b7e3SAndre Fischer#
59f91b7e3SAndre Fischer#  Licensed to the Apache Software Foundation (ASF) under one
69f91b7e3SAndre Fischer#  or more contributor license agreements.  See the NOTICE file
79f91b7e3SAndre Fischer#  distributed with this work for additional information
89f91b7e3SAndre Fischer#  regarding copyright ownership.  The ASF licenses this file
99f91b7e3SAndre Fischer#  to you under the Apache License, Version 2.0 (the
109f91b7e3SAndre Fischer#  "License"); you may not use this file except in compliance
119f91b7e3SAndre Fischer#  with the License.  You may obtain a copy of the License at
129f91b7e3SAndre Fischer#
139f91b7e3SAndre Fischer#    http://www.apache.org/licenses/LICENSE-2.0
149f91b7e3SAndre Fischer#
159f91b7e3SAndre Fischer#  Unless required by applicable law or agreed to in writing,
169f91b7e3SAndre Fischer#  software distributed under the License is distributed on an
179f91b7e3SAndre Fischer#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
189f91b7e3SAndre Fischer#  KIND, either express or implied.  See the License for the
199f91b7e3SAndre Fischer#  specific language governing permissions and limitations
209f91b7e3SAndre Fischer#  under the License.
219f91b7e3SAndre Fischer#
229f91b7e3SAndre Fischer#**************************************************************
239f91b7e3SAndre Fischer
249f91b7e3SAndre Fischeruse Getopt::Long;
259f91b7e3SAndre Fischeruse Pod::Usage;
269f91b7e3SAndre Fischeruse File::Path;
279f91b7e3SAndre Fischeruse File::Spec;
289f91b7e3SAndre Fischeruse File::Basename;
299f91b7e3SAndre Fischeruse XML::LibXML;
309f91b7e3SAndre Fischeruse Digest;
319f91b7e3SAndre Fischeruse Archive::Zip;
329f91b7e3SAndre Fischeruse Archive::Extract;
339f91b7e3SAndre Fischer
349f91b7e3SAndre Fischeruse installer::ziplist;
359f91b7e3SAndre Fischeruse installer::logger;
369f91b7e3SAndre Fischeruse installer::windows::msiglobal;
379f91b7e3SAndre Fischeruse installer::patch::Msi;
389f91b7e3SAndre Fischeruse installer::patch::ReleasesList;
399f91b7e3SAndre Fischeruse installer::patch::Version;
409f91b7e3SAndre Fischer
419f91b7e3SAndre Fischeruse strict;
429f91b7e3SAndre Fischer
439f91b7e3SAndre Fischer
449f91b7e3SAndre Fischer=head1 NAME
459f91b7e3SAndre Fischer
469f91b7e3SAndre Fischer    patch_tool.pl - Create Windows MSI patches.
479f91b7e3SAndre Fischer
489f91b7e3SAndre Fischer=head1 SYNOPSIS
499f91b7e3SAndre Fischer
509f91b7e3SAndre Fischer    patch_tool.pl command [options]
519f91b7e3SAndre Fischer
529f91b7e3SAndre Fischer    Commands:
539f91b7e3SAndre Fischer        create    create patches
549f91b7e3SAndre Fischer        apply     apply patches
559f91b7e3SAndre Fischer
569f91b7e3SAndre Fischer    Options:
579f91b7e3SAndre Fischer        -p|--product-name <product-name>
589f91b7e3SAndre Fischer             The product name, eg Apache_OpenOffice
599f91b7e3SAndre Fischer        -o|--output-path <path>
609f91b7e3SAndre Fischer             Path to the instsetoo_native platform output tree
619f91b7e3SAndre Fischer        -d|--data-path <path>
629f91b7e3SAndre Fischer             Path to the data directory that is expected to be under version control.
639f91b7e3SAndre Fischer        --source-version <major>.<minor>.<micro>
649f91b7e3SAndre Fischer             The version that is to be patched.
659f91b7e3SAndre Fischer        --target-version <major>.<minor>.<micro>
669f91b7e3SAndre Fischer             The version after the patch has been applied.
679f91b7e3SAndre Fischer
689f91b7e3SAndre Fischer=head1 DESCRIPTION
699f91b7e3SAndre Fischer
709f91b7e3SAndre Fischer    Creates windows MSP patch files, one for each relevant language.
719f91b7e3SAndre Fischer    Patches convert an installed OpenOffice to the target version.
729f91b7e3SAndre Fischer
739f91b7e3SAndre Fischer    Required data are:
749f91b7e3SAndre Fischer        Installation sets of the source versions
759f91b7e3SAndre Fischer            Taken from ext_sources/
769f91b7e3SAndre Fischer            Downloaded from archive.apache.org on demand
779f91b7e3SAndre Fischer
789f91b7e3SAndre Fischer        Installation set of the target version
799f91b7e3SAndre Fischer            This is expected to be the current version.
809f91b7e3SAndre Fischer
819f91b7e3SAndre Fischer=cut
829f91b7e3SAndre Fischer
839f91b7e3SAndre Fischer#    my $ImageFamily = "MNPapps";
849f91b7e3SAndre Fischer# The ImageFamily name has to have 1-8 alphanumeric characters.
859f91b7e3SAndre Fischermy $ImageFamily = "AOO";
869f91b7e3SAndre Fischermy $SourceImageName = "Source";
879f91b7e3SAndre Fischermy $TargetImageName = "Target";
889f91b7e3SAndre Fischer
899f91b7e3SAndre Fischer
909f91b7e3SAndre Fischer
919f91b7e3SAndre Fischersub ProcessCommandline ()
929f91b7e3SAndre Fischer{
939f91b7e3SAndre Fischer    my $arguments = {
949f91b7e3SAndre Fischer        'product-name' => undef,
959f91b7e3SAndre Fischer        'output-path' => undef,
969f91b7e3SAndre Fischer        'data-path' => undef,
979f91b7e3SAndre Fischer        'lst-file' => undef,
989f91b7e3SAndre Fischer        'source-version' => undef,
999f91b7e3SAndre Fischer        'target-version' => undef};
1009f91b7e3SAndre Fischer
1019f91b7e3SAndre Fischer    if ( ! GetOptions(
1029f91b7e3SAndre Fischer               "product-name=s", \$arguments->{'product-name'},
1039f91b7e3SAndre Fischer               "output-path=s", \$arguments->{'output-path'},
1049f91b7e3SAndre Fischer               "data-path=s" => \$arguments->{'data-path'},
1059f91b7e3SAndre Fischer               "lst-file=s" => \$arguments->{'lst-file'},
1069f91b7e3SAndre Fischer               "source-version:s" => \$arguments->{'source-version'},
1079f91b7e3SAndre Fischer               "target-version:s" => \$arguments->{'target-version'}
1089f91b7e3SAndre Fischer        ))
1099f91b7e3SAndre Fischer    {
1109f91b7e3SAndre Fischer        pod2usage(2);
1119f91b7e3SAndre Fischer    }
1129f91b7e3SAndre Fischer
1139f91b7e3SAndre Fischer    # Only the command should be left in @ARGV.
1149f91b7e3SAndre Fischer    pod2usage(2) unless scalar @ARGV == 1;
1159f91b7e3SAndre Fischer    $arguments->{'command'} = shift @ARGV;
1169f91b7e3SAndre Fischer
1179f91b7e3SAndre Fischer    # At the moment we only support patches on windows.  When this
1189f91b7e3SAndre Fischer    # is extended in the future we need the package format as an
1199f91b7e3SAndre Fischer    # argument.
1209f91b7e3SAndre Fischer    $arguments->{'package-format'} = "msi";
1219f91b7e3SAndre Fischer
1229f91b7e3SAndre Fischer    return $arguments;
1239f91b7e3SAndre Fischer}
1249f91b7e3SAndre Fischer
1259f91b7e3SAndre Fischer
1269f91b7e3SAndre Fischer
1279f91b7e3SAndre Fischer
1289f91b7e3SAndre Fischersub GetSourceMsiPath ($$)
1299f91b7e3SAndre Fischer{
1309f91b7e3SAndre Fischer    my ($context, $language) = @_;
1319f91b7e3SAndre Fischer    my $unpacked_path = File::Spec->catfile(
1329f91b7e3SAndre Fischer	$context->{'output-path'},
1339f91b7e3SAndre Fischer	$context->{'product-name'},
1349f91b7e3SAndre Fischer        $context->{'package-format'},
1359f91b7e3SAndre Fischer	installer::patch::Version::ArrayToDirectoryName(
1369f91b7e3SAndre Fischer	    installer::patch::Version::StringToNumberArray(
1379f91b7e3SAndre Fischer		$context->{'source-version'})),
1389f91b7e3SAndre Fischer	$language);
1399f91b7e3SAndre Fischer}
1409f91b7e3SAndre Fischer
1419f91b7e3SAndre Fischer
1429f91b7e3SAndre Fischer
1439f91b7e3SAndre Fischer
1449f91b7e3SAndre Fischersub GetTargetMsiPath ($$)
1459f91b7e3SAndre Fischer{
1469f91b7e3SAndre Fischer    my ($context, $language) = @_;
1479f91b7e3SAndre Fischer    return File::Spec->catfile(
1489f91b7e3SAndre Fischer        $context->{'output-path'},
1499f91b7e3SAndre Fischer        $context->{'product-name'},
1509f91b7e3SAndre Fischer        $context->{'package-format'},
1519f91b7e3SAndre Fischer        "install",
1529f91b7e3SAndre Fischer        $language);
1539f91b7e3SAndre Fischer}
1549f91b7e3SAndre Fischer
1559f91b7e3SAndre Fischer
1569f91b7e3SAndre Fischer
1579f91b7e3SAndre Fischersub ProvideInstallationSets ($$)
1589f91b7e3SAndre Fischer{
1599f91b7e3SAndre Fischer    my ($context, $language) = @_;
1609f91b7e3SAndre Fischer
1619f91b7e3SAndre Fischer    # Assume that the target installation set is located in the output tree.
1629f91b7e3SAndre Fischer    my $target_path = GetTargetMsiPath($context, $language);
1639f91b7e3SAndre Fischer    if ( ! -d $target_path)
1649f91b7e3SAndre Fischer    {
1659f91b7e3SAndre Fischer        installer::logger::PrintError("can not find target installation set at '%s'\n", $target_path);
1669f91b7e3SAndre Fischer        return 0;
1679f91b7e3SAndre Fischer    }
1689f91b7e3SAndre Fischer    my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'});
1699f91b7e3SAndre Fischer    my $target_msi_file = File::Spec->catfile(
1709f91b7e3SAndre Fischer        $target_path,
1719f91b7e3SAndre Fischer        sprintf("openoffice%d%d%d.msi", $target_version[0], $target_version[1], $target_version[2]));
1729f91b7e3SAndre Fischer    if ( ! -f $target_msi_file)
1739f91b7e3SAndre Fischer    {
1749f91b7e3SAndre Fischer        installer::logger::PrintError("can not find target msi file at '%s'\n", $target_msi_file);
1759f91b7e3SAndre Fischer        return 0;
1769f91b7e3SAndre Fischer    }
1779f91b7e3SAndre Fischer
1789f91b7e3SAndre Fischer    return 1;
1799f91b7e3SAndre Fischer}
1809f91b7e3SAndre Fischer
1819f91b7e3SAndre Fischer
1829f91b7e3SAndre Fischer
1839f91b7e3SAndre Fischer
1849f91b7e3SAndre Fischersub GetLanguages ()
1859f91b7e3SAndre Fischer{
1869f91b7e3SAndre Fischer    # The set of languages is taken from the WITH_LANG environment variable.
1879f91b7e3SAndre Fischer    # If that is missing or is empty then the default 'en-US' is used instead.
1889f91b7e3SAndre Fischer    my @languages = ("en-US");
1899f91b7e3SAndre Fischer    my $with_lang = $ENV{'WITH_LANG'};
1909f91b7e3SAndre Fischer    if (defined $with_lang && $with_lang ne "")
1919f91b7e3SAndre Fischer    {
1929f91b7e3SAndre Fischer        @languages = split(/\s+/, $with_lang);
1939f91b7e3SAndre Fischer    }
1949f91b7e3SAndre Fischer    return @languages;
1959f91b7e3SAndre Fischer}
1969f91b7e3SAndre Fischer
1979f91b7e3SAndre Fischer
1989f91b7e3SAndre Fischer
1999f91b7e3SAndre Fischer
2009f91b7e3SAndre Fischersub FindValidLanguages ($$$)
2019f91b7e3SAndre Fischer{
2029f91b7e3SAndre Fischer    my ($context, $release_data, $languages) = @_;
2039f91b7e3SAndre Fischer
2049f91b7e3SAndre Fischer    my @valid_languages = ();
2059f91b7e3SAndre Fischer    foreach my $language (@$languages)
2069f91b7e3SAndre Fischer    {
2079f91b7e3SAndre Fischer        if ( ! ProvideInstallationSets($context, $language))
2089f91b7e3SAndre Fischer        {
2099f91b7e3SAndre Fischer            installer::logger::PrintError("    '%s' has no target installation set\n", $language);
2109f91b7e3SAndre Fischer        }
2119f91b7e3SAndre Fischer        elsif ( ! defined $release_data->{$language})
2129f91b7e3SAndre Fischer        {
2139f91b7e3SAndre Fischer            installer::logger::PrintError("    '%s' is not a released language for version %s\n",
2149f91b7e3SAndre Fischer                $language,
2159f91b7e3SAndre Fischer                $context->{'source-version'});
2169f91b7e3SAndre Fischer        }
2179f91b7e3SAndre Fischer        else
2189f91b7e3SAndre Fischer        {
2199f91b7e3SAndre Fischer            push @valid_languages, $language;
2209f91b7e3SAndre Fischer        }
2219f91b7e3SAndre Fischer    }
2229f91b7e3SAndre Fischer
2239f91b7e3SAndre Fischer    return @valid_languages;
2249f91b7e3SAndre Fischer}
2259f91b7e3SAndre Fischer
2269f91b7e3SAndre Fischer
2279f91b7e3SAndre Fischer
2289f91b7e3SAndre Fischer
2299f91b7e3SAndre Fischersub ProvideSourceInstallationSet ($$$)
2309f91b7e3SAndre Fischer{
2319f91b7e3SAndre Fischer    my ($context, $language, $release_data) = @_;
2329f91b7e3SAndre Fischer
2339f91b7e3SAndre Fischer    my $url = $release_data->{$language}->{'URL'};
2349f91b7e3SAndre Fischer    $url =~ /^(.*)\/([^\/]*)$/;
2359f91b7e3SAndre Fischer    my ($location, $basename) = ($1,$2);
2369f91b7e3SAndre Fischer
2379f91b7e3SAndre Fischer    my $ext_sources_path = $ENV{'TARFILE_LOCATION'};
2389f91b7e3SAndre Fischer    if ( ! -d $ext_sources_path)
2399f91b7e3SAndre Fischer    {
2409f91b7e3SAndre Fischer        installer::logger::PrintError("Can not determine the path to ext_sources/.\n");
2419f91b7e3SAndre Fischer        installer::logger::PrintError("Maybe SOURCE_ROOT_DIR has not been correctly set in the environment?");
2429f91b7e3SAndre Fischer        return 0;
2439f91b7e3SAndre Fischer    }
2449f91b7e3SAndre Fischer
2459f91b7e3SAndre Fischer    # We need the unpacked installation set in <platform>/<product>/<package>/<source-version>,
2469f91b7e3SAndre Fischer    # eg wntmsci12.pro/Apache_OpenOffice/msi/v-4-0-0.
2479f91b7e3SAndre Fischer    my $unpacked_path = GetSourceMsiPath($context, $language);
2489f91b7e3SAndre Fischer    if ( ! -d $unpacked_path)
2499f91b7e3SAndre Fischer    {
2509f91b7e3SAndre Fischer        # Make sure that the downloadable installation set (.exe) is present in ext_sources/.
2519f91b7e3SAndre Fischer        my $filename = File::Spec->catfile($ext_sources_path, $basename);
2529f91b7e3SAndre Fischer        if ( -f $filename)
2539f91b7e3SAndre Fischer        {
2549f91b7e3SAndre Fischer            PrintInfo("%s is already present in ext_sources/.  Nothing to do\n", $basename);
2559f91b7e3SAndre Fischer        }
2569f91b7e3SAndre Fischer        else
2579f91b7e3SAndre Fischer        {
258*d575d58fSAndre Fischer            return 0 if ! installer::patch::InstallationSet::Download(
259*d575d58fSAndre Fischer                $language,
260*d575d58fSAndre Fischer                $release_data,
261*d575d58fSAndre Fischer                $filename);
2629f91b7e3SAndre Fischer            return 0 if ! -f $filename;
2639f91b7e3SAndre Fischer        }
2649f91b7e3SAndre Fischer
2659f91b7e3SAndre Fischer        # Unpack the installation set.
2669f91b7e3SAndre Fischer        if ( -d $unpacked_path)
2679f91b7e3SAndre Fischer        {
2689f91b7e3SAndre Fischer            # Take the existence of the destination path as proof that the
2699f91b7e3SAndre Fischer            # installation set was successfully unpacked before.
2709f91b7e3SAndre Fischer        }
2719f91b7e3SAndre Fischer        else
2729f91b7e3SAndre Fischer        {
2739f91b7e3SAndre Fischer            installer::patch::InstallationSet::Unpack($filename, $unpacked_path);
2749f91b7e3SAndre Fischer        }
2759f91b7e3SAndre Fischer    }
2769f91b7e3SAndre Fischer}
2779f91b7e3SAndre Fischer
2789f91b7e3SAndre Fischer
2799f91b7e3SAndre Fischer
2809f91b7e3SAndre Fischer
2819f91b7e3SAndre Fischer# Find the source and target version between which the patch will be
2829f91b7e3SAndre Fischer# created.  Typically the target version is the current version and
2839f91b7e3SAndre Fischer# the source version is the version of the previous release.
2849f91b7e3SAndre Fischersub DetermineVersions ($$)
2859f91b7e3SAndre Fischer{
2869f91b7e3SAndre Fischer    my ($context, $variables) = @_;
2879f91b7e3SAndre Fischer
2889f91b7e3SAndre Fischer    if (defined $context->{'source-version'} && defined $context->{'target-version'})
2899f91b7e3SAndre Fischer    {
2909f91b7e3SAndre Fischer        # Both source and target version have been specified on the
2919f91b7e3SAndre Fischer        # command line.  There remains nothing to be be done.
2929f91b7e3SAndre Fischer        return;
2939f91b7e3SAndre Fischer    }
2949f91b7e3SAndre Fischer
2959f91b7e3SAndre Fischer    if ( ! defined $context->{'target-version'})
2969f91b7e3SAndre Fischer    {
2979f91b7e3SAndre Fischer        # Use the current version as target version.
2989f91b7e3SAndre Fischer        $context->{'target-version'} = $variables->{PRODUCTVERSION};
2999f91b7e3SAndre Fischer    }
3009f91b7e3SAndre Fischer
3019f91b7e3SAndre Fischer    my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'});
3029f91b7e3SAndre Fischer    shift @target_version;
3039f91b7e3SAndre Fischer    my $is_target_version_major = 1;
3049f91b7e3SAndre Fischer    foreach my $number (@target_version)
3059f91b7e3SAndre Fischer    {
3069f91b7e3SAndre Fischer        $is_target_version_major = 0 if ($number ne "0");
3079f91b7e3SAndre Fischer    }
3089f91b7e3SAndre Fischer    if ($is_target_version_major)
3099f91b7e3SAndre Fischer    {
3109f91b7e3SAndre Fischer        installer::logger::PrintError("can not create patch where target version is a new major version (%s)\n",
3119f91b7e3SAndre Fischer            $context->{'target-version'});
3129f91b7e3SAndre Fischer        die;
3139f91b7e3SAndre Fischer    }
3149f91b7e3SAndre Fischer
3159f91b7e3SAndre Fischer    if ( ! defined $context->{'source-version'})
3169f91b7e3SAndre Fischer    {
3179f91b7e3SAndre Fischer        my $releases = installer::patch::ReleasesList::Instance();
3189f91b7e3SAndre Fischer
3199f91b7e3SAndre Fischer        # Search for target release in the list of previous releases.
3209f91b7e3SAndre Fischer        # If it is found, use the previous version as source version.
3219f91b7e3SAndre Fischer        # Otherwise use the last released version.
3229f91b7e3SAndre Fischer        my $last_release = undef;
3239f91b7e3SAndre Fischer        foreach my $release (@{$releases->{'releases'}})
3249f91b7e3SAndre Fischer        {
3259f91b7e3SAndre Fischer            last if ($release eq $context->{'target-version'});
3269f91b7e3SAndre Fischer            $last_release = $release;
3279f91b7e3SAndre Fischer        }
3289f91b7e3SAndre Fischer        $context->{'source-version'} = $last_release;
3299f91b7e3SAndre Fischer    }
3309f91b7e3SAndre Fischer}
3319f91b7e3SAndre Fischer
3329f91b7e3SAndre Fischer
3339f91b7e3SAndre Fischer
3349f91b7e3SAndre Fischer
3359f91b7e3SAndre Fischer=head2 CheckUpgradeCode($source_msi, $target_msi)
3369f91b7e3SAndre Fischer
3379f91b7e3SAndre Fischer    The 'UpgradeCode' values in the 'Property' table differs from source to target
3389f91b7e3SAndre Fischer
3399f91b7e3SAndre Fischer=cut
3409f91b7e3SAndre Fischersub CheckUpgradeCode($$)
3419f91b7e3SAndre Fischer{
3429f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
3439f91b7e3SAndre Fischer
3449f91b7e3SAndre Fischer    my $source_upgrade_code = $source_msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value");
3459f91b7e3SAndre Fischer    my $target_upgrade_code = $target_msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value");
3469f91b7e3SAndre Fischer
3479f91b7e3SAndre Fischer    if ($source_upgrade_code eq $target_upgrade_code)
3489f91b7e3SAndre Fischer    {
3499f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: The UpgradeCode properties have to differ but are both '%s'\n",
3509f91b7e3SAndre Fischer            $source_upgrade_code);
3519f91b7e3SAndre Fischer        return 0;
3529f91b7e3SAndre Fischer    }
3539f91b7e3SAndre Fischer    else
3549f91b7e3SAndre Fischer    {
3559f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: UpgradeCode values are identical\n");
3569f91b7e3SAndre Fischer        return 1;
3579f91b7e3SAndre Fischer    }
3589f91b7e3SAndre Fischer}
3599f91b7e3SAndre Fischer
3609f91b7e3SAndre Fischer
3619f91b7e3SAndre Fischer
3629f91b7e3SAndre Fischer
3639f91b7e3SAndre Fischer=head2 CheckProductCode($source_msi, $target_msi)
3649f91b7e3SAndre Fischer
3659f91b7e3SAndre Fischer    The 'ProductCode' values in the 'Property' tables remain the same.
3669f91b7e3SAndre Fischer
3679f91b7e3SAndre Fischer=cut
3689f91b7e3SAndre Fischersub CheckProductCode($$)
3699f91b7e3SAndre Fischer{
3709f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
3719f91b7e3SAndre Fischer
3729f91b7e3SAndre Fischer    my $source_product_code = $source_msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value");
3739f91b7e3SAndre Fischer    my $target_product_code = $target_msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value");
3749f91b7e3SAndre Fischer
3759f91b7e3SAndre Fischer    if ($source_product_code ne $target_product_code)
3769f91b7e3SAndre Fischer    {
3779f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: The ProductCode properties have to remain the same but are\n");
3789f91b7e3SAndre Fischer        $installer::logger::Info->printf("       '%s' and '%s'\n",
3799f91b7e3SAndre Fischer            $source_product_code,
3809f91b7e3SAndre Fischer            $target_product_code);
3819f91b7e3SAndre Fischer        return 0;
3829f91b7e3SAndre Fischer    }
3839f91b7e3SAndre Fischer    else
3849f91b7e3SAndre Fischer    {
3859f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: ProductCode properties differ\n");
3869f91b7e3SAndre Fischer        return 1;
3879f91b7e3SAndre Fischer    }
3889f91b7e3SAndre Fischer}
3899f91b7e3SAndre Fischer
3909f91b7e3SAndre Fischer
3919f91b7e3SAndre Fischer
3929f91b7e3SAndre Fischer
3939f91b7e3SAndre Fischer=head2 CheckBuildIdCode($source_msi, $target_msi)
3949f91b7e3SAndre Fischer
3959f91b7e3SAndre Fischer    The 'PRODUCTBUILDID' values in the 'Property' tables (not the AOO build ids) differ and the
3969f91b7e3SAndre Fischer    target value is higher than the source value.
3979f91b7e3SAndre Fischer
3989f91b7e3SAndre Fischer=cut
3999f91b7e3SAndre Fischersub CheckBuildIdCode($$)
4009f91b7e3SAndre Fischer{
4019f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
4029f91b7e3SAndre Fischer
4039f91b7e3SAndre Fischer    my $source_build_id = $source_msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value");
4049f91b7e3SAndre Fischer    my $target_build_id = $target_msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value");
4059f91b7e3SAndre Fischer
4069f91b7e3SAndre Fischer    if ($source_build_id >= $target_build_id)
4079f91b7e3SAndre Fischer    {
4089f91b7e3SAndre Fischer        $installer::logger::Info->printf(
4099f91b7e3SAndre Fischer            "Error: The PRODUCTBUILDID properties have to increase but are '%s' and '%s'\n",
4109f91b7e3SAndre Fischer            $source_build_id,
4119f91b7e3SAndre Fischer            $target_build_id);
4129f91b7e3SAndre Fischer        return 0;
4139f91b7e3SAndre Fischer    }
4149f91b7e3SAndre Fischer    else
4159f91b7e3SAndre Fischer    {
4169f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: source build id is lower than target build id\n");
4179f91b7e3SAndre Fischer        return 1;
4189f91b7e3SAndre Fischer    }
4199f91b7e3SAndre Fischer}
4209f91b7e3SAndre Fischer
4219f91b7e3SAndre Fischer
4229f91b7e3SAndre Fischer
4239f91b7e3SAndre Fischer
4249f91b7e3SAndre Fischersub CheckProductName ($$)
4259f91b7e3SAndre Fischer{
4269f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
4279f91b7e3SAndre Fischer
4289f91b7e3SAndre Fischer    my $source_product_name = $source_msi->GetTable("Property")->GetValue("Property", "DEFINEDPRODUCT", "Value");
4299f91b7e3SAndre Fischer    my $target_product_name = $target_msi->GetTable("Property")->GetValue("Property", "DEFINEDPRODUCT", "Value");
4309f91b7e3SAndre Fischer
4319f91b7e3SAndre Fischer    if ($source_product_name ne $target_product_name)
4329f91b7e3SAndre Fischer    {
4339f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: product names of are not identical:\n");
4349f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s != %s\n", $source_product_name, $target_product_name);
4359f91b7e3SAndre Fischer        return 0;
4369f91b7e3SAndre Fischer    }
4379f91b7e3SAndre Fischer    else
4389f91b7e3SAndre Fischer    {
4399f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: product names are identical\n");
4409f91b7e3SAndre Fischer        return 1;
4419f91b7e3SAndre Fischer    }
4429f91b7e3SAndre Fischer}
4439f91b7e3SAndre Fischer
4449f91b7e3SAndre Fischer
4459f91b7e3SAndre Fischer
4469f91b7e3SAndre Fischer
4479f91b7e3SAndre Fischer=head2 CheckRemovedFiles($source_msi, $target_msi)
4489f91b7e3SAndre Fischer
4499f91b7e3SAndre Fischer    Files and components must not be deleted.
4509f91b7e3SAndre Fischer
4519f91b7e3SAndre Fischer=cut
4529f91b7e3SAndre Fischersub CheckRemovedFiles($$)
4539f91b7e3SAndre Fischer{
4549f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
4559f91b7e3SAndre Fischer
4569f91b7e3SAndre Fischer    # Get the 'File' tables.
4579f91b7e3SAndre Fischer    my $source_file_table = $source_msi->GetTable("File");
4589f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
4599f91b7e3SAndre Fischer
4609f91b7e3SAndre Fischer    # Create data structures for fast lookup.
4619f91b7e3SAndre Fischer    my @source_files = map {$_->GetValue("File")} @{$source_file_table->GetAllRows()};
4629f91b7e3SAndre Fischer    my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()};
4639f91b7e3SAndre Fischer
4649f91b7e3SAndre Fischer    # Search for removed files (files in source that are missing from target).
4659f91b7e3SAndre Fischer    my $removed_file_count = 0;
4669f91b7e3SAndre Fischer    foreach my $uniquename (@source_files)
4679f91b7e3SAndre Fischer    {
4689f91b7e3SAndre Fischer        if ( ! defined $target_file_map{$uniquename})
4699f91b7e3SAndre Fischer        {
4709f91b7e3SAndre Fischer            ++$removed_file_count;
4719f91b7e3SAndre Fischer        }
4729f91b7e3SAndre Fischer    }
4739f91b7e3SAndre Fischer
4749f91b7e3SAndre Fischer    if ($removed_file_count > 0)
4759f91b7e3SAndre Fischer    {
4769f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: %d files have been removed\n", $removed_file_count);
4779f91b7e3SAndre Fischer        return 0;
4789f91b7e3SAndre Fischer    }
4799f91b7e3SAndre Fischer    else
4809f91b7e3SAndre Fischer    {
4819f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: no files have been removed\n");
4829f91b7e3SAndre Fischer        return 1;
4839f91b7e3SAndre Fischer    }
4849f91b7e3SAndre Fischer}
4859f91b7e3SAndre Fischer
4869f91b7e3SAndre Fischer
4879f91b7e3SAndre Fischer
4889f91b7e3SAndre Fischer
4899f91b7e3SAndre Fischer=head2 CheckNewFiles($source_msi, $target_msi)
4909f91b7e3SAndre Fischer
4919f91b7e3SAndre Fischer    New files have to be in new components.
4929f91b7e3SAndre Fischer
4939f91b7e3SAndre Fischer=cut
4949f91b7e3SAndre Fischersub CheckNewFiles($$)
4959f91b7e3SAndre Fischer{
4969f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
4979f91b7e3SAndre Fischer
4989f91b7e3SAndre Fischer    # Get the 'File' tables.
4999f91b7e3SAndre Fischer    my $source_file_table = $source_msi->GetTable("File");
5009f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
5019f91b7e3SAndre Fischer
5029f91b7e3SAndre Fischer    # Create data structures for fast lookup.
5039f91b7e3SAndre Fischer    my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()};
5049f91b7e3SAndre Fischer    my @target_files = map {$_->GetValue("File")} @{$target_file_table->GetAllRows()};
5059f91b7e3SAndre Fischer
5069f91b7e3SAndre Fischer    # Search for added files (files in target that where not in source).
5079f91b7e3SAndre Fischer    my $added_file_count = 0;
5089f91b7e3SAndre Fischer    foreach my $uniquename (@target_files)
5099f91b7e3SAndre Fischer    {
5109f91b7e3SAndre Fischer        if ( ! defined $source_file_map{$uniquename})
5119f91b7e3SAndre Fischer        {
5129f91b7e3SAndre Fischer            ++$added_file_count;
5139f91b7e3SAndre Fischer        }
5149f91b7e3SAndre Fischer    }
5159f91b7e3SAndre Fischer
5169f91b7e3SAndre Fischer    if ($added_file_count > 0)
5179f91b7e3SAndre Fischer    {
5189f91b7e3SAndre Fischer        $installer::logger::Info->printf("Warning: %d files have been added\n", $added_file_count);
5199f91b7e3SAndre Fischer
5209f91b7e3SAndre Fischer        $installer::logger::Info->printf("Check for new files being part of new components is not yet implemented\n");
5219f91b7e3SAndre Fischer
5229f91b7e3SAndre Fischer        return 1;
5239f91b7e3SAndre Fischer    }
5249f91b7e3SAndre Fischer    else
5259f91b7e3SAndre Fischer    {
5269f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: no files have been added\n");
5279f91b7e3SAndre Fischer        return 1;
5289f91b7e3SAndre Fischer    }
5299f91b7e3SAndre Fischer}
5309f91b7e3SAndre Fischer
5319f91b7e3SAndre Fischer
5329f91b7e3SAndre Fischer
5339f91b7e3SAndre Fischer
5349f91b7e3SAndre Fischer=head2 CheckComponentSets($source_msi, $target_msi)
5359f91b7e3SAndre Fischer
5369f91b7e3SAndre Fischer    Components must not be removed but can be added.
5379f91b7e3SAndre Fischer    Features of added components have also to be new.
5389f91b7e3SAndre Fischer
5399f91b7e3SAndre Fischer=cut
5409f91b7e3SAndre Fischersub CheckComponentSets($$)
5419f91b7e3SAndre Fischer{
5429f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
5439f91b7e3SAndre Fischer
5449f91b7e3SAndre Fischer    # Get the 'Component' tables.
5459f91b7e3SAndre Fischer    my $source_component_table = $source_msi->GetTable("Component");
5469f91b7e3SAndre Fischer    my $target_component_table = $target_msi->GetTable("Component");
5479f91b7e3SAndre Fischer
5489f91b7e3SAndre Fischer    # Create data structures for fast lookup.
5499f91b7e3SAndre Fischer    my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
5509f91b7e3SAndre Fischer    my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
5519f91b7e3SAndre Fischer
5529f91b7e3SAndre Fischer    # Check that no component has been removed.
5539f91b7e3SAndre Fischer    my @removed_components = ();
5549f91b7e3SAndre Fischer    foreach my $componentname (keys %source_component_map)
5559f91b7e3SAndre Fischer    {
5569f91b7e3SAndre Fischer        if ( ! defined $target_component_map{$componentname})
5579f91b7e3SAndre Fischer        {
5589f91b7e3SAndre Fischer            push @removed_components, $componentname;
5599f91b7e3SAndre Fischer        }
5609f91b7e3SAndre Fischer    }
5619f91b7e3SAndre Fischer    if (scalar @removed_components > 0)
5629f91b7e3SAndre Fischer    {
5639f91b7e3SAndre Fischer        # There are removed components.
5649f91b7e3SAndre Fischer
5659f91b7e3SAndre Fischer        # Check if any of them is not a registry component.
5669f91b7e3SAndre Fischer        my $is_file_component_removed = 0;
5679f91b7e3SAndre Fischer        foreach my $componentname (@removed_components)
5689f91b7e3SAndre Fischer        {
5699f91b7e3SAndre Fischer            if ($componentname !~ /^registry/)
5709f91b7e3SAndre Fischer            {
5719f91b7e3SAndre Fischer                $is_file_component_removed = 1;
5729f91b7e3SAndre Fischer            }
5739f91b7e3SAndre Fischer        }
5749f91b7e3SAndre Fischer        if ($is_file_component_removed)
5759f91b7e3SAndre Fischer        {
5769f91b7e3SAndre Fischer            $installer::logger::Info->printf(
5779f91b7e3SAndre Fischer                "Error: %d components have been removed, some of them are file components:\n",
5789f91b7e3SAndre Fischer                scalar @removed_components);
5799f91b7e3SAndre Fischer            $installer::logger::Info->printf("       %s\n", join(", ", @removed_components));
5809f91b7e3SAndre Fischer            return 0;
5819f91b7e3SAndre Fischer        }
5829f91b7e3SAndre Fischer        else
5839f91b7e3SAndre Fischer        {
5849f91b7e3SAndre Fischer            $installer::logger::Info->printf(
5859f91b7e3SAndre Fischer                "Error: %d components have been removed, all of them are registry components:\n",
5869f91b7e3SAndre Fischer                scalar @removed_components);
5879f91b7e3SAndre Fischer            return 0;
5889f91b7e3SAndre Fischer        }
5899f91b7e3SAndre Fischer    }
5909f91b7e3SAndre Fischer
5919f91b7e3SAndre Fischer    # Check that added components belong to new features.
5929f91b7e3SAndre Fischer    my @added_components = ();
5939f91b7e3SAndre Fischer    foreach my $componentname (keys %target_component_map)
5949f91b7e3SAndre Fischer    {
5959f91b7e3SAndre Fischer        if ( ! defined $source_component_map{$componentname})
5969f91b7e3SAndre Fischer        {
5979f91b7e3SAndre Fischer            push @added_components, $componentname;
5989f91b7e3SAndre Fischer        }
5999f91b7e3SAndre Fischer    }
6009f91b7e3SAndre Fischer
6019f91b7e3SAndre Fischer    if (scalar @added_components > 0)
6029f91b7e3SAndre Fischer    {
6039f91b7e3SAndre Fischer        # Check if any of them is not a registry component.
6049f91b7e3SAndre Fischer        my $is_file_component_removed = 0;
6059f91b7e3SAndre Fischer        foreach my $componentname (@removed_components)
6069f91b7e3SAndre Fischer        {
6079f91b7e3SAndre Fischer            if ($componentname !~ /^registry/)
6089f91b7e3SAndre Fischer            {
6099f91b7e3SAndre Fischer                $is_file_component_removed = 1;
6109f91b7e3SAndre Fischer            }
6119f91b7e3SAndre Fischer        }
6129f91b7e3SAndre Fischer
6139f91b7e3SAndre Fischer        if ($is_file_component_removed)
6149f91b7e3SAndre Fischer        {
6159f91b7e3SAndre Fischer            $installer::logger::Info->printf(
6169f91b7e3SAndre Fischer                "Warning: %d components have been addded\n",
6179f91b7e3SAndre Fischer                scalar @added_components);
6189f91b7e3SAndre Fischer            $installer::logger::Info->printf(
6199f91b7e3SAndre Fischer                "Test for new components belonging to new features has not yet been implemented\n");
6209f91b7e3SAndre Fischer            return 0;
6219f91b7e3SAndre Fischer        }
6229f91b7e3SAndre Fischer        else
6239f91b7e3SAndre Fischer        {
6249f91b7e3SAndre Fischer            $installer::logger::Info->printf(
6259f91b7e3SAndre Fischer                "Warning: %d components have been addded, all of them registry components\n",
6269f91b7e3SAndre Fischer                scalar @added_components);
6279f91b7e3SAndre Fischer        }
6289f91b7e3SAndre Fischer    }
6299f91b7e3SAndre Fischer
6309f91b7e3SAndre Fischer    $installer::logger::Info->printf("OK: component sets in source and target are compatible\n");
6319f91b7e3SAndre Fischer    return 1;
6329f91b7e3SAndre Fischer}
6339f91b7e3SAndre Fischer
6349f91b7e3SAndre Fischer
6359f91b7e3SAndre Fischer
6369f91b7e3SAndre Fischer
6379f91b7e3SAndre Fischer=head2 CheckComponent($source_msi, $target_msi)
6389f91b7e3SAndre Fischer
6399f91b7e3SAndre Fischer    In the 'Component' table the 'ComponentId' and 'Component' values
6409f91b7e3SAndre Fischer    for corresponding componts in the source and target release have
6419f91b7e3SAndre Fischer    to be identical.
6429f91b7e3SAndre Fischer
6439f91b7e3SAndre Fischer=cut
6449f91b7e3SAndre Fischersub CheckComponentValues($$$)
6459f91b7e3SAndre Fischer{
6469f91b7e3SAndre Fischer    my ($source_msi, $target_msi, $variables) = @_;
6479f91b7e3SAndre Fischer
6489f91b7e3SAndre Fischer    # Get the 'Component' tables.
6499f91b7e3SAndre Fischer    my $source_component_table = $source_msi->GetTable("Component");
6509f91b7e3SAndre Fischer    my $target_component_table = $target_msi->GetTable("Component");
6519f91b7e3SAndre Fischer
6529f91b7e3SAndre Fischer    # Create data structures for fast lookup.
6539f91b7e3SAndre Fischer    my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
6549f91b7e3SAndre Fischer    my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
6559f91b7e3SAndre Fischer
6569f91b7e3SAndre Fischer    my @differences = ();
6579f91b7e3SAndre Fischer    my $comparison_count = 0;
6589f91b7e3SAndre Fischer    while (my ($componentname, $source_component_row) = each %source_component_map)
6599f91b7e3SAndre Fischer    {
6609f91b7e3SAndre Fischer        my $target_component_row = $target_component_map{$componentname};
6619f91b7e3SAndre Fischer        if (defined $target_component_row)
6629f91b7e3SAndre Fischer        {
6639f91b7e3SAndre Fischer            ++$comparison_count;
6649f91b7e3SAndre Fischer            if ($source_component_row->GetValue("ComponentId") ne $target_component_row->GetValue("ComponentId"))
6659f91b7e3SAndre Fischer            {
6669f91b7e3SAndre Fischer                push @differences, [
6679f91b7e3SAndre Fischer                    $componentname,
6689f91b7e3SAndre Fischer                    $source_component_row->GetValue("ComponentId"),
6699f91b7e3SAndre Fischer                    $target_component_row->GetValue("ComponentId"),
6709f91b7e3SAndre Fischer                    $target_component_row->GetValue("Component"),
6719f91b7e3SAndre Fischer                ];
6729f91b7e3SAndre Fischer            }
6739f91b7e3SAndre Fischer        }
6749f91b7e3SAndre Fischer    }
6759f91b7e3SAndre Fischer
6769f91b7e3SAndre Fischer    if (scalar @differences > 0)
6779f91b7e3SAndre Fischer    {
6789f91b7e3SAndre Fischer        $installer::logger::Info->printf(
6799f91b7e3SAndre Fischer            "Error: there are %d components with different 'ComponentId' values after %d comparisons.\n",
6809f91b7e3SAndre Fischer            scalar @differences,
6819f91b7e3SAndre Fischer            $comparison_count);
6829f91b7e3SAndre Fischer        foreach my $item (@differences)
6839f91b7e3SAndre Fischer        {
6849f91b7e3SAndre Fischer            $installer::logger::Info->printf("%s  %s\n", $item->[1], $item->[2]);
6859f91b7e3SAndre Fischer        }
6869f91b7e3SAndre Fischer        return 0;
6879f91b7e3SAndre Fischer    }
6889f91b7e3SAndre Fischer    else
6899f91b7e3SAndre Fischer    {
6909f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: components in source and target are identical\n");
6919f91b7e3SAndre Fischer        return 1;
6929f91b7e3SAndre Fischer    }
6939f91b7e3SAndre Fischer}
6949f91b7e3SAndre Fischer
6959f91b7e3SAndre Fischer
6969f91b7e3SAndre Fischer
6979f91b7e3SAndre Fischer
6989f91b7e3SAndre Fischer=head2 CheckFileSequence($source_msi, $target_msi)
6999f91b7e3SAndre Fischer
7009f91b7e3SAndre Fischer    In the 'File' table the 'Sequence' numbers for corresponding files has to be identical.
7019f91b7e3SAndre Fischer
7029f91b7e3SAndre Fischer=cut
7039f91b7e3SAndre Fischersub CheckFileSequence($$)
7049f91b7e3SAndre Fischer{
7059f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
7069f91b7e3SAndre Fischer
7079f91b7e3SAndre Fischer    # Get the 'File' tables.
7089f91b7e3SAndre Fischer    my $source_file_table = $source_msi->GetTable("File");
7099f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
7109f91b7e3SAndre Fischer
7119f91b7e3SAndre Fischer    # Create temporary data structures for fast access.
7129f91b7e3SAndre Fischer    my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()};
7139f91b7e3SAndre Fischer    my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()};
7149f91b7e3SAndre Fischer
7159f91b7e3SAndre Fischer    # Search files with mismatching sequence numbers.
7169f91b7e3SAndre Fischer    my @mismatching_files = ();
7179f91b7e3SAndre Fischer    while (my ($uniquename,$source_file_row) = each %source_file_map)
7189f91b7e3SAndre Fischer    {
7199f91b7e3SAndre Fischer        my $target_file_row = $target_file_map{$uniquename};
7209f91b7e3SAndre Fischer        if (defined $target_file_row)
7219f91b7e3SAndre Fischer        {
7229f91b7e3SAndre Fischer            if ($source_file_row->GetValue('Sequence') ne $target_file_row->GetValue('Sequence'))
7239f91b7e3SAndre Fischer            {
7249f91b7e3SAndre Fischer                push @mismatching_files, [
7259f91b7e3SAndre Fischer                    $uniquename,
7269f91b7e3SAndre Fischer                    $source_file_row,
7279f91b7e3SAndre Fischer                    $target_file_row
7289f91b7e3SAndre Fischer                ];
7299f91b7e3SAndre Fischer            }
7309f91b7e3SAndre Fischer        }
7319f91b7e3SAndre Fischer    }
7329f91b7e3SAndre Fischer
7339f91b7e3SAndre Fischer    if (scalar @mismatching_files > 0)
7349f91b7e3SAndre Fischer    {
7359f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: there are %d files with mismatching 'Sequence' numbers\n",
7369f91b7e3SAndre Fischer            scalar @mismatching_files);
7379f91b7e3SAndre Fischer        foreach my $item (@mismatching_files)
7389f91b7e3SAndre Fischer        {
7399f91b7e3SAndre Fischer            $installer::logger::Info->printf("    %s: %d != %d\n",
7409f91b7e3SAndre Fischer                $item->[0],
7419f91b7e3SAndre Fischer                $item->[1]->GetValue("Sequence"),
7429f91b7e3SAndre Fischer                $item->[2]->GetValue("Sequence"));
7439f91b7e3SAndre Fischer        }
7449f91b7e3SAndre Fischer        return 0;
7459f91b7e3SAndre Fischer    }
7469f91b7e3SAndre Fischer    else
7479f91b7e3SAndre Fischer    {
7489f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: all files have matching 'Sequence' numbers\n");
7499f91b7e3SAndre Fischer        return 1;
7509f91b7e3SAndre Fischer    }
7519f91b7e3SAndre Fischer}
7529f91b7e3SAndre Fischer
7539f91b7e3SAndre Fischer
7549f91b7e3SAndre Fischer
7559f91b7e3SAndre Fischer
7569f91b7e3SAndre Fischer=head2 CheckFileSequenceUnique($source_msi, $target_msi)
7579f91b7e3SAndre Fischer
7589f91b7e3SAndre Fischer    In the 'File' table the 'Sequence' values have to be unique.
7599f91b7e3SAndre Fischer
7609f91b7e3SAndre Fischer=cut
7619f91b7e3SAndre Fischersub CheckFileSequenceUnique($$)
7629f91b7e3SAndre Fischer{
7639f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
7649f91b7e3SAndre Fischer
7659f91b7e3SAndre Fischer    # Get the 'File' tables.
7669f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
7679f91b7e3SAndre Fischer
7689f91b7e3SAndre Fischer    my %sequence_numbers = ();
7699f91b7e3SAndre Fischer    my $collision_count = 0;
7709f91b7e3SAndre Fischer    foreach my $row (@{$target_file_table->GetAllRows()})
7719f91b7e3SAndre Fischer    {
7729f91b7e3SAndre Fischer        my $sequence_number = $row->GetValue("Sequence");
7739f91b7e3SAndre Fischer        if (defined $sequence_numbers{$sequence_number})
7749f91b7e3SAndre Fischer        {
7759f91b7e3SAndre Fischer            ++$collision_count;
7769f91b7e3SAndre Fischer        }
7779f91b7e3SAndre Fischer        else
7789f91b7e3SAndre Fischer        {
7799f91b7e3SAndre Fischer            $sequence_numbers{$sequence_number} = 1;
7809f91b7e3SAndre Fischer        }
7819f91b7e3SAndre Fischer    }
7829f91b7e3SAndre Fischer
7839f91b7e3SAndre Fischer    if ($collision_count > 0)
7849f91b7e3SAndre Fischer    {
7859f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: there are %d collisions ofn the sequence numbers\n",
7869f91b7e3SAndre Fischer            $collision_count);
7879f91b7e3SAndre Fischer        return 0;
7889f91b7e3SAndre Fischer    }
7899f91b7e3SAndre Fischer    else
7909f91b7e3SAndre Fischer    {
7919f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: sequence numbers are unique\n");
7929f91b7e3SAndre Fischer        return 1;
7939f91b7e3SAndre Fischer    }
7949f91b7e3SAndre Fischer}
7959f91b7e3SAndre Fischer
7969f91b7e3SAndre Fischer
7979f91b7e3SAndre Fischer
7989f91b7e3SAndre Fischer
7999f91b7e3SAndre Fischer=head2 CheckFileSequenceHoles ($target_msi)
8009f91b7e3SAndre Fischer
8019f91b7e3SAndre Fischer    Check the sequence numbers of the target msi if the n files use numbers 1..n or if there are holes.
8029f91b7e3SAndre Fischer    Holes are reported as warnings.
8039f91b7e3SAndre Fischer
8049f91b7e3SAndre Fischer=cut
8059f91b7e3SAndre Fischersub CheckFileSequenceHoles ($$)
8069f91b7e3SAndre Fischer{
8079f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
8089f91b7e3SAndre Fischer
8099f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
8109f91b7e3SAndre Fischer    my %sequence_numbers = map {$_->GetValue("Sequence") => $_} @{$target_file_table->GetAllRows()};
8119f91b7e3SAndre Fischer    my @sorted_sequence_numbers = sort {$a <=> $b} keys %sequence_numbers;
8129f91b7e3SAndre Fischer    my $expected_next_sequence_number = 1;
8139f91b7e3SAndre Fischer    my @holes = ();
8149f91b7e3SAndre Fischer    foreach my $sequence_number (@sorted_sequence_numbers)
8159f91b7e3SAndre Fischer    {
8169f91b7e3SAndre Fischer        if ($sequence_number != $expected_next_sequence_number)
8179f91b7e3SAndre Fischer        {
8189f91b7e3SAndre Fischer            push @holes, [$expected_next_sequence_number, $sequence_number-1];
8199f91b7e3SAndre Fischer        }
8209f91b7e3SAndre Fischer        $expected_next_sequence_number = $sequence_number+1;
8219f91b7e3SAndre Fischer    }
8229f91b7e3SAndre Fischer    if (scalar @holes > 0)
8239f91b7e3SAndre Fischer    {
8249f91b7e3SAndre Fischer        $installer::logger::Info->printf("Warning: sequence numbers have %d holes\n");
8259f91b7e3SAndre Fischer        foreach my $hole (@holes)
8269f91b7e3SAndre Fischer        {
8279f91b7e3SAndre Fischer            if ($hole->[0] != $hole->[1])
8289f91b7e3SAndre Fischer            {
8299f91b7e3SAndre Fischer                $installer::logger::Info->printf("    %d\n", $hole->[0]);
8309f91b7e3SAndre Fischer            }
8319f91b7e3SAndre Fischer            else
8329f91b7e3SAndre Fischer            {
8339f91b7e3SAndre Fischer                $installer::logger::Info->printf("    %d -> %d\n", $hole->[0], $hole->[1]);
8349f91b7e3SAndre Fischer            }
8359f91b7e3SAndre Fischer        }
8369f91b7e3SAndre Fischer    }
8379f91b7e3SAndre Fischer    else
8389f91b7e3SAndre Fischer    {
8399f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: there are no holes in the sequence numbers\n");
8409f91b7e3SAndre Fischer    }
8419f91b7e3SAndre Fischer    return 1;
8429f91b7e3SAndre Fischer}
8439f91b7e3SAndre Fischer
8449f91b7e3SAndre Fischer
8459f91b7e3SAndre Fischer
8469f91b7e3SAndre Fischer
8479f91b7e3SAndre Fischer=head2 CheckRegistryItems($source_msi, $target_msi)
8489f91b7e3SAndre Fischer
8499f91b7e3SAndre Fischer    In the 'Registry' table the 'Component_' and 'Key' values must not
8509f91b7e3SAndre Fischer    depend on the version number (beyond the unchanging major
8519f91b7e3SAndre Fischer    version).
8529f91b7e3SAndre Fischer
8539f91b7e3SAndre Fischer    'Value' values must only depend on the major version number to
8549f91b7e3SAndre Fischer    avoid duplicate entries in the start menu.
8559f91b7e3SAndre Fischer
8569f91b7e3SAndre Fischer    Violations are reported as warnings for now.
8579f91b7e3SAndre Fischer
8589f91b7e3SAndre Fischer=cut
8599f91b7e3SAndre Fischersub CheckRegistryItems($$$)
8609f91b7e3SAndre Fischer{
8619f91b7e3SAndre Fischer    my ($source_msi, $target_msi, $product_name) = @_;
8629f91b7e3SAndre Fischer
8639f91b7e3SAndre Fischer    # Get the registry tables.
8649f91b7e3SAndre Fischer    my $source_registry_table = $source_msi->GetTable("Registry");
8659f91b7e3SAndre Fischer    my $target_registry_table = $target_msi->GetTable("Registry");
8669f91b7e3SAndre Fischer
8679f91b7e3SAndre Fischer    my $registry_index = $target_registry_table->GetColumnIndex("Registry");
8689f91b7e3SAndre Fischer    my $component_index = $target_registry_table->GetColumnIndex("Component_");
8699f91b7e3SAndre Fischer
8709f91b7e3SAndre Fischer    # Create temporary data structures for fast access.
8719f91b7e3SAndre Fischer    my %source_registry_map = map {$_->GetValue($registry_index) => $_} @{$source_registry_table->GetAllRows()};
8729f91b7e3SAndre Fischer    my %target_registry_map = map {$_->GetValue($registry_index) => $_} @{$target_registry_table->GetAllRows()};
8739f91b7e3SAndre Fischer
8749f91b7e3SAndre Fischer    # Prepare version numbers to search.
8759f91b7e3SAndre Fischer    my $source_version_number = $source_msi->{'version'};
8769f91b7e3SAndre Fischer    my $source_version_nodots = installer::patch::Version::ArrayToNoDotName(
8779f91b7e3SAndre Fischer        installer::patch::Version::StringToNumberArray($source_version_number));
8789f91b7e3SAndre Fischer    my $source_component_pattern = lc($product_name).$source_version_nodots;
8799f91b7e3SAndre Fischer    my $target_version_number = $target_msi->{'version'};
8809f91b7e3SAndre Fischer    my $target_version_nodots = installer::patch::Version::ArrayToNoDotName(
8819f91b7e3SAndre Fischer        installer::patch::Version::StringToNumberArray($target_version_number));
8829f91b7e3SAndre Fischer    my $target_component_pattern = lc($product_name).$target_version_nodots;
8839f91b7e3SAndre Fischer
8849f91b7e3SAndre Fischer    foreach my $source_row (values %source_registry_map)
8859f91b7e3SAndre Fischer    {
8869f91b7e3SAndre Fischer        my $target_row = $target_registry_map{$source_row->GetValue($registry_index)};
8879f91b7e3SAndre Fischer        if ( ! defined $target_row)
8889f91b7e3SAndre Fischer        {
8899f91b7e3SAndre Fischer            $installer::logger::Info->printf("Error: sets of registry entries differs\n");
8909f91b7e3SAndre Fischer            return 1;
8919f91b7e3SAndre Fischer        }
8929f91b7e3SAndre Fischer
8939f91b7e3SAndre Fischer        my $source_component_name = $source_row->GetValue($component_index);
8949f91b7e3SAndre Fischer        my $target_component_name = $source_row->GetValue($component_index);
8959f91b7e3SAndre Fischer
8969f91b7e3SAndre Fischer    }
8979f91b7e3SAndre Fischer
8989f91b7e3SAndre Fischer    $installer::logger::Info->printf("OK: registry items are OK\n");
8999f91b7e3SAndre Fischer    return 1;
9009f91b7e3SAndre Fischer}
9019f91b7e3SAndre Fischer
9029f91b7e3SAndre Fischer
9039f91b7e3SAndre Fischer
9049f91b7e3SAndre Fischer
9059f91b7e3SAndre Fischer=head2
9069f91b7e3SAndre Fischer
9079f91b7e3SAndre Fischer    Component->KeyPath must not change. (see component.pm/get_component_keypath)
9089f91b7e3SAndre Fischer
9099f91b7e3SAndre Fischer=cut
9109f91b7e3SAndre Fischersub CheckComponentKeyPath ($$)
9119f91b7e3SAndre Fischer{
9129f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
9139f91b7e3SAndre Fischer
9149f91b7e3SAndre Fischer    # Get the registry tables.
9159f91b7e3SAndre Fischer    my $source_component_table = $source_msi->GetTable("Component");
9169f91b7e3SAndre Fischer    my $target_component_table = $target_msi->GetTable("Component");
9179f91b7e3SAndre Fischer
9189f91b7e3SAndre Fischer    # Create temporary data structures for fast access.
9199f91b7e3SAndre Fischer    my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
9209f91b7e3SAndre Fischer    my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
9219f91b7e3SAndre Fischer
9229f91b7e3SAndre Fischer    my @mismatches = ();
9239f91b7e3SAndre Fischer    while (my ($componentname, $source_component_row) = each %source_component_map)
9249f91b7e3SAndre Fischer    {
9259f91b7e3SAndre Fischer        my $target_component_row = $target_component_map{$componentname};
9269f91b7e3SAndre Fischer        if (defined $target_component_row)
9279f91b7e3SAndre Fischer        {
9289f91b7e3SAndre Fischer            my $source_keypath = $source_component_row->GetValue("KeyPath");
9299f91b7e3SAndre Fischer            my $target_keypath = $target_component_row->GetValue("KeyPath");
9309f91b7e3SAndre Fischer            if ($source_keypath ne $target_keypath)
9319f91b7e3SAndre Fischer            {
9329f91b7e3SAndre Fischer                push @mismatches, [$componentname, $source_keypath, $target_keypath];
9339f91b7e3SAndre Fischer            }
9349f91b7e3SAndre Fischer        }
9359f91b7e3SAndre Fischer    }
9369f91b7e3SAndre Fischer
9379f91b7e3SAndre Fischer    if (scalar @mismatches > 0)
9389f91b7e3SAndre Fischer    {
9399f91b7e3SAndre Fischer        $installer::logger::Info->printf(
9409f91b7e3SAndre Fischer            "Error: there are %d mismatches in the 'KeyPath' column of the 'Component' table\n",
9419f91b7e3SAndre Fischer            scalar @mismatches);
9429f91b7e3SAndre Fischer
9439f91b7e3SAndre Fischer        foreach my $item (@mismatches)
9449f91b7e3SAndre Fischer        {
9459f91b7e3SAndre Fischer            $installer::logger::Info->printf(
9469f91b7e3SAndre Fischer                "    %s: %s != %s\n",
9479f91b7e3SAndre Fischer                $item->[0],
9489f91b7e3SAndre Fischer                $item->[1],
9499f91b7e3SAndre Fischer                $item->[2]);
9509f91b7e3SAndre Fischer        }
9519f91b7e3SAndre Fischer
9529f91b7e3SAndre Fischer        return 0;
9539f91b7e3SAndre Fischer    }
9549f91b7e3SAndre Fischer    else
9559f91b7e3SAndre Fischer    {
9569f91b7e3SAndre Fischer        $installer::logger::Info->printf(
9579f91b7e3SAndre Fischer            "OK: no mismatches in the 'KeyPath' column of the 'Component' table\n");
9589f91b7e3SAndre Fischer        return 1;
9599f91b7e3SAndre Fischer    }
9609f91b7e3SAndre Fischer}
9619f91b7e3SAndre Fischer
9629f91b7e3SAndre Fischer
9639f91b7e3SAndre Fischer
9649f91b7e3SAndre Fischer
9659f91b7e3SAndre Fischersub Check ($$$$)
9669f91b7e3SAndre Fischer{
9679f91b7e3SAndre Fischer    my ($source_msi, $target_msi, $variables, $product_name) = @_;
9689f91b7e3SAndre Fischer
9699f91b7e3SAndre Fischer    $installer::logger::Info->printf("checking if source and target releases are compatable\n");
9709f91b7e3SAndre Fischer    $installer::logger::Info->increase_indentation();
9719f91b7e3SAndre Fischer
9729f91b7e3SAndre Fischer    my $result = 1;
9739f91b7e3SAndre Fischer
9749f91b7e3SAndre Fischer    $result &&= CheckUpgradeCode($source_msi, $target_msi);
9759f91b7e3SAndre Fischer    $result &&= CheckProductCode($source_msi, $target_msi);
9769f91b7e3SAndre Fischer    $result &&= CheckBuildIdCode($source_msi, $target_msi);
9779f91b7e3SAndre Fischer    $result &&= CheckProductName($source_msi, $target_msi);
9789f91b7e3SAndre Fischer    $result &&= CheckRemovedFiles($source_msi, $target_msi);
9799f91b7e3SAndre Fischer    $result &&= CheckNewFiles($source_msi, $target_msi);
9809f91b7e3SAndre Fischer    $result &&= CheckComponentSets($source_msi, $target_msi);
9819f91b7e3SAndre Fischer    $result &&= CheckComponentValues($source_msi, $target_msi, $variables);
9829f91b7e3SAndre Fischer    $result &&= CheckFileSequence($source_msi, $target_msi);
9839f91b7e3SAndre Fischer    $result &&= CheckFileSequenceUnique($source_msi, $target_msi);
9849f91b7e3SAndre Fischer    $result &&= CheckFileSequenceHoles($source_msi, $target_msi);
9859f91b7e3SAndre Fischer    $result &&= CheckRegistryItems($source_msi, $target_msi, $product_name);
9869f91b7e3SAndre Fischer    $result &&= CheckComponentKeyPath($source_msi, $target_msi);
9879f91b7e3SAndre Fischer
9889f91b7e3SAndre Fischer    $installer::logger::Info->decrease_indentation();
9899f91b7e3SAndre Fischer
9909f91b7e3SAndre Fischer    return $result;
9919f91b7e3SAndre Fischer}
9929f91b7e3SAndre Fischer
9939f91b7e3SAndre Fischer
9949f91b7e3SAndre Fischer
9959f91b7e3SAndre Fischer
9969f91b7e3SAndre Fischer=head2 FindPcpTemplate ()
9979f91b7e3SAndre Fischer
9989f91b7e3SAndre Fischer    The template.pcp file is part of the Windows SDK.
9999f91b7e3SAndre Fischer
10009f91b7e3SAndre Fischer=cut
10019f91b7e3SAndre Fischersub FindPcpTemplate ()
10029f91b7e3SAndre Fischer{
10039f91b7e3SAndre Fischer    my $psdk_home = $ENV{'PSDK_HOME'};
10049f91b7e3SAndre Fischer    if ( ! defined $psdk_home)
10059f91b7e3SAndre Fischer    {
10069f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: the PSDK_HOME environment variable is not set.\n");
10079f91b7e3SAndre Fischer        $installer::logger::Info->printf("       did you load the AOO build environment?\n");
10089f91b7e3SAndre Fischer        $installer::logger::Info->printf("       you may want to use the --with-psdk-home configure option\n");
10099f91b7e3SAndre Fischer        return undef;
10109f91b7e3SAndre Fischer    }
10119f91b7e3SAndre Fischer    if ( ! -d $psdk_home)
10129f91b7e3SAndre Fischer    {
10139f91b7e3SAndre Fischer        $installer::logger::Info->printf(
10149f91b7e3SAndre Fischer            "Error: the PSDK_HOME environment variable does not point to a valid directory: %s\n",
10159f91b7e3SAndre Fischer            $psdk_home);
10169f91b7e3SAndre Fischer        return undef;
10179f91b7e3SAndre Fischer    }
10189f91b7e3SAndre Fischer
10199f91b7e3SAndre Fischer    my $schema_path = File::Spec->catfile($psdk_home, "Bin", "msitools", "Schemas", "MSI");
10209f91b7e3SAndre Fischer    if (  ! -d $schema_path)
10219f91b7e3SAndre Fischer    {
10229f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: Can not locate the msi template folder in the Windows SDK\n");
10239f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $schema_path);
10249f91b7e3SAndre Fischer        $installer::logger::Info->printf("       Is the Windows SDK properly installed?\n");
10259f91b7e3SAndre Fischer        return undef;
10269f91b7e3SAndre Fischer    }
10279f91b7e3SAndre Fischer
10289f91b7e3SAndre Fischer    my $schema_filename = File::Spec->catfile($schema_path, "template.pcp");
10299f91b7e3SAndre Fischer    if (  ! -f $schema_filename)
10309f91b7e3SAndre Fischer    {
10319f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: Can not locate the pcp template at\n");
10329f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $schema_filename);
10339f91b7e3SAndre Fischer        $installer::logger::Info->printf("       Is the Windows SDK properly installed?\n");
10349f91b7e3SAndre Fischer        return undef;
10359f91b7e3SAndre Fischer    }
10369f91b7e3SAndre Fischer
10379f91b7e3SAndre Fischer    return $schema_filename;
10389f91b7e3SAndre Fischer}
10399f91b7e3SAndre Fischer
10409f91b7e3SAndre Fischer
10419f91b7e3SAndre Fischer
10429f91b7e3SAndre Fischer
10439f91b7e3SAndre Fischersub SetupPcpPatchMetadataTable ($$$)
10449f91b7e3SAndre Fischer{
10459f91b7e3SAndre Fischer    my ($pcp, $source_msi, $target_msi) = @_;
10469f91b7e3SAndre Fischer
10479f91b7e3SAndre Fischer    # Determine values for eg product name and source and new version.
10489f91b7e3SAndre Fischer    my $source_version = $source_msi->{'version'};
10499f91b7e3SAndre Fischer    my $target_version = $target_msi->{'version'};
10509f91b7e3SAndre Fischer
10519f91b7e3SAndre Fischer    my $property_table = $target_msi->GetTable("Property");
10529f91b7e3SAndre Fischer    my $display_product_name = $property_table->GetValue("Property", "DEFINEDPRODUCT", "Value");
10539f91b7e3SAndre Fischer
10549f91b7e3SAndre Fischer    # Set table.
10559f91b7e3SAndre Fischer    my $table = $pcp->GetTable("PatchMetadata");
10569f91b7e3SAndre Fischer    $table->SetRow(
10579f91b7e3SAndre Fischer        "Company", "",
10589f91b7e3SAndre Fischer        "*Property", "Description",
10599f91b7e3SAndre Fischer        "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version)
10609f91b7e3SAndre Fischer        );
10619f91b7e3SAndre Fischer    $table->SetRow(
10629f91b7e3SAndre Fischer        "Company", "",
10639f91b7e3SAndre Fischer        "*Property", "DisplayName",
10649f91b7e3SAndre Fischer        "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version)
10659f91b7e3SAndre Fischer        );
10669f91b7e3SAndre Fischer    $table->SetRow(
10679f91b7e3SAndre Fischer        "Company", "",
10689f91b7e3SAndre Fischer        "*Property", "ManufacturerName",
10699f91b7e3SAndre Fischer        "Value", $property_table->GetValue("Property", "Manufacturer", "Value"),
10709f91b7e3SAndre Fischer        );
10719f91b7e3SAndre Fischer    $table->SetRow(
10729f91b7e3SAndre Fischer        "Company", "",
10739f91b7e3SAndre Fischer        "*Property", "MoreInfoURL",
10749f91b7e3SAndre Fischer        "Value", $property_table->GetValue("Property", "ARPURLINFOABOUT", "Value")
10759f91b7e3SAndre Fischer        );
10769f91b7e3SAndre Fischer    $table->SetRow(
10779f91b7e3SAndre Fischer        "Company", "",
10789f91b7e3SAndre Fischer        "*Property", "TargetProductName",
10799f91b7e3SAndre Fischer        "Value", $property_table->GetValue("Property", "ProductName", "Value")
10809f91b7e3SAndre Fischer        );
10819f91b7e3SAndre Fischer    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
10829f91b7e3SAndre Fischer
10839f91b7e3SAndre Fischer    $table->SetRow(
10849f91b7e3SAndre Fischer        "Company", "",
10859f91b7e3SAndre Fischer        "*Property", "CreationTimeUTC",
10869f91b7e3SAndre Fischer        "Value", sprintf("%d/%d/%d %d:%02d", $mon+1,$mday,$year+1900,$hour,$min)
10879f91b7e3SAndre Fischer        );
10889f91b7e3SAndre Fischer}
10899f91b7e3SAndre Fischer
10909f91b7e3SAndre Fischer
10919f91b7e3SAndre Fischer
10929f91b7e3SAndre Fischer
10939f91b7e3SAndre Fischersub SetupPropertiesTable ($$)
10949f91b7e3SAndre Fischer{
10959f91b7e3SAndre Fischer    my ($pcp, $msp_filename) = @_;
10969f91b7e3SAndre Fischer
10979f91b7e3SAndre Fischer    my $table = $pcp->GetTable("Properties");
10989f91b7e3SAndre Fischer
10999f91b7e3SAndre Fischer    $table->SetRow(
11009f91b7e3SAndre Fischer        "*Name", "PatchOutputPath",
11019f91b7e3SAndre Fischer        "Value", installer::patch::Tools::ToWindowsPath($msp_filename)
11029f91b7e3SAndre Fischer        );
11039f91b7e3SAndre Fischer    # Request at least Windows installer 2.0.
11049f91b7e3SAndre Fischer    # Version 2.0 allows us to omit some values from ImageFamilies table.
11059f91b7e3SAndre Fischer    $table->SetRow(
11069f91b7e3SAndre Fischer        "*Name", "MinimumRequiredMsiVersion",
11079f91b7e3SAndre Fischer        "Value", 200
11089f91b7e3SAndre Fischer        );
11099f91b7e3SAndre Fischer    # Allow diffs for binary files.
11109f91b7e3SAndre Fischer    $table->SetRow(
11119f91b7e3SAndre Fischer        "*Name", "IncludeWholeFilesOnly",
11129f91b7e3SAndre Fischer        "Value", 0
11139f91b7e3SAndre Fischer        );
11149f91b7e3SAndre Fischer
11159f91b7e3SAndre Fischer    my $uuid = installer::windows::msiglobal::create_guid();
11169f91b7e3SAndre Fischer    my $uuid_string = "{" . $uuid . "}";
11179f91b7e3SAndre Fischer    $table->SetRow(
11189f91b7e3SAndre Fischer        "*Name", "PatchGUID",
11199f91b7e3SAndre Fischer        "Value", $uuid_string
11209f91b7e3SAndre Fischer        );
11219f91b7e3SAndre Fischer    $installer::logger::Info->printf("created new PatchGUID %s\n", $uuid_string);
11229f91b7e3SAndre Fischer
11239f91b7e3SAndre Fischer    # Prevent sequence table from being generated.
11249f91b7e3SAndre Fischer    $table->SetRow(
11259f91b7e3SAndre Fischer        "*Name", "SEQUENCE_DATA_GENERATION_DISABLED",
11269f91b7e3SAndre Fischer        "Value", 1);
1127*d575d58fSAndre Fischer
1128*d575d58fSAndre Fischer    # We don't provide file size and hash values.
1129*d575d58fSAndre Fischer    # This value is set to make this fact explicit (0 should be the default).
1130*d575d58fSAndre Fischer    $table->SetRow(
1131*d575d58fSAndre Fischer        "*Name", "TrustMsi",
1132*d575d58fSAndre Fischer        "Value", 0);
11339f91b7e3SAndre Fischer}
11349f91b7e3SAndre Fischer
11359f91b7e3SAndre Fischer
11369f91b7e3SAndre Fischer
11379f91b7e3SAndre Fischer
11389f91b7e3SAndre Fischersub SetupImageFamiliesTable ($)
11399f91b7e3SAndre Fischer{
11409f91b7e3SAndre Fischer    my ($pcp) = @_;
11419f91b7e3SAndre Fischer
11429f91b7e3SAndre Fischer    $pcp->GetTable("ImageFamilies")->SetRow(
11439f91b7e3SAndre Fischer        "Family", $ImageFamily,
11449f91b7e3SAndre Fischer        "MediaSrcPropName", "",#"MNPSrcPropName",
11459f91b7e3SAndre Fischer        "MediaDiskId", "",
11469f91b7e3SAndre Fischer        "FileSequenceStart", "",
11479f91b7e3SAndre Fischer        "DiskPrompt", "",
11489f91b7e3SAndre Fischer        "VolumeLabel", "");
11499f91b7e3SAndre Fischer}
11509f91b7e3SAndre Fischer
11519f91b7e3SAndre Fischer
11529f91b7e3SAndre Fischer
11539f91b7e3SAndre Fischer
11549f91b7e3SAndre Fischersub SetupUpgradedImagesTable ($$)
11559f91b7e3SAndre Fischer{
11569f91b7e3SAndre Fischer    my ($pcp, $target_msi_path) = @_;
11579f91b7e3SAndre Fischer
11589f91b7e3SAndre Fischer    my $msi_path = installer::patch::Tools::ToWindowsPath($target_msi_path);
11599f91b7e3SAndre Fischer    $pcp->GetTable("UpgradedImages")->SetRow(
11609f91b7e3SAndre Fischer        "Upgraded", $TargetImageName,
11619f91b7e3SAndre Fischer        "MsiPath", $msi_path,
11629f91b7e3SAndre Fischer        "PatchMsiPath", "",
11639f91b7e3SAndre Fischer        "SymbolPaths", "",
11649f91b7e3SAndre Fischer        "Family", $ImageFamily);
11659f91b7e3SAndre Fischer}
11669f91b7e3SAndre Fischer
11679f91b7e3SAndre Fischer
11689f91b7e3SAndre Fischer
11699f91b7e3SAndre Fischer
11709f91b7e3SAndre Fischersub SetupTargetImagesTable ($$)
11719f91b7e3SAndre Fischer{
11729f91b7e3SAndre Fischer    my ($pcp, $source_msi_path) = @_;
11739f91b7e3SAndre Fischer
11749f91b7e3SAndre Fischer    $pcp->GetTable("TargetImages")->SetRow(
11759f91b7e3SAndre Fischer        "Target", $SourceImageName,
11769f91b7e3SAndre Fischer        "MsiPath", installer::patch::Tools::ToWindowsPath($source_msi_path),
11779f91b7e3SAndre Fischer        "SymbolPaths", "",
11789f91b7e3SAndre Fischer        "Upgraded", $TargetImageName,
11799f91b7e3SAndre Fischer        "Order", 1,
11809f91b7e3SAndre Fischer        "ProductValidateFlags", "",
11819f91b7e3SAndre Fischer        "IgnoreMissingSrcFiles", 0);
11829f91b7e3SAndre Fischer}
11839f91b7e3SAndre Fischer
11849f91b7e3SAndre Fischer
11859f91b7e3SAndre Fischer
11869f91b7e3SAndre Fischer
11879f91b7e3SAndre Fischersub SetAdditionalValues ($%)
11889f91b7e3SAndre Fischer{
11899f91b7e3SAndre Fischer    my ($pcp, %data) = @_;
11909f91b7e3SAndre Fischer
11919f91b7e3SAndre Fischer    while (my ($key,$value) = each(%data))
11929f91b7e3SAndre Fischer    {
11939f91b7e3SAndre Fischer        $key =~ /^([^\/]+)\/([^:]+):(.+)$/
11949f91b7e3SAndre Fischer            || die("invalid key format");
11959f91b7e3SAndre Fischer        my ($table_name, $key_column,$key_value) = ($1,$2,$3);
11969f91b7e3SAndre Fischer        $value =~ /^([^:]+):(.*)$/
11979f91b7e3SAndre Fischer            || die("invalid value format");
11989f91b7e3SAndre Fischer        my ($value_column,$value_value) = ($1,$2);
11999f91b7e3SAndre Fischer
12009f91b7e3SAndre Fischer        my $table = $pcp->GetTable($table_name);
12019f91b7e3SAndre Fischer        $table->SetRow(
12029f91b7e3SAndre Fischer                "*".$key_column, $key_value,
12039f91b7e3SAndre Fischer                $value_column, $value_value);
12049f91b7e3SAndre Fischer    }
12059f91b7e3SAndre Fischer}
12069f91b7e3SAndre Fischer
12079f91b7e3SAndre Fischer
12089f91b7e3SAndre Fischer
12099f91b7e3SAndre Fischer
12109f91b7e3SAndre Fischersub CreatePcp ($$$$$$%)
12119f91b7e3SAndre Fischer{
12129f91b7e3SAndre Fischer    my ($source_msi,
12139f91b7e3SAndre Fischer        $target_msi,
12149f91b7e3SAndre Fischer        $language,
12159f91b7e3SAndre Fischer        $context,
12169f91b7e3SAndre Fischer        $msp_path,
12179f91b7e3SAndre Fischer        $pcp_schema_filename,
12189f91b7e3SAndre Fischer        %additional_values) = @_;
12199f91b7e3SAndre Fischer
12209f91b7e3SAndre Fischer    # Create filenames.
12219f91b7e3SAndre Fischer    my $pcp_filename = File::Spec->catfile($msp_path, "openoffice.pcp");
12229f91b7e3SAndre Fischer    my $msp_filename = File::Spec->catfile($msp_path, "openoffice.msp");
12239f91b7e3SAndre Fischer
12249f91b7e3SAndre Fischer    # Setup msp path and filename.
12259f91b7e3SAndre Fischer    unlink($pcp_filename) if -f $pcp_filename;
12269f91b7e3SAndre Fischer    if ( ! File::Copy::copy($pcp_schema_filename, $pcp_filename))
12279f91b7e3SAndre Fischer    {
12289f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: could not create openoffice.pcp as copy of pcp schema\n");
12299f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $pcp_schema_filename);
12309f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $pcp_filename);
12319f91b7e3SAndre Fischer        return undef;
12329f91b7e3SAndre Fischer    }
12339f91b7e3SAndre Fischer    my $pcp = installer::patch::Msi->new(
12349f91b7e3SAndre Fischer        $pcp_filename,
12359f91b7e3SAndre Fischer        undef,
12369f91b7e3SAndre Fischer        undef,
12379f91b7e3SAndre Fischer        $language,
12389f91b7e3SAndre Fischer        $context->{'product-name'});
12399f91b7e3SAndre Fischer
12409f91b7e3SAndre Fischer    # Store some values in the pcp for easy reference in the msp creation.
12419f91b7e3SAndre Fischer    $pcp->{'msp_filename'} = $msp_filename;
12429f91b7e3SAndre Fischer
12439f91b7e3SAndre Fischer    SetupPcpPatchMetadataTable($pcp, $source_msi, $target_msi);
12449f91b7e3SAndre Fischer    SetupPropertiesTable($pcp, $msp_filename);
12459f91b7e3SAndre Fischer    SetupImageFamiliesTable($pcp);
12469f91b7e3SAndre Fischer    SetupUpgradedImagesTable($pcp, $target_msi->{'filename'});
12479f91b7e3SAndre Fischer    SetupTargetImagesTable($pcp, $source_msi->{'filename'});
12489f91b7e3SAndre Fischer
12499f91b7e3SAndre Fischer    SetAdditionalValues(%additional_values);
12509f91b7e3SAndre Fischer
12519f91b7e3SAndre Fischer    $pcp->Commit();
12529f91b7e3SAndre Fischer
12539f91b7e3SAndre Fischer    # Remove the PatchSequence table to avoid MsiMsp error message:
12549f91b7e3SAndre Fischer    # "Since MSI 3.0 will block installation of major upgrade patches with
12559f91b7e3SAndre Fischer    #  sequencing information, creation of such patches is blocked."
12569f91b7e3SAndre Fischer    #$pcp->RemoveTable("PatchSequence");
12579f91b7e3SAndre Fischer    # TODO: alternatively add property SEQUENCE_DATA_GENERATION_DISABLED to pcp Properties table.
12589f91b7e3SAndre Fischer
12599f91b7e3SAndre Fischer
12609f91b7e3SAndre Fischer    $installer::logger::Info->printf("created pcp file at\n");
12619f91b7e3SAndre Fischer    $installer::logger::Info->printf("    %s\n", $pcp->{'filename'});
12629f91b7e3SAndre Fischer
12639f91b7e3SAndre Fischer    return $pcp;
12649f91b7e3SAndre Fischer}
12659f91b7e3SAndre Fischer
12669f91b7e3SAndre Fischer
12679f91b7e3SAndre Fischer
12689f91b7e3SAndre Fischer
12699f91b7e3SAndre Fischersub ShowLog ($$$$)
12709f91b7e3SAndre Fischer{
12719f91b7e3SAndre Fischer    my ($log_path, $log_filename, $log_basename, $new_title) = @_;
12729f91b7e3SAndre Fischer
12739f91b7e3SAndre Fischer    if ( -f $log_filename)
12749f91b7e3SAndre Fischer    {
12759f91b7e3SAndre Fischer        my $destination_path = File::Spec->catfile($log_path, $log_basename);
12769f91b7e3SAndre Fischer        File::Path::make_path($destination_path) if ! -d $destination_path;
12779f91b7e3SAndre Fischer        my $command = join(" ",
12789f91b7e3SAndre Fischer            "wilogutl.exe",
12799f91b7e3SAndre Fischer            "/q",
12809f91b7e3SAndre Fischer            "/l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
12819f91b7e3SAndre Fischer            "/o", "'".installer::patch::Tools::ToWindowsPath($destination_path)."'");
12829f91b7e3SAndre Fischer        printf("running command $command\n");
12839f91b7e3SAndre Fischer        my $response = qx($command);
12849f91b7e3SAndre Fischer        printf("response is '%s'\n", $response);
12859f91b7e3SAndre Fischer        my @candidates = glob($destination_path . "/Details*");
12869f91b7e3SAndre Fischer        foreach my $candidate (@candidates)
12879f91b7e3SAndre Fischer        {
12889f91b7e3SAndre Fischer            next unless -f $candidate;
12899f91b7e3SAndre Fischer            my $new_name = $candidate;
12909f91b7e3SAndre Fischer            $new_name =~ s/Details.*$/$log_basename.html/;
12919f91b7e3SAndre Fischer
12929f91b7e3SAndre Fischer            # Rename the top-level html file and replace the title.
12939f91b7e3SAndre Fischer            open my $in, "<", $candidate;
12949f91b7e3SAndre Fischer            open my $out, ">", $new_name;
12959f91b7e3SAndre Fischer            while (<$in>)
12969f91b7e3SAndre Fischer            {
12979f91b7e3SAndre Fischer                if (/^(.*\<title\>)([^<]+)(.*)$/)
12989f91b7e3SAndre Fischer                {
12999f91b7e3SAndre Fischer                    print $out $1.$new_title.$3;
13009f91b7e3SAndre Fischer                }
13019f91b7e3SAndre Fischer                else
13029f91b7e3SAndre Fischer                {
13039f91b7e3SAndre Fischer                    print $out $_;
13049f91b7e3SAndre Fischer                }
13059f91b7e3SAndre Fischer            }
13069f91b7e3SAndre Fischer            close $in;
13079f91b7e3SAndre Fischer            close $out;
13089f91b7e3SAndre Fischer
13099f91b7e3SAndre Fischer            my $URL = $new_name;
13109f91b7e3SAndre Fischer            $URL =~ s/\/c\//c|\//;
13119f91b7e3SAndre Fischer            $URL =~ s/^(.):/$1|/;
13129f91b7e3SAndre Fischer            $URL = "file:///". $URL;
13139f91b7e3SAndre Fischer            $installer::logger::Info->printf("open %s in your browser to see the log messages\n", $URL);
13149f91b7e3SAndre Fischer        }
13159f91b7e3SAndre Fischer    }
13169f91b7e3SAndre Fischer    else
13179f91b7e3SAndre Fischer    {
13189f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: log file not found at %s\n", $log_filename);
13199f91b7e3SAndre Fischer    }
13209f91b7e3SAndre Fischer}
13219f91b7e3SAndre Fischer
13229f91b7e3SAndre Fischer
13239f91b7e3SAndre Fischer
13249f91b7e3SAndre Fischer
13259f91b7e3SAndre Fischersub CreateMsp ($)
13269f91b7e3SAndre Fischer{
13279f91b7e3SAndre Fischer    my ($pcp) = @_;
13289f91b7e3SAndre Fischer
13299f91b7e3SAndre Fischer    # Prepare log files.
13309f91b7e3SAndre Fischer    my $log_path = File::Spec->catfile($pcp->{'path'}, "log");
13319f91b7e3SAndre Fischer    my $log_basename = "msp";
13329f91b7e3SAndre Fischer    my $log_filename = File::Spec->catfile($log_path, $log_basename.".log");
13339f91b7e3SAndre Fischer    my $performance_log_basename = "performance";
13349f91b7e3SAndre Fischer    my $performance_log_filename = File::Spec->catfile($log_path, $performance_log_basename.".log");
13359f91b7e3SAndre Fischer    File::Path::make_path($log_path) if ! -d $log_path;
13369f91b7e3SAndre Fischer    unlink($log_filename) if -f $log_filename;
13379f91b7e3SAndre Fischer    unlink($performance_log_filename) if -f $performance_log_filename;
13389f91b7e3SAndre Fischer
13399f91b7e3SAndre Fischer    # Create the .msp patch file.
13409f91b7e3SAndre Fischer    my $temporary_msimsp_path = File::Spec->catfile($pcp->{'path'}, "tmp");
13419f91b7e3SAndre Fischer    if ( ! -d $temporary_msimsp_path)
13429f91b7e3SAndre Fischer    {
13439f91b7e3SAndre Fischer        File::Path::make_path($temporary_msimsp_path)
13449f91b7e3SAndre Fischer            || die ("can not create temporary path ".$temporary_msimsp_path);
13459f91b7e3SAndre Fischer    }
13469f91b7e3SAndre Fischer    $installer::logger::Info->printf("running msimsp.exe, that will take a while\n");
13479f91b7e3SAndre Fischer    my $command = join(" ",
13489f91b7e3SAndre Fischer        "msimsp.exe",
13499f91b7e3SAndre Fischer        "-s", "'".installer::patch::Tools::ToWindowsPath($pcp->{'filename'})."'",
13509f91b7e3SAndre Fischer        "-p", "'".installer::patch::Tools::ToWindowsPath($pcp->{'msp_filename'})."'",
13519f91b7e3SAndre Fischer        "-l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
13529f91b7e3SAndre Fischer        "-f", "'".installer::patch::Tools::ToWindowsPath($temporary_msimsp_path)."'");
13539f91b7e3SAndre Fischer#	    "-lp", MsiTools::ToEscapedWindowsPath($performance_log_filename),
13549f91b7e3SAndre Fischer    $installer::logger::Info->printf("running command %s\n", $command);
13559f91b7e3SAndre Fischer    my $response = qx($command);
13569f91b7e3SAndre Fischer    $installer::logger::Info->printf("response of msimsp is %s\n", $response);
13579f91b7e3SAndre Fischer    if ( ! -d $temporary_msimsp_path)
13589f91b7e3SAndre Fischer    {
13599f91b7e3SAndre Fischer        die("msimsp failed and deleted temporary path ".$temporary_msimsp_path);
13609f91b7e3SAndre Fischer    }
13619f91b7e3SAndre Fischer
13629f91b7e3SAndre Fischer    # Show the log file that was created by the msimsp.exe command.
13639f91b7e3SAndre Fischer    ShowLog($log_path, $log_filename, $log_basename, "msp creation");
13649f91b7e3SAndre Fischer    ShowLog($log_path, $performance_log_filename, $performance_log_basename, "msp creation perf");
13659f91b7e3SAndre Fischer}
13669f91b7e3SAndre Fischer
13679f91b7e3SAndre Fischer
13689f91b7e3SAndre Fischer
1369*d575d58fSAndre Fischer
1370*d575d58fSAndre Fischer=head CreatePatch($context, $variables)
1371*d575d58fSAndre Fischer
1372*d575d58fSAndre Fischer    Create MSP patch files for all relevant languages.
1373*d575d58fSAndre Fischer    The different steps are:
1374*d575d58fSAndre Fischer    1. Determine the set of languages for which both the source and target installation sets are present.
1375*d575d58fSAndre Fischer    Per language:
1376*d575d58fSAndre Fischer        2. Unpack CAB files (for source and target).
1377*d575d58fSAndre Fischer        3. Check if source and target releases are compatible.
1378*d575d58fSAndre Fischer        4. Create the PCP driver file.
1379*d575d58fSAndre Fischer        5. Create the MSP patch file.
1380*d575d58fSAndre Fischer
1381*d575d58fSAndre Fischer=cut
13829f91b7e3SAndre Fischersub CreatePatch ($$)
13839f91b7e3SAndre Fischer{
13849f91b7e3SAndre Fischer    my ($context, $variables) = @_;
13859f91b7e3SAndre Fischer
13869f91b7e3SAndre Fischer    $installer::logger::Info->printf("patch will update product %s from %s to %s\n",
13879f91b7e3SAndre Fischer        $context->{'product-name'},
13889f91b7e3SAndre Fischer        $context->{'source-version'},
13899f91b7e3SAndre Fischer        $context->{'target-version'});
13909f91b7e3SAndre Fischer
13919f91b7e3SAndre Fischer    # Locate the Pcp schema file early on to report any errors before the lengthy operations that follow.
13929f91b7e3SAndre Fischer    my $pcp_schema_filename = FindPcpTemplate();
13939f91b7e3SAndre Fischer    if ( ! defined $pcp_schema_filename)
13949f91b7e3SAndre Fischer    {
13959f91b7e3SAndre Fischer        exit(1);
13969f91b7e3SAndre Fischer    }
13979f91b7e3SAndre Fischer
13989f91b7e3SAndre Fischer    my $release_data = installer::patch::ReleasesList::Instance()
13999f91b7e3SAndre Fischer        ->{$context->{'source-version'}}
14009f91b7e3SAndre Fischer        ->{$context->{'package-format'}};
14019f91b7e3SAndre Fischer
1402*d575d58fSAndre Fischer    # 1. Determine the set of languages for which we can create patches.
14039f91b7e3SAndre Fischer    my @requested_languages = GetLanguages();
14049f91b7e3SAndre Fischer    my @valid_languages = FindValidLanguages($context, $release_data, \@requested_languages);
14059f91b7e3SAndre Fischer    $installer::logger::Info->printf("of the requested languages '%s' are valid: '%s'\n",
14069f91b7e3SAndre Fischer        join("', '", @requested_languages),
14079f91b7e3SAndre Fischer        join("', '", @valid_languages));
14089f91b7e3SAndre Fischer    foreach my $language (@valid_languages)
14099f91b7e3SAndre Fischer    {
14109f91b7e3SAndre Fischer        $installer::logger::Info->printf("processing language '%s'\n", $language);
14119f91b7e3SAndre Fischer        $installer::logger::Info->increase_indentation();
14129f91b7e3SAndre Fischer
1413*d575d58fSAndre Fischer        # 2a. Provide .msi and .cab files and unpacke .cab for the source release.
14149f91b7e3SAndre Fischer        $installer::logger::Info->printf("locating source package (%s)\n", $context->{'source-version'});
14159f91b7e3SAndre Fischer        $installer::logger::Info->increase_indentation();
14169f91b7e3SAndre Fischer        if ( ! installer::patch::InstallationSet::ProvideUnpackedCab(
14179f91b7e3SAndre Fischer            $context->{'source-version'},
14189f91b7e3SAndre Fischer            0,
14199f91b7e3SAndre Fischer            $language,
14209f91b7e3SAndre Fischer            "msi",
14219f91b7e3SAndre Fischer            $context->{'product-name'}))
14229f91b7e3SAndre Fischer        {
14239f91b7e3SAndre Fischer            die "could not provide unpacked .cab file";
14249f91b7e3SAndre Fischer        }
14259f91b7e3SAndre Fischer        my $source_msi = installer::patch::Msi->FindAndCreate(
14269f91b7e3SAndre Fischer            $context->{'source-version'},
14279f91b7e3SAndre Fischer            0,
14289f91b7e3SAndre Fischer            $language,
14299f91b7e3SAndre Fischer            $context->{'product-name'});
14309f91b7e3SAndre Fischer        die unless $source_msi->IsValid();
14319f91b7e3SAndre Fischer
14329f91b7e3SAndre Fischer        $installer::logger::Info->decrease_indentation();
14339f91b7e3SAndre Fischer
1434*d575d58fSAndre Fischer        # 2b. Provide .msi and .cab files and unpacke .cab for the target release.
14359f91b7e3SAndre Fischer        $installer::logger::Info->printf("locating target package (%s)\n", $context->{'target-version'});
14369f91b7e3SAndre Fischer        $installer::logger::Info->increase_indentation();
14379f91b7e3SAndre Fischer        if ( ! installer::patch::InstallationSet::ProvideUnpackedCab(
14389f91b7e3SAndre Fischer            $context->{'target-version'},
14399f91b7e3SAndre Fischer            1,
14409f91b7e3SAndre Fischer            $language,
14419f91b7e3SAndre Fischer            "msi",
14429f91b7e3SAndre Fischer            $context->{'product-name'}))
14439f91b7e3SAndre Fischer        {
14449f91b7e3SAndre Fischer            die;
14459f91b7e3SAndre Fischer        }
14469f91b7e3SAndre Fischer        my $target_msi = installer::patch::Msi->FindAndCreate(
14479f91b7e3SAndre Fischer            $context->{'target-version'},
14489f91b7e3SAndre Fischer            0,
14499f91b7e3SAndre Fischer            $language,
14509f91b7e3SAndre Fischer            $context->{'product-name'});
14519f91b7e3SAndre Fischer        die unless defined $target_msi;
14529f91b7e3SAndre Fischer        die unless $target_msi->IsValid();
14539f91b7e3SAndre Fischer        $installer::logger::Info->decrease_indentation();
14549f91b7e3SAndre Fischer
14559f91b7e3SAndre Fischer        # Trigger reading of tables.
14569f91b7e3SAndre Fischer        foreach my $table_name (("File", "Component", "Registry"))
14579f91b7e3SAndre Fischer        {
14589f91b7e3SAndre Fischer            $source_msi->GetTable($table_name);
14599f91b7e3SAndre Fischer            $target_msi->GetTable($table_name);
14609f91b7e3SAndre Fischer            $installer::logger::Info->printf("read %s table (source and target\n", $table_name);
14619f91b7e3SAndre Fischer        }
14629f91b7e3SAndre Fischer
1463*d575d58fSAndre Fischer        # 3. Check if the source and target msis fullfil all necessary requirements.
14649f91b7e3SAndre Fischer        if ( ! Check($source_msi, $target_msi, $variables, $context->{'product-name'}))
14659f91b7e3SAndre Fischer        {
14669f91b7e3SAndre Fischer            $installer::logger::Info->printf("Error: Source and target releases are not compatible.\n");
14679f91b7e3SAndre Fischer            $installer::logger::Info->printf("       => Can not create patch.\n");
14689f91b7e3SAndre Fischer            $installer::logger::Info->printf("       Did you create the target installation set with 'release=t' ?\n");
14699f91b7e3SAndre Fischer            exit(1);
14709f91b7e3SAndre Fischer        }
14719f91b7e3SAndre Fischer        else
14729f91b7e3SAndre Fischer        {
14739f91b7e3SAndre Fischer            $installer::logger::Info->printf("OK: Source and target releases are compatible.\n");
14749f91b7e3SAndre Fischer        }
14759f91b7e3SAndre Fischer
14769f91b7e3SAndre Fischer        # Provide the base path for creating .pcp and .mcp file.
14779f91b7e3SAndre Fischer        my $msp_path = File::Spec->catfile(
14789f91b7e3SAndre Fischer            $context->{'output-path'},
14799f91b7e3SAndre Fischer            $context->{'product-name'},
14809f91b7e3SAndre Fischer            "msp",
14819f91b7e3SAndre Fischer            sprintf("%s_%s",
14829f91b7e3SAndre Fischer              installer::patch::Version::ArrayToDirectoryName(
14839f91b7e3SAndre Fischer                installer::patch::Version::StringToNumberArray(
14849f91b7e3SAndre Fischer                    $source_msi->{'version'})),
14859f91b7e3SAndre Fischer              installer::patch::Version::ArrayToDirectoryName(
14869f91b7e3SAndre Fischer                installer::patch::Version::StringToNumberArray(
14879f91b7e3SAndre Fischer                    $target_msi->{'version'}))),
14889f91b7e3SAndre Fischer            $language
14899f91b7e3SAndre Fischer            );
14909f91b7e3SAndre Fischer        File::Path::make_path($msp_path) unless -d $msp_path;
14919f91b7e3SAndre Fischer
1492*d575d58fSAndre Fischer        # 4. Create the .pcp file that drives the msimsp.exe command.
14939f91b7e3SAndre Fischer        my $pcp = CreatePcp(
14949f91b7e3SAndre Fischer            $source_msi,
14959f91b7e3SAndre Fischer            $target_msi,
14969f91b7e3SAndre Fischer            $language,
14979f91b7e3SAndre Fischer            $context,
14989f91b7e3SAndre Fischer            $msp_path,
14999f91b7e3SAndre Fischer            $pcp_schema_filename,
1500*d575d58fSAndre Fischer            "Properties/Name:DontRemoveTempFolderWhenFinished" => "Value:1");
15019f91b7e3SAndre Fischer
1502*d575d58fSAndre Fischer        # 5. Finally create the msp.
15039f91b7e3SAndre Fischer        CreateMsp($pcp);
15049f91b7e3SAndre Fischer
15059f91b7e3SAndre Fischer        $installer::logger::Info->decrease_indentation();
15069f91b7e3SAndre Fischer    }
15079f91b7e3SAndre Fischer}
15089f91b7e3SAndre Fischer
15099f91b7e3SAndre Fischer
15109f91b7e3SAndre Fischer
1511*d575d58fSAndre Fischer=cut ApplyPatch ($context, $variables)
15129f91b7e3SAndre Fischer
1513*d575d58fSAndre Fischer    This is for testing only.
1514*d575d58fSAndre Fischer    The patch is applied and (extensive) log information is created and transformed into HTML format.
1515*d575d58fSAndre Fischer
1516*d575d58fSAndre Fischer=cut
15179f91b7e3SAndre Fischersub ApplyPatch ($$)
15189f91b7e3SAndre Fischer{
15199f91b7e3SAndre Fischer    my ($context, $variables) = @_;
15209f91b7e3SAndre Fischer
15219f91b7e3SAndre Fischer    $installer::logger::Info->printf("will apply patches that update product %s from %s to %s\n",
15229f91b7e3SAndre Fischer        $context->{'product-name'},
15239f91b7e3SAndre Fischer        $context->{'source-version'},
15249f91b7e3SAndre Fischer        $context->{'target-version'});
15259f91b7e3SAndre Fischer    my @languages = GetLanguages();
15269f91b7e3SAndre Fischer
15279f91b7e3SAndre Fischer    my $source_version_dirname = installer::patch::Version::ArrayToDirectoryName(
15289f91b7e3SAndre Fischer      installer::patch::Version::StringToNumberArray(
15299f91b7e3SAndre Fischer          $context->{'source-version'}));
15309f91b7e3SAndre Fischer    my $target_version_dirname = installer::patch::Version::ArrayToDirectoryName(
15319f91b7e3SAndre Fischer      installer::patch::Version::StringToNumberArray(
15329f91b7e3SAndre Fischer          $context->{'target-version'}));
15339f91b7e3SAndre Fischer
15349f91b7e3SAndre Fischer    foreach my $language (@languages)
15359f91b7e3SAndre Fischer    {
15369f91b7e3SAndre Fischer        my $msp_filename = File::Spec->catfile(
15379f91b7e3SAndre Fischer            $context->{'output-path'},
15389f91b7e3SAndre Fischer            $context->{'product-name'},
15399f91b7e3SAndre Fischer            "msp",
15409f91b7e3SAndre Fischer            $source_version_dirname . "_" . $target_version_dirname,
15419f91b7e3SAndre Fischer            $language,
15429f91b7e3SAndre Fischer            "openoffice.msp");
15439f91b7e3SAndre Fischer        if ( ! -f $msp_filename)
15449f91b7e3SAndre Fischer        {
15459f91b7e3SAndre Fischer            $installer::logger::Info->printf("%s does not point to a valid file\n", $msp_filename);
15469f91b7e3SAndre Fischer            next;
15479f91b7e3SAndre Fischer        }
15489f91b7e3SAndre Fischer
15499f91b7e3SAndre Fischer        my $log_path = File::Spec->catfile(dirname($msp_filename), "log");
15509f91b7e3SAndre Fischer        my $log_basename = "apply-msp";
15519f91b7e3SAndre Fischer        my $log_filename = File::Spec->catfile($log_path, $log_basename.".log");
15529f91b7e3SAndre Fischer
15539f91b7e3SAndre Fischer        my $command = join(" ",
15549f91b7e3SAndre Fischer            "msiexec.exe",
15559f91b7e3SAndre Fischer            "/update", "'".installer::patch::Tools::ToWindowsPath($msp_filename)."'",
15569f91b7e3SAndre Fischer            "/L*xv!", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
15579f91b7e3SAndre Fischer            "REINSTALL=ALL",
15589f91b7e3SAndre Fischer#            "REINSTALLMODE=vomus",
15599f91b7e3SAndre Fischer            "REINSTALLMODE=omus",
15609f91b7e3SAndre Fischer            "MSIENFORCEUPGRADECOMPONENTRULES=1");
15619f91b7e3SAndre Fischer
15629f91b7e3SAndre Fischer        printf("executing command %s\n", $command);
15639f91b7e3SAndre Fischer        my $response = qx($command);
15649f91b7e3SAndre Fischer        Encode::from_to($response, "UTF16LE", "UTF8");
15659f91b7e3SAndre Fischer        printf("response was '%s'\n", $response);
15669f91b7e3SAndre Fischer
15679f91b7e3SAndre Fischer        ShowLog($log_path, $log_filename, $log_basename, "msp application");
15689f91b7e3SAndre Fischer    }
15699f91b7e3SAndre Fischer}
15709f91b7e3SAndre Fischer
15719f91b7e3SAndre Fischer
15729f91b7e3SAndre Fischer
15739f91b7e3SAndre Fischer
1574*d575d58fSAndre Fischer=head2 DownloadFile ($url)
1575*d575d58fSAndre Fischer
1576*d575d58fSAndre Fischer    A simpler version of InstallationSet::Download().  It is simple because it is used to
1577*d575d58fSAndre Fischer    setup the $release_data structure that is used by InstallationSet::Download().
1578*d575d58fSAndre Fischer
1579*d575d58fSAndre Fischer=cut
1580*d575d58fSAndre Fischersub DownloadFile ($)
1581*d575d58fSAndre Fischer{
1582*d575d58fSAndre Fischer    my ($url) = shift;
1583*d575d58fSAndre Fischer
1584*d575d58fSAndre Fischer    my $agent = LWP::UserAgent->new();
1585*d575d58fSAndre Fischer    $agent->timeout(120);
1586*d575d58fSAndre Fischer    $agent->show_progress(0);
1587*d575d58fSAndre Fischer
1588*d575d58fSAndre Fischer    my $file_content = "";
1589*d575d58fSAndre Fischer    my $last_was_redirect = 0;
1590*d575d58fSAndre Fischer    my $bytes_read = 0;
1591*d575d58fSAndre Fischer    $agent->add_handler('response_redirect'
1592*d575d58fSAndre Fischer        => sub{
1593*d575d58fSAndre Fischer            $last_was_redirect = 1;
1594*d575d58fSAndre Fischer            return;
1595*d575d58fSAndre Fischer        });
1596*d575d58fSAndre Fischer    $agent->add_handler('response_data'
1597*d575d58fSAndre Fischer        => sub{
1598*d575d58fSAndre Fischer            if ($last_was_redirect)
1599*d575d58fSAndre Fischer            {
1600*d575d58fSAndre Fischer                $last_was_redirect = 0;
1601*d575d58fSAndre Fischer                # Throw away the data we got so far.
1602*d575d58fSAndre Fischer		$file_content = "";
1603*d575d58fSAndre Fischer            }
1604*d575d58fSAndre Fischer            my($response,$agent,$h,$data)=@_;
1605*d575d58fSAndre Fischer	    $file_content .= $data;
1606*d575d58fSAndre Fischer        });
1607*d575d58fSAndre Fischer    $agent->get($url);
1608*d575d58fSAndre Fischer
1609*d575d58fSAndre Fischer    return $file_content;
1610*d575d58fSAndre Fischer}
1611*d575d58fSAndre Fischer
1612*d575d58fSAndre Fischer
1613*d575d58fSAndre Fischer
1614*d575d58fSAndre Fischer
1615*d575d58fSAndre Fischersub CreateReleaseItem ($$$)
1616*d575d58fSAndre Fischer{
1617*d575d58fSAndre Fischer    my ($language, $exe_filename, $msi) = @_;
1618*d575d58fSAndre Fischer
1619*d575d58fSAndre Fischer    die "can not open installation set at ".$exe_filename unless -f $exe_filename;
1620*d575d58fSAndre Fischer
1621*d575d58fSAndre Fischer    open my $in, "<", $exe_filename;
1622*d575d58fSAndre Fischer    my $sha256_checksum = new Digest("SHA-256")->addfile($in)->hexdigest();
1623*d575d58fSAndre Fischer    close $in;
1624*d575d58fSAndre Fischer
1625*d575d58fSAndre Fischer    my $filesize = -s $exe_filename;
1626*d575d58fSAndre Fischer
1627*d575d58fSAndre Fischer    # Get the product code property from the msi and strip the enclosing braces.
1628*d575d58fSAndre Fischer    my $product_code = $msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value");
1629*d575d58fSAndre Fischer    $product_code =~ s/(^{|}$)//g;
1630*d575d58fSAndre Fischer    my $upgrade_code = $msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value");
1631*d575d58fSAndre Fischer    $upgrade_code =~ s/(^{|}$)//g;
1632*d575d58fSAndre Fischer    my $build_id = $msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value");
1633*d575d58fSAndre Fischer
1634*d575d58fSAndre Fischer    return {
1635*d575d58fSAndre Fischer        'language' => $language,
1636*d575d58fSAndre Fischer        'checksum-type' => "sha256",
1637*d575d58fSAndre Fischer        'checksum-value' => $sha256_checksum,
1638*d575d58fSAndre Fischer        'file-size' => $filesize,
1639*d575d58fSAndre Fischer        'product-code' => $product_code,
1640*d575d58fSAndre Fischer        'upgrade-code' => $upgrade_code,
1641*d575d58fSAndre Fischer        'build-id' => $build_id
1642*d575d58fSAndre Fischer    };
1643*d575d58fSAndre Fischer}
1644*d575d58fSAndre Fischer
1645*d575d58fSAndre Fischer
1646*d575d58fSAndre Fischer
1647*d575d58fSAndre Fischer
1648*d575d58fSAndre Fischersub GetReleaseItemForCurrentBuild ($$$)
1649*d575d58fSAndre Fischer{
1650*d575d58fSAndre Fischer    my ($context, $language, $exe_basename) = @_;
1651*d575d58fSAndre Fischer
1652*d575d58fSAndre Fischer    # Target version is the current version.
1653*d575d58fSAndre Fischer    # Search instsetoo_native for the installation set.
1654*d575d58fSAndre Fischer    my $filename = File::Spec->catfile(
1655*d575d58fSAndre Fischer        $context->{'output-path'},
1656*d575d58fSAndre Fischer        $context->{'product-name'},
1657*d575d58fSAndre Fischer        $context->{'package-format'},
1658*d575d58fSAndre Fischer        "install",
1659*d575d58fSAndre Fischer        $language."_download",
1660*d575d58fSAndre Fischer        $exe_basename);
1661*d575d58fSAndre Fischer
1662*d575d58fSAndre Fischer    printf("        current : %s\n", $filename);
1663*d575d58fSAndre Fischer    if ( ! -f $filename)
1664*d575d58fSAndre Fischer    {
1665*d575d58fSAndre Fischer        printf("ERROR: can not find %s\n", $filename);
1666*d575d58fSAndre Fischer        return undef;
1667*d575d58fSAndre Fischer    }
1668*d575d58fSAndre Fischer    else
1669*d575d58fSAndre Fischer    {
1670*d575d58fSAndre Fischer        my $msi = installer::patch::Msi->FindAndCreate(
1671*d575d58fSAndre Fischer            $context->{'target-version'},
1672*d575d58fSAndre Fischer            1,
1673*d575d58fSAndre Fischer            $language,
1674*d575d58fSAndre Fischer            $context->{'product-name'});
1675*d575d58fSAndre Fischer        return CreateReleaseItem($language, $filename, $msi);
1676*d575d58fSAndre Fischer    }
1677*d575d58fSAndre Fischer}
1678*d575d58fSAndre Fischer
1679*d575d58fSAndre Fischer
1680*d575d58fSAndre Fischer
1681*d575d58fSAndre Fischersub GetReleaseItemForOldBuild ($$$$)
1682*d575d58fSAndre Fischer{
1683*d575d58fSAndre Fischer    my ($context, $language, $exe_basename, $url_template) = @_;
1684*d575d58fSAndre Fischer
1685*d575d58fSAndre Fischer    # Use ext_sources/ as local cache for archive.apache.org
1686*d575d58fSAndre Fischer    # and search these for the installation set.
1687*d575d58fSAndre Fischer
1688*d575d58fSAndre Fischer    my $version = $context->{'target-version'};
1689*d575d58fSAndre Fischer    my $package_format =  $context->{'package-format'};
1690*d575d58fSAndre Fischer    my $releases_list = installer::patch::ReleasesList::Instance();
1691*d575d58fSAndre Fischer
1692*d575d58fSAndre Fischer    my $url = $url_template;
1693*d575d58fSAndre Fischer    $url =~ s/%L/$language/g;
1694*d575d58fSAndre Fischer    $releases_list->{$version}->{$package_format}->{$language}->{'URL'} = $url;
1695*d575d58fSAndre Fischer
1696*d575d58fSAndre Fischer    if ( ! installer::patch::InstallationSet::ProvideUnpackedExe(
1697*d575d58fSAndre Fischer               $version,
1698*d575d58fSAndre Fischer               0,
1699*d575d58fSAndre Fischer               $language,
1700*d575d58fSAndre Fischer               $package_format,
1701*d575d58fSAndre Fischer               $context->{'product-name'}))
1702*d575d58fSAndre Fischer    {
1703*d575d58fSAndre Fischer        # Can not provide unpacked EXE.
1704*d575d58fSAndre Fischer        return undef;
1705*d575d58fSAndre Fischer    }
1706*d575d58fSAndre Fischer    else
1707*d575d58fSAndre Fischer    {
1708*d575d58fSAndre Fischer        my $exe_filename = File::Spec->catfile(
1709*d575d58fSAndre Fischer            $ENV{'TARFILE_LOCATION'},
1710*d575d58fSAndre Fischer            $exe_basename);
1711*d575d58fSAndre Fischer        my $msi = installer::patch::Msi->FindAndCreate(
1712*d575d58fSAndre Fischer            $version,
1713*d575d58fSAndre Fischer            0,
1714*d575d58fSAndre Fischer            $language,
1715*d575d58fSAndre Fischer            $context->{'product-name'});
1716*d575d58fSAndre Fischer        return CreateReleaseItem($language, $exe_filename, $msi);
1717*d575d58fSAndre Fischer    }
1718*d575d58fSAndre Fischer}
1719*d575d58fSAndre Fischer
1720*d575d58fSAndre Fischer
1721*d575d58fSAndre Fischer
1722*d575d58fSAndre Fischer
1723*d575d58fSAndre Fischersub UpdateReleasesXML($$)
1724*d575d58fSAndre Fischer{
1725*d575d58fSAndre Fischer    my ($context, $variables) = @_;
1726*d575d58fSAndre Fischer
1727*d575d58fSAndre Fischer    my $releases_list = installer::patch::ReleasesList::Instance();
1728*d575d58fSAndre Fischer    my $output_filename = File::Spec->catfile(
1729*d575d58fSAndre Fischer        $context->{'output-path'},
1730*d575d58fSAndre Fischer        "misc",
1731*d575d58fSAndre Fischer        "releases.xml");
1732*d575d58fSAndre Fischer
1733*d575d58fSAndre Fischer    my $target_version = $context->{'target-version'};
1734*d575d58fSAndre Fischer    my %version_hash = map {$_=>1} @{$releases_list->{'releases'}};
1735*d575d58fSAndre Fischer    my $item_hash = undef;
1736*d575d58fSAndre Fischer    if ( ! defined $version_hash{$context->{'target-version'}})
1737*d575d58fSAndre Fischer    {
1738*d575d58fSAndre Fischer        # Target version is not yet present.  Add it and print message that asks caller to check order.
1739*d575d58fSAndre Fischer        push @{$releases_list->{'releases'}}, $target_version;
1740*d575d58fSAndre Fischer        printf("adding data for new version %s to list of released versions.\n", $target_version);
1741*d575d58fSAndre Fischer        printf("please check order of releases in $output_filename\n");
1742*d575d58fSAndre Fischer        $item_hash = {};
1743*d575d58fSAndre Fischer    }
1744*d575d58fSAndre Fischer    else
1745*d575d58fSAndre Fischer    {
1746*d575d58fSAndre Fischer        printf("adding data for existing version %s to releases.xml\n", $target_version);
1747*d575d58fSAndre Fischer        $item_hash = $releases_list->{$target_version}->{$context->{'package-format'}};
1748*d575d58fSAndre Fischer    }
1749*d575d58fSAndre Fischer    $releases_list->{$target_version} = {$context->{'package-format'} => $item_hash};
1750*d575d58fSAndre Fischer
1751*d575d58fSAndre Fischer    my @languages = GetLanguages();
1752*d575d58fSAndre Fischer    my %language_items = ();
1753*d575d58fSAndre Fischer    foreach my $language (@languages)
1754*d575d58fSAndre Fischer    {
1755*d575d58fSAndre Fischer        # There are three different sources where to find the downloadable installation sets.
1756*d575d58fSAndre Fischer        # 1. archive.apache.org for previously released versions.
1757*d575d58fSAndre Fischer        # 2. A local cache or repository directory that conceptually is a local copy of archive.apache.org
1758*d575d58fSAndre Fischer        # 3. The downloadable installation sets built in instsetoo_native/.
1759*d575d58fSAndre Fischer
1760*d575d58fSAndre Fischer        my $exe_basename = sprintf(
1761*d575d58fSAndre Fischer            "%s_%s_Win_x86_install_%s.exe",
1762*d575d58fSAndre Fischer            $context->{'product-name'},
1763*d575d58fSAndre Fischer            $target_version,
1764*d575d58fSAndre Fischer            $language);
1765*d575d58fSAndre Fischer        my $url_template = sprintf(
1766*d575d58fSAndre Fischer            "http://archive.apache.org/dist/openoffice/%s/binaries/%%L/%s_%s_Win_x86_install_%%L.exe",
1767*d575d58fSAndre Fischer            $target_version,
1768*d575d58fSAndre Fischer            $context->{'product-name'},
1769*d575d58fSAndre Fischer            $target_version);
1770*d575d58fSAndre Fischer
1771*d575d58fSAndre Fischer        my $item = undef;
1772*d575d58fSAndre Fischer        if ($target_version eq $variables->{PRODUCTVERSION})
1773*d575d58fSAndre Fischer        {
1774*d575d58fSAndre Fischer            $item = GetReleaseItemForCurrentBuild($context, $language, $exe_basename);
1775*d575d58fSAndre Fischer        }
1776*d575d58fSAndre Fischer        else
1777*d575d58fSAndre Fischer        {
1778*d575d58fSAndre Fischer            $item = GetReleaseItemForOldBuild($context, $language, $exe_basename, $url_template);
1779*d575d58fSAndre Fischer        }
1780*d575d58fSAndre Fischer
1781*d575d58fSAndre Fischer        next unless defined $item;
1782*d575d58fSAndre Fischer
1783*d575d58fSAndre Fischer        $language_items{$language} = $item;
1784*d575d58fSAndre Fischer        $item_hash->{$language} = $item;
1785*d575d58fSAndre Fischer        $item_hash->{'upgrade-code'} = $item->{'upgrade-code'};
1786*d575d58fSAndre Fischer        $item_hash->{'build-id'} = $item->{'build-id'};
1787*d575d58fSAndre Fischer        $item_hash->{'url-template'} = $url_template;
1788*d575d58fSAndre Fischer    }
1789*d575d58fSAndre Fischer
1790*d575d58fSAndre Fischer    my @valid_languages = sort keys %language_items;
1791*d575d58fSAndre Fischer    $item_hash->{'languages'} = \@valid_languages;
1792*d575d58fSAndre Fischer
1793*d575d58fSAndre Fischer    $releases_list->Write($output_filename);
1794*d575d58fSAndre Fischer
1795*d575d58fSAndre Fischer    printf("\n\n");
1796*d575d58fSAndre Fischer    printf("please copy '%s' to main/instsetoo_native/data\n", $output_filename);
1797*d575d58fSAndre Fischer    printf("and check in the modified file to the version control system\n");
1798*d575d58fSAndre Fischer}
1799*d575d58fSAndre Fischer
1800*d575d58fSAndre Fischer
1801*d575d58fSAndre Fischer
1802*d575d58fSAndre Fischer
18039f91b7e3SAndre Fischersub main ()
18049f91b7e3SAndre Fischer{
18059f91b7e3SAndre Fischer    installer::logger::SetupSimpleLogging(undef);
18069f91b7e3SAndre Fischer    my $context = ProcessCommandline();
1807*d575d58fSAndre Fischer    die "ERROR: list file is not defined, please use --lst-file option"
1808*d575d58fSAndre Fischer        unless defined $context->{'lst-file'};
1809*d575d58fSAndre Fischer    die "ERROR: product name is not defined, please use --product-name option"
1810*d575d58fSAndre Fischer        unless defined $context->{'product-name'};
1811*d575d58fSAndre Fischer
18129f91b7e3SAndre Fischer    my ($variables, undef, undef) = installer::ziplist::read_openoffice_lst_file(
18139f91b7e3SAndre Fischer        $context->{'lst-file'},
18149f91b7e3SAndre Fischer        $context->{'product-name'},
18159f91b7e3SAndre Fischer        undef);
18169f91b7e3SAndre Fischer    DetermineVersions($context, $variables);
18179f91b7e3SAndre Fischer
18189f91b7e3SAndre Fischer    if ($context->{'command'} eq "create")
18199f91b7e3SAndre Fischer    {
18209f91b7e3SAndre Fischer        CreatePatch($context, $variables);
18219f91b7e3SAndre Fischer    }
18229f91b7e3SAndre Fischer    elsif ($context->{'command'} eq "apply")
18239f91b7e3SAndre Fischer    {
18249f91b7e3SAndre Fischer        ApplyPatch($context, $variables);
18259f91b7e3SAndre Fischer    }
1826*d575d58fSAndre Fischer    elsif ($context->{'command'} eq "update-releases-xml")
1827*d575d58fSAndre Fischer    {
1828*d575d58fSAndre Fischer        UpdateReleasesXML($context, $variables);
1829*d575d58fSAndre Fischer    }
18309f91b7e3SAndre Fischer}
18319f91b7e3SAndre Fischer
18329f91b7e3SAndre Fischer
18339f91b7e3SAndre Fischermain();
1834