xref: /aoo42x/main/solenv/bin/patch_tool.pl (revision 75b1440a)
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
41677600b0SAndre Fischer#use Carp::Always;
42677600b0SAndre Fischer
439f91b7e3SAndre Fischeruse strict;
449f91b7e3SAndre Fischer
459f91b7e3SAndre Fischer
469f91b7e3SAndre Fischer=head1 NAME
479f91b7e3SAndre Fischer
489f91b7e3SAndre Fischer    patch_tool.pl - Create Windows MSI patches.
499f91b7e3SAndre Fischer
509f91b7e3SAndre Fischer=head1 SYNOPSIS
519f91b7e3SAndre Fischer
529f91b7e3SAndre Fischer    patch_tool.pl command [options]
539f91b7e3SAndre Fischer
549f91b7e3SAndre Fischer    Commands:
559f91b7e3SAndre Fischer        create    create patches
569f91b7e3SAndre Fischer        apply     apply patches
579f91b7e3SAndre Fischer
589f91b7e3SAndre Fischer    Options:
599f91b7e3SAndre Fischer        -p|--product-name <product-name>
609f91b7e3SAndre Fischer             The product name, eg Apache_OpenOffice
619f91b7e3SAndre Fischer        -o|--output-path <path>
629f91b7e3SAndre Fischer             Path to the instsetoo_native platform output tree
639f91b7e3SAndre Fischer        -d|--data-path <path>
649f91b7e3SAndre Fischer             Path to the data directory that is expected to be under version control.
659f91b7e3SAndre Fischer        --source-version <major>.<minor>.<micro>
669f91b7e3SAndre Fischer             The version that is to be patched.
679f91b7e3SAndre Fischer        --target-version <major>.<minor>.<micro>
689f91b7e3SAndre Fischer             The version after the patch has been applied.
69677600b0SAndre Fischer        --language <language-code>
70677600b0SAndre Fischer             Language of the installation sets.
71677600b0SAndre Fischer        --package-format
72677600b0SAndre Fischer             Only the package format 'msi' is supported at the moment.
739f91b7e3SAndre Fischer
749f91b7e3SAndre Fischer=head1 DESCRIPTION
759f91b7e3SAndre Fischer
769f91b7e3SAndre Fischer    Creates windows MSP patch files, one for each relevant language.
779f91b7e3SAndre Fischer    Patches convert an installed OpenOffice to the target version.
789f91b7e3SAndre Fischer
799f91b7e3SAndre Fischer    Required data are:
809f91b7e3SAndre Fischer        Installation sets of the source versions
819f91b7e3SAndre Fischer            Taken from ext_sources/
829f91b7e3SAndre Fischer            Downloaded from archive.apache.org on demand
839f91b7e3SAndre Fischer
849f91b7e3SAndre Fischer        Installation set of the target version
859f91b7e3SAndre Fischer            This is expected to be the current version.
869f91b7e3SAndre Fischer
879f91b7e3SAndre Fischer=cut
889f91b7e3SAndre Fischer
899f91b7e3SAndre Fischer# The ImageFamily name has to have 1-8 alphanumeric characters.
909f91b7e3SAndre Fischermy $ImageFamily = "AOO";
919f91b7e3SAndre Fischermy $SourceImageName = "Source";
929f91b7e3SAndre Fischermy $TargetImageName = "Target";
939f91b7e3SAndre Fischer
949f91b7e3SAndre Fischer
959f91b7e3SAndre Fischer
969f91b7e3SAndre Fischersub ProcessCommandline ()
979f91b7e3SAndre Fischer{
98677600b0SAndre Fischer    my $context = {
999f91b7e3SAndre Fischer        'product-name' => undef,
1009f91b7e3SAndre Fischer        'output-path' => undef,
1019f91b7e3SAndre Fischer        'data-path' => undef,
1029f91b7e3SAndre Fischer        'lst-file' => undef,
1039f91b7e3SAndre Fischer        'source-version' => undef,
104677600b0SAndre Fischer        'target-version' => undef,
105677600b0SAndre Fischer        'language' => undef,
106677600b0SAndre Fischer        'package-format' => undef
107677600b0SAndre Fischer    };
1089f91b7e3SAndre Fischer
1099f91b7e3SAndre Fischer    if ( ! GetOptions(
110677600b0SAndre Fischer               "product-name=s", \$context->{'product-name'},
111677600b0SAndre Fischer               "output-path=s", \$context->{'output-path'},
112677600b0SAndre Fischer               "data-path=s" => \$context->{'data-path'},
113677600b0SAndre Fischer               "lst-file=s" => \$context->{'lst-file'},
114677600b0SAndre Fischer               "source-version:s" => \$context->{'source-version'},
115677600b0SAndre Fischer               "target-version:s" => \$context->{'target-version'},
116677600b0SAndre Fischer               "language=s" => \$context->{'language'},
117677600b0SAndre Fischer               "package-format=s" => \$context->{'package-format'}
1189f91b7e3SAndre Fischer        ))
1199f91b7e3SAndre Fischer    {
1209f91b7e3SAndre Fischer        pod2usage(2);
1219f91b7e3SAndre Fischer    }
1229f91b7e3SAndre Fischer
1239f91b7e3SAndre Fischer    # Only the command should be left in @ARGV.
1249f91b7e3SAndre Fischer    pod2usage(2) unless scalar @ARGV == 1;
125677600b0SAndre Fischer    $context->{'command'} = shift @ARGV;
1269f91b7e3SAndre Fischer
127677600b0SAndre Fischer    return $context;
1289f91b7e3SAndre Fischer}
1299f91b7e3SAndre Fischer
1309f91b7e3SAndre Fischer
1319f91b7e3SAndre Fischer
1329f91b7e3SAndre Fischer
1339f91b7e3SAndre Fischersub GetSourceMsiPath ($$)
1349f91b7e3SAndre Fischer{
1359f91b7e3SAndre Fischer    my ($context, $language) = @_;
1369f91b7e3SAndre Fischer    my $unpacked_path = File::Spec->catfile(
1379f91b7e3SAndre Fischer	$context->{'output-path'},
1389f91b7e3SAndre Fischer	$context->{'product-name'},
1399f91b7e3SAndre Fischer        $context->{'package-format'},
1409f91b7e3SAndre Fischer	installer::patch::Version::ArrayToDirectoryName(
1419f91b7e3SAndre Fischer	    installer::patch::Version::StringToNumberArray(
1429f91b7e3SAndre Fischer		$context->{'source-version'})),
1439f91b7e3SAndre Fischer	$language);
1449f91b7e3SAndre Fischer}
1459f91b7e3SAndre Fischer
1469f91b7e3SAndre Fischer
1479f91b7e3SAndre Fischer
1489f91b7e3SAndre Fischer
1499f91b7e3SAndre Fischersub GetTargetMsiPath ($$)
1509f91b7e3SAndre Fischer{
1519f91b7e3SAndre Fischer    my ($context, $language) = @_;
1529f91b7e3SAndre Fischer    return File::Spec->catfile(
1539f91b7e3SAndre Fischer        $context->{'output-path'},
1549f91b7e3SAndre Fischer        $context->{'product-name'},
1559f91b7e3SAndre Fischer        $context->{'package-format'},
1569f91b7e3SAndre Fischer        "install",
1579f91b7e3SAndre Fischer        $language);
1589f91b7e3SAndre Fischer}
1599f91b7e3SAndre Fischer
1609f91b7e3SAndre Fischer
1619f91b7e3SAndre Fischer
1629f91b7e3SAndre Fischersub ProvideInstallationSets ($$)
1639f91b7e3SAndre Fischer{
1649f91b7e3SAndre Fischer    my ($context, $language) = @_;
1659f91b7e3SAndre Fischer
1669f91b7e3SAndre Fischer    # Assume that the target installation set is located in the output tree.
1679f91b7e3SAndre Fischer    my $target_path = GetTargetMsiPath($context, $language);
1689f91b7e3SAndre Fischer    if ( ! -d $target_path)
1699f91b7e3SAndre Fischer    {
1709f91b7e3SAndre Fischer        installer::logger::PrintError("can not find target installation set at '%s'\n", $target_path);
1719f91b7e3SAndre Fischer        return 0;
1729f91b7e3SAndre Fischer    }
1739f91b7e3SAndre Fischer    my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'});
1749f91b7e3SAndre Fischer    my $target_msi_file = File::Spec->catfile(
1759f91b7e3SAndre Fischer        $target_path,
1769f91b7e3SAndre Fischer        sprintf("openoffice%d%d%d.msi", $target_version[0], $target_version[1], $target_version[2]));
1779f91b7e3SAndre Fischer    if ( ! -f $target_msi_file)
1789f91b7e3SAndre Fischer    {
1799f91b7e3SAndre Fischer        installer::logger::PrintError("can not find target msi file at '%s'\n", $target_msi_file);
1809f91b7e3SAndre Fischer        return 0;
1819f91b7e3SAndre Fischer    }
1829f91b7e3SAndre Fischer
1839f91b7e3SAndre Fischer    return 1;
1849f91b7e3SAndre Fischer}
1859f91b7e3SAndre Fischer
1869f91b7e3SAndre Fischer
1879f91b7e3SAndre Fischer
1889f91b7e3SAndre Fischer
189677600b0SAndre Fischersub IsLanguageValid ($$$)
1909f91b7e3SAndre Fischer{
191677600b0SAndre Fischer    my ($context, $release_data, $language) = @_;
1929f91b7e3SAndre Fischer
193677600b0SAndre Fischer    my $normalized_language = installer::languages::get_normalized_language($language);
1949f91b7e3SAndre Fischer
195677600b0SAndre Fischer    if ( ! ProvideInstallationSets($context, $language))
1969f91b7e3SAndre Fischer    {
197677600b0SAndre Fischer        installer::logger::PrintError("    '%s' has no target installation set\n", $language);
198677600b0SAndre Fischer        return 0;
199677600b0SAndre Fischer    }
200677600b0SAndre Fischer    elsif ( ! defined $release_data->{$normalized_language})
201677600b0SAndre Fischer    {
202677600b0SAndre Fischer        installer::logger::PrintError("    '%s' is not a released language for version %s\n",
203677600b0SAndre Fischer            $language,
204677600b0SAndre Fischer            $context->{'source-version'});
205677600b0SAndre Fischer        return 0;
206677600b0SAndre Fischer    }
207677600b0SAndre Fischer    else
208677600b0SAndre Fischer    {
209677600b0SAndre Fischer        return 1;
2109f91b7e3SAndre Fischer    }
2119f91b7e3SAndre Fischer}
2129f91b7e3SAndre Fischer
2139f91b7e3SAndre Fischer
2149f91b7e3SAndre Fischer
2159f91b7e3SAndre Fischer
2169f91b7e3SAndre Fischersub ProvideSourceInstallationSet ($$$)
2179f91b7e3SAndre Fischer{
2189f91b7e3SAndre Fischer    my ($context, $language, $release_data) = @_;
2199f91b7e3SAndre Fischer
2209f91b7e3SAndre Fischer    my $url = $release_data->{$language}->{'URL'};
2219f91b7e3SAndre Fischer    $url =~ /^(.*)\/([^\/]*)$/;
2229f91b7e3SAndre Fischer    my ($location, $basename) = ($1,$2);
2239f91b7e3SAndre Fischer
2249f91b7e3SAndre Fischer    my $ext_sources_path = $ENV{'TARFILE_LOCATION'};
2259f91b7e3SAndre Fischer    if ( ! -d $ext_sources_path)
2269f91b7e3SAndre Fischer    {
2279f91b7e3SAndre Fischer        installer::logger::PrintError("Can not determine the path to ext_sources/.\n");
2289f91b7e3SAndre Fischer        installer::logger::PrintError("Maybe SOURCE_ROOT_DIR has not been correctly set in the environment?");
2299f91b7e3SAndre Fischer        return 0;
2309f91b7e3SAndre Fischer    }
2319f91b7e3SAndre Fischer
2329f91b7e3SAndre Fischer    # We need the unpacked installation set in <platform>/<product>/<package>/<source-version>,
2339f91b7e3SAndre Fischer    # eg wntmsci12.pro/Apache_OpenOffice/msi/v-4-0-0.
2349f91b7e3SAndre Fischer    my $unpacked_path = GetSourceMsiPath($context, $language);
2359f91b7e3SAndre Fischer    if ( ! -d $unpacked_path)
2369f91b7e3SAndre Fischer    {
2379f91b7e3SAndre Fischer        # Make sure that the downloadable installation set (.exe) is present in ext_sources/.
2389f91b7e3SAndre Fischer        my $filename = File::Spec->catfile($ext_sources_path, $basename);
2399f91b7e3SAndre Fischer        if ( -f $filename)
2409f91b7e3SAndre Fischer        {
2419f91b7e3SAndre Fischer            PrintInfo("%s is already present in ext_sources/.  Nothing to do\n", $basename);
2429f91b7e3SAndre Fischer        }
2439f91b7e3SAndre Fischer        else
2449f91b7e3SAndre Fischer        {
245d575d58fSAndre Fischer            return 0 if ! installer::patch::InstallationSet::Download(
246d575d58fSAndre Fischer                $language,
247d575d58fSAndre Fischer                $release_data,
248d575d58fSAndre Fischer                $filename);
2499f91b7e3SAndre Fischer            return 0 if ! -f $filename;
2509f91b7e3SAndre Fischer        }
2519f91b7e3SAndre Fischer
2529f91b7e3SAndre Fischer        # Unpack the installation set.
2539f91b7e3SAndre Fischer        if ( -d $unpacked_path)
2549f91b7e3SAndre Fischer        {
2559f91b7e3SAndre Fischer            # Take the existence of the destination path as proof that the
2569f91b7e3SAndre Fischer            # installation set was successfully unpacked before.
2579f91b7e3SAndre Fischer        }
2589f91b7e3SAndre Fischer        else
2599f91b7e3SAndre Fischer        {
2609f91b7e3SAndre Fischer            installer::patch::InstallationSet::Unpack($filename, $unpacked_path);
2619f91b7e3SAndre Fischer        }
2629f91b7e3SAndre Fischer    }
2639f91b7e3SAndre Fischer}
2649f91b7e3SAndre Fischer
2659f91b7e3SAndre Fischer
2669f91b7e3SAndre Fischer
2679f91b7e3SAndre Fischer
2689f91b7e3SAndre Fischer# Find the source and target version between which the patch will be
2699f91b7e3SAndre Fischer# created.  Typically the target version is the current version and
2709f91b7e3SAndre Fischer# the source version is the version of the previous release.
2719f91b7e3SAndre Fischersub DetermineVersions ($$)
2729f91b7e3SAndre Fischer{
2739f91b7e3SAndre Fischer    my ($context, $variables) = @_;
2749f91b7e3SAndre Fischer
2759f91b7e3SAndre Fischer    if (defined $context->{'source-version'} && defined $context->{'target-version'})
2769f91b7e3SAndre Fischer    {
2779f91b7e3SAndre Fischer        # Both source and target version have been specified on the
2789f91b7e3SAndre Fischer        # command line.  There remains nothing to be be done.
2799f91b7e3SAndre Fischer        return;
2809f91b7e3SAndre Fischer    }
2819f91b7e3SAndre Fischer
2829f91b7e3SAndre Fischer    if ( ! defined $context->{'target-version'})
2839f91b7e3SAndre Fischer    {
2849f91b7e3SAndre Fischer        # Use the current version as target version.
2859f91b7e3SAndre Fischer        $context->{'target-version'} = $variables->{PRODUCTVERSION};
2869f91b7e3SAndre Fischer    }
2879f91b7e3SAndre Fischer
2889f91b7e3SAndre Fischer    my @target_version = installer::patch::Version::StringToNumberArray($context->{'target-version'});
2899f91b7e3SAndre Fischer    shift @target_version;
2909f91b7e3SAndre Fischer    my $is_target_version_major = 1;
2919f91b7e3SAndre Fischer    foreach my $number (@target_version)
2929f91b7e3SAndre Fischer    {
2939f91b7e3SAndre Fischer        $is_target_version_major = 0 if ($number ne "0");
2949f91b7e3SAndre Fischer    }
2959f91b7e3SAndre Fischer    if ($is_target_version_major)
2969f91b7e3SAndre Fischer    {
2979f91b7e3SAndre Fischer        installer::logger::PrintError("can not create patch where target version is a new major version (%s)\n",
2989f91b7e3SAndre Fischer            $context->{'target-version'});
2999f91b7e3SAndre Fischer        die;
3009f91b7e3SAndre Fischer    }
3019f91b7e3SAndre Fischer
3029f91b7e3SAndre Fischer    if ( ! defined $context->{'source-version'})
3039f91b7e3SAndre Fischer    {
3049f91b7e3SAndre Fischer        my $releases = installer::patch::ReleasesList::Instance();
3059f91b7e3SAndre Fischer
3069f91b7e3SAndre Fischer        # Search for target release in the list of previous releases.
3079f91b7e3SAndre Fischer        # If it is found, use the previous version as source version.
3089f91b7e3SAndre Fischer        # Otherwise use the last released version.
3099f91b7e3SAndre Fischer        my $last_release = undef;
3109f91b7e3SAndre Fischer        foreach my $release (@{$releases->{'releases'}})
3119f91b7e3SAndre Fischer        {
3129f91b7e3SAndre Fischer            last if ($release eq $context->{'target-version'});
3139f91b7e3SAndre Fischer            $last_release = $release;
3149f91b7e3SAndre Fischer        }
3159f91b7e3SAndre Fischer        $context->{'source-version'} = $last_release;
3169f91b7e3SAndre Fischer    }
317677600b0SAndre Fischer
318677600b0SAndre Fischer    if (defined $context->{'source-version'})
319677600b0SAndre Fischer    {
320677600b0SAndre Fischer        $context->{'source-version-dash'} = installer::patch::Version::ArrayToDirectoryName(
321677600b0SAndre Fischer            installer::patch::Version::StringToNumberArray(
322677600b0SAndre Fischer                $context->{'source-version'}));
323677600b0SAndre Fischer    }
324677600b0SAndre Fischer    if (defined $context->{'target-version'})
325677600b0SAndre Fischer    {
326677600b0SAndre Fischer        $context->{'target-version-dash'} = installer::patch::Version::ArrayToDirectoryName(
327677600b0SAndre Fischer            installer::patch::Version::StringToNumberArray(
328677600b0SAndre Fischer                $context->{'target-version'}));
329677600b0SAndre 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    {
355677600b0SAndre Fischer        $installer::logger::Info->printf("OK: UpgradeCode values are different\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    {
385677600b0SAndre Fischer        $installer::logger::Info->printf("OK: ProductCodes are identical\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()};
504677600b0SAndre Fischer    my %target_files_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()};
5059f91b7e3SAndre Fischer
5069f91b7e3SAndre Fischer    # Search for added files (files in target that where not in source).
507677600b0SAndre Fischer    my @added_files = ();
508677600b0SAndre Fischer    foreach my $uniquename (keys %target_files_map)
5099f91b7e3SAndre Fischer    {
5109f91b7e3SAndre Fischer        if ( ! defined $source_file_map{$uniquename})
5119f91b7e3SAndre Fischer        {
512677600b0SAndre Fischer            push @added_files, $target_files_map{$uniquename};
5139f91b7e3SAndre Fischer        }
5149f91b7e3SAndre Fischer    }
5159f91b7e3SAndre Fischer
516677600b0SAndre Fischer    if (scalar @added_files > 0)
5179f91b7e3SAndre Fischer    {
518677600b0SAndre Fischer        $installer::logger::Info->printf("Warning: %d files have been added\n", scalar @added_files);
5199f91b7e3SAndre Fischer
520677600b0SAndre Fischer        # Prepare component tables and hashes.
521677600b0SAndre Fischer        my $source_component_table = $source_msi->GetTable("Component");
522677600b0SAndre Fischer        my $target_component_table = $target_msi->GetTable("Component");
523677600b0SAndre Fischer        die unless defined $source_component_table && defined $target_component_table;
524677600b0SAndre Fischer        my %source_component_map = map {$_->GetValue('Component') => $_} @{$source_component_table->GetAllRows()};
525677600b0SAndre Fischer        my %target_component_map = map {$_->GetValue('Component') => $_} @{$target_component_table->GetAllRows()};
526677600b0SAndre Fischer
527677600b0SAndre Fischer        my @new_files_with_existing_components = ();
528677600b0SAndre Fischer        foreach my $target_file_row (@added_files)
529677600b0SAndre Fischer        {
530677600b0SAndre Fischer	    $installer::logger::Info->printf("    %s (%s)\n",
531677600b0SAndre Fischer		$target_file_row->GetValue("FileName"),
532677600b0SAndre Fischer		$target_file_row->GetValue("File"));
533677600b0SAndre Fischer
534677600b0SAndre Fischer            # Get target component for target file.
535677600b0SAndre Fischer            my $target_component = $target_file_row->GetValue('Component_');
536677600b0SAndre Fischer
537677600b0SAndre Fischer            # Check that the component is not part of the source components.
538677600b0SAndre Fischer            if (defined $source_component_map{$target_component})
539677600b0SAndre Fischer            {
540677600b0SAndre Fischer                push @new_files_with_existing_components, $target_file_row;
541677600b0SAndre Fischer            }
542677600b0SAndre Fischer        }
543677600b0SAndre Fischer
544677600b0SAndre Fischer        if (scalar @new_files_with_existing_components > 0)
545677600b0SAndre Fischer        {
546677600b0SAndre Fischer            $installer::logger::Info->printf(
547677600b0SAndre Fischer                "Error: %d new files have existing components (which must also be new)\n",
548677600b0SAndre Fischer                scalar @new_files_with_existing_components);
549677600b0SAndre Fischer            return 0;
550677600b0SAndre Fischer        }
551677600b0SAndre Fischer        else
552677600b0SAndre Fischer        {
553677600b0SAndre Fischer            $installer::logger::Info->printf(
554677600b0SAndre Fischer                "OK: all %d new files also have new components\n",
555677600b0SAndre Fischer		scalar @added_files);
556677600b0SAndre Fischer            return 1;
557677600b0SAndre Fischer        }
5589f91b7e3SAndre Fischer    }
5599f91b7e3SAndre Fischer    else
5609f91b7e3SAndre Fischer    {
5619f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: no files have been added\n");
5629f91b7e3SAndre Fischer        return 1;
5639f91b7e3SAndre Fischer    }
5649f91b7e3SAndre Fischer}
5659f91b7e3SAndre Fischer
5669f91b7e3SAndre Fischer
5679f91b7e3SAndre Fischer
5689f91b7e3SAndre Fischer
569677600b0SAndre Fischer=head2 CheckFeatureSets($source_msi, $target_msi)
570677600b0SAndre Fischer
571677600b0SAndre Fischer    Features must not be removed but can be added.
572677600b0SAndre Fischer    Parent features of new features also have to be new.
573677600b0SAndre Fischer
574677600b0SAndre Fischer=cut
575677600b0SAndre Fischersub CheckFeatureSets($$)
576677600b0SAndre Fischer{
577677600b0SAndre Fischer    my ($source_msi, $target_msi) = @_;
578677600b0SAndre Fischer
579677600b0SAndre Fischer    # Get the 'Feature' tables.
580677600b0SAndre Fischer    my $source_feature_table = $source_msi->GetTable("Feature");
581677600b0SAndre Fischer    my $target_feature_table = $target_msi->GetTable("Feature");
582677600b0SAndre Fischer
583677600b0SAndre Fischer    # Create data structures for fast lookup.
584677600b0SAndre Fischer    my %source_feature_map = map {$_->GetValue("Feature") => $_} @{$source_feature_table->GetAllRows()};
585677600b0SAndre Fischer    my %target_feature_map = map {$_->GetValue("Feature") => $_} @{$target_feature_table->GetAllRows()};
586677600b0SAndre Fischer
587677600b0SAndre Fischer    # Check that no feature has been removed.
588677600b0SAndre Fischer    my @removed_features = ();
589677600b0SAndre Fischer    foreach my $feature_name (keys %source_feature_map)
590677600b0SAndre Fischer    {
591677600b0SAndre Fischer        if ( ! defined $target_feature_map{$feature_name})
592677600b0SAndre Fischer        {
593677600b0SAndre Fischer            push @removed_features, $feature_name;
594677600b0SAndre Fischer        }
595677600b0SAndre Fischer    }
596677600b0SAndre Fischer    if (scalar @removed_features > 0)
597677600b0SAndre Fischer    {
598677600b0SAndre Fischer        # There are removed features.
599677600b0SAndre Fischer        $installer::logger::Info->printf(
600677600b0SAndre Fischer            "Error: %d features have been removed:\n",
601677600b0SAndre Fischer            scalar @removed_features);
602677600b0SAndre Fischer        $installer::logger::Info->printf("       %s\n", join(", ", @removed_features));
603677600b0SAndre Fischer        return 0;
604677600b0SAndre Fischer    }
605677600b0SAndre Fischer
606677600b0SAndre Fischer    # Check that added features belong to new parent features.
607677600b0SAndre Fischer    my @added_features = ();
608677600b0SAndre Fischer    foreach my $feature_name (keys %target_feature_map)
609677600b0SAndre Fischer    {
610677600b0SAndre Fischer        if ( ! defined $source_feature_map{$feature_name})
611677600b0SAndre Fischer        {
612677600b0SAndre Fischer            push @added_features, $feature_name;
613677600b0SAndre Fischer        }
614677600b0SAndre Fischer    }
615677600b0SAndre Fischer
616677600b0SAndre Fischer    if (scalar @added_features > 0)
617677600b0SAndre Fischer    {
618677600b0SAndre Fischer        $installer::logger::Info->printf("Warning: %d features have been addded\n", scalar @added_features);
619677600b0SAndre Fischer
620677600b0SAndre Fischer        my @new_features_with_existing_parents = ();
621677600b0SAndre Fischer        foreach my $new_feature (@added_features)
622677600b0SAndre Fischer        {
623677600b0SAndre Fischer            my $target_feature = $target_feature_map{$new_feature};
624677600b0SAndre Fischer            if (defined $source_feature_map{$target_feature->{'Feature_Parent'}})
625677600b0SAndre Fischer            {
626677600b0SAndre Fischer                push @new_features_with_existing_parents, $target_feature;
627677600b0SAndre Fischer            }
628677600b0SAndre Fischer        }
629677600b0SAndre Fischer
630677600b0SAndre Fischer        if (scalar @new_features_with_existing_parents > 0)
631677600b0SAndre Fischer        {
632677600b0SAndre Fischer            $installer::logger::Info->printf(
633677600b0SAndre Fischer                "Error: %d new features have existing parents (which also must be new)\n",
634677600b0SAndre Fischer                scalar @new_features_with_existing_parents);
635677600b0SAndre Fischer            return 0;
636677600b0SAndre Fischer        }
637677600b0SAndre Fischer        else
638677600b0SAndre Fischer        {
639677600b0SAndre Fischer            $installer::logger::Info->printf(
640677600b0SAndre Fischer                "OK: parents of all new features are also new\n");
641677600b0SAndre Fischer            return 1;
642677600b0SAndre Fischer        }
643677600b0SAndre Fischer    }
644677600b0SAndre Fischer
645677600b0SAndre Fischer    $installer::logger::Info->printf("OK: feature sets in source and target are compatible\n");
646677600b0SAndre Fischer    return 1;
647677600b0SAndre Fischer}
648677600b0SAndre Fischer
649677600b0SAndre Fischer
650677600b0SAndre Fischer
651677600b0SAndre Fischer
652677600b0SAndre Fischer=head2 CheckRemovedComponents($source_msi, $target_msi)
6539f91b7e3SAndre Fischer
6549f91b7e3SAndre Fischer    Components must not be removed but can be added.
6559f91b7e3SAndre Fischer    Features of added components have also to be new.
6569f91b7e3SAndre Fischer
6579f91b7e3SAndre Fischer=cut
658677600b0SAndre Fischersub CheckRemovedComponents ($$)
6599f91b7e3SAndre Fischer{
6609f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
6619f91b7e3SAndre Fischer
6629f91b7e3SAndre Fischer    # Get the 'Component' tables.
6639f91b7e3SAndre Fischer    my $source_component_table = $source_msi->GetTable("Component");
6649f91b7e3SAndre Fischer    my $target_component_table = $target_msi->GetTable("Component");
6659f91b7e3SAndre Fischer
6669f91b7e3SAndre Fischer    # Create data structures for fast lookup.
6679f91b7e3SAndre Fischer    my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
6689f91b7e3SAndre Fischer    my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
6699f91b7e3SAndre Fischer
6709f91b7e3SAndre Fischer    # Check that no component has been removed.
6719f91b7e3SAndre Fischer    my @removed_components = ();
6729f91b7e3SAndre Fischer    foreach my $componentname (keys %source_component_map)
6739f91b7e3SAndre Fischer    {
6749f91b7e3SAndre Fischer        if ( ! defined $target_component_map{$componentname})
6759f91b7e3SAndre Fischer        {
6769f91b7e3SAndre Fischer            push @removed_components, $componentname;
6779f91b7e3SAndre Fischer        }
6789f91b7e3SAndre Fischer    }
679677600b0SAndre Fischer    if (scalar @removed_components == 0)
680677600b0SAndre Fischer    {
681677600b0SAndre Fischer	$installer::logger::Info->printf("OK: no removed components\n");
682677600b0SAndre Fischer	return 1;
683677600b0SAndre Fischer    }
684677600b0SAndre Fischer    else
6859f91b7e3SAndre Fischer    {
6869f91b7e3SAndre Fischer        # There are removed components.
6879f91b7e3SAndre Fischer
6889f91b7e3SAndre Fischer        # Check if any of them is not a registry component.
6899f91b7e3SAndre Fischer        my $is_file_component_removed = 0;
6909f91b7e3SAndre Fischer        foreach my $componentname (@removed_components)
6919f91b7e3SAndre Fischer        {
6929f91b7e3SAndre Fischer            if ($componentname !~ /^registry/)
6939f91b7e3SAndre Fischer            {
6949f91b7e3SAndre Fischer                $is_file_component_removed = 1;
6959f91b7e3SAndre Fischer            }
6969f91b7e3SAndre Fischer        }
6979f91b7e3SAndre Fischer        if ($is_file_component_removed)
6989f91b7e3SAndre Fischer        {
6999f91b7e3SAndre Fischer            $installer::logger::Info->printf(
7009f91b7e3SAndre Fischer                "Error: %d components have been removed, some of them are file components:\n",
7019f91b7e3SAndre Fischer                scalar @removed_components);
7029f91b7e3SAndre Fischer            $installer::logger::Info->printf("       %s\n", join(", ", @removed_components));
7039f91b7e3SAndre Fischer            return 0;
7049f91b7e3SAndre Fischer        }
7059f91b7e3SAndre Fischer        else
7069f91b7e3SAndre Fischer        {
7079f91b7e3SAndre Fischer            $installer::logger::Info->printf(
7089f91b7e3SAndre Fischer                "Error: %d components have been removed, all of them are registry components:\n",
7099f91b7e3SAndre Fischer                scalar @removed_components);
7109f91b7e3SAndre Fischer            return 0;
7119f91b7e3SAndre Fischer        }
7129f91b7e3SAndre Fischer    }
713677600b0SAndre Fischer}
714677600b0SAndre Fischer
715677600b0SAndre Fischer
716677600b0SAndre Fischer
717677600b0SAndre Fischer
718677600b0SAndre Fischersub GetTableAndMap ($$$)
719677600b0SAndre Fischer{
720677600b0SAndre Fischer    my ($msi, $table_name, $index_column) = @_;
721677600b0SAndre Fischer
722677600b0SAndre Fischer    my $table = $msi->GetTable($table_name);
723677600b0SAndre Fischer    my %map = map {$_->GetValue($index_column) => $_} @{$table->GetAllRows()};
724677600b0SAndre Fischer
725677600b0SAndre Fischer    return ($table, \%map);
726677600b0SAndre Fischer}
727677600b0SAndre Fischer
728677600b0SAndre Fischer
729677600b0SAndre Fischer=head2 CheckAddedComponents($source_msi, $target_msi)
730677600b0SAndre Fischer
731677600b0SAndre Fischer    Components can be added.
732677600b0SAndre Fischer    Features of added components have also to be new.
733677600b0SAndre Fischer
734677600b0SAndre Fischer=cut
735677600b0SAndre Fischersub CheckAddedComponents ($$)
736677600b0SAndre Fischer{
737677600b0SAndre Fischer    my ($source_msi, $target_msi) = @_;
738677600b0SAndre Fischer
739677600b0SAndre Fischer    # Get the 'Component' tables and maps.
740677600b0SAndre Fischer    my ($source_component_table, $source_component_map)
741677600b0SAndre Fischer	= GetTableAndMap($source_msi, "Component", "Component");
742677600b0SAndre Fischer    my ($target_component_table, $target_component_map)
743677600b0SAndre Fischer	= GetTableAndMap($target_msi, "Component", "Component");
7449f91b7e3SAndre Fischer
7459f91b7e3SAndre Fischer    # Check that added components belong to new features.
7469f91b7e3SAndre Fischer    my @added_components = ();
747677600b0SAndre Fischer    foreach my $componentname (keys %$target_component_map)
7489f91b7e3SAndre Fischer    {
749677600b0SAndre Fischer        if ( ! defined $source_component_map->{$componentname})
7509f91b7e3SAndre Fischer        {
7519f91b7e3SAndre Fischer            push @added_components, $componentname;
7529f91b7e3SAndre Fischer        }
7539f91b7e3SAndre Fischer    }
7549f91b7e3SAndre Fischer
755677600b0SAndre Fischer    if (scalar @added_components == 0)
7569f91b7e3SAndre Fischer    {
757677600b0SAndre Fischer	$installer::logger::Info->printf("OK: no new components\n");
758677600b0SAndre Fischer	return 1;
759677600b0SAndre Fischer    }
760677600b0SAndre Fischer    else
761677600b0SAndre Fischer    {
762677600b0SAndre Fischer	$installer::logger::Info->printf(
763677600b0SAndre Fischer	    "Warning: %d components have been addded\n",
764677600b0SAndre Fischer	    scalar @added_components);
7659f91b7e3SAndre Fischer
766677600b0SAndre Fischer        # Check that the referencing features are also new.
767677600b0SAndre Fischer	my $target_feature_component_table = $target_msi->GetTable("FeatureComponents");
768677600b0SAndre Fischer
769677600b0SAndre Fischer	my $error = 0;
770677600b0SAndre Fischer        foreach my $component_name (@added_components)
7719f91b7e3SAndre Fischer        {
772677600b0SAndre Fischer	    my @feature_names = ();
773677600b0SAndre Fischer	    foreach my $feature_component_row (@{$target_feature_component_table->GetAllRows()})
774677600b0SAndre Fischer	    {
775677600b0SAndre Fischer		if ($feature_component_row->GetValue("Component_") eq $component_name)
776677600b0SAndre Fischer		{
777677600b0SAndre Fischer		    my $feature_name = $feature_component_row->GetValue("Feature_");
778677600b0SAndre Fischer		    push @feature_names, $feature_name;
779677600b0SAndre Fischer		}
780677600b0SAndre Fischer	    }
781677600b0SAndre Fischer	    if (scalar @feature_names == 0)
782677600b0SAndre Fischer	    {
783677600b0SAndre Fischer		$installer::logger::Info->printf("Error: no feature found for component '%s'\n", $component_name);
784677600b0SAndre Fischer		$error = 1;
785677600b0SAndre Fischer	    }
786677600b0SAndre Fischer	    else
787677600b0SAndre Fischer	    {
788677600b0SAndre Fischer		# Check that the referenced features are new and have new parents (if they have parents).
789677600b0SAndre Fischer		my ($source_feature_table, $source_feature_map)
790677600b0SAndre Fischer		    = GetTableAndMap($source_msi, "Feature", "Feature");
791677600b0SAndre Fischer		my ($target_feature_table, $target_feature_map)
792677600b0SAndre Fischer		    = GetTableAndMap($target_msi, "Feature", "Feature");
793677600b0SAndre Fischer		foreach my $feature_name (@feature_names)
794677600b0SAndre Fischer		{
795677600b0SAndre Fischer		    $installer::logger::Info->printf("    component '%s' -> feature '%s'\n",
796677600b0SAndre Fischer			$component_name,
797677600b0SAndre Fischer			$feature_name);
798677600b0SAndre Fischer		    my $source_feature_row = $source_feature_map->{$feature_name};
799677600b0SAndre Fischer		    if (defined $source_feature_row)
800677600b0SAndre Fischer		    {
801677600b0SAndre Fischer			$installer::logger::Info->printf("Warning(Error?): feature of new component is not new\n");
802677600b0SAndre Fischer			$error = 1;
803677600b0SAndre Fischer		    }
804677600b0SAndre Fischer		    else
805677600b0SAndre Fischer		    {
806677600b0SAndre Fischer			# Feature is new. Check that the parent feature is also new.
807677600b0SAndre Fischer			my $target_feature_row = $target_feature_map->{$feature_name};
808677600b0SAndre Fischer			my $parent_feature_name = $target_feature_row->GetValue("Feature_Parent");
809677600b0SAndre Fischer			if ($parent_feature_name ne "" && defined $source_feature_map->{$parent_feature_name})
810677600b0SAndre Fischer			{
811677600b0SAndre Fischer			    $installer::logger::Info->printf("Warning(Error?): parent feature of new component is not new\n");
812677600b0SAndre Fischer			    $error = 1;
813677600b0SAndre Fischer			}
814677600b0SAndre Fischer		    }
815677600b0SAndre Fischer		}
816677600b0SAndre Fischer	    }
817677600b0SAndre Fischer	}
818677600b0SAndre Fischer
819677600b0SAndre Fischer#	return !$error;
820677600b0SAndre Fischer	return 1;
8219f91b7e3SAndre Fischer    }
8229f91b7e3SAndre Fischer}
8239f91b7e3SAndre Fischer
8249f91b7e3SAndre Fischer
8259f91b7e3SAndre Fischer
8269f91b7e3SAndre Fischer
8279f91b7e3SAndre Fischer=head2 CheckComponent($source_msi, $target_msi)
8289f91b7e3SAndre Fischer
8299f91b7e3SAndre Fischer    In the 'Component' table the 'ComponentId' and 'Component' values
8309f91b7e3SAndre Fischer    for corresponding componts in the source and target release have
8319f91b7e3SAndre Fischer    to be identical.
8329f91b7e3SAndre Fischer
8339f91b7e3SAndre Fischer=cut
8349f91b7e3SAndre Fischersub CheckComponentValues($$$)
8359f91b7e3SAndre Fischer{
8369f91b7e3SAndre Fischer    my ($source_msi, $target_msi, $variables) = @_;
8379f91b7e3SAndre Fischer
8389f91b7e3SAndre Fischer    # Get the 'Component' tables.
8399f91b7e3SAndre Fischer    my $source_component_table = $source_msi->GetTable("Component");
8409f91b7e3SAndre Fischer    my $target_component_table = $target_msi->GetTable("Component");
8419f91b7e3SAndre Fischer
8429f91b7e3SAndre Fischer    # Create data structures for fast lookup.
8439f91b7e3SAndre Fischer    my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
8449f91b7e3SAndre Fischer    my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
8459f91b7e3SAndre Fischer
8469f91b7e3SAndre Fischer    my @differences = ();
8479f91b7e3SAndre Fischer    my $comparison_count = 0;
8489f91b7e3SAndre Fischer    while (my ($componentname, $source_component_row) = each %source_component_map)
8499f91b7e3SAndre Fischer    {
8509f91b7e3SAndre Fischer        my $target_component_row = $target_component_map{$componentname};
8519f91b7e3SAndre Fischer        if (defined $target_component_row)
8529f91b7e3SAndre Fischer        {
8539f91b7e3SAndre Fischer            ++$comparison_count;
8549f91b7e3SAndre Fischer            if ($source_component_row->GetValue("ComponentId") ne $target_component_row->GetValue("ComponentId"))
8559f91b7e3SAndre Fischer            {
8569f91b7e3SAndre Fischer                push @differences, [
8579f91b7e3SAndre Fischer                    $componentname,
8589f91b7e3SAndre Fischer                    $source_component_row->GetValue("ComponentId"),
8599f91b7e3SAndre Fischer                    $target_component_row->GetValue("ComponentId"),
8609f91b7e3SAndre Fischer                    $target_component_row->GetValue("Component"),
8619f91b7e3SAndre Fischer                ];
8629f91b7e3SAndre Fischer            }
8639f91b7e3SAndre Fischer        }
8649f91b7e3SAndre Fischer    }
8659f91b7e3SAndre Fischer
8669f91b7e3SAndre Fischer    if (scalar @differences > 0)
8679f91b7e3SAndre Fischer    {
8689f91b7e3SAndre Fischer        $installer::logger::Info->printf(
8699f91b7e3SAndre Fischer            "Error: there are %d components with different 'ComponentId' values after %d comparisons.\n",
8709f91b7e3SAndre Fischer            scalar @differences,
8719f91b7e3SAndre Fischer            $comparison_count);
8729f91b7e3SAndre Fischer        foreach my $item (@differences)
8739f91b7e3SAndre Fischer        {
8749f91b7e3SAndre Fischer            $installer::logger::Info->printf("%s  %s\n", $item->[1], $item->[2]);
8759f91b7e3SAndre Fischer        }
8769f91b7e3SAndre Fischer        return 0;
8779f91b7e3SAndre Fischer    }
8789f91b7e3SAndre Fischer    else
8799f91b7e3SAndre Fischer    {
8809f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: components in source and target are identical\n");
8819f91b7e3SAndre Fischer        return 1;
8829f91b7e3SAndre Fischer    }
8839f91b7e3SAndre Fischer}
8849f91b7e3SAndre Fischer
8859f91b7e3SAndre Fischer
8869f91b7e3SAndre Fischer
8879f91b7e3SAndre Fischer
8889f91b7e3SAndre Fischer=head2 CheckFileSequence($source_msi, $target_msi)
8899f91b7e3SAndre Fischer
8909f91b7e3SAndre Fischer    In the 'File' table the 'Sequence' numbers for corresponding files has to be identical.
8919f91b7e3SAndre Fischer
8929f91b7e3SAndre Fischer=cut
8939f91b7e3SAndre Fischersub CheckFileSequence($$)
8949f91b7e3SAndre Fischer{
8959f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
8969f91b7e3SAndre Fischer
8979f91b7e3SAndre Fischer    # Get the 'File' tables.
8989f91b7e3SAndre Fischer    my $source_file_table = $source_msi->GetTable("File");
8999f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
9009f91b7e3SAndre Fischer
9019f91b7e3SAndre Fischer    # Create temporary data structures for fast access.
9029f91b7e3SAndre Fischer    my %source_file_map = map {$_->GetValue("File") => $_} @{$source_file_table->GetAllRows()};
9039f91b7e3SAndre Fischer    my %target_file_map = map {$_->GetValue("File") => $_} @{$target_file_table->GetAllRows()};
9049f91b7e3SAndre Fischer
9059f91b7e3SAndre Fischer    # Search files with mismatching sequence numbers.
9069f91b7e3SAndre Fischer    my @mismatching_files = ();
9079f91b7e3SAndre Fischer    while (my ($uniquename,$source_file_row) = each %source_file_map)
9089f91b7e3SAndre Fischer    {
9099f91b7e3SAndre Fischer        my $target_file_row = $target_file_map{$uniquename};
9109f91b7e3SAndre Fischer        if (defined $target_file_row)
9119f91b7e3SAndre Fischer        {
9129f91b7e3SAndre Fischer            if ($source_file_row->GetValue('Sequence') ne $target_file_row->GetValue('Sequence'))
9139f91b7e3SAndre Fischer            {
9149f91b7e3SAndre Fischer                push @mismatching_files, [
9159f91b7e3SAndre Fischer                    $uniquename,
9169f91b7e3SAndre Fischer                    $source_file_row,
9179f91b7e3SAndre Fischer                    $target_file_row
9189f91b7e3SAndre Fischer                ];
9199f91b7e3SAndre Fischer            }
9209f91b7e3SAndre Fischer        }
9219f91b7e3SAndre Fischer    }
9229f91b7e3SAndre Fischer
9239f91b7e3SAndre Fischer    if (scalar @mismatching_files > 0)
9249f91b7e3SAndre Fischer    {
9259f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: there are %d files with mismatching 'Sequence' numbers\n",
9269f91b7e3SAndre Fischer            scalar @mismatching_files);
9279f91b7e3SAndre Fischer        foreach my $item (@mismatching_files)
9289f91b7e3SAndre Fischer        {
9299f91b7e3SAndre Fischer            $installer::logger::Info->printf("    %s: %d != %d\n",
9309f91b7e3SAndre Fischer                $item->[0],
9319f91b7e3SAndre Fischer                $item->[1]->GetValue("Sequence"),
9329f91b7e3SAndre Fischer                $item->[2]->GetValue("Sequence"));
9339f91b7e3SAndre Fischer        }
9349f91b7e3SAndre Fischer        return 0;
9359f91b7e3SAndre Fischer    }
9369f91b7e3SAndre Fischer    else
9379f91b7e3SAndre Fischer    {
9389f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: all files have matching 'Sequence' numbers\n");
9399f91b7e3SAndre Fischer        return 1;
9409f91b7e3SAndre Fischer    }
9419f91b7e3SAndre Fischer}
9429f91b7e3SAndre Fischer
9439f91b7e3SAndre Fischer
9449f91b7e3SAndre Fischer
9459f91b7e3SAndre Fischer
9469f91b7e3SAndre Fischer=head2 CheckFileSequenceUnique($source_msi, $target_msi)
9479f91b7e3SAndre Fischer
9489f91b7e3SAndre Fischer    In the 'File' table the 'Sequence' values have to be unique.
9499f91b7e3SAndre Fischer
9509f91b7e3SAndre Fischer=cut
9519f91b7e3SAndre Fischersub CheckFileSequenceUnique($$)
9529f91b7e3SAndre Fischer{
9539f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
9549f91b7e3SAndre Fischer
9559f91b7e3SAndre Fischer    # Get the 'File' tables.
9569f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
9579f91b7e3SAndre Fischer
9589f91b7e3SAndre Fischer    my %sequence_numbers = ();
9599f91b7e3SAndre Fischer    my $collision_count = 0;
9609f91b7e3SAndre Fischer    foreach my $row (@{$target_file_table->GetAllRows()})
9619f91b7e3SAndre Fischer    {
9629f91b7e3SAndre Fischer        my $sequence_number = $row->GetValue("Sequence");
9639f91b7e3SAndre Fischer        if (defined $sequence_numbers{$sequence_number})
9649f91b7e3SAndre Fischer        {
9659f91b7e3SAndre Fischer            ++$collision_count;
9669f91b7e3SAndre Fischer        }
9679f91b7e3SAndre Fischer        else
9689f91b7e3SAndre Fischer        {
9699f91b7e3SAndre Fischer            $sequence_numbers{$sequence_number} = 1;
9709f91b7e3SAndre Fischer        }
9719f91b7e3SAndre Fischer    }
9729f91b7e3SAndre Fischer
9739f91b7e3SAndre Fischer    if ($collision_count > 0)
9749f91b7e3SAndre Fischer    {
9759f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: there are %d collisions ofn the sequence numbers\n",
9769f91b7e3SAndre Fischer            $collision_count);
9779f91b7e3SAndre Fischer        return 0;
9789f91b7e3SAndre Fischer    }
9799f91b7e3SAndre Fischer    else
9809f91b7e3SAndre Fischer    {
9819f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: sequence numbers are unique\n");
9829f91b7e3SAndre Fischer        return 1;
9839f91b7e3SAndre Fischer    }
9849f91b7e3SAndre Fischer}
9859f91b7e3SAndre Fischer
9869f91b7e3SAndre Fischer
9879f91b7e3SAndre Fischer
9889f91b7e3SAndre Fischer
9899f91b7e3SAndre Fischer=head2 CheckFileSequenceHoles ($target_msi)
9909f91b7e3SAndre Fischer
9919f91b7e3SAndre Fischer    Check the sequence numbers of the target msi if the n files use numbers 1..n or if there are holes.
9929f91b7e3SAndre Fischer    Holes are reported as warnings.
9939f91b7e3SAndre Fischer
9949f91b7e3SAndre Fischer=cut
9959f91b7e3SAndre Fischersub CheckFileSequenceHoles ($$)
9969f91b7e3SAndre Fischer{
9979f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
9989f91b7e3SAndre Fischer
9999f91b7e3SAndre Fischer    my $target_file_table = $target_msi->GetTable("File");
10009f91b7e3SAndre Fischer    my %sequence_numbers = map {$_->GetValue("Sequence") => $_} @{$target_file_table->GetAllRows()};
10019f91b7e3SAndre Fischer    my @sorted_sequence_numbers = sort {$a <=> $b} keys %sequence_numbers;
10029f91b7e3SAndre Fischer    my $expected_next_sequence_number = 1;
10039f91b7e3SAndre Fischer    my @holes = ();
10049f91b7e3SAndre Fischer    foreach my $sequence_number (@sorted_sequence_numbers)
10059f91b7e3SAndre Fischer    {
10069f91b7e3SAndre Fischer        if ($sequence_number != $expected_next_sequence_number)
10079f91b7e3SAndre Fischer        {
10089f91b7e3SAndre Fischer            push @holes, [$expected_next_sequence_number, $sequence_number-1];
10099f91b7e3SAndre Fischer        }
10109f91b7e3SAndre Fischer        $expected_next_sequence_number = $sequence_number+1;
10119f91b7e3SAndre Fischer    }
10129f91b7e3SAndre Fischer    if (scalar @holes > 0)
10139f91b7e3SAndre Fischer    {
10149f91b7e3SAndre Fischer        $installer::logger::Info->printf("Warning: sequence numbers have %d holes\n");
10159f91b7e3SAndre Fischer        foreach my $hole (@holes)
10169f91b7e3SAndre Fischer        {
10179f91b7e3SAndre Fischer            if ($hole->[0] != $hole->[1])
10189f91b7e3SAndre Fischer            {
10199f91b7e3SAndre Fischer                $installer::logger::Info->printf("    %d\n", $hole->[0]);
10209f91b7e3SAndre Fischer            }
10219f91b7e3SAndre Fischer            else
10229f91b7e3SAndre Fischer            {
10239f91b7e3SAndre Fischer                $installer::logger::Info->printf("    %d -> %d\n", $hole->[0], $hole->[1]);
10249f91b7e3SAndre Fischer            }
10259f91b7e3SAndre Fischer        }
10269f91b7e3SAndre Fischer    }
10279f91b7e3SAndre Fischer    else
10289f91b7e3SAndre Fischer    {
10299f91b7e3SAndre Fischer        $installer::logger::Info->printf("OK: there are no holes in the sequence numbers\n");
10309f91b7e3SAndre Fischer    }
10319f91b7e3SAndre Fischer    return 1;
10329f91b7e3SAndre Fischer}
10339f91b7e3SAndre Fischer
10349f91b7e3SAndre Fischer
10359f91b7e3SAndre Fischer
10369f91b7e3SAndre Fischer
10379f91b7e3SAndre Fischer=head2 CheckRegistryItems($source_msi, $target_msi)
10389f91b7e3SAndre Fischer
10399f91b7e3SAndre Fischer    In the 'Registry' table the 'Component_' and 'Key' values must not
10409f91b7e3SAndre Fischer    depend on the version number (beyond the unchanging major
10419f91b7e3SAndre Fischer    version).
10429f91b7e3SAndre Fischer
10439f91b7e3SAndre Fischer    'Value' values must only depend on the major version number to
10449f91b7e3SAndre Fischer    avoid duplicate entries in the start menu.
10459f91b7e3SAndre Fischer
10469f91b7e3SAndre Fischer    Violations are reported as warnings for now.
10479f91b7e3SAndre Fischer
10489f91b7e3SAndre Fischer=cut
10499f91b7e3SAndre Fischersub CheckRegistryItems($$$)
10509f91b7e3SAndre Fischer{
10519f91b7e3SAndre Fischer    my ($source_msi, $target_msi, $product_name) = @_;
10529f91b7e3SAndre Fischer
10539f91b7e3SAndre Fischer    # Get the registry tables.
10549f91b7e3SAndre Fischer    my $source_registry_table = $source_msi->GetTable("Registry");
10559f91b7e3SAndre Fischer    my $target_registry_table = $target_msi->GetTable("Registry");
10569f91b7e3SAndre Fischer
10579f91b7e3SAndre Fischer    my $registry_index = $target_registry_table->GetColumnIndex("Registry");
10589f91b7e3SAndre Fischer    my $component_index = $target_registry_table->GetColumnIndex("Component_");
10599f91b7e3SAndre Fischer
10609f91b7e3SAndre Fischer    # Create temporary data structures for fast access.
10619f91b7e3SAndre Fischer    my %source_registry_map = map {$_->GetValue($registry_index) => $_} @{$source_registry_table->GetAllRows()};
10629f91b7e3SAndre Fischer    my %target_registry_map = map {$_->GetValue($registry_index) => $_} @{$target_registry_table->GetAllRows()};
10639f91b7e3SAndre Fischer
10649f91b7e3SAndre Fischer    # Prepare version numbers to search.
10659f91b7e3SAndre Fischer    my $source_version_number = $source_msi->{'version'};
10669f91b7e3SAndre Fischer    my $source_version_nodots = installer::patch::Version::ArrayToNoDotName(
10679f91b7e3SAndre Fischer        installer::patch::Version::StringToNumberArray($source_version_number));
10689f91b7e3SAndre Fischer    my $source_component_pattern = lc($product_name).$source_version_nodots;
10699f91b7e3SAndre Fischer    my $target_version_number = $target_msi->{'version'};
10709f91b7e3SAndre Fischer    my $target_version_nodots = installer::patch::Version::ArrayToNoDotName(
10719f91b7e3SAndre Fischer        installer::patch::Version::StringToNumberArray($target_version_number));
10729f91b7e3SAndre Fischer    my $target_component_pattern = lc($product_name).$target_version_nodots;
10739f91b7e3SAndre Fischer
10749f91b7e3SAndre Fischer    foreach my $source_row (values %source_registry_map)
10759f91b7e3SAndre Fischer    {
10769f91b7e3SAndre Fischer        my $target_row = $target_registry_map{$source_row->GetValue($registry_index)};
10779f91b7e3SAndre Fischer        if ( ! defined $target_row)
10789f91b7e3SAndre Fischer        {
10799f91b7e3SAndre Fischer            $installer::logger::Info->printf("Error: sets of registry entries differs\n");
10809f91b7e3SAndre Fischer            return 1;
10819f91b7e3SAndre Fischer        }
10829f91b7e3SAndre Fischer
10839f91b7e3SAndre Fischer        my $source_component_name = $source_row->GetValue($component_index);
10849f91b7e3SAndre Fischer        my $target_component_name = $source_row->GetValue($component_index);
10859f91b7e3SAndre Fischer
10869f91b7e3SAndre Fischer    }
10879f91b7e3SAndre Fischer
10889f91b7e3SAndre Fischer    $installer::logger::Info->printf("OK: registry items are OK\n");
10899f91b7e3SAndre Fischer    return 1;
10909f91b7e3SAndre Fischer}
10919f91b7e3SAndre Fischer
10929f91b7e3SAndre Fischer
10939f91b7e3SAndre Fischer
10949f91b7e3SAndre Fischer
10959f91b7e3SAndre Fischer=head2
10969f91b7e3SAndre Fischer
10979f91b7e3SAndre Fischer    Component->KeyPath must not change. (see component.pm/get_component_keypath)
10989f91b7e3SAndre Fischer
10999f91b7e3SAndre Fischer=cut
11009f91b7e3SAndre Fischersub CheckComponentKeyPath ($$)
11019f91b7e3SAndre Fischer{
11029f91b7e3SAndre Fischer    my ($source_msi, $target_msi) = @_;
11039f91b7e3SAndre Fischer
11049f91b7e3SAndre Fischer    # Get the registry tables.
11059f91b7e3SAndre Fischer    my $source_component_table = $source_msi->GetTable("Component");
11069f91b7e3SAndre Fischer    my $target_component_table = $target_msi->GetTable("Component");
11079f91b7e3SAndre Fischer
11089f91b7e3SAndre Fischer    # Create temporary data structures for fast access.
11099f91b7e3SAndre Fischer    my %source_component_map = map {$_->GetValue("Component") => $_} @{$source_component_table->GetAllRows()};
11109f91b7e3SAndre Fischer    my %target_component_map = map {$_->GetValue("Component") => $_} @{$target_component_table->GetAllRows()};
11119f91b7e3SAndre Fischer
11129f91b7e3SAndre Fischer    my @mismatches = ();
11139f91b7e3SAndre Fischer    while (my ($componentname, $source_component_row) = each %source_component_map)
11149f91b7e3SAndre Fischer    {
11159f91b7e3SAndre Fischer        my $target_component_row = $target_component_map{$componentname};
11169f91b7e3SAndre Fischer        if (defined $target_component_row)
11179f91b7e3SAndre Fischer        {
11189f91b7e3SAndre Fischer            my $source_keypath = $source_component_row->GetValue("KeyPath");
11199f91b7e3SAndre Fischer            my $target_keypath = $target_component_row->GetValue("KeyPath");
11209f91b7e3SAndre Fischer            if ($source_keypath ne $target_keypath)
11219f91b7e3SAndre Fischer            {
11229f91b7e3SAndre Fischer                push @mismatches, [$componentname, $source_keypath, $target_keypath];
11239f91b7e3SAndre Fischer            }
11249f91b7e3SAndre Fischer        }
11259f91b7e3SAndre Fischer    }
11269f91b7e3SAndre Fischer
11279f91b7e3SAndre Fischer    if (scalar @mismatches > 0)
11289f91b7e3SAndre Fischer    {
11299f91b7e3SAndre Fischer        $installer::logger::Info->printf(
11309f91b7e3SAndre Fischer            "Error: there are %d mismatches in the 'KeyPath' column of the 'Component' table\n",
11319f91b7e3SAndre Fischer            scalar @mismatches);
11329f91b7e3SAndre Fischer
11339f91b7e3SAndre Fischer        foreach my $item (@mismatches)
11349f91b7e3SAndre Fischer        {
11359f91b7e3SAndre Fischer            $installer::logger::Info->printf(
11369f91b7e3SAndre Fischer                "    %s: %s != %s\n",
11379f91b7e3SAndre Fischer                $item->[0],
11389f91b7e3SAndre Fischer                $item->[1],
11399f91b7e3SAndre Fischer                $item->[2]);
11409f91b7e3SAndre Fischer        }
11419f91b7e3SAndre Fischer
11429f91b7e3SAndre Fischer        return 0;
11439f91b7e3SAndre Fischer    }
11449f91b7e3SAndre Fischer    else
11459f91b7e3SAndre Fischer    {
11469f91b7e3SAndre Fischer        $installer::logger::Info->printf(
11479f91b7e3SAndre Fischer            "OK: no mismatches in the 'KeyPath' column of the 'Component' table\n");
11489f91b7e3SAndre Fischer        return 1;
11499f91b7e3SAndre Fischer    }
11509f91b7e3SAndre Fischer}
11519f91b7e3SAndre Fischer
11529f91b7e3SAndre Fischer
11539f91b7e3SAndre Fischer
11549f91b7e3SAndre Fischer
1155677600b0SAndre Fischersub GetMissingReferences ($$$$$)
1156677600b0SAndre Fischer{
1157677600b0SAndre Fischer    my ($table, $key, $map, $what, $report_key) = @_;
1158677600b0SAndre Fischer
1159677600b0SAndre Fischer    my @missing_references = ();
1160677600b0SAndre Fischer
1161677600b0SAndre Fischer    foreach my $row (@{$table->GetAllRows()})
1162677600b0SAndre Fischer    {
1163677600b0SAndre Fischer        my $value = $row->GetValue($key);
1164677600b0SAndre Fischer        if ($value ne "" && ! defined $map->{$value})
1165677600b0SAndre Fischer        {
1166677600b0SAndre Fischer            push @missing_references, [$what, $row->GetValue($report_key), $value];
1167677600b0SAndre Fischer        }
1168677600b0SAndre Fischer    }
1169677600b0SAndre Fischer
1170677600b0SAndre Fischer    return @missing_references;
1171677600b0SAndre Fischer}
1172677600b0SAndre Fischer
1173677600b0SAndre Fischer
1174677600b0SAndre Fischer
1175677600b0SAndre Fischer
1176677600b0SAndre Fischer=head CheckAllReferences ($msi)
1177677600b0SAndre Fischer
1178677600b0SAndre Fischer    Check references from files and registry entries to components,
1179677600b0SAndre Fischer    from components to features, and between features.
1180677600b0SAndre Fischer
1181677600b0SAndre Fischer=cut
1182677600b0SAndre Fischer
1183677600b0SAndre Fischersub CheckAllReferences ($)
1184677600b0SAndre Fischer{
1185677600b0SAndre Fischer    my ($msi) = @_;
1186677600b0SAndre Fischer
1187677600b0SAndre Fischer    # Set up tables and maps for easy iteration and fast lookups.
1188677600b0SAndre Fischer
1189677600b0SAndre Fischer    my $feature_table = $msi->GetTable("Feature");
1190677600b0SAndre Fischer    my $component_table = $msi->GetTable("Component");
1191677600b0SAndre Fischer    my $feature_component_table = $msi->GetTable("FeatureComponents");
1192677600b0SAndre Fischer    my $file_table = $msi->GetTable("File");
1193677600b0SAndre Fischer    my $registry_table = $msi->GetTable("Registry");
1194677600b0SAndre Fischer    my $directory_table = $msi->GetTable("Directory");
1195677600b0SAndre Fischer
1196677600b0SAndre Fischer    my %feature_map = map {$_->GetValue("Feature") => $_} @{$feature_table->GetAllRows()};
1197677600b0SAndre Fischer    my %component_map = map {$_->GetValue("Component") => $_} @{$component_table->GetAllRows()};
1198677600b0SAndre Fischer    my %directory_map = map {$_->GetValue("Directory") => $_} @{$directory_table->GetAllRows()};
1199677600b0SAndre Fischer
1200677600b0SAndre Fischer    my @missing_references = ();
1201677600b0SAndre Fischer
1202677600b0SAndre Fischer    # Check references from files and registry entries to components.
1203677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1204677600b0SAndre Fischer        $file_table,
1205677600b0SAndre Fischer        "Component_",
1206677600b0SAndre Fischer        \%component_map,
1207677600b0SAndre Fischer        "file->component",
1208677600b0SAndre Fischer        "File");
1209677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1210677600b0SAndre Fischer        $registry_table,
1211677600b0SAndre Fischer        "Component_",
1212677600b0SAndre Fischer        \%component_map,
1213677600b0SAndre Fischer        "registry->component",
1214677600b0SAndre Fischer        "Registry");
1215677600b0SAndre Fischer
1216677600b0SAndre Fischer    # Check references between features and components.
1217677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1218677600b0SAndre Fischer        $feature_component_table,
1219677600b0SAndre Fischer        "Feature_",
1220677600b0SAndre Fischer        \%feature_map,
1221677600b0SAndre Fischer        "component->feature",
1222677600b0SAndre Fischer        "Component_");
1223677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1224677600b0SAndre Fischer        $feature_component_table,
1225677600b0SAndre Fischer        "Component_",
1226677600b0SAndre Fischer        \%component_map,
1227677600b0SAndre Fischer        "feature->component",
1228677600b0SAndre Fischer        "Feature_");
1229677600b0SAndre Fischer
1230677600b0SAndre Fischer    # Check references between features.
1231677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1232677600b0SAndre Fischer        $feature_table,
1233677600b0SAndre Fischer        'Feature_Parent',
1234677600b0SAndre Fischer        \%feature_map,
1235677600b0SAndre Fischer        "feature->feature",
1236677600b0SAndre Fischer        'Feature');
1237677600b0SAndre Fischer
1238677600b0SAndre Fischer    # Check references between directories.
1239677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1240677600b0SAndre Fischer        $directory_table,
1241677600b0SAndre Fischer        'Directory_Parent',
1242677600b0SAndre Fischer        \%directory_map,
1243677600b0SAndre Fischer        "directory->directory",
1244677600b0SAndre Fischer        'Directory');
1245677600b0SAndre Fischer
1246677600b0SAndre Fischer    # Check references from components to directories.
1247677600b0SAndre Fischer    push @missing_references, GetMissingReferences(
1248677600b0SAndre Fischer        $component_table,
1249677600b0SAndre Fischer        'Directory_',
1250677600b0SAndre Fischer        \%directory_map,
1251677600b0SAndre Fischer        "component->directory",
1252677600b0SAndre Fischer        'Component');
1253677600b0SAndre Fischer
1254677600b0SAndre Fischer    # Check references from components to files (via the .
1255677600b0SAndre Fischer
1256677600b0SAndre Fischer    # Report the result.
1257677600b0SAndre Fischer    if (scalar @missing_references > 0)
1258677600b0SAndre Fischer    {
1259677600b0SAndre Fischer        $installer::logger::Info->printf("Error: there are %d missing references\n", scalar @missing_references);
1260677600b0SAndre Fischer        foreach my $reference (@missing_references)
1261677600b0SAndre Fischer        {
1262677600b0SAndre Fischer            $installer::logger::Info->printf("    %s : %s -> %s\n",
1263677600b0SAndre Fischer                $reference->[0],
1264677600b0SAndre Fischer                $reference->[1],
1265677600b0SAndre Fischer                $reference->[2]);
1266677600b0SAndre Fischer        }
1267677600b0SAndre Fischer        return 0;
1268677600b0SAndre Fischer    }
1269677600b0SAndre Fischer    else
1270677600b0SAndre Fischer    {
1271677600b0SAndre Fischer        $installer::logger::Info->printf("OK: all references are OK\n");
1272677600b0SAndre Fischer        return 1;
1273677600b0SAndre Fischer
1274677600b0SAndre Fischer    }
1275677600b0SAndre Fischer}
1276677600b0SAndre Fischer
1277677600b0SAndre Fischer
1278677600b0SAndre Fischer
1279677600b0SAndre Fischer
12809f91b7e3SAndre Fischersub Check ($$$$)
12819f91b7e3SAndre Fischer{
12829f91b7e3SAndre Fischer    my ($source_msi, $target_msi, $variables, $product_name) = @_;
12839f91b7e3SAndre Fischer
12849f91b7e3SAndre Fischer    $installer::logger::Info->printf("checking if source and target releases are compatable\n");
12859f91b7e3SAndre Fischer    $installer::logger::Info->increase_indentation();
12869f91b7e3SAndre Fischer
12879f91b7e3SAndre Fischer    my $result = 1;
12889f91b7e3SAndre Fischer
1289677600b0SAndre Fischer    # Using &= below to avoid lazy evaluation.  Even if there are errors, all checks shall be run.
1290677600b0SAndre Fischer    $result &= CheckUpgradeCode($source_msi, $target_msi);
1291677600b0SAndre Fischer    $result &= CheckProductCode($source_msi, $target_msi);
1292677600b0SAndre Fischer    $result &= CheckBuildIdCode($source_msi, $target_msi);
1293677600b0SAndre Fischer    $result &= CheckProductName($source_msi, $target_msi);
1294677600b0SAndre Fischer    $result &= CheckRemovedFiles($source_msi, $target_msi);
1295677600b0SAndre Fischer    $result &= CheckNewFiles($source_msi, $target_msi);
1296677600b0SAndre Fischer    $result &= CheckFeatureSets($source_msi, $target_msi);
1297677600b0SAndre Fischer    $result &= CheckRemovedComponents($source_msi, $target_msi);
1298677600b0SAndre Fischer    $result &= CheckAddedComponents($source_msi, $target_msi);
1299677600b0SAndre Fischer    $result &= CheckComponentValues($source_msi, $target_msi, $variables);
1300677600b0SAndre Fischer    $result &= CheckFileSequence($source_msi, $target_msi);
1301677600b0SAndre Fischer    $result &= CheckFileSequenceUnique($source_msi, $target_msi);
1302677600b0SAndre Fischer    $result &= CheckFileSequenceHoles($source_msi, $target_msi);
1303677600b0SAndre Fischer    $result &= CheckRegistryItems($source_msi, $target_msi, $product_name);
1304677600b0SAndre Fischer    $result &= CheckComponentKeyPath($source_msi, $target_msi);
1305677600b0SAndre Fischer    $result &= CheckAllReferences($target_msi);
13069f91b7e3SAndre Fischer
13079f91b7e3SAndre Fischer    $installer::logger::Info->decrease_indentation();
13089f91b7e3SAndre Fischer
1309677600b0SAndre Fischer    if ($result)
1310677600b0SAndre Fischer    {
1311677600b0SAndre Fischer        $installer::logger::Info->printf("OK: Source and target releases are compatible.\n");
1312677600b0SAndre Fischer    }
1313677600b0SAndre Fischer    else
1314677600b0SAndre Fischer    {
1315677600b0SAndre Fischer        $installer::logger::Info->printf("Error: Source and target releases are not compatible.\n");
1316677600b0SAndre Fischer        $installer::logger::Info->printf("       => Can not create patch.\n");
1317677600b0SAndre Fischer        $installer::logger::Info->printf("       Did you create the target installation set with 'release=t' ?\n");
1318677600b0SAndre Fischer    }
1319677600b0SAndre Fischer
13209f91b7e3SAndre Fischer    return $result;
13219f91b7e3SAndre Fischer}
13229f91b7e3SAndre Fischer
13239f91b7e3SAndre Fischer
13249f91b7e3SAndre Fischer
13259f91b7e3SAndre Fischer
13269f91b7e3SAndre Fischer=head2 FindPcpTemplate ()
13279f91b7e3SAndre Fischer
13289f91b7e3SAndre Fischer    The template.pcp file is part of the Windows SDK.
13299f91b7e3SAndre Fischer
13309f91b7e3SAndre Fischer=cut
13319f91b7e3SAndre Fischersub FindPcpTemplate ()
13329f91b7e3SAndre Fischer{
13339f91b7e3SAndre Fischer    my $psdk_home = $ENV{'PSDK_HOME'};
13349f91b7e3SAndre Fischer    if ( ! defined $psdk_home)
13359f91b7e3SAndre Fischer    {
13369f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: the PSDK_HOME environment variable is not set.\n");
13379f91b7e3SAndre Fischer        $installer::logger::Info->printf("       did you load the AOO build environment?\n");
13389f91b7e3SAndre Fischer        $installer::logger::Info->printf("       you may want to use the --with-psdk-home configure option\n");
13399f91b7e3SAndre Fischer        return undef;
13409f91b7e3SAndre Fischer    }
13419f91b7e3SAndre Fischer    if ( ! -d $psdk_home)
13429f91b7e3SAndre Fischer    {
13439f91b7e3SAndre Fischer        $installer::logger::Info->printf(
13449f91b7e3SAndre Fischer            "Error: the PSDK_HOME environment variable does not point to a valid directory: %s\n",
13459f91b7e3SAndre Fischer            $psdk_home);
13469f91b7e3SAndre Fischer        return undef;
13479f91b7e3SAndre Fischer    }
13489f91b7e3SAndre Fischer
13499f91b7e3SAndre Fischer    my $schema_path = File::Spec->catfile($psdk_home, "Bin", "msitools", "Schemas", "MSI");
13509f91b7e3SAndre Fischer    if (  ! -d $schema_path)
13519f91b7e3SAndre Fischer    {
13529f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: Can not locate the msi template folder in the Windows SDK\n");
13539f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $schema_path);
13549f91b7e3SAndre Fischer        $installer::logger::Info->printf("       Is the Windows SDK properly installed?\n");
13559f91b7e3SAndre Fischer        return undef;
13569f91b7e3SAndre Fischer    }
13579f91b7e3SAndre Fischer
13589f91b7e3SAndre Fischer    my $schema_filename = File::Spec->catfile($schema_path, "template.pcp");
13599f91b7e3SAndre Fischer    if (  ! -f $schema_filename)
13609f91b7e3SAndre Fischer    {
13619f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: Can not locate the pcp template at\n");
13629f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $schema_filename);
13639f91b7e3SAndre Fischer        $installer::logger::Info->printf("       Is the Windows SDK properly installed?\n");
13649f91b7e3SAndre Fischer        return undef;
13659f91b7e3SAndre Fischer    }
13669f91b7e3SAndre Fischer
13679f91b7e3SAndre Fischer    return $schema_filename;
13689f91b7e3SAndre Fischer}
13699f91b7e3SAndre Fischer
13709f91b7e3SAndre Fischer
13719f91b7e3SAndre Fischer
13729f91b7e3SAndre Fischer
13739f91b7e3SAndre Fischersub SetupPcpPatchMetadataTable ($$$)
13749f91b7e3SAndre Fischer{
13759f91b7e3SAndre Fischer    my ($pcp, $source_msi, $target_msi) = @_;
13769f91b7e3SAndre Fischer
13779f91b7e3SAndre Fischer    # Determine values for eg product name and source and new version.
13789f91b7e3SAndre Fischer    my $source_version = $source_msi->{'version'};
13799f91b7e3SAndre Fischer    my $target_version = $target_msi->{'version'};
13809f91b7e3SAndre Fischer
13819f91b7e3SAndre Fischer    my $property_table = $target_msi->GetTable("Property");
13829f91b7e3SAndre Fischer    my $display_product_name = $property_table->GetValue("Property", "DEFINEDPRODUCT", "Value");
13839f91b7e3SAndre Fischer
13849f91b7e3SAndre Fischer    # Set table.
13859f91b7e3SAndre Fischer    my $table = $pcp->GetTable("PatchMetadata");
13869f91b7e3SAndre Fischer    $table->SetRow(
13879f91b7e3SAndre Fischer        "Company", "",
13889f91b7e3SAndre Fischer        "*Property", "Description",
13899f91b7e3SAndre Fischer        "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version)
13909f91b7e3SAndre Fischer        );
13919f91b7e3SAndre Fischer    $table->SetRow(
13929f91b7e3SAndre Fischer        "Company", "",
13939f91b7e3SAndre Fischer        "*Property", "DisplayName",
13949f91b7e3SAndre Fischer        "Value", sprintf("Update of %s from %s to %s", $display_product_name, $source_version, $target_version)
13959f91b7e3SAndre Fischer        );
13969f91b7e3SAndre Fischer    $table->SetRow(
13979f91b7e3SAndre Fischer        "Company", "",
13989f91b7e3SAndre Fischer        "*Property", "ManufacturerName",
13999f91b7e3SAndre Fischer        "Value", $property_table->GetValue("Property", "Manufacturer", "Value"),
14009f91b7e3SAndre Fischer        );
14019f91b7e3SAndre Fischer    $table->SetRow(
14029f91b7e3SAndre Fischer        "Company", "",
14039f91b7e3SAndre Fischer        "*Property", "MoreInfoURL",
14049f91b7e3SAndre Fischer        "Value", $property_table->GetValue("Property", "ARPURLINFOABOUT", "Value")
14059f91b7e3SAndre Fischer        );
14069f91b7e3SAndre Fischer    $table->SetRow(
14079f91b7e3SAndre Fischer        "Company", "",
14089f91b7e3SAndre Fischer        "*Property", "TargetProductName",
14099f91b7e3SAndre Fischer        "Value", $property_table->GetValue("Property", "ProductName", "Value")
14109f91b7e3SAndre Fischer        );
14119f91b7e3SAndre Fischer    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time);
14129f91b7e3SAndre Fischer
14139f91b7e3SAndre Fischer    $table->SetRow(
14149f91b7e3SAndre Fischer        "Company", "",
14159f91b7e3SAndre Fischer        "*Property", "CreationTimeUTC",
14169f91b7e3SAndre Fischer        "Value", sprintf("%d/%d/%d %d:%02d", $mon+1,$mday,$year+1900,$hour,$min)
14179f91b7e3SAndre Fischer        );
14189f91b7e3SAndre Fischer}
14199f91b7e3SAndre Fischer
14209f91b7e3SAndre Fischer
14219f91b7e3SAndre Fischer
14229f91b7e3SAndre Fischer
14239f91b7e3SAndre Fischersub SetupPropertiesTable ($$)
14249f91b7e3SAndre Fischer{
14259f91b7e3SAndre Fischer    my ($pcp, $msp_filename) = @_;
14269f91b7e3SAndre Fischer
14279f91b7e3SAndre Fischer    my $table = $pcp->GetTable("Properties");
14289f91b7e3SAndre Fischer
14299f91b7e3SAndre Fischer    $table->SetRow(
14309f91b7e3SAndre Fischer        "*Name", "PatchOutputPath",
14319f91b7e3SAndre Fischer        "Value", installer::patch::Tools::ToWindowsPath($msp_filename)
14329f91b7e3SAndre Fischer        );
14339f91b7e3SAndre Fischer    # Request at least Windows installer 2.0.
14349f91b7e3SAndre Fischer    # Version 2.0 allows us to omit some values from ImageFamilies table.
14359f91b7e3SAndre Fischer    $table->SetRow(
14369f91b7e3SAndre Fischer        "*Name", "MinimumRequiredMsiVersion",
14379f91b7e3SAndre Fischer        "Value", 200
14389f91b7e3SAndre Fischer        );
14399f91b7e3SAndre Fischer    # Allow diffs for binary files.
14409f91b7e3SAndre Fischer    $table->SetRow(
14419f91b7e3SAndre Fischer        "*Name", "IncludeWholeFilesOnly",
14429f91b7e3SAndre Fischer        "Value", 0
14439f91b7e3SAndre Fischer        );
14449f91b7e3SAndre Fischer
14459f91b7e3SAndre Fischer    my $uuid = installer::windows::msiglobal::create_guid();
14469f91b7e3SAndre Fischer    my $uuid_string = "{" . $uuid . "}";
14479f91b7e3SAndre Fischer    $table->SetRow(
14489f91b7e3SAndre Fischer        "*Name", "PatchGUID",
14499f91b7e3SAndre Fischer        "Value", $uuid_string
14509f91b7e3SAndre Fischer        );
14519f91b7e3SAndre Fischer    $installer::logger::Info->printf("created new PatchGUID %s\n", $uuid_string);
14529f91b7e3SAndre Fischer
14539f91b7e3SAndre Fischer    # Prevent sequence table from being generated.
14549f91b7e3SAndre Fischer    $table->SetRow(
14559f91b7e3SAndre Fischer        "*Name", "SEQUENCE_DATA_GENERATION_DISABLED",
14569f91b7e3SAndre Fischer        "Value", 1);
1457d575d58fSAndre Fischer
1458d575d58fSAndre Fischer    # We don't provide file size and hash values.
1459d575d58fSAndre Fischer    # This value is set to make this fact explicit (0 should be the default).
1460d575d58fSAndre Fischer    $table->SetRow(
1461d575d58fSAndre Fischer        "*Name", "TrustMsi",
1462d575d58fSAndre Fischer        "Value", 0);
14639f91b7e3SAndre Fischer}
14649f91b7e3SAndre Fischer
14659f91b7e3SAndre Fischer
14669f91b7e3SAndre Fischer
14679f91b7e3SAndre Fischer
14689f91b7e3SAndre Fischersub SetupImageFamiliesTable ($)
14699f91b7e3SAndre Fischer{
14709f91b7e3SAndre Fischer    my ($pcp) = @_;
14719f91b7e3SAndre Fischer
14729f91b7e3SAndre Fischer    $pcp->GetTable("ImageFamilies")->SetRow(
14739f91b7e3SAndre Fischer        "Family", $ImageFamily,
14749f91b7e3SAndre Fischer        "MediaSrcPropName", "",#"MNPSrcPropName",
14759f91b7e3SAndre Fischer        "MediaDiskId", "",
14769f91b7e3SAndre Fischer        "FileSequenceStart", "",
14779f91b7e3SAndre Fischer        "DiskPrompt", "",
14789f91b7e3SAndre Fischer        "VolumeLabel", "");
14799f91b7e3SAndre Fischer}
14809f91b7e3SAndre Fischer
14819f91b7e3SAndre Fischer
14829f91b7e3SAndre Fischer
14839f91b7e3SAndre Fischer
14849f91b7e3SAndre Fischersub SetupUpgradedImagesTable ($$)
14859f91b7e3SAndre Fischer{
14869f91b7e3SAndre Fischer    my ($pcp, $target_msi_path) = @_;
14879f91b7e3SAndre Fischer
14889f91b7e3SAndre Fischer    my $msi_path = installer::patch::Tools::ToWindowsPath($target_msi_path);
14899f91b7e3SAndre Fischer    $pcp->GetTable("UpgradedImages")->SetRow(
14909f91b7e3SAndre Fischer        "Upgraded", $TargetImageName,
14919f91b7e3SAndre Fischer        "MsiPath", $msi_path,
14929f91b7e3SAndre Fischer        "PatchMsiPath", "",
14939f91b7e3SAndre Fischer        "SymbolPaths", "",
14949f91b7e3SAndre Fischer        "Family", $ImageFamily);
14959f91b7e3SAndre Fischer}
14969f91b7e3SAndre Fischer
14979f91b7e3SAndre Fischer
14989f91b7e3SAndre Fischer
14999f91b7e3SAndre Fischer
15009f91b7e3SAndre Fischersub SetupTargetImagesTable ($$)
15019f91b7e3SAndre Fischer{
15029f91b7e3SAndre Fischer    my ($pcp, $source_msi_path) = @_;
15039f91b7e3SAndre Fischer
15049f91b7e3SAndre Fischer    $pcp->GetTable("TargetImages")->SetRow(
15059f91b7e3SAndre Fischer        "Target", $SourceImageName,
15069f91b7e3SAndre Fischer        "MsiPath", installer::patch::Tools::ToWindowsPath($source_msi_path),
15079f91b7e3SAndre Fischer        "SymbolPaths", "",
15089f91b7e3SAndre Fischer        "Upgraded", $TargetImageName,
15099f91b7e3SAndre Fischer        "Order", 1,
15109f91b7e3SAndre Fischer        "ProductValidateFlags", "",
15119f91b7e3SAndre Fischer        "IgnoreMissingSrcFiles", 0);
15129f91b7e3SAndre Fischer}
15139f91b7e3SAndre Fischer
15149f91b7e3SAndre Fischer
15159f91b7e3SAndre Fischer
15169f91b7e3SAndre Fischer
15179f91b7e3SAndre Fischersub SetAdditionalValues ($%)
15189f91b7e3SAndre Fischer{
15199f91b7e3SAndre Fischer    my ($pcp, %data) = @_;
15209f91b7e3SAndre Fischer
15219f91b7e3SAndre Fischer    while (my ($key,$value) = each(%data))
15229f91b7e3SAndre Fischer    {
15239f91b7e3SAndre Fischer        $key =~ /^([^\/]+)\/([^:]+):(.+)$/
15249f91b7e3SAndre Fischer            || die("invalid key format");
15259f91b7e3SAndre Fischer        my ($table_name, $key_column,$key_value) = ($1,$2,$3);
15269f91b7e3SAndre Fischer        $value =~ /^([^:]+):(.*)$/
15279f91b7e3SAndre Fischer            || die("invalid value format");
15289f91b7e3SAndre Fischer        my ($value_column,$value_value) = ($1,$2);
15299f91b7e3SAndre Fischer
15309f91b7e3SAndre Fischer        my $table = $pcp->GetTable($table_name);
15319f91b7e3SAndre Fischer        $table->SetRow(
15329f91b7e3SAndre Fischer                "*".$key_column, $key_value,
15339f91b7e3SAndre Fischer                $value_column, $value_value);
15349f91b7e3SAndre Fischer    }
15359f91b7e3SAndre Fischer}
15369f91b7e3SAndre Fischer
15379f91b7e3SAndre Fischer
15389f91b7e3SAndre Fischer
15399f91b7e3SAndre Fischer
15409f91b7e3SAndre Fischersub CreatePcp ($$$$$$%)
15419f91b7e3SAndre Fischer{
15429f91b7e3SAndre Fischer    my ($source_msi,
15439f91b7e3SAndre Fischer        $target_msi,
15449f91b7e3SAndre Fischer        $language,
15459f91b7e3SAndre Fischer        $context,
15469f91b7e3SAndre Fischer        $msp_path,
15479f91b7e3SAndre Fischer        $pcp_schema_filename,
15489f91b7e3SAndre Fischer        %additional_values) = @_;
15499f91b7e3SAndre Fischer
15509f91b7e3SAndre Fischer    # Create filenames.
15519f91b7e3SAndre Fischer    my $pcp_filename = File::Spec->catfile($msp_path, "openoffice.pcp");
15529f91b7e3SAndre Fischer    my $msp_filename = File::Spec->catfile($msp_path, "openoffice.msp");
15539f91b7e3SAndre Fischer
15549f91b7e3SAndre Fischer    # Setup msp path and filename.
15559f91b7e3SAndre Fischer    unlink($pcp_filename) if -f $pcp_filename;
15569f91b7e3SAndre Fischer    if ( ! File::Copy::copy($pcp_schema_filename, $pcp_filename))
15579f91b7e3SAndre Fischer    {
15589f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: could not create openoffice.pcp as copy of pcp schema\n");
15599f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $pcp_schema_filename);
15609f91b7e3SAndre Fischer        $installer::logger::Info->printf("       %s\n", $pcp_filename);
15619f91b7e3SAndre Fischer        return undef;
15629f91b7e3SAndre Fischer    }
15639f91b7e3SAndre Fischer    my $pcp = installer::patch::Msi->new(
15649f91b7e3SAndre Fischer        $pcp_filename,
1565677600b0SAndre Fischer        $target_msi->{'version'},
1566677600b0SAndre Fischer        $target_msi->{'is_current_version'},
15679f91b7e3SAndre Fischer        $language,
15689f91b7e3SAndre Fischer        $context->{'product-name'});
15699f91b7e3SAndre Fischer
15709f91b7e3SAndre Fischer    # Store some values in the pcp for easy reference in the msp creation.
15719f91b7e3SAndre Fischer    $pcp->{'msp_filename'} = $msp_filename;
15729f91b7e3SAndre Fischer
15739f91b7e3SAndre Fischer    SetupPcpPatchMetadataTable($pcp, $source_msi, $target_msi);
15749f91b7e3SAndre Fischer    SetupPropertiesTable($pcp, $msp_filename);
15759f91b7e3SAndre Fischer    SetupImageFamiliesTable($pcp);
15769f91b7e3SAndre Fischer    SetupUpgradedImagesTable($pcp, $target_msi->{'filename'});
15779f91b7e3SAndre Fischer    SetupTargetImagesTable($pcp, $source_msi->{'filename'});
15789f91b7e3SAndre Fischer
15799f91b7e3SAndre Fischer    SetAdditionalValues(%additional_values);
15809f91b7e3SAndre Fischer
15819f91b7e3SAndre Fischer    $pcp->Commit();
15829f91b7e3SAndre Fischer
15839f91b7e3SAndre Fischer    # Remove the PatchSequence table to avoid MsiMsp error message:
15849f91b7e3SAndre Fischer    # "Since MSI 3.0 will block installation of major upgrade patches with
15859f91b7e3SAndre Fischer    #  sequencing information, creation of such patches is blocked."
15869f91b7e3SAndre Fischer    #$pcp->RemoveTable("PatchSequence");
15879f91b7e3SAndre Fischer    # TODO: alternatively add property SEQUENCE_DATA_GENERATION_DISABLED to pcp Properties table.
15889f91b7e3SAndre Fischer
15899f91b7e3SAndre Fischer
15909f91b7e3SAndre Fischer    $installer::logger::Info->printf("created pcp file at\n");
15919f91b7e3SAndre Fischer    $installer::logger::Info->printf("    %s\n", $pcp->{'filename'});
15929f91b7e3SAndre Fischer
15939f91b7e3SAndre Fischer    return $pcp;
15949f91b7e3SAndre Fischer}
15959f91b7e3SAndre Fischer
15969f91b7e3SAndre Fischer
15979f91b7e3SAndre Fischer
15989f91b7e3SAndre Fischer
15999f91b7e3SAndre Fischersub ShowLog ($$$$)
16009f91b7e3SAndre Fischer{
16019f91b7e3SAndre Fischer    my ($log_path, $log_filename, $log_basename, $new_title) = @_;
16029f91b7e3SAndre Fischer
16039f91b7e3SAndre Fischer    if ( -f $log_filename)
16049f91b7e3SAndre Fischer    {
16059f91b7e3SAndre Fischer        my $destination_path = File::Spec->catfile($log_path, $log_basename);
16069f91b7e3SAndre Fischer        File::Path::make_path($destination_path) if ! -d $destination_path;
16079f91b7e3SAndre Fischer        my $command = join(" ",
16089f91b7e3SAndre Fischer            "wilogutl.exe",
16099f91b7e3SAndre Fischer            "/q",
16109f91b7e3SAndre Fischer            "/l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
16119f91b7e3SAndre Fischer            "/o", "'".installer::patch::Tools::ToWindowsPath($destination_path)."'");
16129f91b7e3SAndre Fischer        printf("running command $command\n");
16139f91b7e3SAndre Fischer        my $response = qx($command);
16149f91b7e3SAndre Fischer        my @candidates = glob($destination_path . "/Details*");
16159f91b7e3SAndre Fischer        foreach my $candidate (@candidates)
16169f91b7e3SAndre Fischer        {
16179f91b7e3SAndre Fischer            next unless -f $candidate;
16189f91b7e3SAndre Fischer            my $new_name = $candidate;
16199f91b7e3SAndre Fischer            $new_name =~ s/Details.*$/$log_basename.html/;
16209f91b7e3SAndre Fischer
16219f91b7e3SAndre Fischer            # Rename the top-level html file and replace the title.
16229f91b7e3SAndre Fischer            open my $in, "<", $candidate;
16239f91b7e3SAndre Fischer            open my $out, ">", $new_name;
16249f91b7e3SAndre Fischer            while (<$in>)
16259f91b7e3SAndre Fischer            {
16269f91b7e3SAndre Fischer                if (/^(.*\<title\>)([^<]+)(.*)$/)
16279f91b7e3SAndre Fischer                {
16289f91b7e3SAndre Fischer                    print $out $1.$new_title.$3;
16299f91b7e3SAndre Fischer                }
16309f91b7e3SAndre Fischer                else
16319f91b7e3SAndre Fischer                {
16329f91b7e3SAndre Fischer                    print $out $_;
16339f91b7e3SAndre Fischer                }
16349f91b7e3SAndre Fischer            }
16359f91b7e3SAndre Fischer            close $in;
16369f91b7e3SAndre Fischer            close $out;
16379f91b7e3SAndre Fischer
1638677600b0SAndre Fischer            my $URL = File::Spec->rel2abs($new_name);
1639677600b0SAndre Fischer            $URL =~ s/\/cygdrive\/(.)\//$1|\//;
16409f91b7e3SAndre Fischer            $URL =~ s/^(.):/$1|/;
16419f91b7e3SAndre Fischer            $URL = "file:///". $URL;
16429f91b7e3SAndre Fischer            $installer::logger::Info->printf("open %s in your browser to see the log messages\n", $URL);
16439f91b7e3SAndre Fischer        }
16449f91b7e3SAndre Fischer    }
16459f91b7e3SAndre Fischer    else
16469f91b7e3SAndre Fischer    {
16479f91b7e3SAndre Fischer        $installer::logger::Info->printf("Error: log file not found at %s\n", $log_filename);
16489f91b7e3SAndre Fischer    }
16499f91b7e3SAndre Fischer}
16509f91b7e3SAndre Fischer
16519f91b7e3SAndre Fischer
16529f91b7e3SAndre Fischer
16539f91b7e3SAndre Fischer
16549f91b7e3SAndre Fischersub CreateMsp ($)
16559f91b7e3SAndre Fischer{
16569f91b7e3SAndre Fischer    my ($pcp) = @_;
16579f91b7e3SAndre Fischer
16589f91b7e3SAndre Fischer    # Prepare log files.
16599f91b7e3SAndre Fischer    my $log_path = File::Spec->catfile($pcp->{'path'}, "log");
16609f91b7e3SAndre Fischer    my $log_basename = "msp";
16619f91b7e3SAndre Fischer    my $log_filename = File::Spec->catfile($log_path, $log_basename.".log");
16629f91b7e3SAndre Fischer    my $performance_log_basename = "performance";
16639f91b7e3SAndre Fischer    my $performance_log_filename = File::Spec->catfile($log_path, $performance_log_basename.".log");
16649f91b7e3SAndre Fischer    File::Path::make_path($log_path) if ! -d $log_path;
16659f91b7e3SAndre Fischer    unlink($log_filename) if -f $log_filename;
16669f91b7e3SAndre Fischer    unlink($performance_log_filename) if -f $performance_log_filename;
16679f91b7e3SAndre Fischer
16689f91b7e3SAndre Fischer    # Create the .msp patch file.
16699f91b7e3SAndre Fischer    my $temporary_msimsp_path = File::Spec->catfile($pcp->{'path'}, "tmp");
16709f91b7e3SAndre Fischer    if ( ! -d $temporary_msimsp_path)
16719f91b7e3SAndre Fischer    {
16729f91b7e3SAndre Fischer        File::Path::make_path($temporary_msimsp_path)
16739f91b7e3SAndre Fischer            || die ("can not create temporary path ".$temporary_msimsp_path);
16749f91b7e3SAndre Fischer    }
16759f91b7e3SAndre Fischer    $installer::logger::Info->printf("running msimsp.exe, that will take a while\n");
1676677600b0SAndre Fischer    my $create_performance_log = 0;
16779f91b7e3SAndre Fischer    my $command = join(" ",
16789f91b7e3SAndre Fischer        "msimsp.exe",
16799f91b7e3SAndre Fischer        "-s", "'".installer::patch::Tools::ToWindowsPath($pcp->{'filename'})."'",
16809f91b7e3SAndre Fischer        "-p", "'".installer::patch::Tools::ToWindowsPath($pcp->{'msp_filename'})."'",
16819f91b7e3SAndre Fischer        "-l", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
16829f91b7e3SAndre Fischer        "-f", "'".installer::patch::Tools::ToWindowsPath($temporary_msimsp_path)."'");
1683677600b0SAndre Fischer    if ($create_performance_log)
1684677600b0SAndre Fischer    {
1685677600b0SAndre Fischer        $command .= " -lp " . MsiTools::ToEscapedWindowsPath($performance_log_filename);
1686677600b0SAndre Fischer    }
16879f91b7e3SAndre Fischer    $installer::logger::Info->printf("running command %s\n", $command);
16889f91b7e3SAndre Fischer    my $response = qx($command);
16899f91b7e3SAndre Fischer    $installer::logger::Info->printf("response of msimsp is %s\n", $response);
16909f91b7e3SAndre Fischer    if ( ! -d $temporary_msimsp_path)
16919f91b7e3SAndre Fischer    {
16929f91b7e3SAndre Fischer        die("msimsp failed and deleted temporary path ".$temporary_msimsp_path);
16939f91b7e3SAndre Fischer    }
16949f91b7e3SAndre Fischer
16959f91b7e3SAndre Fischer    # Show the log file that was created by the msimsp.exe command.
16969f91b7e3SAndre Fischer    ShowLog($log_path, $log_filename, $log_basename, "msp creation");
1697677600b0SAndre Fischer    if ($create_performance_log)
1698677600b0SAndre Fischer    {
1699677600b0SAndre Fischer        ShowLog($log_path, $performance_log_filename, $performance_log_basename, "msp creation perf");
1700677600b0SAndre Fischer    }
1701677600b0SAndre Fischer}
1702677600b0SAndre Fischer
1703677600b0SAndre Fischer
1704677600b0SAndre Fischersub ProvideMsis ($$$)
1705677600b0SAndre Fischer{
1706677600b0SAndre Fischer    my ($context, $variables, $language) = @_;
1707677600b0SAndre Fischer
1708677600b0SAndre Fischer    # 2a. Provide .msi and .cab files and unpack .cab for the source release.
1709677600b0SAndre Fischer    $installer::logger::Info->printf("locating source package (%s)\n", $context->{'source-version'});
1710677600b0SAndre Fischer    $installer::logger::Info->increase_indentation();
1711677600b0SAndre Fischer    if ( ! installer::patch::InstallationSet::ProvideUnpackedCab(
1712677600b0SAndre Fischer	       $context->{'source-version'},
1713677600b0SAndre Fischer	       0,
1714677600b0SAndre Fischer	       $language,
1715677600b0SAndre Fischer	       "msi",
1716677600b0SAndre Fischer	       $context->{'product-name'}))
1717677600b0SAndre Fischer    {
1718677600b0SAndre Fischer        die "could not provide unpacked .cab file";
1719677600b0SAndre Fischer    }
1720677600b0SAndre Fischer    my $source_msi = installer::patch::Msi->FindAndCreate(
1721677600b0SAndre Fischer        $context->{'source-version'},
1722677600b0SAndre Fischer        0,
1723677600b0SAndre Fischer        $language,
1724677600b0SAndre Fischer        $context->{'product-name'});
1725677600b0SAndre Fischer    die unless defined $source_msi;
1726677600b0SAndre Fischer    die unless $source_msi->IsValid();
1727677600b0SAndre Fischer    $installer::logger::Info->decrease_indentation();
1728677600b0SAndre Fischer
1729677600b0SAndre Fischer    # 2b. Provide .msi and .cab files and unpacked .cab for the target release.
1730677600b0SAndre Fischer    $installer::logger::Info->printf("locating target package (%s)\n", $context->{'target-version'});
1731677600b0SAndre Fischer    $installer::logger::Info->increase_indentation();
1732677600b0SAndre Fischer    if ( ! installer::patch::InstallationSet::ProvideUnpackedCab(
1733677600b0SAndre Fischer               $context->{'target-version'},
1734677600b0SAndre Fischer               1,
1735677600b0SAndre Fischer               $language,
1736677600b0SAndre Fischer               "msi",
1737677600b0SAndre Fischer               $context->{'product-name'}))
1738677600b0SAndre Fischer    {
1739677600b0SAndre Fischer        die;
1740677600b0SAndre Fischer    }
1741677600b0SAndre Fischer    my $target_msi = installer::patch::Msi->FindAndCreate(
1742677600b0SAndre Fischer        $context->{'target-version'},
1743677600b0SAndre Fischer        0,
1744677600b0SAndre Fischer        $language,
1745677600b0SAndre Fischer        $context->{'product-name'});
1746677600b0SAndre Fischer    die unless defined $target_msi;
1747677600b0SAndre Fischer    die unless $target_msi->IsValid();
1748677600b0SAndre Fischer    $installer::logger::Info->decrease_indentation();
1749677600b0SAndre Fischer
1750677600b0SAndre Fischer    return ($source_msi, $target_msi);
17519f91b7e3SAndre Fischer}
17529f91b7e3SAndre Fischer
17539f91b7e3SAndre Fischer
17549f91b7e3SAndre Fischer
1755d575d58fSAndre Fischer
1756d575d58fSAndre Fischer=head CreatePatch($context, $variables)
1757d575d58fSAndre Fischer
1758d575d58fSAndre Fischer    Create MSP patch files for all relevant languages.
1759d575d58fSAndre Fischer    The different steps are:
1760d575d58fSAndre Fischer    1. Determine the set of languages for which both the source and target installation sets are present.
1761d575d58fSAndre Fischer    Per language:
1762d575d58fSAndre Fischer        2. Unpack CAB files (for source and target).
1763d575d58fSAndre Fischer        3. Check if source and target releases are compatible.
1764d575d58fSAndre Fischer        4. Create the PCP driver file.
1765d575d58fSAndre Fischer        5. Create the MSP patch file.
1766d575d58fSAndre Fischer
1767d575d58fSAndre Fischer=cut
17689f91b7e3SAndre Fischersub CreatePatch ($$)
17699f91b7e3SAndre Fischer{
17709f91b7e3SAndre Fischer    my ($context, $variables) = @_;
17719f91b7e3SAndre Fischer
17729f91b7e3SAndre Fischer    $installer::logger::Info->printf("patch will update product %s from %s to %s\n",
17739f91b7e3SAndre Fischer        $context->{'product-name'},
17749f91b7e3SAndre Fischer        $context->{'source-version'},
17759f91b7e3SAndre Fischer        $context->{'target-version'});
17769f91b7e3SAndre Fischer
17779f91b7e3SAndre Fischer    # Locate the Pcp schema file early on to report any errors before the lengthy operations that follow.
17789f91b7e3SAndre Fischer    my $pcp_schema_filename = FindPcpTemplate();
17799f91b7e3SAndre Fischer    if ( ! defined $pcp_schema_filename)
17809f91b7e3SAndre Fischer    {
17819f91b7e3SAndre Fischer        exit(1);
17829f91b7e3SAndre Fischer    }
17839f91b7e3SAndre Fischer
17849f91b7e3SAndre Fischer    my $release_data = installer::patch::ReleasesList::Instance()
17859f91b7e3SAndre Fischer        ->{$context->{'source-version'}}
17869f91b7e3SAndre Fischer        ->{$context->{'package-format'}};
17879f91b7e3SAndre Fischer
1788d575d58fSAndre Fischer    # 1. Determine the set of languages for which we can create patches.
1789677600b0SAndre Fischer    my $language = $context->{'language'};
1790677600b0SAndre Fischer    my %no_ms_lang_locale_map = map {$_=>1} @installer::globals::noMSLocaleLangs;
1791677600b0SAndre Fischer    if (defined $no_ms_lang_locale_map{$language})
17929f91b7e3SAndre Fischer    {
1793677600b0SAndre Fischer        $language = "en-US_".$language;
1794677600b0SAndre Fischer    }
17959f91b7e3SAndre Fischer
1796677600b0SAndre Fischer    if ( ! IsLanguageValid($context, $release_data, $language))
1797677600b0SAndre Fischer    {
1798677600b0SAndre Fischer        $installer::logger::Info->printf("can not create patch for language '%s'\n", $language);
1799677600b0SAndre Fischer    }
1800677600b0SAndre Fischer    else
1801677600b0SAndre Fischer    {
1802677600b0SAndre Fischer        $installer::logger::Info->printf("processing language '%s'\n", $language);
18039f91b7e3SAndre Fischer        $installer::logger::Info->increase_indentation();
18049f91b7e3SAndre Fischer
1805677600b0SAndre Fischer        my ($source_msi, $target_msi) = ProvideMsis($context, $variables, $language);
18069f91b7e3SAndre Fischer
18079f91b7e3SAndre Fischer        # Trigger reading of tables.
18089f91b7e3SAndre Fischer        foreach my $table_name (("File", "Component", "Registry"))
18099f91b7e3SAndre Fischer        {
18109f91b7e3SAndre Fischer            $source_msi->GetTable($table_name);
18119f91b7e3SAndre Fischer            $target_msi->GetTable($table_name);
18129f91b7e3SAndre Fischer            $installer::logger::Info->printf("read %s table (source and target\n", $table_name);
18139f91b7e3SAndre Fischer        }
18149f91b7e3SAndre Fischer
1815d575d58fSAndre Fischer        # 3. Check if the source and target msis fullfil all necessary requirements.
18169f91b7e3SAndre Fischer        if ( ! Check($source_msi, $target_msi, $variables, $context->{'product-name'}))
18179f91b7e3SAndre Fischer        {
18189f91b7e3SAndre Fischer            exit(1);
18199f91b7e3SAndre Fischer        }
18209f91b7e3SAndre Fischer
18219f91b7e3SAndre Fischer        # Provide the base path for creating .pcp and .mcp file.
18229f91b7e3SAndre Fischer        my $msp_path = File::Spec->catfile(
18239f91b7e3SAndre Fischer            $context->{'output-path'},
18249f91b7e3SAndre Fischer            $context->{'product-name'},
18259f91b7e3SAndre Fischer            "msp",
18269f91b7e3SAndre Fischer            sprintf("%s_%s",
1827677600b0SAndre Fischer                installer::patch::Version::ArrayToDirectoryName(
1828677600b0SAndre Fischer                    installer::patch::Version::StringToNumberArray(
1829677600b0SAndre Fischer                        $source_msi->{'version'})),
1830677600b0SAndre Fischer                installer::patch::Version::ArrayToDirectoryName(
1831677600b0SAndre Fischer                    installer::patch::Version::StringToNumberArray(
1832677600b0SAndre Fischer                        $target_msi->{'version'}))),
18339f91b7e3SAndre Fischer            $language
18349f91b7e3SAndre Fischer            );
18359f91b7e3SAndre Fischer        File::Path::make_path($msp_path) unless -d $msp_path;
18369f91b7e3SAndre Fischer
1837d575d58fSAndre Fischer        # 4. Create the .pcp file that drives the msimsp.exe command.
18389f91b7e3SAndre Fischer        my $pcp = CreatePcp(
18399f91b7e3SAndre Fischer            $source_msi,
18409f91b7e3SAndre Fischer            $target_msi,
18419f91b7e3SAndre Fischer            $language,
18429f91b7e3SAndre Fischer            $context,
18439f91b7e3SAndre Fischer            $msp_path,
18449f91b7e3SAndre Fischer            $pcp_schema_filename,
1845d575d58fSAndre Fischer            "Properties/Name:DontRemoveTempFolderWhenFinished" => "Value:1");
18469f91b7e3SAndre Fischer
1847d575d58fSAndre Fischer        # 5. Finally create the msp.
18489f91b7e3SAndre Fischer        CreateMsp($pcp);
18499f91b7e3SAndre Fischer
18509f91b7e3SAndre Fischer        $installer::logger::Info->decrease_indentation();
18519f91b7e3SAndre Fischer    }
18529f91b7e3SAndre Fischer}
18539f91b7e3SAndre Fischer
18549f91b7e3SAndre Fischer
18559f91b7e3SAndre Fischer
1856677600b0SAndre Fischer
1857677600b0SAndre Fischersub CheckPatchCompatability ($$)
1858677600b0SAndre Fischer{
1859677600b0SAndre Fischer    my ($context, $variables) = @_;
1860677600b0SAndre Fischer
1861677600b0SAndre Fischer    $installer::logger::Info->printf("patch will update product %s from %s to %s\n",
1862677600b0SAndre Fischer        $context->{'product-name'},
1863677600b0SAndre Fischer        $context->{'source-version'},
1864677600b0SAndre Fischer        $context->{'target-version'});
1865677600b0SAndre Fischer
1866677600b0SAndre Fischer    my $release_data = installer::patch::ReleasesList::Instance()
1867677600b0SAndre Fischer        ->{$context->{'source-version'}}
1868677600b0SAndre Fischer        ->{$context->{'package-format'}};
1869677600b0SAndre Fischer
1870677600b0SAndre Fischer    # 1. Determine the set of languages for which we can create patches.
1871677600b0SAndre Fischer    my $language = $context->{'language'};
1872677600b0SAndre Fischer    my %no_ms_lang_locale_map = map {$_=>1} @installer::globals::noMSLocaleLangs;
1873677600b0SAndre Fischer    if (defined $no_ms_lang_locale_map{$language})
1874677600b0SAndre Fischer    {
1875677600b0SAndre Fischer        $language = "en-US_".$language;
1876677600b0SAndre Fischer    }
1877677600b0SAndre Fischer
1878677600b0SAndre Fischer    if ( ! IsLanguageValid($context, $release_data, $language))
1879677600b0SAndre Fischer    {
1880677600b0SAndre Fischer        $installer::logger::Info->printf("can not create patch for language '%s'\n", $language);
1881677600b0SAndre Fischer    }
1882677600b0SAndre Fischer    else
1883677600b0SAndre Fischer    {
1884677600b0SAndre Fischer        $installer::logger::Info->printf("processing language '%s'\n", $language);
1885677600b0SAndre Fischer        $installer::logger::Info->increase_indentation();
1886677600b0SAndre Fischer
1887677600b0SAndre Fischer        my ($source_msi, $target_msi) = ProvideMsis($context, $variables, $language);
1888677600b0SAndre Fischer
1889677600b0SAndre Fischer        # Trigger reading of tables.
1890677600b0SAndre Fischer        foreach my $table_name (("File", "Component", "Registry"))
1891677600b0SAndre Fischer        {
1892677600b0SAndre Fischer            $source_msi->GetTable($table_name);
1893677600b0SAndre Fischer            $target_msi->GetTable($table_name);
1894677600b0SAndre Fischer            $installer::logger::Info->printf("read %s table (source and target\n", $table_name);
1895677600b0SAndre Fischer        }
1896677600b0SAndre Fischer
1897677600b0SAndre Fischer        # 3. Check if the source and target msis fullfil all necessary requirements.
1898677600b0SAndre Fischer        if ( ! Check($source_msi, $target_msi, $variables, $context->{'product-name'}))
1899677600b0SAndre Fischer        {
1900677600b0SAndre Fischer            exit(1);
1901677600b0SAndre Fischer        }
1902677600b0SAndre Fischer    }
1903677600b0SAndre Fischer}
1904677600b0SAndre Fischer
1905677600b0SAndre Fischer
1906677600b0SAndre Fischer
1907677600b0SAndre Fischer
1908d575d58fSAndre Fischer=cut ApplyPatch ($context, $variables)
19099f91b7e3SAndre Fischer
1910d575d58fSAndre Fischer    This is for testing only.
1911d575d58fSAndre Fischer    The patch is applied and (extensive) log information is created and transformed into HTML format.
1912d575d58fSAndre Fischer
1913d575d58fSAndre Fischer=cut
19149f91b7e3SAndre Fischersub ApplyPatch ($$)
19159f91b7e3SAndre Fischer{
19169f91b7e3SAndre Fischer    my ($context, $variables) = @_;
19179f91b7e3SAndre Fischer
19189f91b7e3SAndre Fischer    $installer::logger::Info->printf("will apply patches that update product %s from %s to %s\n",
19199f91b7e3SAndre Fischer        $context->{'product-name'},
19209f91b7e3SAndre Fischer        $context->{'source-version'},
19219f91b7e3SAndre Fischer        $context->{'target-version'});
19229f91b7e3SAndre Fischer
19239f91b7e3SAndre Fischer    my $source_version_dirname = installer::patch::Version::ArrayToDirectoryName(
19249f91b7e3SAndre Fischer      installer::patch::Version::StringToNumberArray(
19259f91b7e3SAndre Fischer          $context->{'source-version'}));
19269f91b7e3SAndre Fischer    my $target_version_dirname = installer::patch::Version::ArrayToDirectoryName(
19279f91b7e3SAndre Fischer      installer::patch::Version::StringToNumberArray(
19289f91b7e3SAndre Fischer          $context->{'target-version'}));
19299f91b7e3SAndre Fischer
1930677600b0SAndre Fischer    my $language = $context->{'language'};
1931677600b0SAndre Fischer    my %no_ms_lang_locale_map = map {$_=>1} @installer::globals::noMSLocaleLangs;
1932677600b0SAndre Fischer    if (defined $no_ms_lang_locale_map{$language})
19339f91b7e3SAndre Fischer    {
1934677600b0SAndre Fischer        $language = "en-US_".$language;
1935677600b0SAndre Fischer    }
1936677600b0SAndre Fischer
1937677600b0SAndre Fischer    my $msp_filename = File::Spec->catfile(
1938677600b0SAndre Fischer        $context->{'output-path'},
1939677600b0SAndre Fischer        $context->{'product-name'},
1940677600b0SAndre Fischer        "msp",
1941677600b0SAndre Fischer        $source_version_dirname . "_" . $target_version_dirname,
1942677600b0SAndre Fischer        $language,
1943677600b0SAndre Fischer        "openoffice.msp");
1944677600b0SAndre Fischer    if ( ! -f $msp_filename)
1945677600b0SAndre Fischer    {
1946677600b0SAndre Fischer        $installer::logger::Info->printf("%s does not point to a valid file\n", $msp_filename);
1947677600b0SAndre Fischer        next;
1948677600b0SAndre Fischer    }
19499f91b7e3SAndre Fischer
1950677600b0SAndre Fischer    my $log_path = File::Spec->catfile(dirname($msp_filename), "log");
1951677600b0SAndre Fischer    my $log_basename = "apply-msp";
1952677600b0SAndre Fischer    my $log_filename = File::Spec->catfile($log_path, $log_basename.".log");
1953677600b0SAndre Fischer
1954677600b0SAndre Fischer    my $command = join(" ",
1955677600b0SAndre Fischer        "msiexec.exe",
1956677600b0SAndre Fischer        "/update", "'".installer::patch::Tools::ToWindowsPath($msp_filename)."'",
1957677600b0SAndre Fischer        "/L*xv!", "'".installer::patch::Tools::ToWindowsPath($log_filename)."'",
1958677600b0SAndre Fischer        "REINSTALL=ALL",
19599f91b7e3SAndre Fischer#            "REINSTALLMODE=vomus",
1960677600b0SAndre Fischer        "REINSTALLMODE=omus",
1961677600b0SAndre Fischer        "MSIENFORCEUPGRADECOMPONENTRULES=1");
19629f91b7e3SAndre Fischer
1963677600b0SAndre Fischer    printf("executing command %s\n", $command);
1964677600b0SAndre Fischer    my $response = qx($command);
1965677600b0SAndre Fischer    Encode::from_to($response, "UTF16LE", "UTF8");
1966677600b0SAndre Fischer    printf("response was '%s'\n", $response);
19679f91b7e3SAndre Fischer
1968677600b0SAndre Fischer    ShowLog($log_path, $log_filename, $log_basename, "msp application");
19699f91b7e3SAndre Fischer}
19709f91b7e3SAndre Fischer
19719f91b7e3SAndre Fischer
19729f91b7e3SAndre Fischer
19739f91b7e3SAndre Fischer
1974d575d58fSAndre Fischer=head2 DownloadFile ($url)
1975d575d58fSAndre Fischer
1976d575d58fSAndre Fischer    A simpler version of InstallationSet::Download().  It is simple because it is used to
1977d575d58fSAndre Fischer    setup the $release_data structure that is used by InstallationSet::Download().
1978d575d58fSAndre Fischer
1979d575d58fSAndre Fischer=cut
1980d575d58fSAndre Fischersub DownloadFile ($)
1981d575d58fSAndre Fischer{
1982d575d58fSAndre Fischer    my ($url) = shift;
1983d575d58fSAndre Fischer
1984d575d58fSAndre Fischer    my $agent = LWP::UserAgent->new();
1985d575d58fSAndre Fischer    $agent->timeout(120);
1986d575d58fSAndre Fischer    $agent->show_progress(0);
1987d575d58fSAndre Fischer
1988d575d58fSAndre Fischer    my $file_content = "";
1989d575d58fSAndre Fischer    my $last_was_redirect = 0;
1990d575d58fSAndre Fischer    my $bytes_read = 0;
1991d575d58fSAndre Fischer    $agent->add_handler('response_redirect'
1992d575d58fSAndre Fischer        => sub{
1993d575d58fSAndre Fischer            $last_was_redirect = 1;
1994d575d58fSAndre Fischer            return;
1995d575d58fSAndre Fischer        });
1996d575d58fSAndre Fischer    $agent->add_handler('response_data'
1997d575d58fSAndre Fischer        => sub{
1998d575d58fSAndre Fischer            if ($last_was_redirect)
1999d575d58fSAndre Fischer            {
2000d575d58fSAndre Fischer                $last_was_redirect = 0;
2001d575d58fSAndre Fischer                # Throw away the data we got so far.
2002d575d58fSAndre Fischer		$file_content = "";
2003d575d58fSAndre Fischer            }
2004d575d58fSAndre Fischer            my($response,$agent,$h,$data)=@_;
2005d575d58fSAndre Fischer	    $file_content .= $data;
2006d575d58fSAndre Fischer        });
2007d575d58fSAndre Fischer    $agent->get($url);
2008d575d58fSAndre Fischer
2009d575d58fSAndre Fischer    return $file_content;
2010d575d58fSAndre Fischer}
2011d575d58fSAndre Fischer
2012d575d58fSAndre Fischer
2013d575d58fSAndre Fischer
2014d575d58fSAndre Fischer
2015d575d58fSAndre Fischersub CreateReleaseItem ($$$)
2016d575d58fSAndre Fischer{
2017d575d58fSAndre Fischer    my ($language, $exe_filename, $msi) = @_;
2018d575d58fSAndre Fischer
2019d575d58fSAndre Fischer    die "can not open installation set at ".$exe_filename unless -f $exe_filename;
2020d575d58fSAndre Fischer
2021d575d58fSAndre Fischer    open my $in, "<", $exe_filename;
2022d575d58fSAndre Fischer    my $sha256_checksum = new Digest("SHA-256")->addfile($in)->hexdigest();
2023d575d58fSAndre Fischer    close $in;
2024d575d58fSAndre Fischer
2025d575d58fSAndre Fischer    my $filesize = -s $exe_filename;
2026d575d58fSAndre Fischer
2027d575d58fSAndre Fischer    # Get the product code property from the msi and strip the enclosing braces.
2028d575d58fSAndre Fischer    my $product_code = $msi->GetTable("Property")->GetValue("Property", "ProductCode", "Value");
2029d575d58fSAndre Fischer    $product_code =~ s/(^{|}$)//g;
2030d575d58fSAndre Fischer    my $upgrade_code = $msi->GetTable("Property")->GetValue("Property", "UpgradeCode", "Value");
2031d575d58fSAndre Fischer    $upgrade_code =~ s/(^{|}$)//g;
2032d575d58fSAndre Fischer    my $build_id = $msi->GetTable("Property")->GetValue("Property", "PRODUCTBUILDID", "Value");
2033d575d58fSAndre Fischer
2034d575d58fSAndre Fischer    return {
2035d575d58fSAndre Fischer        'language' => $language,
2036d575d58fSAndre Fischer        'checksum-type' => "sha256",
2037d575d58fSAndre Fischer        'checksum-value' => $sha256_checksum,
2038d575d58fSAndre Fischer        'file-size' => $filesize,
2039d575d58fSAndre Fischer        'product-code' => $product_code,
2040d575d58fSAndre Fischer        'upgrade-code' => $upgrade_code,
2041d575d58fSAndre Fischer        'build-id' => $build_id
2042d575d58fSAndre Fischer    };
2043d575d58fSAndre Fischer}
2044d575d58fSAndre Fischer
2045d575d58fSAndre Fischer
2046d575d58fSAndre Fischer
2047d575d58fSAndre Fischer
2048d575d58fSAndre Fischersub GetReleaseItemForCurrentBuild ($$$)
2049d575d58fSAndre Fischer{
2050d575d58fSAndre Fischer    my ($context, $language, $exe_basename) = @_;
2051d575d58fSAndre Fischer
2052d575d58fSAndre Fischer    # Target version is the current version.
2053d575d58fSAndre Fischer    # Search instsetoo_native for the installation set.
2054d575d58fSAndre Fischer    my $filename = File::Spec->catfile(
2055d575d58fSAndre Fischer        $context->{'output-path'},
2056d575d58fSAndre Fischer        $context->{'product-name'},
2057d575d58fSAndre Fischer        $context->{'package-format'},
2058d575d58fSAndre Fischer        "install",
2059d575d58fSAndre Fischer        $language."_download",
2060d575d58fSAndre Fischer        $exe_basename);
2061d575d58fSAndre Fischer
2062d575d58fSAndre Fischer    printf("        current : %s\n", $filename);
2063d575d58fSAndre Fischer    if ( ! -f $filename)
2064d575d58fSAndre Fischer    {
2065d575d58fSAndre Fischer        printf("ERROR: can not find %s\n", $filename);
2066d575d58fSAndre Fischer        return undef;
2067d575d58fSAndre Fischer    }
2068d575d58fSAndre Fischer    else
2069d575d58fSAndre Fischer    {
2070d575d58fSAndre Fischer        my $msi = installer::patch::Msi->FindAndCreate(
2071d575d58fSAndre Fischer            $context->{'target-version'},
2072d575d58fSAndre Fischer            1,
2073d575d58fSAndre Fischer            $language,
2074d575d58fSAndre Fischer            $context->{'product-name'});
2075d575d58fSAndre Fischer        return CreateReleaseItem($language, $filename, $msi);
2076d575d58fSAndre Fischer    }
2077d575d58fSAndre Fischer}
2078d575d58fSAndre Fischer
2079d575d58fSAndre Fischer
2080d575d58fSAndre Fischer
2081d575d58fSAndre Fischersub GetReleaseItemForOldBuild ($$$$)
2082d575d58fSAndre Fischer{
2083d575d58fSAndre Fischer    my ($context, $language, $exe_basename, $url_template) = @_;
2084d575d58fSAndre Fischer
2085d575d58fSAndre Fischer    # Use ext_sources/ as local cache for archive.apache.org
2086d575d58fSAndre Fischer    # and search these for the installation set.
2087d575d58fSAndre Fischer
2088d575d58fSAndre Fischer    my $version = $context->{'target-version'};
2089d575d58fSAndre Fischer    my $package_format =  $context->{'package-format'};
2090d575d58fSAndre Fischer    my $releases_list = installer::patch::ReleasesList::Instance();
2091d575d58fSAndre Fischer
2092d575d58fSAndre Fischer    my $url = $url_template;
2093d575d58fSAndre Fischer    $url =~ s/%L/$language/g;
2094d575d58fSAndre Fischer    $releases_list->{$version}->{$package_format}->{$language}->{'URL'} = $url;
2095d575d58fSAndre Fischer
2096d575d58fSAndre Fischer    if ( ! installer::patch::InstallationSet::ProvideUnpackedExe(
2097d575d58fSAndre Fischer               $version,
2098d575d58fSAndre Fischer               0,
2099d575d58fSAndre Fischer               $language,
2100d575d58fSAndre Fischer               $package_format,
2101d575d58fSAndre Fischer               $context->{'product-name'}))
2102d575d58fSAndre Fischer    {
2103d575d58fSAndre Fischer        # Can not provide unpacked EXE.
2104d575d58fSAndre Fischer        return undef;
2105d575d58fSAndre Fischer    }
2106d575d58fSAndre Fischer    else
2107d575d58fSAndre Fischer    {
2108d575d58fSAndre Fischer        my $exe_filename = File::Spec->catfile(
2109d575d58fSAndre Fischer            $ENV{'TARFILE_LOCATION'},
2110d575d58fSAndre Fischer            $exe_basename);
2111d575d58fSAndre Fischer        my $msi = installer::patch::Msi->FindAndCreate(
2112d575d58fSAndre Fischer            $version,
2113d575d58fSAndre Fischer            0,
2114d575d58fSAndre Fischer            $language,
2115d575d58fSAndre Fischer            $context->{'product-name'});
2116d575d58fSAndre Fischer        return CreateReleaseItem($language, $exe_filename, $msi);
2117d575d58fSAndre Fischer    }
2118d575d58fSAndre Fischer}
2119d575d58fSAndre Fischer
2120d575d58fSAndre Fischer
2121d575d58fSAndre Fischer
2122d575d58fSAndre Fischer
2123d575d58fSAndre Fischersub UpdateReleasesXML($$)
2124d575d58fSAndre Fischer{
2125d575d58fSAndre Fischer    my ($context, $variables) = @_;
2126d575d58fSAndre Fischer
2127d575d58fSAndre Fischer    my $releases_list = installer::patch::ReleasesList::Instance();
2128d575d58fSAndre Fischer    my $output_filename = File::Spec->catfile(
2129d575d58fSAndre Fischer        $context->{'output-path'},
2130d575d58fSAndre Fischer        "misc",
2131d575d58fSAndre Fischer        "releases.xml");
2132d575d58fSAndre Fischer
2133d575d58fSAndre Fischer    my $target_version = $context->{'target-version'};
2134d575d58fSAndre Fischer    my %version_hash = map {$_=>1} @{$releases_list->{'releases'}};
2135d575d58fSAndre Fischer    my $item_hash = undef;
2136d575d58fSAndre Fischer    if ( ! defined $version_hash{$context->{'target-version'}})
2137d575d58fSAndre Fischer    {
2138d575d58fSAndre Fischer        # Target version is not yet present.  Add it and print message that asks caller to check order.
2139d575d58fSAndre Fischer        push @{$releases_list->{'releases'}}, $target_version;
2140d575d58fSAndre Fischer        printf("adding data for new version %s to list of released versions.\n", $target_version);
2141d575d58fSAndre Fischer        printf("please check order of releases in $output_filename\n");
2142d575d58fSAndre Fischer        $item_hash = {};
2143d575d58fSAndre Fischer    }
2144d575d58fSAndre Fischer    else
2145d575d58fSAndre Fischer    {
2146d575d58fSAndre Fischer        printf("adding data for existing version %s to releases.xml\n", $target_version);
2147d575d58fSAndre Fischer        $item_hash = $releases_list->{$target_version}->{$context->{'package-format'}};
2148d575d58fSAndre Fischer    }
2149d575d58fSAndre Fischer    $releases_list->{$target_version} = {$context->{'package-format'} => $item_hash};
2150d575d58fSAndre Fischer
2151d575d58fSAndre Fischer    my @languages = GetLanguages();
2152d575d58fSAndre Fischer    my %language_items = ();
2153d575d58fSAndre Fischer    foreach my $language (@languages)
2154d575d58fSAndre Fischer    {
2155d575d58fSAndre Fischer        # There are three different sources where to find the downloadable installation sets.
2156d575d58fSAndre Fischer        # 1. archive.apache.org for previously released versions.
2157d575d58fSAndre Fischer        # 2. A local cache or repository directory that conceptually is a local copy of archive.apache.org
2158d575d58fSAndre Fischer        # 3. The downloadable installation sets built in instsetoo_native/.
2159d575d58fSAndre Fischer
2160d575d58fSAndre Fischer        my $exe_basename = sprintf(
2161d575d58fSAndre Fischer            "%s_%s_Win_x86_install_%s.exe",
2162d575d58fSAndre Fischer            $context->{'product-name'},
2163d575d58fSAndre Fischer            $target_version,
2164d575d58fSAndre Fischer            $language);
2165d575d58fSAndre Fischer        my $url_template = sprintf(
2166d575d58fSAndre Fischer            "http://archive.apache.org/dist/openoffice/%s/binaries/%%L/%s_%s_Win_x86_install_%%L.exe",
2167d575d58fSAndre Fischer            $target_version,
2168d575d58fSAndre Fischer            $context->{'product-name'},
2169d575d58fSAndre Fischer            $target_version);
2170d575d58fSAndre Fischer
2171d575d58fSAndre Fischer        my $item = undef;
2172d575d58fSAndre Fischer        if ($target_version eq $variables->{PRODUCTVERSION})
2173d575d58fSAndre Fischer        {
2174d575d58fSAndre Fischer            $item = GetReleaseItemForCurrentBuild($context, $language, $exe_basename);
2175d575d58fSAndre Fischer        }
2176d575d58fSAndre Fischer        else
2177d575d58fSAndre Fischer        {
2178d575d58fSAndre Fischer            $item = GetReleaseItemForOldBuild($context, $language, $exe_basename, $url_template);
2179d575d58fSAndre Fischer        }
2180d575d58fSAndre Fischer
2181d575d58fSAndre Fischer        next unless defined $item;
2182d575d58fSAndre Fischer
2183d575d58fSAndre Fischer        $language_items{$language} = $item;
2184d575d58fSAndre Fischer        $item_hash->{$language} = $item;
2185d575d58fSAndre Fischer        $item_hash->{'upgrade-code'} = $item->{'upgrade-code'};
2186d575d58fSAndre Fischer        $item_hash->{'build-id'} = $item->{'build-id'};
2187d575d58fSAndre Fischer        $item_hash->{'url-template'} = $url_template;
2188d575d58fSAndre Fischer    }
2189d575d58fSAndre Fischer
2190d575d58fSAndre Fischer    my @valid_languages = sort keys %language_items;
2191d575d58fSAndre Fischer    $item_hash->{'languages'} = \@valid_languages;
2192d575d58fSAndre Fischer
2193d575d58fSAndre Fischer    $releases_list->Write($output_filename);
2194d575d58fSAndre Fischer
2195d575d58fSAndre Fischer    printf("\n\n");
2196d575d58fSAndre Fischer    printf("please copy '%s' to main/instsetoo_native/data\n", $output_filename);
2197d575d58fSAndre Fischer    printf("and check in the modified file to the version control system\n");
2198d575d58fSAndre Fischer}
2199d575d58fSAndre Fischer
2200d575d58fSAndre Fischer
2201d575d58fSAndre Fischer
2202d575d58fSAndre Fischer
22039f91b7e3SAndre Fischersub main ()
22049f91b7e3SAndre Fischer{
22059f91b7e3SAndre Fischer    my $context = ProcessCommandline();
2206*75b1440aSAndre Fischer#    installer::logger::starttime();
2207*75b1440aSAndre Fischer#    $installer::logger::Global->add_timestamp("starting logging");
2208*75b1440aSAndre Fischer    installer::logger::SetupSimpleLogging(undef);
2209677600b0SAndre Fischer
2210d575d58fSAndre Fischer    die "ERROR: list file is not defined, please use --lst-file option"
2211d575d58fSAndre Fischer        unless defined $context->{'lst-file'};
2212d575d58fSAndre Fischer    die "ERROR: product name is not defined, please use --product-name option"
2213d575d58fSAndre Fischer        unless defined $context->{'product-name'};
2214677600b0SAndre Fischer    die sprintf("ERROR: package format %s is not supported", $context->{'package-format'})
2215677600b0SAndre Fischer        unless defined $context->{'package-format'} ne "msi";
2216d575d58fSAndre Fischer
22179f91b7e3SAndre Fischer    my ($variables, undef, undef) = installer::ziplist::read_openoffice_lst_file(
22189f91b7e3SAndre Fischer        $context->{'lst-file'},
22199f91b7e3SAndre Fischer        $context->{'product-name'},
22209f91b7e3SAndre Fischer        undef);
22219f91b7e3SAndre Fischer    DetermineVersions($context, $variables);
22229f91b7e3SAndre Fischer
2223677600b0SAndre Fischer    if ($context->{'command'} =~ /create|check/)
2224677600b0SAndre Fischer    {
2225*75b1440aSAndre Fischer        my $filename = File::Spec->catfile(
2226*75b1440aSAndre Fischer            $context->{'output-path'},
2227*75b1440aSAndre Fischer            $context->{'product-name'},
2228*75b1440aSAndre Fischer            "msp",
2229*75b1440aSAndre Fischer            $context->{'source-version-dash'} . "_" . $context->{'target-version-dash'},
2230*75b1440aSAndre Fischer            $context->{'language'},
2231*75b1440aSAndre Fischer            "log",
2232*75b1440aSAndre Fischer            "patch-creation.log");
2233*75b1440aSAndre Fischer        my $dirname = dirname($filename);
2234*75b1440aSAndre Fischer        File::Path::make_path($dirname) unless -d $dirname;
2235*75b1440aSAndre Fischer        printf("directing output to $filename\n");
2236*75b1440aSAndre Fischer
2237*75b1440aSAndre Fischer        $installer::logger::Lang->set_filename($filename);
2238677600b0SAndre Fischer        $installer::logger::Lang->copy_lines_from($installer::logger::Global);
2239677600b0SAndre Fischer        $installer::logger::Lang->set_forward(undef);
2240677600b0SAndre Fischer        $installer::logger::Info->set_forward($installer::logger::Lang);
2241677600b0SAndre Fischer    }
2242677600b0SAndre Fischer
22439f91b7e3SAndre Fischer    if ($context->{'command'} eq "create")
22449f91b7e3SAndre Fischer    {
22459f91b7e3SAndre Fischer        CreatePatch($context, $variables);
22469f91b7e3SAndre Fischer    }
22479f91b7e3SAndre Fischer    elsif ($context->{'command'} eq "apply")
22489f91b7e3SAndre Fischer    {
22499f91b7e3SAndre Fischer        ApplyPatch($context, $variables);
22509f91b7e3SAndre Fischer    }
2251d575d58fSAndre Fischer    elsif ($context->{'command'} eq "update-releases-xml")
2252d575d58fSAndre Fischer    {
2253d575d58fSAndre Fischer        UpdateReleasesXML($context, $variables);
2254d575d58fSAndre Fischer    }
2255677600b0SAndre Fischer    elsif ($context->{'command'} eq "check")
2256677600b0SAndre Fischer    {
2257677600b0SAndre Fischer        CheckPatchCompatability($context, $variables);
2258677600b0SAndre Fischer    }
22599f91b7e3SAndre Fischer}
22609f91b7e3SAndre Fischer
22619f91b7e3SAndre Fischer
22629f91b7e3SAndre Fischermain();
2263