From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- solenv/bin/modules/installer/control.pm | 474 ++++ solenv/bin/modules/installer/converter.pm | 187 ++ solenv/bin/modules/installer/copyproject.pm | 105 + solenv/bin/modules/installer/download.pm | 666 +++++ solenv/bin/modules/installer/environment.pm | 131 + solenv/bin/modules/installer/epmfile.pm | 2669 ++++++++++++++++++++ solenv/bin/modules/installer/exiter.pm | 33 + solenv/bin/modules/installer/filelists.pm | 154 ++ solenv/bin/modules/installer/files.pm | 120 + solenv/bin/modules/installer/globals.pm | 290 +++ solenv/bin/modules/installer/helppack.pm | 530 ++++ solenv/bin/modules/installer/languagepack.pm | 509 ++++ solenv/bin/modules/installer/languages.pm | 142 ++ solenv/bin/modules/installer/logger.pm | 256 ++ solenv/bin/modules/installer/packagelist.pm | 840 ++++++ solenv/bin/modules/installer/parameter.pm | 552 ++++ solenv/bin/modules/installer/pathanalyzer.pm | 66 + solenv/bin/modules/installer/profiles.pm | 223 ++ solenv/bin/modules/installer/remover.pm | 50 + solenv/bin/modules/installer/scpzipfiles.pm | 143 ++ solenv/bin/modules/installer/scriptitems.pm | 2404 ++++++++++++++++++ solenv/bin/modules/installer/setupscript.pm | 486 ++++ solenv/bin/modules/installer/simplepackage.pm | 732 ++++++ solenv/bin/modules/installer/strip.pm | 136 + solenv/bin/modules/installer/systemactions.pm | 1329 ++++++++++ solenv/bin/modules/installer/windows/admin.pm | 515 ++++ solenv/bin/modules/installer/windows/assembly.pm | 279 ++ solenv/bin/modules/installer/windows/binary.pm | 67 + solenv/bin/modules/installer/windows/component.pm | 505 ++++ .../bin/modules/installer/windows/createfolder.pm | 154 ++ solenv/bin/modules/installer/windows/directory.pm | 634 +++++ solenv/bin/modules/installer/windows/feature.pm | 403 +++ .../modules/installer/windows/featurecomponent.pm | 165 ++ solenv/bin/modules/installer/windows/file.pm | 1019 ++++++++ solenv/bin/modules/installer/windows/font.pm | 69 + solenv/bin/modules/installer/windows/icon.pm | 68 + solenv/bin/modules/installer/windows/idtglobal.pm | 1862 ++++++++++++++ solenv/bin/modules/installer/windows/inifile.pm | 121 + solenv/bin/modules/installer/windows/language.pm | 41 + solenv/bin/modules/installer/windows/media.pm | 202 ++ .../bin/modules/installer/windows/mergemodule.pm | 1703 +++++++++++++ solenv/bin/modules/installer/windows/msiglobal.pm | 1684 ++++++++++++ .../installer/windows/msishortcutproperty.pm | 143 ++ solenv/bin/modules/installer/windows/msp.pm | 1264 +++++++++ solenv/bin/modules/installer/windows/property.pm | 566 +++++ solenv/bin/modules/installer/windows/registry.pm | 407 +++ solenv/bin/modules/installer/windows/removefile.pm | 141 ++ solenv/bin/modules/installer/windows/shortcut.pm | 659 +++++ solenv/bin/modules/installer/windows/strip.pm | 149 ++ solenv/bin/modules/installer/windows/update.pm | 620 +++++ solenv/bin/modules/installer/windows/upgrade.pm | 80 + solenv/bin/modules/installer/worker.pm | 921 +++++++ solenv/bin/modules/installer/ziplist.pm | 828 ++++++ 53 files changed, 28496 insertions(+) create mode 100644 solenv/bin/modules/installer/control.pm create mode 100644 solenv/bin/modules/installer/converter.pm create mode 100644 solenv/bin/modules/installer/copyproject.pm create mode 100644 solenv/bin/modules/installer/download.pm create mode 100644 solenv/bin/modules/installer/environment.pm create mode 100644 solenv/bin/modules/installer/epmfile.pm create mode 100644 solenv/bin/modules/installer/exiter.pm create mode 100644 solenv/bin/modules/installer/filelists.pm create mode 100644 solenv/bin/modules/installer/files.pm create mode 100644 solenv/bin/modules/installer/globals.pm create mode 100644 solenv/bin/modules/installer/helppack.pm create mode 100644 solenv/bin/modules/installer/languagepack.pm create mode 100644 solenv/bin/modules/installer/languages.pm create mode 100644 solenv/bin/modules/installer/logger.pm create mode 100644 solenv/bin/modules/installer/packagelist.pm create mode 100644 solenv/bin/modules/installer/parameter.pm create mode 100644 solenv/bin/modules/installer/pathanalyzer.pm create mode 100644 solenv/bin/modules/installer/profiles.pm create mode 100644 solenv/bin/modules/installer/remover.pm create mode 100644 solenv/bin/modules/installer/scpzipfiles.pm create mode 100644 solenv/bin/modules/installer/scriptitems.pm create mode 100644 solenv/bin/modules/installer/setupscript.pm create mode 100644 solenv/bin/modules/installer/simplepackage.pm create mode 100644 solenv/bin/modules/installer/strip.pm create mode 100644 solenv/bin/modules/installer/systemactions.pm create mode 100644 solenv/bin/modules/installer/windows/admin.pm create mode 100644 solenv/bin/modules/installer/windows/assembly.pm create mode 100644 solenv/bin/modules/installer/windows/binary.pm create mode 100644 solenv/bin/modules/installer/windows/component.pm create mode 100644 solenv/bin/modules/installer/windows/createfolder.pm create mode 100644 solenv/bin/modules/installer/windows/directory.pm create mode 100644 solenv/bin/modules/installer/windows/feature.pm create mode 100644 solenv/bin/modules/installer/windows/featurecomponent.pm create mode 100644 solenv/bin/modules/installer/windows/file.pm create mode 100644 solenv/bin/modules/installer/windows/font.pm create mode 100644 solenv/bin/modules/installer/windows/icon.pm create mode 100644 solenv/bin/modules/installer/windows/idtglobal.pm create mode 100644 solenv/bin/modules/installer/windows/inifile.pm create mode 100644 solenv/bin/modules/installer/windows/language.pm create mode 100644 solenv/bin/modules/installer/windows/media.pm create mode 100644 solenv/bin/modules/installer/windows/mergemodule.pm create mode 100644 solenv/bin/modules/installer/windows/msiglobal.pm create mode 100644 solenv/bin/modules/installer/windows/msishortcutproperty.pm create mode 100644 solenv/bin/modules/installer/windows/msp.pm create mode 100644 solenv/bin/modules/installer/windows/property.pm create mode 100644 solenv/bin/modules/installer/windows/registry.pm create mode 100644 solenv/bin/modules/installer/windows/removefile.pm create mode 100644 solenv/bin/modules/installer/windows/shortcut.pm create mode 100644 solenv/bin/modules/installer/windows/strip.pm create mode 100644 solenv/bin/modules/installer/windows/update.pm create mode 100644 solenv/bin/modules/installer/windows/upgrade.pm create mode 100644 solenv/bin/modules/installer/worker.pm create mode 100644 solenv/bin/modules/installer/ziplist.pm (limited to 'solenv/bin/modules/installer') diff --git a/solenv/bin/modules/installer/control.pm b/solenv/bin/modules/installer/control.pm new file mode 100644 index 000000000..196a13546 --- /dev/null +++ b/solenv/bin/modules/installer/control.pm @@ -0,0 +1,474 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::control; + +use Cwd; +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::pathanalyzer; +use installer::scriptitems; +use installer::systemactions; + +######################################################### +# Function that can be used for additional controls. +# Search happens in $installer::globals::patharray. +######################################################### + +sub check_needed_files_in_path +{ + my ( $filesref ) = @_; + + foreach $onefile ( @{$filesref} ) + { + installer::logger::print_message( "... searching $onefile ..." ); + + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath_classic(\$onefile, $installer::globals::patharray , 0); + + if ( $$fileref eq "" ) + { + $error = 1; + installer::logger::print_error( "$onefile not found\n" ); + } + else + { + installer::logger::print_message( "\tFound: $$fileref\n" ); + } + } + + if ( $error ) + { + installer::exiter::exit_program("ERROR: Could not find all needed files in path!", "check_needed_files_in_path"); + } +} + +######################################################### +# Checking the local system +# Checking existence of needed files in include path +######################################################### + +sub check_system_path +{ + # The following files have to be found in the environment variable PATH + # All platforms: zip + # Windows only: "msiinfo.exe", "msidb.exe", "uuidgen.exe", "makecab.exe", "msitran.exe", "expand.exe" for msi database and packaging + + my $onefile; + my $error = 0; + my $pathvariable = $ENV{'PATH'}; + my $local_pathseparator = $installer::globals::pathseparator; + + if( $^O =~ /cygwin/i ) + { + # When using cygwin's perl the PATH variable is POSIX style and + # has to be converted to DOS style for further use. + $pathvariable = join ';', + map { $dir = qx{cygpath -m "$_"}; chomp($dir); $dir } + split /\Q$local_pathseparator\E\s*/, $pathvariable; + $local_pathseparator = ';'; + } + my $patharrayref = installer::converter::convert_stringlist_into_array(\$pathvariable, $local_pathseparator); + + $installer::globals::patharray = $patharrayref; + + my @needed_files_in_path = (); + + if (($installer::globals::iswin) && ($installer::globals::iswindowsbuild)) + { + @needed_files_in_path = ("zip.exe", "msiinfo.exe", "msidb.exe", "uuidgen", "makecab.exe", "msitran.exe", "expand.exe"); + } + elsif ($installer::globals::iswin) + { + @needed_files_in_path = ("zip.exe"); + } + else + { + @needed_files_in_path = ("zip"); + } + + foreach $onefile ( @needed_files_in_path ) + { + installer::logger::print_message( "... searching $onefile ..." ); + + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath_classic(\$onefile, $patharrayref , 0); + + if ( $$fileref eq "" ) + { + $error = 1; + installer::logger::print_error( "$onefile not found\n" ); + } + else + { + installer::logger::print_message( "\tFound: $$fileref\n" ); + # Saving the absolute path for msitran.exe. This is required for the determination of the checksum. + if ( $onefile eq "msitran.exe" ) { $installer::globals::msitranpath = $$fileref; } + } + } + + if ( $error ) + { + installer::exiter::exit_program("ERROR: Could not find all needed files in path!", "check_system_path"); + } + + # checking for epm, which has to be in the path or in the solver + + if (( $installer::globals::call_epm ) && (!($installer::globals::iswindowsbuild))) + { + my $onefile = "epm"; + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref , 0); + if (!( $$fileref eq "" )) + { + $installer::globals::epm_in_path = 1; + + if ( $$fileref =~ /^\s*\.\/epm\s*$/ ) + { + my $currentdir = cwd(); + $$fileref =~ s/\./$currentdir/; + } + + $installer::globals::epm_path = $$fileref; + } + } +} + +###################################################################### +# Determining the version of file makecab.exe +###################################################################### + +sub get_makecab_version +{ + my $makecabversion = -1; + + my $systemcall = "makecab.exe |"; + my @makecaboutput = (); + + open (CAB, $systemcall); + while () { push(@makecaboutput, $_); } + close (CAB); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + my $versionline = ""; + + for ( my $i = 0; $i <= $#makecaboutput; $i++ ) + { + if ( $makecaboutput[$i] =~ /\bVersion\b/i ) + { + $versionline = $makecaboutput[$i]; + last; + } + } + + $infoline = $versionline; + push( @installer::globals::globallogfileinfo, $infoline); + + if ( $versionline =~ /\bVersion\b\s+(\d+[\d\.]+\d+)\s+/ ) + { + $makecabversion = $1; + } + + # Only using the first number + + if ( $makecabversion =~ /^\s*(\d+?)\D*/ ) + { + $makecabversion = $1; + } + + $infoline = "Using version: " . $makecabversion . "\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + + return $makecabversion; +} + +###################################################################### +# Checking the version of file makecab.exe +###################################################################### + +sub check_makecab_version +{ + # checking version of makecab.exe + # Now it is guaranteed, that makecab.exe is in the path + + my $do_check = 1; + + my $makecabversion = get_makecab_version(); + + my $infoline = "Tested version: " . $installer::globals::controlledmakecabversion . "\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + if ( $makecabversion < 0 ) { $do_check = 0; } # version could not be determined + + if ( $do_check ) + { + if ( $makecabversion < $installer::globals::controlledmakecabversion ) + { + installer::exiter::exit_program("makecab.exe too old. Found version: \"$makecabversion\", required version: \"$installer::globals::controlledmakecabversion\"!", "check_makecab_version"); + } + } + else + { + $infoline = "Warning: No version check of makecab.exe\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } +} + +###################################################################### +# Reading the environment variables for the paths in ziplist. +# solarenvpath, os, pmiscpath +###################################################################### + +sub check_system_environment +{ + my %variables = (); + my $error = 0; + + my @environmentvariables = qw( + LIBO_VERSION_MAJOR + LIBO_VERSION_MINOR + CPUNAME + OS + COM + PLATFORMID + LOCAL_OUT + LOCAL_COMMON_OUT + WORKDIR + SRCDIR + ); + + for my $key ( @environmentvariables ) + { + $variables{$key} = defined($ENV{$key}) ? $ENV{$key} : ""; + + if ( $variables{$key} eq "" ) + { + installer::logger::print_error( "$key not set in environment\n" ); + $error = 1; + } + } + + if ( $error ) + { + installer::exiter::exit_program("ERROR: Environment variable not set!", "check_system_environment"); + } + + return \%variables; +} + +############################################################# +# Controlling the log file at the end of the +# packaging process +############################################################# + +sub check_logfile +{ + my ($logfile) = @_; + + my @errors = (); + my @output = (); + my $contains_error = 0; + + my $ignore_error = 0; + my $make_error_to_warning = 0; + + for ( my $i = 0; $i <= $#{$logfile}; $i++ ) + { + my $line = ${$logfile}[$i]; + + # Errors are all errors, but not the Windows installer table "Error.idt" + + my $compareline = $line; + $compareline =~ s/Error\.idt//g; # removing all occurrences of "Error.idt" + $compareline =~ s/Error\.ulf//g; # removing all occurrences of "Error.ulf" + $compareline =~ s/Error\.idl//g; # removing all occurrences of "Error.idl" + $compareline =~ s/Error\.html//g; # removing all occurrences of "Error.html" + $compareline =~ s/error\.py//g; # removing all occurrences of "error.py" + $compareline =~ s/error\.cpython\-3.(\.opt\-.|)\.py[co]//g; # removing all occurrences of "error-cpython" + $compareline =~ s/libgpg-error//g; + $compareline =~ s/Error-xref\.html//g; + + if ( $compareline =~ /\bError\b/i ) + { + $contains_error = 1; + push(@errors, $line); + + if ( $ignore_error ) + { + $contains_error = 0; + $make_error_to_warning = 1; + } + } + } + + if ($contains_error) + { + my $line = "\n*********************************************************************\n"; + push(@output, $line); + $line = "ERROR: The following errors occurred in packaging process:\n\n"; + push(@output, $line); + + for ( my $i = 0; $i <= $#errors; $i++ ) + { + $line = "$errors[$i]"; + push(@output, $line); + } + + $line = "*********************************************************************\n"; + push(@output, $line); + } + else + { + my $line = ""; + + if ( $make_error_to_warning ) + { + $line = "\n*********************************************************************\n"; + push(@output, $line); + $line = "The following errors in the log file were ignored:\n\n"; + push(@output, $line); + + for ( my $i = 0; $i <= $#errors; $i++ ) + { + $line = "$errors[$i]"; + push(@output, $line); + } + + $line = "*********************************************************************\n"; + push(@output, $line); + } + + $line = "\n***********************************************************\n"; + push(@output, $line); + $line = "Successful packaging process!\n"; + push(@output, $line); + $line = "***********************************************************\n"; + push(@output, $line); + } + + # printing the output file and adding it to the logfile + + installer::logger::include_header_into_logfile("Summary:"); + + my $force = 1; # print this message even in 'quiet' mode + for ( my $i = 0; $i <= $#output; $i++ ) + { + my $line = "$output[$i]"; + installer::logger::print_message( "$line", $force ); + push( @installer::globals::logfileinfo, $line); + push( @installer::globals::errorlogfileinfo, $line); + } + + return $contains_error; +} + +############################################################# +# Reading the Windows list file for Windows language codes +# Encoding field is no longer used. We use UTF-8 everywhere. +############################################################# + +sub read_lcidlist +{ + my ($patharrayref) = @_; + + if ( ! -f $installer::globals::lcidlistname ) { installer::exiter::exit_program("ERROR: Did not find Windows LCID list $installer::globals::lcidlistname!", "read_lcidlist"); } + + my $infoline = "Found LCID file: $installer::globals::lcidlistname\n"; + push(@installer::globals::globallogfileinfo, $infoline); + + my $lcidlist = installer::files::read_file($installer::globals::lcidlistname); + my %msilanguage = (); + + for ( my $i = 0; $i <= $#{$lcidlist}; $i++ ) + { + my $line = ${$lcidlist}[$i]; + # de-mangle various potential DOS line-ending problems + $line =~ s/\r//g; + $line =~ s/\n//g; + $line =~ s/\s*\#.*$//; # removing comments after "#" + if ( $line =~ /^\s*$/ ) { next; } # this is an empty line + + if ( $line =~ /^\s*([\w-]+)\s+(\d+)\s+(\d+)\s*$/ ) + { + my $onelanguage = $1; + my $windowslanguage = $3; + $msilanguage{$onelanguage} = $windowslanguage; + } + else + { + installer::exiter::exit_program("ERROR: Wrong syntax in Windows LCID list $installer::globals::lcidlistname in line $i: '$line'", "read_lcidlist"); + } + } + $installer::globals::msilanguage = \%msilanguage; +} + +############################################################# +# Only for Windows and Linux (RPM)there is currently +# a reliable mechanism to register extensions during +# installation process. Therefore it is for all other +# platforms forbidden to install oxt files into that +# directory, in which they are searched for registration. +############################################################# + +sub check_oxtfiles +{ + my ( $filesarray ) = @_; + + for ( my $i = 0; $i <= $#{$filesarray}; $i++ ) + { + my $onefile = ${$filesarray}[$i]; + + if (( $onefile->{'Name'} ) && ( $onefile->{'Dir'} )) + { + if (( $onefile->{'Name'} =~ /\.oxt\s*$/ ) && ( $onefile->{'Dir'} eq $installer::globals::extensioninstalldir )) + { + installer::exiter::exit_program("There is currently only for Linux (RPM) and Windows a reliable mechanism to register extensions during installation.\nPlease remove file \"$onefile->{'gid'}\" from your installation set!\nYou can use \"\#ifdef _WIN32\" and \"\#ifdef LINUX\" in scp.", "check_oxtfiles"); + } + } + } +} + +####################################################################### +# Setting global variable "$installer::globals::addsystemintegration" +####################################################################### + +sub set_addsystemintegration +{ + my ($allvariables) = @_; + + if ( $allvariables->{'ADDSYSTEMINTEGRATION'} ) { $installer::globals::addsystemintegration = 1; } + + if ( $installer::globals::languagepack ) { $installer::globals::addsystemintegration = 0; } + if ( $installer::globals::helppack ) { $installer::globals::addsystemintegration = 0; } + + my $infoline = "Value of \$installer::globals::addsystemintegration: $installer::globals::addsystemintegration\n"; + push( @installer::globals::globallogfileinfo, $infoline); +} + +1; diff --git a/solenv/bin/modules/installer/converter.pm b/solenv/bin/modules/installer/converter.pm new file mode 100644 index 000000000..f825e0439 --- /dev/null +++ b/solenv/bin/modules/installer/converter.pm @@ -0,0 +1,187 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::converter; + +use strict; +use warnings; + +use installer::globals; + +############################# +# Converter +############################# + +sub convert_array_to_hash +{ + my ($arrayref) = @_; + + my %newhash = (); + + for (@{$arrayref}) + { + next unless /^\s*([\w-]+?)\s+(.*?)\s*$/; + $newhash{$1} = $2; + } + + return \%newhash; +} + +############################################################################# +# Converting a string list with separator $listseparator +# into an array +############################################################################# + +sub convert_stringlist_into_array +{ + my ( $includestringref, $listseparator ) = @_; + + return [map "$_\n", split /\Q$listseparator\E\s*/, ${$includestringref}]; +} + +############################################################################# +# Converting a string list with separator $listseparator +# into a hash with values 1. +############################################################################# + +sub convert_stringlist_into_hash +{ + my ( $includestringref, $listseparator ) = @_; + + return {map {$_, 1} split /\Q$listseparator\E\s*/, ${$includestringref}}; +} + +############################################################################# +# Converting an array into a space separated string +############################################################################# + +sub convert_array_to_space_separated_string +{ + my ( $arrayref ) = @_; + + my $newstring; + for (@{$arrayref}) { + my $tmp = $_; + $tmp =~ s/\s+$//; + $newstring .= "$tmp "; + } + $newstring =~ s/ $//; + + return $newstring; +} + +############################################################################# +# The file name contains for some files "/". If this programs runs on +# a windows platform, this has to be converted to "\". +############################################################################# + +sub convert_slash_to_backslash +{ + my ($filesarrayref) = @_; + + for my $onefile (@{$filesarrayref}) + { + if ( $onefile->{'Name'} ) { $onefile->{'Name'} =~ s/\//\\/g; } + } +} + +############################################################################ +# Creating a copy of an existing file object +# No converter +############################################################################ + +sub copy_item_object +{ + my ($olditemhashref, $newitemhashref) = @_; + + $newitemhashref = {%{$olditemhashref}}; +} + +################################################################# +# Windows paths must not contain the following structure: +# c:\dirA\dirB\..\dirC +# This has to be exchanged to +# c:\dirA\dirC +################################################################# + +sub make_path_conform +{ + my ( $path ) = @_; + my $s = $installer::globals::separator; + + while ($path =~ s/[^\.\Q$s\E]+?\Q$s\E\.\.(?:\Q$s\E|$)//g) {} + + return $path; +} + +################################################################# +# Copying an item collector +# A reference to an array consisting of references to hashes. +################################################################# + +sub copy_collector +{ + return [map { {%{$_}} } @{$_[0]}]; +} + +################################################################# +# Combining two arrays, first wins +################################################################# + +sub combine_arrays_from_references_first_win +{ + my ( $arrayref1, $arrayref2 ) = @_; + + my $hashref1 = convert_array_to_hash($arrayref1); + my $hashref2 = convert_array_to_hash($arrayref2); + + # add key-value pairs from hash1 to hash2 (overwrites existing keys) + @{$hashref2}{keys %{$hashref1}} = values %{$hashref1}; + + return [map { "$_ $hashref2->{$_}\n" } keys %{$hashref2}]; +} + +################################################################# +# Replacing separators, that are included into quotes +################################################################# + +sub replace_masked_separator +{ + my ($string, $separator, $replacementstring) = @_; + + $string =~ s/\\\Q$separator\E/$replacementstring/g; + + return $string; +} + +################################################################# +# Resolving separators, that were replaced +# in function mask_separator_in_quotes +################################################################# + +sub resolve_masked_separator +{ + my ($arrayref, $separator, $replacementstring) = @_; + + for (@{$arrayref}) + { + s/$replacementstring/$separator/g; + } +} + +1; diff --git a/solenv/bin/modules/installer/copyproject.pm b/solenv/bin/modules/installer/copyproject.pm new file mode 100644 index 000000000..309636657 --- /dev/null +++ b/solenv/bin/modules/installer/copyproject.pm @@ -0,0 +1,105 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::copyproject; + +use strict; +use warnings; + +use installer::control; +use installer::converter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::systemactions; +use installer::worker; + +#################################################### +# Including header files into the logfile +#################################################### + +sub copy_project +{ + my ( $filesref, $scpactionsref, $loggingdir, $languagestringref, $shipinstalldir, $allsettingsarrayref ) = @_; + + # Creating directories + + installer::logger::include_header_into_logfile("Creating installation directory"); + + my $current_install_number = ""; + + my $installdir = installer::worker::create_installation_directory($shipinstalldir, $languagestringref, \$current_install_number); + + my $installlogdir = installer::systemactions::create_directory_next_to_directory($installdir, "log"); + + # Copy files and ScpActions + + installer::logger::include_header_into_logfile("Copying files:"); + + # copy Files + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + my $source = $onefile->{'sourcepath'}; + my $destination = $installdir . $installer::globals::separator . $onefile->{'Name'}; + + installer::systemactions::copy_one_file($source, $destination); + + if ( $onefile->{'UnixRights'} ) + { + chmod oct($onefile->{'UnixRights'}), $destination; + } + elsif ( $destination =~ /install\s*$/ ) + { + chmod 0775, $destination; + } + } + + # copy ScpActions + + for ( my $i = 0; $i <= $#{$scpactionsref}; $i++ ) + { + my $onefile = ${$scpactionsref}[$i]; + + my $source = $onefile->{'sourcepath'}; + my $destination = $installdir . $installer::globals::separator . $onefile->{'DestinationName'}; + + installer::systemactions::copy_one_file($source, $destination); + + if ( $onefile->{'UnixRights'} ) + { + chmod oct($onefile->{'UnixRights'}), $destination; + } + elsif ( $destination =~ /install\s*$/ ) + { + chmod 0775, $destination; + } + } + + # Analyzing the log file + + installer::worker::analyze_and_save_logfile($loggingdir, $installdir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number); + + # That's all + + exit(0); +} + +1; diff --git a/solenv/bin/modules/installer/download.pm b/solenv/bin/modules/installer/download.pm new file mode 100644 index 000000000..f46dae735 --- /dev/null +++ b/solenv/bin/modules/installer/download.pm @@ -0,0 +1,666 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::download; + +use strict; +use warnings; + +use File::Spec; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::remover; +use installer::systemactions; + +BEGIN { # This is needed so that cygwin's perl evaluates ACLs + # (needed for correctly evaluating the -x test.) + if( $^O =~ /cygwin/i ) { + require filetest; import filetest "access"; + } +} + +################################################################## +# Including the lowercase product name into the script template +################################################################## + +sub put_productname_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $productname = $variableshashref->{'PRODUCTNAME'}; + $productname = lc($productname); + $productname =~ s/\.//g; # openoffice.org -> openofficeorg + $productname =~ s/\s*//g; + + my $infoline = "Adding productname $productname into download shell script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/PRODUCTNAMEPLACEHOLDER/$productname/; + } +} + +######################################################### +# Including the linenumber into the script template +######################################################### + +sub put_linenumber_into_script +{ + my ( $scriptfile ) = @_; + + my $linenumber = $#{$scriptfile} + 2; + + my $infoline = "Adding linenumber $linenumber into download shell script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/LINENUMBERPLACEHOLDER/$linenumber/; + } +} + +######################################################### +# Determining the name of the new scriptfile +######################################################### + +sub determine_scriptfile_name +{ + my ( $filename ) = @_; + + $installer::globals::downloadfileextension = ".sh"; + $filename = $filename . $installer::globals::downloadfileextension; + $installer::globals::downloadfilename = $filename; + + my $infoline = "Setting download shell script file name to $filename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $filename; +} + +######################################################### +# Saving the script file in the installation directory +######################################################### + +sub save_script_file +{ + my ($directory, $newscriptfilename, $scriptfile) = @_; + + $newscriptfilename = $directory . $installer::globals::separator . $newscriptfilename; + installer::files::save_file($newscriptfilename, $scriptfile); + + my $infoline = "Saving script file $newscriptfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( ! $installer::globals::iswindowsbuild ) + { + chmod 0775, $newscriptfilename; + } + + return $newscriptfilename; +} + +######################################################### +# Including checksum and size into script file +######################################################### + +sub put_checksum_and_size_into_script +{ + my ($scriptfile, $sumout) = @_; + + my $checksum = ""; + my $size = ""; + + if ( $sumout =~ /^\s*(\d+)\s+(\d+)\s*$/ ) + { + $checksum = $1; + $size = $2; + } + else + { + installer::exiter::exit_program("ERROR: Incorrect return value from /usr/bin/sum: $sumout", "put_checksum_and_size_into_script"); + } + + my $infoline = "Adding checksum $checksum and size $size into download shell script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/CHECKSUMPLACEHOLDER/$checksum/; + ${$scriptfile}[$i] =~ s/DISCSPACEPLACEHOLDER/$size/; + } + +} + +######################################################### +# Determining checksum and size of tar file +######################################################### + +sub call_sum +{ + my ($filename) = @_; + + my $systemcall = "/usr/bin/sum $filename |"; + + my $sumoutput = ""; + + open (SUM, "$systemcall"); + $sumoutput = ; + close (SUM); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + $sumoutput =~ s/\s+$filename\s$//; + return $sumoutput; +} + +######################################################### +# Include the tar file into the script +######################################################### + +sub include_tar_into_script +{ + my ($scriptfile, $temporary_tarfile) = @_; + + my $systemcall = "cat $temporary_tarfile >> $scriptfile && rm $temporary_tarfile"; + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + return $returnvalue; +} + +######################################################### +# Create a tar file from the binary package +######################################################### + +sub tar_package +{ + my ( $installdir, $tarfilename, $usefakeroot) = @_; + + my $fakerootstring = ""; + if ( $usefakeroot ) { $fakerootstring = "fakeroot"; } + + my $systemcall = "cd $installdir; $fakerootstring tar -cf - * > $tarfilename"; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + chmod 0775, $tarfilename; + + return ( -s $tarfilename ); +} + +######################################################### +# Setting installation languages +######################################################### + +sub get_downloadname_language +{ + my ($languagestringref) = @_; + + my $languages = $$languagestringref; + + if ( $installer::globals::added_english ) + { + $languages =~ s/en-US_//; + $languages =~ s/_en-US//; + } + + # do not list languages if there are too many + if ( length ($languages) > $installer::globals::max_lang_length ) + { + $languages = ''; + } + + # do not list pure en-US, except for helppack and langpack + if ( ( $languages eq "en-US" ) && + ( ! $installer::globals::languagepack ) && + ( ! $installer::globals::helppack ) ) + { + $languages = ''; + } + + return $languages; +} + +######################################################### +# Setting download name +######################################################### + +sub get_downloadname_productname +{ + my ($allvariables) = @_; + + my $start = ""; + + $start = $allvariables->{'PRODUCTNAME'}; + + return $start; +} + +######################################################### +# Setting download version +######################################################### + +sub get_download_version +{ + my ($allvariables) = @_; + + my $version = ""; + + $version = $allvariables->{'PRODUCTVERSION'}; + if (( $allvariables->{'PRODUCTEXTENSION'} ) && ( $allvariables->{'PRODUCTEXTENSION'} ne "" )) { $version = $version . $allvariables->{'PRODUCTEXTENSION'}; } + + return $version; +} + +################################################################# +# Setting the platform name for download +################################################################# + +sub get_download_platformname +{ + my $platformname = ""; + + if ( $installer::globals::islinuxbuild ) + { + $platformname = "Linux"; + } + elsif ( $installer::globals::issolarisbuild ) + { + $platformname = "Solaris"; + } + elsif ( $installer::globals::iswindowsbuild ) + { + $platformname = "Win"; + } + elsif ( $installer::globals::isfreebsdbuild ) + { + $platformname = "FreeBSD"; + } + elsif ( $installer::globals::ismacbuild ) + { + $platformname = "MacOS"; + } + else + { + $platformname = $installer::globals::os; + } + + return $platformname; +} + +######################################################### +# Setting the architecture for the download name +######################################################### + +sub get_download_architecture +{ + my $arch = ""; + + if ( $installer::globals::issolarissparcbuild ) + { + $arch = "Sparc"; + } + elsif ( $installer::globals::issolarisx86build ) + { + $arch = "x86"; + } + elsif ( $installer::globals::cpuname eq 'INTEL' ) + { + $arch = "x86"; + } + elsif ( $installer::globals::cpuname eq 'POWERPC' ) + { + $arch = "PPC"; + } + elsif ( $installer::globals::cpuname eq 'POWERPC64' ) + { + $arch = "PPC"; + } + elsif ( $installer::globals::cpuname eq 'X86_64' ) + { + $arch = $installer::globals::os eq 'WNT' ? 'x64' : 'x86-64'; + } + elsif ( $installer::globals::cpuname eq 'AARCH64' ) + { + $arch = "aarch64"; + } + + return $arch; +} + +######################################################### +# Setting the content type for the download name +######################################################### + +sub get_download_content +{ + my ($allvariables) = @_; + + my $content = ""; + + # content type included in the installer + if ( $installer::globals::isrpmbuild ) + { + $content = "rpm"; + } + elsif ( $installer::globals::isdebbuild ) + { + $content = "deb"; + } + elsif ( $installer::globals::packageformat eq "archive" ) + { + $content = "archive"; + } + + return $content; +} + +######################################################### +# Setting the functionality type for the download name +######################################################### + +sub get_download_functionality +{ + my ($allvariables) = @_; + + my $functionality = ""; + + if ( $installer::globals::languagepack ) + { + $functionality = "langpack"; + } + elsif ( $installer::globals::helppack ) + { + $functionality = "helppack"; + } + elsif ( $allvariables->{'POSTVERSIONEXTENSION'} eq "SDK" ) + { + $functionality = "sdk"; + } + elsif ( $allvariables->{'POSTVERSIONEXTENSION'} eq "TEST" ) + { + $functionality = "test"; + } + elsif ( $allvariables->{'PRODUCTNAME'} eq "URE" ) + { + $functionality = "ure"; + } + + return $functionality; +} + +############################################################################################### +# Setting the download file name +# Syntax: +# (PRODUCTNAME)_(VERSION)_(OS)_(ARCH)_(INSTALLTYPE)_(LANGUAGE).(FILEEXTENSION) +############################################################################################### + +sub set_download_filename +{ + my ($languagestringref, $allvariables) = @_; + + my $start = get_downloadname_productname($allvariables); + my $versionstring = get_download_version($allvariables); + my $platform = get_download_platformname(); + my $architecture = get_download_architecture(); + my $content = get_download_content($allvariables); + my $functionality = get_download_functionality($allvariables); + my $language = get_downloadname_language($languagestringref); + + # Setting the extension happens automatically + + my $filename = $start . "_" . $versionstring . "_" . $platform . "_" . $architecture . "_" . $content . "_" . $functionality . "_" . $language; + + # get rid of duplicit "_" delimiters when some strings are empty + $filename =~ s/\_\_\_/\_/g; + $filename =~ s/\_\_/\_/g; + $filename =~ s/\_\s*$//; + + $installer::globals::ooodownloadfilename = $filename; + + return $filename; +} + + +######################################################### +# Creating a tar.gz file +######################################################### + +sub create_tar_gz_file_from_directory +{ + my ($installdir, $usefakeroot, $downloaddir, $downloadfilename) = @_; + + my $infoline = ""; + + my $packdir = $installdir; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$packdir); + my $changedir = $installdir; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$changedir); + + my $fakerootstring = ""; + if ( $usefakeroot ) { $fakerootstring = "fakeroot"; } + + $installer::globals::downloadfileextension = ".tar.gz"; + $installer::globals::downloadfilename = $downloadfilename . $installer::globals::downloadfileextension; + my $targzname = $downloaddir . $installer::globals::separator . $installer::globals::downloadfilename; + + # fdo#67060 - install script is for RPM only + if ( -e "$installdir/install" && !$installer::globals::isrpmbuild ) + { + unlink("$installdir/install"); + } + + my $systemcall = "cd $changedir; $fakerootstring tar -cf - $packdir | $installer::globals::packertool > $targzname"; + + my $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + return $targzname; +} + +############################################################## +# Returning the complete block in all languages +# for a specified string +############################################################## + +sub get_language_block_from_language_file +{ + my ($searchstring, $languagefile) = @_; + + my @language_block = (); + + for ( my $i = 0; $i <= $#{$languagefile}; $i++ ) + { + if ( ${$languagefile}[$i] =~ /^\s*\[\s*$searchstring\s*\]\s*$/ ) + { + my $counter = $i; + + push(@language_block, ${$languagefile}[$counter]); + $counter++; + + while (( $counter <= $#{$languagefile} ) && (!( ${$languagefile}[$counter] =~ /^\s*\[/ ))) + { + push(@language_block, ${$languagefile}[$counter]); + $counter++; + } + + last; + } + } + + return \@language_block; +} + +############################################################## +# Returning a specific language string from the block +# of all translations +############################################################## + +sub get_language_string_from_language_block +{ + my ($language_block, $language) = @_; + + my $newstring = ""; + + for ( my $i = 0; $i <= $#{$language_block}; $i++ ) + { + if ( ${$language_block}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + last; + } + } + + if ( $newstring eq "" ) + { + $language = "en-US"; # defaulting to english + + for ( my $i = 0; $i <= $#{$language_block}; $i++ ) + { + if ( ${$language_block}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + last; + } + } + } + + return $newstring; +} + +#################################################### +# Creating download installation sets +#################################################### + +sub create_download_sets +{ + my ($installationdir, $includepatharrayref, $allvariableshashref, $downloadname, $languagestringref, $languagesarrayref) = @_; + + my $infoline = ""; + + my $force = 1; # print this message even in 'quiet' mode + installer::logger::print_message( "\n******************************************\n" ); + installer::logger::print_message( "... creating download installation set ...\n", $force ); + installer::logger::print_message( "******************************************\n" ); + + installer::logger::include_header_into_logfile("Creating download installation sets:"); + + my $firstdir = $installationdir; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$firstdir); + + my $lastdir = $installationdir; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$lastdir); + + if ( $installer::globals::iswindowsbuild && $lastdir =~ /\./ ) { $lastdir =~ s/\./_download_inprogress\./ } + else { $lastdir = $lastdir . "_download_inprogress"; } + + # removing existing directory "_native_packed_inprogress" and "_native_packed_witherror" and "_native_packed" + + my $downloaddir = $firstdir . $lastdir; + + if ( -d $downloaddir ) { installer::systemactions::remove_complete_directory($downloaddir); } + + my $olddir = $downloaddir; + $olddir =~ s/_inprogress/_witherror/; + if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); } + + $olddir = $downloaddir; + $olddir =~ s/_inprogress//; + if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); } + + # creating the new directory + + installer::systemactions::create_directory($downloaddir); + + $installer::globals::saveinstalldir = $downloaddir; + + # evaluating the name of the download file + + $downloadname = set_download_filename($languagestringref, $allvariableshashref); + + if ( ! $installer::globals::iswindowsbuild ) # Unix specific part + { + + # whether to use fakeroot (only required for Solaris and Linux) + my $usefakeroot = 0; + if (( $installer::globals::issolarisbuild ) || ( $installer::globals::islinuxbuild )) { $usefakeroot = 1; } + + my $downloadfile = create_tar_gz_file_from_directory($installationdir, $usefakeroot, $downloaddir, $downloadname); + } + + return $downloaddir; +} + +1; diff --git a/solenv/bin/modules/installer/environment.pm b/solenv/bin/modules/installer/environment.pm new file mode 100644 index 000000000..b45227f8a --- /dev/null +++ b/solenv/bin/modules/installer/environment.pm @@ -0,0 +1,131 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::environment; + +use installer::globals; + +###################################################### +# Create path variables from environment variables +###################################################### + +sub create_pathvariables +{ + my ($environment) = @_; + + my %variables = (); + + # The following variables are needed in the path file list + # solarenvpath, os, pmiscpath + + my $solarenvpath = ""; + + if ( $ENV{'SO_PACK'} ) { $solarenvpath = $ENV{'SO_PACK'}; } + # overriding with STAR_INSTPATH, if set + if ( $ENV{'STAR_INSTPATH'} ) { $solarenvpath = $ENV{'STAR_INSTPATH'}; } + + $variables{'solarenvpath'} = $solarenvpath; + + my $localpath = $environment->{'LOCAL_OUT'}; + $variables{'localpath'} = $localpath; + + my $localcommonpath = $environment->{'LOCAL_COMMON_OUT'}; + $variables{'localcommonpath'} = $localcommonpath; + + my $installscriptdir = $environment->{'WORKDIR'} . $installer::globals::separator . "InstallScriptTarget"; + $variables{'installscriptdir'} = $installscriptdir; + + my $extensionsdir = $environment->{'WORKDIR'} . $installer::globals::separator . "Extension"; + $variables{'extensionsdir'} = $extensionsdir; + + my $customtargetpath = $environment->{'WORKDIR'} . $installer::globals::separator . "CustomTarget"; + $variables{'customtargetpath'} = $customtargetpath; + + my $filelistpath = $environment->{'WORKDIR'}; + $variables{'filelistpath'} = $filelistpath; + + my $licensepath = $environment->{'WORKDIR'} . $installer::globals::separator . "CustomTarget/readlicense_oo/license"; + $variables{'licensepath'} = $licensepath; + + my $packinfopath = $environment->{'SRCDIR'} . $installer::globals::separator . "setup_native/source/packinfo"; + $variables{'packinfopath'} = $packinfopath; + + return \%variables; +} + +################################################## +# Replacing tilde in paths, because of +# problem with deep recursion (task 104830) +################################################## + +sub check_tilde_in_directory +{ + if ( $ENV{'HOME'} ) + { + my $home = $ENV{'HOME'}; + $home =~ s/\Q$installer::globals::separator\E\s*$//; + $installer::globals::localinstalldir =~ s/~/$home/; + my $infoline = "Info: Changing LOCALINSTALLDIR to $installer::globals::localinstalldir\n"; + push(@installer::globals::logfileinfo, $infoline); + } + else + { + # exit, because "~" is not allowed, if HOME is not set + my $infoline = "ERROR: If \"~\" is used in \"LOCALINSTALLDIR\", environment variable \"HOME\" needs to be defined!\n"; + push(@installer::globals::logfileinfo, $infoline); + die 'If "~" is used in "LOCALINSTALLDIR", environment variable "HOME" needs to be defined!'; + } +} + +################################################## +# Setting some fundamental global variables. +# All these variables can be overwritten +# by parameters. +################################################## + +sub set_global_environment_variables +{ + my ( $environment ) = @_; + + $installer::globals::build = $environment->{'LIBO_VERSION_MAJOR'}.$environment->{'LIBO_VERSION_MINOR'}."0"; + $installer::globals::os = $environment->{'OS'}; + $installer::globals::com = $environment->{'COM'}; + $installer::globals::cpuname = $environment->{'CPUNAME'}; + $installer::globals::platformid = $environment->{'PLATFORMID'}; + + if ( $ENV{'ENABLE_DBGUTIL'} ) {} else { $installer::globals::pro = 1; } + + if ( $ENV{'VERBOSE'} && ( (lc $ENV{'VERBOSE'}) eq "false" ) ) { $installer::globals::quiet = 1; } + if ( $ENV{'PREPARE_WINPATCH'} ) { $installer::globals::prepare_winpatch = 1; } + if ( $ENV{'PREVIOUS_IDT_DIR'} ) { $installer::globals::previous_idt_dir = $ENV{'PREVIOUS_IDT_DIR'}; } + if ( $ENV{'LOCALINSTALLDIR'} ) { $installer::globals::localinstalldir = $ENV{'LOCALINSTALLDIR'}; } + if ( $ENV{'LOCALUNPACKDIR'} ) { $installer::globals::localunpackdir = $ENV{'LOCALUNPACKDIR'}; } + if ( $ENV{'MAX_LANG_LENGTH'} ) { $installer::globals::max_lang_length = $ENV{'MAX_LANG_LENGTH'}; } + + if ( $ENV{'RPM'} ) { $installer::globals::rpm = $ENV{'RPM'}; } + if ( $ENV{'DONTCOMPRESS'} ) { $installer::globals::solarisdontcompress = 1; } + if ( $ENV{'IGNORE_ERROR_IN_LOGFILE'} ) { $installer::globals::ignore_error_in_logfile = 1; } + if (( $ENV{'ENABLE_STRIP'} ) && ( $ENV{'ENABLE_STRIP'} ne '' )) { $installer::globals::strip = 1; } + if (( $ENV{'DISABLE_STRIP'} ) && ( $ENV{'DISABLE_STRIP'} ne '' )) { $installer::globals::strip = 0; } + + if ( $installer::globals::localinstalldir ) { $installer::globals::localinstalldirset = 1; } + # Special handling, if LOCALINSTALLDIR contains "~" in the path + if ( $installer::globals::localinstalldir =~ /^\s*\~/ ) { check_tilde_in_directory(); } +} + +1; diff --git a/solenv/bin/modules/installer/epmfile.pm b/solenv/bin/modules/installer/epmfile.pm new file mode 100644 index 000000000..495366823 --- /dev/null +++ b/solenv/bin/modules/installer/epmfile.pm @@ -0,0 +1,2669 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::epmfile; + +use Cwd qw(); +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::packagelist; +use installer::pathanalyzer; +use installer::remover; +use installer::scpzipfiles; +use installer::scriptitems; +use installer::systemactions; +use POSIX; + +# please Debian packaging, fdo#53341 +sub debian_rewrite($) +{ + my $dep = shift; + if ( $installer::globals::debian ) { + $dep =~ s/_/-/g; # Debian allows no underline in package name + $dep = lc ($dep); + } + return $dep; +} + +############################################################################ +# Reading the package map to find Solaris package names for +# the corresponding abbreviations +############################################################################ + +sub read_packagemap +{ + my ($allvariables, $includepatharrayref, $languagesarrayref) = @_; + + my $packagemapname = ""; + if ( $allvariables->{'PACKAGEMAP'} ) { $packagemapname = $allvariables->{'PACKAGEMAP'}; } + if ( $packagemapname eq "" ) { installer::exiter::exit_program("ERROR: Property PACKAGEMAP must be defined!", "read_packagemap"); } + + my $infoline = "\n\nCollected abbreviations and package names:\n"; + push(@installer::globals::logfileinfo, $infoline); + + # Can be a comma separated list. All files have to be found in include paths + my $allpackagemapnames = installer::converter::convert_stringlist_into_hash(\$packagemapname, ","); + foreach my $onepackagemapname ( keys %{$allpackagemapnames} ) + { + my $packagemapref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$onepackagemapname, $includepatharrayref, 0); + + if ( $$packagemapref eq "" ) { installer::exiter::exit_program("ERROR: Could not find package map file \"$onepackagemapname\" (property PACKAGEMAP)!", "read_packagemap"); } + + my $packagemapcontent = installer::files::read_file($$packagemapref); + + for ( my $i = 0; $i <= $#{$packagemapcontent}; $i++ ) + { + my $line = ${$packagemapcontent}[$i]; + + if ( $line =~ /^\s*\#/ ) { next; } # comment line + if ( $line =~ /^\s*$/ ) { next; } # empty line + + if ( $line =~ /^\s*(.*?)\t(.*?)\s*$/ ) + { + my $abbreviation = $1; + my $packagename = $2; + installer::packagelist::resolve_packagevariables(\$abbreviation, $allvariables, 0); + installer::packagelist::resolve_packagevariables(\$packagename, $allvariables, 0); + + # Special handling for language strings %LANGUAGESTRING + + if (( $abbreviation =~ /\%LANGUAGESTRING/ ) || ( $packagename =~ /\%LANGUAGESTRING/ )) + { + foreach my $onelang ( @{$languagesarrayref} ) + { + my $local_abbreviation = $abbreviation; + my $local_packagename = $packagename; + $local_abbreviation =~ s/\%LANGUAGESTRING/$onelang/g; + $local_packagename =~ s/\%LANGUAGESTRING/$onelang/g; + + # Logging all abbreviations and packagenames + $infoline = "$onelang : $local_abbreviation : $local_packagename\n"; + push(@installer::globals::logfileinfo, $infoline); + + if ( exists($installer::globals::dependfilenames{$local_abbreviation}) ) + { + installer::exiter::exit_program("ERROR: Packagename for Solaris package $local_abbreviation already defined ($installer::globals::dependfilenames{$local_abbreviation})!", "read_packagemap"); + } + else + { + $installer::globals::dependfilenames{$local_abbreviation} = $local_packagename; + } + } + } + else + { + # Logging all abbreviations and packagenames + $infoline = "$abbreviation : $packagename\n"; + push(@installer::globals::logfileinfo, $infoline); + + if ( exists($installer::globals::dependfilenames{$abbreviation}) ) + { + installer::exiter::exit_program("ERROR: Packagename for Solaris package $abbreviation already defined ($installer::globals::dependfilenames{$abbreviation})!", "read_packagemap"); + } + else + { + $installer::globals::dependfilenames{$abbreviation} = $packagename; + } + } + } + else + { + my $errorline = $i + 1; + installer::exiter::exit_program("ERROR: Wrong syntax in file \"$onepackagemapname\" (line $errorline)!", "read_packagemap"); + } + } + } + + $infoline = "\n\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +########################################################## +# Filling the epm file with directories, files and links +########################################################## + +sub put_directories_into_epmfile +{ + my ($directoriesarrayref, $epmfileref, $allvariables, $packagerootpath) = @_; + my $group = "bin"; + + if ( $installer::globals::islinuxbuild ) + { + $group = "root"; + } + + for ( my $i = 0; $i <= $#{$directoriesarrayref}; $i++ ) + { + my $onedir = ${$directoriesarrayref}[$i]; + my $dir = ""; + + if ( $onedir->{'Dir'} ) { $dir = $onedir->{'Dir'}; } + + if ((!($dir =~ /\bPREDEFINED_/ )) || ( $dir =~ /\bPREDEFINED_PROGDIR\b/ )) + { + my $hostname = $onedir->{'HostName'}; + + my $line = "d 755 root $group $hostname -\n"; + + push(@{$epmfileref}, $line) + } + } +} + +sub put_files_into_epmfile +{ + my ($filesinproductarrayref, $epmfileref) = @_; + + for ( my $i = 0; $i <= $#{$filesinproductarrayref}; $i++ ) + { + my $onefile = ${$filesinproductarrayref}[$i]; + + my $unixrights = $onefile->{'UnixRights'}; + my $destination = $onefile->{'destination'}; + my $sourcepath = $onefile->{'sourcepath'}; + + my $filetype = "f"; + my $styles = ""; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + if ( $styles =~ /\bCONFIGFILE\b/ ) { $filetype = "c"; } + + my $group = "bin"; + if ( $installer::globals::islinuxbuild ) { $group = "root"; } + if (( $installer::globals::issolarisbuild ) && ( $onefile->{'SolarisGroup'} )) { $group = $onefile->{'SolarisGroup'}; } + + my $line = "$filetype $unixrights root $group $destination $sourcepath\n"; + + push(@{$epmfileref}, $line); + } +} + +sub put_links_into_epmfile +{ + my ($linksinproductarrayref, $epmfileref) = @_; + my $group = "bin"; + + if ( $installer::globals::islinuxbuild ) + { + $group = "root"; + } + + + for ( my $i = 0; $i <= $#{$linksinproductarrayref}; $i++ ) + { + my $onelink = ${$linksinproductarrayref}[$i]; + my $destination = $onelink->{'destination'}; + my $destinationfile = $onelink->{'destinationfile'}; + + my $line = "l 000 root $group $destination $destinationfile\n"; + + push(@{$epmfileref}, $line) + } +} + +sub put_unixlinks_into_epmfile +{ + my ($unixlinksinproductarrayref, $epmfileref) = @_; + my $group = "bin"; + + if ( $installer::globals::islinuxbuild ) { $group = "root"; } + + for ( my $i = 0; $i <= $#{$unixlinksinproductarrayref}; $i++ ) + { + my $onelink = ${$unixlinksinproductarrayref}[$i]; + my $destination = $onelink->{'destination'}; + my $target = $onelink->{'Target'}; + + my $line = "l 000 root $group $destination $target\n"; + + push(@{$epmfileref}, $line) + } +} + +############################################### +# Creating epm header file +############################################### + +sub create_epm_header +{ + my ($variableshashref, $filesinproduct, $languagesref, $onepackage) = @_; + + my @epmheader = (); + + my ($licensefilename, $readmefilename, $readmefilenameen); + + my $foundlicensefile = 0; + my $foundreadmefile = 0; + + my $line = ""; + my $infoline = ""; + + # %product LibreOffice Software + # %version 2.0 + # %description A really great software + # %copyright 1999-2003 by OOo + # %vendor LibreOffice + # %license /test/replace/01/LICENSE01 + # %readme /test/replace/01/README01 + # %requires foo + # %provides bar + # %replaces bar + # %incompat bar + + # The first language in the languages array determines the language of license and readme file + + my $searchlanguage = ${$languagesref}[0]; + + # using the description for the %product line in the epm list file + + my $productnamestring = $onepackage->{'description'}; + installer::packagelist::resolve_packagevariables(\$productnamestring, $variableshashref, 0); + if ( $variableshashref->{'PRODUCTEXTENSION'} ) { $productnamestring = $productnamestring . $variableshashref->{'PRODUCTEXTENSION'}; } + + $line = "%product" . " " . $productnamestring . "\n"; + push(@epmheader, $line); + + # Determining the release version + # This release version has to be listed in the line %version : %version versionnumber releasenumber + + if ( ! $onepackage->{'packageversion'} ) { installer::exiter::exit_program("ERROR: No packageversion defined for package: $onepackage->{'module'}!", "create_epm_header"); } + $installer::globals::packageversion = $onepackage->{'packageversion'}; + installer::packagelist::resolve_packagevariables(\$installer::globals::packageversion, $variableshashref, 0); + if ( $variableshashref->{'PACKAGEREVISION'} ) { $installer::globals::packagerevision = $variableshashref->{'PACKAGEREVISION'}; } + + $line = "%version" . " " . $installer::globals::packageversion . "\n"; + push(@epmheader, $line); + + $line = "%release" . " " . $installer::globals::packagerevision . "\n"; + if ( $installer::globals::isrpmbuild ) { $line = "%release" . " " . $installer::globals::buildid . "\n"; } + push(@epmheader, $line); + + # Description, Copyright and Vendor are multilingual and are defined in + # the string file for the header file ($headerfileref) + + my $descriptionstring = $onepackage->{'description'}; + installer::packagelist::resolve_packagevariables(\$descriptionstring, $variableshashref, 0); + $line = "%description" . " " . $descriptionstring . "\n"; + push(@epmheader, $line); + + my $copyrightstring = $onepackage->{'copyright'}; + installer::packagelist::resolve_packagevariables(\$copyrightstring, $variableshashref, 0); + $line = "%copyright" . " " . $copyrightstring . "\n"; + push(@epmheader, $line); + + my $vendorstring = $onepackage->{'vendor'}; + installer::packagelist::resolve_packagevariables(\$vendorstring, $variableshashref, 0); + $line = "%vendor" . " " . $vendorstring . "\n"; + push(@epmheader, $line); + + # License and Readme file can be included automatically from the file list + + if ( $installer::globals::iswindowsbuild ) + { + $licensefilename = "license.txt"; + $readmefilename = "readme.txt"; + $readmefilenameen = "readme_en-US.txt"; + } + else + { + $licensefilename = "LICENSE"; + $readmefilename = "README"; + $readmefilenameen = "README_en-US"; + } + + if (( $installer::globals::languagepack ) # in language packs and help packs the files LICENSE and README are removed, because they are not language specific + || ( $installer::globals::helppack ) + || ( $variableshashref->{'NO_README_IN_ROOTDIR'} )) + { + if ( $installer::globals::iswindowsbuild ) + { + $licensefilename = "license.txt"; + $readmefilename = "readme_$searchlanguage.txt"; + } + else + { + $licensefilename = "LICENSE"; + $readmefilename = "README_$searchlanguage"; + } + } + + my $license_in_package_defined = 0; + + if ( $installer::globals::issolarisbuild ) + { + if ( $onepackage->{'solariscopyright'} ) + { + $licensefilename = $onepackage->{'solariscopyright'}; + $license_in_package_defined = 1; + } + } + + # Process for Linux packages, in which only a very basic license file is + # included into the package. + + if ( $installer::globals::islinuxbuild ) + { + if ( $variableshashref->{'COPYRIGHT_INTO_LINUXPACKAGE'} ) + { + $licensefilename = "linuxcopyrightfile"; + $license_in_package_defined = 1; + } + } + + # searching for and readme file; + # URE uses special README; others use README_en-US + # it does not matter which one is passed for epm if both are packaged + foreach my $possiblereadmefilename ($readmefilenameen, $readmefilename) + { + last if ($foundreadmefile); + for ( my $i = 0; $i <= $#{$filesinproduct}; $i++ ) + { + my $onefile = ${$filesinproduct}[$i]; + my $filename = $onefile->{'Name'}; + # in the SDK it's in subdirectory sdk/share/readme + if ( $filename =~ /$possiblereadmefilename$/ ) + { + $foundreadmefile = 1; + $line = "%readme" . " " . $onefile->{'sourcepath'} . "\n"; + push(@epmheader, $line); + last; + } + } + } + + # the readme file need not be packaged more times in the help content + # it needs to be installed in parallel with the main package anyway + # try to find the README file between all available files (not only between the packaged) + if (!($foundreadmefile) && $installer::globals::helppack) + { + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$readmefilenameen, "" , 0); + if($$fileref ne "" ) + { + $infoline = "Fallback to readme file: \"$$fileref\"!\n"; + push(@installer::globals::logfileinfo, $infoline); + + $foundreadmefile = 1; + $line = "%readme" . " " . $$fileref . "\n"; + push(@epmheader, $line); + } + } + + # searching for and license file + + if ( $license_in_package_defined ) + { + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$licensefilename, "" , 0); + + if ( $$fileref eq "" ) { installer::exiter::exit_program("ERROR: Could not find license file $licensefilename (A)!", "create_epm_header"); } + + # Special handling to add the content of the file "license_en-US" to the solaris copyrightfile. But not for all products + + if (( $installer::globals::issolarispkgbuild ) && ( ! $variableshashref->{'NO_LICENSE_INTO_COPYRIGHT'} )) + { + if ( ! $installer::globals::englishlicenseset ) { _set_english_license() } + + # The location for the new file + my $languagestring = ""; + for ( my $i = 0; $i <= $#{$languagesref}; $i++ ) { $languagestring = $languagestring . "_" . ${$languagesref}[$i]; } + $languagestring =~ s/^\s*_//; + + my $copyrightdir = installer::systemactions::create_directories("copyright", \$languagestring); + + my $copyrightfile = installer::files::read_file($$fileref); + + # Adding license content to copyright file + push(@{$copyrightfile}, "\n"); + for ( my $i = 0; $i <= $#{$installer::globals::englishlicense}; $i++ ) { push(@{$copyrightfile}, ${$installer::globals::englishlicense}[$i]); } + + # New destination for $$fileref + $$fileref = $copyrightdir . $installer::globals::separator . "solariscopyrightfile_" . $onepackage->{'module'}; + if ( -f $$fileref ) { unlink $$fileref; } + installer::files::save_file($$fileref, $copyrightfile); + } + + $infoline = "Using license file: \"$$fileref\"!\n"; + push(@installer::globals::logfileinfo, $infoline); + + $foundlicensefile = 1; + $line = "%license" . " " . $$fileref . "\n"; + push(@epmheader, $line); + } + else + { + for my $onefile (@{$filesinproduct}) + { + # in the SDK it's in subdirectory sdk/share/readme so try to match that + if ($onefile->{'Name'} =~ /$licensefilename$/) + { + push @epmheader, "%license" . " " . $onefile->{'sourcepath'} . "\n"; + $foundlicensefile = 1; + last; + } + } + + # the license file need not be packaged more times in the langpacks + # they need to be installed in parallel with the main package anyway + # try to find the LICENSE file between all available files (not only between the packaged) + if (!($foundlicensefile) && ($installer::globals::languagepack || $installer::globals::helppack)) + { + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$licensefilename, "" , 0); + if($$fileref ne "" ) + { + $infoline = "Fallback to license file: \"$$fileref\"!\n"; + push(@installer::globals::logfileinfo, $infoline); + + $foundlicensefile = 1; + $line = "%license" . " " . $$fileref . "\n"; + push(@epmheader, $line); + } + } + } + + if (!($foundlicensefile)) + { + installer::exiter::exit_program("ERROR: Could not find license file $licensefilename (B)", "create_epm_header"); + } + + if (!($foundreadmefile)) + { + installer::exiter::exit_program("ERROR: Could not find readme file $readmefilename (C)", "create_epm_header"); + } + + # including %replaces + + my $replaces = ""; + + if ( $installer::globals::issolarispkgbuild ) + { + $replaces = "solarisreplaces"; # the name in the packagelist + } + elsif ( $installer::globals::islinuxbuild ) + { + $replaces = "linuxreplaces"; # the name in the packagelist + } + + if ( $replaces ) + { + if ( $onepackage->{$replaces} ) + { + my $replacesstring = $onepackage->{$replaces}; + + my $allreplaces = installer::converter::convert_stringlist_into_array(\$replacesstring, ","); + + for ( my $i = 0; $i <= $#{$allreplaces}; $i++ ) + { + my $onereplaces = ${$allreplaces}[$i]; + $onereplaces =~ s/\s*$//; + installer::packagelist::resolve_packagevariables(\$onereplaces, $variableshashref, 1); + $onereplaces = debian_rewrite($onereplaces); + $line = "%replaces" . " " . $onereplaces . "\n"; + push(@epmheader, $line); + } + } + } + + # including %incompat + + my $incompat = ""; + + if (( $installer::globals::issolarispkgbuild ) && ( ! $installer::globals::patch )) + { + $incompat = "solarisincompat"; # the name in the packagelist + } + elsif (( $installer::globals::islinuxbuild ) && ( ! $installer::globals::patch )) + { + $incompat = "linuxincompat"; # the name in the packagelist + } + + if (( $incompat ) && ( ! $installer::globals::patch )) + { + if ( $onepackage->{$incompat} ) + { + my $incompatstring = $onepackage->{$incompat}; + + my $allincompat = installer::converter::convert_stringlist_into_array(\$incompatstring, ","); + + for ( my $i = 0; $i <= $#{$allincompat}; $i++ ) + { + my $oneincompat = ${$allincompat}[$i]; + $oneincompat =~ s/\s*$//; + installer::packagelist::resolve_packagevariables(\$oneincompat, $variableshashref, 1); + $oneincompat = debian_rewrite($oneincompat); + $line = "%incompat" . " " . $oneincompat . "\n"; + push(@epmheader, $line); + } + } + } + + # including the directives for %requires and %provides + + my $provides = ""; + my $requires = ""; + + if ( $installer::globals::issolarispkgbuild ) + { + $provides = "solarisprovides"; # the name in the packagelist + $requires = "solarisrequires"; # the name in the packagelist + } + elsif ( $installer::globals::isfreebsdpkgbuild ) + { + $provides = "freebsdprovides"; # the name in the packagelist + $requires = "freebsdrequires"; # the name in the packagelist + } + else + { + $provides = "provides"; # the name in the packagelist + $requires = "requires"; # the name in the packagelist + } + + my $isdict = 0; + if ( $onepackage->{'packagename'} =~ /-dict-/ ) { $isdict = 1; } + + if ( $onepackage->{$provides} ) + { + my $providesstring = $onepackage->{$provides}; + + my $allprovides = installer::converter::convert_stringlist_into_array(\$providesstring, ","); + + for ( my $i = 0; $i <= $#{$allprovides}; $i++ ) + { + my $oneprovides = ${$allprovides}[$i]; + $oneprovides =~ s/\s*$//; + installer::packagelist::resolve_packagevariables(\$oneprovides, $variableshashref, 1); + $oneprovides = debian_rewrite($oneprovides); + $line = "%provides" . " " . $oneprovides . "\n"; + push(@epmheader, $line); + } + } + + if ( $onepackage->{$requires} ) + { + my $requiresstring = $onepackage->{$requires}; + + # The requires string can contain the separator "," in the names (descriptions) of the packages + # (that are required for Solaris depend files). Therefore "," inside such a description has to + # masked with a backslash. + # This masked separator need to be found and replaced, before the stringlist is converted into an array. + # This replacement has to be turned back after the array is created. + + my $replacementstring = "COMMAREPLACEMENT"; + $requiresstring = installer::converter::replace_masked_separator($requiresstring, ",", "$replacementstring"); + + my $allrequires = installer::converter::convert_stringlist_into_array(\$requiresstring, ","); + + installer::converter::resolve_masked_separator($allrequires, ",", $replacementstring); + + for ( my $i = 0; $i <= $#{$allrequires}; $i++ ) + { + my $onerequires = ${$allrequires}[$i]; + $onerequires =~ s/\s*$//; + installer::packagelist::resolve_packagevariables2(\$onerequires, $variableshashref, 0, $isdict); + $onerequires = debian_rewrite($onerequires); + $line = "%requires" . " " . $onerequires . "\n"; + push(@epmheader, $line); + } + } + + return \@epmheader; +} + +####################################### +# Adding header to epm file +####################################### + +sub adding_header_to_epm_file +{ + my ($epmfileref, $epmheaderref) = @_; + + for ( my $i = 0; $i <= $#{$epmheaderref}; $i++ ) + { + push( @{$epmfileref}, ${$epmheaderref}[$i] ); + } + + push( @{$epmfileref}, "\n\n" ); +} + +##################################################### +# Replace one in shell scripts ( ${VARIABLENAME} ) +##################################################### + +sub replace_variable_in_shellscripts +{ + my ($scriptref, $variable, $searchstring) = @_; + + for ( my $i = 0; $i <= $#{$scriptref}; $i++ ) + { + ${$scriptref}[$i] =~ s/\$\{$searchstring\}/$variable/g; + } +} + +################################################ +# Replacing many variables in shell scripts +################################################ + +sub replace_many_variables_in_shellscripts +{ + my ($scriptref, $variableshashref) = @_; + + my $key; + + foreach $key (keys %{$variableshashref}) + { + my $value = $variableshashref->{$key}; + replace_variable_in_shellscripts($scriptref, $value, $key); + } +} + +####################################### +# Adding shell scripts to epm file +####################################### + +sub adding_shellscripts_to_epm_file +{ + my ($epmfileref, $shellscriptsfilename, $localrootpath, $allvariableshashref, $filesinpackage) = @_; + + push( @{$epmfileref}, "\n\n" ); + + my $shellscriptsfileref = installer::files::read_file($shellscriptsfilename); + + replace_variable_in_shellscripts($shellscriptsfileref, $localrootpath, "rootpath"); + + replace_many_variables_in_shellscripts($shellscriptsfileref, $allvariableshashref); + + for ( my $i = 0; $i <= $#{$shellscriptsfileref}; $i++ ) + { + push( @{$epmfileref}, ${$shellscriptsfileref}[$i] ); + } + + push( @{$epmfileref}, "\n" ); +} + +################################################# +# Determining the epm on the system +################################################# + +sub find_epm_on_system +{ + my ($includepatharrayref) = @_; + + installer::logger::include_header_into_logfile("Check epm on system"); + + my $epmname = "epm"; + + # epm should be defined through the configure script but we need to + # check for it to be defined because of the Sun environment. + # Check the environment variable first and if it is not defined, + # or if it is but the location is not executable, search further. + # It has to be found in the solver or it has to be in the path + # (saved in $installer::globals::epm_in_path) or we get the specified + # one through the environment (i.e. when --with-epm=... is specified) + + if ($ENV{'EPM'}) + { + if (($ENV{'EPM'} ne "") && (-x "$ENV{'EPM'}")) + { + $epmname = $ENV{'EPM'}; + } + else + { + installer::exiter::exit_program("Environment variable EPM set (\"$ENV{'EPM'}\"), but file does not exist or is not executable!", "find_epm_on_system"); + } + } + else + { + my $epmfileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$epmname, $includepatharrayref, 0); + + if (($$epmfileref eq "") && (!($installer::globals::epm_in_path))) { installer::exiter::exit_program("ERROR: Could not find program $epmname!", "find_epm_on_system"); } + if (($$epmfileref eq "") && ($installer::globals::epm_in_path)) { $epmname = $installer::globals::epm_path; } + if (!($$epmfileref eq "")) { $epmname = $$epmfileref; } + } + + my $infoline = "Using epmfile: $epmname\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $epmname; +} + +################################################# +# Determining the epm patch state +# saved in $installer::globals::is_special_epm +################################################# + +sub set_patch_state +{ + my ($epmexecutable) = @_; + + my $infoline = ""; + + my $systemcall = "$epmexecutable |"; + open (EPMPATCH, "$systemcall"); + + while () + { + chop; + if ( $_ =~ /Patched for .*Office/ ) { $installer::globals::is_special_epm = 1; } + } + + close (EPMPATCH); + + if ( $installer::globals::is_special_epm ) + { + $infoline = "\nPatch state: This is a patched version of epm!\n\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "\nPatch state: This is an unpatched version of epm!\n\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + if ( ( $installer::globals::is_special_epm ) && (($installer::globals::isrpmbuild) || ($installer::globals::issolarispkgbuild)) ) + { + # Special postprocess handling only for Linux RPM and Solaris packages + $installer::globals::postprocess_specialepm = 1; + $installer::globals::postprocess_standardepm = 0; + } + else + { + $installer::globals::postprocess_specialepm = 0; + $installer::globals::postprocess_standardepm = 1; + } +} + +################################################# +# Calling epm to create the installation sets +################################################# + +sub call_epm +{ + my ($epmname, $epmlistfilename, $packagename, $includepatharrayref) = @_; + + installer::logger::include_header_into_logfile("epm call for $packagename"); + + my $packageformat = $installer::globals::packageformat; + + my $localpackagename = $packagename; + # Debian allows only lowercase letters in package name + if ( $installer::globals::debian ) { $localpackagename = lc($localpackagename); } + + my $outdirstring = ""; + if ( $installer::globals::epmoutpath ne "" ) { $outdirstring = " --output-dir $installer::globals::epmoutpath"; } + + # Debian package build needs to be run with fakeroot for correct ownerships/permissions + + my $fakerootstring = ""; + + if ( $installer::globals::debian ) { $fakerootstring = "fakeroot "; } + + my $extraflags = ""; + if ($ENV{'EPM_FLAGS'}) { $extraflags = $ENV{'EPM_FLAGS'}; } + + $extraflags .= ' -g' unless $installer::globals::strip; + + my $verboseflag = "-v"; + if ( ! $installer::globals::quiet ) { $verboseflag = "-v2"; }; + + my $systemcall = $fakerootstring . $epmname . " -f " . $packageformat . " " . $extraflags . " " . $localpackagename . " " . $epmlistfilename . $outdirstring . " " . $verboseflag . " " . " 2\>\&1 |"; + + installer::logger::print_message( "... $systemcall ...\n" ); + + my $maxepmcalls = 3; + + for ( my $i = 1; $i <= $maxepmcalls; $i++ ) + { + my @epmoutput = (); + + open (EPM, "$systemcall"); + while () {push(@epmoutput, $_); } + close (EPM); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + my $infoline = "Systemcall (Try $i): $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $j = 0; $j <= $#epmoutput; $j++ ) + { + if ( $i < $maxepmcalls ) { $epmoutput[$j] =~ s/\bERROR\b/PROBLEM/ig; } + push( @installer::globals::logfileinfo, "$epmoutput[$j]"); + } + + if ($returnvalue) + { + $infoline = "Try $i : Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + if ( $i == $maxepmcalls ) { installer::exiter::exit_program("ERROR: \"$systemcall\"!", "call_epm"); } + } + else + { + installer::logger::print_message( "Success (Try $i): \"$systemcall\"\n" ); + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } +} + +##################################################################### +# Adding the new line for relocatables into pkginfo file (Solaris) +# or spec file (Linux) created by epm +##################################################################### + +sub add_one_line_into_file +{ + my ($file, $insertline, $filename) = @_; + + if ( $installer::globals::issolarispkgbuild ) + { + push(@{$file}, $insertline); # simply adding at the end of pkginfo file + } + + if ( $installer::globals::isrpmbuild ) + { + # Adding behind the line beginning with: Group: + + my $inserted_line = 0; + + for ( my $i = 0; $i <= $#{$file}; $i++ ) + { + if ( ${$file}[$i] =~ /^\s*Group\:\s*/ ) + { + splice(@{$file},$i+1,0,$insertline); + $inserted_line = 1; + last; + } + } + + if (! $inserted_line) { installer::exiter::exit_program("ERROR: Did not find string \"Group:\" in file: $filename", "add_one_line_into_file"); } + } + + $insertline =~ s/\s*$//; # removing line end for correct logging + my $infoline = "Success: Added line $insertline into file $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +##################################################################### +# Setting the revision VERSION=1.9,REV=66 . +# Also adding the new line: "AutoReqProv: no" +##################################################################### + +sub set_revision_in_pkginfo +{ + my ($file, $filename, $variables, $packagename) = @_; + + my $revisionstring = "\,REV\=" . $installer::globals::packagerevision; + + # Adding also a time string to the revision. Syntax: VERSION=8.0.0,REV=66.2005.01.24 + + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + + $mday = $mday; + $mon = $mon + 1; + $year = $year + 1900; + + if ( $mday < 10 ) { $mday = "0" . $mday; } + if ( $mon < 10 ) { $mon = "0" . $mon; } + $datestring = $year . "." . $mon . "." . $mday; + $revisionstring = $revisionstring . "." . $datestring; + + for ( my $i = 0; $i <= $#{$file}; $i++ ) + { + if ( ${$file}[$i] =~ /^\s*(VERSION\=.*?)\s*$/ ) + { + my $oldstring = $1; + my $newstring = $oldstring . $revisionstring; # also adding the date string + ${$file}[$i] =~ s/$oldstring/$newstring/; + my $infoline = "Info: Changed in $filename file: \"$oldstring\" to \"$newstring\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } + + # For Update and Patch reasons, this string can also be kept constant + + my $pkgversion = "SOLSPARCPKGVERSION"; + if ( $installer::globals::issolarisx86build ) { $pkgversion = "SOLIAPKGVERSION"; } + + if (( $variables->{$pkgversion} ) && ( $variables->{$pkgversion} ne "" )) + { + if ( $variables->{$pkgversion} ne "FINALVERSION" ) + { + # In OOo 3.x timeframe, this string is no longer unique for all packages, because of the three layer. + # In the string: "3.0.0,REV=9.2008.09.30" only the part "REV=9.2008.09.30" can be unique for all packages + # and therefore be set as $pkgversion. + # The first part "3.0.0" has to be derived from the + + my $version = $installer::globals::packageversion; + if ( $version =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ ) + { + my $major = $1; + my $minor = $2; + my $micro = $3; + + my $finalmajor = $major; + my $finalminor = $minor; + my $finalmicro = 0; + + $version = "$finalmajor.$finalminor.$finalmicro"; + } + + my $datestring = $variables->{$pkgversion}; + + # Allowing some packages to have another date of creation. + # They can be defined in product definition using a key like "SOLSPARCPKGVERSION_$packagename" + + my $additionalkey = $pkgversion . "_" . $packagename; + if (( $variables->{$additionalkey} ) && ( $variables->{$additionalkey} ne "" )) { $datestring = $variables->{$additionalkey}; } + + my $versionstring = "$version,$datestring"; + + for ( my $i = 0; $i <= $#{$file}; $i++ ) + { + if ( ${$file}[$i] =~ /^\s*(VERSION\=).*?\s*$/ ) + { + my $start = $1; + my $newstring = $start . $versionstring . "\n"; # setting the complete new string + my $oldstring = ${$file}[$i]; + ${$file}[$i] = $newstring; + $oldstring =~ s/\s*$//; + $newstring =~ s/\s*$//; + my $infoline = "Info: Changed in $filename file: \"$oldstring\" to \"$newstring\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } + } + } +} + +######################################################## +# Setting MAXINST=1000 into the pkginfo file. +######################################################## + +sub set_maxinst_in_pkginfo +{ + my ($changefile, $filename) = @_; + + my $newline = "MAXINST\=1000\n"; + + add_one_line_into_file($changefile, $newline, $filename); +} + +############################################################# +# Setting several Solaris variables into the pkginfo file. +############################################################# + +sub set_solaris_parameter_in_pkginfo +{ + my ($changefile, $filename, $allvariables) = @_; + + my $newline = ""; + + # SUNW_PRODNAME + # SUNW_PRODVERS + # SUNW_PKGVERS + # Not: SUNW_PKGTYPE + # HOTLINE + # EMAIL + + my $productname = $allvariables->{'PRODUCTNAME'}; + $newline = "SUNW_PRODNAME=$productname\n"; + add_one_line_into_file($changefile, $newline, $filename); + + my $productversion = ""; + if ( $allvariables->{'PRODUCTVERSION'} ) + { + $productversion = $allvariables->{'PRODUCTVERSION'}; + if ( $allvariables->{'PRODUCTEXTENSION'} ) { $productversion = $productversion . "/" . $allvariables->{'PRODUCTEXTENSION'}; } + } + $newline = "SUNW_PRODVERS=$productversion\n"; + add_one_line_into_file($changefile, $newline, $filename); + + $newline = "SUNW_PKGVERS=1\.0\n"; + add_one_line_into_file($changefile, $newline, $filename); + + if ( $allvariables->{'SUNW_PKGTYPE'} ) + { + $newline = "SUNW_PKGTYPE=$allvariables->{'SUNW_PKGTYPE'}\n"; + add_one_line_into_file($changefile, $newline, $filename); + } + else + { + $newline = "SUNW_PKGTYPE=\n"; + add_one_line_into_file($changefile, $newline, $filename); + } + + $newline = "HOTLINE=Please contact your local service provider\n"; + add_one_line_into_file($changefile, $newline, $filename); + + $newline = "EMAIL=\n"; + add_one_line_into_file($changefile, $newline, $filename); + +} + +##################################################################### +# epm uses as architecture for Solaris x86 "i86pc". This has to be +# changed to "i386". +##################################################################### + +sub fix_architecture_setting +{ + my ($changefile) = @_; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*ARCH=i86pc\s*$/ ) + { + ${$changefile}[$i] =~ s/i86pc/i386/; + last; + } + + } +} + +##################################################################### +# Adding a new line for topdir into specfile, removing old +# topdir if set. +##################################################################### + +sub set_topdir_in_specfile +{ + my ($changefile, $filename, $newepmdir) = @_; + + $newepmdir = Cwd::cwd() . $installer::globals::separator . $newepmdir; # only absolute path allowed + + # removing "%define _topdir", if existing + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*\%define _topdir\s+/ ) + { + my $removeline = ${$changefile}[$i]; + $removeline =~ s/\s*$//; + splice(@{$changefile},$i,1); + my $infoline = "Info: Removed line \"$removeline\" from file $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } + + # Adding "topdir" behind the line beginning with: Group: + + my $inserted_line = 0; + + my $topdirline = "\%define _topdir $newepmdir\n"; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*Group\:\s*/ ) + { + splice(@{$changefile},$i+1,0,$topdirline); + $inserted_line = 1; + $topdirline =~ s/\s*$//; + my $infoline = "Success: Added line $topdirline into file $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + if (! $inserted_line) { installer::exiter::exit_program("ERROR: Did not find string \"Group:\" in file: $filename", "set_topdir_in_specfile"); } + +} + +##################################################################### +# Setting the packager in the spec file +# Syntax: Packager: abc@def +##################################################################### + +sub set_packager_in_specfile +{ + my ($changefile) = @_; + + my $packager = $installer::globals::longmanufacturer; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*Packager\s*:\s*(.+?)\s*$/ ) + { + my $oldstring = $1; + ${$changefile}[$i] =~ s/\Q$oldstring\E/$packager/; + my $infoline = "Info: Changed Packager in spec file from $oldstring to $packager!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } +} + +##################################################################### +# Setting the requirements in the spec file (i81494) +# Syntax: PreReq: "requirements" (only for shared extensions) +##################################################################### + +sub set_prereq_in_specfile +{ + my ($changefile) = @_; + + my $prereq = "PreReq:"; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*Requires:\s*(.+?)\s*$/ ) + { + my $oldstring = ${$changefile}[$i]; + ${$changefile}[$i] =~ s/Requires:/$prereq/; + my $infoline = "Info: Changed requirements in spec file from $oldstring to ${$changefile}[$i]!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } +} + +##################################################################### +# Setting the Auto[Req]Prov line and __find_requires +##################################################################### + +sub set_autoprovreq_in_specfile +{ + my ($changefile, $findrequires, $bindir) = @_; + + my $autoreqprovline = "AutoReqProv\: no\n"; + + if ( $findrequires ) + { + # don't let rpm invoke it, we never want to use AutoReq because + # rpm will generate Requires: config(packagename) + open (FINDREQUIRES, "echo | $bindir/$findrequires |"); + while () { $autoreqprovline .= "Requires: $_\n"; } + close (FINDREQUIRES); + } + + $autoreqprovline .= "%define _binary_filedigest_algorithm 1\n%define _binary_payload w1T.xzdio\n"; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + # Adding "autoreqprov" behind the line beginning with: Group: + if ( ${$changefile}[$i] =~ /^\s*Group\:\s*/ ) + { + splice(@{$changefile},$i+1,0,$autoreqprovline); + $autoreqprovline =~ s/\s*$//; + $infoline = "Success: Added line $autoreqprovline into spec file!\n"; + push( @installer::globals::logfileinfo, $infoline); + + last; + } + } +} + +##################################################################### +# Replacing Copyright with License in the spec file +# Syntax: License: LGPLv3 (or MPLv2 on ALv2, older usages were LGPL, SISSL) +##################################################################### + +sub set_license_in_specfile +{ + my ($changefile, $variableshashref) = @_; + + my $license = $variableshashref->{'LICENSENAME'}; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*Copyright\s*:\s*(.+?)\s*$/ ) + { + ${$changefile}[$i] = "License: $license\n"; + my $infoline = "Info: Replaced Copyright with License: $license !\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } +} + +######################################################### +# Building relocatable Solaris packages means: +# 1. Add "BASEDIR=/opt" into pkginfo +# 2. Remove "/opt/" from all objects in prototype file +# For step2 this function exists +# Sample: d none /opt/openofficeorg20/help 0755 root other +# -> d none openofficeorg20/help 0755 root other +######################################################### + +sub make_prototypefile_relocatable +{ + my ($prototypefile, $relocatablepath) = @_; + + for ( my $i = 0; $i <= $#{$prototypefile}; $i++ ) + { + if ( ${$prototypefile}[$i] =~ /^\s*\w\s+\w+\s+\/\w+/ ) # this is an object line + { + ${$prototypefile}[$i] =~ s/$relocatablepath//; # Important: $relocatablepath has a "/" at the end. Example "/opt/" + } + } + + # If the $relocatablepath is "/opt/openoffice20/" the line "d none /opt/openoffice20" was not changed. + # This line has to be removed now + + if ( $relocatablepath ne "/" ) { $relocatablepath =~ s/\/\s*$//; } # removing the ending slash + + for ( my $i = 0; $i <= $#{$prototypefile}; $i++ ) + { + if ( ${$prototypefile}[$i] =~ /^\s*d\s+\w+\s+\Q$relocatablepath\E/ ) + { + my $line = ${$prototypefile}[$i]; + splice(@{$prototypefile},$i,1); # removing the line + $line =~ s/\s*$//; + my $infoline = "Info: Removed line \"$line\" from prototype file!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } + + # Making "\$" to "$" in prototype file. "\$" was created by epm. + + for ( my $i = 0; $i <= $#{$prototypefile}; $i++ ) + { + if ( ${$prototypefile}[$i] =~ /\\\$/ ) + { + ${$prototypefile}[$i] =~ s/\\\$/\$/g; + my $infoline2 = "Info: Changed line in prototype file: ${$prototypefile}[$i] !\n"; + push( @installer::globals::logfileinfo, $infoline2); + } + } +} + +######################################################################### +# Replacing the variables in the shell scripts or in the epm list file +# Linux: spec file +# Solaris: preinstall, postinstall, preremove, postremove +# If epm is used in the original version (not relocatable) +# the variables have to be exchanged in the list file, +# created for epm. +######################################################################### + +sub replace_variables_in_shellscripts +{ + my ($scriptfile, $scriptfilename, $oldstring, $newstring) = @_; + + my $debug = 0; + if ( $oldstring eq "PRODUCTDIRECTORYNAME" ) { $debug = 1; } + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + if ( ${$scriptfile}[$i] =~ /\Q$oldstring\E/ ) + { + my $oldline = ${$scriptfile}[$i]; + ${$scriptfile}[$i] =~ s/\Q$oldstring\E/$newstring/g; + ${$scriptfile}[$i] =~ s/\/\//\//g; # replacing "//" by "/" , if path $newstring is empty! + my $infoline = "Info: Substituting in $scriptfilename $oldstring by $newstring\n"; + push(@installer::globals::logfileinfo, $infoline); + if ( $debug ) + { + $infoline = "Old Line: $oldline"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "New Line: ${$scriptfile}[$i]"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } +} + +############################################################ +# Determining the directory created by epm, in which the +# RPMS or Solaris packages are created. +############################################################ + +sub determine_installdir_ooo +{ + # A simple "ls" command returns the directory name + + my $dirname = ""; + + my $systemcall = "ls |"; + open (LS, "$systemcall"); + $dirname = ; + close (LS); + + $dirname =~ s/\s*$//; + + my $infoline = "Info: Directory created by epm: $dirname\n"; + push(@installer::globals::logfileinfo, $infoline); + + return $dirname; +} + +############################################################ +# Setting the tab content into the file container +############################################################ + +sub set_tab_into_datafile +{ + my ($changefile, $filesref) = @_; + + my @newclasses = (); + my $newclassesstring = ""; + + if ( $installer::globals::issolarispkgbuild ) + { + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + if ( $onefile->{'SolarisClass'} ) + { + my $sourcepath = $onefile->{'sourcepath'}; + + for ( my $j = 0; $j <= $#{$changefile}; $j++ ) + { + if (( ${$changefile}[$j] =~ /^\s*f\s+none\s+/ ) && ( ${$changefile}[$j] =~ /\=\Q$sourcepath\E\s+/ )) + { + my $oldline = ${$changefile}[$j]; + ${$changefile}[$j] =~ s/f\s+none/e $onefile->{'SolarisClass'}/; + my $newline = ${$changefile}[$j]; + $oldline =~ s/\s*$//; + $newline =~ s/\s*$//; + + my $infoline = "TAB: Changing content from \"$oldline\" to \"$newline\" .\n"; + push(@installer::globals::logfileinfo, $infoline); + + # collecting all new classes + if (! grep {$_ eq $onefile->{'SolarisClass'}} @newclasses) + { + push(@newclasses, $onefile->{'SolarisClass'}); + } + + last; + } + } + } + } + + $newclassesstring = installer::converter::convert_array_to_space_separated_string(\@newclasses); + } + + if ( $installer::globals::isrpmbuild ) + { + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + if ( $onefile->{'SpecFileContent'} ) + { + my $destination = $onefile->{'destination'}; + + for ( my $j = 0; $j <= $#{$changefile}; $j++ ) + { + if ( ${$changefile}[$j] =~ /^\s*(\%attr\(.*\))\s+(\".*?\Q$destination\E\"\s*)$/ ) + { + my $begin = $1; + my $end = $2; + + my $oldline = ${$changefile}[$j]; + ${$changefile}[$j] = $begin . " " . $onefile->{'SpecFileContent'} . " " . $end; + my $newline = ${$changefile}[$j]; + + $oldline =~ s/\s*$//; + $newline =~ s/\s*$//; + + my $infoline = "TAB: Changing content from \"$oldline\" to \"$newline\" .\n"; + push(@installer::globals::logfileinfo, $infoline); + + last; + } + } + } + } + } + + return $newclassesstring; +} + +############################################################ +# Including additional classes into the pkginfo file +############################################################ + +sub include_classes_into_pkginfo +{ + my ($changefile, $classesstring) = @_; + + for ( my $i = 0; $i <= $#{$changefile}; $i++ ) + { + if ( ${$changefile}[$i] =~ /^\s*CLASSES\=none/ ) + { + ${$changefile}[$i] =~ s/\s*$//; + my $oldline = ${$changefile}[$i]; + ${$changefile}[$i] = ${$changefile}[$i] . " " . $classesstring . "\n"; + my $newline = ${$changefile}[$i]; + $newline =~ s/\s*$//; + + my $infoline = "pkginfo file: Changing content from \"$oldline\" to \"$newline\" .\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } +} + +########################################################################################## +# Checking, if an extension is included into the package (Linux). +# All extension files have to be installed into directory +# share/extension/install +# %attr(0444,root,root) "/opt/staroffice8/share/extension/install/SunSearchToolbar.oxt" +########################################################################################## + +sub is_extension_package +{ + my ($specfile) = @_; + + my $is_extension_package = 0; + + for ( my $i = 0; $i <= $#{$specfile}; $i++ ) + { + my $line = ${$specfile}[$i]; + if ( $line =~ /share\/extension\/install\/.*?\.oxt\"\s*$/ ) + { + $is_extension_package = 1; + last; + } + } + + return $is_extension_package; +} + +###################################################################### +# Checking, if an extension is included into the package (Solaris). +# All extension files have to be installed into directory +# share/extension/install +###################################################################### + +sub contains_extension_dir +{ + my ($prototypefile) = @_; + + my $contains_extension_dir = 0; + + # d none opt/libreoffice/share/extensions/ + + for ( my $i = 0; $i <= $#{$prototypefile}; $i++ ) + { + my $line = ${$prototypefile}[$i]; + if ( $line =~ /^\s*d\s+none\s.*\/share\/extensions\// ) + { + $contains_extension_dir = 1; + last; + } + } + + return $contains_extension_dir; +} + +############################################################ +# Setting the correct Solaris locales +############################################################ + +sub get_solaris_language_for_langpack +{ + my ( $onelanguage ) = @_; + + my $sollanguage = $onelanguage; + $sollanguage =~ s/\-/\_/; + + if ( $sollanguage eq "de" ) { $sollanguage = "de"; } + elsif ( $sollanguage eq "en_US" ) { $sollanguage = "en_AU,en_CA,en_GB,en_IE,en_MT,en_NZ,en_US,en_US.UTF-8"; } + elsif ( $sollanguage eq "es" ) { $sollanguage = "es"; } + elsif ( $sollanguage eq "fr" ) { $sollanguage = "fr"; } + elsif ( $sollanguage eq "hu" ) { $sollanguage = "hu_HU"; } + elsif ( $sollanguage eq "it" ) { $sollanguage = "it"; } + elsif ( $sollanguage eq "nl" ) { $sollanguage = "nl_BE,nl_NL"; } + elsif ( $sollanguage eq "pl" ) { $sollanguage = "pl_PL"; } + elsif ( $sollanguage eq "sv" ) { $sollanguage = "sv"; } + elsif ( $sollanguage eq "pt" ) { $sollanguage = "pt_PT"; } + elsif ( $sollanguage eq "pt_BR" ) { $sollanguage = "pt_BR"; } + elsif ( $sollanguage eq "ru" ) { $sollanguage = "ru_RU"; } + elsif ( $sollanguage eq "ja" ) { $sollanguage = "ja,ja_JP,ja_JP.PCK,ja_JP.UTF-8"; } + elsif ( $sollanguage eq "ko" ) { $sollanguage = "ko,ko.UTF-8"; } + elsif ( $sollanguage eq "zh_CN" ) { $sollanguage = "zh,zh.GBK,zh_CN.GB18030,zh.UTF-8"; } + elsif ( $sollanguage eq "zh_TW" ) { $sollanguage = "zh_TW,zh_TW.BIG5,zh_TW.UTF-8,zh_HK.BIG5HK,zh_HK.UTF-8"; } + + return $sollanguage; +} + +############################################################ +# Adding language infos in pkginfo file +############################################################ + +sub include_languageinfos_into_pkginfo +{ + my ( $changefile, $filename, $languagestringref, $onepackage, $variableshashref ) = @_; + + # SUNWPKG_LIST=core01 + # SUNW_LOC=de + + my $locallang = $onepackage->{'language'}; + my $solarislanguage = get_solaris_language_for_langpack($locallang); + + my $newline = "SUNW_LOC=" . $solarislanguage . "\n"; + add_one_line_into_file($changefile, $newline, $filename); + + # SUNW_PKGLIST is required, if SUNW_LOC is defined. + if ( $onepackage->{'pkg_list_entry'} ) + { + my $packagelistentry = $onepackage->{'pkg_list_entry'}; + installer::packagelist::resolve_packagevariables(\$packagelistentry, $variableshashref, 1); + $newline = "SUNW_PKGLIST=" . $packagelistentry . "\n"; + add_one_line_into_file($changefile, $newline, $filename); + } + else + { + # Using default package ooobasis30-core01. + my $packagelistentry = "%BASISPACKAGEPREFIX%WITHOUTDOTPRODUCTVERSION-core01"; + installer::packagelist::resolve_packagevariables(\$packagelistentry, $variableshashref, 1); + $newline = "SUNW_PKGLIST=" . $packagelistentry . "\n"; + add_one_line_into_file($changefile, $newline, $filename); + } +} + +############################################################ +# Including package names into the depend files. +# The package names have to be included into +# packagelist. They are already saved in +# %installer::globals::dependfilenames. +############################################################ + +sub put_packagenames_into_dependfile +{ + my ( $file ) = @_; + + for ( my $i = 0; $i <= $#{$file}; $i++ ) + { + my $line = ${$file}[$i]; + if ( $line =~ /^\s*\w\s+(.*?)\s*$/ ) + { + my $abbreviation = $1; + + if ( $abbreviation =~ /\%/ ) { installer::exiter::exit_program("ERROR: Could not resolve all properties in Solaris package abbreviation \"$abbreviation\"!", "read_packagemap"); } + + if ( exists($installer::globals::dependfilenames{$abbreviation}) ) + { + my $packagename = $installer::globals::dependfilenames{$abbreviation}; + if ( $packagename =~ /\%/ ) { installer::exiter::exit_program("ERROR: Could not resolve all properties in Solaris package name \"$packagename\"!", "read_packagemap"); } + + $line =~ s/\s*$//; + ${$file}[$i] = $line . "\t" . $packagename . "\n"; + } + else + { + installer::exiter::exit_program("ERROR: Missing packagename for Solaris package \"$abbreviation\"!", "put_packagenames_into_dependfile"); + } + } + } +} + +############################################################ +# Including the relocatable directory into +# spec file and pkginfo file +# Linux: set topdir in specfile +# Solaris: remove $relocatablepath (/opt/) +# for all objects in prototype file +# and changing "topdir" for Linux +############################################################ + +sub prepare_packages +{ + my ($loggingdir, $packagename, $staticpath, $relocatablepath, $onepackage, $variableshashref, $filesref, $languagestringref) = @_; + + my $filename = ""; + my $newline = ""; + my $newepmdir = $installer::globals::epmoutpath . $installer::globals::separator; + + my $localrelocatablepath = $relocatablepath; + if ( $localrelocatablepath ne "/" ) { $localrelocatablepath =~ s/\/\s*$//; } + + if ( $installer::globals::issolarispkgbuild ) + { + $filename = $packagename . ".pkginfo"; + $newline = "BASEDIR\=" . $localrelocatablepath . "\n"; + } + + if ( $installer::globals::isrpmbuild ) + { + $filename = $packagename . ".spec"; + $newline = "Prefix\:\ " . $localrelocatablepath . "\n"; + } + + my $completefilename = $newepmdir . $filename; + + if ( ! -f $completefilename) { installer::exiter::exit_program("ERROR: Did not find file: $completefilename", "prepare_packages"); } + my $changefile = installer::files::read_file($completefilename); + if ( $newline ne "" ) + { + add_one_line_into_file($changefile, $newline, $filename); + installer::files::save_file($completefilename, $changefile); + } + + # adding new "topdir" and removing old "topdir" in specfile + + if ( $installer::globals::isrpmbuild ) + { + set_topdir_in_specfile($changefile, $filename, $newepmdir); + set_autoprovreq_in_specfile($changefile, $onepackage->{'findrequires'}, "$installer::globals::workpath" . "/bin"); + set_packager_in_specfile($changefile); + if ( is_extension_package($changefile) ) { set_prereq_in_specfile($changefile); } + set_license_in_specfile($changefile, $variableshashref); + set_tab_into_datafile($changefile, $filesref); + installer::files::save_file($completefilename, $changefile); + } + + # removing the relocatable path in prototype file + + if ( $installer::globals::issolarispkgbuild ) + { + set_revision_in_pkginfo($changefile, $filename, $variableshashref, $packagename); + set_maxinst_in_pkginfo($changefile, $filename); + set_solaris_parameter_in_pkginfo($changefile, $filename, $variableshashref); + if ( $installer::globals::issolarisx86build ) { fix_architecture_setting($changefile); } + if (( $onepackage->{'language'} ) && ( $onepackage->{'language'} ne "" ) && ( $onepackage->{'language'} ne "en-US" )) { include_languageinfos_into_pkginfo($changefile, $filename, $languagestringref, $onepackage, $variableshashref); } + installer::files::save_file($completefilename, $changefile); + + my $prototypefilename = $packagename . ".prototype"; + $prototypefilename = $newepmdir . $prototypefilename; + if (! -f $prototypefilename) { installer::exiter::exit_program("ERROR: Did not find prototype file: $prototypefilename", "prepare_packages"); } + + my $prototypefile = installer::files::read_file($prototypefilename); + make_prototypefile_relocatable($prototypefile, $relocatablepath); + my $classesstring = set_tab_into_datafile($prototypefile, $filesref); + if ($classesstring) + { + include_classes_into_pkginfo($changefile, $classesstring); + installer::files::save_file($completefilename, $changefile); + } + + installer::files::save_file($prototypefilename, $prototypefile); + + # Adding package names into depend files for Solaris (not supported by epm) + my $dependfilename = $packagename . ".depend"; + $dependfilename = $newepmdir . $dependfilename; + if ( -f $dependfilename) + { + my $dependfile = installer::files::read_file($dependfilename); + put_packagenames_into_dependfile($dependfile); + installer::files::save_file($dependfilename, $dependfile); + } + } + + return $newepmdir; +} + +############################################################################### +# Replacement of PRODUCTINSTALLLOCATION and PRODUCTDIRECTORYNAME in the +# epm list file. +# The complete rootpath is stored in $installer::globals::rootpath +# or for each package in $onepackage->{'destpath'} +# The static rootpath is stored in $staticpath +# The relocatable path is stored in $relocatablepath +# PRODUCTINSTALLLOCATION is the relocatable part ("/opt") and +# PRODUCTDIRECTORYNAME the static path ("openofficeorg20"). +# In standard epm process: +# No usage of package specific variables like $BASEDIR, because +# 1. These variables would be replaced in epm process +# 2. epm version 3.7 does not support relocatable packages +############################################################################### + +sub resolve_path_in_epm_list_before_packaging +{ + my ($listfile, $listfilename, $variable, $path) = @_; + + installer::logger::include_header_into_logfile("Replacing variables in epm list file:"); + + $path =~ s/\/\s*$//; + replace_variables_in_shellscripts($listfile, $listfilename, $variable, $path); + +} + +################################################################# +# Determining the rpm version. Beginning with rpm version 4.0 +# the tool to create RPMs is "rpmbuild" and no longer "rpm" +################################################################# + +sub determine_rpm_version +{ + my $rpmversion = 0; + my $rpmout = ""; + my $systemcall = ""; + + # "rpm --version" has problems since LD_LIBRARY_PATH was removed. Therefore the content of $RPM has to be called. + # "rpm --version" and "rpmbuild --version" have the same output. Therefore $RPM can be used. Its value + # is saved in $installer::globals::rpm + + if ( $installer::globals::rpm ne "" ) + { + $systemcall = "$installer::globals::rpm --version |"; + } + else + { + $systemcall = "rpm --version |"; + } + + open (RPM, "$systemcall"); + $rpmout = ; + close (RPM); + + if ( $rpmout ne "" ) + { + $rpmout =~ s/\s*$//g; + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( $rpmout eq "" ) { $infoline = "ERROR: Could not find file \"rpm\" !\n"; } + else { $infoline = "Success: rpm version: $rpmout\n"; } + + push( @installer::globals::logfileinfo, $infoline); + + if ( $rpmout =~ /(\d+)\.(\d+)\.(\d+)/ ) { $rpmversion = $1; } + elsif ( $rpmout =~ /(\d+)\.(\d+)/ ) { $rpmversion = $1; } + elsif ( $rpmout =~ /(\d+)/ ) { $rpmversion = $1; } + else { installer::exiter::exit_program("ERROR: Unknown format: $rpmout ! Expected: \"a.b.c\", or \"a.b\", or \"a\"", "determine_rpm_version"); } + } + + return $rpmversion; +} + +#################################################### +# Writing some info about rpm into the log file +#################################################### + +sub log_rpm_info +{ + my $systemcall = ""; + my $infoline = ""; + + $infoline = "\nLogging rpmrc content using --showrc\n\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( $installer::globals::rpm ne "" ) + { + $systemcall = "$installer::globals::rpm --showrc |"; + } + else + { + $systemcall = "rpm --showrc |"; + } + + my @fullrpmout = (); + + open (RPM, "$systemcall"); + while () {push(@fullrpmout, $_); } + close (RPM); + + if ( $#fullrpmout > -1 ) + { + for ( my $i = 0; $i <= $#fullrpmout; $i++ ) + { + my $rpmout = $fullrpmout[$i]; + $rpmout =~ s/\s*$//g; + + $infoline = "$rpmout\n"; + $infoline =~ s/error/e_r_r_o_r/gi; # avoiding log problems + push( @installer::globals::logfileinfo, $infoline); + } + } + else + { + $infoline = "Problem in systemcall: $systemcall : No return value\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + $infoline = "End of logging rpmrc\n\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +################################################# +# Systemcall to start the packaging process +################################################# + +sub create_packages_without_epm +{ + my ($epmdir, $packagename, $includepatharrayref, $allvariables, $languagestringref) = @_; + + # Solaris: pkgmk -o -f solaris-2.8-sparc/SUNWso8m34.prototype -d solaris-2.8-sparc + # Solaris: pkgtrans solaris-2.8-sparc SUNWso8m34.pkg SUNWso8m34 + # Solaris: tar -cf - SUNWso8m34 | $installer::globals::packertool > SUNWso8m34.tar.gz + + if ( $installer::globals::issolarispkgbuild ) + { + my $prototypefile = $epmdir . $packagename . ".prototype"; + if (! -f $prototypefile) { installer::exiter::exit_program("ERROR: Did not find file: $prototypefile", "create_packages_without_epm"); } + + my $destinationdir = $prototypefile; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destinationdir); + $destinationdir =~ s/\/\s*$//; # removing ending slashes + + my $systemcall = "pkgmk -l 1073741824 -o -f $prototypefile -d $destinationdir 2\>\&1 |"; + installer::logger::print_message( "... $systemcall ...\n" ); + + my $maxpkgmkcalls = 3; + + for ( my $i = 1; $i <= $maxpkgmkcalls; $i++ ) + { + my @pkgmkoutput = (); + + open (PKGMK, "$systemcall"); + while () {push(@pkgmkoutput, $_); } + close (PKGMK); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + my $infoline = "Systemcall (Try $i): $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $j = 0; $j <= $#pkgmkoutput; $j++ ) + { + if ( $i < $maxpkgmkcalls ) { $pkgmkoutput[$j] =~ s/\bERROR\b/PROBLEM/ig; } + push( @installer::globals::logfileinfo, "$pkgmkoutput[$j]"); + } + + if ($returnvalue) + { + $infoline = "Try $i : Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + if ( $i == $maxpkgmkcalls ) { installer::exiter::exit_program("ERROR: \"$systemcall\"!", "create_packages_without_epm"); } + } + else + { + installer::logger::print_message( "Success (Try $i): \"$systemcall\"\n" ); + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } + + # It might be necessary to save uncompressed Solaris packages + + # compressing packages + + if ( ! $installer::globals::solarisdontcompress ) + { + my $faspac = "faspac-so.sh"; + + my $compressorref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$faspac, $includepatharrayref, 0); + if ($$compressorref ne "") + { + # Saving original pkginfo, to set time stamp later + my $pkginfoorig = "$destinationdir/$packagename/pkginfo"; + my $pkginfotmp = "$destinationdir/$packagename" . ".pkginfo.tmp"; + $systemcall = "cp -p $pkginfoorig $pkginfotmp"; + installer::systemactions::make_systemcall($systemcall); + + $faspac = $$compressorref; + $infoline = "Found compressor: $faspac\n"; + push( @installer::globals::logfileinfo, $infoline); + + installer::logger::print_message( "... $faspac ...\n" ); + installer::logger::include_timestamp_into_logfile("Starting $faspac"); + + $systemcall = "/bin/sh $faspac -a -q -d $destinationdir $packagename"; # $faspac has to be the absolute path! + installer::systemactions::make_systemcall($systemcall); + + # Setting time stamp for pkginfo, because faspac-so.sh + # changed the pkginfo file, updated the size and + # checksum, but not the time stamp. + $systemcall = "touch -r $pkginfotmp $pkginfoorig"; + installer::systemactions::make_systemcall($systemcall); + if ( -f $pkginfotmp ) { unlink($pkginfotmp); } + + installer::logger::include_timestamp_into_logfile("End of $faspac"); + } + else + { + $infoline = "Not found: $faspac\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + # Setting unix rights to "775" for all created directories inside the package + + $systemcall = "cd $destinationdir; find $packagename -type d | xargs -i chmod 775 \{\} \;"; + installer::logger::print_message( "... $systemcall ...\n" ); + + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + + ###################### + # making pkg files + ###################### + + # my $streamname = $packagename . ".pkg"; + # $systemcall = "pkgtrans $destinationdir $streamname $packagename"; + # print "... $systemcall ...\n"; + + # $returnvalue = system($systemcall); + + # $infoline = "Systemcall: $systemcall\n"; + # push( @installer::globals::logfileinfo, $infoline); + + # if ($returnvalue) + # { + # $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + # push( @installer::globals::logfileinfo, $infoline); + # } + # else + # { + # $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + # push( @installer::globals::logfileinfo, $infoline); + # } + + ######################### + # making tar.gz files + ######################### + + # my $targzname = $packagename . ".tar.gz"; + # $systemcall = "cd $destinationdir; tar -cf - $packagename | $installer::globals::packertool > $targzname"; + # print "... $systemcall ...\n"; + + # $returnvalue = system($systemcall); + + # $infoline = "Systemcall: $systemcall\n"; + # push( @installer::globals::logfileinfo, $infoline); + + # if ($returnvalue) + # { + # $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + # push( @installer::globals::logfileinfo, $infoline); + # } + # else + # { + # $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + # push( @installer::globals::logfileinfo, $infoline); + # } + + } + + # Linux: rpm -bb so8m35.spec ( -> dependency check abklemmen? ) + + if ( $installer::globals::isrpmbuild ) + { + my $specfilename = $epmdir . $packagename . ".spec"; + if (! -f $specfilename) { installer::exiter::exit_program("ERROR: Did not find file: $specfilename", "create_packages_without_epm"); } + + my $rpmcommand = $installer::globals::rpm; + my $rpmversion = determine_rpm_version(); + + my $target = ""; + if ( $installer::globals::platformid eq 'linux_x86') + { + $target = "i586"; + } + elsif ( $installer::globals::platformid eq 'aix_powerpc') + { + $target = "ppc"; + } + elsif ( $installer::globals::os eq 'LINUX') + { + $target = (POSIX::uname())[4]; + } + + # rpm 4.6 ignores buildroot tag in spec file + + my $buildrootstring = ""; + + if ( $rpmversion >= 4 ) + { + my $dir = Cwd::getcwd; + my $buildroot = $dir . "/" . $epmdir . "buildroot/"; + $buildrootstring = "--buildroot=$buildroot"; + mkdir($buildroot = $dir . "/" . $epmdir . "BUILD/"); + } + + if ( ! $installer::globals::rpminfologged ) + { + log_rpm_info(); + $installer::globals::rpminfologged = 1; + } + + my $systemcall = "$rpmcommand -bb --define \"_unpackaged_files_terminate_build 0\" $specfilename --target $target $buildrootstring 2\>\&1 |"; + + installer::logger::print_message( "... $systemcall ...\n" ); + + my $maxrpmcalls = 3; + my $rpm_failed = 0; + + for ( my $i = 1; $i <= $maxrpmcalls; $i++ ) + { + my @rpmoutput = (); + + open (RPM, "$systemcall"); + while () {push(@rpmoutput, $_); } + close (RPM); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + my $infoline = "Systemcall (Try $i): $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $j = 0; $j <= $#rpmoutput; $j++ ) + { + $rpmoutput[$j] =~ s/\bERROR\b/PROBLEM/ig; + push( @installer::globals::logfileinfo, "$rpmoutput[$j]"); + } + + if ($returnvalue) + { + $infoline = "Try $i : Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + $rpm_failed = 1; + } + else + { + installer::logger::print_message( "Success (Try $i): \"$systemcall\"\n" ); + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + $rpm_failed = 0; + last; + } + } + + if ( $rpm_failed ) + { + # Because of the problems with LD_LIBRARY_PATH, a direct call of local "rpm" or "rpmbuild" might be successful + my $rpmprog = ""; + if ( -f "/usr/bin/rpmbuild" ) { $rpmprog = "/usr/bin/rpmbuild"; } + elsif ( -f "/usr/bin/rpm" ) { $rpmprog = "/usr/bin/rpm"; } + + if ( $rpmprog ne "" ) + { + installer::logger::print_message( "... $rpmprog ...\n" ); + + my $helpersystemcall = "$rpmprog -bb $specfilename --target $target $buildrootstring 2\>\&1 |"; + + my @helperrpmoutput = (); + + open (RPM, "$helpersystemcall"); + while () {push(@helperrpmoutput, $_); } + close (RPM); + + my $helperreturnvalue = $?; # $? contains the return value of the systemcall + + $infoline = "\nLast try: Using $rpmprog directly (problem with LD_LIBRARY_PATH)\n"; + push( @installer::globals::logfileinfo, $infoline); + + $infoline = "\nSystemcall: $helpersystemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $j = 0; $j <= $#helperrpmoutput; $j++ ) { push( @installer::globals::logfileinfo, "$helperrpmoutput[$j]"); } + + if ($helperreturnvalue) + { + $infoline = "Could not execute \"$helpersystemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + installer::logger::print_message( "Success: \"$helpersystemcall\"\n" ); + $infoline = "Success: Executed \"$helpersystemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + $rpm_failed = 0; + } + } + + # Now it is really time to exit this packaging process, if the error still occurs + if ( $rpm_failed ) { installer::exiter::exit_program("ERROR: \"$systemcall\"!", "create_packages_without_epm"); } + } + } +} + +################################################# +# Removing all temporary files created by epm +################################################# + +sub remove_temporary_epm_files +{ + my ($epmdir, $loggingdir, $packagename) = @_; + + # saving the files into the loggingdir + + if ( $installer::globals::issolarispkgbuild ) + { + my @extensions = (); + push(@extensions, ".pkginfo"); + push(@extensions, ".prototype"); + push(@extensions, ".postinstall"); + push(@extensions, ".postremove"); + push(@extensions, ".preinstall"); + push(@extensions, ".preremove"); + push(@extensions, ".depend"); + + for ( my $i = 0; $i <= $#extensions; $i++ ) + { + my $removefile = $epmdir . $packagename . $extensions[$i]; + my $destfile = $loggingdir . $packagename . $extensions[$i] . ".log"; + + if (! -f $removefile) { next; } + + my $systemcall = "mv -f $removefile $destfile"; + system($systemcall); # ignoring the return value + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + if ( $installer::globals::isrpmbuild ) + { + my $removefile = $epmdir . $packagename . ".spec"; + my $destfile = $loggingdir . $packagename . ".spec.log"; + + my $systemcall = "mv -f $removefile $destfile"; + system($systemcall); # ignoring the return value + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + # removing the directory "buildroot" + + my $removedir = $epmdir . "buildroot"; + + $systemcall = "rm -rf $removedir"; + + installer::logger::print_message( "... $systemcall ...\n" ); + + my $returnvalue = system($systemcall); + + $removedir = $epmdir . "BUILD"; + + $systemcall = "rm -rf $removedir"; + + installer::logger::print_message( "... $systemcall ...\n" ); + + $returnvalue = system($systemcall); + + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } +} + +########################################################### +# Creating a better directory structure in the solver. +########################################################### + +sub create_new_directory_structure +{ + my ($newepmdir) = @_; + + my $newdir = $installer::globals::epmoutpath; + + if ( $installer::globals::isrpmbuild ) + { + my $rpmdir; + my $machine = ""; + if ( $installer::globals::platformid eq 'linux_x86') + { + $rpmdir = "$installer::globals::epmoutpath/RPMS/i586"; + } + elsif ( $installer::globals::platformid eq 'aix_powerpc') + { + $machine = "ppc"; + $rpmdir = "$installer::globals::epmoutpath/RPMS/$machine"; + } + elsif ( $installer::globals::os eq 'LINUX') + { + $machine = (POSIX::uname())[4]; + $rpmdir = "$installer::globals::epmoutpath/RPMS/$machine"; + } + else + { + installer::exiter::exit_program("ERROR: rpmdir undefined !", "create_new_directory_structure"); + } + + my $systemcall = "mv $rpmdir/* $newdir"; # moving the rpms into the directory "RPMS" + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not move content of \"$rpmdir\" to \"$newdir\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Moved content of \"$rpmdir\" to \"$newdir\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # and removing the empty directory + + if ( $machine ne "" ) + { + rmdir "$installer::globals::epmoutpath/RPMS/$machine"; + } + rmdir "$installer::globals::epmoutpath/RPMS/powerpc"; + rmdir "$installer::globals::epmoutpath/RPMS/x86_64"; + rmdir "$installer::globals::epmoutpath/RPMS/i586"; + rmdir "$installer::globals::epmoutpath/RPMS/i386"; + rmdir "$installer::globals::epmoutpath/RPMS" + or warn "Could not remove RPMS dir: $!"; + } + + # Setting unix rights to "775" for $newdir ("RPMS" or "packages") + chmod 0775, $newdir; +} + +###################################################### +# Collect modules with product specific styles. +###################################################### + +sub collect_modules_with_style +{ + my ($style, $modulesarrayref) = @_; + + my @allmodules = (); + + for ( my $i = 0; $i <= $#{$modulesarrayref}; $i++ ) + { + my $onemodule = ${$modulesarrayref}[$i]; + my $styles = ""; + if ( $onemodule->{'Styles'} ) { $styles = $onemodule->{'Styles'}; } + if ( $styles =~ /\b\Q$style\E\b/ ) + { + push(@allmodules, $onemodule); + } + } + + return \@allmodules; +} + +###################################################### +# Remove modules without packagecontent. +###################################################### + +sub remove_modules_without_package +{ + my ($allmodules) = @_; + + my @allmodules = (); + + for ( my $i = 0; $i <= $#{$allmodules}; $i++ ) + { + my $onemodule = ${$allmodules}[$i]; + my $packagename = ""; + if ( $onemodule->{'PackageName'} ) { $packagename = $onemodule->{'PackageName'}; } + if ( $packagename ne "" ) + { + push(@allmodules, $onemodule); + } + } + + return \@allmodules; +} + +###################################################### +# Copying files for system integration. +###################################################### + +sub copy_and_unpack_tar_gz_files +{ + my ($sourcefile, $destdir) = @_; + + my $systemcall = "cd $destdir; cat $sourcefile | gunzip | tar -xf -"; + installer::systemactions::make_systemcall($systemcall); +} + +###################################################### +# Checking whether the new content is a directory and +# not a package. If it is a directory, the complete +# content of the directory has to be added to the +# array newcontent. +###################################################### + +sub control_subdirectories +{ + my ($content, $subdir) = @_; + + my @newcontent = (); + + for ( my $i = 0; $i <= $#{$content}; $i++ ) + { + if ( -d ${$content}[$i] ) + { + $subdir = ${$content}[$i]; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$subdir); + my $allpackages = installer::systemactions::read_directory(${$content}[$i]); + for ( my $j = 0; $j <= $#{$allpackages}; $j++ ) + { + # Currently only Linux rpm is supported, debian packages cannot be installed via xpd installer + if (( $installer::globals::islinuxbuild ) && ( ! ( ${$allpackages}[$j] =~ /\.rpm\s*$/ ))) { next; } + push(@newcontent, ${$allpackages}[$j]); + } + } + else + { + push(@newcontent, ${$content}[$i]); + } + } + + return (\@newcontent, $subdir); +} + +###################################################### +# Including the system integration files into the +# installation sets. +###################################################### + +sub put_systemintegration_into_installset +{ + my ($newdir, $includepatharrayref, $allvariables, $modulesarrayref) = @_; + + my $destdir = $newdir; + + # adding System integration files + + my $sourcefile = ""; + + # Finding the modules defined in scp (with flag SYSTEMMODULE) + # Getting name of package from scp-Module + # Search package in list off all include files + # Copy file into installation set and unpack it (always tar.gz) + # tar.gz can contain a different number of packages -> automatically create hidden sub modules + + # Collect all modules with flag "SYSTEMMODULE" + my $allmodules = collect_modules_with_style("SYSTEMMODULE", $modulesarrayref); + $allmodules = remove_modules_without_package($allmodules); + + for ( my $i = 0; $i <= $#{$allmodules}; $i++ ) + { + my $onemodule = ${$allmodules}[$i]; + my $packagetarfilename = $onemodule->{'PackageName'}; + + my $infoline = "Including into installation set: $packagetarfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $sourcepathref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$packagetarfilename, $includepatharrayref, 1); + if ( $$sourcepathref eq "" ) { installer::exiter::exit_program("ERROR: Source path not found for $packagetarfilename!", "copy_systemintegration_files"); } + + # Collecting all packages in directory "packages" or "RPMS" + my $oldcontent = installer::systemactions::read_directory($destdir); + + copy_and_unpack_tar_gz_files($$sourcepathref, $destdir); + + # Finding new content -> that is the package name + my ($newcontent, $allcontent ) = installer::systemactions::find_new_content_in_directory($destdir, $oldcontent); + + # special handling, if new content is a directory + my $subdir = ""; + if ( ! $installer::globals::issolarispkgbuild ) { ($newcontent, $subdir) = control_subdirectories($newcontent); } + + # Adding license content into Solaris packages + if (( $installer::globals::issolarispkgbuild ) && ( $installer::globals::englishlicenseset ) && ( ! $variableshashref->{'NO_LICENSE_INTO_COPYRIGHT'} )) { _add_license_into_systemintegrationpackages($destdir, $newcontent); } + } +} + +###################################################### +# Analyzing the Unix installation path. +# From the installation path /opt/openofficeorg20 +# is the part /opt relocatable and the part +# openofficeorg20 static. +###################################################### + +sub analyze_rootpath +{ + my ($rootpath, $staticpathref, $relocatablepathref, $allvariables) = @_; + + $rootpath =~ s/\/\s*$//; # removing ending slash + + ############################################################## + # Version 3: "/" is variable and "/opt/openofficeorg20" fixed + ############################################################## + + $$relocatablepathref = "/"; + # Static path has to contain the office directory name. This is replaced in shellscripts. + $$staticpathref = $rootpath . $installer::globals::separator . $installer::globals::officedirhostname; + # For RPM version 3.x it is required, that Prefix is not "/" in spec file. In this case --relocate will not work, + # because RPM 3.x says, that the package is not relocatable. Therefore we have to use Prefix=/opt and for + # all usages of --relocate this path has to be on both sides of the "=": --relocate /opt=/opt . + if ( $installer::globals::isrpmbuild ) + { + $$relocatablepathref = $rootpath . "\/"; # relocatable path must end with "/", will be "/opt/" + $$staticpathref = $installer::globals::officedirhostname; # to be used as replacement in shell scripts + } + + if ( $installer::globals::isdebbuild ) + { + $$relocatablepathref = ""; + # $$staticpathref is already "/opt/libreoffice", no additional $rootpath required. + } + +} + +################################################ +# Defining the English license text to add +# it into Solaris packages. +################################################ + +sub _set_english_license +{ + my $additional_license_name = $installer::globals::englishsolarislicensename; # always the English file + my $licensefileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$additional_license_name, "" , 0); + if ( $$licensefileref eq "" ) { installer::exiter::exit_program("ERROR: Could not find license file $additional_license_name!", "set_english_license"); } + $installer::globals::englishlicenseset = 1; + $installer::globals::englishlicense = installer::files::read_file($$licensefileref); + installer::scpzipfiles::replace_all_ziplistvariables_in_file($installer::globals::englishlicense, $variableshashref); +} + +################################################ +# Adding the content of the English license +# file into the system integration packages. +################################################ + +sub _add_license_into_systemintegrationpackages +{ + my ($destdir, $packages) = @_; + + for ( my $i = 0; $i <= $#{$packages}; $i++ ) + { + my $copyrightfilename = ${$packages}[$i] . $installer::globals::separator . "install" . $installer::globals::separator . "copyright"; + if ( ! -f $copyrightfilename ) { installer::exiter::exit_program("ERROR: Could not find license file in system integration package: $copyrightfilename!", "add_license_into_systemintegrationpackages"); } + my $copyrightfile = installer::files::read_file($copyrightfilename); + + # Saving time stamp of old copyrightfile + my $savcopyrightfilename = $copyrightfilename . ".sav"; + installer::systemactions::copy_one_file($copyrightfilename, $savcopyrightfilename); + _set_time_stamp_for_file($copyrightfilename, $savcopyrightfilename); # now $savcopyrightfile has the time stamp of $copyrightfile + + # Adding license content to copyright file + push(@{$copyrightfile}, "\n"); + for ( my $i = 0; $i <= $#{$installer::globals::englishlicense}; $i++ ) { push(@{$copyrightfile}, ${$installer::globals::englishlicense}[$i]); } + installer::files::save_file($copyrightfilename, $copyrightfile); + + # Setting the old time stamp saved with $savcopyrightfilename + _set_time_stamp_for_file($savcopyrightfilename, $copyrightfilename); # now $copyrightfile has the time stamp of $savcopyrightfile + unlink($savcopyrightfilename); + + # Changing content of copyright file in pkgmap + my $pkgmapfilename = ${$packages}[$i] . $installer::globals::separator . "pkgmap"; + if ( ! -f $pkgmapfilename ) { installer::exiter::exit_program("ERROR: Could not find pkgmap in system integration package: $pkgmapfilename!", "add_license_into_systemintegrationpackages"); } + my $pkgmap = installer::files::read_file($pkgmapfilename); + _change_onefile_in_pkgmap($pkgmap, $copyrightfilename, "copyright"); + installer::files::save_file($pkgmapfilename, $pkgmap); + } +} + +############################################## +# Setting time stamp of copied files to avoid +# errors from pkgchk. +############################################## + +sub _set_time_stamp_for_file +{ + my ($sourcefile, $destfile) = @_; + + my $systemcall = "touch -r $sourcefile $destfile"; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: \"$systemcall\" failed!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: \"$systemcall\" !\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +############################################## +# Setting checksum and wordcount for changed +# pkginfo file into pkgmap. +############################################## + +sub _change_onefile_in_pkgmap +{ + my ($pkgmapfile, $fullfilename, $shortfilename) = @_; + + # 1 i pkginfo 442 34577 1166716297 + # -> + # 1 i pkginfo 443 34737 1166716297 + # + # wc -c pkginfo | cut -f6 -d' ' -> 442 (variable) + # sum pkginfo | cut -f1 -d' ' -> 34577 (variable) + # grep 'pkginfo' pkgmap | cut -f6 -d' ' -> 1166716297 (fix) + + my $checksum = _call_sum($fullfilename); + if ( $checksum =~ /^\s*(\d+)\s+.*$/ ) { $checksum = $1; } + + my $wordcount = _call_wc($fullfilename); + if ( $wordcount =~ /^\s*(\d+)\s+.*$/ ) { $wordcount = $1; } + + for ( my $i = 0; $i <= $#{$pkgmapfile}; $i++ ) + { + if ( ${$pkgmapfile}[$i] =~ /(^.*\b\Q$shortfilename\E\b\s+)(\d+)(\s+)(\d+)(\s+)(\d+)(\s*$)/ ) + { + my $newline = $1 . $wordcount . $3 . $checksum . $5 . $6 . $7; + ${$pkgmapfile}[$i] = $newline; + last; + } + } +} + +######################################################### +# Calling sum +######################################################### + +sub _call_sum +{ + my ($filename) = @_; + + $sumfile = "/usr/bin/sum"; + + if ( ! -f $sumfile ) { installer::exiter::exit_program("ERROR: No file /usr/bin/sum", "call_sum"); } + + my $systemcall = "$sumfile $filename |"; + + my $sumoutput = ""; + + open (SUM, "$systemcall"); + $sumoutput = ; + close (SUM); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + return $sumoutput; +} + +######################################################### +# Calling wc +# wc -c pkginfo | cut -f6 -d' ' +######################################################### + +sub _call_wc +{ + my ($filename) = @_; + + $wcfile = "/usr/bin/wc"; + + if ( ! -f $wcfile ) { installer::exiter::exit_program("ERROR: No file /usr/bin/wc", "call_wc"); } + + my $systemcall = "$wcfile -c $filename |"; + + my $wcoutput = ""; + + open (WC, "$systemcall"); + $wcoutput = ; + close (WC); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + return $wcoutput; +} + +1; diff --git a/solenv/bin/modules/installer/exiter.pm b/solenv/bin/modules/installer/exiter.pm new file mode 100644 index 000000000..c6e49f9ce --- /dev/null +++ b/solenv/bin/modules/installer/exiter.pm @@ -0,0 +1,33 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::exiter; + +use strict; +use warnings; + +use Carp; + +sub exit_program +{ + my $message = shift; + + croak $message; +} + +1; diff --git a/solenv/bin/modules/installer/filelists.pm b/solenv/bin/modules/installer/filelists.pm new file mode 100644 index 000000000..1320a89e6 --- /dev/null +++ b/solenv/bin/modules/installer/filelists.pm @@ -0,0 +1,154 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +package installer::filelists; + +use File::stat; + +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; + +sub resolve_filelist_flag +{ + my ($files, $links, $outdir) = @_; + my @newfiles = (); + my $error = 0; + + foreach my $file (@{$files}) + { + my $is_filelist = 0; + my $use_internal_rights = 0; + if ($file->{'Styles'}) + { + if ($file->{'Styles'} =~ /\bFILELIST\b/) + { + $is_filelist = 1; + } + if ($file->{'Styles'} =~ /\bUSE_INTERNAL_RIGHTS\b/ && !$installer::globals::iswin) + { + $use_internal_rights = 1; + } + } + + if ($is_filelist) + { + my $filelist_path = $file->{'sourcepath'}; + my $filelist = read_filelist($filelist_path); + if (@{$filelist}) + { + my $destination = $file->{'destination'}; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination); + + foreach my $path (@{$filelist}) + { + my $is_symlink = 0; + + if ((index $path, $outdir) != 0) + { + installer::logger::print_error("file '$path' is not in '$outdir'"); + $error = 1; + } + if ($path =~ '\/\/') + { + installer::logger::print_error("file '$path' contains 2 consecutive '/' which breaks MSIs"); + $error = 1; + } + if (-l $path) + { + $is_symlink = 1; + } + else + { + if (!-e $path) + { + installer::logger::print_error("file '$path' does not exist"); + $error = 1; + } + } + + my $subpath = substr $path, ((length $outdir) + 1); # drop separator too + + my %newfile = (); + %newfile = %{$file}; + $newfile{'Name'} = $subpath; + $newfile{'sourcepath'} = $path; + $newfile{'destination'} = $destination . $subpath; + $newfile{'filelistname'} = $file->{'Name'}; + $newfile{'filelistpath'} = $file->{'sourcepath'}; + + if ($is_symlink) + { + # FIXME: for symlinks destination is mangled later in + # get_Destination_Directory_For_Item_From_Directorylist + $newfile{'DoNotMessWithSymlinks'} = 1; + $newfile{'Target'} = readlink($path); + push ( @{$links}, \%newfile ); + } + else + { + if ($use_internal_rights) + { + my $st = stat($path); + $newfile{'UnixRights'} = sprintf("%o", $st->mode & 0777); + } + + push @newfiles, \%newfile; + } + } + } + else + { + installer::logger::print_message("filelist $filelist_path is empty\n"); + } + } + else # not a filelist, just pass the current file over + { + push @newfiles, $file; + } + } + + if ( $error ) + { + installer::exiter::exit_program("ERROR: error(s) in resolve_filelist_flag"); + } + + return (\@newfiles, $links); +} + +sub read_filelist +{ + my ($path) = @_; + my $content = installer::files::read_file($path); + my @filelist = (); + + # split on space, but only if followed by / (don't split within a filename) + my $splitRE = qr!\s+(?=/)!; + # filelist on win have C:/cygwin style however - also reading dos-file under + # cygwin retains \r\n - so chomp below still leaves \r to strip in the RE + $splitRE = qr!\s+(?:$|(?=[A-Z]:/))! if ($installer::globals::os eq "WNT"); + + foreach my $line (@{$content}) + { + chomp $line; + foreach my $file (split $splitRE, $line) + { + if ($file ne "") + { + push @filelist, $file; + } + } + } + + return \@filelist; +} + +1; + +# vim: set expandtab shiftwidth=4 tabstop=4: diff --git a/solenv/bin/modules/installer/files.pm b/solenv/bin/modules/installer/files.pm new file mode 100644 index 000000000..1eb41482c --- /dev/null +++ b/solenv/bin/modules/installer/files.pm @@ -0,0 +1,120 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::files; + +use strict; +use warnings; + +use installer::exiter; +use installer::logger; + +############################################ +# File Operations +############################################ + +sub check_file +{ + my ($arg) = @_; + + if(!( -f $arg )) + { + installer::exiter::exit_program("ERROR: Cannot find file $arg", "check_file"); + } +} + +sub read_file +{ + my ($localfile) = @_; + my @localfile = (); + + open( IN, "<$localfile" ) || installer::exiter::exit_program("ERROR: Cannot open file $localfile for reading", "read_file"); + +# Don't use "my @localfile = " here, because +# perl has a problem with the internal "large_and_huge_malloc" function +# when calling perl using MacOS 10.5 with a perl built with MacOS 10.4 + while ( my $line = ) { + push @localfile, $line; + } + + close( IN ); + + return \@localfile; +} + +########################################### +# Saving files, arrays and hashes +########################################### + +sub save_file +{ + my ($savefile, $savecontent) = @_; + + if ( open( OUT, ">$savefile" ) ) + { + print OUT @{$savecontent}; + close( OUT); + } + else + { + # it is useless to save a log file, if there is no write access + + if ( $savefile =~ /\.log/ ) + { + print "\n*************************************************\n"; + print "ERROR: Cannot write log file $savefile, $!"; + print "\n*************************************************\n"; + exit(-1); # exiting the program to avoid endless loops + } + + installer::exiter::exit_program("ERROR: Cannot open file $savefile for writing", "save_file"); + } +} + +########################################### +# Binary file operations +########################################### + +sub read_binary_file +{ + my ($filename) = @_; + + my $file; + + open( IN, "<$filename" ) || installer::exiter::exit_program("ERROR: Cannot open file $filename for reading", "read_binary_file"); + binmode IN; + seek IN, 0, 2; + my $length = tell IN; + seek IN, 0, 0; + read IN, $file, $length; + close IN; + + return $file; +} + +sub save_binary_file +{ + my ($file, $filename) = @_; + + open( OUT, ">$filename" ) || installer::exiter::exit_program("ERROR: Cannot open file $filename for writing", "save_binary_file"); + binmode OUT; + print OUT $file; + close OUT; +} + +1; diff --git a/solenv/bin/modules/installer/globals.pm b/solenv/bin/modules/installer/globals.pm new file mode 100644 index 000000000..5bbaef309 --- /dev/null +++ b/solenv/bin/modules/installer/globals.pm @@ -0,0 +1,290 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::globals; + +############################################ +# Global settings +############################################ + +BEGIN +{ + $ziplistname = ""; + $pathfilename = ""; + $setupscriptname = ""; + $product = ""; + $languagelist = ""; + $added_english = 0; + $set_office_start_language = 0; + + $destdir = ""; + $rootpath = ""; + + @languageproducts = (); + $build = ""; + $os = ""; + $cpuname = ""; + $com = ""; + $platformid = ""; + $pro = 0; + $dounzip = 1; + $languages_defined_in_productlist = 0; + $setupscript_defined_in_productlist = 0; + $iswindowsbuild = 0; + $islinuxbuild = 0; + $isrpmbuild = 0; + $isdebbuild = 0; + $issolarisbuild = 0; + $issolarispkgbuild = 0; + $issolarissparcbuild = 0; + $issolarisx86build = 0; + $isfreebsdbuild = 0; + $isfreebsdpkgbuild = 0; + $ismacbuild = 0; + $ismacdmgbuild = 0; + $unpackpath = ""; + $workpath = ""; # installation working dir; some helper scripts are + # placed here by gbuild + $idttemplatepath = ""; + $idtlanguagepath = ""; + $buildid = "Not set"; + $fontsfolder = "FontsFolder"; + $fontsfoldername = "Fonts"; + $fontsdirparent = ""; + $fontsdirname = ""; + $fontsdirhostname = "truetype"; + $officemenufolder = "OfficeMenuFolder"; + $startupfolder = "StartupFolder"; + $startmenufolder = "StartMenuFolder"; + $desktopfolder = "DesktopFolder"; + $programfilesfolder = "ProgramFilesFolder"; + $commonfilesfolder = "CommonFilesFolder"; + $commonappdatafolder = "CommonAppDataFolder"; + $localappdatafolder = "LocalAppDataFolder"; + $templatefolder = "TemplateFolder"; + $templatefoldername = "Templates"; + $programmenufolder = "ProgramMenuFolder"; + $lcidlistname = $ENV{'SRCDIR'} . "/l10ntools/source/ulfconv/msi-encodinglist.txt"; + $msilanguage = ""; # hash reference for msi languages LCID + $sofficeiconadded = 0; + $temppath = ""; + $cyg_temppath = ""; + $temppathdefined = 0; + $packageversion = 1; + $packagerevision = 1; + $rpm = ""; + $rpminfologged = 0; + $debian = ""; + $installertypedir = ""; + $controlledmakecabversion = "5"; + $max_lang_length = 50; + $globalblock = "Globals"; + $rootmodulegid = ""; + %alllangmodules = (); + $englishlicenseset = 0; + $englishlicense = ""; + $englishsolarislicensename = "LICENSE"; # _en-US"; + $solarisdontcompress = 0; + $patharray = ""; + + $is_special_epm = 0; + $epm_in_path = 0; + $epm_path = ""; + $epmoutpath = ""; + $simple = 0; + $simpledefaultuserdir = "\$ORIGIN/.."; + $call_epm = 1; + $packageformat = ""; + $packagename = ""; + $packagelist = ""; + $shiptestdirectory = ""; + $archiveformat = ""; + $updatelastsequence = 0; + $updatesequencecounter = 0; + $updatedatabase = 0; + $updatedatabasepath = ""; + $pffcabfilename = "ooobasis3.0_pff.cab"; + %allmergemodulefilesequences = (); + %newupdatefiles = (); + %allusedupdatesequences = (); + %mergemodulefiles = (); + $mergefiles_added_into_collector = 0; + $creating_windows_installer_patch = 0; + + $strip = 0; + + $packertool = "gzip"; # the default package compression tool for *NIX + + $logfilename = "logfile.log"; # the default logfile name for global errors + @logfileinfo = (); + @errorlogfileinfo = (); + @globallogfileinfo = (); + $ignore_error_in_logfile = 0; + $exitlog = ""; + $quiet = 0; + + $ismultilingual = 0; + %alluniquefilenames = (); + %alllcuniquefilenames = (); + %uniquefilenamesequence = (); + %dependfilenames = (); + $manufacturer = ""; + $longmanufacturer = ""; + $codefilename = "codes.txt"; + $componentfilename = "components.txt"; + $productcode = ""; + $upgradecode = ""; + $msiproductversion = ""; + $msimajorproductversion = ""; + @allddffiles = (); + $infodirectory = ""; + + %mergemodules = (); + %merge_media_line = (); + %merge_allfeature_hash = (); + %merge_alldirectory_hash = (); + %merge_directory_hash = (); + %copy_msm_files = (); + $mergefeaturecollected = 0; + $mergedirectoriescollected = 0; + $lastsequence_before_merge = 0; + $lastcabfilename = ""; + + $defaultlanguage = ""; + $addlicensefile = 1; + $addsystemintegration = 0; + $makedownload = 1; + @binarytableonlyfiles = (); + @allscpactions = (); + $languagepackaddon = "LanguagePack"; + $helppackaddon = "HelpPack"; + $ooodownloadfilename = ""; + $downloadfilename = ""; + $downloadfileextension = ""; + %multilingual_only_modules = (); + %application_modules = (); + + $is_copy_only_project = 0; + $is_simple_packager_project = 0; + $patch_user_dir = 0; + $languagepack = 0; + $helppack = 0; + $refresh_includepaths = 0; + $include_paths_read = 0; + @patchfilecollector = (); + @userregistrycollector = (); + $addeduserregitrykeys = 0; + $desktoplinkexists = 0; + $analyze_spellcheckerlanguage = 0; + %spellcheckerlanguagehash = (); + %spellcheckerfilehash = (); + $registryrootcomponent = ""; + %allcomponents = (); + %allcomponents_in_this_database = (); + %allshortcomponents = (); + %allregistrycomponents_ = (); + %allregistrycomponents_in_this_database_ = (); + %allshortregistrycomponents = (); + + $installlocationdirectory = ""; + $installlocationdirectoryset = 0; + $vendordirectory = ""; + $officeinstalldirectory = ""; + $rootbrandpackage = ""; + $rootbrandpackageset = 0; + $officedirhostname = ""; + $officedirgid = ""; + + %treestyles = (); + %treelayername = (); + %hostnametreestyles = (); + %treeconditions = (); + %usedtreeconditions = (); + %moduledestination = (); + + $fix_number_of_cab_files = 1; + $cabfilecompressionlevel = 21; # Using LZX compression, possible values are: 15 | 16 | ... | 21 (best compression) + $number_of_cabfiles = 1; # only for $fix_number_of_cab_files = 1 + $include_cab_in_msi = 1; + $msidatabasename = ""; + $prepare_winpatch = 0; + $previous_idt_dir = ""; + $msitranpath = ""; + $insert_file_at_end = 0; + $newfilesexist = 0; + $usesharepointpath = 0; + %newfilescollector = (); + + $saveinstalldir = ""; + $csp_installdir = ""; # global installdir of createsimplepackage() in simplepackage.pm + $csp_installlogdir = ""; # global installlogdir of createsimplepackage() in simplepackage.pm + $csp_languagestring = ""; # global languagestring of createsimplepackage() in simplepackage.pm + $localunpackdir = ""; + $localinstalldirset = 0; + $localinstalldir = ""; + + $postprocess_specialepm = 0; + $postprocess_standardepm = 0; + $mergemodules_analyzed = 0; + + @packagelistitems = ("module", "solarispackagename", "packagename", "copyright", "vendor", "description" ); + @featurecollector =(); + $msiassemblyfiles = ""; + $macinstallfilename = $ENV{'WORKDIR'} . "/CustomTarget/setup_native/mac/macinstall.ulf"; + $extensioninstalldir = "gid_Dir_Share_Extension_Install"; + @languagenames = (); + %componentcondition = (); + %componentid = (); + %allcabinets = (); + %allcabinetassigns = (); + %cabfilecounter = (); + %lastsequence = (); + %allcalculated_guids = (); + %calculated_component_guids = (); + %all_english_languagestrings = (); + %all_required_english_languagestrings = (); + + @removedirs = (); + @removefiletable = (); + + if ( $^O =~ /cygwin/i ) + { + $zippath = "zip"; # Has to be in the path: /usr/bin/zip + $separator = "/"; + $pathseparator = "\:"; + $isunix = 0; + $iswin = 1; + $archiveformat = ".zip"; + %savedmapping = (); + %savedrevmapping = (); + %savedrev83mapping = (); + %saved83dirmapping = (); + } + else + { + $zippath = "zip"; # Has to be in the path: /usr/bin/zip + $separator = "/"; + $pathseparator = "\:"; + $archiveformat = ".tar.gz"; + $isunix = 1; + $iswin = 0; + } + +} + +1; diff --git a/solenv/bin/modules/installer/helppack.pm b/solenv/bin/modules/installer/helppack.pm new file mode 100644 index 000000000..456e91ecf --- /dev/null +++ b/solenv/bin/modules/installer/helppack.pm @@ -0,0 +1,530 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::helppack; + +use strict; +use warnings; + +use installer::converter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::scpzipfiles; +use installer::scriptitems; +use installer::systemactions; +use installer::worker; + +sub select_help_items +{ + my ( $itemsref, $languagesarrayref, $itemname ) = @_; + + installer::logger::include_header_into_logfile("Selecting items for help pack. Item: $itemname"); + + my @itemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + my $oneitem = ${$itemsref}[$i]; + + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + + if (( $styles =~ /\bHELPPACK\b/ ) || ( $styles =~ /\bFORCEHELPPACK\b/ )) + { + # Files with style "HELPPACK" and "FORCEHELPPACK" also have to be included into the help pack. + # Files with style "HELPPACK" are only included into help packs. + # Files with style "FORCEHELPPACK" are included into help packs and non help packs. They are + # forced, because otherwise they may not be included into helppacks. + + my $ismultilingual = $oneitem->{'ismultilingual'}; + + if ($ismultilingual) + { + my $specificlanguage = ""; + if ( $oneitem->{'specificlanguage'} ) { $specificlanguage = $oneitem->{'specificlanguage'}; } + + for ( my $j = 0; $j <= $#{$languagesarrayref}; $j++ ) # iterating over all languages + { + my $onelanguage = ${$languagesarrayref}[$j]; + my $locallang = $onelanguage; + $locallang =~ s/-/_/; + + if ( $specificlanguage eq $onelanguage ) + { + push(@itemsarray, $oneitem); + } + } + } + else + { + push(@itemsarray, $oneitem); + } + } + } + + return \@itemsarray; +} + +sub replace_languagestring_variable +{ + my ($onepackageref, $languagestringref) = @_; + + my $key; + + foreach $key (keys %{$onepackageref}) + { + my $value = $onepackageref->{$key}; + $value =~ s/\%LANGUAGESTRING/$$languagestringref/g; + $onepackageref->{$key} = $value; + } +} + +######################################################### +# Including the license text into the script template +######################################################### + +sub put_license_file_into_script +{ + my ($scriptfile, $licensefile) = @_; + + my $infoline = "Adding licensefile into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $includestring = ""; + + for ( my $i = 0; $i <= $#{$licensefile}; $i++ ) + { + $includestring = $includestring . ${$licensefile}[$i]; + } + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/LICENSEFILEPLACEHOLDER/$includestring/; + } +} + +######################################################### +# Creating a tar.gz file from a Solaris package +######################################################### + +sub create_tar_gz_file +{ + my ($installdir, $packagename, $packagestring) = @_; + + $packagename =~ s/\.rpm\s*$//; + my $targzname = $packagename . ".tar.gz"; + my $systemcall = "cd $installdir; tar -cf - $packagestring | $installer::globals::packertool > $targzname"; + installer::logger::print_message( "... $systemcall ...\n" ); + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + return $targzname; +} + +######################################################### +# Determining the name of the package file +######################################################### + +sub get_packagename_from_packagelist +{ + my ( $alldirs, $allvariables, $languagestringref ) = @_; + + # my $packagename = ""; + + # for ( my $i = 0; $i <= $#{$alldirs}; $i++ ) + # { + # if ( ${$alldirs}[$i] =~ /-fonts/ ) { next; } + # if ( ${$alldirs}[$i] =~ /-help/ ) { next; } + # if ( ${$alldirs}[$i] =~ /-res/ ) { next; } + # + # $packagename = ${$alldirs}[$i]; + # last; + # } + + # if ( $packagename eq "" ) { installer::exiter::exit_program("ERROR: Could not find base package in directory $installdir!", "get_packagename_from_packagelist"); } + + my $localproductname = $allvariables->{'PRODUCTNAME'}; + $localproductname = lc($localproductname); + $localproductname =~ s/ //g; + $localproductname =~ s/-/_/g; + + my $packagename = $localproductname . "_" . $$languagestringref; + + return $packagename; +} + +######################################################### +# Determining the name of the package file or the rpm +# in the installation directory. For help packs +# there is only one file in this directory +######################################################### + +sub determine_packagename +{ + my ( $installdir, $allvariables, $languagestringref ) = @_; + + my $packagename = ""; + my $allnames = ""; + + if ( $installer::globals::isrpmbuild ) + { + # determining the rpm file in directory $installdir + + my $fileextension = "rpm"; + my $rpmfiles = installer::systemactions::find_file_with_file_extension($fileextension, $installdir); + if ( ! ( $#{$rpmfiles} > -1 )) { installer::exiter::exit_program("ERROR: Could not find package in directory $installdir!", "determine_packagename"); } + my $rpmsav = [@{$rpmfiles}]; + for ( my $i = 0; $i <= $#{$rpmfiles}; $i++ ) { installer::pathanalyzer::make_absolute_filename_to_relative_filename(\${$rpmfiles}[$i]); } + + $packagename = get_packagename_from_packagelist($rpmfiles, $allvariables, $languagestringref); + + my $packagestring = installer::converter::convert_array_to_space_separated_string($rpmfiles); + $packagename = create_tar_gz_file($installdir, $packagename, $packagestring); # only one file + for ( my $i = 0; $i <= $#{$rpmsav}; $i++ ) + { + my $onefile = $installdir . $installer::globals::separator . ${$rpmsav}[$i]; + unlink($onefile); + } + + $allnames = $rpmfiles; + } + + if ( $installer::globals::issolarisbuild ) + { + # determining the Solaris package file in directory $installdir + my $alldirs = installer::systemactions::get_all_directories($installdir); + + if ( ! ( $#{$alldirs} > -1 )) { installer::exiter::exit_program("ERROR: Could not find package in directory $installdir!", "determine_packagename"); } + my $alldirssav = [@{$alldirs}]; + for ( my $i = 0; $i <= $#{$alldirs}; $i++ ) { installer::pathanalyzer::make_absolute_filename_to_relative_filename(\${$alldirs}[$i]); } + + $packagename = get_packagename_from_packagelist($alldirs, $allvariables, $languagestringref); + my $packagestring = installer::converter::convert_array_to_space_separated_string($alldirs); + $packagename = create_tar_gz_file($installdir, $packagename, $packagestring); # only a file (not a directory) can be included into the shell script + for ( my $i = 0; $i <= $#{$alldirssav}; $i++ ) { installer::systemactions::remove_complete_directory(${$alldirssav}[$i], 1); } + $allnames = $alldirs; + } + + my $infoline = "Found package in installation directory $installdir : $packagename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return ( $packagename, $allnames); +} + +######################################################### +# Including the name of the package file or the rpm +# into the script template +######################################################### + +sub put_packagename_into_script +{ + my ($scriptfile, $packagename, $allnames) = @_; + + my $localpackagename = $packagename; + $localpackagename =~ s/\.tar\.gz//; # making "OOOopenoffice-it-ea.tar.gz" to "OOOopenoffice-it-ea" + my $infoline = "Adding packagename $localpackagename into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $installline = ""; + + if ( $installer::globals::issolarisbuild ) { $installline = " /usr/sbin/pkgadd -d \$outdir -a \$adminfile"; } + + if ( $installer::globals::isrpmbuild ) { $installline = " rpm --prefix \$PRODUCTINSTALLLOCATION --replacepkgs -i"; } + + for ( my $i = 0; $i <= $#{$allnames}; $i++ ) + { + if ( $installer::globals::issolarisbuild ) { $installline = $installline . " ${$allnames}[$i]"; } + + if ( $installer::globals::isrpmbuild ) { $installline = $installline . " \$outdir/${$allnames}[$i]"; } + } + + for ( my $j = 0; $j <= $#{$scriptfile}; $j++ ) + { + ${$scriptfile}[$j] =~ s/INSTALLLINES/$installline/; + } +} + +################################################################## +# Including the lowercase product name into the script template +################################################################## + +sub put_productname_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $productname = $variableshashref->{'PRODUCTNAME'}; + $productname = lc($productname); + $productname =~ s/\.//g; # openoffice.org -> openofficeorg + + my $infoline = "Adding productname $productname into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/PRODUCTNAMEPLACEHOLDER/$productname/; + } +} + +################################################################## +# Including the full product name into the script template +# (name and version) +################################################################## + +sub put_fullproductname_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $productname = $variableshashref->{'PRODUCTNAME'}; + my $productversion = ""; + if ( $variableshashref->{'PRODUCTVERSION'} ) { $productversion = $variableshashref->{'PRODUCTVERSION'}; }; + my $fullproductname = $productname . " " . $productversion; + + my $infoline = "Adding full productname \"$fullproductname\" into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/FULLPRODUCTNAMELONGPLACEHOLDER/$fullproductname/; + } +} + +################################################################## +# Including the name of the search package (-core01) +# into the script template +################################################################## + +sub put_searchpackage_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $basispackageprefix = $variableshashref->{'BASISPACKAGEPREFIX'}; + my $productversion = $variableshashref->{'PRODUCTVERSION'}; + + if ( $installer::globals::issolarisbuild ) { $productversion =~ s/\.//g; } # "3.0" -> "30" + + my $infoline = "Adding basis package prefix $basispackageprefix into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + $infoline = "Adding basis package version $productversion into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/BASISPACKAGEPREFIXPLACEHOLDER/$basispackageprefix/; + ${$scriptfile}[$i] =~ s/PRODUCTVERSIONPLACEHOLDER/$productversion/; + } + +} + +######################################################### +# Including the linenumber into the script template +######################################################### + +sub put_linenumber_into_script +{ + my ( $scriptfile, $licensefile, $allnames ) = @_; + + my $linenumber = $#{$scriptfile} + $#{$licensefile} + 3; # also adding the content of the license file! + + my $infoline = "Adding linenumber $linenumber into help pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/LINENUMBERPLACEHOLDER/$linenumber/; + } +} + +######################################################### +# Determining the name of the new scriptfile +######################################################### + +sub determine_scriptfile_name +{ + my ( $packagename ) = @_; + + my $scriptfilename = $packagename; + +# if ( $installer::globals::isrpmbuild ) { $scriptfilename =~ s/\.rpm\s*$/\.sh/; } +# if ( $installer::globals::issolarisbuild ) { $scriptfilename =~ s/\.tar\.gz\s*$/\.sh/; } + + $scriptfilename =~ s/\.tar\.gz\s*$/\.sh/; + + my $infoline = "Setting help pack script file name to $scriptfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $scriptfilename; +} + +######################################################### +# Saving the script file in the installation directory +######################################################### + +sub save_script_file +{ + my ($installdir, $newscriptfilename, $scriptfile) = @_; + + $newscriptfilename = $installdir . $installer::globals::separator . $newscriptfilename; + installer::files::save_file($newscriptfilename, $scriptfile); + + my $infoline = "Saving script file $newscriptfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $newscriptfilename; +} + +######################################################### +# Including the binary package into the script +######################################################### + +sub include_package_into_script +{ + my ( $scriptfilename, $installdir, $packagename ) = @_; + + my $longpackagename = $installdir . $installer::globals::separator . $packagename; + my $systemcall = "cat $longpackagename >>$scriptfilename"; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + my $localcall = "chmod 775 $scriptfilename \>\/dev\/null 2\>\&1"; + system($localcall); + +} + +######################################################### +# Removing the binary package +######################################################### + +sub remove_package +{ + my ( $installdir, $packagename ) = @_; + + my $remove_package = 1; + + if ( $ENV{'DONT_REMOVE_PACKAGE'} ) { $remove_package = 0; } + + if ( $remove_package ) + { + my $longpackagename = $installdir . $installer::globals::separator . $packagename; + unlink $longpackagename; + + my $infoline = "Removing package: $longpackagename \n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +#################################################### +# Unix help packs, that are not part of +# multilingual installation sets, need a +# shell script installer +#################################################### + +sub build_installer_for_helppack +{ + my ($installdir, $allvariableshashref, $includepatharrayref, $languagesarrayref, $languagestringref) = @_; + + installer::logger::print_message( "... creating shell script installer ...\n" ); + + installer::logger::include_header_into_logfile("Creating shell script installer:"); + + # find and read setup script template + + my $scriptfilename = "langpackscript.sh"; + my $scriptref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$scriptfilename, $includepatharrayref, 0); + if ($$scriptref eq "") { installer::exiter::exit_program("ERROR: Could not find script file $scriptfilename!", "build_installer_for_helppack"); } + my $scriptfile = installer::files::read_file($$scriptref); + + my $infoline = "Found script file $scriptfilename: $$scriptref \n"; + push( @installer::globals::logfileinfo, $infoline); + + # find and read english license file + my $licenselanguage = "en-US"; # always english ! + my $licensefilename = "LICENSE"; + my $licenseincludepatharrayref = installer::worker::get_language_specific_include_paths($includepatharrayref, $licenselanguage); + + my $licenseref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$licensefilename, $licenseincludepatharrayref, 0); + if ($$licenseref eq "") { installer::exiter::exit_program("ERROR: Could not find License file $licensefilename!", "build_installer_for_helppack"); } + my $licensefile = installer::files::read_file($$licenseref); + + $infoline = "Found licensefile $licensefilename: $$licenseref \n"; + push( @installer::globals::logfileinfo, $infoline); + + # including variables into license file + installer::scpzipfiles::replace_all_ziplistvariables_in_file($licensefile, $allvariableshashref); + + # add license text into script template + put_license_file_into_script($scriptfile, $licensefile); + + # add rpm or package file name into script template + my ( $packagename, $allnames) = determine_packagename($installdir, $allvariableshashref, $languagestringref); + put_packagename_into_script($scriptfile, $packagename, $allnames); + + # add product name into script template + put_productname_into_script($scriptfile, $allvariableshashref); + + # add product name into script template + put_fullproductname_into_script($scriptfile, $allvariableshashref); + + # add product name into script template + put_searchpackage_into_script($scriptfile, $allvariableshashref); + + # replace linenumber in script template + put_linenumber_into_script($scriptfile, $licensefile, $allnames); + + # saving the script file + my $newscriptfilename = determine_scriptfile_name($packagename); + $newscriptfilename = save_script_file($installdir, $newscriptfilename, $scriptfile); + + # include rpm or package into script + include_package_into_script($newscriptfilename, $installdir, $packagename); + + # remove rpm or package + remove_package($installdir, $packagename); +} + +1; diff --git a/solenv/bin/modules/installer/languagepack.pm b/solenv/bin/modules/installer/languagepack.pm new file mode 100644 index 000000000..14a870866 --- /dev/null +++ b/solenv/bin/modules/installer/languagepack.pm @@ -0,0 +1,509 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::languagepack; + +use installer::converter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::scpzipfiles; +use installer::scriptitems; +use installer::systemactions; +use installer::worker; + +#################################################### +# Selecting all files with the correct language +#################################################### + +sub select_language_items +{ + my ( $itemsref, $languagesarrayref, $itemname ) = @_; + + installer::logger::include_header_into_logfile("Selecting languages for language pack. Item: $itemname"); + + my @itemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + my $oneitem = ${$itemsref}[$i]; + + my $ismultilingual = $oneitem->{'ismultilingual'}; + + if (!($ismultilingual)) + { + # Files with style "LANGUAGEPACK" and "FORCELANGUAGEPACK" also have to be included into the language pack. + # Files with style "LANGUAGEPACK" are only included into language packs. + # Files with style "FORCELANGUAGEPACK" are included into language packs and non language packs. They are + # forced, because otherwise they may not be included into languagepacks. + + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + + if (( $styles =~ /\bLANGUAGEPACK\b/ ) || ( $styles =~ /\bFORCELANGUAGEPACK\b/ )) { push(@itemsarray, $oneitem); } + + next; # single language files are not included into language pack + } + + my $specificlanguage = ""; + if ( $oneitem->{'specificlanguage'} ) { $specificlanguage = $oneitem->{'specificlanguage'}; } + + for ( my $j = 0; $j <= $#{$languagesarrayref}; $j++ ) # iterating over all languages + { + my $onelanguage = ${$languagesarrayref}[$j]; + my $locallang = $onelanguage; + $locallang =~ s/-/_/; + + if ( $specificlanguage eq $onelanguage ) + { + push(@itemsarray, $oneitem); + } + } + } + + return \@itemsarray; +} + +sub replace_languagestring_variable +{ + my ($onepackageref, $languagestringref) = @_; + + my $key; + + foreach $key (keys %{$onepackageref}) + { + my $value = $onepackageref->{$key}; + $value =~ s/\%LANGUAGESTRING/$$languagestringref/g; + $onepackageref->{$key} = $value; + } +} + +######################################################### +# Including the license text into the script template +######################################################### + +sub put_license_file_into_script +{ + my ($scriptfile, $licensefile) = @_; + + my $infoline = "Adding licensefile into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $includestring = ""; + + for ( my $i = 0; $i <= $#{$licensefile}; $i++ ) + { + $includestring = $includestring . ${$licensefile}[$i]; + } + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/LICENSEFILEPLACEHOLDER/$includestring/; + } +} + +######################################################### +# Creating a tar.gz file from a Solaris package +######################################################### + +sub create_tar_gz_file +{ + my ($installdir, $packagename, $packagestring) = @_; + + $packagename =~ s/\.rpm\s*$//; + my $targzname = $packagename . ".tar.gz"; + $systemcall = "cd $installdir; tar -cf - $packagestring | $installer::globals::packertool > $targzname"; + installer::logger::print_message( "... $systemcall ...\n" ); + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + return $targzname; +} + +######################################################### +# Determining the name of the package file +######################################################### + +sub get_packagename_from_packagelist +{ + my ( $alldirs, $allvariables, $languagestringref ) = @_; + + my $localproductname = $allvariables->{'PRODUCTNAME'}; + $localproductname = lc($localproductname); + $localproductname =~ s/ //g; + $localproductname =~ s/-/_/g; + + my $packagename = $localproductname . "_" . $$languagestringref; + + return $packagename; +} + +######################################################### +# Determining the name of the package file or the rpm +# in the installation directory. For language packs +# there is only one file in this directory +######################################################### + +sub determine_packagename +{ + my ( $installdir, $allvariables, $languagestringref ) = @_; + + my $packagename = ""; + my $allnames = ""; + + if ( $installer::globals::isrpmbuild ) + { + # determining the rpm file in directory $installdir + + my $fileextension = "rpm"; + my $rpmfiles = installer::systemactions::find_file_with_file_extension($fileextension, $installdir); + if ( ! ( $#{$rpmfiles} > -1 )) { installer::exiter::exit_program("ERROR: Could not find package in directory $installdir!", "determine_packagename"); } + my $rpmsav = [@{$rpmfiles}]; + for ( my $i = 0; $i <= $#{$rpmfiles}; $i++ ) { installer::pathanalyzer::make_absolute_filename_to_relative_filename(\${$rpmfiles}[$i]); } + + $packagename = get_packagename_from_packagelist($rpmfiles, $allvariables, $languagestringref); + + my $packagestring = installer::converter::convert_array_to_space_separated_string($rpmfiles); + $packagename = create_tar_gz_file($installdir, $packagename, $packagestring); # only one file + for ( my $i = 0; $i <= $#{$rpmsav}; $i++ ) + { + my $onefile = $installdir . $installer::globals::separator . ${$rpmsav}[$i]; + unlink($onefile); + } + + $allnames = $rpmfiles; + } + + if ( $installer::globals::issolarisbuild ) + { + # determining the Solaris package file in directory $installdir + my $alldirs = installer::systemactions::get_all_directories($installdir); + + if ( ! ( $#{$alldirs} > -1 )) { installer::exiter::exit_program("ERROR: Could not find package in directory $installdir!", "determine_packagename"); } + my $alldirssav = [@{$alldirs}]; + for ( my $i = 0; $i <= $#{$alldirs}; $i++ ) { installer::pathanalyzer::make_absolute_filename_to_relative_filename(\${$alldirs}[$i]); } + + $packagename = get_packagename_from_packagelist($alldirs, $allvariables, $languagestringref); + my $packagestring = installer::converter::convert_array_to_space_separated_string($alldirs); + $packagename = create_tar_gz_file($installdir, $packagename, $packagestring); # only a file (not a directory) can be included into the shell script + for ( my $i = 0; $i <= $#{$alldirssav}; $i++ ) { installer::systemactions::remove_complete_directory(${$alldirssav}[$i], 1); } + $allnames = $alldirs; + } + + my $infoline = "Found package in installation directory $installdir : $packagename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return ( $packagename, $allnames); +} + +######################################################### +# Including the name of the package file or the rpm +# into the script template +######################################################### + +sub put_packagename_into_script +{ + my ($scriptfile, $packagename, $allnames) = @_; + + my $localpackagename = $packagename; + $localpackagename =~ s/\.tar\.gz//; # making "OOOopenoffice-it-ea.tar.gz" to "OOOopenoffice-it-ea" + my $infoline = "Adding packagename $localpackagename into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $installline = ""; + + if ( $installer::globals::issolarisbuild ) { $installline = " /usr/sbin/pkgadd -d \$outdir -a \$adminfile"; } + + if ( $installer::globals::isrpmbuild ) { $installline = " rpm --prefix \$PRODUCTINSTALLLOCATION --replacepkgs -i"; } + + for ( my $i = 0; $i <= $#{$allnames}; $i++ ) + { + if ( $installer::globals::issolarisbuild ) { $installline = $installline . " ${$allnames}[$i]"; } + + if ( $installer::globals::isrpmbuild ) { $installline = $installline . " \$outdir/${$allnames}[$i]"; } + } + + for ( my $j = 0; $j <= $#{$scriptfile}; $j++ ) + { + ${$scriptfile}[$j] =~ s/INSTALLLINES/$installline/; + } +} + +################################################################## +# Including the lowercase product name into the script template +################################################################## + +sub put_productname_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $productname = $variableshashref->{'PRODUCTNAME'}; + $productname = lc($productname); + $productname =~ s/\.//g; # openoffice.org -> openofficeorg + + my $infoline = "Adding productname $productname into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/PRODUCTNAMEPLACEHOLDER/$productname/; + } +} + +################################################################## +# Including the full product name into the script template +# (name and version) +################################################################## + +sub put_fullproductname_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $productname = $variableshashref->{'PRODUCTNAME'}; + my $productversion = ""; + if ( $variableshashref->{'PRODUCTVERSION'} ) { $productversion = $variableshashref->{'PRODUCTVERSION'}; }; + my $fullproductname = $productname . " " . $productversion; + + my $infoline = "Adding full productname \"$fullproductname\" into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/FULLPRODUCTNAMELONGPLACEHOLDER/$fullproductname/; + } +} + +################################################################## +# Including the name of the search package (-core01) +# into the script template +################################################################## + +sub put_searchpackage_into_script +{ + my ($scriptfile, $variableshashref) = @_; + + my $basispackageprefix = $variableshashref->{'BASISPACKAGEPREFIX'}; + my $productversion = $variableshashref->{'PRODUCTVERSION'}; + + if ( $installer::globals::issolarisbuild ) { $productversion =~ s/\.//g; } # "3.0" -> "30" + + my $infoline = "Adding basis package prefix $basispackageprefix into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + $infoline = "Adding basis package version $productversion into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/BASISPACKAGEPREFIXPLACEHOLDER/$basispackageprefix/; + ${$scriptfile}[$i] =~ s/PRODUCTVERSIONPLACEHOLDER/$productversion/; + } + +} + +######################################################### +# Including the linenumber into the script template +######################################################### + +sub put_linenumber_into_script +{ + my ( $scriptfile, $licensefile, $allnames ) = @_; + + my $linenumber = $#{$scriptfile} + $#{$licensefile} + 3; # also adding the content of the license file! + + my $infoline = "Adding linenumber $linenumber into language pack script\n"; + push( @installer::globals::logfileinfo, $infoline); + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/LINENUMBERPLACEHOLDER/$linenumber/; + } +} + +######################################################### +# Determining the name of the new scriptfile +######################################################### + +sub determine_scriptfile_name +{ + my ( $packagename ) = @_; + + my $scriptfilename = $packagename; + + $scriptfilename =~ s/\.tar\.gz\s*$/\.sh/; + + my $infoline = "Setting language pack script file name to $scriptfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $scriptfilename; +} + +######################################################### +# Saving the script file in the installation directory +######################################################### + +sub save_script_file +{ + my ($installdir, $newscriptfilename, $scriptfile) = @_; + + $newscriptfilename = $installdir . $installer::globals::separator . $newscriptfilename; + installer::files::save_file($newscriptfilename, $scriptfile); + + my $infoline = "Saving script file $newscriptfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $newscriptfilename; +} + +######################################################### +# Including the binary package into the script +######################################################### + +sub include_package_into_script +{ + my ( $scriptfilename, $installdir, $packagename ) = @_; + + my $longpackagename = $installdir . $installer::globals::separator . $packagename; + my $systemcall = "cat $longpackagename >>$scriptfilename"; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + chmod 0775, $scriptfilename; + +} + +######################################################### +# Removing the binary package +######################################################### + +sub remove_package +{ + my ( $installdir, $packagename ) = @_; + + my $remove_package = 1; + + if ( $ENV{'DONT_REMOVE_PACKAGE'} ) { $remove_package = 0; } + + if ( $remove_package ) + { + my $longpackagename = $installdir . $installer::globals::separator . $packagename; + unlink $longpackagename; + + my $infoline = "Removing package: $longpackagename \n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +#################################################### +# Unix language packs, that are not part of +# multilingual installation sets, need a +# shell script installer +#################################################### + +sub build_installer_for_languagepack +{ + my ($installdir, $allvariableshashref, $includepatharrayref, $languagesarrayref, $languagestringref) = @_; + + installer::logger::print_message( "... creating shell script installer ...\n" ); + + installer::logger::include_header_into_logfile("Creating shell script installer:"); + + # find and read setup script template + + my $scriptfilename = $ENV{'SRCDIR'} . "/setup_native/scripts/langpackscript.sh"; + if (! -f $scriptfilename) { installer::exiter::exit_program("ERROR: Could not find script file $scriptfilename!", "build_installer_for_languagepack"); } + my $scriptfile = installer::files::read_file($scriptfilename); + + my $infoline = "Found script file $scriptfilename: $scriptfile \n"; + push( @installer::globals::logfileinfo, $infoline); + + # find and read english license file + my $licenselanguage = "en-US"; # always english ! + my $licensefilename = "LICENSE"; + my $licenseincludepatharrayref = installer::worker::get_language_specific_include_paths($includepatharrayref, $licenselanguage); + + my $licenseref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$licensefilename, $licenseincludepatharrayref, 0); + if ($$licenseref eq "") { installer::exiter::exit_program("ERROR: Could not find License file $licensefilename!", "build_installer_for_languagepack"); } + my $licensefile = installer::files::read_file($$licenseref); + + $infoline = "Found licensefile $licensefilename: $$licenseref \n"; + push( @installer::globals::logfileinfo, $infoline); + + # including variables into license file + installer::scpzipfiles::replace_all_ziplistvariables_in_file($licensefile, $allvariableshashref); + + # add license text into script template + put_license_file_into_script($scriptfile, $licensefile); + + # add rpm or package file name into script template + my ( $packagename, $allnames) = determine_packagename($installdir, $allvariableshashref, $languagestringref); + put_packagename_into_script($scriptfile, $packagename, $allnames); + + # add product name into script template + put_productname_into_script($scriptfile, $allvariableshashref); + + # add product name into script template + put_fullproductname_into_script($scriptfile, $allvariableshashref); + + # add product name into script template + put_searchpackage_into_script($scriptfile, $allvariableshashref); + + # replace linenumber in script template + put_linenumber_into_script($scriptfile, $licensefile, $allnames); + + # saving the script file + my $newscriptfilename = determine_scriptfile_name($packagename); + $newscriptfilename = save_script_file($installdir, $newscriptfilename, $scriptfile); + + # include rpm or package into script + include_package_into_script($newscriptfilename, $installdir, $packagename); + + # remove rpm or package + remove_package($installdir, $packagename); +} + +1; diff --git a/solenv/bin/modules/installer/languages.pm b/solenv/bin/modules/installer/languages.pm new file mode 100644 index 000000000..e25905383 --- /dev/null +++ b/solenv/bin/modules/installer/languages.pm @@ -0,0 +1,142 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::languages; + +use installer::converter; +use installer::exiter; +use installer::globals; +use installer::remover; +use installer::ziplist; + +############################################################################# +# Analyzing the language list parameter and language list from zip list file +############################################################################# + +sub analyze_languagelist +{ + my $first = $installer::globals::languagelist; + + $first =~ s/\_/\,/g; # substituting "_" by ",", in case of dmake definition 01_49 + + # Products are separated by a "#", if defined in zip-list by a "|". But "get_info_about_languages" + # substitutes already "|" to "#". This procedure only knows "#" as product separator. + # Different languages for one product are separated by ",". But on the command line the "_" is used. + # Therefore "_" is replaced by "," at the beginning of this procedure. + + while ($first =~ /^(\S+)\#(\S+?)$/) # Minimal matching, to keep the order of languages + { + $first = $1; + my $last = $2; + unshift(@installer::globals::languageproducts, $last); + } + + unshift(@installer::globals::languageproducts, $first); +} + +#################################################### +# Reading languages from zip list file +#################################################### + +sub get_info_about_languages +{ + my ( $allsettingsarrayref ) = @_; + + my $languagelistref; + + $languagelistref = installer::ziplist::getinfofromziplist($allsettingsarrayref, "languages"); + $installer::globals::languagelist = $$languagelistref; + + if ( $installer::globals::languagelist eq "" ) # not defined on command line and not in product list + { + installer::exiter::exit_program("ERROR: Languages not defined on command line (-l) and not in product list!", "get_info_about_languages"); + } + + # Adapting the separator format from zip list. + # | means new product, , (comma) means more than one language in one product + # On the command line, | is difficult to use. Therefore this script uses hashes + + $installer::globals::languagelist =~ s/\|/\#/g; + + analyze_languagelist(); +} + +############################################# +# All languages defined for one product +############################################# + +sub get_all_languages_for_one_product +{ + my ( $languagestring, $allvariables ) = @_; + + my @languagearray = (); + + my $last = $languagestring; + + $installer::globals::ismultilingual = 0; # setting the global variable $ismultilingual ! + if ( $languagestring =~ /\,/ ) { $installer::globals::ismultilingual = 1; } + + while ( $last =~ /^\s*(.+?)\,(.+)\s*$/) # "$" for minimal matching, comma separated list + { + my $first = $1; + $last = $2; + installer::remover::remove_leading_and_ending_whitespaces(\$first); + push(@languagearray, "$first"); + } + + installer::remover::remove_leading_and_ending_whitespaces(\$last); + push(@languagearray, "$last"); + + return \@languagearray; +} + +########################################################## +# Converting the language array into a string for output +########################################################## + +sub get_language_string +{ + my ($languagesref) = @_; + + my $newstring = ""; + + for ( my $i = 0; $i <= $#{$languagesref}; $i++ ) + { + $newstring = $newstring . ${$languagesref}[$i] . "_"; + } + + # remove ending underline + + $newstring =~ s/\_\s*$//; + + return \$newstring; +} + +########################################################## +# Analyzing the languages in the languages array and +# returning the most important language +########################################################## + +sub get_default_language +{ + my ($languagesref) = @_; + + return ${$languagesref}[0]; # ToDo, only returning the first language +} + +1; diff --git a/solenv/bin/modules/installer/logger.pm b/solenv/bin/modules/installer/logger.pm new file mode 100644 index 000000000..24fe5fc7b --- /dev/null +++ b/solenv/bin/modules/installer/logger.pm @@ -0,0 +1,256 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::logger; + +use strict; +use warnings; + +use base 'Exporter'; + +use installer::files; +use installer::globals; + +our @EXPORT_OK = qw( + include_header_into_logfile + include_timestamp_into_logfile + log_hashref + globallog + copy_globalinfo_into_logfile + starttime + stoptime + print_message + print_warning + print_error +); + +my $starttime; + +#################################################### +# Including header files into the logfile +#################################################### + +sub include_header_into_logfile +{ + my ($message) = @_; + + my $infoline; + + $infoline = "\n" . _get_time_string(); + push( @installer::globals::logfileinfo, $infoline); + + $infoline = "######################################################\n"; + push( @installer::globals::logfileinfo, $infoline); + + $infoline = "$message\n"; + push( @installer::globals::logfileinfo, $infoline); + + + $infoline = "######################################################\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +#################################################### +# Write timestamp into log file +#################################################### + +sub include_timestamp_into_logfile +{ + my ($message) = @_; + + my $infoline; + my $timestring = _get_time_string(); + $infoline = "$message\t$timestring"; + push( @installer::globals::logfileinfo, $infoline); +} + +#################################################### +# Writing all variables content into the log file +#################################################### + +sub log_hashref +{ + my ($hashref) = @_; + + my $infoline = "\nLogging variable settings:\n"; + push(@installer::globals::globallogfileinfo, $infoline); + + my $itemkey; + + foreach $itemkey ( keys %{$hashref} ) + { + my $line = ""; + my $itemvalue = ""; + if ( $hashref->{$itemkey} ) { $itemvalue = $hashref->{$itemkey}; } + $line = $itemkey . "=" . $itemvalue . "\n"; + push(@installer::globals::globallogfileinfo, $line); + } + + $infoline = "\n"; + push(@installer::globals::globallogfileinfo, $infoline); +} + +######################################################### +# Including global logging info into global log array +######################################################### + +sub globallog +{ + my ($message) = @_; + + my $infoline; + + $infoline = "\n" . _get_time_string(); + push( @installer::globals::globallogfileinfo, $infoline); + + $infoline = "######################################################\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + $infoline = "$message\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + $infoline = "######################################################\n"; + push( @installer::globals::globallogfileinfo, $infoline); + +} + +############################################################### +# For each product (new language) a new log file is created. +# Therefore the global logging has to be saved in this file. +############################################################### + +sub copy_globalinfo_into_logfile +{ + for ( my $i = 0; $i <= $#installer::globals::globallogfileinfo; $i++ ) + { + push(@installer::globals::logfileinfo, $installer::globals::globallogfileinfo[$i]); + } +} + +############################################################### +# Starting the time +############################################################### + +sub starttime +{ + $starttime = time(); +} + +############################################################### +# Convert time string +############################################################### + +sub _convert_timestring +{ + my ($secondstring) = @_; + + my $timestring = ""; + + if ( $secondstring < 60 ) # less than a minute + { + if ( $secondstring < 10 ) { $secondstring = "0" . $secondstring; } + $timestring = "00\:$secondstring min\."; + } + elsif ( $secondstring < 3600 ) + { + my $minutes = $secondstring / 60; + my $seconds = $secondstring % 60; + if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; } + if ( $minutes < 10 ) { $minutes = "0" . $minutes; } + if ( $seconds < 10 ) { $seconds = "0" . $seconds; } + $timestring = "$minutes\:$seconds min\."; + } + else # more than one hour + { + my $hours = $secondstring / 3600; + my $secondstring = $secondstring % 3600; + my $minutes = $secondstring / 60; + my $seconds = $secondstring % 60; + if ( $hours =~ /(\d*)\.\d*/ ) { $hours = $1; } + if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; } + if ( $hours < 10 ) { $hours = "0" . $hours; } + if ( $minutes < 10 ) { $minutes = "0" . $minutes; } + if ( $seconds < 10 ) { $seconds = "0" . $seconds; } + $timestring = "$hours\:$minutes\:$seconds hours"; + } + + return $timestring; +} + +############################################################### +# Returning time string for logging +############################################################### + +sub _get_time_string +{ + my $currenttime = time(); + $currenttime = $currenttime - $starttime; + $currenttime = _convert_timestring($currenttime); + $currenttime = localtime() . " \(" . $currenttime . "\)\n"; + return $currenttime; +} + +############################################################### +# Stopping the time +############################################################### + +sub stoptime +{ + print_message( _get_time_string() ); +} + +############################################################### +# Console output: messages +############################################################### + +sub print_message +{ + my $message = shift; + chomp $message; + my $force = shift || 0; + print "$message\n" if ( $force || ! $installer::globals::quiet ); + return; +} + +############################################################### +# Console output: warnings +############################################################### + +sub print_warning +{ + my $message = shift; + chomp $message; + print STDERR "WARNING: $message"; + return; +} + +############################################################### +# Console output: errors +############################################################### + +sub print_error +{ + my $message = shift; + chomp $message; + print STDERR "\n**************************************************\n"; + print STDERR "ERROR: $message"; + print STDERR "\n**************************************************\n"; + return; +} + +1; diff --git a/solenv/bin/modules/installer/packagelist.pm b/solenv/bin/modules/installer/packagelist.pm new file mode 100644 index 000000000..a0e1da760 --- /dev/null +++ b/solenv/bin/modules/installer/packagelist.pm @@ -0,0 +1,840 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::packagelist; + +use installer::converter; +use installer::exiter; +use installer::globals; +use installer::remover; +use installer::scriptitems; + +######################################## +# Check existence of module +######################################## + +sub check_module_existence +{ + my ($onegid, $moduleslist) = @_; + + my $foundgid = 0; + + for ( my $i = 0; $i <= $#{$moduleslist}; $i++ ) + { + my $gid = ${$moduleslist}[$i]->{'gid'}; + + if ( $gid eq $onegid ) + { + $foundgid = 1; + last; + } + } + + return $foundgid; +} + +################################################### +# Analyzing the gids, defined in the packagelist +################################################### + +sub analyze_list +{ + my ($packagelist, $moduleslist) = @_; + + @allpackages = (); + + my $moduleshash = get_module_hash($moduleslist); + + for ( my $i = 0; $i <= $#{$packagelist}; $i++ ) + { + my $onepackage = ${$packagelist}[$i]; + + my $onegid = $onepackage->{'module'}; + + installer::remover::remove_leading_and_ending_whitespaces(\$onegid); + + my $moduleexists = check_module_existence($onegid, $moduleslist); + + if ( ! $moduleexists ) { next; } + + my @allmodules = (); + + push(@allmodules, $onegid); + + get_children_with_hash($moduleshash, $onegid, \@allmodules); + + $onepackage->{'allmodules'} = \@allmodules; + + push(@allpackages, $onepackage); + } + + return \@allpackages; +} + +################################################### +# Creating a hash, that contains the module gids +# as keys and the parentids as values +################################################### + +sub get_module_hash +{ + my ($moduleslist) = @_; + + my %modulehash = (); + + for ( my $i = 0; $i <= $#{$moduleslist}; $i++ ) + { + my $gid = ${$moduleslist}[$i]->{'gid'}; + # Containing only modules with parent. Root modules can be ignored. + if ( ${$moduleslist}[$i]->{'ParentID'} ) { $modulehash{$gid} = ${$moduleslist}[$i]->{'ParentID'}; } + } + + return \%modulehash; +} + +######################################################## +# Recursively defined procedure to order +# modules and directories +######################################################## + +sub get_children_with_hash +{ + my ($modulehash, $parentgid, $newitemorder) = @_; + + foreach my $gid ( keys %{$modulehash} ) + { + my $parent = $modulehash->{$gid}; + + if ( $parent eq $parentgid ) + { + push(@{$newitemorder}, $gid); + my $parent = $gid; + get_children_with_hash($modulehash, $parent, $newitemorder); # recursive! + } + } +} + +######################################################## +# Recursively defined procedure to order +# modules and directories +######################################################## + +sub get_children +{ + my ($allitems, $startparent, $newitemorder) = @_; + + for ( my $i = 0; $i <= $#{$allitems}; $i++ ) + { + my $gid = ${$allitems}[$i]->{'gid'}; + my $parent = ""; + if ( ${$allitems}[$i]->{'ParentID'} ) { $parent = ${$allitems}[$i]->{'ParentID'}; } + + if ( $parent eq $startparent ) + { + push(@{$newitemorder}, $gid); + my $parent = $gid; + get_children($allitems, $parent, $newitemorder); # recursive! + } + } +} + +##################################################################### +# All modules below a defined gid_Module_A are collected now for +# each modules defined in the packagelist. Now the modules have +# to be removed, that are part of more than one package. +##################################################################### + +sub remove_multiple_modules_packages +{ + my ($allpackagemodules) = @_; + + # iterating over all packages + + for ( my $i = 0; $i <= $#{$allpackagemodules}; $i++ ) + { + my $onepackage = ${$allpackagemodules}[$i]; + my $allmodules = $onepackage->{'allmodules'}; + + # Comparing each package, with all following packages. If a + # gid for the module is part of more than one package, it is + # removed if the number of modules in the package is greater + # in the current package than in the compare package. + + # Taking all modules from package $i + + my $packagecount = $#{$allmodules}; + + my @optimizedpackage = (); + + # iterating over all modules of this package + + for ( my $j = 0; $j <= $#{$allmodules}; $j++ ) + { + my $onemodule = ${$allmodules}[$j]; # this is the module, that shall be removed or not + + my $put_module_into_new_package = 1; + + # iterating over all other packages + + for ( my $k = 0; $k <= $#{$allpackagemodules}; $k++ ) + { + if ( $k == $i ) { next; } # not comparing equal module + + if (! $put_module_into_new_package) { next; } # do not compare, if already found + + my $comparepackage = ${$allpackagemodules}[$k]; + my $allcomparemodules = $comparepackage->{'allmodules'}; + + my $comparepackagecount = $#{$allcomparemodules}; + + # modules will only be removed from packages, that have more modules + # than the compare package + + if ( $packagecount < $comparepackagecount ) { next; } # nothing to do, take next package + + # iterating over all modules of this package + + for ( my $m = 0; $m <= $#{$allcomparemodules}; $m++ ) + { + my $onecomparemodule = ${$allcomparemodules}[$m]; + + if ( $onemodule eq $onecomparemodule ) # this $onemodule has to be removed + { + $put_module_into_new_package = 0; + } + } + } + + if ( $put_module_into_new_package ) + { + push(@optimizedpackage, $onemodule) + } + } + + $onepackage->{'allmodules'} = \@optimizedpackage; + } +} + +##################################################################### +# Analyzing all files if they belong to a special package. +# A package is described by a list of modules. +##################################################################### + +sub find_files_for_package +{ + my ($filelist, $onepackage) = @_; + + my @newfilelist; + my $lastmodules = ''; + my $lastincludefile = 0; + + my %packagemodules = map {$_ => 1} @{$onepackage->{'allmodules'}}; + + for my $onefile (@$filelist) + { + my $modulesstring = $onefile->{'modules'}; # comma separated modules list + + # check if the modules string is the same as in the last file + if ($modulesstring eq $lastmodules) + { + push(@newfilelist, $onefile) if $lastincludefile; + next; + } + + my $moduleslist = installer::converter::convert_stringlist_into_array(\$modulesstring, ","); + + my $includefile = 0; + + # iterating over all modules of this file + for my $filemodule (@$moduleslist) { + installer::remover::remove_leading_and_ending_whitespaces(\$filemodule); + if ($packagemodules{$filemodule}) { + $includefile = 1; + last; + } + } + + push(@newfilelist, $onefile) if $includefile; + + # cache last result for this modules list + $lastmodules = $modulesstring; + $lastincludefile = $includefile; + + } + return \@newfilelist; +} + +##################################################################### +# Analyzing all links if they belong to a special package. +# A package is described by a list of modules. +# A link is inserted into the package, if the corresponding +# file is also inserted. +##################################################################### + +sub find_links_for_package +{ + my ($linklist, $filelist) = @_; + + # First looking for all links with a FileID. + # Then looking for all links with a ShortcutID. + + my @newlinklist = (); + + for ( my $i = 0; $i <= $#{$linklist}; $i++ ) + { + my $includelink = 0; + + my $onelink = ${$linklist}[$i]; + + my $fileid = ""; + if ( $onelink->{'FileID'} ) { $fileid = $onelink->{'FileID'}; } + + if ( $fileid eq "" ) { next; } # A link with a ShortcutID + + for ( my $j = 0; $j <= $#{$filelist}; $j++ ) # iterating over file list + { + my $onefile = ${$filelist}[$j]; + my $gid = $onefile->{'gid'}; + + if ( $gid eq $fileid ) + { + $includelink = 1; + last; + } + } + + if ( $includelink ) + { + push(@newlinklist, $onelink); + } + } + + # iterating over the new list, because of all links with a ShortcutID + + for ( my $i = 0; $i <= $#{$linklist}; $i++ ) + { + my $includelink = 0; + + my $onelink = ${$linklist}[$i]; + + my $shortcutid = ""; + if ( $onelink->{'ShortcutID'} ) { $shortcutid = $onelink->{'ShortcutID'}; } + + if ( $shortcutid eq "" ) { next; } # A link with a ShortcutID + + for ( my $j = 0; $j <= $#newlinklist; $j++ ) # iterating over newly created link list + { + my $onefilelink = $newlinklist[$j]; + my $gid = $onefilelink->{'gid'}; + + if ( $gid eq $shortcutid ) + { + $includelink = 1; + last; + } + } + + if ( $includelink ) + { + push(@newlinklist, $onelink); + } + } + + return \@newlinklist; +} + +##################################################################### +# Analyzing all directories if they belong to a special package. +# A package is described by a list of modules. +# Directories are included into the package, if they are needed +# by a file or a link included into the package. +# Attention: A directory with the flag CREATE, is only included +# into the root module: +# ($packagename eq $installer::globals::rootmodulegid) +##################################################################### + +sub find_dirs_for_package +{ + my ($dirlist, $onepackage) = @_; + + my @newdirlist = (); + + for ( my $i = 0; $i <= $#{$dirlist}; $i++ ) + { + my $onedir = ${$dirlist}[$i]; + my $modulesstring = $onedir->{'modules'}; # comma separated modules list + my $moduleslist = installer::converter::convert_stringlist_into_array(\$modulesstring, ","); + + my $includedir = 0; + + # iterating over all modules of this dir + + for ( my $j = 0; $j <= $#{$moduleslist}; $j++ ) + { + if ( $includedir ) { last; } + my $dirmodule = ${$moduleslist}[$j]; + installer::remover::remove_leading_and_ending_whitespaces(\$dirmodule); + + # iterating over all modules of the package + + my $packagemodules = $onepackage->{'allmodules'}; + + for ( my $k = 0; $k <= $#{$packagemodules}; $k++ ) + { + my $packagemodule = ${$packagemodules}[$k]; + + if ( $dirmodule eq $packagemodule ) + { + $includedir = 1; + last; + } + } + } + + if ( $includedir ) + { + push(@newdirlist, $onedir); + } + } + + return \@newdirlist; +} + +##################################################################### +# Resolving all variables in the packagename. +##################################################################### + +sub resolve_packagevariables +{ + my ($packagenameref, $variableshashref, $make_lowercase) = @_; + + my $key; + + # Special handling for dictionaries + if ( $$packagenameref =~ /-dict-/ ) + { + if (exists($variableshashref->{'DICTIONARYUNIXPRODUCTNAME'}) ) { $$packagenameref =~ s/\%UNIXPRODUCTNAME/$variableshashref->{'DICTIONARYUNIXPRODUCTNAME'}/g; } + if (exists($variableshashref->{'DICTIONARYBRANDPACKAGEVERSION'}) ) { $$packagenameref =~ s/\%BRANDPACKAGEVERSION/$variableshashref->{'DICTIONARYBRANDPACKAGEVERSION'}/g; } + } + + foreach $key (keys %{$variableshashref}) + { + my $value = $variableshashref->{$key}; + if ( $make_lowercase ) { $value = lc($value); } + $$packagenameref =~ s/\%$key/$value/g; + } +} + +##################################################################### +# Resolving all variables in the packagename. +##################################################################### + +sub resolve_packagevariables2 +{ + my ($packagenameref, $variableshashref, $make_lowercase, $isdict ) = @_; + + my $key; + + # Special handling for dictionaries + if ( $isdict ) + { + if (exists($variableshashref->{'DICTIONARYUNIXPRODUCTNAME'}) ) { $$packagenameref =~ s/\%UNIXPRODUCTNAME/$variableshashref->{'DICTIONARYUNIXPRODUCTNAME'}/g; } + if (exists($variableshashref->{'DICTIONARYBRANDPACKAGEVERSION'}) ) { $$packagenameref =~ s/\%BRANDPACKAGEVERSION/$variableshashref->{'DICTIONARYBRANDPACKAGEVERSION'}/g; } + } + + foreach $key (keys %{$variableshashref}) + { + my $value = $variableshashref->{$key}; + if ( $make_lowercase ) { $value = lc($value); } + $$packagenameref =~ s/\%$key/$value/g; + } +} + +##################################################################### +# New packages system. +##################################################################### + +################################################################## +# Controlling the content of the packagelist +# 1. Items in @installer::globals::packagelistitems must exist +# 2. If a shellscript file is defined, it must exist +################################################################## + +sub check_packagelist +{ + my ($packages) = @_; + + if ( ! ( $#{$packages} > -1 )) { installer::exiter::exit_program("ERROR: No packages defined!", "check_packagelist"); } + + for ( my $i = 0; $i <= $#{$packages}; $i++ ) + { + my $onepackage = ${$packages}[$i]; + + my $element; + + # checking all items that must be defined + + foreach $element (@installer::globals::packagelistitems) + { + if ( ! exists($onepackage->{$element}) ) + { + installer::exiter::exit_program("ERROR in package list: No value for $element !", "check_packagelist"); + } + } + + # checking the existence of the script file, if defined + + if ( $onepackage->{'script'} ) + { + my $scriptfile = $onepackage->{'script'}; + my $gid = $onepackage->{'module'}; + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$scriptfile, "" , 0); + + if ( $$fileref eq "" ) { installer::exiter::exit_program("ERROR: Could not find script file $scriptfile for module $gid!", "check_packagelist"); } + + my $infoline = "$gid: Using script file: \"$$fileref\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + + $onepackage->{'script'} = $$fileref; + } + } +} + +##################################################################### +# Reading pack info for one module from packinfo file. +##################################################################### + +sub get_packinfo +{ + my ($gid, $filename, $packages, $onelanguage, $islanguagemodule) = @_; + + my $packagelist = installer::files::read_file($filename); + + my @allpackages = (); + + for ( my $i = 0; $i <= $#{$packagelist}; $i++ ) + { + my $line = ${$packagelist}[$i]; + + if ( $line =~ /^\s*\#/ ) { next; } # this is a comment line + + if ( $line =~ /^\s*Start\s*$/i ) # a new package definition + { + my %onepackage = (); + + my $counter = $i + 1; + + while (!( ${$packagelist}[$counter] =~ /^\s*End\s*$/i )) + { + if ( ${$packagelist}[$counter] =~ /^\s*(\S+)\s*\=\s*\"(.*)\"/ ) + { + my $key = $1; + my $value = $2; + $onepackage{$key} = $value; + } + + $counter++; + } + + $onepackage{'islanguagemodule'} = $islanguagemodule; + if ( $islanguagemodule ) + { + $saveonelanguage = $onelanguage; + $saveonelanguage =~ s/_/-/g; + $onepackage{'language'} = $saveonelanguage; + } + + push(@allpackages, \%onepackage); + } + } + + # looking for the packinfo with the correct gid + + my $foundgid = 0; + my $onepackage; + foreach $onepackage (@allpackages) + { + # Adding the language to the module gid for LanguagePacks ! + # Making the module gid language specific: gid_Module_Root -> gir_Module_Root_pt_BR (as defined in scp2) + if ( $onelanguage ne "" ) { $onepackage->{'module'} = $onepackage->{'module'} . "_$onelanguage"; } + + if ( $onepackage->{'module'} eq $gid ) + { + # Resolving the language identifier + my $onekey; + foreach $onekey ( keys %{$onepackage} ) + { + # Some keys require "-" instead of "_" for example in "en-US". All package names do not use underlines. + my $locallang = $onelanguage; + if (( $onekey eq "solarispackagename" ) || + ( $onekey eq "solarisrequires" ) || + ( $onekey eq "packagename" ) || + ( $onekey eq "requires" )) { $locallang =~ s/_/-/g; } # avoiding illegal package abbreviation + $onepackage->{$onekey} =~ s/\%LANGUAGESTRING/$locallang/g; + } + + # Saving the language for the package + my $lang = $onelanguage; + $lang =~ s/_/-/g; + $onepackage->{'specificlanguage'} = $lang; + + push(@{$packages}, $onepackage); + $foundgid = 1; + last; + } + } + + if ( ! $foundgid ) + { + installer::exiter::exit_program("ERROR: Could not find package info for module $gid in file \"$filename\"!", "get_packinfo"); + } +} + +##################################################################### +# Collecting all packages from scp project. +##################################################################### + +sub collectpackages +{ + my ( $allmodules, $languagesarrayref ) = @_; + + installer::logger::include_header_into_logfile("Collecting packages:"); + + my @packages = (); + my %gid_analyzed = (); + + my $onemodule; + foreach $onemodule ( @{$allmodules} ) + { + if ( $onemodule->{'PackageInfo'} ) # this is a package module! + { + my $modulegid = $onemodule->{'gid'}; + + my $styles = ""; + if ( $onemodule->{'Styles'} ) { $styles = $onemodule->{'Styles'}; } + + # checking modules with style LANGUAGEMODULE + my $islanguagemodule = 0; + my $onelanguage = ""; + if ( $styles =~ /\bLANGUAGEMODULE\b/ ) + { + $islanguagemodule = 1; + $onelanguage = $onemodule->{'Language'}; # already checked, that it is set. + $onelanguage =~ s/-/_/g; # pt-BR -> pt_BR in scp + } + + # Modules in different languages are listed more than once in multilingual installation sets + if ( exists($gid_analyzed{$modulegid}) ) { next; } + $gid_analyzed{$modulegid} = 1; + + my $packinfofile = $onemodule->{'PackageInfo'}; + + # The file with package information has to be found in path list + my $fileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$packinfofile, "" , 0); + + if ( $$fileref eq "" ) { installer::exiter::exit_program("ERROR: Could not find file $packinfofile for module $modulegid!", "collectpackages"); } + + my $infoline = "$modulegid: Using packinfo: \"$$fileref\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + + get_packinfo($modulegid, $$fileref, \@packages, $onelanguage, $islanguagemodule); + } + } + + return \@packages; +} + +##################################################################### +# Printing packages content for debugging purposes +##################################################################### + +sub log_packages_content +{ + my ($packages) = @_; + + if ( ! ( $#{$packages} > -1 )) { installer::exiter::exit_program("ERROR: No packages defined!", "print_content"); } + + installer::logger::include_header_into_logfile("Logging packages content:"); + + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$packages}; $i++ ) + { + my $onepackage = ${$packages}[$i]; + + # checking all items that must be defined + + $infoline = "Package $onepackage->{'module'}\n"; + push(@installer::globals::logfileinfo, $infoline); + + my $key; + foreach $key (sort keys %{$onepackage}) + { + if ( $key =~ /^\s*\;/ ) { next; } + + if ( $key eq "allmodules" ) + { + $infoline = "\t$key:\n"; + push(@installer::globals::logfileinfo, $infoline); + my $onemodule; + foreach $onemodule ( @{$onepackage->{$key}} ) + { + $infoline = "\t\t$onemodule\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + else + { + $infoline = "\t$key: $onepackage->{$key}\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + + $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + + } +} + +##################################################################### +# Creating assignments from modules to destination paths. +# This is required for logging in fileinfo file. Otherwise +# the complete destination file would not be known in file list. +# Saved in %installer::globals::moduledestination +##################################################################### + +sub create_module_destination_hash +{ + my ($packages, $allvariables) = @_; + + for ( my $i = 0; $i <= $#{$packages}; $i++ ) + { + my $onepackage = ${$packages}[$i]; + + my $defaultdestination = $onepackage->{'destpath'}; + resolve_packagevariables(\$defaultdestination, $allvariables, 1); + if ( $^O =~ /darwin/i ) { $defaultdestination =~ s/\/opt\//\/Applications\//; } + + foreach my $onemodule ( @{$onepackage->{'allmodules'}} ) + { + $installer::globals::moduledestination{$onemodule} = $defaultdestination; + } + } +} + +##################################################################### +# Adding the default paths into the files collector for Unixes. +# This is necessary to know the complete destination path in +# fileinfo log file. +##################################################################### + +sub add_defaultpaths_into_filescollector +{ + my ($allfiles) = @_; + + for ( my $i = 0; $i <= $#{$allfiles}; $i++ ) + { + my $onefile = ${$allfiles}[$i]; + + if ( ! $onefile->{'destination'} ) { installer::exiter::exit_program("ERROR: No destination found at file $onefile->{'gid'}!", "add_defaultpaths_into_filescollector"); } + my $destination = $onefile->{'destination'}; + + if ( ! $onefile->{'modules'} ) { installer::exiter::exit_program("ERROR: No modules found at file $onefile->{'gid'}!", "add_defaultpaths_into_filescollector"); } + my $module = $onefile->{'modules'}; + # If modules contains a list of modules, only taking the first one. + if ( $module =~ /^\s*(.*?)\,/ ) { $module = $1; } + + if ( ! exists($installer::globals::moduledestination{$module}) ) { installer::exiter::exit_program("ERROR: No default destination path found for module $module!", "add_defaultpaths_into_filescollector"); } + my $defaultpath = $installer::globals::moduledestination{$module}; + $defaultpath =~ s/\/\s*$//; # removing ending slashes + my $fulldestpath = $defaultpath . $installer::globals::separator . $destination; + + $onefile->{'fulldestpath'} = $fulldestpath; + } +} + +##################################################################### +# Creating list of cabinet files from packages +##################################################################### + +sub prepare_cabinet_files +{ + my ($packages, $allvariables) = @_; + + if ( ! ( $#{$packages} > -1 )) { installer::exiter::exit_program("ERROR: No packages defined!", "print_content"); } + + installer::logger::include_header_into_logfile("Preparing cabinet files:"); + + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$packages}; $i++ ) + { + my $onepackage = ${$packages}[$i]; + + my $cabinetfile = "$onepackage->{'packagename'}\.cab"; + + resolve_packagevariables(\$cabinetfile, $allvariables, 0); + + $installer::globals::allcabinets{$cabinetfile} = 1; + + # checking all items that must be defined + + $infoline = "Package $onepackage->{'module'}\n"; + push(@installer::globals::logfileinfo, $infoline); + + # Assigning the cab file to the module and also to all corresponding sub modules + + my $onemodule; + foreach $onemodule ( @{$onepackage->{'allmodules'}} ) + { + if ( ! exists($installer::globals::allcabinetassigns{$onemodule}) ) + { + $installer::globals::allcabinetassigns{$onemodule} = $cabinetfile; + } + else + { + my $infoline = "Warning: Already existing assignment: $onemodule : $installer::globals::allcabinetassigns{$onemodule}\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Ignoring further assignment: $onemodule : $cabinetfile\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } +} + +##################################################################### +# Logging assignments of cabinet files +##################################################################### + +sub log_cabinet_assignments +{ + installer::logger::include_header_into_logfile("Logging cabinet files:"); + + my $infoline = "List of cabinet files:\n"; + push(@installer::globals::logfileinfo, $infoline); + + my $key; + foreach $key ( sort keys %installer::globals::allcabinets ) { push(@installer::globals::logfileinfo, "\t$key\n"); } + + $infoline = "\nList of assignments from modules to cabinet files:\n"; + push(@installer::globals::logfileinfo, $infoline); + + foreach $key ( sort keys %installer::globals::allcabinetassigns ) { push(@installer::globals::logfileinfo, "\t$key : $installer::globals::allcabinetassigns{$key}\n"); } +} + +1; diff --git a/solenv/bin/modules/installer/parameter.pm b/solenv/bin/modules/installer/parameter.pm new file mode 100644 index 000000000..befed7202 --- /dev/null +++ b/solenv/bin/modules/installer/parameter.pm @@ -0,0 +1,552 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::parameter; + +use Cwd; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::remover; +use installer::systemactions; +use File::Temp qw/ :mktemp /; + +############################################ +# Parameter Operations +############################################ + +sub usage +{ + print <= 0 ) + { + my $param = shift(@ARGV); + + if ($param eq "-f") { $installer::globals::ziplistname = shift(@ARGV); } + elsif ($param eq "-s") { $installer::globals::setupscriptname = shift(@ARGV); } + elsif ($param eq "-p") { $installer::globals::product = shift(@ARGV); } + elsif ($param eq "-l") { $installer::globals::languagelist = shift(@ARGV); } + elsif ($param eq "-dontunzip") { $installer::globals::dounzip = 0; } + elsif ($param eq "-pro") { $installer::globals::pro = 1; } + elsif ($param eq "-format") { $installer::globals::packageformat = shift(@ARGV); } + elsif ($param eq "-quiet") { $installer::globals::quiet = 1; } + elsif ($param eq "-verbose") { $installer::globals::quiet = 0; } + elsif ($param eq "-u") { $installer::globals::unpackpath = shift(@ARGV); } + elsif ($param eq "-i") { $installer::globals::rootpath = shift(@ARGV); } + elsif ($param eq "-dontcallepm") { $installer::globals::call_epm = 0; } + elsif ($param eq "-msitemplate") { $installer::globals::idttemplatepath = shift(@ARGV); } + elsif ($param eq "-msilanguage") { $installer::globals::idtlanguagepath = shift(@ARGV); } + elsif ($param eq "-buildid") { $installer::globals::buildid = shift(@ARGV); } + elsif ($param eq "-copyproject") { $installer::globals::is_copy_only_project = 1; } + elsif ($param eq "-languagepack") { $installer::globals::languagepack = 1; } + elsif ($param eq "-helppack") { $installer::globals::helppack = 1;} + elsif ($param eq "-debian") { $installer::globals::debian = 1; } + elsif ($param eq "-strip") { $installer::globals::strip = 1; } + elsif ($param eq "-packer") { $installer::globals::packertool = shift(@ARGV); } + elsif ($param eq "-destdir") # new parameter for simple installer + { + $installer::globals::rootpath ne "" && die "must set destdir before -i or -simple"; + + my $path = shift(@ARGV); + mkdir $path; + $installer::globals::destdir = Cwd::realpath($path); + } + elsif ($param eq "-simple") # new parameter for simple installer + { + $installer::globals::simple = 1; + $installer::globals::call_epm = 0; + $installer::globals::makedownload = 0; + my $path = shift(@ARGV); + $path =~ s/^\Q$installer::globals::destdir\E//; + $installer::globals::rootpath = $path; + } + else + { + installer::logger::print_error( "unknown parameter: $param" ); + usage(); + exit(-1); + } + } + + # Usage of simple installer (not for Windows): + # $PERL -w $SRCDIR/solenv/bin/make_installer.pl \ + # -f openoffice.lst -l en-US -p OpenOffice \ + # -buildid $BUILD -rpm \ + # -destdir /tmp/nurk -simple $INSTALL_PATH +} + +############################################ +# Controlling the fundamental parameter +# (required for every process) +############################################ + +sub control_fundamental_parameter +{ + if ($installer::globals::product eq "") + { + installer::logger::print_error( "Product name not set!" ); + usage(); + exit(-1); + } +} + +########################################################## +# The path parameters can be relative or absolute. +# This function creates absolute paths. +########################################################## + +sub make_path_absolute +{ + my ($pathref) = @_; + + if ( $installer::globals::isunix ) + { + if (!($$pathref =~ /^\s*\//)) # this is a relative unix path + { + $$pathref = cwd() . $installer::globals::separator . $$pathref; + } + } + + if ( $installer::globals::iswin ) + { + if ( $^O =~ /cygwin/i ) + { + if ( $$pathref !~ /^\s*\// && $$pathref !~ /^\s*\w\:/ ) # not an absolute POSIX or DOS path + { + $$pathref = cwd() . $installer::globals::separator . $$pathref; + } + my $p = $$pathref; + chomp( $p ); + my $q = ''; + # Avoid the $(LANG) problem. + if ($p =~ /(\A.*)(\$\(.*\Z)/) { + $p = $1; + $q = $2; + } + $p =~ s/\\/\\\\/g; + chomp( $p = qx{cygpath -w "$p"} ); + $$pathref = $p.$q; + # Use windows paths, but with '/'s. + $$pathref =~ s/\\/\//g; + } + else + { + if (!($$pathref =~ /^\s*\w\:/)) # this is a relative windows path (no dos drive) + { + $$pathref = cwd() . $installer::globals::separator . $$pathref; + + $$pathref =~ s/\//\\/g; + } + } + } + $$pathref =~ s/[\/\\]\s*$//; # removing ending slashes +} + +# Setting some global parameters +# This has to be expanded with further platforms + +sub setglobalvariables +{ + # Setting the installertype directory corresponding to the environment variable PKGFORMAT + # The global variable $installer::globals::packageformat can only contain one package format. + # If PKGFORMAT contains more than one format (for example "rpm deb") this is split in the + # makefile calling the perl program. + $installer::globals::installertypedir = $installer::globals::packageformat; + + if ( $installer::globals::os eq 'WNT' ) + { + $installer::globals::iswindowsbuild = 1; + } + + if ( $installer::globals::os eq 'SOLARIS') + { + $installer::globals::issolarisbuild = 1; + if ( $installer::globals::packageformat eq "pkg" ) + { + $installer::globals::issolarispkgbuild = 1; + $installer::globals::epmoutpath = "packages"; + } + if ( $installer::globals::cpuname eq 'INTEL') + { + $installer::globals::issolarisx86build = 1; + } + else + { + $installer::globals::issolarissparcbuild = 1; + } + } + + if ( $installer::globals::os eq 'MACOSX' ) + { + $installer::globals::ismacbuild = 1; + + if ( $installer::globals::packageformat eq "dmg" ) + { + $installer::globals::ismacdmgbuild = 1; + } + } + + if ( $installer::globals::os eq 'OPENBSD') + { + $installer::globals::epmoutpath = "openbsd"; + } + + if ( $installer::globals::os eq 'FREEBSD') + { + $installer::globals::isfreebsdbuild = 1; + + if ( $installer::globals::packageformat eq "bsd" ) + { + $installer::globals::epmoutpath = "freebsd"; + $installer::globals::isfreebsdpkgbuild = 1; + } + } + + if ($installer::globals::os eq 'AIX') + { + if ( $installer::globals::packageformat eq "rpm" ) + { + $installer::globals::isrpmbuild = 1; + $installer::globals::epmoutpath = "RPMS"; + } + if ( $installer::globals::rpm eq "" ) { installer::exiter::exit_program("ERROR: Environment variable \"\$RPM\" has to be defined!", "setglobalvariables"); } + } + + if ($installer::globals::os eq 'LINUX') + { + $installer::globals::islinuxbuild = 1; + if ( $installer::globals::packageformat eq "rpm" ) + { + $installer::globals::isrpmbuild = 1; + $installer::globals::epmoutpath = "RPMS"; + + if ( $installer::globals::rpm eq "" ) { installer::exiter::exit_program("ERROR: Environment variable \"\$RPM\" has to be defined!", "setglobalvariables"); } + } + + # Creating Debian packages ? + if (( $installer::globals::packageformat eq "deb" ) || ( $installer::globals::debian )) + { + $installer::globals::debian = 1; + $installer::globals::packageformat = "deb"; + my $message = "Creating Debian packages"; + installer::logger::print_message( $message ); + push(@installer::globals::globallogfileinfo, $message); + $installer::globals::isrpmbuild = 0; + $installer::globals::isdebbuild = 1; + $installer::globals::epmoutpath = "DEBS"; + } + } + + # Defaulting to native package format for epm + + # no languages defined as parameter + if ($installer::globals::languagelist eq "") { $installer::globals::languages_defined_in_productlist = 1; } + + # setting and creating the unpackpath + + if ($installer::globals::unpackpath eq "") # unpackpath not set + { + $installer::globals::unpackpath = cwd(); + } + + if ($installer::globals::workpath eq "") # workpath not set + { + $installer::globals::workpath = cwd(); + } + + if ( $installer::globals::localunpackdir ne "" ) { $installer::globals::unpackpath = $installer::globals::localunpackdir; } + + if (!($installer::globals::unpackpath eq "")) + { + make_path_absolute(\$installer::globals::unpackpath); + } + + $installer::globals::unpackpath =~ s/\Q$installer::globals::separator\E\s*$//; + + if (! -d $installer::globals::unpackpath ) # create unpackpath + { + installer::systemactions::create_directory($installer::globals::unpackpath); + } + + # setting and creating the temppath + + if ( $ENV{'TMPDIR'} ) + { + $installer::globals::temppath = $ENV{'TMPDIR'}; + $installer::globals::temppath =~ s/\Q$installer::globals::separator\E\s*$//; # removing ending slashes and backslashes + $installer::globals::temppath .= $installer::globals::separator . 'ooopackagingXXXXXX'; + $installer::globals::temppath = mkdtemp($installer::globals::temppath); + + my $dirsave = $installer::globals::temppath; + + if ( $installer::globals::platformid eq 'maosx_x86_64') + { + chmod 0777, $installer::globals::temppath; + } + + $installer::globals::temppath = $installer::globals::temppath . $installer::globals::separator . "i"; + $installer::globals::temppath = installer::systemactions::create_pid_directory($installer::globals::temppath); + push(@installer::globals::removedirs, $installer::globals::temppath); + + if ( ! -d $installer::globals::temppath ) { installer::exiter::exit_program("ERROR: Failed to create directory $installer::globals::temppath ! Possible reason: Wrong privileges in directory $dirsave .", "setglobalvariables"); } + + $installer::globals::temppath = $installer::globals::temppath . $installer::globals::separator . $installer::globals::platformid; + installer::systemactions::create_directory($installer::globals::temppath); + if ( $^O =~ /cygwin/i ) + { + $installer::globals::cyg_temppath = $installer::globals::temppath; + $installer::globals::cyg_temppath =~ s/\\/\\\\/g; + chomp( $installer::globals::cyg_temppath = qx{cygpath -w "$installer::globals::cyg_temppath"} ); + } + $installer::globals::temppathdefined = 1; + } + else + { + $installer::globals::temppathdefined = 0; + } + + # only one cab file, if Windows msp patches shall be prepared + if ( $installer::globals::prepare_winpatch ) { $installer::globals::number_of_cabfiles = 1; } + +} + +############################################ +# Controlling the parameter that are +# required for special processes +############################################ + +sub control_required_parameter +{ + if (!($installer::globals::is_copy_only_project)) + { + ############################################################################################## + # idt template path. Only required for Windows build + # for the creation of the msi database. + ############################################################################################## + + if (($installer::globals::idttemplatepath eq "") && ($installer::globals::iswindowsbuild)) + { + installer::logger::print_error( "idt template path not set (-msitemplate)!" ); + usage(); + exit(-1); + } + + ############################################################################################## + # idt language path. Only required for Windows build + # for the creation of the msi database. + ############################################################################################## + + if (($installer::globals::idtlanguagepath eq "") && ($installer::globals::iswindowsbuild)) + { + installer::logger::print_error( "idt language path not set (-msilanguage)!" ); + usage(); + exit(-1); + } + + # Analyzing the idt template path + + if (!($installer::globals::idttemplatepath eq "")) # idttemplatepath set, relative or absolute? + { + make_path_absolute(\$installer::globals::idttemplatepath); + } + + installer::remover::remove_ending_pathseparator(\$installer::globals::idttemplatepath); + + # Analyzing the idt language path + + if (!($installer::globals::idtlanguagepath eq "")) # idtlanguagepath set, relative or absolute? + { + make_path_absolute(\$installer::globals::idtlanguagepath); + } + + installer::remover::remove_ending_pathseparator(\$installer::globals::idtlanguagepath); + + # In the msi template directory a files "codes.txt" has to exist, in which the ProductCode + # and the UpgradeCode for the product are defined. + # The name "codes.txt" can be overwritten in Product definition with CODEFILENAME (msiglobal.pm) + + if (( $installer::globals::iswindowsbuild ) && ( $installer::globals::packageformat ne "archive" ) && ( $installer::globals::packageformat ne "installed" )) + { + $installer::globals::codefilename = $installer::globals::idttemplatepath . $installer::globals::separator . $installer::globals::codefilename; + installer::files::check_file($installer::globals::codefilename); + $installer::globals::componentfilename = $installer::globals::idttemplatepath . $installer::globals::separator . $installer::globals::componentfilename; + installer::files::check_file($installer::globals::componentfilename); + } + + } + + ####################################### + # Testing existence of files + # also for copy-only projects + ####################################### + + if ($installer::globals::ziplistname eq "") + { + installer::logger::print_error( "ERROR: Zip list file has to be defined (Parameter -f) !" ); + usage(); + exit(-1); + } + else + { + installer::files::check_file($installer::globals::ziplistname); + } + + if ($installer::globals::setupscriptname eq "") { $installer::globals::setupscript_defined_in_productlist = 1; } + else { installer::files::check_file($installer::globals::setupscriptname); } # if the setupscript file is defined, it has to exist + +} + +################################################ +# Writing parameter to shell and into logfile +################################################ + +sub outputparameter +{ + my $element; + + my @output = (); + + push(@output, "\n########################################################\n"); + push(@output, "Product list file: $installer::globals::ziplistname\n"); + if (!($installer::globals::setupscript_defined_in_productlist)) + { + push(@output, "Setup script: $installer::globals::setupscriptname\n"); + } + else + { + push(@output, "Taking setup script from workdir\n"); + } + push(@output, "Unpackpath: $installer::globals::unpackpath\n"); + push(@output, "PLATFORMID: $installer::globals::platformid\n"); + push(@output, "OS: $installer::globals::os\n"); + push(@output, "CPUNAME: $installer::globals::cpuname\n"); + push(@output, "COM: $installer::globals::com\n"); + push(@output, "Product: $installer::globals::product\n"); + push(@output, "BuildID: $installer::globals::buildid\n"); + push(@output, "Build: $installer::globals::build\n"); + if ( $installer::globals::pro ) { push(@output, "Product version\n"); } + else { push(@output, "Non-Product version\n"); } + if ( $installer::globals::rootpath eq "" ) { push(@output, "Using default installpath\n"); } + else { push(@output, "Installpath: $installer::globals::rootpath\n"); } + push(@output, "Package format: $installer::globals::packageformat\n"); + if (!($installer::globals::idttemplatepath eq "")) { push(@output, "msi templatepath: $installer::globals::idttemplatepath\n"); } + if ((!($installer::globals::idttemplatepath eq "")) && (!($installer::globals::iswindowsbuild))) { push(@output, "msi template path will be ignored for non Windows builds!\n"); } + if (!($installer::globals::idtlanguagepath eq "")) { push(@output, "msi languagepath: $installer::globals::idtlanguagepath\n"); } + if ((!($installer::globals::idtlanguagepath eq "")) && (!($installer::globals::iswindowsbuild))) { push(@output, "msi language path will be ignored for non Windows builds!\n"); } + if ((!($installer::globals::iswindowsbuild)) && ( $installer::globals::call_epm )) { push(@output, "Calling epm\n"); } + if ((!($installer::globals::iswindowsbuild)) && (!($installer::globals::call_epm))) { push(@output, "Not calling epm\n"); } + if ( $installer::globals::strip ) { push(@output, "Stripping files\n"); } + else { push(@output, "No file stripping\n"); } + if ( $installer::globals::debian ) { push(@output, "Linux: Creating Debian packages\n"); } + if ( $installer::globals::dounzip ) { push(@output, "Unzip ARCHIVE files\n"); } + else { push(@output, "Not unzipping ARCHIVE files\n"); } + if (!($installer::globals::languages_defined_in_productlist)) + { + push(@output, "Languages:\n"); + foreach $element (@installer::globals::languageproducts) { push(@output, "\t$element\n"); } + } + else + { + push(@output, "Languages defined in $installer::globals::ziplistname\n"); + } + if ( $installer::globals::is_copy_only_project ) { push(@output, "This is a copy only project!\n"); } + if ( $installer::globals::languagepack ) { push(@output, "Creating language pack!\n"); } + if ( $installer::globals::helppack ) { push(@output, "Creating help pack!\n"); } + push(@output, "########################################################\n"); + + # output into shell and into logfile + + for ( my $i = 0; $i <= $#output; $i++ ) + { + installer::logger::print_message( $output[$i] ); + push(@installer::globals::globallogfileinfo, $output[$i]); + } +} + +1; diff --git a/solenv/bin/modules/installer/pathanalyzer.pm b/solenv/bin/modules/installer/pathanalyzer.pm new file mode 100644 index 000000000..312042acb --- /dev/null +++ b/solenv/bin/modules/installer/pathanalyzer.pm @@ -0,0 +1,66 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::pathanalyzer; + +use installer::globals; + +########################################### +# Path analyzer +########################################### + +sub get_path_from_fullqualifiedname +{ + my ($longfilenameref) = @_; + + if ( $$longfilenameref =~ /\Q$installer::globals::separator\E/ ) # Is there a separator in the path? Otherwise the path is empty. + { + if ( $$longfilenameref =~ /^\s*(.*\Q$installer::globals::separator\E)(.+)/ ) + { + $$longfilenameref = $1; + } + } + else + { + $$longfilenameref = ""; # there is no path + } +} + +sub make_absolute_filename_to_relative_filename +{ + my ($longfilenameref) = @_; + + if ( $installer::globals::isunix ) + { + if ( $$longfilenameref =~ /^.*\/(?=\S)([^\/]+)(?<=\S)/ ) + { + $$longfilenameref = $1; + } + } + + if ( $installer::globals::iswin ) + { + # Either '/' or '\'. It would be possible to use $installer::globals::separator. + if ( $$longfilenameref =~ /^.*[\/\\](?=\S)([^\/\\]+)(?<=\S)/ ) + { + $$longfilenameref = $1; + } + } +} + +1; diff --git a/solenv/bin/modules/installer/profiles.pm b/solenv/bin/modules/installer/profiles.pm new file mode 100644 index 000000000..a1a7b4507 --- /dev/null +++ b/solenv/bin/modules/installer/profiles.pm @@ -0,0 +1,223 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::profiles; + +use installer::converter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::remover; +use installer::systemactions; + +############################# +# Profiles +############################# + +####################################################### +# Sorting the content of a profile +####################################################### + +sub sorting_profile { + my ($profilesref) = @_; + + my @sections; + my %section_content; + + for ( my $i = 0; $i < @{$profilesref}; $i++ ) { + my $line = ${$profilesref}[$i]; + + # Skip unless this is a section (every second line) + next unless ( $line =~ /^\s*(\[.*\])\s*$/ ); + + my $section = $1; + my $next_line = ${$profilesref}[$i+1]; + + if ( ! exists $section_content{$section} ) { + push @sections, $section; + } + + push @{ $section_content{$section} }, $next_line; + } + + my @profile; + + for my $section (@sections) { + push @profile, "$section\n"; + push @profile, @{ $section_content{$section} }; + } + + return \@profile; +} + +##################################################################### +# Adding the newly created profile into the file list +##################################################################### + +sub add_profile_into_filelist +{ + my ($filesarrayref, $oneprofile, $completeprofilename, $allvariables) = @_; + + my %profile = (); + + # Taking the base data from a file in the root module + + if ( ! $allvariables->{'ROOTMODULEGID'} ) { installer::exiter::exit_program("ERROR: ROOTMODULEGID must be defined in list file!", "add_profile_into_filelist"); } + my $rootmodulegid = $allvariables->{'ROOTMODULEGID'}; + my $rootfile; + foreach my $file (@{$filesarrayref}) { + if ($file->{'modules'} eq $rootmodulegid) + { + $rootfile = $file; + last; + } + } + if (! defined $rootfile) { + die "Could not find any file from module $rootmodulegid in list of files!"; + } + + # copying all base data + installer::converter::copy_item_object($rootfile, \%profile); + + # and overriding all new values + + $profile{'ismultilingual'} = 0; + $profile{'sourcepath'} = $completeprofilename; + $profile{'Name'} = $oneprofile->{'Name'}; + $profile{'UnixRights'} = "644"; + $profile{'gid'} = $oneprofile->{'gid'}; + $profile{'Dir'} = $oneprofile->{'Dir'}; + $profile{'destination'} = $oneprofile->{'destination'}; + $profile{'Styles'} = ""; + if ( $oneprofile->{'Styles'} ) { $profile{'Styles'} = $oneprofile->{'Styles'}; } + $profile{'modules'} = $oneprofile->{'ModuleID'}; # Profiles can only be added completely to a module + + push(@{$filesarrayref}, \%profile); +} + +################################################### +# Including Windows line ends in ini files +# Profiles on Windows shall have \r\n line ends +################################################### + +sub include_windows_lineends +{ + my ($onefile) = @_; + + for ( my $i = 0; $i <= $#{$onefile}; $i++ ) + { + ${$onefile}[$i] =~ s/\r?\n$/\r\n/; + } +} + +#################################### +# Create profiles +#################################### + +sub create_profiles +{ + my ($profilesref, $profileitemsref, $filesarrayref, $languagestringref, $allvariables) = @_; + + my $infoline; + + my $profilesdir = installer::systemactions::create_directories("profiles", $languagestringref); + + installer::logger::include_header_into_logfile("Creating profiles:"); + + # Attention: The module dependencies from ProfileItems have to be ignored, because + # the Profile has to be installed completely with all of its content and the correct name. + # Only complete profiles can belong to a specified module, but not ProfileItems! + + # iterating over all files + + for ( my $i = 0; $i <= $#{$profilesref}; $i++ ) + { + my $oneprofile = ${$profilesref}[$i]; + my $dir = $oneprofile->{'Dir'}; + if ( $dir eq "PREDEFINED_CONFIGDIR" ) { next; } # ignoring the profile sversion file + + my $profilegid = $oneprofile->{'gid'}; + my $profilename = $oneprofile->{'Name'}; + + my $localprofilesdir = $profilesdir . $installer::globals::separator . $profilegid; # uniqueness guaranteed by gid + if ( ! -d $localprofilesdir ) { installer::systemactions::create_directory($localprofilesdir); } + + my @onefile = (); + my $profileempty = 1; + + for ( my $j = 0; $j <= $#{$profileitemsref}; $j++ ) + { + my $oneprofileitem = ${$profileitemsref}[$j]; + + my $styles = ""; + if ( $oneprofileitem->{'Styles'} ) { $styles = $oneprofileitem->{'Styles'}; } + if ( $styles =~ /\bINIFILETABLE\b/ ) { next; } # these values are written during installation, not during packing + + my $profileid = $oneprofileitem->{'ProfileID'}; + + if ( $profileid eq $profilegid ) + { + my $section = $oneprofileitem->{'Section'}; + my $key = $oneprofileitem->{'Key'}; + my $value = $oneprofileitem->{'Value'}; + for (my $pk = 1; $pk <= 50; $pk++) + { + my $key = "ValueList" . $pk; + if ( $oneprofileitem->{$key} ) + { $value = $value . " " . $oneprofileitem->{$key} } + } + my $order = $oneprofileitem->{'Order'}; # ignoring order at the moment + + my $line = "[" . $section . "]" . "\n"; + push(@onefile, $line); + $line = $key . "=" . $value . "\n"; + push(@onefile, $line); + + $profileempty = 0; + } + } + + if ( $profileempty ) { next; } # ignoring empty profiles + + # Sorting the array @onefile + my $onefileref = sorting_profile(\@onefile); + + if ( $installer::globals::iswin && $^O =~ /cygwin/i) # Windows line ends only for Cygwin + { + include_windows_lineends($onefileref); + } + + # Saving the profile as a file + $completeprofilename = $localprofilesdir . $installer::globals::separator . $profilename; + + installer::files::save_file($completeprofilename, $onefileref); + + # Adding the file to the filearray + # Some data are set now, others are taken from the file "soffice.exe" ("soffice.bin") + add_profile_into_filelist($filesarrayref, $oneprofile, $completeprofilename, $allvariables); + + $infoline = "Created Profile: $completeprofilename\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); +} + + +1; diff --git a/solenv/bin/modules/installer/remover.pm b/solenv/bin/modules/installer/remover.pm new file mode 100644 index 000000000..426056eef --- /dev/null +++ b/solenv/bin/modules/installer/remover.pm @@ -0,0 +1,50 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::remover; + +use installer::globals; + +############################################ +# Remover +############################################ + +sub remove_leading_and_ending_whitespaces +{ + my ( $stringref ) = @_; + + $$stringref =~ s/^\s+//; + $$stringref =~ s/\s+$//; +} + +sub remove_leading_and_ending_quotationmarks +{ + my ( $stringref ) = @_; + + $$stringref =~ s/^\s*\"//; + $$stringref =~ s/\"\s*$//; +} + +sub remove_ending_pathseparator +{ + my ( $stringref ) = @_; + + $$stringref =~ s/\Q$installer::globals::separator\E\s*$//; +} + +1; diff --git a/solenv/bin/modules/installer/scpzipfiles.pm b/solenv/bin/modules/installer/scpzipfiles.pm new file mode 100644 index 000000000..e6c773196 --- /dev/null +++ b/solenv/bin/modules/installer/scpzipfiles.pm @@ -0,0 +1,143 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::scpzipfiles; + +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::systemactions; + +# Replacing all zip list variables. +# Called for setup script as well as files with flag scpzip_replace. + +sub replace_all_ziplistvariables_in_file +{ + my ( $fileref, $variablesref ) = @_; + + for my $line ( @{$fileref} ) + { + # Avoid removing variables we do not recognise. + $line =~ s/\$\{(\w+)\}/defined $variablesref->{$1} + ? $variablesref->{$1} + : $&/eg; + } +} + +# Replacing all zip list variables in rtf files. + +sub replace_all_ziplistvariables_in_rtffile +{ + my ( $fileref, $variablesref ) = @_; + + for my $line ( @{$fileref} ) + { + # In rtf files the braces are backslash-escaped. + # Avoid removing variables we do not recognise. + $line =~ s/\$\\\{(\w+)\\\}/defined $variablesref->{$1} + ? $variablesref->{$1} + : $&/eg; + } +} + +######################################################### +# Analyzing files with flag SCPZIP_REPLACE +# $item can be "File" or "ScpAction" +######################################################### + +sub resolving_scpzip_replace_flag +{ + my ($filesarrayref, $variableshashref, $item, $languagestringref) = @_; + + my $diritem = lc($item); + + my $replacedirbase = installer::systemactions::create_directories("replace_$diritem", $languagestringref); + + installer::logger::include_header_into_logfile("$item with flag SCPZIP:"); + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + my $styles = ""; + + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + + if ( $styles =~ /\bSCPZIP_REPLACE\b/ ) + { + # Language specific subdirectory + + my $onelanguage = $onefile->{'specificlanguage'}; + + if ($onelanguage eq "") + { + $onelanguage = "00"; # files without language into directory "00" + } + + my $replacedir = $replacedirbase . $installer::globals::separator . $onelanguage . $installer::globals::separator; + installer::systemactions::create_directory($replacedir); # creating language specific directories + + # copy files and edit them with the variables defined in the zip.lst + + my $longfilename = 0; + + my $onefilename = $onefile->{'Name'}; + my $sourcepath = $onefile->{'sourcepath'}; + + if ( $onefilename =~ /^\s*\Q$installer::globals::separator\E/ ) # filename begins with a slash, for instance /registry/schema/org/openoffice/VCL.xcs + { + $onefilename =~ s/^\s*\Q$installer::globals::separator\E//; + $longfilename = 1; + } + + my $destinationpath = $replacedir . $onefilename; + my $movepath = $destinationpath . ".orig"; + + if ( $longfilename ) # the destination directory has to be created before copying + { + my $destdir = $movepath; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destdir); + installer::systemactions::create_directory_structure($destdir); + } + + my $copysuccess = installer::systemactions::copy_one_file($sourcepath, $movepath); + + if ( $copysuccess ) + { + # Now the file can be edited + # ToDo: How about binary patching? + + my $onefileref = installer::files::read_file($movepath); + replace_all_ziplistvariables_in_file($onefileref, $variableshashref); + installer::files::save_file($destinationpath ,$onefileref); + } + + # Saving the original source, where the file was found + $onefile->{'originalsourcepath'} = $onefile->{'sourcepath'}; + + # Writing the new sourcepath into the hashref, even if it was no copied + + $onefile->{'sourcepath'} = $destinationpath; + } + } + + my $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +1; diff --git a/solenv/bin/modules/installer/scriptitems.pm b/solenv/bin/modules/installer/scriptitems.pm new file mode 100644 index 000000000..54247d01d --- /dev/null +++ b/solenv/bin/modules/installer/scriptitems.pm @@ -0,0 +1,2404 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::scriptitems; + +use installer::converter; +use installer::exiter; +use installer::globals; +use installer::languages; +use installer::logger; +use installer::pathanalyzer; +use installer::remover; +use installer::systemactions; + +################################################################ +# Resolving the GID for the directories defined in setup script +################################################################ + +sub resolve_all_directory_names +{ + my ($directoryarrayref) = @_; + + # After this procedure the hash shall contain the complete language + # dependent path, not only the language dependent HostName. + + my ($key, $value, $parentvalue, $parentgid, $parentdirectoryhashref); + + for ( my $i = 0; $i <= $#{$directoryarrayref}; $i++ ) + { + my $directoryhashref = ${$directoryarrayref}[$i]; + my $gid = $directoryhashref-> {'gid'}; + my $parentid = $directoryhashref-> {'ParentID'}; + + if ( $parentid ne "PREDEFINED_PROGDIR" ) + { + # find the array of the parentid, which has to be defined before in setup script + # and is therefore listed before in this array + + for ( my $j = 0; $j <= $i; $j++ ) + { + $parentdirectoryhashref = ${$directoryarrayref}[$j]; + $parentgid = $parentdirectoryhashref->{'gid'}; + + if ( $parentid eq $parentgid) + { + last; + } + } + + # and now we can put the path together + # But take care of the languages! + + my $dirismultilingual = $directoryhashref->{'ismultilingual'}; + my $parentismultilingual = $parentdirectoryhashref->{'ismultilingual'}; + + # First: Both directories are language independent or both directories are language dependent + + if ((( ! $dirismultilingual ) && ( ! $parentismultilingual )) || + (( $dirismultilingual ) && ( $parentismultilingual ))) + { + foreach $key (keys %{$directoryhashref}) + { + # the key ("HostName (en-US)") must be usable for both hashes + + if ( $key =~ /\bHostName\b/ ) + { + $parentvalue = ""; + $value = $directoryhashref->{$key}; + if ( $parentdirectoryhashref->{$key} ) { $parentvalue = $parentdirectoryhashref->{$key}; } + + # It is possible, that in scp project, a directory is defined in more languages than + # the directory parent (happened after automatic generation of macros.inc). + # Therefore this is checked now and written with a warning into the logfile. + # This is no error, because (in most cases) the concerned language is not build. + + if ($parentvalue eq "") + { + $directoryhashref->{$key} = "FAILURE"; + my $infoline = "WARNING: No hostname for $parentid with \"$key\". Needed by child directory $gid !\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + else + { + $directoryhashref->{$key} = $parentvalue . $installer::globals::separator . $value; + } + } + } + } + + # Second: The directory is language dependent, the parent not + + if (( $dirismultilingual ) && ( ! $parentismultilingual )) + { + $parentvalue = $parentdirectoryhashref->{'HostName'}; # there is only one + + foreach $key (keys %{$directoryhashref}) # the current directory + { + if ( $key =~ /\bHostName\b/ ) + { + $value = $directoryhashref->{$key}; + $directoryhashref->{$key} = $parentvalue . $installer::globals::separator . $value; + } + } + } + + # Third: The directory is not language dependent, the parent is language dependent + + if (( ! $dirismultilingual ) && ( $parentismultilingual )) + { + $value = $directoryhashref->{'HostName'}; # there is only one + delete($directoryhashref->{'HostName'}); + + foreach $key (keys %{$parentdirectoryhashref}) # the parent directory + { + if ( $key =~ /\bHostName\b/ ) + { + $parentvalue = $parentdirectoryhashref->{$key}; # there is only one + $directoryhashref->{$key} = $parentvalue . $installer::globals::separator . $value; + } + } + + $directoryhashref->{'ismultilingual'} = 1; # now this directory is also language dependent + } + } + } +} + +############################################################################# +# Files with flag NOT_IN_SUITE do not need to be packed into +# Suite installation sets +############################################################################# + +sub remove_office_start_language_files +{ + my ($productarrayref) = @_; + + my @newitems = (); + + for ( my $i = 0; $i <= $#{$productarrayref}; $i++ ) + { + my $oneitem = ${$productarrayref}[$i]; + my $styles = ""; + + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + + if (!($styles =~ /\bSET_OFFICE_LANGUAGE\b/)) + { + push(@newitems, $oneitem); + } + else + { + my $infoline = "INFO: Flag SET_OFFICE_LANGUAGE \-\> Removing $oneitem->{'gid'} from file list.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + return \@newitems; +} + +############################################################################# +# Registryitems for Uninstall have to be removed +############################################################################# + +sub remove_uninstall_regitems_from_script +{ + my ($registryarrayref) = @_; + + my @newitems = (); + + for ( my $i = 0; $i <= $#{$registryarrayref}; $i++ ) + { + my $oneitem = ${$registryarrayref}[$i]; + my $subkey = ""; + + if ( $oneitem->{'Subkey'} ) { $subkey = $oneitem->{'Subkey'}; } + + if ( $subkey =~ /Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall/ ) { next; } + + push(@newitems, $oneitem); + } + + return \@newitems; +} + +############################################################################## +# Searching the language module for a specified language +############################################################################## + +sub get_languagespecific_module +{ + my ( $lang, $modulestring ) = @_; + + my $langmodulestring = ""; + + my $module; + foreach $module ( keys %installer::globals::alllangmodules ) + { + if (( $installer::globals::alllangmodules{$module} eq $lang ) && ( $modulestring =~ /\b$module\b/ )) + { + $langmodulestring = "$langmodulestring,$module"; + } + } + + $langmodulestring =~ s/^\s*,//; + + if ( $langmodulestring eq "" ) { installer::exiter::exit_program("ERROR: No language pack module found for language $lang in string \"$modulestring\"!", "get_languagespecific_module"); } + + return $langmodulestring; +} + +############################################################################## +# Removing all items in product lists which do not have the correct languages +############################################################################## + +sub resolving_all_languages_in_productlists +{ + my ($productarrayref, $languagesarrayref) = @_; + + my @itemsinalllanguages = (); + + my ($key, $value); + + for ( my $i = 0; $i <= $#{$productarrayref}; $i++ ) + { + my $oneitem = ${$productarrayref}[$i]; + + my $ismultilingual = $oneitem->{'ismultilingual'}; + + if (!($ismultilingual)) # nothing to do with single language items + { + $oneitem->{'specificlanguage'} = ""; + push(@itemsinalllanguages, $oneitem); + } + else #all language dependent files + { + for ( my $j = 0; $j <= $#{$languagesarrayref}; $j++ ) # iterating over all languages + { + my $onelanguage = ${$languagesarrayref}[$j]; + + my %oneitemhash = (); + + foreach $key (keys %{$oneitem}) + { + if ( $key =~ /\(\S+\)/ ) # this are the language dependent keys + { + if ( $key =~ /\(\Q$onelanguage\E\)/ ) + { + $value = $oneitem->{$key}; + $oneitemhash{$key} = $value; + } + } + else + { + $value = $oneitem->{$key}; + $oneitemhash{$key} = $value; + } + } + + $oneitemhash{'specificlanguage'} = $onelanguage; + + if ( $oneitemhash{'haslanguagemodule'} ) + { + my $langmodulestring = get_languagespecific_module($onelanguage, $oneitemhash{'modules'}); + $oneitemhash{'modules'} = $langmodulestring; + } + + push(@itemsinalllanguages, \%oneitemhash); + } + } + } + + return \@itemsinalllanguages; +} + +################################################################################ +# Removing all modules, that have the flag LANGUAGEMODULE, but do not +# have the correct language +################################################################################ + +sub remove_not_required_language_modules +{ + my ($modulesarrayref, $languagesarrayref) = @_; + + my @allmodules = (); + + for ( my $i = 0; $i <= $#{$modulesarrayref}; $i++ ) + { + my $module = ${$modulesarrayref}[$i]; + my $styles = ""; + if ( $module->{'Styles'} ) { $styles = $module->{'Styles'}; } + + if ( $styles =~ /\bLANGUAGEMODULE\b/ ) + { + if ( ! exists($module->{'Language'}) ) { installer::exiter::exit_program("ERROR: \"$module->{'gid'}\" has flag LANGUAGEMODULE, but does not know its language!", "remove_not_required_language_modules"); } + my $modulelanguage = $module->{'Language'}; + # checking, if language is required + my $doinclude = 0; + for ( my $j = 0; $j <= $#{$languagesarrayref}; $j++ ) + { + my $onelanguage = ${$languagesarrayref}[$j]; + if ( $onelanguage eq $modulelanguage ) + { + $doinclude = 1; + last; + } + } + + if ( $doinclude ) { push(@allmodules, $module); } + } + else + { + push(@allmodules, $module); + } + } + + return \@allmodules; +} + +################################################################################ +# Removing all modules, that have a spellchecker language that is not +# required for this product (spellchecker selection). +# All required spellchecker languages are stored in +# %installer::globals::spellcheckerlanguagehash +################################################################################ + +sub remove_not_required_spellcheckerlanguage_modules +{ + my ($modulesarrayref) = @_; + + my $infoline = ""; + my @allmodules = (); + + for ( my $i = 0; $i <= $#{$modulesarrayref}; $i++ ) + { + my $module = ${$modulesarrayref}[$i]; + if ( $module->{'Spellcheckerlanguage'} ) # selecting modules with Spellcheckerlanguage + { + if ( exists($installer::globals::spellcheckerlanguagehash{$module->{'Spellcheckerlanguage'}}) ) + { + push(@allmodules, $module); + } + else + { + $infoline = "Spellchecker selection: Removing module $module->{'gid'}\n"; + push( @installer::globals::logfileinfo, $infoline); + + # Collecting all files at modules that are removed + + if ( $module->{'Files'} ) + { + if ( $module->{'Files'} =~ /^\s*\((.*?)\)\s*$/ ) + { + my $filelist = $1; + + my $filelisthash = installer::converter::convert_stringlist_into_hash(\$filelist, ","); + foreach my $onefile ( keys %{$filelisthash} ) { $installer::globals::spellcheckerfilehash{$onefile} = 1; } + } + } + } + } + else + { + push(@allmodules, $module); + } + } + + return \@allmodules; +} + +################################################################################ +# Removing all modules, that belong to a module that was removed +# in "remove_not_required_spellcheckerlanguage_modules" because of the +# spellchecker language. The files belonging to the modules are collected +# in %installer::globals::spellcheckerfilehash. +################################################################################ + +sub remove_not_required_spellcheckerlanguage_files +{ + my ($filesarrayref) = @_; + + my @filesarray = (); + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + # FIXME: some items don't have 'gid' + if ( (defined $onefile->{'gid'}) && exists($installer::globals::spellcheckerfilehash{$onefile->{'gid'}}) ) + { + $infoline = "Spellchecker selection: Removing file $onefile->{'gid'}\n"; + push( @installer::globals::logfileinfo, $infoline); + next; + } + push(@filesarray, $onefile); + } + + return \@filesarray; +} + +################################################################################ +# Looking for directories without correct HostName +################################################################################ + +sub checking_directories_with_corrupt_hostname +{ + my ($dirsref, $languagesarrayref) = @_; + + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + + my $hostname = ""; + + if ( $onedir->{'HostName'} ) { $hostname = $onedir->{'HostName'}; } + + if ( $hostname eq "" ) + { + my $langstring = ""; + for ( my $j = 0; $j <= $#{$languagesarrayref}; $j++ ) { $langstring .= ${$languagesarrayref}[$j] . " "; } + installer::exiter::exit_program("ERROR: HostName not defined for $onedir->{'gid'} for specified language. Probably you wanted to create an installation set, in a language not defined in scp2 project. You selected the following language(s): $langstring", "checking_directories_with_corrupt_hostname"); + } + + if ( $hostname eq "FAILURE" ) + { + installer::exiter::exit_program("ERROR: Could not create HostName for $onedir->{'gid'} (missing language at parent). See logfile warning for more info!", "checking_directories_with_corrupt_hostname"); + } + } +} + +################################################################################ +# Setting global properties +################################################################################ + +sub set_global_directory_hostnames +{ + my ($dirsref, $allvariables) = @_; + + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + my $styles = ""; + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + if ( $styles =~ /\bOFFICEDIRECTORY\b/ ) + { + $installer::globals::officedirhostname = $onedir->{'HostName'}; + $installer::globals::officedirgid = $onedir->{'gid'}; + $allvariables->{'OFFICEDIRECTORYHOSTNAME'} = $installer::globals::officedirhostname; + } + } +} + +######################################################## +# Recursively defined procedure to order +# modules and directories +######################################################## + +sub get_children +{ + my ($allitems, $startparent, $newitemorder) = @_; + + for ( my $i = 0; $i <= $#{$allitems}; $i++ ) + { + my $gid = ${$allitems}[$i]->{'gid'}; + my $parent = ""; + if ( ${$allitems}[$i]->{'ParentID'} ) { $parent = ${$allitems}[$i]->{'ParentID'}; } + + if ( $parent eq $startparent ) + { + push(@{$newitemorder}, ${$allitems}[$i]); + my $parent = $gid; + get_children($allitems, $parent, $newitemorder); # recursive! + } + } +} + +################################################################################ +# Using langpack copy action for language packs +################################################################################ + +sub use_langpack_copy_scpaction +{ + my ($scpactionsref) = @_; + + for ( my $i = 0; $i <= $#{$scpactionsref}; $i++ ) + { + my $onescpaction = ${$scpactionsref}[$i]; + if (( $onescpaction->{'LangPackCopy'} ) && ( $onescpaction->{'LangPackCopy'} ne "" )) { $onescpaction->{'Copy'} = $onescpaction->{'LangPackCopy'}; } + } +} + +################################################################################ +# Using dev copy patch action for developer snapshot builds +################################################################################ + +sub use_devversion_copy_scpaction +{ + my ($scpactionsref) = @_; + + for ( my $i = 0; $i <= $#{$scpactionsref}; $i++ ) + { + my $onescpaction = ${$scpactionsref}[$i]; + if (( $onescpaction->{'DevVersionCopy'} ) && ( $onescpaction->{'DevVersionCopy'} ne "" )) { $onescpaction->{'Copy'} = $onescpaction->{'DevVersionCopy'}; } + } +} + +################################################################################ +# Shifting parent directories of URE and Basis layer, so that +# these directories are located below the Brand layer. +# Style: SHIFT_BASIS_INTO_BRAND_LAYER +################################################################################ + +sub shift_basis_directory_parents +{ + my ($dirsref) = @_; + + my @alldirs = (); + + my $officedirgid = ""; + + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + my $styles = ""; + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + if ( $styles =~ /\bOFFICEDIRECTORY\b/ ) { $officedirgid = $onedir->{'gid'}; } + } + + if ( $officedirgid ne "" ) + { + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + my $styles = ""; + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + if (( $styles =~ /\bBASISDIRECTORY\b/ ) || ( $styles =~ /\bUREDIRECTORY\b/ )) + { + $onedir->{'ParentID'} = $officedirgid; + } + } + + # Sorting directories + my $startgid = "PREDEFINED_PROGDIR"; + get_children($dirsref, $startgid, \@alldirs); + } + + return \@alldirs; +} + +################################################################################ +# Setting the name of the directory with style OFFICEDIRECTORY. +# The name can be defined in property OFFICEDIRECTORYNAME. +################################################################################ + +sub set_officedirectory_name +{ + my ($dirsref, $officedirname) = @_; + + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + my $styles = ""; + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + if ( $styles =~ /\bOFFICEDIRECTORY\b/ ) + { + $onedir->{'HostName'} = $officedirname; + last; + } + } +} + +################################################################################ +# Simplifying the name for language dependent items from "Name (xy)" to "Name" +################################################################################ + +sub changing_name_of_language_dependent_keys +{ + my ($itemsarrayref) = @_; + + # Changing key for multilingual items from "Name ( )" to "Name" or "HostName ( )" to "HostName" + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + my $onelanguage = $oneitem->{'specificlanguage'}; + + if (!($onelanguage eq "" )) # language dependent item + { + my $itemkey; + + foreach $itemkey (keys %{$oneitem}) + { + if ( $itemkey =~ /^\s*(\S+?)\s+\(\S+\)\s*$/ ) + { + my $newitemkey = $1; + my $itemvalue = $oneitem->{$itemkey}; + $oneitem->{$newitemkey} = $itemvalue; + delete($oneitem->{$itemkey}); + } + } + } + } +} + +################################################################################ +# Replacement of setup variables in ConfigurationItems and ProfileItems +# , , , , , +################################################################################ + +sub replace_setup_variables +{ + my ($itemsarrayref, $languagestringref, $hashref) = @_; + + my $languagesstring = $$languagestringref; + $languagesstring =~ s/\_/ /g; # replacing underscore with whitespace + + my $productname = $hashref->{'PRODUCTNAME'}; + my $productversion = $hashref->{'PRODUCTVERSION'}; + my $libo_version_major = ""; + if ( $hashref->{'LIBO_VERSION_MAJOR'} ) { $libo_version_major = $hashref->{'LIBO_VERSION_MAJOR'}; } + my $productkey = $productname . " " . $productversion; + + # string $buildid, which is used to replace the setup variable + + my $localbuild = $installer::globals::build; + + if ( $localbuild =~ /^\s*(\w+?)(\d+)\s*$/ ) { $localbuild = $2; } # using "680" instead of "src680" + + my $buildidstring = `cd $ENV{'SRCDIR'} 2>&1 >/dev/null && git log -n 1 --pretty=format:"%H"`; + if ($? || !$buildidstring) { + $buildidstring = $localbuild . "(Build:" . $installer::globals::buildid . ")"; + } + + my $updateid = $productname . "_" . $libo_version_major . "_" . $$languagestringref; + $updateid =~ s/ /_/g; + + my $updatechannel = ""; + if ( $ENV{'UPDATE_CONFIG'} && $ENV{'UPDATE_CONFIG'} ne "") + { + open(CONFIG, glob($ENV{'UPDATE_CONFIG'})); + while () + { + chomp; + if (/^s*(\S+)=(\S+)$/) + { + $key = $1; + $val = $2; + if ($key eq "channel") + { + $updatechannel = $val; + } + } + } + close(CONFIG); + } + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + my $value = $oneitem->{'Value'}; + + $value =~ s/\/$buildidstring/; + $value =~ s/\/$languagesstring/; + $value =~ s/\/$productkey/; + $value =~ s/\/$installer::globals::productcode/; + $value =~ s/\/$installer::globals::upgradecode/; + $value =~ s/\/$languagesstring/; + $value =~ s/\/$installer::globals::build/; + $value =~ s/\/$updateid/; + $value =~ s/\/$updatechannel/; + $value =~ s/\/$installer::globals::packageformat/; + $ENV{'OOO_VENDOR'} = "" if !defined $ENV{'OOO_VENDOR'}; + $value =~ s/\/$ENV{'OOO_VENDOR'}/; + + $oneitem->{'Value'} = $value; + } +} + +################################################################################ +# By defining variable LOCALUSERDIR in *.lst it is possible to change +# the standard destination of user directory defined in scp2 ($SYSUSERCONFIG). +################################################################################ + +sub replace_userdir_variable +{ + my ($itemsarrayref) = @_; + + my $userdir = ""; + if ( $allvariableshashref->{'LOCALUSERDIR'} ) { $userdir = $allvariableshashref->{'LOCALUSERDIR'}; } + else { $userdir = $installer::globals::simpledefaultuserdir; } + + if ( $userdir ne "" ) + { + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + $oneitem->{'Value'} =~ s/\$SYSUSERCONFIG/$userdir/; + } + } +} + +##################################################################################### +# Files and ConfigurationItems are not included for all languages. +# For instance asian fonts. These can be removed, if no "Name" is found. +# ConfigurationItems are not always defined in the linguistic configuration file. +# The "Key" cannot be found for them. +##################################################################################### + +sub remove_non_existent_languages_in_productlists +{ + my ($itemsarrayref, $languagestringref, $searchkey, $itemtype) = @_; + + # Removing of all non existent files, for instance asian fonts + + installer::logger::include_header_into_logfile("Removing for this language $$languagestringref:"); + + my @allexistentitems = (); + + my $infoline; + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + my $oneitemname = ""; # $searchkey is "Name" for files and "Key" for ConfigurationItems + + if ( $oneitem->{$searchkey} ) { $oneitemname = $oneitem->{$searchkey} } + + my $itemtoberemoved = 0; + + if ($oneitemname eq "") # for instance asian font in english installation set + { + $itemtoberemoved = 1; + } + + if ($itemtoberemoved) + { + $infoline = "WARNING: Language $$languagestringref: No $itemtype packed for $oneitem->{'gid'}!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + push(@allexistentitems, $oneitem); + } + } + + $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); + + return \@allexistentitems; +} + +######################################################################## +# Input is the directory gid, output the "HostName" of the directory +######################################################################## + +sub get_Directoryname_From_Directorygid +{ + my ($dirsarrayref ,$searchgid, $onelanguage, $oneitemgid) = @_; + + my $directoryname = ""; + my $onedirectory; + my $foundgid = 0; + + for ( my $i = 0; $i <= $#{$dirsarrayref}; $i++ ) + { + $onedirectory = ${$dirsarrayref}[$i]; + my $directorygid = $onedirectory->{'gid'}; + + if ($directorygid eq $searchgid) + { + $foundgid = 1; + last; + } + } + + if (!($foundgid)) + { + installer::exiter::exit_program("ERROR: Gid $searchgid not defined in $installer::globals::setupscriptname", "get_Directoryname_From_Directorygid"); + } + + if ( ! ( $onedirectory->{'ismultilingual'} )) # the directory is not language dependent + { + $directoryname = $onedirectory->{'HostName'}; + } + else + { + $directoryname = $onedirectory->{"HostName ($onelanguage)"}; + } + + # gid_Dir_Template_Wizard_Letter is defined as language dependent directory, but the file gid_Dir_Template_Wizard_Letter + # is not language dependent. Therefore $onelanguage is not defined. But which language is the correct language for the + # directory? + # Perhaps better solution: In scp it must be forbidden to have a language independent file in a language dependent directory. + + if (( ! $directoryname ) && ( $onelanguage eq "" )) + { + installer::exiter::exit_program("ERROR (in scp): Directory $searchgid is language dependent, but not $oneitemgid inside this directory", "get_Directoryname_From_Directorygid"); + } + + return \$directoryname; +} + +################################################################## +# Getting destination directory for links, files and profiles +################################################################## + +sub get_Destination_Directory_For_Item_From_Directorylist # this is used for Files, Profiles and Links +{ + my ($itemarrayref, $dirsarrayref) = @_; + + for ( my $i = 0; $i <= $#{$itemarrayref}; $i++ ) + { + my $oneitem = ${$itemarrayref}[$i]; + my $oneitemgid = $oneitem->{'gid'}; + my $directorygid = $oneitem->{'Dir'}; # for instance gid_Dir_Program + my $netdirectorygid = ""; + my $onelanguage = $oneitem->{'specificlanguage'}; + my $ispredefinedprogdir = 0; + my $ispredefinedconfigdir = 0; + + my $oneitemname = $oneitem->{'Name'}; + + if ( $oneitem->{'NetDir'} ) { $netdirectorygid = $oneitem->{'NetDir'}; } + + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$oneitemname); # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs + + my $searchdirgid; + + if ( $netdirectorygid eq "" ) # if NetDir is defined, it is privileged + { + $searchdirgid = $directorygid + } + else + { + $searchdirgid = $netdirectorygid + } + + if ($searchdirgid =~ /PREDEFINED_PROGDIR/) # the root directory is not defined in setup script + { + $ispredefinedprogdir = 1; + } + + if ($searchdirgid =~ /PREDEFINED_CONFIGDIR/) # the root directory is not defined in setup script + { + $ispredefinedconfigdir = 1; + } + + my $destfilename; + + if ($oneitem->{'DoNotMessWithSymlinks'}) + { + $destfilename = $oneitem->{'Name'}; + } + elsif ((!( $ispredefinedprogdir )) && (!( $ispredefinedconfigdir ))) + { + my $directorynameref = get_Directoryname_From_Directorygid($dirsarrayref, $searchdirgid, $onelanguage, $oneitemgid); + my $styles = ""; + if ($oneitem->{'Styles'}) { $styles = $oneitem->{'Styles'}; } + if ($styles =~ /\bFILELIST\b/) + { + $destfilename = $$directorynameref . $installer::globals::separator . $oneitemname; + } + else + { + $destfilename = $$directorynameref . $installer::globals::separator . $oneitem->{'Name'}; + } + } + else + { + $destfilename = $oneitemname; + } + + $oneitem->{'destination'} = $destfilename; + } +} + +########################################################################## +# Searching a file in a list of paths +########################################################################## + +sub get_sourcepath_from_filename_and_includepath_classic +{ + my ($searchfilenameref, $includepatharrayref, $write_logfile) = @_; + + my ($onefile, $includepath, $infoline); + + my $foundsourcefile = 0; + + for ( my $j = 0; $j <= $#{$includepatharrayref}; $j++ ) + { + $includepath = ${$includepatharrayref}[$j]; + installer::remover::remove_leading_and_ending_whitespaces(\$includepath); + + $onefile = $includepath . $installer::globals::separator . $$searchfilenameref; + + if ( -f $onefile ) + { + $foundsourcefile = 1; + last; + } + } + + if (!($foundsourcefile)) + { + $onefile = ""; # the sourcepath has to be empty + if ( $write_logfile) + { + $infoline = "ERROR: Source for $$searchfilenameref not found (classic)!\n"; # Important message in log file + push( @installer::globals::logfileinfo, $infoline); + } + } + else + { + if ( $write_logfile) + { + $infoline = "SUCCESS: Source for $$searchfilenameref: $onefile\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + return \$onefile; +} + +########################################################################## +# Input is one file name, output the complete absolute path of this file +########################################################################## + +sub get_sourcepath_from_filename_and_includepath +{ + my ($searchfilenameref, $unused, $write_logfile) = @_; + + my ($onefile, $includepath, $infoline); + + my $foundsourcefile = 0; + my $foundnewname = 0; + + for ( my $j = 0; $j <= $#installer::globals::allincludepaths; $j++ ) + { + my $allfiles = $installer::globals::allincludepaths[$j]; + + if ( exists( $allfiles->{$$searchfilenameref} )) + { + $onefile = $allfiles->{'includepath'} . $installer::globals::separator . $$searchfilenameref; + $foundsourcefile = 1; + last; + } + } + + if (!($foundsourcefile)) # testing with lowercase filename + { + # Attention: README01.html is copied for Windows to readme01.html, not case sensitive + + for ( my $j = 0; $j <= $#installer::globals::allincludepaths; $j++ ) + { + my $allfiles = $installer::globals::allincludepaths[$j]; + + my $newfilename = $$searchfilenameref; + $newfilename =~ s/readme/README/; # special handling for readme files + $newfilename =~ s/license/LICENSE/; # special handling for license files + + if ( exists( $allfiles->{$newfilename} )) + { + $onefile = $allfiles->{'includepath'} . $installer::globals::separator . $newfilename; + $foundsourcefile = 1; + $foundnewname = 1; + last; + } + } + } + + if (!($foundsourcefile)) + { + $onefile = ""; # the sourcepath has to be empty + if ( $write_logfile) + { + $infoline = "ERROR: Source for $$searchfilenameref not found!\n"; # Important message in log file + push( @installer::globals::logfileinfo, $infoline); + } + } + else + { + if ( $write_logfile) + { + if (!($foundnewname)) + { + $infoline = "SUCCESS: Source for $$searchfilenameref: $onefile\n"; + } + else + { + $infoline = "SUCCESS/WARNING: Special handling for $$searchfilenameref: $onefile\n"; + } + push( @installer::globals::logfileinfo, $infoline); + } + } + + return \$onefile; +} + +############################################################## +# Getting all source paths for all files to be packed +# $item can be "Files" or "ScpActions" +############################################################## + +sub get_Source_Directory_For_Files_From_Includepathlist +{ + my ($filesarrayref, $includepatharrayref, $dirsref, $item, $allvariables) = @_; + + installer::logger::include_header_into_logfile("$item:"); + + my ($foundit, $dontcare, $extrarootdir) = + get_office_directory_gid_and_hostname($dirsref); + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + my $onelanguage = $onefile->{'specificlanguage'}; + + if ( ! $onefile->{'Name'} ) { installer::exiter::exit_program("ERROR: $item without name ! GID: $onefile->{'gid'} ! Language: $onelanguage", "get_Source_Directory_For_Files_From_Includepathlist"); } + + my $onefilename = $onefile->{'Name'}; + if ( $item eq "ScpActions" ) { $onefilename =~ s/\//$installer::globals::separator/g; } + $onefilename =~ s/^\s*\Q$installer::globals::separator\E//; # filename begins with a slash, for instance /registry/schema/org/openoffice/VCL.xcs + + my $styles = ""; + my $file_can_miss = 0; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + + if (( $installer::globals::languagepack ) && ( ! $onefile->{'ismultilingual'} ) && ( ! ( $styles =~ /\bFORCELANGUAGEPACK\b/ ))) { $file_can_miss = 1; } + if (( $installer::globals::helppack ) && ( ! $onefile->{'ismultilingual'} ) && ( ! ( $styles =~ /\bFORCEHELPPACK\b/ ))) { $file_can_miss = 1; } + + my $sourcepathref = ""; + + my $destination = $onefile->{'destination'}; + my $instdirdestination; + if ($destination) + { + if (($installer::globals::iswindowsbuild) && $foundit && $extrarootdir) + { + $destination =~ s,$extrarootdir/,,; # remove it from path + } + if (($installer::globals::languagepack) && ($installer::globals::ismacbuild)) + { # source files are in $(PRODUCTNAME).app where they will + # actually copied by the user executing the Language Pack.app + $destination =~ s, Language Pack.app/,.app/,; + } + $instdirdestination = $ENV{'INSTDIR'} . $installer::globals::separator . $destination; + } + if ($instdirdestination && -f $instdirdestination) + { + $infoline = "SUCCESS: INSTDIR Source for $onefilename: $instdirdestination\n"; + push( @installer::globals::logfileinfo, $infoline); + $$sourcepathref = $instdirdestination; + } + else + { + if ( $file_can_miss ) { $sourcepathref = get_sourcepath_from_filename_and_includepath(\$onefilename, $includepatharrayref, 0); } + else { $sourcepathref = get_sourcepath_from_filename_and_includepath(\$onefilename, $includepatharrayref, 1); } + } + + $onefile->{'sourcepath'} = $$sourcepathref; # This $$sourcepathref is empty, if no source was found + } + + $infoline = "\n"; # empty line after listing of all files + push( @installer::globals::logfileinfo, $infoline); +} + +################################################################################# +# Removing files, that shall not be included into languagepacks +# (because of rpm conflicts) +################################################################################# + +sub remove_Files_For_Languagepacks +{ + my ($itemsarrayref) = @_; + + my $infoline; + + my @newitemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + my $gid = $oneitem->{'gid'}; + + # scp Todo: Remove asap after removal of old setup + + if (( $gid eq "gid_File_Extra_Fontunxpsprint" ) || + ( $gid eq "gid_File_Extra_Migration_Lang" )) + { + $infoline = "ATTENTION: Removing item $oneitem->{'gid'} from the installation set.\n"; + push( @installer::globals::logfileinfo, $infoline); + + next; + } + + push(@newitemsarray, $oneitem); + } + + return \@newitemsarray; +} + +################################################################################# +# Files, whose source directory is not found, are removed now (this is an ERROR) +################################################################################# + +sub remove_Files_Without_Sourcedirectory +{ + my ($filesarrayref) = @_; + + my $infoline; + + my $error_occurred = 0; + my @missingfiles = (); + push(@missingfiles, "ERROR: The following files could not be found: \n"); + + my @newfilesarray = (); + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + my $sourcepath = $onefile->{'sourcepath'}; + + if ($sourcepath eq "") + { + my $styles = $onefile->{'Styles'}; + my $filename = $onefile->{'Name'}; + + if ( ! $installer::globals::languagepack && !$installer::globals::helppack) + { + $infoline = "ERROR: Removing file $filename from file list.\n"; + push( @installer::globals::logfileinfo, $infoline); + + push(@missingfiles, "ERROR: File not found: $filename\n"); + $error_occurred = 1; + + next; # removing this file from list, if sourcepath is empty + } + elsif ( $installer::globals::languagepack ) # special case for language packs + { + if (( $onefile->{'ismultilingual'} ) || ( $styles =~ /\bFORCELANGUAGEPACK\b/ )) + { + $infoline = "ERROR: Removing file $filename from file list.\n"; + push( @installer::globals::logfileinfo, $infoline); + + push(@missingfiles, "ERROR: File not found: $filename\n"); + $error_occurred = 1; + + next; # removing this file from list, if sourcepath is empty + } + else + { + $infoline = "INFO: Removing file $filename from file list. It is not language dependent.\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "INFO: It is not language dependent and can be ignored in language packs.\n"; + push( @installer::globals::logfileinfo, $infoline); + + next; # removing this file from list, if sourcepath is empty + } + } + else # special case for help packs + { + if (( $onefile->{'ismultilingual'} ) || ( $styles =~ /\bFORCEHELPPACK\b/ )) + { + $infoline = "ERROR: Removing file $filename from file list.\n"; + push( @installer::globals::logfileinfo, $infoline); + + push(@missingfiles, "ERROR: File not found: $filename\n"); + $error_occurred = 1; + + next; # removing this file from list, if sourcepath is empty + } + else + { + $infoline = "INFO: Removing file $filename from file list. It is not language dependent.\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "INFO: It is not language dependent and can be ignored in help packs.\n"; + push( @installer::globals::logfileinfo, $infoline); + + next; # removing this file from list, if sourcepath is empty + } + } + } + + push(@newfilesarray, $onefile); + } + + $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( $error_occurred ) + { + for ( my $i = 0; $i <= $#missingfiles; $i++ ) { print "$missingfiles[$i]"; } + installer::exiter::exit_program("ERROR: Missing files", "remove_Files_Without_Sourcedirectory"); + } + + return \@newfilesarray; +} + +############################################################################ +# License and Readme files in the default language have to be installed +# in the directory with flag OFFICEDIRECTORY. If this is not defined +# they have to be installed in the installation root. +############################################################################ + +sub get_office_directory_gid_and_hostname +{ + my ($dirsarrayref) = @_; + + my $foundofficedir = 0; + my $gid = ""; + my $hostname = ""; + + for ( my $i = 0; $i <= $#{$dirsarrayref}; $i++ ) + { + my $onedir = ${$dirsarrayref}[$i]; + if ( $onedir->{'Styles'} ) + { + my $styles = $onedir->{'Styles'}; + + if ( $styles =~ /\bOFFICEDIRECTORY\b/ ) + { + $foundofficedir = 1; + $gid = $onedir->{'gid'}; + $hostname = $onedir->{'HostName'}; + last; + } + } + } + + return ($foundofficedir, $gid, $hostname); +} + +############################################################################ +# License and Readme files in the default language have to be installed +# in the installation root (next to the program dir). This is in scp +# project done by a post install basic script +############################################################################ + +sub add_License_Files_into_Installdir +{ + my ($filesarrayref, $dirsarrayref, $languagesarrayref) = @_; + + my $infoline; + + my @newfilesarray = (); + + my $defaultlanguage = installer::languages::get_default_language($languagesarrayref); + + my ($foundofficedir, $officedirectorygid, $officedirectoryhostname) = get_office_directory_gid_and_hostname($dirsarrayref); + + # copy all files from directory share/readme, that contain the default language in their name + # without default language into the installation root. This makes the settings of the correct + # file names superfluous. On the other hand this requires a dependency to the directory + # share/readme + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + my $destination = $onefile->{'destination'}; + my $styles = ""; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + + if ( ( $destination =~ /share\Q$installer::globals::separator\Ereadme\Q$installer::globals::separator\E(\w+?)_?$defaultlanguage\.?(\w*)\s*/ ) + || (( $styles =~ /\bROOTLICENSEFILE\b/ ) && ( $destination =~ /\Q$installer::globals::separator\E?(\w+?)_?$defaultlanguage\.?(\w*?)\s*$/ )) ) + { + my $filename = $1; + my $extension = $2; + + my $newfilename; + + if ( $extension eq "" ) { $newfilename = $filename; } + else { $newfilename = $filename . "\." . $extension; } + + my %newfile = (); + my $newfile = \%newfile; + + installer::converter::copy_item_object($onefile, $newfile); + + $newfile->{'gid'} = $onefile->{'gid'} . "_Copy"; + $newfile->{'Name'} = $newfilename; + $newfile->{'ismultilingual'} = "0"; + $newfile->{'specificlanguage'} = ""; + $newfile->{'haslanguagemodule'} = "0"; + + if ( defined $newfile->{'InstallName'} ) + { + if ( $newfile->{'InstallName'} =~ /^\s*(.*?)_$defaultlanguage\.?(\w*?)\s*$/ ) + { + my $localfilename = $1; + my $localextension = $2; + + if ( $localextension eq "" ) { $newfile->{'InstallName'} = $localfilename; } + else { $newfile->{'InstallName'} = $localfilename . "\." . $localextension; } + } + } + + $newfile->{'removelangfromfile'} = "1"; # Important for files with an InstallName, because language also has to be removed there. + + if ( $foundofficedir ) + { + $newfile->{'Dir'} = $officedirectorygid; + $newfile->{'destination'} = $officedirectoryhostname . $installer::globals::separator . $newfilename; + } + else + { + $newfile->{'Dir'} = "PREDEFINED_PROGDIR"; + $newfile->{'destination'} = $newfilename; + } + + # Also setting "modules=gid_Module_Root_Brand" (module with style: ROOT_BRAND_PACKAGE) + if ( $installer::globals::rootbrandpackageset ) + { + $newfile->{'modules'} = $installer::globals::rootbrandpackage; + } + + push(@newfilesarray, $newfile); + + $infoline = "New files: Adding file $newfilename for the installation root to the file list. Language: $defaultlanguage\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( defined $newfile->{'InstallName'} ) + { + $infoline = "New files: Using installation name: $newfile->{'InstallName'}\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + push(@newfilesarray, $onefile); + } + + return \@newfilesarray; +} + +############################################################################ +# Some files are included for more than one language and have the same +# name and the same destination directory for all languages. This would +# lead to conflicts, if the filenames are not changed. +# In scp project this files must have the flag MAKE_LANG_SPECIFIC +# For this files, the language is included into the filename. +############################################################################ + +sub make_filename_language_specific +{ + my ($filesarrayref) = @_; + + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + + if ( $onefile->{'ismultilingual'} ) + { + my $styles = ""; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + if ( $styles =~ /\bMAKE_LANG_SPECIFIC\b/ ) + { + my $language = $onefile->{'specificlanguage'}; + my $olddestination = $onefile->{'destination'}; + my $oldname = $onefile->{'Name'}; + + # Including the language into the file name. + # But be sure, to include the language before the file extension. + + my $fileextension = ""; + + if ( $onefile->{'Name'} =~ /(\.\w+?)\s*$/ ) { $fileextension = $1; } + if ( $fileextension ne "" ) + { + $onefile->{'Name'} =~ s/\Q$fileextension\E\s*$/_$language$fileextension/; + $onefile->{'destination'} =~ s/\Q$fileextension\E\s*$/_$language$fileextension/; + } + + $infoline = "Flag MAKE_LANG_SPECIFIC:\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Changing name from $oldname to $onefile->{'Name'} !\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Changing destination from $olddestination to $onefile->{'destination'} !\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + } +} + +############################################################################ +# Because of the item "File" the source name must be "Name". Therefore +# "Copy" is changed to "Name" and "Name" is changed to "DestinationName". +############################################################################ + +sub change_keys_of_scpactions +{ + my ($itemsarrayref) = @_; + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + + my $key; + + # First Name to DestinationName, then deleting Name + foreach $key (keys %{$oneitem}) + { + if ( $key =~ /\bName\b/ ) + { + my $value = $oneitem->{$key}; + my $oldkey = $key; + $key =~ s/Name/DestinationName/; + $oneitem->{$key} = $value; + delete($oneitem->{$oldkey}); + } + } + + # Second Copy to Name, then deleting Copy + foreach $key (keys %{$oneitem}) + { + if ( $key =~ /\bCopy\b/ ) + { + my $value = $oneitem->{$key}; + my $oldkey = $key; + $key =~ s/Copy/Name/; + $oneitem->{$key} = $value; + delete($oneitem->{$oldkey}); + } + } + } +} + +############################################################################ +# Removing all language pack files from installation set (files with +# the style LANGUAGEPACK), except this is a language pack. +############################################################################ + +sub remove_Languagepacklibraries_from_Installset +{ + my ($itemsarrayref) = @_; + + my $infoline; + + my @newitemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + + if ( $styles =~ /\bLANGUAGEPACK\b/ ) + { + $infoline = "Removing language pack file $oneitem->{'gid'} from the installation set.\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + next; + } + + push(@newitemsarray, $oneitem); + } + + $infoline = "\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + return \@newitemsarray; +} + +############################################################################ +# Removing all help pack files from installation set (files with +# the style HELPPACK), except this is a help pack. +############################################################################ + +sub remove_Helppacklibraries_from_Installset +{ + my ($itemsarrayref) = @_; + + my $infoline; + + my @newitemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ ) + { + my $oneitem = ${$itemsarrayref}[$i]; + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + + if ( $styles =~ /\bHELPPACK\b/ ) + { + $infoline = "Removing help pack file $oneitem->{'gid'} from the installation set.\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + next; + } + + push(@newitemsarray, $oneitem); + } + + $infoline = "\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + return \@newitemsarray; +} + +############################################################################ +# Some files contain a $ in their name. epm conflicts with such files. +# Solution: Renaming this files, converting "$" to "$$" +############################################################################ + +sub quoting_illegal_filenames +{ + my ($filesarrayref) = @_; + + # This function has to be removed as soon as possible! + + installer::logger::include_header_into_logfile("Renaming illegal filenames:"); + + for ( my $i = 0; $i <= $#{$filesarrayref}; $i++ ) + { + my $onefile = ${$filesarrayref}[$i]; + my $filename = $onefile->{'Name'}; + + if ( $filename =~ /\$/ ) + { + my $sourcepath = $onefile->{'sourcepath'}; + my $destpath = $onefile->{'destination'}; + + # sourcepath and destination have to be quoted for epm list file + + $destpath =~ s/\$/\$\$/g; + $sourcepath =~ s/\$/\$\$/g; + + my $infoline = "ATTENTION: Files: Quoting sourcepath $onefile->{'sourcepath'} to $sourcepath\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "ATTENTION: Files: Quoting destination path $onefile->{'destination'} to $destpath\n"; + push( @installer::globals::logfileinfo, $infoline); + + $onefile->{'sourcepath'} = $sourcepath; + $onefile->{'destination'} = $destpath; + } + } +} + +############################################################################ +# Removing multiple occurrences of same module. +############################################################################ + +sub optimize_list +{ + my ( $longlist ) = @_; + my %tmpHash; + + $longlist =~ s/^\s+//; + $longlist =~ s/\s+$//; + $longlist =~ s/\s*,\s*/,/g; + + @tmpHash{split /,/, $longlist} = (); + return join(",", sort keys %tmpHash); +} + +####################################################################### +# Collecting all directories needed for the epm list +# 1. Looking for all destination paths in the files array +# 2. Looking for directories with CREATE flag in the directory array +####################################################################### + +################################## +# Collecting directories: Part 1 +################################## + +sub collect_directories_from_filesarray +{ + my ($filesarrayref, $unixlinksarrayref) = @_; + my @allfiles; + push @allfiles, @{$filesarrayref}; + push @allfiles, @{$unixlinksarrayref}; + + my @alldirectories = (); + my %alldirectoryhash = (); + + my $predefinedprogdir_added = 0; + + # Preparing this already as hash, although the only needed value at the moment is the HostName + # But also adding: "specificlanguage" and "Dir" (for instance gid_Dir_Program) + + for ( my $i = 0; $i <= $#allfiles; $i++ ) + { + my $onefile = $allfiles[$i]; + my $destinationpath = $onefile->{'destination'}; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destinationpath); + $destinationpath =~ s/\Q$installer::globals::separator\E\s*$//; # removing ending slashes or backslashes + + do + { + if (!exists($alldirectoryhash{$destinationpath})) + { + my %directoryhash = (); + $directoryhash{'HostName'} = $destinationpath; + $directoryhash{'specificlanguage'} = $onefile->{'specificlanguage'}; + $directoryhash{'Dir'} = $onefile->{'Dir'}; + $directoryhash{'modules'} = $onefile->{'modules'}; # NEW, saving modules + $directoryhash{'gid'} = $onefile->{'gid'}; + + $predefinedprogdir_added ||= $onefile->{'Dir'} eq "PREDEFINED_PROGDIR"; + + $alldirectoryhash{$destinationpath} = \%directoryhash; + } + else + { + # Adding the modules to the module list! + $alldirectoryhash{$destinationpath}->{'modules'} .= "," . $onefile->{'modules'}; + # Save file's gid iff this directory appears in only a single + # file's FILELIST (so that unused directories will be filtered + # out in remove_not_required_spellcheckerlanguage_files, based + # on gid): + if ($alldirectoryhash{$destinationpath}->{'gid'} + ne $onefile->{'gid'}) + { + $alldirectoryhash{$destinationpath}->{'gid'} = ''; + } + } + } while ($destinationpath =~ s/(^.*\S)\Q$installer::globals::separator\E(\S.*?)\s*$/$1/); # as long as the path contains slashes + } + + # if there is no file in the root directory PREDEFINED_PROGDIR, it has to be included into the directory array now + # HostName= specificlanguage= Dir=PREDEFINED_PROGDIR + + if (! $predefinedprogdir_added ) + { + my %directoryhash = (); + $directoryhash{'HostName'} = ""; + $directoryhash{'specificlanguage'} = ""; + $directoryhash{'modules'} = ""; # ToDo? + $directoryhash{'Dir'} = "PREDEFINED_PROGDIR"; + + push(@alldirectories, \%directoryhash); + } + + # Creating directory array + foreach my $destdir ( sort keys %alldirectoryhash ) + { + $alldirectoryhash{$destdir}->{'modules'} = optimize_list($alldirectoryhash{$destdir}->{'modules'}); + push(@alldirectories, $alldirectoryhash{$destdir}); + } + + return (\@alldirectories, \%alldirectoryhash); +} + +################################## +# Collecting directories: Part 2 +################################## + +sub collect_directories_with_create_flag_from_directoryarray +{ + my ($directoryarrayref, $alldirectoryhash) = @_; + + my $alreadyincluded = 0; + my @alldirectories = (); + + for ( my $i = 0; $i <= $#{$directoryarrayref}; $i++ ) + { + my $onedir = ${$directoryarrayref}[$i]; + my $styles = ""; + $newdirincluded = 0; + + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + if ( $styles =~ /\bCREATE\b/ ) + { + my $directoryname = ""; + + if ( $onedir->{'HostName'} ) { $directoryname = $onedir->{'HostName'}; } + else { installer::exiter::exit_program("ERROR: No directory name (HostName) set for specified language in gid $onedir->{'gid'}", "collect_directories_with_create_flag_from_directoryarray"); } + + $alreadyincluded = 0; + if ( exists($alldirectoryhash->{$directoryname}) ) { $alreadyincluded = 1; } + + if (!($alreadyincluded)) + { + my %directoryhash = (); + $directoryhash{'HostName'} = $directoryname; + $directoryhash{'specificlanguage'} = $onedir->{'specificlanguage'}; + $directoryhash{'Dir'} = $onedir->{'gid'}; + $directoryhash{'Styles'} = $onedir->{'Styles'}; + + # saving also the modules + if ( ! $onedir->{'modules'} ) { installer::exiter::exit_program("ERROR: No assigned modules found for directory $onedir->{'gid'}", "collect_directories_with_create_flag_from_directoryarray"); } + $directoryhash{'modules'} = $onedir->{'modules'}; + + $alldirectoryhash->{$directoryname} = \%directoryhash; + $newdirincluded = 1; + + # Problem: The $destinationpath can be share/registry/schema/org/openoffice + # but not all directories contain files and will be added to this list. + # Therefore the path has to be analyzed. + + while ( $directoryname =~ /(^.*\S)\Q$installer::globals::separator\E(\S.*?)\s*$/ ) # as long as the path contains slashes + { + $directoryname = $1; + + $alreadyincluded = 0; + if ( exists($alldirectoryhash->{$directoryname}) ) { $alreadyincluded = 1; } + + if (!($alreadyincluded)) + { + my %directoryhash = (); + + $directoryhash{'HostName'} = $directoryname; + $directoryhash{'specificlanguage'} = $onedir->{'specificlanguage'}; + $directoryhash{'Dir'} = $onedir->{'gid'}; + if ( ! $installer::globals::iswindowsbuild ) { $directoryhash{'Styles'} = "(CREATE)"; } # Exception for Windows? + + # saving also the modules + $directoryhash{'modules'} = $onedir->{'modules'}; + + $alldirectoryhash->{$directoryname} = \%directoryhash; + $newdirincluded = 1; + } + else + { + # Adding the modules to the module list! + $alldirectoryhash->{$directoryname}->{'modules'} = $alldirectoryhash->{$directoryname}->{'modules'} . "," . $onedir->{'modules'}; + } + } + } + else + { + # Adding the modules to the module list! + $alldirectoryhash->{$directoryname}->{'modules'} = $alldirectoryhash->{$directoryname}->{'modules'} . "," . $onedir->{'modules'}; + + while ( $directoryname =~ /(^.*\S)\Q$installer::globals::separator\E(\S.*?)\s*$/ ) # as long as the path contains slashes + { + $directoryname = $1; + # Adding the modules to the module list! + $alldirectoryhash->{$directoryname}->{'modules'} = $alldirectoryhash->{$directoryname}->{'modules'} . "," . $onedir->{'modules'}; + } + } + } + + # Saving the styles for already added directories in function collect_directories_from_filesarray + + if (( ! $newdirincluded ) && ( $styles ne "" )) + { + $styles =~ s/\bWORKSTATION\b//; + $styles =~ s/\bCREATE\b//; + + if (( ! ( $styles =~ /^\s*\(\s*\)\s*$/ )) && ( ! ( $styles =~ /^\s*\(\s*\,\s*\)\s*$/ )) && ( ! ( $styles =~ /^\s*$/ ))) # checking, if there are styles left + { + my $directoryname = ""; + if ( $onedir->{'HostName'} ) { $directoryname = $onedir->{'HostName'}; } + else { installer::exiter::exit_program("ERROR: No directory name (HostName) set for specified language in gid $onedir->{'gid'}", "collect_directories_with_create_flag_from_directoryarray"); } + + if ( exists($alldirectoryhash->{$directoryname}) ) + { + $alldirectoryhash->{$directoryname}->{'Styles'} = $styles; + } + } + } + } + + # Creating directory array + foreach my $destdir ( sort keys %{$alldirectoryhash} ) + { + $alldirectoryhash->{$destdir}->{'modules'} = optimize_list($alldirectoryhash->{$destdir}->{'modules'}); + push(@alldirectories, $alldirectoryhash->{$destdir}); + } + + return (\@alldirectories, \%alldirectoryhash); +} + +################################################# +# Determining the destination file of a link +################################################# + +sub get_destination_file_path_for_links +{ + my ($linksarrayref, $filesarrayref) = @_; + + my $infoline; + + for ( my $i = 0; $i <= $#{$linksarrayref}; $i++ ) + { + my $fileid = ""; + my $onelink = ${$linksarrayref}[$i]; + if ( $onelink->{'FileID'} ) { $fileid = $onelink->{'FileID'}; } + + if (!( $fileid eq "" )) + { + my $foundfile = 0; + + for ( my $j = 0; $j <= $#{$filesarrayref}; $j++ ) + { + my $onefile = ${$filesarrayref}[$j]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $fileid ) + { + $foundfile = 1; + $onelink->{'destinationfile'} = $onefile->{'destination'}; + last; + } + } + + if (!($foundfile)) + { + $infoline = "Warning: FileID $fileid for Link $onelink->{'gid'} not found!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + } + + $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +################################################# +# Determining the destination link of a link +################################################# + +sub get_destination_link_path_for_links +{ + my ($linksarrayref) = @_; + + my $infoline; + + for ( my $i = 0; $i <= $#{$linksarrayref}; $i++ ) + { + my $shortcutid = ""; + my $onelink = ${$linksarrayref}[$i]; + if ( $onelink->{'ShortcutID'} ) { $shortcutid = $onelink->{'ShortcutID'}; } + + if (!( $shortcutid eq "" )) + { + my $foundlink = 0; + + for ( my $j = 0; $j <= $#{$linksarrayref}; $j++ ) + { + my $destlink = ${$linksarrayref}[$j]; + $shortcutgid = $destlink->{'gid'}; + + if ( $shortcutgid eq $shortcutid ) + { + $foundlink = 1; + $onelink->{'destinationfile'} = $destlink->{'destination'}; # making key 'destinationfile' + last; + } + } + + if (!($foundlink)) + { + $infoline = "Warning: ShortcutID $shortcutid for Link $onelink->{'gid'} not found!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + } + + $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +################################################################################### +# Items with flag WORKSTATION are not needed (here: links and configurationitems) +################################################################################### + +sub remove_workstation_only_items +{ + my ($itemarrayref) = @_; + + my @newitemarray = (); + + for ( my $i = 0; $i <= $#{$itemarrayref}; $i++ ) + { + my $oneitem = ${$itemarrayref}[$i]; + my $styles = $oneitem->{'Styles'}; + + if (( $styles =~ /\bWORKSTATION\b/ ) && + (!( $styles =~ /\bNETWORK\b/ )) && + (!( $styles =~ /\bSTANDALONE\b/ ))) + { + next; # removing this link, it is only needed for a workstation installation + } + + push(@newitemarray, $oneitem); + } + + return \@newitemarray; +} + +################################################ +# Resolving relative path in links +################################################ + +sub resolve_links_with_flag_relative +{ + my ($linksarrayref) = @_; + + # Before this step is: + # destination=program/libsalhelperC52.so.3, this will be the name of the link + # destinationfile=program/libsalhelperC52.so.3, this will be the linked file or name + # If the flag RELATIVE is set, the paths have to be analyzed. If the flag is not set + # (this will not occur in the future?) destinationfile has to be an absolute path name + + for ( my $i = 0; $i <= $#{$linksarrayref}; $i++ ) + { + my $onelink = ${$linksarrayref}[$i]; + my $styles = $onelink->{'Styles'}; + + if ( $styles =~ /\bRELATIVE\b/ ) + { + # ToDo: This is only a simple not sufficient mechanism + + my $destination = $onelink->{'destination'}; + my $destinationfile = $onelink->{'destinationfile'}; + + my $destinationpath = $destination; + + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destinationpath); + + my $destinationfilepath = $destinationfile; + + # it is possible, that the destinationfile is no longer part of the files collector + if ($destinationfilepath) { installer::pathanalyzer::get_path_from_fullqualifiedname(\$destinationfilepath); } + else { $destinationfilepath = ""; } + + if ( $destinationpath eq $destinationfilepath ) + { + # link and file are in the same directory + # Therefore the path of the file can be removed + + my $newdestinationfile = $destinationfile; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$newdestinationfile); + + $onelink->{'destinationfile'} = $newdestinationfile; + } + } + } +} + +######################################################################## +# This function is a helper of function "assigning_modules_to_items" +######################################################################## + +sub insert_for_item ($$$) +{ + my ($hash, $item, $id) = @_; + + if (!defined $hash->{$item}) + { + my @gids = (); + $hash->{$item} = \@gids; + } + my $gid_list = $hash->{$item}; + push @{$gid_list}, $id; + $hash->{$item} = $gid_list; +} + +sub build_modulegids_table +{ + my ($modulesref, $itemname) = @_; + + my %module_lookup_table = (); + + # build map of item names to list of respective module gids + # containing these items + for my $onemodule (@{$modulesref}) + { + next if ( ! defined $onemodule->{$itemname} ); + # these are the items contained in this module + # eg. Files = (gid_a_b_c,gid_d_e_f) + my $module_gids = $onemodule->{$itemname}; + + # prune outer brackets + $module_gids =~ s|^\s*\(||g; + $module_gids =~ s|\)\s*$||g; + for my $id (split (/,/, $module_gids)) + { + chomp $id; + insert_for_item(\%module_lookup_table, lc ($id), $onemodule->{'gid'}); + } + } + + return \%module_lookup_table; +} + +######################################################################## +# Items like files do not know their modules +# This function is a helper of function "assigning_modules_to_items" +######################################################################## + +sub get_string_of_modulegids_for_itemgid +{ + my ($module_lookup_table, $modulesref, $itemgid, $itemname) = @_; + + my $allmodules = ""; + my $haslanguagemodule = 0; + my %foundmodules = (); + + my $gid_list = $module_lookup_table->{lc($itemgid)}; + + for my $gid (@{$gid_list}) + { + $foundmodules{$gid} = 1; + $allmodules = $allmodules . "," . $gid; + # Is this module a language module? This info should be stored at the file. + if ( exists($installer::globals::alllangmodules{$gid}) ) { $haslanguagemodule = 1; } + } + + $allmodules =~ s/^\s*\,//; # removing leading comma + + # Check: All modules or no module must have flag LANGUAGEMODULE + if ( $haslanguagemodule ) + { + my $isreallylanguagemodule = _key_in_a_is_also_key_in_b(\%foundmodules, \%installer::globals::alllangmodules); + if ( ! $isreallylanguagemodule ) { installer::exiter::exit_program("ERROR: \"$itemgid\" is assigned to modules with flag \"LANGUAGEMODULE\" and also to modules without this flag! Modules: $allmodules", "get_string_of_modulegids_for_itemgid"); } + } + + return ($allmodules, $haslanguagemodule); +} + +######################################################## +# Items like files do not know their modules +# This function add the {'modules'} to these items +######################################################## + +sub assigning_modules_to_items +{ + my ($modulesref, $itemsref, $itemname) = @_; + + my $infoline = ""; + my $languageassignmenterror = 0; + my @languageassignmenterrors = (); + + my $module_lookup_table = build_modulegids_table($modulesref, $itemname); + + for my $oneitem (@{$itemsref}) + { + my $itemgid = $oneitem->{'gid'}; + + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + if (( $itemname eq "Dirs" ) && ( ! ( $styles =~ /\bCREATE\b/ ))) { next; } + + if ( $itemgid eq "" ) + { + installer::exiter::exit_program("ERROR in item collection: No gid for item $oneitem->{'Name'}", "assigning_modules_to_items"); + } + + # every item can belong to many modules + + my ($modulegids, $haslanguagemodule) = get_string_of_modulegids_for_itemgid($module_lookup_table, $modulesref, $itemgid, $itemname); + + if ($modulegids eq "") + { + installer::exiter::exit_program("ERROR in file collection: No module found for $itemname $itemgid", "assigning_modules_to_items"); + } + + $oneitem->{'modules'} = $modulegids; + $oneitem->{'haslanguagemodule'} = $haslanguagemodule; + + # Important check: "ismultilingual" and "haslanguagemodule" must have the same value ! + if (( $oneitem->{'ismultilingual'} ) && ( ! $oneitem->{'haslanguagemodule'} )) + { + $infoline = "Error: \"$oneitem->{'gid'}\" is multi lingual, but not in language pack (Assigned module: $modulegids)!\n"; + push( @installer::globals::globallogfileinfo, $infoline); + push( @languageassignmenterrors, $infoline ); + $languageassignmenterror = 1; + } + if (( $oneitem->{'haslanguagemodule'} ) && ( ! $oneitem->{'ismultilingual'} )) + { + $infoline = "Error: \"$oneitem->{'gid'}\" is in language pack, but not multi lingual (Assigned module: $modulegids)!\n"; + push( @installer::globals::globallogfileinfo, $infoline); + push( @languageassignmenterrors, $infoline ); + $languageassignmenterror = 1; + } + } + + if ($languageassignmenterror) + { + for ( my $i = 0; $i <= $#languageassignmenterrors; $i++ ) { print "$languageassignmenterrors[$i]"; } + installer::exiter::exit_program("ERROR: Incorrect assignments for language packs.", "assigning_modules_to_items"); + } + +} + +################################################################################################# +# Root path (for instance /opt/openofficeorg20) needs to be added to directories, files and links +################################################################################################# + +sub add_rootpath_to_directories +{ + my ($dirsref, $rootpath) = @_; + + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + my $dir = ""; + + if ( $onedir->{'Dir'} ) { $dir = $onedir->{'Dir'}; } + + if (!($dir =~ /\bPREDEFINED_/ )) + { + my $hostname = $onedir->{'HostName'}; + $hostname = $rootpath . $installer::globals::separator . $hostname; + $onedir->{'HostName'} = $hostname; + } + + # added + + if ( $dir =~ /\bPREDEFINED_PROGDIR\b/ ) + { + my $hostname = $onedir->{'HostName'}; + if ( $hostname eq "" ) { $onedir->{'HostName'} = $rootpath; } + else { $onedir->{'HostName'} = $rootpath . $installer::globals::separator . $hostname; } + } + } +} + +sub add_rootpath_to_files +{ + my ($filesref, $rootpath) = @_; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + my $destination = $onefile->{'destination'}; + $destination = $rootpath . $installer::globals::separator . $destination; + $onefile->{'destination'} = $destination; + } +} + +sub add_rootpath_to_links +{ + my ($linksref, $rootpath) = @_; + + for ( my $i = 0; $i <= $#{$linksref}; $i++ ) + { + my $onelink = ${$linksref}[$i]; + my $styles = $onelink->{'Styles'}; + + my $destination = $onelink->{'destination'}; + $destination = $rootpath . $installer::globals::separator . $destination; + $onelink->{'destination'} = $destination; + + if (!($styles =~ /\bRELATIVE\b/ )) # for absolute links + { + my $destinationfile = $onelink->{'destinationfile'}; + $destinationfile = $rootpath . $installer::globals::separator . $destinationfile; + $onelink->{'destinationfile'} = $destinationfile; + } + } +} + +################################################################################# +# Collecting all parent gids +################################################################################# + +sub collect_all_parent_feature +{ + my ($modulesref) = @_; + + my @allparents = (); + + my $found_root_module = 0; + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + + my $parentgid = ""; + if ( $onefeature->{'ParentID'} ) + { + $parentgid = $onefeature->{'ParentID'}; + } + + if ( $parentgid ne "" ) + { + if (! grep {$_ eq $parentgid} @allparents) + { + push(@allparents, $parentgid); + } + } + + # Setting the global root module + + if ( $parentgid eq "" ) + { + if ( $found_root_module ) { installer::exiter::exit_program("ERROR: Only one module without ParentID or with empty ParentID allowed ($installer::globals::rootmodulegid, $onefeature->{'gid'}).", "collect_all_parent_feature"); } + $installer::globals::rootmodulegid = $onefeature->{'gid'}; + $found_root_module = 1; + $infoline = "Setting Root Module: $installer::globals::rootmodulegid\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + + if ( ! $found_root_module ) { installer::exiter::exit_program("ERROR: Could not define root module. No module without ParentID or with empty ParentID exists.", "collect_all_parent_feature"); } + + } + + return \@allparents; +} + +################################################################################# +# Checking for every feature, whether it has children +################################################################################# + +sub set_children_flag +{ + my ($modulesref) = @_; + + my $allparents = collect_all_parent_feature($modulesref); + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + my $gid = $onefeature->{'gid'}; + + # is this gid a parent? + + if ( grep {$_ eq $gid} @{$allparents} ) + { + $onefeature->{'has_children'} = 1; + } + else + { + $onefeature->{'has_children'} = 0; + } + } +} + +################################################################################# +# All modules, that use a template module, do now get the assignments of +# the template module. +################################################################################# + +sub resolve_assigned_modules +{ + my ($modulesref) = @_; + + # collecting all template modules + + my %directaccess = (); + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + my $styles = ""; + if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } + if ( $styles =~ /\bTEMPLATEMODULE\b/ ) { $directaccess{$onefeature->{'gid'}} = $onefeature; } + + # also looking for module with flag ROOT_BRAND_PACKAGE, to save is for further usage + if ( $styles =~ /\bROOT_BRAND_PACKAGE\b/ ) + { + $installer::globals::rootbrandpackage = $onefeature->{'gid'}; + $installer::globals::rootbrandpackageset = 1; + } + } + + # looking, where template modules are assigned + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + if ( $onefeature->{'Assigns'} ) + { + my $templategid = $onefeature->{'Assigns'}; + + if ( ! exists($directaccess{$templategid}) ) + { + installer::exiter::exit_program("ERROR: Did not find definition of assigned template module \"$templategid\"", "resolve_assigned_modules"); + } + + # Currently no merging of Files, Dirs, ... + # This has to be included here, if it is required + my @items_at_modules = ("Files", "Dirs", "Unixlinks"); + for my $item (@items_at_modules) + { + if ( exists($directaccess{$templategid}->{$item}) ) { $onefeature->{$item} = $directaccess{$templategid}->{$item}; } + } + } + } +} + +################################################################################# +# Removing the template modules from the list, after all +# assignments are transferred to the "real" modules. +################################################################################# + +sub remove_template_modules +{ + my ($modulesref) = @_; + + my @modules = (); + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + my $styles = ""; + if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } + if ( $styles =~ /\bTEMPLATEMODULE\b/ ) { next; } + + push(@modules, $onefeature); + } + + return \@modules; +} + +################################################################################# +# Collecting all modules with flag LANGUAGEMODULE in a global +# collector. +################################################################################# + +sub collect_all_languagemodules +{ + my ($modulesref) = @_; + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + my $styles = ""; + if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } + if ( $styles =~ /\bLANGUAGEMODULE\b/ ) + { + if ( ! exists($onefeature->{'Language'}) ) { installer::exiter::exit_program("ERROR: \"$onefeature->{'gid'}\" has flag LANGUAGEMODULE, but does not know its language!", "collect_all_languagemodules"); } + $installer::globals::alllangmodules{$onefeature->{'gid'}} = $onefeature->{'Language'}; + # Collecting also the english names, that are used for nsis unpack directory for language packs + my $lang = $onefeature->{'Language'}; + my $name = ""; + foreach my $localkey ( keys %{$onefeature} ) + { + if ( $localkey =~ /^\s*Name\s*\(\s*en-US\s*\)\s*$/ ) + { + $installer::globals::all_english_languagestrings{$lang} = $onefeature->{$localkey}; + } + } + } + } +} + +################################################################################# +# Selecting from all collected english language strings those, that are really +# required in this installation set. +################################################################################# + +sub select_required_language_strings +{ + my ($modulesref) = @_; + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + my $styles = ""; + if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } + if ( $styles =~ /\bLANGUAGEMODULE\b/ ) + { + if ( ! exists($onefeature->{'Language'}) ) { installer::exiter::exit_program("ERROR: \"$onefeature->{'gid'}\" has flag LANGUAGEMODULE, but does not know its language!", "select_required_language_strings"); } + my $lang = $onefeature->{'Language'}; + + if (( exists($installer::globals::all_english_languagestrings{$lang}) ) && ( ! exists($installer::globals::all_required_english_languagestrings{$lang}) )) + { + $installer::globals::all_required_english_languagestrings{$lang} = $installer::globals::all_english_languagestrings{$lang}; + } + } + } +} + +################################################ +# Controlling that all keys in hash A are +# also key in hash B. +################################################ + +sub _key_in_a_is_also_key_in_b +{ + my ( $hashref_a, $hashref_b) = @_; + + my $returnvalue = 1; + + my $key; + foreach $key ( keys %{$hashref_a} ) + { + if ( ! exists($hashref_b->{$key}) ) + { + print "*****\n"; + foreach $keyb ( keys %{$hashref_b} ) { print "$keyb : $hashref_b->{$keyb}\n"; } + print "*****\n"; + $returnvalue = 0; + } + } + + return $returnvalue; +} + +1; diff --git a/solenv/bin/modules/installer/setupscript.pm b/solenv/bin/modules/installer/setupscript.pm new file mode 100644 index 000000000..6eefe01f0 --- /dev/null +++ b/solenv/bin/modules/installer/setupscript.pm @@ -0,0 +1,486 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::setupscript; + +use base 'Exporter'; + +use installer::exiter; +use installer::globals; +use installer::logger qw(globallog); +use installer::remover; +use installer::scriptitems; +use installer::ziplist; + +our @EXPORT_OK = qw( + add_installationobject_to_variables + add_lowercase_productname_setupscriptvariable + add_predefined_folder + get_all_items_from_script + get_all_scriptvariables_from_installation_object + prepare_non_advertised_files + replace_all_setupscriptvariables_in_script + replace_preset_properties + resolve_lowercase_productname_setupscriptvariable + set_setupscript_name +); + +####################################################### +# Set setup script name, if not defined as parameter +####################################################### + +sub set_setupscript_name +{ + my ( $allsettingsarrayref, $includepatharrayref ) = @_; + + my $scriptnameref = installer::ziplist::getinfofromziplist($allsettingsarrayref, "script"); + + my $scriptname = $$scriptnameref; + + if ( $scriptname eq "" ) # not defined on command line and not in product list + { + installer::exiter::exit_program("ERROR: Setup script not defined on command line (-l) and not in product list!", "set_setupscript_name"); + } + + if ( $installer::globals::os eq 'WNT') + { + $scriptname .= ".inf"; + } + else + { + $scriptname .= ".ins"; + } + + # and now the complete path for the setup script is needed + # The log file cannot be used, because this is the language independent section + + $scriptnameref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$scriptname, $includepatharrayref, 1); + + $installer::globals::setupscriptname = $$scriptnameref; + + if ( $installer::globals::setupscriptname eq "" ) + { + installer::exiter::exit_program("ERROR: Script $scriptname not found!", "set_setupscript_name"); + } +} + +##################################################################### +# Reading script variables from installation object of script file +##################################################################### + +sub get_all_scriptvariables_from_installation_object +{ + my ($scriptref) = @_; + + my @installobjectvariables; + + for ( my $i = 0; $i <= $#{$scriptref}; $i++ ) + { + my $line = ${$scriptref}[$i]; + + if ( $line =~ /^\s*Installation\s+\w+\s*$/ ) # should be the first line + { + my $counter = $i+1; + my $installline = ${$scriptref}[$counter]; + + while (!($installline =~ /^\s*End\s*$/ )) + { + if ( $installline =~ /^\s*(\w+)\s+\=\s*(.*?)\s*\;\s*$/ ) + { + my $key = $1; + my $value = $2; + + # removing leading and ending " in $value + + if ( $value =~ /^\s*\"(.*)\"\s*$/ ) + { + $value = $1; + } + + $key = "\%" . uc($key); # $key is %PRODUCTNAME + + my $input = $key . " " . $value . "\n"; # $key can only be the first word + + push(@installobjectvariables ,$input); + } + + $counter++; + $installline = ${$scriptref}[$counter]; + } + } + + last; # not interesting after installation object + } + + return \@installobjectvariables; +} + +###################################################################### +# Including LCPRODUCTNAME into the array +###################################################################### + +sub add_lowercase_productname_setupscriptvariable +{ + my ( $variablesref ) = @_; + + for ( my $j = 0; $j <= $#{$variablesref}; $j++ ) + { + my $variableline = ${$variablesref}[$j]; + + my ($key, $value); + + if ( $variableline =~ /^\s*\%(\w+?)\s+(.*?)\s*$/ ) + { + $key = $1; + $value = $2; + + if ( $key eq "PRODUCTNAME" ) + { + my $newline = "\%LCPRODUCTNAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + my $original = $value; + $value =~ s/\s*//g; + $newline = "\%ONEWORDPRODUCTNAME " . $value . "\n"; + push(@{$variablesref} ,$newline); + $newline = "\%LCONEWORDPRODUCTNAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $value = $original; + $value =~ s/\s*$//g; + $value =~ s/^\s*//g; + $value =~ s/ /\%20/g; + $newline = "\%MASKEDPRODUCTNAME " . $value . "\n"; + push(@{$variablesref} ,$newline); + $value = $original; + $value =~ s/\s/\_/g; + $newline = "\%UNIXPRODUCTNAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $newline = "\%SYSTEMINTUNIXPACKAGENAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $newline = "\%UNIXPACKAGENAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $value = $original; + $value =~ s/\s/\_/g; + $value =~ s/\.//g; + $newline = "\%WITHOUTDOTUNIXPRODUCTNAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $newline = "\%WITHOUTDOTUNIXPACKAGENAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $newline = "\%SOLARISBRANDPACKAGENAME " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + $value = $original; + } + elsif ( $key eq "PRODUCTEXTENSION" ) + { + my $newline = "\%LCPRODUCTEXTENSION " . lc($value) . "\n"; + push(@{$variablesref} ,$newline); + } + elsif ( $key eq "PRODUCTVERSION" ) + { + $value =~ s/\.//g; + my $newline = "\%WITHOUTDOTPRODUCTVERSION " . $value . "\n"; + push(@{$variablesref} ,$newline); + } + } + } +} + +###################################################################### +# Resolving the new introduced lowercase script variables +###################################################################### + +sub resolve_lowercase_productname_setupscriptvariable +{ + my ( $variablesref ) = @_; + + my %variables = (); + + # First step: Collecting variables + + for ( my $j = 0; $j <= $#{$variablesref}; $j++ ) + { + my $variableline = ${$variablesref}[$j]; + + my ($key, $value); + + if ( $variableline =~ /^\s*\%(\w+?)\s+(.*?)\s*$/ ) + { + $key = $1; + $value = $2; + $variables{$key} = $value; + } + } + + # Second step: Resolving variables + + for ( my $j = 0; $j <= $#{$variablesref}; $j++ ) + { + if ( ${$variablesref}[$j] =~ /\$\{(.*?)\}/ ) + { + my $key = $1; + ${$variablesref}[$j] =~ s/\$\{\Q$key\E\}/$variables{$key}/g; + } + } + +} + +###################################################################### +# Replacing all setup script variables inside the setup script file +###################################################################### + +sub replace_all_setupscriptvariables_in_script +{ + my ( $scriptref, $variablesref ) = @_; + + globallog("Replacing variables in setup script (start)"); + + # make hash of variables to be substituted if they appear in the script + my %subs; + for ( my $j = 0; $j <= $#{$variablesref}; $j++ ) + { + my $variableline = ${$variablesref}[$j]; + + if ( $variableline =~ /^\s*(\%\w+?)\s+(.*?)\s*$/ ) + { + $subs{$1}= $2; + } + } + + # This is far faster than running a regexp for each line + my $bigstring = ''; + for my $line (@{$scriptref}) { $bigstring = $bigstring . $line; } + + foreach my $key (sort { length ($b) <=> length ($a) } keys %subs) + { + # Attention: It must be possible to substitute "%PRODUCTNAMEn", "%PRODUCTNAME%PRODUCTVERSIONabc" + my $value = $subs{$key}; + $bigstring =~ s/$key/$value/g; + } + + my @newlines = split /\n/, $bigstring; + $scriptref = \@newlines; + + # now check for any mis-named '%' variables that we have left + my $num = 0; + for my $check (@newlines) + { + $num++; + if ( $check =~ /^.*\%\w+.*$/ ) + { + if (( $check =~ /%1/ ) || ( $check =~ /%2/ ) || ( $check =~ /%verify/ )) { next; } + my $infoline = "WARNING: mis-named or un-known '%' variable in setup script at line $num:\n$check\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + } + + globallog("Replacing variables in setup script (end)"); + + return $scriptref; +} + +####################################################################### +# Collecting all items of the type "searchitem" from the setup script +####################################################################### + +sub get_all_items_from_script +{ + my ($scriptref, $searchitem) = @_; + + my @allitemarray = (); + + my ($itemkey, $itemvalue); + + for ( my $i = 0; $i <= $#{$scriptref}; $i++ ) + { + my $line = ${$scriptref}[$i]; + + next unless ($line =~ /^\s*\Q$searchitem\E\s+(\S+)\s*$/); + my $gid = $1; + + my %oneitemhash = (); + my $ismultilang = 0; + + $oneitemhash{'gid'} = $gid; + + while (!( $line =~ /^\s*End\s*$/ )) + { + if ( $i >= $#{$scriptref} ) { + installer::exiter::exit_program("Invalid setup script file. End of file reached before 'End' line of '$searchitem' section.", "get_all_items_from_script"); + } + $line = ${$scriptref}[++$i]; + + if ( $line =~ /^\s*(.+?)\=\s*(.+?)\;\s*$/ ) # only oneliner! + { + $itemkey = $1; + $itemvalue = $2; + + $itemkey =~ s/\s+$//; + $itemvalue =~ s/\s+$//; + + installer::remover::remove_leading_and_ending_quotationmarks(\$itemvalue); + + $oneitemhash{$itemkey} = $itemvalue; + + $ismultilang ||= $itemkey =~ /^\S+\s+\(\S+\)$/; + } + elsif (($searchitem eq "Module") && + ($line =~ /^\s*.+?\s*\=\s*\(/) && + (!($line =~ /\)\;\s*$/))) # more than one line, for instance files at modules! + { + $line =~ /^\s*(.+?)\s*\=\s*(.+?)\s*$/; # the first line + $itemkey = $1; + $itemvalue = $2; + + # collecting the complete itemvalue + do + { + if ( $i >= $#{$scriptref} ) { + installer::exiter::exit_program("Invalid setup script file. Premature end of file.", "get_all_items_from_script"); + } + $line = ${$scriptref}[++$i]; + installer::remover::remove_leading_and_ending_whitespaces(\$line); + $itemvalue .= $line; + } while (!($line =~ /\)\;\s*$/)); + + # removing ending ";" + $itemvalue =~ s/\;\s*$//; + + $oneitemhash{$itemkey} = $itemvalue; + + $ismultilang ||= $itemkey =~ /^\S+\s+\(\S+\)$/; + } + } + + $oneitemhash{'ismultilingual'} = $ismultilang+0; + + push(@allitemarray, \%oneitemhash); + } + + return \@allitemarray; +} + +###################################################################### +# Collecting all folder at folderitems, that are predefined values +# For example: PREDEFINED_AUTOSTART +###################################################################### + +sub add_predefined_folder +{ + my ( $folderitemref, $folderref ) = @_; + + for my $folderid ( map { $_->{FolderID} } @{$folderitemref} ) { + # FIXME: Anchor to start of line? + next unless ( $folderid =~ /PREDEFINED_/ ); + next if grep { $_->{gid} eq $folderid } @{$folderref}; + + push @{$folderref}, { + ismultilingual => 0, + Name => "", + gid => $folderid, + }; + } +} + +##################################################################################### +# If folderitems are non-advertised, the component needs to have a registry key +# below HKCU as key path. Therefore it is required, to mark the file belonging +# to a non-advertised shortcut, that a special userreg_xxx registry key can be +# created during packing process. +##################################################################################### + +sub prepare_non_advertised_files +{ + my ( $folderitemref, $filesref ) = @_; + + for ( my $i = 0; $i <= $#{$folderitemref}; $i++ ) + { + my $folderitem = ${$folderitemref}[$i]; + my $styles = ""; + if ( $folderitem->{'Styles'} ) { $styles = $folderitem->{'Styles'}; } + + if ( $styles =~ /\bNON_ADVERTISED\b/ ) + { + my $fileid = $folderitem->{'FileID'}; + if ( $folderitem->{'ComponentIDFile'} ) { $fileid = $folderitem->{'ComponentIDFile'}; } + my $onefile = installer::worker::find_file_by_id($filesref, $fileid); + + if ( $onefile ne "" ) { $onefile->{'needs_user_registry_key'} = 1; } + else { + installer::exiter::exit_program("ERROR: Did not find FileID $fileid in file collection", "prepare_non_advertised_files"); + } + } + } +} + +##################################################################################### +# Adding all variables defined in the installation object into the hash +# of all variables from the zip list file. +# This is needed if variables are defined in the installation object, +# but not in the zip list file. +# If there is a definition in the zip list file and in the installation +# object, the installation object is more important +##################################################################################### + +sub add_installationobject_to_variables +{ + my ($allvariables, $allscriptvariablesref) = @_; + + for ( my $i = 0; $i <= $#{$allscriptvariablesref}; $i++ ) + { + my $line = ${$allscriptvariablesref}[$i]; + + if ( $line =~ /^\s*\%(\w+)\s+(.*?)\s*$/ ) + { + my $key = $1; + my $value = $2; + + $allvariables->{$key} = $value; # overwrite existing values from zip.lst + } + } +} + +##################################################################################### +# Some properties are created automatically. It should be possible to +# overwrite them, with PRESET properties. For example UNIXPRODUCTNAME +# with PRESETUNIXPRODUCTNAME, if this is defined and the automatic process +# does not deliver the desired results. +##################################################################################### + +sub replace_preset_properties +{ + my ($allvariables) = @_; + + # SOLARISBRANDPACKAGENAME + # needs to be replaced by + # PRESETSOLARISBRANDPACKAGENAME + + my @presetproperties = (); + push(@presetproperties, "SOLARISBRANDPACKAGENAME"); + push(@presetproperties, "SYSTEMINTUNIXPACKAGENAME"); + + + foreach $property ( @presetproperties ) + { + my $presetproperty = "PRESET" . $property; + if (( exists($allvariables->{$presetproperty}) ) && ( $allvariables->{$presetproperty} ne "" )) + { + $allvariables->{$property} = $allvariables->{$presetproperty}; + } + } +} + +1; diff --git a/solenv/bin/modules/installer/simplepackage.pm b/solenv/bin/modules/installer/simplepackage.pm new file mode 100644 index 000000000..e681a00ff --- /dev/null +++ b/solenv/bin/modules/installer/simplepackage.pm @@ -0,0 +1,732 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::simplepackage; + +use Cwd; +use File::Copy; +use installer::download; +use installer::exiter; +use installer::globals; +use installer::logger; +use installer::strip qw(strip_libraries); +use installer::systemactions; +use installer::worker; + +#################################################### +# Checking if the simple packager is required. +# This can be achieved by setting the global +# variable SIMPLE_PACKAGE in *.lst file or by +# setting the environment variable SIMPLE_PACKAGE. +#################################################### + +sub check_simple_packager_project +{ + my ( $allvariables ) = @_; + + if (( $installer::globals::packageformat eq "installed" ) || + ( $installer::globals::packageformat eq "archive" )) + { + $installer::globals::is_simple_packager_project = 1; + $installer::globals::patch_user_dir = 1; + } + elsif( $installer::globals::packageformat eq "dmg" ) + { + $installer::globals::is_simple_packager_project = 1; + } +} + +#################################################### +# Detecting the directory with extensions +#################################################### + +sub get_extensions_dir +{ + my ( $subfolderdir ) = @_; + + my $extensiondir = $subfolderdir . $installer::globals::separator; + if ( $installer::globals::officedirhostname ne "" ) { $extensiondir = $extensiondir . $installer::globals::officedirhostname . $installer::globals::separator; } + my $extensionsdir = $extensiondir . "share" . $installer::globals::separator . "extensions"; + + return $extensionsdir; +} + +################################################################## +# Collecting all identifier from ulf file +################################################################## + +sub get_identifier +{ + my ( $translationfile ) = @_; + + my @identifier = (); + + for ( my $i = 0; $i <= $#{$translationfile}; $i++ ) + { + my $oneline = ${$translationfile}[$i]; + + if ( $oneline =~ /^\s*\[(.+)\]\s*$/ ) + { + my $identifier = $1; + push(@identifier, $identifier); + } + } + + return \@identifier; +} + +############################################################## +# Returning the complete block in all languages +# for a specified string +############################################################## + +sub get_language_block_from_language_file +{ + my ($searchstring, $languagefile) = @_; + + my @language_block = (); + + for ( my $i = 0; $i <= $#{$languagefile}; $i++ ) + { + if ( ${$languagefile}[$i] =~ /^\s*\[\s*$searchstring\s*\]\s*$/ ) + { + my $counter = $i; + + push(@language_block, ${$languagefile}[$counter]); + $counter++; + + while (( $counter <= $#{$languagefile} ) && (!( ${$languagefile}[$counter] =~ /^\s*\[/ ))) + { + push(@language_block, ${$languagefile}[$counter]); + $counter++; + } + + last; + } + } + + return \@language_block; +} + +############################################################## +# Returning a specific language string from the block +# of all translations +############################################################## + +sub get_language_string_from_language_block +{ + my ($language_block, $language) = @_; + + my $newstring = ""; + + for ( my $i = 0; $i <= $#{$language_block}; $i++ ) + { + if ( ${$language_block}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + last; + } + } + + if ( $newstring eq "" ) + { + $language = "en-US"; # defaulting to english + + for ( my $i = 0; $i <= $#{$language_block}; $i++ ) + { + if ( ${$language_block}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + last; + } + } + } + + return $newstring; +} + +######################################################################## +# Localizing the script for the Mac Language Pack installer +######################################################################## + +sub localize_scriptfile +{ + my ($scriptfile, $translationfile, $languagestringref) = @_; + + my $onelanguage = $$languagestringref; + if ( $onelanguage =~ /^\s*(.*?)_/ ) { $onelanguage = $1; } + + # Analyzing the ulf file, collecting all Identifier + my $allidentifier = get_identifier($translationfile); + + for ( my $i = 0; $i <= $#{$allidentifier}; $i++ ) + { + my $identifier = ${$allidentifier}[$i]; + my $language_block = get_language_block_from_language_file($identifier, $translationfile); + my $newstring = get_language_string_from_language_block($language_block, $onelanguage); + + # removing mask + $newstring =~ s/\\\'/\'/g; + + replace_one_variable_in_shellscript($scriptfile, $newstring, $identifier); + } +} + +################################################################################# +# Replacing one variable in Mac shell script +################################################################################# + +sub replace_one_variable_in_shellscript +{ + my ($scriptfile, $variable, $searchstring) = @_; + + for ( my $i = 0; $i <= $#{$scriptfile}; $i++ ) + { + ${$scriptfile}[$i] =~ s/\[$searchstring\]/$variable/g; + } +} + +############################################# +# Replacing variables in Mac shell script +############################################# + +sub replace_variables_in_scriptfile +{ + my ($scriptfile, $volume_name, $volume_name_app, $allvariables) = @_; + + replace_one_variable_in_shellscript($scriptfile, $volume_name, "FULLPRODUCTNAME" ); + replace_one_variable_in_shellscript($scriptfile, $volume_name_app, "FULLAPPPRODUCTNAME" ); + replace_one_variable_in_shellscript($scriptfile, $allvariables->{'PRODUCTNAME'}, "PRODUCTNAME" ); + replace_one_variable_in_shellscript($scriptfile, $allvariables->{'PRODUCTVERSION'}, "PRODUCTVERSION" ); + + my $scriptname = $allvariables->{'BUNDLEIDENTIFIER'}; + + replace_one_variable_in_shellscript($scriptfile, $scriptname, "SEARCHSCRIPTNAME" ); +} + +############################################# +# Creating the "simple" package. +# "zip" for Windows +# "tar.gz" for all other platforms +# additionally "dmg" on macOS +############################################# + +sub create_package +{ + my ( $installdir, $archivedir, $packagename, $allvariables, $includepatharrayref, $languagestringref, $format ) = @_; + + installer::logger::print_message( "... creating $installer::globals::packageformat file ...\n" ); + installer::logger::include_header_into_logfile("Creating $installer::globals::packageformat file:"); + + # moving dir into temporary directory + my $pid = $$; # process id + my $tempdir = $installdir . "_temp" . "." . $pid; + my $systemcall = ""; + my $from = ""; + my $makesystemcall = 1; + my $return_to_start = 0; + installer::systemactions::rename_directory($installdir, $tempdir); + + # creating new directory with original name + installer::systemactions::create_directory($archivedir); + + my $archive = $archivedir . $installer::globals::separator . $packagename . $format; + + if ( $archive =~ /zip$/ ) + { + $from = cwd(); + $return_to_start = 1; + chdir($tempdir); + $systemcall = "$installer::globals::zippath -qr $archive ."; + + # Using Archive::Zip fails because of very long path names below "share/uno_packages/cache" + # my $packzip = Archive::Zip->new(); + # $packzip->addTree("."); # after changing into $tempdir + # $packzip->writeToFileNamed($archive); + # $makesystemcall = 0; + } + elsif ( $archive =~ /dmg$/ ) + { + my $folder = (( -l "$tempdir/$packagename/Applications" ) or ( -l "$tempdir/$packagename/opt" )) ? $packagename : "\."; + + if ( $allvariables->{'PACK_INSTALLED'} ) { + $folder = $packagename; + } + + my $volume_name = $allvariables->{'PRODUCTNAME'}; + my $volume_name_classic = $allvariables->{'PRODUCTNAME'} . ' ' . $allvariables->{'PRODUCTVERSION'}; + my $volume_name_classic_app = $volume_name; # "app" should not contain version number + if ( $allvariables->{'DMG_VOLUMEEXTENSION'} ) { + $volume_name = $volume_name . ' ' . $allvariables->{'DMG_VOLUMEEXTENSION'}; + $volume_name_classic = $volume_name_classic . ' ' . $allvariables->{'DMG_VOLUMEEXTENSION'}; + $volume_name_classic_app = $volume_name_classic_app . ' ' . $allvariables->{'DMG_VOLUMEEXTENSION'}; + } + + my $sla = 'sla.r'; + my $ref = ""; + + if ( ! $allvariables->{'HIDELICENSEDIALOG'} ) + { + installer::scriptitems::get_sourcepath_from_filename_and_includepath( \$sla, $includepatharrayref, 0); + } + + my $localtempdir = $tempdir; + + if (( $installer::globals::languagepack ) || ( $installer::globals::helppack )) + { + # LanguagePack and HelpPack files are collected in $srcfolder, packaged into + # tarball.tar.bz2 and finally the Language Pack.app is assembled in $appfolder + $localtempdir = "$tempdir/$packagename"; + my $srcfolder = $localtempdir . "/" . $volume_name_classic_app . "\.app"; + + $volume_name .= " " . $$languagestringref . " Language Pack"; + $volume_name_classic .= " Language Pack"; + $volume_name_classic_app .= " Language Pack"; + + my $appfolder = $localtempdir . "/" . $volume_name_classic_app . "\.app"; + my $contentsfolder = $appfolder . "/Contents"; + my $tarballname = "tarball.tar.bz2"; + + my $localfrom = cwd(); + chdir $srcfolder; + + $systemcall = "tar -cjf $tarballname Contents/"; + + print "... $systemcall ...\n"; + my $localreturnvalue = system($systemcall); + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($localreturnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + my $sourcefile = $srcfolder . "/" . $tarballname; + my $destfile = $contentsfolder . "/Resources/" . $tarballname; + + installer::systemactions::remove_complete_directory($appfolder); + installer::systemactions::create_directory($appfolder); + installer::systemactions::create_directory($contentsfolder); + installer::systemactions::create_directory($contentsfolder . "/Resources"); + + installer::systemactions::copy_one_file($sourcefile, $destfile); + installer::systemactions::remove_complete_directory($srcfolder); + + # Copy two files into installation set next to the tar ball + # 1. "osx_install.applescript" + # 2 "OpenOffice.org Languagepack" + + my $scriptrealfilename = "osx_install.applescript"; + my $scriptfilename = ""; + if ( $installer::globals::languagepack ) { $scriptfilename = "osx_install_languagepack.applescript"; } + if ( $installer::globals::helppack ) { $scriptfilename = "osx_install_helppack.applescript"; } + my $scripthelperfilename = $ENV{'SRCDIR'} . "/setup_native/scripts/mac_install.script"; + my $scripthelperrealfilename = $volume_name_classic_app; + + # Finding both files in source tree + + my $scriptref = $ENV{'SRCDIR'} . "/setup_native/scripts/" . $scriptfilename; + if (! -f $scriptref) { installer::exiter::exit_program("ERROR: Could not find Apple script $scriptfilename ($scriptref)!", "create_package"); } + if (! -f $scripthelperfilename) { installer::exiter::exit_program("ERROR: Could not find Apple script $scripthelperfilename!", "create_package"); } + + $scriptfilename = $contentsfolder . "/Resources/" . $scriptrealfilename; + $scripthelperrealfilename = $contentsfolder . "/" . $scripthelperrealfilename; + + installer::systemactions::copy_one_file($scriptref, $scriptfilename); + installer::systemactions::copy_one_file($scripthelperfilename, $scripthelperrealfilename); + + # Replacing variables in script $scriptfilename + # Localizing script $scriptfilename + my $scriptfilecontent = installer::files::read_file($scriptfilename); + my $translationfilecontent = installer::files::read_file($installer::globals::macinstallfilename); + localize_scriptfile($scriptfilecontent, $translationfilecontent, $languagestringref); + + replace_variables_in_scriptfile($scriptfilecontent, $volume_name_classic, $volume_name_classic_app, $allvariables); + installer::files::save_file($scriptfilename, $scriptfilecontent); + + chmod 0775, $scriptfilename; + chmod 0775, $scripthelperrealfilename; + + # Copy also Info.plist and icon file + # Finding both files in source tree + my $iconfile = "ooo3_installer.icns"; + my $iconfileref = $ENV{'SRCDIR'} . "/setup_native/source/mac/" . $iconfile; + if (! -f $iconfileref) { installer::exiter::exit_program("ERROR: Could not find Apple script icon file $iconfile ($iconfileref)!", "create_package"); } + my $subdir = $contentsfolder . "/" . "Resources"; + if ( ! -d $subdir ) { installer::systemactions::create_directory($subdir); } + $destfile = $subdir . "/" . $iconfile; + installer::systemactions::copy_one_file($iconfileref, $destfile); + + my $infoplistfile = $ENV{'SRCDIR'} . "/setup_native/source/mac/Info.plist.langpack"; + if (! -f $infoplistfile) { installer::exiter::exit_program("ERROR: Could not find Apple script Info.plist: $infoplistfile!", "create_package"); } + $destfile = "$contentsfolder/Info.plist"; + # Replacing variables in Info.plist + $scriptfilecontent = installer::files::read_file($infoplistfile); + + replace_one_variable_in_shellscript($scriptfilecontent, $volume_name_classic_app, "FULLAPPPRODUCTNAME" ); # OpenOffice.org Language Pack + replace_one_variable_in_shellscript($scriptfilecontent, $ENV{'MACOSX_BUNDLE_IDENTIFIER'}, "BUNDLEIDENTIFIER" ); + installer::files::save_file($destfile, $scriptfilecontent); + + chdir $localfrom; + + if ( $ENV{'MACOSX_CODESIGNING_IDENTITY'} ) { + my $lp_sign = "codesign --verbose --sign $ENV{'MACOSX_CODESIGNING_IDENTITY'} --deep '$appfolder'" ; + my $output = `$lp_sign 2>&1`; + unless ($?) { + $infoline = "Success: \"$lp_sign\" executed successfully!\n"; + } else { + $infoline = "ERROR: Could not codesign the languagepack using \"$lp_sign\"!\n$output\n"; + } + push( @installer::globals::logfileinfo, $infoline); + } + } + elsif ($volume_name_classic_app eq 'LibreOffice' || $volume_name_classic_app eq 'LibreOfficeDev') + { + my $subdir = "$tempdir/$packagename/$volume_name_classic_app.app/Contents/Resources"; + if ( ! -d $subdir ) { installer::systemactions::create_directory($subdir); } + if ( $ENV{'MACOSX_CODESIGNING_IDENTITY'} ) + { + $systemcall = "$ENV{'SRCDIR'}/solenv/bin/macosx-codesign-app-bundle $localtempdir/$folder/$volume_name_classic_app.app"; + print "... $systemcall ...\n"; + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + my $output = `$systemcall 2>&1`; + if ($?) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n$output\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + } + elsif ($volume_name_classic_app eq 'LibreOffice SDK' || $volume_name_classic_app eq 'LibreOfficeDev SDK') + { + if ( $ENV{'MACOSX_CODESIGNING_IDENTITY'} ) + { + my $sdkbindir = "$localtempdir/$folder/$allvariables->{'PRODUCTNAME'}$allvariables->{'PRODUCTVERSION'}_SDK/bin"; + opendir(my $dh, $sdkbindir); + foreach my $sdkbinary (readdir $dh) { + next unless -f "$sdkbindir/$sdkbinary"; + $systemcall = "codesign --force --verbose --options=runtime --identifier='$ENV{MACOSX_BUNDLE_IDENTIFIER}.$sdkbinary' --sign '$ENV{MACOSX_CODESIGNING_IDENTITY}' --entitlements $ENV{BUILDDIR}/hardened_runtime.xcent $sdkbindir/$sdkbinary > /tmp/codesign_losdk_$sdkbinary.log 2>&1"; + print "... $systemcall ...\n"; + my $returnvalue = system($systemcall); + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + unlink "/tmp/codesign_losdk_$sdkbinary.log"; + } + } + closedir($dh); + } + } + my $megabytes = 1500; + $megabytes = 3000 if $ENV{'ENABLE_DEBUG'}; + + # tdf#151341 Use lzfse compression instead of bzip2 + # Several users reported that copying the LibreOffice.app package + # in the Finder from a .dmg file compressed with lzfse compression + # copies the package several times faster than from a .dmg compressed + # with bzip2 compression. + # On a mid-2015 Intel MacBook Pro running macOS, copying in the Finder + # was at least 5 times faster with lzfse than with bzip2. Also, the + # hdiutil man page as of macOS Monterey 12.6.2 has marked bzip2 as + # deprecated. lzfse is marked as supported since macOS El Capitan 10.11 + # so this change appears safe. + # The one thing that bzip2 has is better compression so a .dmg with + # bzip2 should be smaller than with lzfse. A .dmg built from a debug + # build was 262M with bzip2 and 273MB for lzfse. So it appears that + # lzfse creates .dmg files that are only 4% or 5% larger than bzip2. + $systemcall = "cd $localtempdir && hdiutil create -megabytes $megabytes -srcfolder $folder $archive -ov -fs HFS+ -volname \"$volume_name\" -format ULFO"; + if (( $ref ne "" ) && ( $$ref ne "" ) && system("hdiutil 2>&1 | grep unflatten") == 0) { + $systemcall .= " && hdiutil unflatten $archive && Rez -a $$ref -o $archive && hdiutil flatten $archive &&"; + } + } + else + { + # use fakeroot (only required for Solaris and Linux) + my $fakerootstring = ""; + if (( $installer::globals::issolarisbuild ) || ( $installer::globals::islinuxbuild )) + { + $fakerootstring = "fakeroot"; + } + + $systemcall = "cd $tempdir; $fakerootstring tar -cf - . | $installer::globals::packertool > $archive"; + } + + if ( $makesystemcall ) + { + print "... $systemcall ...\n"; + my $returnvalue = system($systemcall); + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\": $returnvalue\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + if ( $return_to_start ) { chdir($from); } + + print "... removing $tempdir ...\n"; + installer::systemactions::remove_complete_directory($tempdir); +} + +#################################################### +# Main method for creating the simple package +# installation sets +#################################################### + +sub create_simple_package +{ + my ( $filesref, $dirsref, $scpactionsref, $linksref, $unixlinksref, $loggingdir, $languagestringref, $shipinstalldir, $allsettingsarrayref, $allvariables, $includepatharrayref ) = @_; + + # Creating directories + + my $current_install_number = ""; + my $infoline = ""; + + installer::logger::print_message( "... creating installation directory ...\n" ); + installer::logger::include_header_into_logfile("Creating installation directory"); + + $installer::globals::csp_installdir = installer::worker::create_installation_directory($shipinstalldir, $languagestringref, \$current_install_number); + $installer::globals::csp_installlogdir = installer::systemactions::create_directory_next_to_directory($installer::globals::csp_installdir, "log"); + + my $installdir = $installer::globals::csp_installdir; + my $installlogdir = $installer::globals::csp_installlogdir; + + # Setting package name (similar to the download name) + my $packagename = ""; + + if ( $installer::globals::packageformat eq "archive" || + $installer::globals::packageformat eq "dmg" ) + { + $installer::globals::csp_languagestring = $$languagestringref; + + my $locallanguage = $installer::globals::csp_languagestring; + + $packagename = installer::download::set_download_filename(\$locallanguage, $allvariables); + } + + # Work around Windows problems with long pathnames (see issue 50885) by + # putting the to-be-archived installation tree into the temp directory + # instead of the module output tree (unless LOCALINSTALLDIR dictates + # otherwise, anyway); can be removed once issue 50885 is fixed: + my $tempinstalldir = $installdir; + if ( $installer::globals::iswindowsbuild && + $installer::globals::packageformat eq "archive" && + !$installer::globals::localinstalldirset ) + { + $tempinstalldir = File::Temp::tempdir; + } + + # Creating subfolder in installdir, which shall become the root of package or zip file + my $subfolderdir = ""; + if ( $packagename ne "" ) { $subfolderdir = $tempinstalldir . $installer::globals::separator . $packagename; } + else { $subfolderdir = $tempinstalldir; } + + if ( ! -d $subfolderdir ) { installer::systemactions::create_directory($subfolderdir); } + + # Create directories, copy files and ScpActions + + installer::logger::print_message( "... creating directories ...\n" ); + installer::logger::include_header_into_logfile("Creating directories:"); + + for ( my $i = 0; $i <= $#{$dirsref}; $i++ ) + { + my $onedir = ${$dirsref}[$i]; + + if ( $onedir->{'HostName'} ) + { + my $destdir = $subfolderdir . $installer::globals::separator . $onedir->{'HostName'}; + + if ( ! -d $destdir ) + { + if ( $^O =~ /cygwin/i ) # Cygwin performance check + { + $infoline = "Try to create directory $destdir\n"; + push(@installer::globals::logfileinfo, $infoline); + # Directories in $dirsref are sorted and all parents were added -> "mkdir" works without parent creation! + if ( ! ( -d $destdir )) { mkdir($destdir, 0775); } + } + else + { + installer::systemactions::create_directory_structure($destdir); + } + } + } + } + + # stripping files ?! + if (( $installer::globals::strip ) && ( ! $installer::globals::iswindowsbuild )) { strip_libraries($filesref, $languagestringref); } + + # copy Files + installer::logger::print_message( "... copying files ...\n" ); + installer::logger::include_header_into_logfile("Copying files:"); + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + if (( $onefile->{'Styles'} ) && ( $onefile->{'Styles'} =~ /\bBINARYTABLE_ONLY\b/ )) { next; } + + my $source = $onefile->{'sourcepath'}; + my $destination = $onefile->{'destination'}; + $destination = $subfolderdir . $installer::globals::separator . $destination; + + # Replacing $$ by $ is necessary to install files with $ in its name (back-masquerading) + # Otherwise, the following shell command does not work and the file list is not correct + $source =~ s/\$\$/\$/; + $destination =~ s/\$\$/\$/; + + if ( $^O =~ /cygwin/i ) # Cygwin performance, do not use copy_one_file. "chmod -R" at the end + { + my $copyreturn = copy($source, $destination); + + if ($copyreturn) + { + $infoline = "Copy: $source to $destination\n"; + $returnvalue = 1; + } + else + { + $infoline = "ERROR: Could not copy $source to $destination $!\n"; + $returnvalue = 0; + } + + push(@installer::globals::logfileinfo, $infoline); + } + else + { + installer::systemactions::copy_one_file($source, $destination); + + if ( ! $installer::globals::iswindowsbuild ) + { + # see issue 102274 + if ( $onefile->{'UnixRights'} ) + { + if ( ! -l $destination ) # that would be rather pointless + { + chmod oct($onefile->{'UnixRights'}), $destination; + } + } + } + } + } + + # creating Links + + installer::logger::print_message( "... creating links ...\n" ); + installer::logger::include_header_into_logfile("Creating links:"); + + for ( my $i = 0; $i <= $#{$linksref}; $i++ ) + { + my $onelink = ${$linksref}[$i]; + + my $destination = $onelink->{'destination'}; + $destination = $subfolderdir . $installer::globals::separator . $destination; + my $destinationfile = $onelink->{'destinationfile'}; + + my $localcall = "ln -sf \'$destinationfile\' \'$destination\' \>\/dev\/null 2\>\&1"; + system($localcall); + + $infoline = "Creating link: \"ln -sf $destinationfile $destination\"\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + for ( my $i = 0; $i <= $#{$unixlinksref}; $i++ ) + { + my $onelink = ${$unixlinksref}[$i]; + + my $target = $onelink->{'Target'}; + my $destination = $subfolderdir . $installer::globals::separator . $onelink->{'destination'}; + + my @localcall = ('ln', '-sf', $target, $destination); + system(@localcall) == 0 or die "system @localcall failed: $?"; + + $infoline = "Creating Unix link: \"@localcall\"\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + # Setting privileges for cygwin globally + + if ( $^O =~ /cygwin/i ) + { + installer::logger::print_message( "... changing privileges in $subfolderdir ...\n" ); + installer::logger::include_header_into_logfile("Changing privileges in $subfolderdir:"); + + my $localcall = "chmod -R 755 " . "\"" . $subfolderdir . "\""; + system($localcall); + } + + installer::logger::print_message( "... removing superfluous directories ...\n" ); + installer::logger::include_header_into_logfile("Removing superfluous directories:"); + + my $extensionfolder = get_extensions_dir($subfolderdir); + installer::systemactions::remove_empty_dirs_in_folder($extensionfolder); + + if ( $installer::globals::ismacbuild ) + { + installer::worker::put_scpactions_into_installset("$installdir/$packagename"); + } + + # Creating archive file + if ( $installer::globals::packageformat eq "archive" ) + { + create_package($tempinstalldir, $installdir, $packagename, $allvariables, $includepatharrayref, $languagestringref, $installer::globals::archiveformat); + } + elsif ( $installer::globals::packageformat eq "dmg" ) + { + create_package($installdir, $installdir, $packagename, $allvariables, $includepatharrayref, $languagestringref, ".dmg"); + } + + # Analyzing the log file + + installer::worker::clean_output_tree(); # removing directories created in the output tree + installer::worker::analyze_and_save_logfile($loggingdir, $installdir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number); +} + +1; + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/solenv/bin/modules/installer/strip.pm b/solenv/bin/modules/installer/strip.pm new file mode 100644 index 000000000..207497382 --- /dev/null +++ b/solenv/bin/modules/installer/strip.pm @@ -0,0 +1,136 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::strip; + +use strict; +use warnings; + +use base 'Exporter'; + +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::systemactions; + +our @EXPORT_OK = qw(strip_libraries); + +##################################################################### +# Checking whether a file has to be stripped +##################################################################### + +sub _need_to_strip +{ + my ( $filename ) = @_; + + my $strip = 0; + + # Check using the "file" command + + $filename =~ s/'/'\\''/g; + open (FILE, "file '$filename' |"); + my $fileoutput = ; + close (FILE); + + if (( $fileoutput =~ /not stripped/i ) && ( $fileoutput =~ /\bELF\b/ )) { $strip = 1; } + + return $strip +} + +##################################################################### +# Checking whether a file has to be stripped +##################################################################### + +sub _do_strip +{ + my ( $filename ) = @_; + + my $systemcall = "strip" . " " . $filename; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not strip $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "SUCCESS: Stripped library $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +##################################################################### +# Resolving all variables in the packagename +##################################################################### + +sub strip_libraries +{ + my ( $filelist, $languagestringref ) = @_; + + installer::logger::include_header_into_logfile("Stripping files:"); + + my $strippeddirbase = installer::systemactions::create_directories("stripped", $languagestringref); + + if (! grep {$_ eq $strippeddirbase} @installer::globals::removedirs) + { + push(@installer::globals::removedirs, $strippeddirbase); + } + + for ( my $i = 0; $i <= $#{$filelist}; $i++ ) + { + my $sourcefilename = ${$filelist}[$i]->{'sourcepath'}; + + if ( _need_to_strip($sourcefilename) ) + { + my $shortfilename = $sourcefilename; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$shortfilename); + + my $infoline = "Strip: $shortfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + # copy file into directory for stripped libraries + + my $onelanguage = ${$filelist}[$i]->{'specificlanguage'}; + + # files without language into directory "00" + + if ($onelanguage eq "") { $onelanguage = "00"; } + + my $strippeddir = $strippeddirbase . $installer::globals::separator . $onelanguage; + installer::systemactions::create_directory($strippeddir); # creating language specific subdirectories + + my $destfilename = $strippeddir . $installer::globals::separator . $shortfilename; + installer::systemactions::copy_one_file($sourcefilename, $destfilename); + + # change sourcepath in files collector + + ${$filelist}[$i]->{'sourcepath'} = $destfilename; + + # strip file + + _do_strip($destfilename); + } + } +} + +1; diff --git a/solenv/bin/modules/installer/systemactions.pm b/solenv/bin/modules/installer/systemactions.pm new file mode 100644 index 000000000..a33c5a957 --- /dev/null +++ b/solenv/bin/modules/installer/systemactions.pm @@ -0,0 +1,1329 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::systemactions; + +use Cwd; +use File::Copy; +use installer::converter; +use installer::exiter; +use installer::globals; +use installer::pathanalyzer; +use installer::remover; +use installer::windows::msiglobal; + +###################################################### +# Creating a new directory +###################################################### + +sub create_directory +{ + my ($directory) = @_; + + create_directory_with_privileges( $directory, "755" ); +} + +###################################################### +# Creating a new directory with defined privileges +###################################################### + +sub create_directory_with_privileges +{ + my ($directory, $privileges) = @_; + + my $returnvalue = 1; + my $infoline = ""; + my $localprivileges = oct("0".$privileges); # changes "777" to 0777 + + if (!(-d $directory)) + { + $returnvalue = mkdir($directory, $localprivileges); + + if ($returnvalue) + { + $infoline = "\nCreated directory: $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + + chmod $localprivileges, $directory; + } + else + { + # New solution in parallel packing: It is possible, that the directory now exists, although it + # was not created in this process. There is only an important error, if the directory does not + # exist now. + + $infoline = "\nDid not succeed in creating directory: \"$directory\". Further attempts will follow.\n"; + push(@installer::globals::logfileinfo, $infoline); + + if (!(-d $directory)) + { + # Problem with parallel packaging? -> Try a little harder, before exiting. + # Did someone else remove the parent directory in the meantime? + my $parentdir = $directory; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$parentdir); + if (!(-d $parentdir)) + { + $returnvalue = mkdir($directory, $localprivileges); + + if ($returnvalue) + { + $infoline = "\nAttention: Successfully created parent directory (should already be created before): $parentdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + chmod $localprivileges, $parentdir; + } + else + { + $infoline = "\nError: \"$directory\" could not be created. Even the parent directory \"$parentdir\" does not exist and could not be created.\n"; + push(@installer::globals::logfileinfo, $infoline); + if ( -d $parentdir ) + { + $infoline = "\nAttention: Finally the parent directory \"$parentdir\" exists, but I could not create it.\n"; + push(@installer::globals::logfileinfo, $infoline); + } + else + { + # Now it is time to exit, even the parent could not be created. + installer::exiter::exit_program("ERROR: Could not create parent directory \"$parentdir\"", "create_directory_with_privileges"); + } + } + } + + # At this point we have to assume, that the parent directory exist. + # Trying once more to create the desired directory + + $returnvalue = mkdir($directory, $localprivileges); + + if ($returnvalue) + { + $infoline = "\nAttention: Created directory \"$directory\" in the second try.\n"; + push(@installer::globals::logfileinfo, $infoline); + + chmod $localprivileges, $directory; + } + else + { + if ( -d $directory ) + { + $infoline = "\nAttention: Finally the directory \"$directory\" exists, but I could not create it.\n"; + push(@installer::globals::logfileinfo, $infoline); + } + else + { + # It is time to exit, even the second try failed. + installer::exiter::exit_program("ERROR: Failed to create the directory: $directory", "create_directory_with_privileges"); + } + } + } + else + { + $infoline = "\nAnother process created this directory in exactly this moment :-) : $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } + else + { + $infoline = "\nAlready existing directory, did not create: $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + + chmod $localprivileges, $directory; + } +} + +####################################################################### +# Calculating the number of languages in the string +####################################################################### + +sub get_number_of_langs +{ + my ($languagestring) = @_; + + my $number = 1; + + my $workstring = $languagestring; + + while ( $workstring =~ /^\s*(.*)_(.*?)\s*$/ ) + { + $workstring = $1; + $number++; + } + + return $number; +} + +####################################################################### +# Creating the directories, in which files are generated or unzipped +####################################################################### + +sub create_directories +{ + my ($newdirectory, $languagesref) =@_; + + $installer::globals::unpackpath =~ s/\Q$installer::globals::separator\E\s*$//; # removing ending slashes and backslashes + + my $path = ""; + + if (( $newdirectory eq "uno" ) || ( $newdirectory eq "zip" ) || ( $newdirectory eq "cab" ) || ( $newdirectory =~ /rdb\s*$/i )) # special handling for zip files, cab files and services file because of performance reasons + { + if ( $installer::globals::temppathdefined ) { $path = $installer::globals::temppath; } + else { $path = $installer::globals::unpackpath; } + $path =~ s/\Q$installer::globals::separator\E\s*$//; # removing ending slashes and backslashes + $path = $path . $installer::globals::separator; + } + else + { + $path = $installer::globals::unpackpath . $installer::globals::separator; + + # special handling, if LOCALINSTALLDIR is set + if (( $installer::globals::localinstalldirset ) && ( $newdirectory eq "install" )) + { + $installer::globals::localinstalldir =~ s/\Q$installer::globals::separator\E\s*$//; + $path = $installer::globals::localinstalldir . $installer::globals::separator; + } + } + + $infoline = "create_directories: Using $path for $newdirectory !\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($newdirectory eq "unzip" ) # special handling for common directory + { + } + else + { + my $localproductname = $installer::globals::product; + my $localproductsubdir = ""; + + if ( $installer::globals::product =~ /^\s*(.+?)\_\_(.+?)\s*$/ ) + { + $localproductname = $1; + $localproductsubdir = $2; + } + + if ( $installer::globals::languagepack ) { $path = $path . $localproductname . "_languagepack" . $installer::globals::separator; } + elsif ( $installer::globals::helppack ) { $path = $path . $localproductname . "_helppack" . $installer::globals::separator; } + else { $path = $path . $localproductname . $installer::globals::separator; } + + create_directory($path); + + if ( $localproductsubdir ) + { + $path = $path . $localproductsubdir . $installer::globals::separator; + create_directory($path); + } + + $path = $path . $installer::globals::installertypedir . $installer::globals::separator; + create_directory($path); + + $path = $path . $newdirectory . $installer::globals::separator; + create_directory($path); + + my $locallanguagesref = ""; + + if ( $$languagesref ) { $locallanguagesref = $$languagesref; } + + if ($newdirectory eq "install" && $installer::globals::ooodownloadfilename ne "" ) + { + # put packages into versioned path; needed only on linux (fdo#30837) + $path = $path . "$installer::globals::ooodownloadfilename" . $installer::globals::separator; + create_directory($path); + } + else + { + if ($locallanguagesref ne "") # this will be a path like "01_49", for Profiles and ConfigurationFiles, idt-Files + { + + my $languagestring = $$languagesref; + + if (length($languagestring) > $installer::globals::max_lang_length ) + { + my $number_of_languages = get_number_of_langs($languagestring); + #replace this in the same it was done in installer/windows/directory.pm + #chomp(my $shorter = `echo $languagestring | $ENV{'MD5SUM'} | sed -e "s/ .*//g"`); + #my $id = substr($shorter, 0, 8); # taking only the first 8 digits + my $id = installer::windows::msiglobal::calculate_id($languagestring, 8); # taking only the first 8 digits + $languagestring = "lang_" . $number_of_languages . "_id_" . $id; + } + + $path = $path . $languagestring . $installer::globals::separator; + create_directory($path); + } + } + } + + installer::remover::remove_ending_pathseparator(\$path); + + $path = installer::converter::make_path_conform($path); + + return $path; +} + +######################## +# Copying one file +######################## + +sub is_empty_dir +{ + my ($dir) = @_; + my ($handle, $count); + opendir($handle, $dir) or return 0; + $count = scalar(grep { $_ ne '.' && $_ ne '..' } readdir($handle)); + closedir($handle); + return $count == 0; +} + +sub copy_one_file +{ + my ($source, $dest) = @_; + + my ($returnvalue, $infoline, $copyreturn); + + if ( -l $source ) { + $copyreturn = symlink(readlink("$source"), "$dest"); + } + elsif (-d $source && is_empty_dir($source)) { + my $mode = (stat($source))[2] & 07777; + $copyreturn = mkdir($dest, $mode); + } + else { + $copyreturn = copy($source, $dest); + } + + if ($copyreturn) + { + $infoline = "Copy: $source to $dest\n"; + $returnvalue = 1; + } + else + { + $infoline = "ERROR: Could not copy $source to $dest $!\n"; + $returnvalue = 0; + } + + push(@installer::globals::logfileinfo, $infoline); + + if ( !$returnvalue ) { + return $returnvalue; + } + + # taking care of file attributes + if ($installer::globals::iswin && -f $dest) { + my $mode = -x $source ? 0775 : 0664; + my $mode_str = sprintf("%o", $mode); + my $chmodreturn = chmod($mode, $dest); + if ($chmodreturn) + { + $infoline = "chmod $mode_str, $dest\n"; + $returnvalue = 1; + } + else + { + $infoline = "WARNING: Could not chmod $dest: $!\n"; + $returnvalue = 0; + } + + push(@installer::globals::logfileinfo, $infoline); + } + + return $returnvalue; +} + +########################## +# Hard linking one file +########################## + +sub hardlink_one_file +{ + my ($source, $dest) = @_; + + my ($returnvalue, $infoline); + + my $copyreturn = link($source, $dest); + + if ($copyreturn) + { + $infoline = "Link: $source to $dest\n"; + $returnvalue = 1; + } + else + { + $infoline = "ERROR: Could not link $source to $dest\n"; + $returnvalue = 0; + } + + push(@installer::globals::logfileinfo, $infoline); + + return $returnvalue; +} + +########################## +# Soft linking one file +########################## + +sub softlink_one_file +{ + my ($source, $dest) = @_; + + my ($returnvalue, $infoline); + + my $linkreturn = symlink($source, $dest); + + if ($linkreturn) + { + $infoline = "Symlink: $source to $dest\n"; + $returnvalue = 1; + } + else + { + $infoline = "ERROR: Could not symlink $source to $dest\n"; + $returnvalue = 0; + } + + push(@installer::globals::logfileinfo, $infoline); + + return $returnvalue; +} + +######################## +# Renaming one file +######################## + +sub rename_one_file +{ + my ($source, $dest) = @_; + + my ($returnvalue, $infoline); + + my $renamereturn = rename($source, $dest); + + if ($renamereturn) + { + $infoline = "Rename: $source to $dest\n"; + $returnvalue = 1; + } + else + { + $infoline = "ERROR: Could not rename $source to $dest\n"; + $returnvalue = 0; + } + + push(@installer::globals::logfileinfo, $infoline); + + return $returnvalue; +} + +########################################## +# Copying all files from one directory +# to another directory +########################################## + +sub copy_directory +{ + my ($sourcedir, $destdir) = @_; + + my @sourcefiles = (); + + $sourcedir =~ s/\Q$installer::globals::separator\E\s*$//; + $destdir =~ s/\Q$installer::globals::separator\E\s*$//; + + my $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Copying files from directory $sourcedir to directory $destdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $sourcedir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + my $sourcefile = $sourcedir . $installer::globals::separator . $onefile; + my $destfile = $destdir . $installer::globals::separator . $onefile; + if ( -f $sourcefile ) # only files, no directories + { + copy_one_file($sourcefile, $destfile); + } + } + } +} + +##################################################################### +# Creating hard links to a complete directory with sub directories. +##################################################################### + +sub hardlink_complete_directory +{ + my ($sourcedir, $destdir) = @_; + + my @sourcefiles = (); + + $sourcedir =~ s/\Q$installer::globals::separator\E\s*$//; + $destdir =~ s/\Q$installer::globals::separator\E\s*$//; + + if ( ! -d $destdir ) { create_directory($destdir); } + + my $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Creating hard links for all files from directory $sourcedir to directory $destdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $sourcedir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + my $source = $sourcedir . $installer::globals::separator . $onefile; + my $dest = $destdir . $installer::globals::separator . $onefile; + if ( -f $source ) # only files, no directories + { + hardlink_one_file($source, $dest); + } + if ( -d $source ) # recursive + { + hardlink_complete_directory($source, $dest); + } + } + } +} + +##################################################################### +# Creating hard links to a complete directory with sub directories. +##################################################################### + +sub softlink_complete_directory +{ + my ($sourcedir, $destdir, $depth) = @_; + + my @sourcefiles = (); + + $sourcedir =~ s/\Q$installer::globals::separator\E\s*$//; + $destdir =~ s/\Q$installer::globals::separator\E\s*$//; + + if ( ! -d $destdir ) { create_directory($destdir); } + + my $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Creating soft links for all files from directory $sourcedir to directory $destdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $sourcedir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + my $source = $sourcedir . $installer::globals::separator . $onefile; + my $dest = $destdir . $installer::globals::separator . $onefile; + if ( -f $source ) # only files, no directories + { + my $localsource = $source; + if ( $depth > 0 ) { for ( my $i = 1; $i <= $depth; $i++ ) { $localsource = "../" . $localsource; } } + softlink_one_file($localsource, $dest); + } + if ( -d $source ) # recursive + { + my $newdepth = $depth + 1; + softlink_complete_directory($source, $dest, $newdepth); + } + } + } +} + +##################################################### +# Copying a complete directory with sub directories. +##################################################### + +sub copy_complete_directory +{ + my ($sourcedir, $destdir) = @_; + + my @sourcefiles = (); + + $sourcedir =~ s/\Q$installer::globals::separator\E\s*$//; + $destdir =~ s/\Q$installer::globals::separator\E\s*$//; + + if ( ! -d $destdir ) { create_directory($destdir); } + + my $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Copying files from directory $sourcedir to directory $destdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $sourcedir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + my $source = $sourcedir . $installer::globals::separator . $onefile; + my $dest = $destdir . $installer::globals::separator . $onefile; + if ( -f $source ) # only files, no directories + { + copy_one_file($source, $dest); + } + if ( -d $source ) # recursive + { + if ((!( $source =~ /packages\/SUNW/ )) && (!( $source =~ /packages\/OOO/ ))) # do not copy complete Solaris packages! + { + copy_complete_directory($source, $dest); + } + } + } + } +} + +##################################################### +# Copying all files with a specified file extension +# from one directory to another directory. +##################################################### + +sub copy_directory_with_fileextension +{ + my ($sourcedir, $destdir, $extension) = @_; + + my @sourcefiles = (); + + $sourcedir =~ s/\Q$installer::globals::separator\E\s*$//; + $destdir =~ s/\Q$installer::globals::separator\E\s*$//; + + $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Copying files with extension $extension from directory $sourcedir to directory $destdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $sourcedir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + if ( $onefile =~ /\.$extension\s*$/ ) # only copying specified files + { + my $sourcefile = $sourcedir . $installer::globals::separator . $onefile; + my $destfile = $destdir . $installer::globals::separator . $onefile; + if ( -f $sourcefile ) # only files, no directories + { + copy_one_file($sourcefile, $destfile); + } + } + } + } +} + +######################################################## +# Renaming all files with a specified file extension +# in a specified directory. +# Example: "Feature.idt.01" -> "Feature.idt" +######################################################## + +sub rename_files_with_fileextension +{ + my ($dir, $extension) = @_; + + my @sourcefiles = (); + + $dir =~ s/\Q$installer::globals::separator\E\s*$//; + + my $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Renaming files with extension \"$extension\" in the directory $dir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $dir); + @sourcefiles = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + if ( $onefile =~ /^\s*(\S.*?)\.$extension\s*$/ ) # only renaming specified files + { + my $destfile = $1; + my $sourcefile = $dir . $installer::globals::separator . $onefile; + $destfile = $dir . $installer::globals::separator . $destfile; + if ( -f $sourcefile ) # only files, no directories + { + rename_one_file($sourcefile, $destfile); + } + } + } + } +} + +######################################################## +# Finding all files with a specified file extension +# in a specified directory. +######################################################## + +sub find_file_with_file_extension +{ + my ($extension, $dir) = @_; + + my @allfiles = (); + + $dir =~ s/\Q$installer::globals::separator\E\s*$//; + + my $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Searching files with extension \"$extension\" in the directory $dir\n"; + push(@installer::globals::logfileinfo, $infoline); + + opendir(DIR, $dir); + @sourcefiles = sort readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@sourcefiles) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + if ( $onefile =~ /^\s*(\S.*?)\.$extension\s*$/ ) + { + push(@allfiles, $onefile) + } + } + } + + return \@allfiles; +} + +############################################################## +# Creating a unique directory, for example "01_inprogress_7" +# in the install directory. +############################################################## + +sub make_numbered_dir +{ + my ($newstring, $olddir) = @_; + + my $basedir = $olddir; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$basedir); + + my $alldirs = get_all_directories($basedir); + + # searching for the highest number extension + + my $maxnumber = 0; + + for ( my $i = 0; $i <= $#{$alldirs}; $i++ ) + { + if ( ${$alldirs}[$i] =~ /\_(\d+)\s*$/ ) + { + my $number = $1; + if ( $number > $maxnumber ) { $maxnumber = $number; } + } + } + + my $newnumber = $maxnumber + 1; + + my $newdir = $olddir . "_" . $newstring . "_" . $newnumber; + + my $returndir = ""; + + if ( move($olddir, $newdir) ) + { + $infoline = "\nMoved directory from $olddir to $newdir\n"; + push(@installer::globals::logfileinfo, $infoline); + $returndir = $newdir; + } + else + { + $infoline = "\nATTENTION: Could not move directory from $olddir to $newdir, \"make_numbered_dir\"\n"; + push(@installer::globals::logfileinfo, $infoline); + $returndir = $olddir; + } + + return $returndir; +} + +##################################################################################### +# Renaming a directory by exchanging a string, for example from "01_inprogress_7" +# to "01_witherror_7". +##################################################################################### + +sub rename_string_in_directory +{ + my ($olddir, $oldstring, $newstring) = @_; + + my $newdir = $olddir; + my $infoline = ""; + + $newdir =~ s/$oldstring/$newstring/g; + + if (( -d $newdir ) && ( $olddir ne $newdir )) { remove_complete_directory($newdir, 1); } + + if ( move($olddir, $newdir) ) + { + $infoline = "\nMoved directory from $olddir to $newdir\n"; + push(@installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "\nATTENTION: Could not move directory from $olddir to $newdir, \"rename_string_in_directory\"\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + return $newdir; +} + +###################################################### +# Returning the complete directory name, +# input is the first part of the directory name. +###################################################### + +sub get_directoryname +{ + my ($searchdir, $startstring) = @_; + + my $dirname = ""; + my $founddir = 0; + my $direntry; + + opendir(DIR, $searchdir); + + foreach $direntry (readdir (DIR)) + { + next if $direntry eq "."; + next if $direntry eq ".."; + + if (( -d $direntry ) && ( $direntry =~ /^\s*\Q$startstring\E/ )) + { + $dirname = $direntry; + $founddir = 1; + last; + } + } + + closedir(DIR); + + if ( ! $founddir ) { installer::exiter::exit_program("ERROR: Did not find directory beginning with $startstring in directory $searchdir", "get_directoryname"); } + + return $dirname; +} + + +################################### +# Renaming a directory +################################### + +sub rename_directory +{ + my ($olddir, $newdir) = @_; + + my $infoline = ""; + + if ( move($olddir, $newdir) ) + { + $infoline = "\nMoved directory from $olddir to $newdir\n"; + push(@installer::globals::logfileinfo, $infoline); + } + else + { + installer::exiter::exit_program("ERROR: Could not move directory from $olddir to $newdir $!", "rename_directory"); + } + + return $newdir; +} + +############################################################## +# Creating a directory next to an existing directory +############################################################## + +sub create_directory_next_to_directory +{ + my ($topdir, $dirname) = @_; + + my $basedir = $topdir; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$basedir); + + $basedir =~ s/\Q$installer::globals::separator\E\s*$//; + + my $newdir = $basedir . $installer::globals::separator . $dirname; + + create_directory($newdir); + + return $newdir; +} + +############################################################## +# Collecting all directories inside a directory +############################################################## + +sub get_all_directories +{ + my ($basedir) = @_; + + my @alldirs = (); + my $direntry; + + $basedir =~ s/\Q$installer::globals::separator\E\s*$//; + + opendir(DIR, $basedir); + + foreach $direntry (readdir (DIR)) + { + next if $direntry eq "."; + next if $direntry eq ".."; + + my $completeentry = $basedir . $installer::globals::separator . $direntry; + + if ( -d $completeentry ) { push(@alldirs, $completeentry); } + } + + closedir(DIR); + + return \@alldirs; +} + +############################################################## +# Collecting all directories inside a directory +# Returning without path +############################################################## + +sub get_all_directories_without_path +{ + my ($basedir) = @_; + + my @alldirs = (); + my $direntry; + + $basedir =~ s/\Q$installer::globals::separator\E\s*$//; + + opendir(DIR, $basedir); + + foreach $direntry (readdir (DIR)) + { + next if $direntry eq "."; + next if $direntry eq ".."; + + my $completeentry = $basedir . $installer::globals::separator . $direntry; + + if ( -d $completeentry ) { push(@alldirs, $direntry); } + } + + closedir(DIR); + + return \@alldirs; +} + +############################################################## +# Collecting all files and directories inside one directory +############################################################## + +sub read_directory +{ + my ($basedir) = @_; + + my @allcontent = (); + my $direntry; + + $basedir =~ s/\Q$installer::globals::separator\E\s*$//; + + opendir(DIR, $basedir); + + foreach $direntry (readdir (DIR)) + { + next if $direntry eq "."; + next if $direntry eq ".."; + + my $completeentry = $basedir . $installer::globals::separator . $direntry; + + if (( -f $completeentry ) || ( -d $completeentry )) { push(@allcontent, $completeentry); } + } + + closedir(DIR); + + return \@allcontent; +} + +############################################################## +# Finding the new content in a directory +############################################################## + +sub find_new_content_in_directory +{ + my ( $basedir, $oldcontent ) = @_; + + my @newcontent = (); + my @allcontent = (); + + my $direntry; + + $basedir =~ s/\Q$installer::globals::separator\E\s*$//; + + opendir(DIR, $basedir); + + foreach $direntry (readdir (DIR)) + { + next if $direntry eq "."; + next if $direntry eq ".."; + + my $completeentry = $basedir . $installer::globals::separator . $direntry; + + if (( -f $completeentry ) || ( -d $completeentry )) + { + push(@allcontent, $completeentry); + if (! grep {$_ eq $completeentry} @{$oldcontent}) + { + push(@newcontent, $completeentry); + } + } + } + + closedir(DIR); + + return (\@newcontent, \@allcontent); +} + +############################################################## +# Trying to create a directory, no error if this fails +############################################################## + +sub try_to_create_directory +{ + my ($directory) = @_; + + my $returnvalue = 1; + my $created_directory = 0; + + if (!(-d $directory)) + { + $returnvalue = mkdir($directory, 0775); + + if ($returnvalue) + { + $created_directory = 1; + $infoline = "\nCreated directory: $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + + chmod 0775, $directory; + } + else + { + $created_directory = 0; + } + } + else + { + $created_directory = 1; + } + + return $created_directory; +} + +############################################################## +# Creating a complete directory structure +############################################################## + +sub create_directory_structure +{ + my ($directory) = @_; + + if ( ! try_to_create_directory($directory) ) + { + my $parentdir = $directory; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$parentdir); + + my $infoline = "INFO: Did not create directory $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Now trying to create parent directory $parentdir\n"; + push(@installer::globals::logfileinfo, $infoline); + + create_directory_structure($parentdir); # recursive + } + + create_directory($directory); # now it has to succeed +} + +###################################################### +# Removing a complete directory with subdirectories +###################################################### + +sub remove_complete_directory +{ + my ($directory, $start) = @_; + + my @content = (); + my $infoline = ""; + + $directory =~ s/\Q$installer::globals::separator\E\s*$//; + + if ( -d $directory ) + { + if ( $start ) + { + $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Removing directory $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + opendir(DIR, $directory); + @content = readdir(DIR); + closedir(DIR); + + my $oneitem; + + foreach $oneitem (@content) + { + if ((!($oneitem eq ".")) && (!($oneitem eq ".."))) + { + my $item = $directory . $installer::globals::separator . $oneitem; + + if ( -f $item || -l $item ) # deleting files or links + { + unlink($item); + } + + if ( -d $item ) # recursive + { + remove_complete_directory($item, 0); + } + } + } + + # try to remove empty directory + + my $returnvalue = rmdir $directory; + + if ( ! $returnvalue ) + { + $infoline = "Warning: Problem with removing empty dir $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + # try a little bit harder (sometimes there is a performance problem) + if ( -d $directory ) + { + for ( my $j = 1; $j <= 3; $j++ ) + { + if ( -d $directory ) + { + $infoline = "\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Warning (Try $j): Problems with removing directory $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + + $returnvalue = rmdir $directory; + + if ( $returnvalue ) + { + $infoline = "Successfully removed empty dir $directory\n"; + push(@installer::globals::logfileinfo, $infoline); + } else { + $infoline = "Warning: rmdir $directory failed.\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } + } + } +} + +###################################################### +# Creating a unique directory with pid extension +###################################################### + +sub create_pid_directory +{ + my ($directory) = @_; + + $directory =~ s/\Q$installer::globals::separator\E\s*$//; + my $pid = $$; # process id + my $time = time(); # time + + $directory = $directory . "_" . $pid . $time; + + if ( ! -d $directory ) { create_directory($directory); } + else { installer::exiter::exit_program("ERROR: Directory $directory already exists!", "create_pid_directory"); } + + return $directory; +} + +############################################################## +# Reading all files from a directory and its subdirectories +############################################################## + +sub read_complete_directory +{ + my ($directory, $pathstring, $filecollector) = @_; + + my @content = (); + opendir(DIR, $directory); + @content = readdir(DIR); + closedir(DIR); + + my $onefile; + + foreach $onefile (@content) + { + if ((!($onefile eq ".")) && (!($onefile eq ".."))) + { + my $completefilename = $directory . $installer::globals::separator . $onefile; + my $sep = ""; + if ( $pathstring ne "" ) { $sep = $installer::globals::separator; } + + if ( ! -d $completefilename ) # only files, no directories + { + my $content = $pathstring . $sep . $onefile; + push(@{$filecollector}, $content); + } + else # recursive for directories + { + my $newpathstring = $pathstring . $sep . $onefile; + read_complete_directory($completefilename, $newpathstring, $filecollector); + } + } + } +} + +############################################################## +# Reading all files from a directory and its subdirectories +# Version 2 +############################################################## + +sub read_full_directory { + my ( $currentdir, $pathstring, $collector ) = @_; + my $item; + my $fullname; + local *DH; + + unless (opendir(DH, $currentdir)) + { + return; + } + while (defined ($item = readdir(DH))) + { + next if($item eq "." or $item eq ".."); + $fullname = $currentdir . $installer::globals::separator . $item; + my $sep = ""; + if ( $pathstring ne "" ) { $sep = $installer::globals::separator; } + + if( -d $fullname) + { + my $newpathstring = $pathstring . $sep . $item; + read_full_directory($fullname, $newpathstring, $collector) if(-d $fullname); + } + else + { + my $content = $pathstring . $sep . $item; + push(@{$collector}, $content); + } + } + closedir(DH); + return +} + +############################################################## +# Removing all empty directories below a specified directory +############################################################## + +sub remove_empty_dirs_in_folder +{ + my ( $dir ) = @_; + + my @content = (); + my $infoline = ""; + + $dir =~ s/\Q$installer::globals::separator\E\s*$//; + + if ( -d $dir ) + { + opendir(DIR, $dir); + @content = readdir(DIR); + closedir(DIR); + + my $oneitem; + + foreach $oneitem (@content) + { + if ((!($oneitem eq ".")) && (!($oneitem eq ".."))) + { + my $item = $dir . $installer::globals::separator . $oneitem; + + if ( -d $item ) # recursive + { + remove_empty_dirs_in_folder($item); + } + } + } + + # try to remove empty directory + my $returnvalue = rmdir $dir; + + if ( $returnvalue ) + { + $infoline = "Successfully removed empty dir $dir\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + } + +} + +###################################################### +# Making systemcall +###################################################### + +sub make_systemcall +{ + my ($systemcall) = @_; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +1; diff --git a/solenv/bin/modules/installer/windows/admin.pm b/solenv/bin/modules/installer/windows/admin.pm new file mode 100644 index 000000000..27e4ba7c3 --- /dev/null +++ b/solenv/bin/modules/installer/windows/admin.pm @@ -0,0 +1,515 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::admin; + +use File::Copy; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::pathanalyzer; +use installer::systemactions; +use installer::worker; +use installer::windows::idtglobal; + +################################################################################# +# Unpacking cabinet files with expand +################################################################################# + +sub unpack_cabinet_file +{ + my ($cabfilename, $unpackdir) = @_; + + my $infoline = "Unpacking cabinet file: $cabfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $expandfile = "expand.exe"; # Has to be in the path + + # expand.exe has to be located in the system directory. + # Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course. + # But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack + # cabinet files. + + if ( $^O =~ /cygwin/i ) + { + $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe); + chomp $expandfile; + } + + my $expandlogfile = $unpackdir . $installer::globals::separator . "expand.log"; + + # exclude cabinet file + + my $systemcall = ""; + if ( $^O =~ /cygwin/i ) { + my $localunpackdir = qx{cygpath -w "$unpackdir"}; + chomp ($localunpackdir); + $localunpackdir =~ s/\\/\\\\/g; + $cabfilename =~ s/\\/\\\\/g; + $cabfilename =~ s/\s*$//g; + $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $localunpackdir . " \> " . $expandlogfile; + } + else + { + $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile; + } + + my $returnvalue = system($systemcall); + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +################################################################################# +# Extracting tables from msi database +################################################################################# + +sub extract_tables_from_pcpfile +{ + my ($fullmsidatabasepath, $workdir, $tablelist) = @_; + + my $msidb = "msidb.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + + my $localfullmsidatabasepath = $fullmsidatabasepath; + + # Export of all tables by using "*" + + if ( $^O =~ /cygwin/i ) { + # Copying the msi database locally guarantees the format of the directory. + # Otherwise it is defined in the file of UPDATE_DATABASE_LISTNAME + + my $msifilename = $localfullmsidatabasepath; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename); + my $destdatabasename = $workdir . $installer::globals::separator . $msifilename; + installer::systemactions::copy_one_file($localfullmsidatabasepath, $destdatabasename); + $localfullmsidatabasepath = $destdatabasename; + + chomp( $localfullmsidatabasepath = qx{cygpath -w "$localfullmsidatabasepath"} ); + chomp( $workdir = qx{cygpath -w "$workdir"} ); + + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $localfullmsidatabasepath =~ s/\\/\\\\/g; + $workdir =~ s/\\/\\\\/g; + + # and if there are still slashes, they also need to be double backslash + $localfullmsidatabasepath =~ s/\//\\\\/g; + $workdir =~ s/\//\\\\/g; + } + + $systemcall = $msidb . " -d " . $localfullmsidatabasepath . " -f " . $workdir . " -e $tablelist"; + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not exclude tables from pcp file: $localfullmsidatabasepath !", "extract_tables_from_pcpfile"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +################################################################################ +# Analyzing the content of Directory.idt +################################################################################# + +sub analyze_directory_file +{ + my ($filecontent) = @_; + + my %table = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $dir = $1; + my $parent = $2; + my $name = $3; + + if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; } + if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; } + + my %helphash = (); + $helphash{'Directory_Parent'} = $parent; + $helphash{'DefaultDir'} = $name; + $table{$dir} = \%helphash; + } + } + + return \%table; +} + +################################################################################# +# Analyzing the content of Component.idt +################################################################################# + +sub analyze_component_file +{ + my ($filecontent) = @_; + + my %table = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $component = $1; + my $dir = $3; + + $table{$component} = $dir; + } + } + + return \%table; +} + +################################################################################# +# Analyzing the content of File.idt +################################################################################# + +sub analyze_file_file +{ + my ($filecontent) = @_; + + my %table = (); + my %fileorder = (); + my $maxsequence = 0; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $file = $1; + my $comp = $2; + my $filename = $3; + my $sequence = $8; + + if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; } + + my %helphash = (); + $helphash{'Component'} = $comp; + $helphash{'FileName'} = $filename; + $helphash{'Sequence'} = $sequence; + + $table{$file} = \%helphash; + + $fileorder{$sequence} = $file; + + if ( $sequence > $maxsequence ) { $maxsequence = $sequence; } + } + } + + return (\%table, \%fileorder, $maxsequence); +} + +#################################################################################### +# Recursively creating the directory tree +#################################################################################### + +sub create_directory_tree +{ + my ($parent, $pathcollector, $fulldir, $dirhash) = @_; + + foreach my $dir ( keys %{$dirhash} ) + { + if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." )) + { + my $dirname = $dirhash->{$dir}->{'DefaultDir'}; + # Create the directory + my $newdir = $fulldir . $installer::globals::separator . $dirname; + if ( ! -f $newdir ) { mkdir $newdir; } + # Saving in collector + $pathcollector->{$dir} = $newdir; + # Iteration + create_directory_tree($dir, $pathcollector, $newdir, $dirhash); + } + } +} + +#################################################################################### +# Creating the directory tree +#################################################################################### + +sub create_directory_structure +{ + my ($dirhash, $targetdir) = @_; + + my %fullpathhash = (); + + my @startparents = ("TARGETDIR", "INSTALLLOCATION"); + + foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); } + + # Also adding the paths of the startparents + foreach $dir (@startparents) + { + if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; } + } + + return \%fullpathhash; +} + +#################################################################################### +# Copying files into installation set +#################################################################################### + +sub copy_files_into_directory_structure +{ + my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_; + + for ( my $i = 1; $i <= $maxsequence; $i++ ) + { + if ( exists($fileorder->{$i}) ) + { + my $file = $fileorder->{$i}; + if ( ! exists($filehash->{$file}->{'Component'}) ) { installer::exiter::exit_program("ERROR: Did not find component for file: \"$file\".", "copy_files_into_directory_structure"); } + my $component = $filehash->{$file}->{'Component'}; + if ( ! exists($componenthash->{$component}) ) { installer::exiter::exit_program("ERROR: Did not find directory for component: \"$component\".", "copy_files_into_directory_structure"); } + my $dirname = $componenthash->{$component}; + if ( ! exists($fullpathhash->{$dirname}) ) { installer::exiter::exit_program("ERROR: Did not find full directory path for dir: \"$dirname\".", "copy_files_into_directory_structure"); } + my $destdir = $fullpathhash->{$dirname}; + if ( ! exists($filehash->{$file}->{'FileName'}) ) { installer::exiter::exit_program("ERROR: Did not find \"FileName\" for file: \"$file\".", "copy_files_into_directory_structure"); } + my $destfile = $filehash->{$file}->{'FileName'}; + + $destfile = $destdir . $installer::globals::separator . $destfile; + my $sourcefile = $unpackdir . $installer::globals::separator . $file; + + if ( ! -f $sourcefile ) + { + # It is possible, that this was an unpacked file + # Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname) + # subdir is not recursively analyzed, only one directory. + + my $oldsourcefile = $sourcefile; + my $subdir = ""; + if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $installer::globals::separator; } + my $realfilename = $filehash->{$file}->{'FileName'}; + my $localinstalldir = $installdir; + + $localinstalldir =~ s/\\\s*$//; + $localinstalldir =~ s/\/\s*$//; + + $sourcefile = $localinstalldir . $installer::globals::separator . $subdir . $realfilename; + + if ( ! -f $sourcefile ) + { + installer::exiter::exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\").", "copy_files_into_directory_structure"); + } + } + + my $copyreturn = copy($sourcefile, $destfile); + + if ( ! $copyreturn) # only logging problems + { + my $infoline = "ERROR: Could not copy $sourcefile to $destfile (insufficient disc space for $destfile ?)\n"; + $returnvalue = 0; + push(@installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program($infoline, "copy_files_into_directory_structure"); + } + } + } +} + + +############################################################### +# Setting the time string for the +# Summary Information stream in the +# msi database of the admin installations. +############################################################### + +sub get_sis_time_string +{ + # Syntax: + my $second = (localtime())[0]; + my $minute = (localtime())[1]; + my $hour = (localtime())[2]; + my $day = (localtime())[3]; + my $month = (localtime())[4]; + my $year = 1900 + (localtime())[5]; + + $month++; # zero based month + + if ( $second < 10 ) { $second = "0" . $second; } + if ( $minute < 10 ) { $minute = "0" . $minute; } + if ( $hour < 10 ) { $hour = "0" . $hour; } + if ( $day < 10 ) { $day = "0" . $day; } + if ( $month < 10 ) { $month = "0" . $month; } + + my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second; + + return $timestring; +} + +############################################################### +# Writing content of administrative installations into +# Summary Information Stream of msi database. +# This is required for example for following +# patch processes using Windows Installer service. +############################################################### + +sub write_sis_info +{ + my ($msidatabase) = @_ ; + + if ( ! -f $msidatabase ) { installer::exiter::exit_program("ERROR: Cannot find file $msidatabase", "write_sis_info"); } + + my $msiinfo = "msiinfo.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + + # Required setting for administrative installations: + # -w 4 (source files are unpacked), wordcount + # -s , LastPrinted, Syntax: + # -l , LastSavedBy + + my $wordcount = 4; # Unpacked files + my $lastprinted = get_sis_time_string(); + my $lastsavedby = "Installer"; + + my $localmsidatabase = $msidatabase; + + if( $^O =~ /cygwin/i ) + { + $localmsidatabase = qx{cygpath -w "$localmsidatabase"}; + $localmsidatabase =~ s/\\/\\\\/g; + $localmsidatabase =~ s/\s*$//g; + } + + $systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby"; + push(@installer::globals::logfileinfo, $systemcall); + $returnvalue = system($systemcall); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push(@installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program($infoline, "write_sis_info"); + } +} + +#################################################################################### +# Simulating an administrative installation +#################################################################################### + +sub make_admin_install +{ + my ($databasepath, $targetdir) = @_; + + # Create helper directory + + installer::logger::print_message( "... installing $databasepath in directory $targetdir ...\n" ); + + my $helperdir = $targetdir . $installer::globals::separator . "installhelper"; + installer::systemactions::create_directory($helperdir); + + # Get File.idt, Component.idt and Directory.idt from database + + my $tablelist = "File Directory Component Registry"; + extract_tables_from_pcpfile($databasepath, $helperdir, $tablelist); + + # Unpack all cab files into $helperdir, cab files must be located next to msi database + my $installdir = $databasepath; + + if ( $^O =~ /cygwin/i ) { $installdir =~ s/\\/\//g; } # backslash to slash + + installer::pathanalyzer::get_path_from_fullqualifiedname(\$installdir); + + if ( $^O =~ /cygwin/i ) { $installdir =~ s/\//\\/g; } # slash to backslash + + my $databasefilename = $databasepath; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$databasefilename); + + my $cabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir); + + if ( $#{$cabfiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find any cab file in directory $installdir", "make_admin_install"); } + + # Set unpackdir + my $unpackdir = $helperdir . $installer::globals::separator . "unpack"; + installer::systemactions::create_directory($unpackdir); + + for ( my $i = 0; $i <= $#{$cabfiles}; $i++ ) + { + my $cabfile = ""; + if ( $^O =~ /cygwin/i ) + { + $cabfile = $installdir . ${$cabfiles}[$i]; + } + else + { + $cabfile = $installdir . $installer::globals::separator . ${$cabfiles}[$i]; + } + unpack_cabinet_file($cabfile, $unpackdir); + } + + # Reading tables + my $filename = $helperdir . $installer::globals::separator . "Directory.idt"; + my $filecontent = installer::files::read_file($filename); + my $dirhash = analyze_directory_file($filecontent); + + $filename = $helperdir . $installer::globals::separator . "Component.idt"; + my $componentfilecontent = installer::files::read_file($filename); + my $componenthash = analyze_component_file($componentfilecontent); + + $filename = $helperdir . $installer::globals::separator . "File.idt"; + $filecontent = installer::files::read_file($filename); + my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent); + + # Creating the directory structure + my $fullpathhash = create_directory_structure($dirhash, $targetdir); + + # Copying files + copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash); + + my $msidatabase = $targetdir . $installer::globals::separator . $databasefilename; + installer::systemactions::copy_one_file($databasepath, $msidatabase); + + # Saving info in Summary Information Stream of msi database (required for following patches) + write_sis_info($msidatabase); + + return $msidatabase; +} + +1; diff --git a/solenv/bin/modules/installer/windows/assembly.pm b/solenv/bin/modules/installer/windows/assembly.pm new file mode 100644 index 000000000..38c04a799 --- /dev/null +++ b/solenv/bin/modules/installer/windows/assembly.pm @@ -0,0 +1,279 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::assembly; + +use installer::files; +use installer::globals; +use installer::worker; +use installer::windows::idtglobal; + +############################################################## +# Returning the first module of a file from the +# comma separated list of modules. +############################################################## + +sub get_msiassembly_feature +{ + my ( $onefile ) = @_; + + my $module = ""; + + if ( $onefile->{'modules'} ) { $module = $onefile->{'modules'}; } + + # If modules contains a list of modules, only taking the first one. + + if ( $module =~ /^\s*(.*?)\,/ ) { $module = $1; } + + # Attention: Maximum feature length is 38! + installer::windows::idtglobal::shorten_feature_gid(\$module); + + return $module; +} + +############################################################## +# Returning the component of a file. +############################################################## + +sub get_msiassembly_component +{ + my ( $onefile ) = @_; + + my $component = ""; + + $component = $onefile->{'componentname'}; + + return $component; +} + +############################################################## +# Returning the file name as manifest file +############################################################## + +sub get_msiassembly_filemanifest +{ + my ( $onefile ) = @_; + + my $filemanifest = ""; + + $filemanifest = $onefile->{'uniquename'}; + + return $filemanifest; +} + +############################################################## +# Returning the file application +############################################################## + +sub get_msiassembly_fileapplication +{ + my ( $onefile ) = @_; + + my $fileapplication = ""; + + return $fileapplication; +} + +############################################################## +# Returning the file attributes +############################################################## + +sub get_msiassembly_attributes +{ + my ( $onefile ) = @_; + + my $fileattributes = ""; + + if ( $onefile->{'Attributes'} ne "" ) { $fileattributes = $onefile->{'Attributes'}; } + + return $fileattributes; +} + +#################################################################################### +# Creating the file MsiAssembly.idt dynamically +# Content: +# Component_ Feature_ File_Manifest File_Application Attributes +# s72 s38 S72 S72 I2 +# MsiAssembly Component_ +#################################################################################### + +sub create_msiassembly_table +{ + my ($filesref, $basedir) = @_; + + $installer::globals::msiassemblyfiles = installer::worker::collect_all_items_with_special_flag($filesref, "ASSEMBLY"); + + my @msiassemblytable = (); + + installer::windows::idtglobal::write_idt_header(\@msiassemblytable, "msiassembly"); + + # Registering all libraries listed in $installer::globals::msiassemblyfiles + + for ( my $i = 0; $i <= $#{$installer::globals::msiassemblyfiles}; $i++ ) + { + my $onefile = ${$installer::globals::msiassemblyfiles}[$i]; + + my %msiassembly = (); + + $msiassembly{'Component_'} = get_msiassembly_component($onefile); + $msiassembly{'Feature_'} = get_msiassembly_feature($onefile); + $msiassembly{'File_Manifest'} = get_msiassembly_filemanifest($onefile); + $msiassembly{'File_Application'} = get_msiassembly_fileapplication($onefile); + $msiassembly{'Attributes'} = get_msiassembly_attributes($onefile); + + my $oneline = $msiassembly{'Component_'} . "\t" . $msiassembly{'Feature_'} . "\t" . + $msiassembly{'File_Manifest'} . "\t" . $msiassembly{'File_Application'} . "\t" . + $msiassembly{'Attributes'} . "\n"; + + push(@msiassemblytable, $oneline); + } + + # Saving the file + + my $msiassemblytablename = $basedir . $installer::globals::separator . "MsiAssem.idt"; + installer::files::save_file($msiassemblytablename ,\@msiassemblytable); + my $infoline = "Created idt file: $msiassemblytablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +#################################################################################### +# Creating the file MsiAssemblyName.idt dynamically +# Content: +# Component_ Name Value +# s72 s255 s255 +# MsiAssemblyName Component_ Name +#################################################################################### + +sub create_msiassemblyname_table +{ + my ($filesref, $basedir) = @_; + + my @msiassemblynametable = (); + + installer::windows::idtglobal::write_idt_header(\@msiassemblynametable, "msiassemblyname"); + + for ( my $i = 0; $i <= $#{$installer::globals::msiassemblyfiles}; $i++ ) + { + my $onefile = ${$installer::globals::msiassemblyfiles}[$i]; + + my $component = get_msiassembly_component($onefile); + my $oneline = ""; + + # Order: (Assembly)name, publicKeyToken, version, culture. + + if ( $onefile->{'Assemblyname'} ) + { + $oneline = $component . "\t" . "name" . "\t" . $onefile->{'Assemblyname'} . "\n"; + push(@msiassemblynametable, $oneline); + } + + if ( $onefile->{'PublicKeyToken'} ) + { + $oneline = $component . "\t" . "publicKeyToken" . "\t" . $onefile->{'PublicKeyToken'} . "\n"; + push(@msiassemblynametable, $oneline); + } + + if ( $onefile->{'Version'} ) + { + $oneline = $component . "\t" . "version" . "\t" . $onefile->{'Version'} . "\n"; + push(@msiassemblynametable, $oneline); + } + + if ( $onefile->{'Culture'} ) + { + $oneline = $component . "\t" . "culture" . "\t" . $onefile->{'Culture'} . "\n"; + push(@msiassemblynametable, $oneline); + } + + if ( $onefile->{'ProcessorArchitecture'} ) + { + $oneline = $component . "\t" . "processorArchitecture" . "\t" . $onefile->{'ProcessorArchitecture'} . "\n"; + push(@msiassemblynametable, $oneline); + } + } + + # Saving the file + + my $msiassemblynametablename = $basedir . $installer::globals::separator . "MsiAsseN.idt"; + installer::files::save_file($msiassemblynametablename ,\@msiassemblynametable); + my $infoline = "Created idt file: $msiassemblynametablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +#################################################################################### +# setting an installation condition for the assembly libraries saved in +# @installer::globals::msiassemblynamecontent +#################################################################################### + +sub add_assembly_condition_into_component_table +{ + my ($filesref, $basedir) = @_; + + my $componenttablename = $basedir . $installer::globals::separator . "Componen.idt"; + my $componenttable = installer::files::read_file($componenttablename); + my $changed = 0; + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$installer::globals::msiassemblyfiles}; $i++ ) + { + my $onefile = ${$installer::globals::msiassemblyfiles}[$i]; + + my $filecomponent = get_msiassembly_component($onefile); + + for ( my $j = 0; $j <= $#{$componenttable}; $j++ ) + { + my $oneline = ${$componenttable}[$j]; + + if ( $oneline =~ /(.*)\t(.*)\t(.*)\t(.*)\t(.*)\t(.*)/ ) + { + my $component = $1; + my $componentid = $2; + my $directory = $3; + my $attributes = $4; + my $condition = $5; + my $keypath = $6; + + if ( $component eq $filecomponent ) + { + # setting the condition + + $condition = "MsiNetAssemblySupport >= \"4.0.0.0\""; + $oneline = $component . "\t" . $componentid . "\t" . $directory . "\t" . $attributes . "\t" . $condition . "\t" . $keypath . "\n"; + ${$componenttable}[$j] = $oneline; + $changed = 1; + $infoline = "Changing $componenttablename :\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = $oneline; + push(@installer::globals::logfileinfo, $infoline); + last; + } + } + } + } + + if ( $changed ) + { + # Saving the file + installer::files::save_file($componenttablename ,$componenttable); + $infoline = "Saved idt file: $componenttablename\n"; + push(@installer::globals::logfileinfo, $infoline); + } +} + +1; diff --git a/solenv/bin/modules/installer/windows/binary.pm b/solenv/bin/modules/installer/windows/binary.pm new file mode 100644 index 000000000..b6f979ce0 --- /dev/null +++ b/solenv/bin/modules/installer/windows/binary.pm @@ -0,0 +1,67 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::binary; + +use installer::files; +use installer::globals; + +########################################################################################################### +# Updating the table Binary dynamically with all files from $binarytablefiles +# Content: +# Name Data +# s72 v0 +# Binary Name +########################################################################################################### + +sub update_binary_table +{ + my ($languageidtdir, $filesref, $binarytablefiles) = @_; + + my $binaryidttablename = $languageidtdir . $installer::globals::separator . "Binary.idt"; + my $binaryidttable = installer::files::read_file($binaryidttablename); + + # Only the iconfiles, that are used in the shortcut table for the + # FolderItems (entries in Windows startmenu) are added into the icon table. + + for ( my $i = 0; $i <= $#{$binarytablefiles}; $i++ ) + { + my $binaryfile = ${$binarytablefiles}[$i]; + my $binaryfilename = $binaryfile->{'Name'}; + my $binaryfiledata = $binaryfilename; + + $binaryfilename =~ s/\.//g; # removing "." in filename: "abc.dll" to "abcdll" in name column + + my %binary = (); + + $binary{'Name'} = $binaryfilename; + $binary{'Data'} = $binaryfiledata; + + my $oneline = $binary{'Name'} . "\t" . $binary{'Data'} . "\n"; + + push(@{$binaryidttable}, $oneline); + } + + # Saving the file + + installer::files::save_file($binaryidttablename ,$binaryidttable); + my $infoline = "Updated idt file: $binaryidttablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +1; diff --git a/solenv/bin/modules/installer/windows/component.pm b/solenv/bin/modules/installer/windows/component.pm new file mode 100644 index 000000000..9751caabd --- /dev/null +++ b/solenv/bin/modules/installer/windows/component.pm @@ -0,0 +1,505 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::component; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; +use installer::windows::language; + +############################################################## +# Returning a globally unique ID (GUID) for a component +# If the component is new, a unique guid has to be created. +# If the component already exists, the guid has to be +# taken from a list component <-> guid +# Sample for a guid: {B68FD953-3CEF-4489-8269-8726848056E8} +############################################################## + +sub get_component_guid +{ + my ( $componentname, $componentidhashref ) = @_; + + # At this time only a template + my $returnvalue = "\{COMPONENTGUID\}"; + + if (( $installer::globals::updatedatabase ) && ( exists($componentidhashref->{$componentname}) )) + { + $returnvalue = $componentidhashref->{$componentname}; + } + + # Returning a ComponentID, that is assigned in scp project + if ( exists($installer::globals::componentid{$componentname}) ) + { + $returnvalue = "\{" . $installer::globals::componentid{$componentname} . "\}"; + } + + return $returnvalue; +} + +############################################################## +# Returning the directory for a file component. +############################################################## + +sub get_file_component_directory +{ + my ($componentname, $filesref, $dirref) = @_; + + my ($onefile, $component, $onedir, $hostname, $uniquedir); + my $found = 0; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + $component = $onefile->{'componentname'}; + + if ( $component eq $componentname ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + # This component can be ignored, if it exists in a version with extension "_pff" (this was renamed in file::get_sequence_for_file() ) + my $ignore_this_component = 0; + my $origcomponentname = $componentname; + my $componentname = $componentname . "_pff"; + + for ( my $j = 0; $j <= $#{$filesref}; $j++ ) + { + $onefile = ${$filesref}[$j]; + $component = $onefile->{'componentname'}; + + if ( $component eq $componentname ) + { + $ignore_this_component = 1; + last; + } + } + + if ( $ignore_this_component ) { return "IGNORE_COMP"; } + else { installer::exiter::exit_program("ERROR: Did not find component \"$origcomponentname\" in file collection", "get_file_component_directory"); } + } + + my $localstyles = ""; + + if ( $onefile->{'Styles'} ) { $localstyles = $onefile->{'Styles'}; } + + if ( $localstyles =~ /\bFONT\b/ ) # special handling for font files + { + return $installer::globals::fontsfolder; + } + + my $destdir = ""; + + if ( $onefile->{'Dir'} ) { $destdir = $onefile->{'Dir'}; } + + my $destination = $onefile->{'destination'}; + + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination); + + $destination =~ s/\Q$installer::globals::separator\E\s*$//; + + # This path has to be defined in the directory collection at "HostName" + + if ($destination eq "") # files in the installation root + { + $uniquedir = "INSTALLLOCATION"; + } + else + { + $found = 0; + + for ( my $i = 0; $i <= $#{$dirref}; $i++ ) + { + $onedir = ${$dirref}[$i]; + $hostname = $onedir->{'HostName'}; + + if ( $hostname eq $destination ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find destination $destination in directory collection", "get_file_component_directory"); + } + + $uniquedir = $onedir->{'uniquename'}; + + if ( $uniquedir eq $installer::globals::officeinstalldirectory ) + { + $uniquedir = "INSTALLLOCATION"; + } + } + + $onefile->{'uniquedirname'} = $uniquedir; # saving it in the file collection + + return $uniquedir +} + +############################################################## +# Returning the directory for a registry component. +# This cannot be a useful value +############################################################## + +sub get_registry_component_directory +{ + my $componentdir = "INSTALLLOCATION"; + + return $componentdir; +} + +############################################################## +# Returning the attributes for a file component. +############################################################## + +sub get_file_component_attributes +{ + my ($componentname, $filesref, $allvariables) = @_; + + my $attributes; + + $attributes = 2; + + # special handling for font files + + my $onefile; + my $found = 0; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $component = $onefile->{'componentname'}; + + if ( $component eq $componentname ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find component in file collection", "get_file_component_attributes"); + } + + my $localstyles = ""; + + if ( $onefile->{'Styles'} ) { $localstyles = $onefile->{'Styles'}; } + + if ( $localstyles =~ /\bFONT\b/ ) + { + $attributes = 8; # font files will be deinstalled if the ref count is 0 + } + + if ( $localstyles =~ /\bASSEMBLY\b/ ) + { + $attributes = 0; # Assembly files cannot run from source + } + + if ( $onefile->{'needs_user_registry_key'} ) + { + $attributes = 4; # Files in non advertised startmenu entries must have user registry key as KeyPath + } + + # Setting msidbComponentAttributes64bit, if this is a 64 bit installation set. + if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) { $attributes |= 256; } + + return $attributes; +} + +############################################################## +# Returning the attributes for a registry component. +# Always 4, indicating, the keypath is a defined in +# table registry +############################################################## + +sub get_registry_component_attributes +{ + my ($componentname, $allvariables) = @_; + + my $attributes; + + $attributes = 4; + + # Setting msidbComponentAttributes64bit, if this is a 64 bit installation set. + if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) { $attributes |= 256; } + + # Setting msidbComponentAttributes64bit for 64 bit shell extension in 32 bit installer, too + if ( $componentname =~ m/winexplorerext_x64/ ) { $attributes |= 256; } + + return $attributes; +} + +############################################################## +# Returning the conditions for a component. +# This is important for language dependent components +# in multilingual installation sets. +############################################################## + +sub get_file_component_condition +{ + my ($componentname, $filesref) = @_; + + my $condition = ""; + + if (exists($installer::globals::componentcondition{$componentname})) + { + $condition = $installer::globals::componentcondition{$componentname}; + } + + # there can be also tree conditions for multilayer products + if (exists($installer::globals::treeconditions{$componentname})) + { + if ( $condition eq "" ) + { + $condition = $installer::globals::treeconditions{$componentname}; + } + else + { + $condition = "($condition) And ($installer::globals::treeconditions{$componentname})"; + } + } + + return $condition +} + +############################################################## +# Returning the conditions for a registry component. +############################################################## + +sub get_component_condition +{ + my ($componentname) = @_; + + my $condition; + + $condition = ""; # Always ? + + if (exists($installer::globals::componentcondition{$componentname})) + { + $condition = $installer::globals::componentcondition{$componentname}; + } + + return $condition +} + +#################################################################### +# Returning the keypath for a component. +# This will be the name of the first file/registry, found in the +# collection $itemsref +# Attention: This has to be the unique (file)name, not the +# real filename! +#################################################################### + +sub get_component_keypath +{ + my ($componentname, $itemsref, $componentidkeypathhashref) = @_; + + my $oneitem; + my $found = 0; + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + $oneitem = ${$itemsref}[$i]; + my $component = $oneitem->{'componentname'}; + + if ( $component eq $componentname ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find component in file/registry collection, function get_component_keypath", "get_component_keypath"); + } + + my $keypath = $oneitem->{'uniquename'}; # "uniquename", not "Name" + + # Special handling for updates from existing databases, because KeyPath must not change + if (( $installer::globals::updatedatabase ) && ( exists($componentidkeypathhashref->{$componentname}) )) + { + $keypath = $componentidkeypathhashref->{$componentname}; + # -> check, if this is a valid key path?! + if ( $keypath ne $oneitem->{'uniquename'} ) + { + # Warning: This keypath was changed because of info from old database + $infoline = "WARNING: The KeyPath for component \"$componentname\" was changed from \"$oneitem->{'uniquename'}\" to \"$keypath\" because of information from update database"; + push(@installer::globals::logfileinfo, $infoline); + } + } + + if ( $oneitem->{'userregkeypath'} ) { $keypath = $oneitem->{'userregkeypath'}; } + + # saving it in the file and registry collection + $oneitem->{'keypath'} = $keypath; + + return $keypath +} + +################################################################### +# Creating the file Componen.idt dynamically +# Content: +# Component ComponentId Directory_ Attributes Condition KeyPath +################################################################### + +sub create_component_table +{ + my ($filesref, $registryref, $dirref, $allfilecomponentsref, $allregistrycomponents, $basedir, $componentidhashref, $componentidkeypathhashref, $allvariables) = @_; + + my @componenttable = (); + + my ($oneline, $infoline); + + installer::windows::idtglobal::write_idt_header(\@componenttable, "component"); + + # File components + + for ( my $i = 0; $i <= $#{$allfilecomponentsref}; $i++ ) + { + my %onecomponent = (); + + $onecomponent{'name'} = ${$allfilecomponentsref}[$i]; + $onecomponent{'guid'} = get_component_guid($onecomponent{'name'}, $componentidhashref); + $onecomponent{'directory'} = get_file_component_directory($onecomponent{'name'}, $filesref, $dirref); + if ( $onecomponent{'directory'} eq "IGNORE_COMP" ) { next; } + $onecomponent{'attributes'} = get_file_component_attributes($onecomponent{'name'}, $filesref, $allvariables); + $onecomponent{'condition'} = get_file_component_condition($onecomponent{'name'}, $filesref); + $onecomponent{'keypath'} = get_component_keypath($onecomponent{'name'}, $filesref, $componentidkeypathhashref); + + $oneline = $onecomponent{'name'} . "\t" . $onecomponent{'guid'} . "\t" . $onecomponent{'directory'} . "\t" + . $onecomponent{'attributes'} . "\t" . $onecomponent{'condition'} . "\t" . $onecomponent{'keypath'} . "\n"; + + push(@componenttable, $oneline); + } + + # Registry components + + for ( my $i = 0; $i <= $#{$allregistrycomponents}; $i++ ) + { + my %onecomponent = (); + + $onecomponent{'name'} = ${$allregistrycomponents}[$i]; + $onecomponent{'guid'} = get_component_guid($onecomponent{'name'}, $componentidhashref); + $onecomponent{'directory'} = get_registry_component_directory(); + $onecomponent{'attributes'} = get_registry_component_attributes($onecomponent{'name'}, $allvariables); + $onecomponent{'condition'} = get_component_condition($onecomponent{'name'}); + $onecomponent{'keypath'} = get_component_keypath($onecomponent{'name'}, $registryref, $componentidkeypathhashref); + + $oneline = $onecomponent{'name'} . "\t" . $onecomponent{'guid'} . "\t" . $onecomponent{'directory'} . "\t" + . $onecomponent{'attributes'} . "\t" . $onecomponent{'condition'} . "\t" . $onecomponent{'keypath'} . "\n"; + + push(@componenttable, $oneline); + } + + # Saving the file + + my $componenttablename = $basedir . $installer::globals::separator . "Componen.idt"; + installer::files::save_file($componenttablename ,\@componenttable); + $infoline = "Created idt file: $componenttablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +#################################################################################### +# Returning a component for a scp module gid. +# Pairs are saved in the files collector. +#################################################################################### + +sub get_component_name_from_modulegid +{ + my ($modulegid, $filesref) = @_; + + my $componentname = ""; + + for my $file ( @{$filesref} ) + { + next if ( ! $file->{'modules'} ); + + my @filemodules = split /,\s*/, $file->{'modules'}; + + if (grep {$_ eq $modulegid} @filemodules) + { + $componentname = $file->{'componentname'}; + last; + } + } + + return $componentname; +} + +#################################################################################### +# Updating the file Environm.idt dynamically +# Content: +# Environment Name Value Component_ +#################################################################################### + +sub set_component_in_environment_table +{ + my ($basedir, $filesref) = @_; + + my $infoline = ""; + + my $environmentfilename = $basedir . $installer::globals::separator . "Environm.idt"; + + if ( -f $environmentfilename ) # only do something, if file exists + { + my $environmentfile = installer::files::read_file($environmentfilename); + + for ( my $i = 3; $i <= $#{$environmentfile}; $i++ ) # starting in line 4 of Environm.idt + { + if ( ${$environmentfile}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $modulegid = $4; # in Environment table a scp module gid can be used as component replacement + + my $componentname = get_component_name_from_modulegid($modulegid, $filesref); + + if ( $componentname ) # only do something if a component could be found + { + $infoline = "Updated Environment table:\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Old line: ${$environmentfile}[$i]\n"; + push(@installer::globals::logfileinfo, $infoline); + + ${$environmentfile}[$i] =~ s/$modulegid/$componentname/; + + $infoline = "New line: ${$environmentfile}[$i]\n"; + push(@installer::globals::logfileinfo, $infoline); + + } + } + } + + # Saving the file + + installer::files::save_file($environmentfilename ,$environmentfile); + $infoline = "Updated idt file: $environmentfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + + } +} + +1; diff --git a/solenv/bin/modules/installer/windows/createfolder.pm b/solenv/bin/modules/installer/windows/createfolder.pm new file mode 100644 index 000000000..a5c534c43 --- /dev/null +++ b/solenv/bin/modules/installer/windows/createfolder.pm @@ -0,0 +1,154 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::createfolder; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +############################################################## +# Returning directory for createfolder table. +############################################################## + +sub get_createfolder_directory +{ + my ($onedir) = @_; + + my $uniquename = $onedir->{'uniquename'}; + + return $uniquename; +} + +############################################################## +# Searching the correct file for language pack directories. +############################################################## + +sub get_languagepack_file +{ + my ($filesref, $onedir) = @_; + + my $language = $onedir->{'specificlanguage'}; + my $foundfile = 0; + my $onefile = ""; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + + if ( $onefile->{'specificlanguage'} eq $onedir->{'specificlanguage'} ) + { + $foundfile = 1; + last; + } + } + + if ( ! $foundfile ) { installer::exiter::exit_program("ERROR: No file with correct language found (language pack build)!", "get_languagepack_file"); } + + return $onefile; +} + +############################################################## +# Returning component for createfolder table. +############################################################## + +sub get_createfolder_component +{ + my ($onedir, $filesref, $allvariableshashref) = @_; + + # Directories do not belong to a module. + # Therefore they can only belong to the root module and + # will be added to a component at the root module. + # All directories will be added to the component + # $allvariableshashref->{'ROOTMODULEGID'} + + if ( ! $allvariableshashref->{'ROOTMODULEGID'} ) { installer::exiter::exit_program("ERROR: ROOTMODULEGID must be defined in list file!", "get_createfolder_component"); } + + my $rootmodulegid = $allvariableshashref->{'ROOTMODULEGID'}; + + my $onefile; + if ( $installer::globals::languagepack ) { $onefile = get_languagepack_file($filesref, $onedir); } + elsif ( $installer::globals::helppack ) { ($onefile) = grep {$_->{gid} eq 'gid_File_Help_Common_Zip'} @{$filesref} } + else { + foreach my $file (@{$filesref}) { + if ($file->{'modules'} eq $rootmodulegid) + { + $onefile = $file; + last; + } + } + } + + if (! defined $onefile) { + installer::exiter::exit_program("ERROR: Could not find file!", "get_createfolder_component"); + } + + return $onefile->{'componentname'}; +} + +#################################################################################### +# Creating the file CreateFo.idt dynamically for creation of empty directories +# Content: +# Directory_ Component_ +#################################################################################### + +sub create_createfolder_table +{ + my ($dirref, $filesref, $basedir, $allvariableshashref) = @_; + + my @createfoldertable = (); + + my $infoline; + + installer::windows::idtglobal::write_idt_header(\@createfoldertable, "createfolder"); + + for ( my $i = 0; $i <= $#{$dirref}; $i++ ) + { + my $onedir = ${$dirref}[$i]; + + # language packs and help packs get only language dependent directories + if (( $installer::globals::languagepack ) || ( $installer::globals::languagepack ) && ( $onedir->{'specificlanguage'} eq "" )) { next }; + + my $styles = ""; + + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + if ( $styles =~ /\bCREATE\b/ ) + { + my %directory = (); + + $directory{'Directory_'} = get_createfolder_directory($onedir); + $directory{'Component_'} = get_createfolder_component($onedir, $filesref, $allvariableshashref); + + my $oneline = $directory{'Directory_'} . "\t" . $directory{'Component_'} . "\n"; + + push(@createfoldertable, $oneline); + } + } + + # Saving the file + + my $createfoldertablename = $basedir . $installer::globals::separator . "CreateFo.idt"; + installer::files::save_file($createfoldertablename ,\@createfoldertable); + $infoline = "Created idt file: $createfoldertablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +1; diff --git a/solenv/bin/modules/installer/windows/directory.pm b/solenv/bin/modules/installer/windows/directory.pm new file mode 100644 index 000000000..829687e8b --- /dev/null +++ b/solenv/bin/modules/installer/windows/directory.pm @@ -0,0 +1,634 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::directory; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::pathanalyzer; +use installer::windows::idtglobal; +use installer::windows::msiglobal; + +############################################################## +# Collecting all directory trees in global hash +############################################################## + +my @msistandarddirectorynames = qw( + AdminToolsFolder + AppDataFolder + CommonAppDataFolder + CommonFiles64Folder + CommonFilesFolder + DesktopFolder + FavoritesFolder + FontsFolder + LocalAppDataFolder + MyPicturesFolder + NetHoodFolder + PersonalFolder + PrintHoodFolder + ProgramFiles64Folder + ProgramFilesFolder + ProgramMenuFolder + RecentFolder + SendToFolder + StartMenuFolder + StartupFolder + System16Folder + System64Folder + SystemFolder + TempFolder + TemplateFolder + WindowsFolder + WindowsVolume +); + +sub collectdirectorytrees +{ + my ( $directoryref ) = @_; + + for ( my $i = 0; $i <= $#{$directoryref}; $i++ ) + { + my $onedir = ${$directoryref}[$i]; + my $styles = ""; + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + if ( $styles ne "" ) + { + foreach my $treestyle ( keys %installer::globals::treestyles ) + { + if ( $styles =~ /\b$treestyle\b/ ) + { + my $hostname = $onedir->{'HostName'}; + # -> hostname is the key, the style the value! + $installer::globals::hostnametreestyles{$hostname} = $treestyle; + } + } + } + } +} + +############################################################## +# Overwriting global programfilesfolder, if required +############################################################## + +sub overwrite_programfilesfolder +{ + my ( $allvariables ) = @_; + + if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) + { + $installer::globals::programfilesfolder = "ProgramFiles64Folder"; + } +} + +############################################################## +# Maximum length of directory name is 72. +# Taking care of underlines, which are the separator. +############################################################## + +sub make_short_dir_version +{ + my ($longstring) = @_; + + my $shortstring = ""; + my $cutlength = 60; + my $length = 5; # So the directory can still be recognized + my $longstring_save = $longstring; + + # Splitting the string at each "underline" and allowing only + # $length characters per directory name. + # Checking also uniqueness and length. + + for my $onestring ( split /_\s*/, $longstring ) + { + my $partstring = ""; + + if ( $onestring =~ /\-/ ) + { + for my $onelocalstring ( split /-\s*/, $onestring ) + { + if ( length($onelocalstring) > $length ) { + $onelocalstring = substr($onelocalstring, 0, $length); + } + $partstring .= "-" . $onelocalstring; + } + $partstring =~ s/^\s*\-//; + } + else + { + if ( length($onestring) > $length ) { + $partstring = substr($onestring, 0, $length); + } + else { + $partstring = $onestring; + } + } + + $shortstring .= "_" . $partstring; + } + + $shortstring =~ s/^\s*\_//; + + # Setting unique ID to each directory + # No counter allowed, process must be absolute reproducible due to patch creation process. + + # chomp(my $id = `echo $longstring_save | md5sum | sed -e "s/ .*//g"`); # Very, very slow + # my $subid = substr($id, 0, 9); # taking only the first 9 digits + + my $subid = installer::windows::msiglobal::calculate_id($longstring_save, 9); # taking only the first 9 digits + + if ( length($shortstring) > $cutlength ) { $shortstring = substr($shortstring, 0, $cutlength); } + + $shortstring = $shortstring . "_" . $subid; + + return $shortstring; +} + +############################################################## +# Adding unique directory names to the directory collection +############################################################## + +my $already_checked_the_frigging_directories_for_uniqueness = 0; + +sub create_unique_directorynames +{ + my ($directoryref, $allvariables) = @_; + + my %completedirhashstep1 = (); + my %shortdirhash = (); + my %shortdirhashreverse = (); + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$directoryref}; $i++ ) + { + my $onedir = ${$directoryref}[$i]; + my $uniquename = $onedir->{'HostName'}; + + my $styles = ""; + if ( $onedir->{'Styles'} ) { $styles = $onedir->{'Styles'}; } + + $uniquename =~ s/^\s*//g; # removing beginning white spaces + $uniquename =~ s/\s*$//g; # removing ending white spaces + $uniquename =~ s/\s//g; # removing white spaces + $uniquename =~ s/\_//g; # removing existing underlines + $uniquename =~ s/\.//g; # removing dots in directoryname + $uniquename =~ s/\Q$installer::globals::separator\E/\_/g; # replacing slash and backslash with underline + $uniquename =~ s/OpenOffice/OO/g; + $uniquename =~ s/LibreOffice/LO/g; + $uniquename =~ s/_registry/_rgy/g; + $uniquename =~ s/_registration/_rgn/g; + $uniquename =~ s/_extension/_ext/g; + $uniquename =~ s/_frame/_frm/g; + $uniquename =~ s/_table/_tbl/g; + $uniquename =~ s/_chart/_crt/g; + $uniquename =~ s/_plat-linux/_plx/g; + + # The names after this small changes must still be unique! + if ( exists($completedirhashstep1{$uniquename}) ) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 1): \"$uniquename\".", "create_unique_directorynames"); } + $completedirhashstep1{$uniquename} = 1; + + # Starting to make unique name for the parent and its directory + my $originaluniquename = $uniquename; + + $uniquename = make_short_dir_version($uniquename); + + # Checking if the same directory already exists, but has another short version. + if (( exists($shortdirhash{$originaluniquename}) ) && ( $shortdirhash{$originaluniquename} ne $uniquename )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 2A): \"$uniquename\".", "create_unique_directorynames"); } + + # Also checking vice versa + # Checking if the same short directory already exists, but has another long version. + if (( exists($shortdirhashreverse{$uniquename}) ) && ( $shortdirhashreverse{$uniquename} ne $originaluniquename )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 2B): \"$uniquename\".", "create_unique_directorynames"); } + + # Creating assignment from long to short directory names + $shortdirhash{$originaluniquename} = $uniquename; + $shortdirhashreverse{$uniquename} = $originaluniquename; + + # Important: The unique parent is generated from the string $originaluniquename (with the use of underlines). + + my $uniqueparentname = $originaluniquename; + my $keepparent = 1; + + if ( $uniqueparentname =~ /^\s*(.*)\_(.*?)\s*$/ ) # the underline is now the separator + { + $uniqueparentname = $1; + $keepparent = 0; + } + else + { + $uniqueparentname = $installer::globals::programfilesfolder; + $keepparent = 1; + } + + if ( $styles =~ /\bPROGRAMFILESFOLDER\b/ ) + { + $uniqueparentname = $installer::globals::programfilesfolder; + $keepparent = 1; + } + if ( $styles =~ /\bCOMMONFILESFOLDER\b/ ) + { + $uniqueparentname = $installer::globals::commonfilesfolder; + $keepparent = 1; + } + if ( $styles =~ /\bCOMMONAPPDATAFOLDER\b/ ) + { + $uniqueparentname = $installer::globals::commonappdatafolder; + $keepparent = 1; + } + if ( $styles =~ /\bLOCALAPPDATAFOLDER\b/ ) + { + $uniqueparentname = $installer::globals::localappdatafolder; + $keepparent = 1; + } + + if ( $styles =~ /\bSHAREPOINTPATH\b/ ) + { + $uniqueparentname = "SHAREPOINTPATH"; + $installer::globals::usesharepointpath = 1; + $keepparent = 1; + } + + # also setting short directory name for the parent + + my $originaluniqueparentname = $uniqueparentname; + + if ( ! $keepparent ) + { + $uniqueparentname = make_short_dir_version($uniqueparentname); + } + + # Again checking if the same directory already exists, but has another short version. + if (( exists($shortdirhash{$originaluniqueparentname}) ) && ( $shortdirhash{$originaluniqueparentname} ne $uniqueparentname )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 3A): \"$uniqueparentname\".", "create_unique_directorynames"); } + + # Also checking vice versa + # Checking if the same short directory already exists, but has another long version. + if (( exists($shortdirhashreverse{$uniqueparentname}) ) && ( $shortdirhashreverse{$uniqueparentname} ne $originaluniqueparentname )) { installer::exiter::exit_program("ERROR: Error in packaging process. Unallowed modification of directory name, not unique (step 3B): \"$uniqueparentname\".", "create_unique_directorynames"); } + + $shortdirhash{$originaluniqueparentname} = $uniqueparentname; + $shortdirhashreverse{$uniqueparentname} = $originaluniqueparentname; + + # Hyphen not allowed in database + $uniquename =~ s/\-/\_/g; # making "-" to "_" + $uniqueparentname =~ s/\-/\_/g; # making "-" to "_" + + # And finally setting the values for the directories + $onedir->{'uniquename'} = $uniquename; + $onedir->{'uniqueparentname'} = $uniqueparentname; + + # setting the installlocation directory + if ( $styles =~ /\bISINSTALLLOCATION\b/ ) + { + if ( $installer::globals::installlocationdirectoryset ) { installer::exiter::exit_program("ERROR: Directory with flag ISINSTALLLOCATION already set: \"$installer::globals::installlocationdirectory\".", "create_unique_directorynames"); } + $installer::globals::installlocationdirectory = $uniquename; + $installer::globals::installlocationdirectoryset = 1; + } + } +} + +##################################################### +# Adding ":." to selected default directory names +##################################################### + +sub check_sourcedir_addon +{ + my ( $onedir, $allvariableshashref ) = @_; + + if (($installer::globals::languagepack) || + ($installer::globals::helppack) || + ($allvariableshashref->{'CHANGETARGETDIR'})) + { + my $sourcediraddon = "\:\."; + $onedir->{'defaultdir'} = $onedir->{'defaultdir'} . $sourcediraddon; + } + +} + +##################################################### +# The directory with the style ISINSTALLLOCATION +# will be replaced by INSTALLLOCATION +##################################################### + +sub set_installlocation_directory +{ + my ( $directoryref, $allvariableshashref ) = @_; + + if ( ! $installer::globals::installlocationdirectoryset ) { installer::exiter::exit_program("ERROR: Directory with flag ISINSTALLLOCATION not set!", "set_installlocation_directory"); } + + for ( my $i = 0; $i <= $#{$directoryref}; $i++ ) + { + my $onedir = ${$directoryref}[$i]; + + if ( $onedir->{'uniquename'} eq $installer::globals::installlocationdirectory ) + { + $onedir->{'uniquename'} = "INSTALLLOCATION"; + check_sourcedir_addon($onedir, $allvariableshashref); + } + + if ( $onedir->{'uniquename'} eq $installer::globals::vendordirectory ) + { + check_sourcedir_addon($onedir, $allvariableshashref); + } + + if ( $onedir->{'uniqueparentname'} eq $installer::globals::installlocationdirectory ) + { + $onedir->{'uniqueparentname'} = "INSTALLLOCATION"; + } + } +} + +##################################################### +# Getting the name of the top level directory. This +# can have only one letter +##################################################### + +sub get_last_directory_name +{ + my ($completepathref) = @_; + + if ( $$completepathref =~ /^.*[\/\\](.+?)\s*$/ ) + { + $$completepathref = $1; + } +} + +##################################################### +# Creating the defaultdir for the file Director.idt +##################################################### + +sub create_defaultdir_directorynames +{ + my ($directoryref, $shortdirnamehashref) = @_; + + my @shortnames = (); + if ( $installer::globals::updatedatabase ) { @shortnames = values(%{$shortdirnamehashref}); } + elsif ( $installer::globals::prepare_winpatch ) { @shortnames = values(%installer::globals::saved83dirmapping); } + + for ( my $i = 0; $i <= $#{$directoryref}; $i++ ) + { + my $onedir = ${$directoryref}[$i]; + my $hostname = $onedir->{'HostName'}; + + $hostname =~ s/\Q$installer::globals::separator\E\s*$//; + get_last_directory_name(\$hostname); + my $uniquename = $onedir->{'uniquename'}; + my $shortstring; + if (( $installer::globals::updatedatabase ) && ( exists($shortdirnamehashref->{$uniquename}) )) + { + $shortstring = $shortdirnamehashref->{$uniquename}; + } + elsif (( $installer::globals::prepare_winpatch ) && ( exists($installer::globals::saved83dirmapping{$uniquename}) )) + { + $shortstring = $installer::globals::saved83dirmapping{$uniquename}; + } + else + { + $shortstring = installer::windows::idtglobal::make_eight_three_conform($hostname, "dir", \@shortnames); + } + + my $defaultdir; + + if ( $shortstring eq $hostname ) + { + $defaultdir = $hostname; + } + else + { + $defaultdir = $shortstring . "|" . $hostname; + } + + $onedir->{'defaultdir'} = $defaultdir; + + my $fontdir = ""; + if ( $onedir->{'Dir'} ) { $fontdir = $onedir->{'Dir'}; } + + my $fontdefaultdir = ""; + if ( $onedir->{'defaultdir'} ) { $fontdefaultdir = $onedir->{'defaultdir'}; } + + if (( $fontdir eq $installer::globals::fontsdirhostname ) && ( $fontdefaultdir eq $installer::globals::fontsdirhostname )) + { + $installer::globals::fontsdirname = $onedir->{'defaultdir'}; + $installer::globals::fontsdirparent = $onedir->{'uniqueparentname'}; + } + } +} + +############################################### +# Fill content into the directory table +############################################### + +sub create_directorytable_from_collection +{ + my ($directorytableref, $directoryref) = @_; + + for ( my $i = 0; $i <= $#{$directoryref}; $i++ ) + { + my $onedir = ${$directoryref}[$i]; + my $hostname = $onedir->{'HostName'}; + my $dir = ""; + + if ( $onedir->{'Dir'} ) { $dir = $onedir->{'Dir'}; } + + if (( $dir eq "PREDEFINED_PROGDIR" ) && ( $hostname eq "" )) { next; } # removing files from root directory + + my $oneline = $onedir->{'uniquename'} . "\t" . $onedir->{'uniqueparentname'} . "\t" . $onedir->{'defaultdir'} . "\n"; + + push(@{$directorytableref}, $oneline); + } +} + +############################################### +# Defining the root installation structure +############################################### + +sub add_root_directories +{ + my ($directorytableref, $allvariableshashref, $onelanguage) = @_; + + my $oneline = ""; + + if (( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack ) && ( ! $allvariableshashref->{'DONTUSESTARTMENUFOLDER'} )) + { + my $productname; + + $productname = $allvariableshashref->{'PRODUCTNAME'}; + my $productversion = $allvariableshashref->{'PRODUCTVERSION'}; + my $baseproductversion = $productversion; + + if (( $installer::globals::prepare_winpatch ) && ( $allvariableshashref->{'BASEPRODUCTVERSION'} )) + { + $baseproductversion = $allvariableshashref->{'BASEPRODUCTVERSION'}; # for example "2.0" for OOo + } + + my $realproductkey = $productname . " " . $productversion; + my $productkey = $productname . " " . $baseproductversion; + + if (( $allvariableshashref->{'POSTVERSIONEXTENSION'} ) && ( ! $allvariableshashref->{'DONTUSEEXTENSIONINDEFAULTDIR'} )) + { + $productkey = $productkey . " " . $allvariableshashref->{'POSTVERSIONEXTENSION'}; + $realproductkey = $realproductkey . " " . $allvariableshashref->{'POSTVERSIONEXTENSION'}; + } + if ( $allvariableshashref->{'NOVERSIONINDIRNAME'} ) + { + $productkey = $productname; + $realproductkey = $realproductname; + } + if ( $allvariableshashref->{'NOSPACEINDIRECTORYNAME'} ) + { + $productkey =~ s/\ /\_/g; + $realproductkey =~ s/\ /\_/g; + } + + my $shortproductkey = installer::windows::idtglobal::make_eight_three_conform($productkey, "dir"); # third parameter not used + $shortproductkey =~ s/\s/\_/g; # changing empty space to underline + + $oneline = "$installer::globals::officemenufolder\t$installer::globals::programmenufolder\t$shortproductkey|$realproductkey\n"; + push(@{$directorytableref}, $oneline); + } + + $oneline = "TARGETDIR\t\tSourceDir\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "WindowsFolder\tTARGETDIR\tWindows\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::programfilesfolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::programmenufolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::startupfolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::desktopfolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::startmenufolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::commonfilesfolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::commonappdatafolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + $oneline = "$installer::globals::localappdatafolder\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + + if ( $installer::globals::usesharepointpath ) + { + $oneline = "SHAREPOINTPATH\tTARGETDIR\t.\n"; + push(@{$directorytableref}, $oneline); + } + + my $localtemplatefoldername = $installer::globals::templatefoldername; + my $directorytableentry = $localtemplatefoldername; + my $shorttemplatefoldername = installer::windows::idtglobal::make_eight_three_conform($localtemplatefoldername, "dir"); + if ( $shorttemplatefoldername ne $localtemplatefoldername ) { $directorytableentry = "$shorttemplatefoldername|$localtemplatefoldername"; } + $oneline = "$installer::globals::templatefolder\tTARGETDIR\t$directorytableentry\n"; + push(@{$directorytableref}, $oneline); + + if ( $installer::globals::fontsdirname ) + { + $oneline = "$installer::globals::fontsfolder\t$installer::globals::fontsdirparent\t$installer::globals::fontsfoldername\:$installer::globals::fontsdirname\n"; + } + else + { + $oneline = "$installer::globals::fontsfolder\tTARGETDIR\t$installer::globals::fontsfoldername\n"; + } + + push(@{$directorytableref}, $oneline); + +} + +############################################### +# Creating the file Director.idt dynamically +############################################### + +sub create_directory_table +{ + my ($directoryref, $languagesarrayref, $basedir, $allvariableshashref, $shortdirnamehashref, $loggingdir) = @_; + + # Structure of the directory table: + # Directory Directory_Parent DefaultDir + # Directory is a unique identifier + # Directory_Parent is the unique identifier of the parent + # DefaultDir is .:APPLIC~1|Application Data with + # Before ":" : [sourcedir]:[destdir] (not programmed yet) + # After ":" : 8+3 and not 8+3 the destination directory name + + for ( my $m = 0; $m <= $#{$languagesarrayref}; $m++ ) + { + my $onelanguage = ${$languagesarrayref}[$m]; + $installer::globals::installlocationdirectoryset = 0; + + my @directorytable = (); + my $infoline; + + overwrite_programfilesfolder($allvariableshashref); + create_unique_directorynames($directoryref, $allvariableshashref); + $already_checked_the_frigging_directories_for_uniqueness++; + create_defaultdir_directorynames($directoryref, $shortdirnamehashref); # only destdir! + set_installlocation_directory($directoryref, $allvariableshashref); + installer::windows::idtglobal::write_idt_header(\@directorytable, "directory"); + add_root_directories(\@directorytable, $allvariableshashref, $onelanguage); + create_directorytable_from_collection(\@directorytable, $directoryref); + + # Saving the file + + my $directorytablename = $basedir . $installer::globals::separator . "Director.idt" . "." . $onelanguage; + installer::files::save_file($directorytablename ,\@directorytable); + $infoline = "Created idt file: $directorytablename\n"; + push(@installer::globals::logfileinfo, $infoline); + } +} + +################################################ +# Check if the string starts with another string +################################################ + +sub starts_with +{ + my ($first, $second) = @_; + + return substr($first, 0, length($second)) eq $second; +} + +############################################### +# Check if the directory prefix is a standard +# directory name. If it is the case, then the +# standard directory name is returned in $var. +############################################### + +sub has_standard_directory_prefix +{ + my ($dir, $var) = @_; + + for my $d (@msistandarddirectorynames) { + if (starts_with($dir, $d) && $dir ne $d) { + installer::logger::print_message("... match found: [$d]\n"); + ${$var} = $d; + return 1; + } + } + + return 0; +} + +1; diff --git a/solenv/bin/modules/installer/windows/feature.pm b/solenv/bin/modules/installer/windows/feature.pm new file mode 100644 index 000000000..356220829 --- /dev/null +++ b/solenv/bin/modules/installer/windows/feature.pm @@ -0,0 +1,403 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::feature; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::worker; +use installer::windows::idtglobal; +use installer::windows::language; + +############################################################## +# Returning the gid for a feature. +# Attention: Maximum length +############################################################## + +sub get_feature_gid +{ + my ($onefeature) = @_; + + my $gid = ""; + + if ( $onefeature->{'gid'} ) { $gid = $onefeature->{'gid'}; } + + # Attention: Maximum feature length is 38! + installer::windows::idtglobal::shorten_feature_gid(\$gid); + + return $gid +} + +############################################################## +# Returning the gid of the parent. +# Attention: Maximum length +############################################################## + +sub get_feature_parent +{ + my ($onefeature) = @_; + + my $parentgid = ""; + + if ( $onefeature->{'ParentID'} ) { $parentgid = $onefeature->{'ParentID'}; } + + # The modules, hanging directly below the root, have to be root modules. + # Only then it is possible to make the "real" root module invisible by + # setting the display to "0". + + if ( $parentgid eq $installer::globals::rootmodulegid ) { $parentgid = ""; } + + # Attention: Maximum feature length is 38! + installer::windows::idtglobal::shorten_feature_gid(\$parentgid); + + return $parentgid +} + +############################################################## +# Returning the display for a feature. +# 0: Feature is not shown +# odd: subfeatures are shown +# even: subfeatures are not shown +############################################################## + +sub get_feature_display +{ + my ($onefeature) = @_; + + my $display; + my $parentid = ""; + + if ( $onefeature->{'ParentID'} ) { $parentid = $onefeature->{'ParentID'}; } + + if ( $parentid eq "" ) + { + $display = "0"; # root module is not visible + } + elsif ( $onefeature->{'gid'} eq "gid_Module_Prg") # program module shows subfeatures + { + $display = "1"; # root module shows subfeatures + } + else + { + $display = "2"; # all other modules do not show subfeatures + } + + # special case: Feature has flag "HIDDEN_ROOT" -> $display is 0 + my $styles = ""; + if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } + if ( $styles =~ /\bHIDDEN_ROOT\b/ ) { $display = "0"; } + + # Special handling for language modules. Only visible in multilingual installation set + if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && ( ! $installer::globals::ismultilingual )) { $display = "0"; } + + # No program module visible. + if ( $onefeature->{'gid'} eq "gid_Module_Prg" ) { $display = "0"; } + + # making all feature invisible in Language packs and in Help packs! + if ( $installer::globals::languagepack || $installer::globals::helppack ) { $display = "0"; } + + return $display +} + +############################################################## +# Returning the level for a feature. +############################################################## + +sub get_feature_level +{ + my ($onefeature) = @_; + + my $level = "20"; # the default + + if ( $onefeature->{'Disabled'} ) + { + if ( $onefeature->{'Disabled'} eq "YES" ) # Disabled = "YES" + { + $level = "0"; # disabled for installation at any INSTALLLEVEL + } + } + elsif ( $onefeature->{'Default'} ) + { + if ( $onefeature->{'Default'} eq "NO" ) # explicitly set Default = "NO" + { + $level = "200"; # deselected in default installation, base is 100 + } + } + + return $level +} + +############################################################## +# Returning the directory for a feature. +############################################################## + +sub get_feature_directory +{ + my ($onefeature) = @_; + + my $directory; + + $directory = "INSTALLLOCATION"; + + return $directory +} + +############################################################## +# Returning the directory for a feature. +############################################################## + +sub get_feature_attributes +{ + my ($onefeature) = @_; + + my $attributes; + + # 2 = msidbFeatureAttributesFollowParent + # 8 = msidbFeatureAttributesDisallowAdvertise + # 16 = msidbFeatureAttributesUIDisallowAbsent + + # No advertising of features and no leaving on network. + # Feature without parent must not have the "2" + + my $parentgid = ""; + if ( $onefeature->{'ParentID'} ) { $parentgid = $onefeature->{'ParentID'}; } + + if (( $parentgid eq "" ) || ( $parentgid eq $installer::globals::rootmodulegid )) { $attributes = "8"; } + elsif ( $onefeature->{'Independent'} && ($onefeature->{'Independent'} eq "YES") ) { $attributes = "8"; } + elsif ( get_feature_display($onefeature) eq "0" ) { $attributes = "26"; } # fdo#33798 + else { $attributes = "10"; } + + return $attributes +} + +################################################################################# +# Collecting the feature recursively. +################################################################################# + +sub collect_modules_recursive +{ + my ($modulesref, $parentid, $feature, $directaccess, $directgid, $directparent, $directsortkey, $sorted) = @_; + + my @allchildren = (); + my $childrenexist = 0; + + # Collecting children from Module $parentid + + my $modulegid; + foreach $modulegid ( keys %{$directparent}) + { + if ( $directparent->{$modulegid} eq $parentid ) + { + push @allchildren, [ $directsortkey->{$modulegid}, $modulegid ]; + $childrenexist = 1; + } + } + + # Sorting children + + if ( $childrenexist ) + { + # Sort children + @allchildren = map { $_->[1] } + sort { $a->[0] <=> $b->[0] } + @allchildren; + + # Adding children to new array + foreach my $gid ( @allchildren ) + { + # Saving all lines, that have this 'gid' + + my $unique; + foreach $unique ( keys %{$directgid} ) + { + if ( $directgid->{$unique} eq $gid ) + { + push(@{$feature}, ${$modulesref}[$directaccess->{$unique}]); + if ( $sorted->{$unique} == 1 ) { installer::exiter::exit_program("ERROR: Sorting feature failed! \"$unique\" already sorted.", "sort_feature"); } + $sorted->{$unique} = 1; + } + } + + collect_modules_recursive($modulesref, $gid, $feature, $directaccess, $directgid, $directparent, $directsortkey, $sorted); + } + } +} + +################################################################################# +# Sorting the feature in specified order. Evaluated is the key "Sortkey", that +# is set in scp2 projects. +# The display order of modules in Windows Installer is dependent from the order +# in the idt file. Therefore the order of the modules array has to be adapted +# to the Sortkey order, before the idt file is created. +################################################################################# + +sub sort_feature +{ + my ($modulesref) = @_; + + my @feature = (); + + my %directaccess = (); + my %directparent = (); + my %directgid = (); + my %directsortkey = (); + my %sorted = (); + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + + my $uniquekey = $onefeature->{'uniquekey'}; + my $modulegid = $onefeature->{'gid'}; + + $directaccess{$uniquekey} = $i; + + $directgid{$uniquekey} = $onefeature->{'gid'}; + + # ParentID and Sortkey are not saved for the 'uniquekey', but only for the 'gid' + + if ( $onefeature->{'ParentID'} ) { $directparent{$modulegid} = $onefeature->{'ParentID'}; } + else { $directparent{$modulegid} = ""; } + + if ( $onefeature->{'Sortkey'} ) { $directsortkey{$modulegid} = $onefeature->{'Sortkey'}; } + else { $directsortkey{$modulegid} = "9999"; } + + # Bookkeeping: + $sorted{$uniquekey} = 0; + } + + # Searching all feature recursively, beginning with ParentID = "" + my $parentid = ""; + collect_modules_recursive($modulesref, $parentid, \@feature, \%directaccess, \%directgid, \%directparent, \%directsortkey, \%sorted); + + # Bookkeeping + my $modulekey; + foreach $modulekey ( keys %sorted ) + { + if ( $sorted{$modulekey} == 0 ) + { + my $infoline = "Warning: Module \"$modulekey\" could not be sorted. Added to the end of the module array.\n"; + push(@installer::globals::logfileinfo, $infoline); + push(@feature, ${$modulesref}[$directaccess{$modulekey}]); + } + } + + return \@feature; +} + +################################################################################# +# Adding a unique key to the modules array. The gid is not unique for +# multilingual modules. Only the combination from gid and specific language +# is unique. Uniqueness is required for sorting mechanism. +################################################################################# + +sub add_uniquekey +{ + my ( $modulesref ) = @_; + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $uniquekey = ${$modulesref}[$i]->{'gid'}; + if ( ${$modulesref}[$i]->{'specificlanguage'} ) { $uniquekey = $uniquekey . "_" . ${$modulesref}[$i]->{'specificlanguage'}; } + ${$modulesref}[$i]->{'uniquekey'} = $uniquekey; + } +} + +################################################################################# +# Creating the file Feature.idt dynamically +# Content: +# Feature Feature_Parent Title Description Display Level Directory_ Attributes +################################################################################# + +sub create_feature_table +{ + my ($modulesref, $basedir, $languagesarrayref, $allvariableshashref) = @_; + + for ( my $m = 0; $m <= $#{$languagesarrayref}; $m++ ) + { + my $onelanguage = ${$languagesarrayref}[$m]; + + my $infoline; + + my @featuretable = (); + + installer::windows::idtglobal::write_idt_header(\@featuretable, "feature"); + + for ( my $i = 0; $i <= $#{$modulesref}; $i++ ) + { + my $onefeature = ${$modulesref}[$i]; + + # Java and Ada only, if the correct settings are set + my $styles = ""; + if ( $onefeature->{'Styles'} ) { $styles = $onefeature->{'Styles'}; } + + # Controlling the language! + # Only language independent feature or feature with the correct language will be included into the table + # But help packs are different. They have en-US added as setup language. + + if (! (!(( $onefeature->{'ismultilingual'} )) || ( $onefeature->{'specificlanguage'} eq $onelanguage ) || $installer::globals::helppack ) ) { next; } + + my %feature = (); + + $feature{'feature'} = get_feature_gid($onefeature); + $feature{'feature_parent'} = get_feature_parent($onefeature); + $feature{'Title'} = $onefeature->{'Name'} // ""; + $feature{'Description'} = $onefeature->{'Description'} // ""; + $feature{'Description'} =~ s/\\\"/\"/g; # no more masquerading of '"' + $feature{'Display'} = get_feature_display($onefeature); + $feature{'Level'} = get_feature_level($onefeature); + $feature{'Directory_'} = get_feature_directory($onefeature); + $feature{'Attributes'} = get_feature_attributes($onefeature); + + my $oneline = $feature{'feature'} . "\t" . $feature{'feature_parent'} . "\t" . $feature{'Title'} . "\t" + . $feature{'Description'} . "\t" . $feature{'Display'} . "\t" . $feature{'Level'} . "\t" + . $feature{'Directory_'} . "\t" . $feature{'Attributes'} . "\n"; + + push(@featuretable, $oneline); + + # collecting all feature in global feature collector (so that properties can be set in property table) + if ( ! grep {$_ eq $feature{'feature'}} @installer::globals::featurecollector ) + { + push(@installer::globals::featurecollector, $feature{'feature'}); + } + + # collecting all language feature in feature collector for check of language selection + if (( $styles =~ /\bSHOW_MULTILINGUAL_ONLY\b/ ) && ( $onefeature->{'ParentID'} ne $installer::globals::rootmodulegid )) + { + $installer::globals::multilingual_only_modules{$feature{'feature'}} = 1; + } + + # collecting all application feature in global feature collector for check of application selection + if ( $styles =~ /\bAPPLICATIONMODULE\b/ ) + { + $installer::globals::application_modules{$feature{'feature'}} = 1; + } + } + + # Saving the file + + my $featuretablename = $basedir . $installer::globals::separator . "Feature.idt" . "." . $onelanguage; + installer::files::save_file($featuretablename ,\@featuretable); + $infoline = "Created idt file: $featuretablename\n"; + push(@installer::globals::logfileinfo, $infoline); + } + +} + +1; diff --git a/solenv/bin/modules/installer/windows/featurecomponent.pm b/solenv/bin/modules/installer/windows/featurecomponent.pm new file mode 100644 index 000000000..26ab9281c --- /dev/null +++ b/solenv/bin/modules/installer/windows/featurecomponent.pm @@ -0,0 +1,165 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::featurecomponent; + +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +################################################################################# +# Collecting all pairs of features and components from the files collector +################################################################################# + +sub create_featurecomponent_table_from_files_collector +{ + my ($featurecomponenttableref, $filesref) = @_; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + my $filecomponent = $onefile->{'componentname'}; + my $filemodules = $onefile->{'modules'}; + + if ( $filecomponent eq "" ) + { + installer::exiter::exit_program("ERROR: No component defined for file $onefile->{'Name'}", "create_featurecomponent_table_from_files_collector"); + } + if ( $filemodules eq "" ) + { + installer::exiter::exit_program("ERROR: No modules found for file $onefile->{'Name'}", "create_featurecomponent_table_from_files_collector"); + } + + my $filemodulesarrayref = installer::converter::convert_stringlist_into_array(\$filemodules, ","); + + for ( my $j = 0; $j <= $#{$filemodulesarrayref}; $j++ ) + { + my %featurecomponent = (); + + my $onemodule = ${$filemodulesarrayref}[$j]; + $onemodule =~ s/\s*$//; + $featurecomponent{'Feature'} = $onemodule; + $featurecomponent{'Component'} = $filecomponent; + + # Attention: Features are renamed, because the maximum length is 38. + # But in the files collector ($filesref), the original names are saved. + + installer::windows::idtglobal::shorten_feature_gid(\$featurecomponent{'Feature'}); + + $oneline = "$featurecomponent{'Feature'}\t$featurecomponent{'Component'}\n"; + + # control of uniqueness + + if (! grep {$_ eq $oneline} @{$featurecomponenttableref}) + { + push(@{$featurecomponenttableref}, $oneline); + } + } + } +} + +################################################################################# +# Collecting all pairs of features and components from the registry collector +################################################################################# + +sub create_featurecomponent_table_from_registry_collector +{ + my ($featurecomponenttableref, $registryref) = @_; + + for ( my $i = 0; $i <= $#{$registryref}; $i++ ) + { + my $oneregistry = ${$registryref}[$i]; + + my $registrycomponent = $oneregistry->{'componentname'}; + my $registrymodule = $oneregistry->{'ModuleID'}; + + if ( $registrycomponent eq "" ) + { + installer::exiter::exit_program("ERROR: No component defined for registry $oneregistry->{'gid'}", "create_featurecomponent_table_from_registry_collector"); + } + if ( $registrymodule eq "" ) + { + installer::exiter::exit_program("ERROR: No modules found for registry $oneregistry->{'gid'}", "create_featurecomponent_table_from_registry_collector"); + } + + my %featurecomponent = (); + + $featurecomponent{'Feature'} = $registrymodule; + $featurecomponent{'Component'} = $registrycomponent; + + # Attention: Features are renamed, because the maximum length is 38. + # But in the files collector ($filesref), the original names are saved. + + installer::windows::idtglobal::shorten_feature_gid(\$featurecomponent{'Feature'}); + + $oneline = "$featurecomponent{'Feature'}\t$featurecomponent{'Component'}\n"; + + # control of uniqueness + + if (! grep {$_ eq $oneline} @{$featurecomponenttableref}) + { + push(@{$featurecomponenttableref}, $oneline); + } + } +} + +################################################################################# +# Creating the file FeatureC.idt dynamically +# Content: +# Feature Component +################################################################################# + +sub create_featurecomponent_table +{ + my ($filesref, $registryref, $basedir) = @_; + + my @featurecomponenttable = (); + my $infoline; + + installer::windows::idtglobal::write_idt_header(\@featurecomponenttable, "featurecomponent"); + + # This is the first time, that features and components are related + # Problem: How about created profiles, configurationfiles, services.rdb + # -> simple solution: putting them all to the root module + # Otherwise profiles and configurationfiles cannot be created the way, they are now created + # -> especially a problem for the configurationfiles! # ToDo + # Very good: All ProfileItems belong to the root + # services.rdb belongs to the root anyway. + + # At the moment only the files are related to components (and the files know their modules). + # The component for each file is written into the files collector $filesinproductlanguageresolvedarrayref + + create_featurecomponent_table_from_files_collector(\@featurecomponenttable, $filesref); + + create_featurecomponent_table_from_registry_collector(\@featurecomponenttable, $registryref); + + # Additional components have to be added here + + # Saving the file + + my $featurecomponenttablename = $basedir . $installer::globals::separator . "FeatureC.idt"; + installer::files::save_file($featurecomponenttablename ,\@featurecomponenttable); + $infoline = "Created idt file: $featurecomponenttablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +1; diff --git a/solenv/bin/modules/installer/windows/file.pm b/solenv/bin/modules/installer/windows/file.pm new file mode 100644 index 000000000..100002f5a --- /dev/null +++ b/solenv/bin/modules/installer/windows/file.pm @@ -0,0 +1,1019 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::file; + +use Digest::MD5; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::worker; +use installer::windows::font; +use installer::windows::idtglobal; +use installer::windows::msiglobal; +use installer::windows::language; +use installer::windows::component; + +########################################################################## +# Assigning one cabinet file to each file. This is required, +# if cabinet files shall be equivalent to packages. +########################################################################## + +sub assign_cab_to_files +{ + my ( $filesref ) = @_; + + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + if ( ! exists(${$filesref}[$i]->{'modules'}) ) { installer::exiter::exit_program("ERROR: No module assignment found for ${$filesref}[$i]->{'gid'} !", "assign_cab_to_files"); } + my $module = ${$filesref}[$i]->{'modules'}; + # If modules contains a list of modules, only taking the first one. + if ( $module =~ /^\s*(.*?)\,/ ) { $module = $1; } + + if ( ! exists($installer::globals::allcabinetassigns{$module}) ) { installer::exiter::exit_program("ERROR: No cabinet file assigned to module \"$module\" (${$filesref}[$i]->{'gid'}) !", "assign_cab_to_files"); } + ${$filesref}[$i]->{'assignedcabinetfile'} = $installer::globals::allcabinetassigns{$module}; + + # Counting the files in each cabinet file + if ( ! exists($installer::globals::cabfilecounter{${$filesref}[$i]->{'assignedcabinetfile'}}) ) + { + $installer::globals::cabfilecounter{${$filesref}[$i]->{'assignedcabinetfile'}} = 1; + } + else + { + $installer::globals::cabfilecounter{${$filesref}[$i]->{'assignedcabinetfile'}}++; + } + } + + # logging the number of files in each cabinet file + + $infoline = "\nCabinet file content:\n"; + push(@installer::globals::logfileinfo, $infoline); + my $cabfile; + foreach $cabfile ( sort keys %installer::globals::cabfilecounter ) + { + $infoline = "$cabfile : $installer::globals::cabfilecounter{$cabfile} files\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + # assigning startsequencenumbers for each cab file + + my $offset = 1; + foreach $cabfile ( sort keys %installer::globals::cabfilecounter ) + { + my $filecount = $installer::globals::cabfilecounter{$cabfile}; + $installer::globals::cabfilecounter{$cabfile} = $offset; + $offset = $offset + $filecount; + + $installer::globals::lastsequence{$cabfile} = $offset - 1; + } + + # logging the start sequence numbers + + $infoline = "\nCabinet file start sequences:\n"; + push(@installer::globals::logfileinfo, $infoline); + foreach $cabfile ( sort keys %installer::globals::cabfilecounter ) + { + $infoline = "$cabfile : $installer::globals::cabfilecounter{$cabfile}\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + # logging the last sequence numbers + + $infoline = "\nCabinet file last sequences:\n"; + push(@installer::globals::logfileinfo, $infoline); + foreach $cabfile ( sort keys %installer::globals::lastsequence ) + { + $infoline = "$cabfile : $installer::globals::lastsequence{$cabfile}\n"; + push(@installer::globals::logfileinfo, $infoline); + } +} + +########################################################################## +# Assigning sequencenumbers to files. This is required, +# if cabinet files shall be equivalent to packages. +########################################################################## + +sub assign_sequencenumbers_to_files +{ + my ( $filesref ) = @_; + + my %directaccess = (); + my %allassigns = (); + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + # Keeping order in cabinet files + # -> collecting all files in one cabinet file + # -> sorting files and assigning numbers + + # Saving counter $i for direct access into files array + # "destination" of the file is a unique identifier ('Name' is not unique!) + if ( exists($directaccess{$onefile->{'destination'}}) ) { installer::exiter::exit_program("ERROR: 'destination' at file not unique: $onefile->{'destination'}", "assign_sequencenumbers_to_files"); } + $directaccess{$onefile->{'destination'}} = $i; + + my $cabfilename = $onefile->{'assignedcabinetfile'}; + # collecting files in cabinet files + if ( ! exists($allassigns{$cabfilename}) ) + { + my %onecabfile = (); + $onecabfile{$onefile->{'destination'}} = 1; + $allassigns{$cabfilename} = \%onecabfile; + } + else + { + $allassigns{$cabfilename}->{$onefile->{'destination'}} = 1; + } + } + + # Sorting each hash and assigning numbers + # The destination of the file determines the sort order, not the filename! + my $cabfile; + foreach $cabfile ( sort keys %allassigns ) + { + my $counter = $installer::globals::cabfilecounter{$cabfile}; + my $dest; + foreach $dest ( sort keys %{$allassigns{$cabfile}} ) # <- sorting the destination! + { + my $directaccessnumber = $directaccess{$dest}; + ${$filesref}[$directaccessnumber]->{'assignedsequencenumber'} = $counter; + $counter++; + } + } +} + +######################################################### +# Create a shorter version of a long component name, +# because maximum length in msi database is 72. +# Attention: In multi msi installation sets, the short +# names have to be unique over all packages, because +# this string is used to create the globally unique id +# -> no resetting of +# %installer::globals::allshortcomponents +# after a package was created. +# Using no counter because of reproducibility. +######################################################### + +sub generate_new_short_componentname +{ + my ($componentname) = @_; + + my $startversion = substr($componentname, 0, 60); # taking only the first 60 characters + my $subid = installer::windows::msiglobal::calculate_id($componentname, 9); # taking only the first 9 digits + my $shortcomponentname = $startversion . "_" . $subid; + + if ( exists($installer::globals::allshortcomponents{$shortcomponentname}) ) { installer::exiter::exit_program("Failed to create unique component name: \"$shortcomponentname\"", "generate_new_short_componentname"); } + + $installer::globals::allshortcomponents{$shortcomponentname} = 1; + + return $shortcomponentname; +} + +############################################### +# Generating the component name from a file +############################################### + +sub get_file_component_name +{ + my ($fileref, $filesref) = @_; + + my $componentname = ""; + + # Special handling for files with ASSIGNCOMPONENT + + my $styles = ""; + if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } + if ( $styles =~ /\bASSIGNCOMPONENT\b/ ) + { + $componentname = get_component_from_assigned_file($fileref->{'AssignComponent'}, $filesref); + } + else + { + # In this function exists the rule to create components from files + # Rule: + # Two files get the same componentid, if: + # both have the same destination directory. + # both have the same "gid" -> both were packed in the same zip file + # All other files are included into different components! + + # my $componentname = $fileref->{'gid'} . "_" . $fileref->{'Dir'}; + + # $fileref->{'Dir'} is not sufficient! All files in a zip file have the same $fileref->{'Dir'}, + # but can be in different subdirectories. + # Solution: destination=share\Scripts\beanshell\Capitalise\capitalise.bsh + # in which the filename (capitalise.bsh) has to be removed and all backslashes (slashes) are + # converted into underline. + + my $destination = $fileref->{'destination'}; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination); + $destination =~ s/\s//g; + $destination =~ s/\\/\_/g; + $destination =~ s/\//\_/g; + $destination =~ s/\_\s*$//g; # removing ending underline + + $componentname = $fileref->{'gid'} . "__" . $destination; + + # Files with different languages, need to be packed into different components. + # Then the installation of the language specific component is determined by a language condition. + + if ( $fileref->{'ismultilingual'} ) + { + my $officelanguage = $fileref->{'specificlanguage'}; + $componentname = $componentname . "_" . $officelanguage; + } + + $componentname = lc($componentname); # componentnames always lowercase + + $componentname =~ s/\-/\_/g; # converting "-" to "_" + $componentname =~ s/\./\_/g; # converting "-" to "_" + + # Attention: Maximum length for the componentname is 72 + # %installer::globals::allcomponents_in_this_database : reset for each database + # %installer::globals::allcomponents : not reset for each database + # Component strings must be unique for the complete product, because they are used for + # the creation of the globally unique identifier. + + my $fullname = $componentname; # This can be longer than 72 + + if (( exists($installer::globals::allcomponents{$fullname}) ) && ( ! exists($installer::globals::allcomponents_in_this_database{$fullname}) )) + { + # This is not allowed: One component cannot be installed with different packages. + installer::exiter::exit_program("ERROR: Component \"$fullname\" is already included into another package. This is not allowed.", "get_file_component_name"); + } + + if ( exists($installer::globals::allcomponents{$fullname}) ) + { + $componentname = $installer::globals::allcomponents{$fullname}; + } + else + { + if ( length($componentname) > 70 ) + { + $componentname = generate_new_short_componentname($componentname); # This has to be unique for the complete product, not only one package + } + + $installer::globals::allcomponents{$fullname} = $componentname; + $installer::globals::allcomponents_in_this_database{$fullname} = 1; + } + + # $componentname =~ s/gid_file_/g_f_/g; + # $componentname =~ s/_extra_/_e_/g; + # $componentname =~ s/_config_/_c_/g; + # $componentname =~ s/_org_openoffice_/_o_o_/g; + # $componentname =~ s/_program_/_p_/g; + # $componentname =~ s/_typedetection_/_td_/g; + # $componentname =~ s/_linguistic_/_l_/g; + # $componentname =~ s/_module_/_m_/g; + # $componentname =~ s/_optional_/_opt_/g; + # $componentname =~ s/_packages/_pack/g; + # $componentname =~ s/_menubar/_mb/g; + # $componentname =~ s/_common_/_cm_/g; + # $componentname =~ s/_export_/_exp_/g; + # $componentname =~ s/_table_/_tb_/g; + # $componentname =~ s/_sofficecfg_/_sc_/g; + # $componentname =~ s/_soffice_cfg_/_sc_/g; + # $componentname =~ s/_startmodulecommands_/_smc_/g; + # $componentname =~ s/_drawimpresscommands_/_dic_/g; + # $componentname =~ s/_basiccommands_/_bac_/g; + # $componentname =~ s/_basicidecommands_/_baic_/g; + # $componentname =~ s/_genericcommands_/_genc_/g; + # $componentname =~ s/_bibliographycommands_/_bibc_/g; + # $componentname =~ s/_gentiumbookbasicbolditalic_/_gbbbi_/g; + # $componentname =~ s/_share_/_s_/g; + # $componentname =~ s/_extension_/_ext_/g; + # $componentname =~ s/_extensions_/_exs_/g; + # $componentname =~ s/_modules_/_ms_/g; + # $componentname =~ s/_uiconfig_zip_/_ucz_/g; + # $componentname =~ s/_productivity_/_pr_/g; + # $componentname =~ s/_wizard_/_wz_/g; + # $componentname =~ s/_import_/_im_/g; + # $componentname =~ s/_javascript_/_js_/g; + # $componentname =~ s/_template_/_tpl_/g; + # $componentname =~ s/_tplwizletter_/_twl_/g; + # $componentname =~ s/_beanshell_/_bs_/g; + # $componentname =~ s/_presentation_/_bs_/g; + # $componentname =~ s/_columns_/_cls_/g; + # $componentname =~ s/_python_/_py_/g; + + # $componentname =~ s/_tools/_ts/g; + # $componentname =~ s/_transitions/_trs/g; + # $componentname =~ s/_scriptbinding/_scrb/g; + # $componentname =~ s/_spreadsheet/_ssh/g; + # $componentname =~ s/_publisher/_pub/g; + # $componentname =~ s/_presenter/_pre/g; + # $componentname =~ s/_registry/_reg/g; + + # $componentname =~ s/screen/sc/g; + # $componentname =~ s/wordml/wm/g; + # $componentname =~ s/openoffice/oo/g; + } + + return $componentname; +} + +#################################################################### +# Returning the component name for a defined file gid. +# This is necessary for files with flag ASSIGNCOMPONENT +#################################################################### + +sub get_component_from_assigned_file +{ + my ($gid, $filesref) = @_; + + my ($onefile) = grep {$_->{gid} eq $gid} @{$filesref}; + if (! defined $onefile) { + installer::exiter::exit_program("ERROR: Could not find file $gid in list of files!", "get_component_from_assigned_file"); + } + + my $componentname = ""; + if ( $onefile->{'componentname'} ) { $componentname = $onefile->{'componentname'}; } + else { installer::exiter::exit_program("ERROR: No component defined for file: $gid", "get_component_from_assigned_file"); } + + return $componentname; +} + +#################################################################### +# Generating the special filename for the database file File.idt +# Sample: CONTEXTS, CONTEXTS1 +# This name has to be unique. +# In most cases this is simply the filename. +#################################################################### + +sub generate_unique_filename_for_filetable +{ + my ($fileref, $component, $uniquefilenamehashref) = @_; + + # This new filename has to be saved into $fileref, because this is needed to find the source. + # The filename sbasic.idx/OFFSETS is changed to OFFSETS, but OFFSETS is not unique. + # In this procedure names like OFFSETS5 are produced. And exactly this string has to be added to + # the array of all files. + + my $uniquefilename = ""; + my $counter = 0; + + if ( $fileref->{'Name'} ) { $uniquefilename = $fileref->{'Name'}; } + + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$uniquefilename); # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs + + # Reading unique filename with help of "Component_" in File table from old database + if (( $installer::globals::updatedatabase ) && ( exists($uniquefilenamehashref->{"$component/$uniquefilename"}) )) + { + $uniquefilename = $uniquefilenamehashref->{"$component/$uniquefilename"}; # syntax of $value: ($uniquename;$shortname) + if ( $uniquefilename =~ /^\s*(.*?)\;\s*(.*?)\s*$/ ) { $uniquefilename = $1; } + $lcuniquefilename = lc($uniquefilename); + $installer::globals::alluniquefilenames{$uniquefilename} = 1; + $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; + return $uniquefilename; + } + elsif (( $installer::globals::prepare_winpatch ) && ( exists($installer::globals::savedmapping{"$component/$uniquefilename"}) )) + { + # If we have a FTK mapping for this component/file, use it. + $installer::globals::savedmapping{"$component/$uniquefilename"} =~ m/^(.*);/; + $uniquefilename = $1; + $lcuniquefilename = lc($uniquefilename); + $installer::globals::alluniquefilenames{$uniquefilename} = 1; + $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; + return $uniquefilename; + } + + $uniquefilename =~ s/\-/\_/g; # no "-" allowed + $uniquefilename =~ s/\@/\_/g; # no "@" allowed + $uniquefilename =~ s/\$/\_/g; # no "$" allowed + $uniquefilename =~ s/^\s*\./\_/g; # no "." at the beginning allowed + $uniquefilename =~ s/^\s*\d/\_d/g; # no number at the beginning allowed (even file "0.gif", replacing to "_d.gif") + $uniquefilename =~ s/org_openoffice_/ooo_/g; # shorten the unique file name + + my $lcuniquefilename = lc($uniquefilename); # only lowercase names + + my $newname = 0; + + if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename}) && + ! exists($installer::globals::savedrevmapping{$lcuniquefilename}) ) + { + $installer::globals::alluniquefilenames{$uniquefilename} = 1; + $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; + $newname = 1; + } + + if ( ! $newname ) + { + # adding a number until the name is really unique: OFFSETS, OFFSETS1, OFFSETS2, ... + # But attention: Making "abc.xcu" to "abc1.xcu" + + my $uniquefilenamebase = $uniquefilename; + + do + { + $counter++; + + if ( $uniquefilenamebase =~ /\./ ) + { + $uniquefilename = $uniquefilenamebase; + $uniquefilename =~ s/\./$counter\./; + } + else + { + $uniquefilename = $uniquefilenamebase . $counter; + } + + $newname = 0; + $lcuniquefilename = lc($uniquefilename); # only lowercase names + + if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename}) && + ! exists($installer::globals::savedrevmapping{$lcuniquefilename}) ) + { + $installer::globals::alluniquefilenames{$uniquefilename} = 1; + $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; + $newname = 1; + } + } + until ( $newname ) + } + + return $uniquefilename; +} + +#################################################################### +# Generating the special file column for the database file File.idt +# Sample: NAMETR~1.TAB|.nametranslation.table +# The first part has to be 8.3 conform. +#################################################################### + +sub generate_filename_for_filetable +{ + my ($fileref, $shortnamesref, $uniquefilenamehashref) = @_; + + my $returnstring = ""; + + my $filename = $fileref->{'Name'}; + + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$filename); # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs + + my $shortstring; + + # Reading short string with help of "FileName" in File table from old database + if (( $installer::globals::updatedatabase ) && ( exists($uniquefilenamehashref->{"$fileref->{'componentname'}/$filename"}) )) + { + my $value = $uniquefilenamehashref->{"$fileref->{'componentname'}/$filename"}; # syntax of $value: ($uniquename;$shortname) + if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ ) { $shortstring = $2; } # already collected in function "collect_shortnames_from_old_database" + else { $shortstring = $filename; } + } + elsif (( $installer::globals::prepare_winpatch ) && ( exists($installer::globals::savedmapping{"$fileref->{'componentname'}/$filename"}) )) + { + $installer::globals::savedmapping{"$fileref->{'componentname'}/$filename"} =~ m/.*;(.*)/; + if ($1 ne '') + { + $shortstring = $1; + } + else + { + $shortstring = installer::windows::idtglobal::make_eight_three_conform_with_hash($filename, "file", $shortnamesref); + } + } + else + { + $shortstring = installer::windows::idtglobal::make_eight_three_conform_with_hash($filename, "file", $shortnamesref); + } + + if ( $shortstring eq $filename ) { $returnstring = $filename; } # nothing changed + else {$returnstring = $shortstring . "\|" . $filename; } + + return $returnstring; +} + +######################################### +# Returning the filesize of a file +######################################### + +sub get_filesize +{ + my ($fileref) = @_; + + my $file = $fileref->{'sourcepath'}; + + my $filesize; + + if ( -f $file ) # test of existence. For instance services.rdb does not always exist + { + $filesize = ( -s $file ); # file size can be "0" + } + else + { + $filesize = -1; + } + + return $filesize; +} + +############################################# +# Returning the file version, if required +# Sample: "8.0.1.8976"; +############################################# + +sub get_fileversion +{ + my ($onefile, $allvariables, $styles) = @_; + + my $fileversion = ""; + + if ( $onefile->{'Name'} =~ /\.bin$|\.com$|\.dll$|\.exe$|\.pyd$/ ) + { + open (EXE, "<$onefile->{'sourcepath'}"); + binmode EXE; + {local $/ = undef; $exedata = ;} + close EXE; + + my $binaryfileversion = "(V\x00S\x00_\x00V\x00E\x00R\x00S\x00I\x00O\x00N\x00_\x00I\x00N\x00F\x00O\x00\x00\x00\x00\x00\xbd\x04\xef\xfe\x00\x00\x01\x00)(........)"; + + if ($exedata =~ /$binaryfileversion/ms) + { + my ($header, $subversion, $version, $vervariant, $microversion) = ($1,unpack( "vvvv", $2)); + $fileversion = $version . "." . $subversion . "." . $microversion . "." . $vervariant; + } + } + # file version for font files (tdf#76239) + if ( $onefile->{'Name'} =~ /\.(otf|ttf|ttc)$/i ) + { + require Font::TTF::Font; + Font::TTF::Font->import; + my $fnt = Font::TTF::Font->open("<$onefile->{'sourcepath'}"); + # 5 is pre-defined name ID for version string - see + # https://docs.microsoft.com/en-us/typography/opentype/spec/name + my $ttfdata = $fnt->{'name'}->read->find_name(5); + $fnt->release; + + if ($ttfdata =~ /(Version )?([0-9]+(\.[0-9]+)*)/i) + { + my ($version, $subversion, $microversion, $vervariant) = split(/\./,$2); + $subversion = 0 if not defined $subversion; + $microversion = 0 if not defined $microversion; + $vervariant = 0 if not defined $vervariant; + $fileversion = int($version) . "." . int($subversion) . "." . int($microversion) . "." . int($vervariant); + } + else + { + $fileversion = "1.0.0.0"; + } + } + + return $fileversion; +} + +############################################# +# Returning the sequence for a file +############################################# + +sub get_sequence_for_file +{ + my ($number, $onefile, $fileentry, $allupdatesequenceshashref, $allupdatecomponentshashref, $allupdatefileorderhashref, $allfilecomponents) = @_; + + my $sequence = ""; + my $infoline = ""; + my $pffcomponentname = $onefile->{'componentname'} . "_pff"; + + if ( $installer::globals::updatedatabase ) + { + if (( exists($allupdatesequenceshashref->{$onefile->{'uniquename'}}) ) && + (( $onefile->{'componentname'} eq $allupdatecomponentshashref->{$onefile->{'uniquename'}} ) || + ( $pffcomponentname eq $allupdatecomponentshashref->{$onefile->{'uniquename'}} ))) + { + # The second condition is necessary to find shifted files, that have same "uniquename", but are now + # located in another directory. This can be seen at the component name. + $sequence = $allupdatesequenceshashref->{$onefile->{'uniquename'}}; + $onefile->{'assignedsequencenumber'} = $sequence; + # Collecting all used sequences, to guarantee, that no number is unused + $installer::globals::allusedupdatesequences{$sequence} = 1; + # Special help for files, that already have a "pff" component name (for example after ServicePack 1) + if ( $pffcomponentname eq $allupdatecomponentshashref->{$onefile->{'uniquename'}} ) + { + $infoline = "Warning: Special handling for component \"$pffcomponentname\". This file was added after the final, but before this ServicePack.\n"; + push(@installer::globals::logfileinfo, $infoline); + $onefile->{'componentname'} = $pffcomponentname; # pff for "post final file" + $fileentry->{'Component_'} = $onefile->{'componentname'}; + if ( ! exists($allfilecomponents->{$fileentry->{'Component_'}}) ) { $allfilecomponents->{$fileentry->{'Component_'}} = 1; } + } + } + else + { + $installer::globals::updatesequencecounter++; + $sequence = $installer::globals::updatesequencecounter; + $onefile->{'assignedsequencenumber'} = $sequence; + # $onefile->{'assignedcabinetfile'} = $installer::globals::pffcabfilename; # assigning to cabinet file for "post final files" + # Collecting all new files + $installer::globals::newupdatefiles{$sequence} = $onefile; + # Saving in sequence hash + $allupdatefileorderhashref->{$sequence} = $onefile->{'uniquename'}; + + # If the new file is part of an existing component, this must be changed now. All files + # of one component have to be included in one cabinet file. But because the order must + # not change, all new files have to be added to new components. + # $onefile->{'componentname'} = $file{'Component_'}; + + $onefile->{'componentname'} = $onefile->{'componentname'} . "_pff"; # pff for "post final file" + $fileentry->{'Component_'} = $onefile->{'componentname'}; + if ( ! exists($allfilecomponents->{$fileentry->{'Component_'}}) ) { $allfilecomponents->{$fileentry->{'Component_'}} = 1; } + $onefile->{'PostFinalFile'} = 1; + # The sequence for this file has changed. It has to be inserted at the end of the files collector. + $installer::globals::insert_file_at_end = 1; + $installer::globals::newfilescollector{$sequence} = $onefile; # Adding new files to the end of the filescollector + $installer::globals::newfilesexist = 1; + } + } + else + { + $sequence = $number; + # my $sequence = $number + 1; + + # Idea: Each component is packed into a cab file. + # This requires that all files in one cab file have sequences directly following each other, + # for instance from 1456 to 1466. Then in the media table the LastSequence for this cab file + # is 1466. + # Because all files belonging to one component are directly behind each other in the file + # collector, it is possible to use simply an increasing number as sequence value. + # If files belonging to one component are not directly behind each other in the files collector + # this mechanism will no longer work. + } + + return $sequence; +} + +############################################# +# Returning the Windows language of a file +############################################# + +sub get_language_for_file +{ + my ($fileref) = @_; + + my $language = ""; + + if ( $fileref->{'specificlanguage'} ) { $language = $fileref->{'specificlanguage'}; } + + if ( $language eq "" ) + { + $language = 0; # language independent + # If this is not a font, the return value should be "0" (Check ICE 60) + my $styles = ""; + if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } + if ( $styles =~ /\bFONT\b/ ) { $language = ""; } + } + else + { + $language = installer::windows::language::get_windows_language($language); + } + + return $language; +} + +#################################################################### +# Creating a new KeyPath for components in TemplatesFolder. +#################################################################### + +sub generate_registry_keypath +{ + my ($onefile) = @_; + + my $keypath = $onefile->{'Name'}; + $keypath =~ s/\.//g; + $keypath = lc($keypath); + $keypath = "userreg_" . $keypath; + + return $keypath; +} + +#################################################################### +# Check, if in an update process files are missing. No removal +# of files allowed for Windows Patch creation. +# Also logging all new files, that have to be included in extra +# components and cab files. +#################################################################### + +sub check_file_sequences +{ + my ($allupdatefileorderhashref, $allupdatecomponentorderhashref) = @_; + + # All used sequences stored in %installer::globals::allusedupdatesequences + # Maximum sequence number of old database stored in $installer::globals::updatelastsequence + # All new files stored in %installer::globals::newupdatefiles + + my $infoline = ""; + + my @missing_sequences = (); + my @really_missing_sequences = (); + + for ( my $i = 1; $i <= $installer::globals::updatelastsequence; $i++ ) + { + if ( ! exists($installer::globals::allusedupdatesequences{$i}) ) { push(@missing_sequences, $i); } + } + + if ( $#missing_sequences > -1 ) + { + # Missing sequences can also be caused by files included in merge modules. This files are added later into the file table. + # Therefore now it is time to check the content of the merge modules. + + for ( my $j = 0; $j <= $#missing_sequences; $j++ ) + { + my $filename = $allupdatefileorderhashref->{$missing_sequences[$j]}; + + # Is this a file from a merge module? Then this is no error. + if ( ! exists($installer::globals::mergemodulefiles{$filename}) ) + { + push(@really_missing_sequences, $missing_sequences[$j]); + } + } + } + + if ( $#really_missing_sequences > -1 ) + { + my $errorstring = ""; + for ( my $j = 0; $j <= $#really_missing_sequences; $j++ ) + { + my $filename = $allupdatefileorderhashref->{$really_missing_sequences[$j]}; + my $comp = $allupdatecomponentorderhashref->{$really_missing_sequences[$j]}; + $errorstring = "$errorstring$filename (Sequence: $really_missing_sequences[$j], Component: \"$comp\")\n"; + } + + $infoline = "ERROR: Files are removed compared with update database.\nThe following files are missing:\n$errorstring"; + push(@installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program($infoline, "check_file_sequences"); + } + + # Searching for new files + + my $counter = 0; + + foreach my $key ( keys %installer::globals::newupdatefiles ) + { + my $onefile = $installer::globals::newupdatefiles{$key}; + $counter++; + if ( $counter == 1 ) + { + $infoline = "\nNew files compared to the update database:\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + $infoline = "$onefile->{'Name'} ($onefile->{'gid'}) Sequence: $onefile->{'assignedsequencenumber'}\n"; + push(@installer::globals::logfileinfo, $infoline); + } + + if ( $counter == 0 ) + { + $infoline = "Info: No new file compared with update database!\n"; + push(@installer::globals::logfileinfo, $infoline); + } + +} + +################################################################### +# Collecting further conditions for the component table. +# This is used by multilayer products, to enable installation +# of separate layers. +################################################################### + +sub get_tree_condition_for_component +{ + my ($onefile, $componentname) = @_; + + if ( $onefile->{'destination'} ) + { + my $dest = $onefile->{'destination'}; + + # Comparing the destination path with + # $installer::globals::hostnametreestyles{$hostname} = $treestyle; + # (-> hostname is the key, the style the value!) + + foreach my $hostname ( keys %installer::globals::hostnametreestyles ) + { + if (( $dest eq $hostname ) || ( $dest =~ /^\s*\Q$hostname\E\\/ )) + { + # the value is the style + my $style = $installer::globals::hostnametreestyles{$hostname}; + # the condition is saved in %installer::globals::treestyles + my $condition = $installer::globals::treestyles{$style}; + # Saving condition to be added in table Property + $installer::globals::usedtreeconditions{$condition} = 1; + $condition = $condition . "=1"; + # saving this condition + $installer::globals::treeconditions{$componentname} = $condition; + + # saving also at the file, for usage in fileinfo + $onefile->{'layer'} = $installer::globals::treelayername{$style}; + } + } + } +} + +############################################ +# Collecting all short names, that are +# already used by the old database +############################################ + +sub collect_shortnames_from_old_database +{ + my ($uniquefilenamehashref, $shortnameshashref) = @_; + + foreach my $key ( keys %{$uniquefilenamehashref} ) + { + my $value = $uniquefilenamehashref->{$key}; # syntax of $value: ($uniquename;$shortname) + + if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ ) + { + my $shortstring = $2; + $shortnameshashref->{$shortstring} = 1; # adding the shortname to the array of all shortnames + } + } +} + +############################################ +# Creating the file File.idt dynamically +############################################ + +sub create_files_table +{ + my ($filesref, $dirref, $allfilecomponentsref, $basedir, $allvariables, $uniquefilenamehashref, $allupdatesequenceshashref, $allupdatecomponentshashref, $allupdatefileorderhashref) = @_; + + installer::logger::include_timestamp_into_logfile("Performance Info: File Table start"); + + # Structure of the files table: + # File Component_ FileName FileSize Version Language Attributes Sequence + # In this function, all components are created. + # + # $allfilecomponentsref is empty at the beginning + + my $infoline; + + my @allfiles = (); + my @filetable = (); + my @filehashtable = (); + my %allfilecomponents = (); + my $counter = 0; + + if ( $^O =~ /cygwin/i ) { installer::worker::generate_cygwin_paths($filesref); } + + # The filenames must be collected because of uniqueness + # 01-44-~1.DAT, 01-44-~2.DAT, ... + my %shortnames = (); + + if ( $installer::globals::updatedatabase ) { collect_shortnames_from_old_database($uniquefilenamehashref, \%shortnames); } + + installer::windows::idtglobal::write_idt_header(\@filetable, "file"); + installer::windows::idtglobal::write_idt_header(\@filehashtable, "filehash"); + installer::windows::idtglobal::write_idt_header(\@installer::globals::removefiletable, "removefile"); + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my %file = (); + + my $onefile = ${$filesref}[$i]; + + my $styles = ""; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + + $file{'Component_'} = get_file_component_name($onefile, $filesref); + $file{'File'} = generate_unique_filename_for_filetable($onefile, $file{'Component_'}, $uniquefilenamehashref); + + $onefile->{'uniquename'} = $file{'File'}; + $onefile->{'componentname'} = $file{'Component_'}; + + # Collecting all components + + if ( ! exists($allfilecomponents{$file{'Component_'}}) ) { $allfilecomponents{$file{'Component_'}} = 1; } + + $file{'FileName'} = generate_filename_for_filetable($onefile, \%shortnames, $uniquefilenamehashref); + + $file{'FileSize'} = get_filesize($onefile); + + $file{'Version'} = get_fileversion($onefile, $allvariables, $styles); + + $file{'Language'} = get_language_for_file($onefile); + + if ( $styles =~ /\bDONT_PACK\b/ ) { $file{'Attributes'} = "8192"; } + else { $file{'Attributes'} = "16384"; } + + # $file{'Attributes'} = "16384"; # Sourcefile is packed + # $file{'Attributes'} = "8192"; # Sourcefile is unpacked + + $installer::globals::insert_file_at_end = 0; + $counter++; + $file{'Sequence'} = get_sequence_for_file($counter, $onefile, \%file, $allupdatesequenceshashref, $allupdatecomponentshashref, $allupdatefileorderhashref, \%allfilecomponents); + + $onefile->{'sequencenumber'} = $file{'Sequence'}; + + my $oneline = $file{'File'} . "\t" . $file{'Component_'} . "\t" . $file{'FileName'} . "\t" + . $file{'FileSize'} . "\t" . $file{'Version'} . "\t" . $file{'Language'} . "\t" + . $file{'Attributes'} . "\t" . $file{'Sequence'} . "\n"; + + push(@filetable, $oneline); + + if ( $file{'File'} =~ /\.py$/ ) + { + my %removefile = (); + + $removefile{'FileKey'} = "remove_" . $file{'File'} . "c"; + $removefile{'Component_'} = $file{'Component_'}; + $removefile{'FileName'} = $file{'FileName'}; + $removefile{'FileName'} =~ s/\.py$/.pyc/; + $removefile{'FileName'} =~ s/\.PY\|/.PYC|/; + $removefile{'DirProperty'} = installer::windows::component::get_file_component_directory($file{'Component_'}, $filesref, $dirref); + $removefile{'InstallMode'} = 2; # msiInstallStateAbsent + $oneline = $removefile{'FileKey'} . "\t" . $removefile{'Component_'} . "\t" . $removefile{'FileName'} . "\t" + . $removefile{'DirProperty'} . "\t" . $removefile{'InstallMode'} . "\n"; + + push(@installer::globals::removefiletable, $oneline); + } + + if ( ! $installer::globals::insert_file_at_end ) { push(@allfiles, $onefile); } + + # Collecting all component conditions + if ( $onefile->{'ComponentCondition'} ) + { + if ( ! exists($installer::globals::componentcondition{$file{'Component_'}})) + { + $installer::globals::componentcondition{$file{'Component_'}} = $onefile->{'ComponentCondition'}; + } + } + + # Collecting also all tree conditions for multilayer products + get_tree_condition_for_component($onefile, $file{'Component_'}); + + unless ( $file{'Version'} ) + { + my $path = $onefile->{'sourcepath'}; + if ( $^O =~ /cygwin/i ) { $path = $onefile->{'cyg_sourcepath'}; } + + open(FILE, $path) or die "ERROR: Can't open $path for creating file hash"; + binmode(FILE); + my $hashinfo = pack("l", 20); + $hashinfo .= Digest::MD5->new->addfile(*FILE)->digest; + + my @i = unpack ('x[l]l4', $hashinfo); + $oneline = $file{'File'} . "\t" . + "0" . "\t" . + $i[0] . "\t" . + $i[1] . "\t" . + $i[2] . "\t" . + $i[3] . "\n"; + push (@filehashtable, $oneline); + } + + # Saving the sequence number in a hash with uniquefilename as key. + # This is used for better performance in "save_packorder" + $installer::globals::uniquefilenamesequence{$onefile->{'uniquename'}} = $onefile->{'sequencenumber'}; + + my $destdir = ""; + if ( $onefile->{'Dir'} ) { $destdir = $onefile->{'Dir'}; } + + if ( $onefile->{'needs_user_registry_key'} ) + { + my $keypath = generate_registry_keypath($onefile); + $onefile->{'userregkeypath'} = $keypath; + push(@installer::globals::userregistrycollector, $onefile); + $installer::globals::addeduserregitrykeys = 1; + } + } + + # putting content from %allfilecomponents to $allfilecomponentsref for later usage + foreach $localkey (keys %allfilecomponents ) { push( @{$allfilecomponentsref}, $localkey); } + + my $filetablename = $basedir . $installer::globals::separator . "File.idt"; + installer::files::save_file($filetablename ,\@filetable); + $infoline = "\nCreated idt file: $filetablename\n"; + push(@installer::globals::logfileinfo, $infoline); + + installer::logger::include_timestamp_into_logfile("Performance Info: File Table end"); + + my $filehashtablename = $basedir . $installer::globals::separator . "MsiFileHash.idt"; + installer::files::save_file($filehashtablename ,\@filehashtable); + $infoline = "\nCreated idt file: $filehashtablename\n"; + push(@installer::globals::logfileinfo, $infoline); + + # Now the new files can be added to the files collector (only in update packaging processes) + if ( $installer::globals::newfilesexist ) + { + foreach my $seq (sort keys %installer::globals::newfilescollector) { push(@allfiles, $installer::globals::newfilescollector{$seq}) } + } + + return \@allfiles; +} + +1; diff --git a/solenv/bin/modules/installer/windows/font.pm b/solenv/bin/modules/installer/windows/font.pm new file mode 100644 index 000000000..f1b678870 --- /dev/null +++ b/solenv/bin/modules/installer/windows/font.pm @@ -0,0 +1,69 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::font; + +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + + +################################################################################# +# Creating the file Font.idt dynamically +# Content: +# File_ FontTitle +################################################################################# + +sub create_font_table +{ + my ($filesref, $basedir) = @_; + + my @fonttable = (); + + installer::windows::idtglobal::write_idt_header(\@fonttable, "font"); + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + my $styles = ""; + + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; } + + if ( $styles =~ /\bFONT\b/ ) + { + my %font = (); + + $font{'File_'} = $onefile->{'uniquename'}; + $font{'FontTitle'} = ""; + + my $oneline = $font{'File_'} . "\t" . $font{'FontTitle'} . "\n"; + + push(@fonttable, $oneline); + } + } + + # Saving the file + + my $fonttablename = $basedir . $installer::globals::separator . "Font.idt"; + installer::files::save_file($fonttablename ,\@fonttable); + my $infoline = "Created idt file: $fonttablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +1; diff --git a/solenv/bin/modules/installer/windows/icon.pm b/solenv/bin/modules/installer/windows/icon.pm new file mode 100644 index 000000000..10cd24e46 --- /dev/null +++ b/solenv/bin/modules/installer/windows/icon.pm @@ -0,0 +1,68 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::icon; + +use installer::files; +use installer::globals; +use installer::pathanalyzer; +use installer::windows::idtglobal; + +########################################################################################################### +# Creating the file Icon.idt dynamically +# Content: +# Name Data +########################################################################################################### + +sub create_icon_table +{ + my ($iconfilecollector, $basedir) = @_; + + my @icontable = (); + + installer::windows::idtglobal::write_idt_header(\@icontable, "icon"); + + # Only the iconfiles, that are used in the shortcut table for the + # FolderItems (entries in Windows startmenu) are added into the icon table. + + for ( my $i = 0; $i <= $#{$iconfilecollector}; $i++ ) + { + my $iconfile = ${$iconfilecollector}[$i]; + + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$iconfile); + + my %icon = (); + + $icon{'Name'} = $iconfile; # simply soffice.exe + $icon{'Data'} = $iconfile; # simply soffice.exe + + my $oneline = $icon{'Name'} . "\t" . $icon{'Data'} . "\n"; + + push(@icontable, $oneline); + } + + # Saving the file + + my $icontablename = $basedir . $installer::globals::separator . "Icon.idt"; + installer::files::save_file($icontablename ,\@icontable); + my $infoline = "Created idt file: $icontablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +1; diff --git a/solenv/bin/modules/installer/windows/idtglobal.pm b/solenv/bin/modules/installer/windows/idtglobal.pm new file mode 100644 index 000000000..26c8e951c --- /dev/null +++ b/solenv/bin/modules/installer/windows/idtglobal.pm @@ -0,0 +1,1862 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::idtglobal; + +use Cwd; +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::pathanalyzer; +use installer::remover; +use installer::scriptitems; +use installer::systemactions; +use installer::windows::language; + +############################################################## +# Shorten the gid for a feature. +# Attention: Maximum length is 38 +############################################################## + +sub shorten_feature_gid +{ + my ($stringref) = @_; + + $$stringref =~ s/gid_Module_/gm_/; + $$stringref =~ s/_Extension_/_ex_/; + $$stringref =~ s/_Root_/_r_/; + $$stringref =~ s/_Prg_/_p_/; + $$stringref =~ s/_Optional_/_o_/; + $$stringref =~ s/_Tools_/_tl_/; + $$stringref =~ s/_Wrt_Flt_/_w_f_/; + $$stringref =~ s/_Productivity_/_pr_/; +# $$stringref =~ s/_Replacement_/_rpl_/; # native373 fix +} + +############################################ +# Getting the next free number, that +# can be added. +# Sample: 01-44-~1.DAT, 01-44-~2.DAT, ... +############################################ + +sub get_next_free_number +{ + my ($name, $shortnamesref) = @_; + + my $counter = 0; + my $dontsave = 0; + my $alreadyexists; + my ($newname, $shortname); + + do + { + $alreadyexists = 0; + $counter++; + $newname = $name . $counter; + + for ( my $i = 0; $i <= $#{$shortnamesref}; $i++ ) + { + $shortname = ${$shortnamesref}[$i]; + + if ( uc($shortname) eq uc($newname) ) # case insensitive + { + $alreadyexists = 1; + last; + } + } + } + until (!($alreadyexists)); + + if (( $counter > 9 ) && ( length($name) > 6 )) { $dontsave = 1; } + if (( $counter > 99 ) && ( length($name) > 5 )) { $dontsave = 1; } + + if (!($dontsave)) + { + push(@{$shortnamesref}, $newname); # adding the new shortname to the array of shortnames + } + + return $counter +} + +############################################ +# Getting the next free number, that +# can be added. +# Sample: 01-44-~1.DAT, 01-44-~2.DAT, ... +############################################ + +sub get_next_free_number_with_hash +{ + my ($name, $shortnamesref, $ext) = @_; + + my $counter = 0; + my $dontsave = 0; + my $saved = 0; + my $alreadyexists; + my ($newname, $shortname); + + do + { + $alreadyexists = 0; + $counter++; + $newname = $name . $counter; + $newname = uc($newname); # case insensitive, always upper case + if ( exists($shortnamesref->{$newname}) || + exists($installer::globals::savedrev83mapping{$newname.$ext}) ) + { + $alreadyexists = 1; + } + } + until (!($alreadyexists)); + + if (( $counter > 9 ) && ( length($name) > 6 )) { $dontsave = 1; } + if (( $counter > 99 ) && ( length($name) > 5 )) { $dontsave = 1; } + + if (!($dontsave)) + { + $shortnamesref->{$newname} = 1; # adding the new shortname to the array of shortnames, always uppercase + $saved = 1; + } + + return ( $counter, $saved ) +} + +######################################### +# 8.3 for filenames and directories +######################################### + +sub make_eight_three_conform +{ + my ($inputstring, $pattern, $shortnamesref) = @_; + + # all shortnames are collected in $shortnamesref, because of uniqueness + + my ($name, $namelength, $number); + my $conformstring = ""; + my $changed = 0; + + if (( $inputstring =~ /^\s*(.*?)\.(.*?)\s*$/ ) && ( $pattern eq "file" )) # files with a dot + { + $name = $1; + my $extension = $2; + + $namelength = length($name); + my $extensionlength = length($extension); + + if ( $extensionlength > 3 ) + { + # simply taking the first three letters + $extension = substr($extension, 0, 3); # name, offset, length + } + + # Attention: readme.html -> README~1.HTM + + if (( $namelength > 8 ) || ( $extensionlength > 3 )) + { + # taking the first six letters + $name = substr($name, 0, 6); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + $number = get_next_free_number($name, $shortnamesref); + + # if $number>9 the new name would be "abcdef~10.xyz", which is 9+3, and therefore not allowed + + if ( $number > 9 ) + { + $name = substr($name, 0, 5); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + $number = get_next_free_number($name, $shortnamesref); + + if ( $number > 99 ) + { + $name = substr($name, 0, 4); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + $number = get_next_free_number($name, $shortnamesref); + } + } + + $name = $name . "$number"; + + $changed = 1; + } + + $conformstring = $name . "\." . $extension; + + if ( $changed ) { $conformstring= uc($conformstring); } + } + else # no dot in filename or directory (also used for shortcuts) + { + $name = $inputstring; + $namelength = length($name); + + if ( $namelength > 8 ) + { + # taking the first six letters + $name = substr($name, 0, 6); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + $number = get_next_free_number($name, $shortnamesref); + + # if $number>9 the new name would be "abcdef~10.xyz", which is 9+3, and therefore not allowed + + if ( $number > 9 ) + { + $name = substr($name, 0, 5); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + $number = get_next_free_number($name, $shortnamesref); + + if ( $number > 99 ) + { + $name = substr($name, 0, 4); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + $number = get_next_free_number($name, $shortnamesref); + } + } + + $name = $name . "$number"; + $changed = 1; + if ( $pattern eq "dir" ) { $name =~ s/\./\_/g; } # in directories replacing "." with "_" + } + + $conformstring = $name; + + if ( $changed ) { $conformstring = uc($name); } + } + + return $conformstring; +} + +######################################### +# 8.3 for filenames and directories +# $shortnamesref is a hash in this case +# -> performance reasons +######################################### + +sub make_eight_three_conform_with_hash +{ + my ($inputstring, $pattern, $shortnamesref) = @_; + + # all shortnames are collected in $shortnamesref, because of uniqueness (a hash!) + + my ($name, $namelength, $number); + my $conformstring = ""; + my $changed = 0; + my $saved; + + if (( $inputstring =~ /^\s*(.*)\.(.*?)\s*$/ ) && ( $pattern eq "file" )) # files with a dot + { + # extension has to be non-greedy, but name is. This is important to find the last dot in the filename + $name = $1; + my $extension = $2; + + if ( $name =~ /^\s*(.*?)\s*$/ ) { $name = $1; } # now the name is also non-greedy + $name =~ s/\.//g; # no dots in 8+3 conform filename + + $namelength = length($name); + my $extensionlength = length($extension); + + if ( $extensionlength > 3 ) + { + # simply taking the first three letters + $extension = substr($extension, 0, 3); # name, offset, length + $changed = 1; + } + + # Attention: readme.html -> README~1.HTM + + if (( $namelength > 8 ) || ( $extensionlength > 3 )) + { + # taking the first six letters, if filename is longer than 6 characters + if ( $namelength > 6 ) + { + $name = substr($name, 0, 6); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + ($number, $saved) = get_next_free_number_with_hash($name, $shortnamesref, '.'.uc($extension)); + + # if $number>9 the new name would be "abcdef~10.xyz", which is 9+3, and therefore not allowed + + if ( ! $saved ) + { + $name = substr($name, 0, 5); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + ($number, $saved) = get_next_free_number_with_hash($name, $shortnamesref, '.'.uc($extension)); + + # if $number>99 the new name would be "abcde~100.xyz", which is 9+3, and therefore not allowed + + if ( ! $saved ) + { + $name = substr($name, 0, 4); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + ($number, $saved) = get_next_free_number_with_hash($name, $shortnamesref, '.'.uc($extension)); + + if ( ! $saved ) + { + installer::exiter::exit_program("ERROR: Could not set 8+3 conform name for $inputstring !", "make_eight_three_conform_with_hash"); + } + } + } + + $name = $name . "$number"; + $changed = 1; + } + } + + $conformstring = $name . "\." . $extension; + + if ( $changed ) { $conformstring= uc($conformstring); } + } + else # no dot in filename or directory (also used for shortcuts) + { + $name = $inputstring; + $namelength = length($name); + + if ( $namelength > 8 ) + { + # taking the first six letters + $name = substr($name, 0, 6); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + ( $number, $saved ) = get_next_free_number_with_hash($name, $shortnamesref, ''); + + # if $number>9 the new name would be "abcdef~10", which is 9+0, and therefore not allowed + + if ( ! $saved ) + { + $name = substr($name, 0, 5); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + ( $number, $saved ) = get_next_free_number_with_hash($name, $shortnamesref, ''); + + # if $number>99 the new name would be "abcde~100", which is 9+0, and therefore not allowed + + if ( ! $saved ) + { + $name = substr($name, 0, 4); # name, offset, length + $name =~ s/\s*$//; # removing ending whitespaces + $name = $name . "\~"; + ( $number, $saved ) = get_next_free_number_with_hash($name, $shortnamesref, ''); + + if ( ! $saved ) { installer::exiter::exit_program("ERROR: Could not set 8+3 conform name for $inputstring !", "make_eight_three_conform_with_hash"); } + } + } + + $name = $name . "$number"; + $changed = 1; + if ( $pattern eq "dir" ) { $name =~ s/\./\_/g; } # in directories replacing "." with "_" + } + + $conformstring = $name; + + if ( $changed ) { $conformstring = uc($name); } + } + + return $conformstring; +} + +######################################### +# Writing the header for idt files +######################################### + +sub write_idt_header +{ + my ($idtref, $definestring) = @_; + + my $oneline; + + if ( $definestring eq "file" ) + { + $oneline = "File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts72\tl255\ti4\tS72\tS20\tI2\ti4\n"; + push(@{$idtref}, $oneline); + $oneline = "File\tFile\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "filehash" ) + { + $oneline = "File_\tOptions\tHashPart1\tHashPart2\tHashPart3\tHashPart4\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ti2\ti4\ti4\ti4\ti4\n"; + push(@{$idtref}, $oneline); + $oneline = "MsiFileHash\tFile_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "directory" ) + { + $oneline = "Directory\tDirectory_Parent\tDefaultDir\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\tS72\tl255\n"; + push(@{$idtref}, $oneline); + $oneline = "Directory\tDirectory\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "component" ) + { + $oneline = "Component\tComponentId\tDirectory_\tAttributes\tCondition\tKeyPath\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\tS38\ts72\ti2\tS255\tS72\n"; + push(@{$idtref}, $oneline); + $oneline = "Component\tComponent\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "feature" ) + { + $oneline = "Feature\tFeature_Parent\tTitle\tDescription\tDisplay\tLevel\tDirectory_\tAttributes\n"; + push(@{$idtref}, $oneline); + $oneline = "s38\tS38\tL64\tL255\tI2\ti2\tS72\ti2\n"; + push(@{$idtref}, $oneline); + $oneline = "65001\tFeature\tFeature\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "featurecomponent" ) + { + $oneline = "Feature_\tComponent_\n"; + push(@{$idtref}, $oneline); + $oneline = "s38\ts72\n"; + push(@{$idtref}, $oneline); + $oneline = "FeatureComponents\tFeature_\tComponent_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "media" ) + { + $oneline = "DiskId\tLastSequence\tDiskPrompt\tCabinet\tVolumeLabel\tSource\n"; + push(@{$idtref}, $oneline); + $oneline = "i2\ti4\tL64\tS255\tS32\tS72\n"; + push(@{$idtref}, $oneline); + $oneline = "Media\tDiskId\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "font" ) + { + $oneline = "File_\tFontTitle\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\tS128\n"; + push(@{$idtref}, $oneline); + $oneline = "Font\tFile_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "shortcut" ) + { + $oneline = "Shortcut\tDirectory_\tName\tComponent_\tTarget\tArguments\tDescription\tHotkey\tIcon_\tIconIndex\tShowCmd\tWkDir\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts72\tl128\ts72\ts72\tS255\tL255\tI2\tS72\tI2\tI2\tS72\n"; + push(@{$idtref}, $oneline); + $oneline = "65001\tShortcut\tShortcut\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "msishortcutproperty" ) + { + $oneline = "MsiShortcutProperty\tShortcut_\tPropertyKey\tPropVariantValue\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts72\ts255\ts255\n"; + push(@{$idtref}, $oneline); + $oneline = "MsiShortcutProperty\tMsiShortcutProperty\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "registry" ) + { + $oneline = "Registry\tRoot\tKey\tName\tValue\tComponent_\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ti2\tl255\tL255\tL0\ts72\n"; + push(@{$idtref}, $oneline); + $oneline = "Registry\tRegistry\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "createfolder" ) + { + $oneline = "Directory_\tComponent_\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts72\n"; + push(@{$idtref}, $oneline); + $oneline = "CreateFolder\tDirectory_\tComponent_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "removefile" ) + { + $oneline = "FileKey\tComponent_\tFileName\tDirProperty\tInstallMode\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts72\tL255\ts72\ti2\n"; + push(@{$idtref}, $oneline); + $oneline = "RemoveFile\tFileKey\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "upgrade" ) + { + $oneline = "UpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\tRemove\tActionProperty\n"; + push(@{$idtref}, $oneline); + $oneline = "s38\tS20\tS20\tS255\ti4\tS255\ts72\n"; + push(@{$idtref}, $oneline); + $oneline = "Upgrade\tUpgradeCode\tVersionMin\tVersionMax\tLanguage\tAttributes\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "icon" ) + { + $oneline = "Name\tData\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\tv0\n"; + push(@{$idtref}, $oneline); + $oneline = "Icon\tName\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "inifile" ) + { + $oneline = "IniFile\tFileName\tDirProperty\tSection\tKey\tValue\tAction\tComponent_\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\tl255\tS72\tl96\tl128\tl255\ti2\ts72\n"; + push(@{$idtref}, $oneline); + $oneline = "IniFile\tIniFile\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "msiassembly" ) + { + $oneline = "Component_\tFeature_\tFile_Manifest\tFile_Application\tAttributes\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts38\tS72\tS72\tI2\n"; + push(@{$idtref}, $oneline); + $oneline = "MsiAssembly\tComponent_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "msiassemblyname" ) + { + $oneline = "Component_\tName\tValue\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts255\ts255\n"; + push(@{$idtref}, $oneline); + $oneline = "MsiAssemblyName\tComponent_\tName\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "appsearch" ) + { + $oneline = "Property\tSignature_\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts72\n"; + push(@{$idtref}, $oneline); + $oneline = "AppSearch\tProperty\tSignature_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "reglocat" ) + { + $oneline = "Signature_\tRoot\tKey\tName\tType\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ti2\ts255\tS255\tI2\n"; + push(@{$idtref}, $oneline); + $oneline = "RegLocator\tSignature_\n"; + push(@{$idtref}, $oneline); + } + + if ( $definestring eq "signatur" ) + { + $oneline = "Signature\tFileName\tMinVersion\tMaxVersion\tMinSize\tMaxSize\tMinDate\tMaxDate\tLanguages\n"; + push(@{$idtref}, $oneline); + $oneline = "s72\ts255\tS20\tS20\tI4\tI4\tI4\tI4\tS255\n"; + push(@{$idtref}, $oneline); + $oneline = "Signature\tSignature\n"; + push(@{$idtref}, $oneline); + } + +} + +############################################################## +# Returning the name of the translation file for a +# given language. +# Sample: "01" order "en-US" -> "1033.txt" +############################################################## + +sub get_languagefilename +{ + my ($idtfilename, $basedir) = @_; + + $idtfilename =~ s/\.idt/\.ulf/; + + my $languagefilename = $basedir . $installer::globals::separator . $idtfilename; + + return $languagefilename; +} + +############################################################## +# Returning the complete block in all languages +# for a specified string +############################################################## + +sub get_language_block_from_language_file +{ + my ($searchstring, $languagefile) = @_; + + my @language_block = (); + + for ( my $i = 0; $i <= $#{$languagefile}; $i++ ) + { + if ( ${$languagefile}[$i] =~ /^\s*\[\s*$searchstring\s*\]\s*$/ ) + { + my $counter = $i; + + push(@language_block, ${$languagefile}[$counter]); + $counter++; + + while (( $counter <= $#{$languagefile} ) && (!( ${$languagefile}[$counter] =~ /^\s*\[/ ))) + { + push(@language_block, ${$languagefile}[$counter]); + $counter++; + } + + last; + } + } + + return \@language_block; +} + +############################################################## +# Returning a specific language string from the block +# of all translations +############################################################## + +sub get_language_string_from_language_block +{ + my ($language_block, $language, $oldstring) = @_; + + my $newstring = ""; + + for ( my $i = 0; $i <= $#{$language_block}; $i++ ) + { + if ( ${$language_block}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + $newstring =~ s/\\\"/\"/g; #un-escape quotes, fdo#59321 + last; + } + } + + if ( $newstring eq "" ) + { + $language = "en-US"; # defaulting to english + + for ( my $i = 0; $i <= $#{$language_block}; $i++ ) + { + if ( ${$language_block}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + last; + } + } + } + + return $newstring; +} + +############################################################## +# Returning a specific code from the block +# of all codes. No defaulting to english! +############################################################## + +sub get_code_from_code_block +{ + my ($codeblock, $language) = @_; + + my $newstring = ""; + + for ( my $i = 0; $i <= $#{$codeblock}; $i++ ) + { + if ( ${$codeblock}[$i] =~ /^\s*$language\s*\=\s*\"(.*)\"\s*$/ ) + { + $newstring = $1; + last; + } + } + + return $newstring; +} + +############################################################## +# Translating an idt file +############################################################## + +sub translate_idtfile +{ + my ($idtfile, $languagefile, $onelanguage) = @_; + + for ( my $i = 0; $i <= $#{$idtfile}; $i++ ) + { + my @allstrings = (); + + my $oneline = ${$idtfile}[$i]; + + while ( $oneline =~ /\b(OOO_\w+)\b/ ) + { + my $replacestring = $1; + push(@allstrings, $replacestring); + $oneline =~ s/$replacestring//; + } + + my $oldstring; + + foreach $oldstring (@allstrings) + { + my $language_block = get_language_block_from_language_file($oldstring, $languagefile); + my $newstring = get_language_string_from_language_block($language_block, $onelanguage, $oldstring); + + ${$idtfile}[$i] =~ s/$oldstring/$newstring/; # always substitute, even if $newstring eq "" (there are empty strings for control.idt) + } + } +} + +############################################################## +# Copying all needed files to create a msi database +# into one language specific directory +############################################################## + +sub prepare_language_idt_directory +{ + my ($destinationdir, $newidtdir, $onelanguage, $filesref, $iconfilecollector, $binarytablefiles, $allvariables) = @_; + + # Copying all idt-files from the source $installer::globals::idttemplatepath to the destination $destinationdir + # Copying all files in the subdirectory "Binary" + # Copying all files in the subdirectory "Icon" + + my $infoline = ""; + + installer::systemactions::copy_directory($installer::globals::idttemplatepath, $destinationdir); + + if ( -d $installer::globals::idttemplatepath . $installer::globals::separator . "Binary") + { + installer::systemactions::create_directory($destinationdir . $installer::globals::separator . "Binary"); + installer::systemactions::copy_directory($installer::globals::idttemplatepath . $installer::globals::separator . "Binary", $destinationdir . $installer::globals::separator . "Binary"); + } + + installer::systemactions::create_directory($destinationdir . $installer::globals::separator . "Icon"); + + if ( -d $installer::globals::idttemplatepath . $installer::globals::separator . "Icon") + { + installer::systemactions::copy_directory($installer::globals::idttemplatepath . $installer::globals::separator . "Icon", $destinationdir . $installer::globals::separator . "Icon"); + } + + # Copying all files in $iconfilecollector, that describe icons of folderitems + + for ( my $i = 0; $i <= $#{$iconfilecollector}; $i++ ) + { + my $iconfilename = ${$iconfilecollector}[$i]; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$iconfilename); + installer::systemactions::copy_one_file(${$iconfilecollector}[$i], $destinationdir . $installer::globals::separator . "Icon" . $installer::globals::separator . $iconfilename); + } + + # Copying all files in $binarytablefiles in the binary directory + + for ( my $i = 0; $i <= $#{$binarytablefiles}; $i++ ) + { + my $binaryfile = ${$binarytablefiles}[$i]; + my $binaryfilepath = $binaryfile->{'sourcepath'}; + my $binaryfilename = $binaryfilepath; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$binaryfilename); + installer::systemactions::copy_one_file($binaryfilepath, $destinationdir . $installer::globals::separator . "Binary" . $installer::globals::separator . $binaryfilename); + } + + # Copying all new created and language independent idt-files to the destination $destinationdir. + # Example: "File.idt" + + installer::systemactions::copy_directory_with_fileextension($newidtdir, $destinationdir, "idt"); + + # Copying all new created and language dependent idt-files to the destination $destinationdir. + # Example: "Feature.idt.01" + + installer::systemactions::copy_directory_with_fileextension($newidtdir, $destinationdir, $onelanguage); + installer::systemactions::rename_files_with_fileextension($destinationdir, $onelanguage); + +} + +############################################################## +# Returning the source path of the rtf licensefile for +# a specified language +############################################################## + +sub get_rtflicensefilesource +{ + my ($language, $includepatharrayref) = @_; + + my $licensefilename = "license_" . $language . ".rtf"; + + my $sourcefileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$licensefilename, $includepatharrayref, 1); + + if ($$sourcefileref eq "") { installer::exiter::exit_program("ERROR: Could not find $licensefilename!", "get_rtflicensefilesource"); } + + my $infoline = "Using licensefile: $$sourcefileref\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $$sourcefileref; +} + +############################################################## +# A simple converter to create a license txt string from +# the rtf format +############################################################## + +sub make_string_licensetext +{ + my ($licensefile) = @_; + + my $rtf_licensetext = ""; + + for ( my $i = 0; $i <= $#{$licensefile}; $i++ ) + { + my $oneline = ${$licensefile}[$i]; + $oneline =~ s/\s*$//g; # no whitespace at line end + + $rtf_licensetext = $rtf_licensetext . $oneline . " "; + } + + return $rtf_licensetext; +} + +############################################################## +# Including the license text into the table control.idt +############################################################## + +sub add_licensefile_to_database +{ + my ($licensefile, $controltable) = @_; + + # Nine tabs before the license text and two tabs after it + # The license text has to be included into the dialog + # LicenseAgreement into the control Memo. + + my $foundlicenseline = 0; + my ($number, $line); + + for ( my $i = 0; $i <= $#{$controltable}; $i++ ) + { + $line = ${$controltable}[$i]; + + if ( $line =~ /^\s*\bLicenseAgreement\b\t\bMemo\t/ ) + { + $foundlicenseline = 1; + $number = $i; + last; + } + } + + if (!($foundlicenseline)) + { + installer::exiter::exit_program("ERROR: Line for license file in Control.idt not found!", "add_licensefile_to_database"); + } + else + { + my %control = (); + + if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + $control{'Dialog_'} = $1; + $control{'Control'} = $2; + $control{'Type'} = $3; + $control{'X'} = $4; + $control{'Y'} = $5; + $control{'Width'} = $6; + $control{'Height'} = $7; + $control{'Attributes'} = $8; + $control{'Property'} = $9; + $control{'Text'} = $10; + $control{'Control_Next'} = $11; + $control{'Help'} = $12; + } + else + { + installer::exiter::exit_program("ERROR: Could not split line correctly!", "add_licensefile_to_database"); + } + + my $licensetext = make_string_licensetext($licensefile); + + $control{'Text'} = $licensetext; + + my $newline = $control{'Dialog_'} . "\t" . $control{'Control'} . "\t" . $control{'Type'} . "\t" . + $control{'X'} . "\t" . $control{'Y'} . "\t" . $control{'Width'} . "\t" . + $control{'Height'} . "\t" . $control{'Attributes'} . "\t" . $control{'Property'} . "\t" . + $control{'Text'} . "\t" . $control{'Control_Next'} . "\t" . $control{'Help'} . "\n"; + + ${$controltable}[$number] = $newline + } +} + +################################################################### +# Determining the last position in a sequencetable +# into the tables CustomAc.idt and InstallE.idt. +################################################################### + +sub get_last_position_in_sequencetable +{ + my ($sequencetable) = @_; + + my $position = 0; + + for ( my $i = 0; $i <= $#{$sequencetable}; $i++ ) + { + my $line = ${$sequencetable}[$i]; + + if ( $line =~ /^\s*\w+\t.*\t\s*(\d+)\s$/ ) + { + my $newposition = $1; + if ( $newposition > $position ) { $position = $newposition; } + } + } + + return $position; +} + +######################################################################### +# Determining the position of a specified Action in the sequencetable +######################################################################### + +sub get_position_in_sequencetable +{ + my ($action, $sequencetable) = @_; + + my $position = 0; + + $action =~ s/^\s*behind_//; + + for ( my $i = 0; $i <= $#{$sequencetable}; $i++ ) + { + my $line = ${$sequencetable}[$i]; + + if ( $line =~ /^\s*(\w+)\t.*\t\s*(\d+)\s$/ ) + { + my $compareaction = $1; + $position = $2; + if ( $compareaction eq $action ) { last; } + } + } + + return $position; +} + +################################################################################################ +# Including the CustomAction for the configuration +# into the tables CustomAc.idt and InstallE.idt. +# +# CustomAc.idt: ExecutePkgchk 82 pkgchk.exe -s +# InstallE.idt: ExecutePkgchk Not REMOVE="ALL" 3175 +# +# CustomAc.idt: ExecuteQuickstart 82 install_quickstart.exe +# InstallE.idt: ExecuteQuickstart &gm_o_Quickstart=3 3200 +# +# CustomAc.idt: ExecuteInstallRegsvrex 82 regsvrex.exe shlxthdl.dll +# InstallE.idt: ExecuteInstallRegsvrex Not REMOVE="ALL" 3225 +# +# CustomAc.idt: ExecuteUninstallRegsvrex 82 regsvrex.exe /u shlxthdl.dll +# InstallE.idt: ExecuteUninstallRegsvrex REMOVE="ALL" 690 +# +# CustomAc.idt: Regmsdocmsidll1 1 reg4msdocmsidll Reg4MsDocEntry +# InstallU.idt: Regmsdocmsidll1 Not REMOVE="ALL" 610 +# +# CustomAc.idt: Regmsdocmsidll2 1 reg4msdocmsidll Reg4MsDocEntry +# InstallE.idt: Regmsdocmsidll2 Not REMOVE="ALL" 3160 +################################################################################################ + +sub set_custom_action +{ + my ($customactionidttable, $actionname, $actionflags, $exefilename, $actionparameter, $inbinarytable, $filesref, $customactionidttablename, $styles) = @_; + + my $included_customaction = 0; + my $infoline = ""; + my $customaction_exefilename = $exefilename; + my $uniquename = ""; + + # when the style NO_FILE is set, no searching for the file is needed, no filtering is done, we can add that custom action + if ( $styles =~ /\bNO_FILE\b/ ) + { + my $line = $actionname . "\t" . $actionflags . "\t" . $customaction_exefilename . "\t" . $actionparameter . "\n"; + push(@{$customactionidttable}, $line); + + $infoline = "Added $actionname CustomAction into table $customactionidttablename (NO_FILE has been set)\n"; + push(@installer::globals::logfileinfo, $infoline); + + $included_customaction = 1; + return $included_customaction; + } + + # is the $exefilename a library that is included into the binary table + + if ( $inbinarytable ) { $customaction_exefilename =~ s/\.//g; } # this is the entry in the binary table ("abc.dll" -> "abcdll") + + # is the $exefilename included into the product? + + my $contains_file = 0; + + # All files are located in $filesref and in @installer::globals::binarytableonlyfiles. + # Both must be added together + my $localfilesref = [@installer::globals::binarytableonlyfiles, @{$filesref}]; + + for ( my $i = 0; $i <= $#{$localfilesref}; $i++ ) + { + my $onefile = ${$localfilesref}[$i]; + my $filename = ""; + if ( exists($onefile->{'Name'}) ) + { + $filename = $onefile->{'Name'}; + + if ( $filename eq $exefilename ) + { + $contains_file = 1; + $uniquename = ${$localfilesref}[$i]->{'uniquename'}; + last; + } + } + else + { + installer::exiter::exit_program("ERROR: Did not find \"Name\" for file \"$onefile->{'uniquename'}\" ($onefile->{'gid'})!", "set_custom_action"); + } + } + + if ( $contains_file ) + { + # Now the CustomAction can be included into the CustomAc.idt + + if ( ! $inbinarytable ) { $customaction_exefilename = $uniquename; } # the unique file name has to be added to the custom action table + + my $line = $actionname . "\t" . $actionflags . "\t" . $customaction_exefilename . "\t" . $actionparameter . "\n"; + push(@{$customactionidttable}, $line); + + $included_customaction = 1; + } + + if ( $included_customaction ) { $infoline = "Added $actionname CustomAction into table $customactionidttablename\n"; } + else { $infoline = "Did not add $actionname CustomAction into table $customactionidttablename\n"; } + push(@installer::globals::logfileinfo, $infoline); + + return $included_customaction; +} + +#################################################################### +# Adding a Custom Action to InstallExecuteTable or InstallUITable +#################################################################### + +sub add_custom_action_to_install_table +{ + my ($installtable, $exefilename, $actionname, $actioncondition, $position, $filesref, $installtablename, $styles) = @_; + + my $included_customaction = 0; + my $feature = ""; + my $infoline = ""; + + # when the style NO_FILE is set, no searching for the file is needed, no filtering is done, we can add that custom action + if ( $styles =~ /\bNO_FILE\b/ ) + { + # then the InstallE.idt.idt or InstallU.idt.idt + $actioncondition =~ s/FEATURETEMPLATE/$feature/g; # only execute Custom Action, if feature of the file is installed + + my $actionposition = 0; + + if ( $position =~ /^\s*\d+\s*$/ ) { $actionposition = $position; } # setting the position directly, number defined in scp2 + else { $actionposition = "POSITIONTEMPLATE_" . $position; } + + my $line = $actionname . "\t" . $actioncondition . "\t" . $actionposition . "\n"; + push(@{$installtable}, $line); + + $infoline = "Added $actionname CustomAction into table $installtablename (NO_FILE has been set)\n"; + push(@installer::globals::logfileinfo, $infoline); + return; + } + + my $contains_file = 0; + + # All files are located in $filesref and in @installer::globals::binarytableonlyfiles. + # Both must be added together + my $localfilesref = [@installer::globals::binarytableonlyfiles, @{$filesref}]; + + for ( my $i = 0; $i <= $#{$localfilesref}; $i++ ) + { + my $filename = ${$localfilesref}[$i]->{'Name'}; + + if ( $filename eq $exefilename ) + { + $contains_file = 1; + + # Determining the feature of the file + + if ( ${$localfilesref}[$i] ) { $feature = ${$localfilesref}[$i]->{'modules'}; } + + # If modules contains a list of modules, only taking the first one. + if ( $feature =~ /^\s*(.*?)\,/ ) { $feature = $1; } + # Attention: Maximum feature length is 38! + shorten_feature_gid(\$feature); + + last; + } + } + + if ( $contains_file ) + { + # then the InstallE.idt.idt or InstallU.idt.idt + + $actioncondition =~ s/FEATURETEMPLATE/$feature/g; # only execute Custom Action, if feature of the file is installed + + my $positiontemplate = ""; + if ( $position =~ /^\s*\d+\s*$/ ) { $positiontemplate = $position; } # setting the position directly, number defined in scp2 + else { $positiontemplate = "POSITIONTEMPLATE_" . $position; } + + my $line = $actionname . "\t" . $actioncondition . "\t" . $positiontemplate . "\n"; + push(@{$installtable}, $line); + + $included_customaction = 1; + } + + if ( $included_customaction ) { $infoline = "Added $actionname CustomAction into table $installtablename\n"; } + else { $infoline = "Did not add $actionname CustomAction into table $installtablename\n"; } + push(@installer::globals::logfileinfo, $infoline); + +} + +################################################################## +# A line in the table ControlEvent connects a Control +# with a Custom Action +################################################################# + +sub connect_custom_action_to_control +{ + my ( $table, $tablename, $dialog, $control, $event, $argument, $condition, $ordering) = @_; + + my $line = $dialog . "\t" . $control. "\t" . $event. "\t" . $argument. "\t" . $condition. "\t" . $ordering . "\n"; + + push(@{$table}, $line); + + $line =~ s/\s*$//g; + + $infoline = "Added line \"$line\" into table $tablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +################################################################## +# A line in the table ControlCondition connects a Control state +# with a condition +################################################################## + +sub connect_condition_to_control +{ + my ( $table, $tablename, $dialog, $control, $event, $condition) = @_; + + my $line = $dialog . "\t" . $control. "\t" . $event. "\t" . $condition. "\n"; + + push(@{$table}, $line); + + $line =~ s/\s*$//g; + + $infoline = "Added line \"$line\" into table $tablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +################################################################## +# Searching for a sequencenumber in InstallUISequence table +# "ExecuteAction" must be the last action +################################################################## + +sub get_free_number_in_uisequence_table +{ + my ( $installuitable ) = @_; + + # determining the sequence of "ExecuteAction" + + my $executeactionnumber = 0; + + for ( my $i = 0; $i <= $#{$installuitable}; $i++ ) + { + if ( ${$installuitable}[$i] =~ /^\s*(\w+)\t\w*\t(\d+)\s*$/ ) + { + my $actionname = $1; + my $actionnumber = $2; + + if ( $actionname eq "ExecuteAction" ) + { + $executeactionnumber = $actionnumber; + last; + } + } + } + + if ( $executeactionnumber == 0 ) { installer::exiter::exit_program("ERROR: Did not find \"ExecuteAction\" in InstallUISequence table!", "get_free_number_in_uisequence_table"); } + + # determining the sequence of the action before "ExecuteAction" + + my $lastactionnumber = 0; + + for ( my $i = 0; $i <= $#{$installuitable}; $i++ ) + { + if ( ${$installuitable}[$i] =~ /^\s*\w+\t\w*\t(\d+)\s*$/ ) + { + my $actionnumber = $1; + + if (( $actionnumber > $lastactionnumber ) && ( $actionnumber != $executeactionnumber )) + { + $lastactionnumber = $actionnumber; + } + } + } + + # the new number can now be calculated + + my $newnumber = 0; + + if ((( $lastactionnumber + $executeactionnumber ) % 2 ) == 0 ) { $newnumber = ( $lastactionnumber + $executeactionnumber ) / 2; } + else { $newnumber = ( $lastactionnumber + $executeactionnumber -1 ) / 2; } + + return $newnumber; +} + +############################################################# +# Including the new subdir into the directory table +############################################################# + +sub include_subdirname_into_directory_table +{ + my ($dirname, $directorytable, $directorytablename, $onefile) = @_; + + my $subdir = ""; + if ( $onefile->{'Subdir'} ) { $subdir = $onefile->{'Subdir'}; } + if ( $subdir eq "" ) { installer::exiter::exit_program("ERROR: No \"Subdir\" defined for $onefile->{'Name'}", "include_subdirname_into_directory_table"); } + + # program INSTALLLOCATION program -> subjava INSTALLLOCATION program:java + + my $uniquename = ""; + my $parent = ""; + my $name = ""; + + my $includedline = 0; + + my $newdir = ""; + + for ( my $i = 0; $i <= $#{$directorytable}; $i++ ) + { + + if ( ${$directorytable}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + $uniquename = $1; + $parent = $2; + $name = $3; + + if ( $dirname eq $name ) + { + my $newuniquename = "sub" . $subdir; + $newdir = $newuniquename; + my $newparent = "INSTALLLOCATION"; + my $newname = $name . "\:" . $subdir; + my $newline = + $line = "$newuniquename\t$newparent\t$newname\n"; + push(@{$directorytable}, $line); + installer::remover::remove_leading_and_ending_whitespaces(\$line); + $infoline = "Added $line into directory table $directorytablename\n"; + push(@installer::globals::logfileinfo, $infoline); + + $includedline = 1; + last; + } + } + } + + if ( ! $includedline ) { installer::exiter::exit_program("ERROR: Could not include new subdirectory into directory table for file $onefile->{'Name'}!", "include_subdirname_into_directory_table"); } + + return $newdir; +} + +################################################################## +# Including the new sub directory into the component table +################################################################## + +sub include_subdir_into_componenttable +{ + my ($subdir, $onefile, $componenttable) = @_; + + my $componentname = $onefile->{'componentname'}; + + my $changeddirectory = 0; + + for ( my $i = 0; $i <= $#{$componenttable}; $i++ ) + { + if ( ${$componenttable}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $localcomponentname = $1; + my $directory = $3; + + if ( $componentname eq $localcomponentname ) + { + my $oldvalue = ${$componenttable}[$i]; + ${$componenttable}[$i] =~ s/\b\Q$directory\E\b/$subdir/; + my $newvalue = ${$componenttable}[$i]; + + installer::remover::remove_leading_and_ending_whitespaces(\$oldvalue); + installer::remover::remove_leading_and_ending_whitespaces(\$newvalue); + $infoline = "Change in Component table: From \"$oldvalue\" to \"$newvalue\"\n"; + push(@installer::globals::logfileinfo, $infoline); + + $changeddirectory = 1; + last; + } + } + } + + if ( ! $changeddirectory ) { installer::exiter::exit_program("ERROR: Could not change directory for component: $onefile->{'Name'}!", "include_subdir_into_componenttable"); } + +} + +################################################################## +# Setting the condition, that at least one module is selected. +# All modules with flag SHOW_MULTILINGUAL_ONLY were already +# collected. In table ControlE.idt, the string +# LANGUAGECONDITIONINSTALL needs to be replaced. +# Also for APPLICATIONCONDITIONINSTALL for the applications +# with flag APPLICATIONMODULE. +################################################################## + +sub set_multilanguageonly_condition +{ + my ( $languageidtdir ) = @_; + + my $onefilename = $languageidtdir . $installer::globals::separator . "ControlE.idt"; + my $onefile = installer::files::read_file($onefilename); + + # Language modules + + my $condition = ""; + + foreach my $module ( sort keys %installer::globals::multilingual_only_modules ) + { + $condition = $condition . " &$module=3 Or"; + } + + $condition =~ s/^\s*//; + $condition =~ s/\s*Or\s*$//; # removing the ending "Or" + + if ( $condition eq "" ) { $condition = "1"; } + + for ( my $j = 0; $j <= $#{$onefile}; $j++ ) + { + ${$onefile}[$j] =~ s/LANGUAGECONDITIONINSTALL/$condition/; + } + + # Application modules + + $condition = ""; + + foreach my $module ( sort keys %installer::globals::application_modules ) + { + $condition = $condition . " &$module=3 Or"; + } + + $condition =~ s/^\s*//; + $condition =~ s/\s*Or\s*$//; # removing the ending "Or" + + if ( $condition eq "" ) { $condition = "1"; } + + for ( my $j = 0; $j <= $#{$onefile}; $j++ ) + { + ${$onefile}[$j] =~ s/APPLICATIONCONDITIONINSTALL/$condition/; + } + + installer::files::save_file($onefilename, $onefile); +} + +############################################# +# Putting array values into hash +############################################# + +sub fill_assignment_hash +{ + my ($gid, $name, $key, $assignmenthashref, $parameter, $tablename, $assignmentarray) = @_; + + my $max = $parameter - 1; + + if ( $max != $#{$assignmentarray} ) + { + my $definedparameter = $#{$assignmentarray} + 1; + installer::exiter::exit_program("ERROR: gid: $gid, key: $key ! Wrong parameter in scp. For table $tablename $parameter parameter are required ! You defined: $definedparameter", "fill_assignment_hash"); + } + + for ( my $i = 0; $i <= $#{$assignmentarray}; $i++ ) + { + my $counter = $i + 1; + my $key = "parameter". $counter; + + my $localvalue = ${$assignmentarray}[$i]; + installer::remover::remove_leading_and_ending_quotationmarks(\$localvalue); + $localvalue =~ s/\\\"/\"/g; + $localvalue =~ s/\\\!/\!/g; + $localvalue =~ s/\\\&/\&/g; + $localvalue =~ s/\\\/\>/g; + $assignmenthashref->{$key} = $localvalue; + } +} + +########################################################################## +# Checking the assignment of a Windows CustomAction and putting it +# into a hash +########################################################################## + +sub create_customaction_assignment_hash +{ + my ($gid, $name, $key, $assignmentarray) = @_; + + my %assignment = (); + my $assignmenthashref = \%assignment; + + my $tablename = ${$assignmentarray}[0]; + installer::remover::remove_leading_and_ending_quotationmarks(\$tablename); + + my $tablename_defined = 0; + my $parameter = 0; + + if ( $tablename eq "InstallUISequence" ) + { + $tablename_defined = 1; + $parameter = 3; + fill_assignment_hash($gid, $name, $key, $assignmenthashref, $parameter, $tablename, $assignmentarray); + } + + if ( $tablename eq "InstallExecuteSequence" ) + { + $tablename_defined = 1; + $parameter = 3; + fill_assignment_hash($gid, $name, $key, $assignmenthashref, $parameter, $tablename, $assignmentarray); + } + + if ( $tablename eq "AdminExecuteSequence" ) + { + $tablename_defined = 1; + $parameter = 3; + fill_assignment_hash($gid, $name, $key, $assignmenthashref, $parameter, $tablename, $assignmentarray); + } + + if ( $tablename eq "ControlEvent" ) + { + $tablename_defined = 1; + $parameter = 7; + fill_assignment_hash($gid, $name, $key, $assignmenthashref, $parameter, $tablename, $assignmentarray); + } + + if ( $tablename eq "ControlCondition" ) + { + $tablename_defined = 1; + $parameter = 5; + fill_assignment_hash($gid, $name, $key, $assignmenthashref, $parameter, $tablename, $assignmentarray); + } + + if ( ! $tablename_defined ) + { + installer::exiter::exit_program("ERROR: gid: $gid, key: $key ! Unknown Windows CustomAction table: $tablename ! Currently supported: InstallUISequence, InstallExecuteSequence, ControlEvent, ControlCondition", "create_customaction_assignment_hash"); + } + + return $assignmenthashref; +} + +########################################################################## +# Finding the position of a specified CustomAction. +# If the CustomAction is not found, the return value is "-1". +# If the CustomAction position is not defined yet, +# the return value is also "-1". +########################################################################## + +sub get_customaction_position +{ + my ($action, $sequencetable) = @_; + + my $position = -1; + + for ( my $i = 0; $i <= $#{$sequencetable}; $i++ ) + { + my $line = ${$sequencetable}[$i]; + + if ( $line =~ /^\s*([\w\.]+)\t.*\t\s*(\d+)\s$/ ) # matching only, if position is a number! + { + my $compareaction = $1; + my $localposition = $2; + + if ( $compareaction eq $action ) + { + $position = $localposition; + last; + } + } + } + + return $position; +} + +########################################################################## +# Setting the position of CustomActions in sequence tables. +# Replacing all occurrences of "POSITIONTEMPLATE_" +########################################################################## + +sub set_positions_in_table +{ + my ( $sequencetable, $tablename ) = @_; + + my $infoline = "\nSetting positions in table \"$tablename\".\n"; + push(@installer::globals::logfileinfo, $infoline); + + # Step 1: Resolving all occurrences of "POSITIONTEMPLATE_end" + + my $lastposition = get_last_position_in_sequencetable($sequencetable); + + for ( my $i = 0; $i <= $#{$sequencetable}; $i++ ) + { + if ( ${$sequencetable}[$i] =~ /^\s*([\w\.]+)\t.*\t\s*POSITIONTEMPLATE_end\s*$/ ) + { + my $customaction = $1; + $lastposition = $lastposition + 25; + ${$sequencetable}[$i] =~ s/POSITIONTEMPLATE_end/$lastposition/; + $infoline = "Setting position \"$lastposition\" for custom action \"$customaction\".\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + + # Step 2: Resolving all occurrences of "POSITIONTEMPLATE_abc" or "POSITIONTEMPLATE_behind_abc" + # where abc is the name of the reference Custom Action. + # This has to be done, until there is no more occurrence of POSITIONTEMPLATE (success) + # or there is no replacement in one circle (failure). + + my $template_exists = 0; + my $template_replaced = 0; + my $counter = 0; + + do + { + $template_exists = 0; + $template_replaced = 0; + $counter++; + + for ( my $i = 0; $i <= $#{$sequencetable}; $i++ ) + { + if ( ${$sequencetable}[$i] =~ /^\s*([\w\.]+)\t.*\t\s*(POSITIONTEMPLATE_.*?)\s*$/ ) + { + my $onename = $1; + my $templatename = $2; + my $positionname = $templatename; + my $customaction = $templatename; + $customaction =~ s/POSITIONTEMPLATE_//; + $template_exists = 1; + + # Trying to find the correct number. + # This can fail, if the custom action has no number + + my $setbehind = 0; + if ( $customaction =~ /^\s*behind_(.*?)\s*$/ ) + { + $customaction = $1; + $setbehind = 1; + } + + my $position = get_customaction_position($customaction, $sequencetable); + + if ( $position >= 0 ) # Found CustomAction and is has a position. Otherwise return value is "-1". + { + my $newposition = 0; + if ( $setbehind ) { $newposition = $position + 2; } + else { $newposition = $position - 2; } + ${$sequencetable}[$i] =~ s/$templatename/$newposition/; + $template_replaced = 1; + $infoline = "Setting position \"$newposition\" for custom action \"$onename\" (scp: \"$positionname\" at position $position).\n"; + push(@installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Could not assign position for custom action \"$onename\" yet (scp: \"$positionname\").\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } + } while (( $template_exists ) && ( $template_replaced )); + + # An error occurred, because templates still exist, but could not be replaced. + # Reason: + # 1. Wrong name of CustomAction in scp2 (typo?) + # 2. Circular dependencies of CustomActions (A after B and B after A) + + # Problem: It is allowed, that a CustomAction is defined in scp2 in a library that is + # part of product ABC, but this CustomAction is not used in this product + # and the reference CustomAction is not part of this product. + # Therefore this cannot be an error, but only produce a warning. The assigned number + # must be the last sequence number. + + if (( $template_exists ) && ( ! $template_replaced )) + { + for ( my $i = 0; $i <= $#{$sequencetable}; $i++ ) + { + if ( ${$sequencetable}[$i] =~ /^\s*([\w\.]+)\t.*\t\s*(POSITIONTEMPLATE_.*?)\s*$/ ) + { + my $customactionname = $1; + my $fulltemplate = $2; + my $template = $fulltemplate; + $template =~ s/POSITIONTEMPLATE_//; + $lastposition = $lastposition + 25; + ${$sequencetable}[$i] =~ s/$fulltemplate/$lastposition/; + $infoline = "WARNING: Setting position \"$lastposition\" for custom action \"$customactionname\". Could not find CustomAction \"$template\".\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } +} + +########################################################################## +# Setting the Windows custom actions into different tables +# CustomAc.idt, InstallE.idt, InstallU.idt, ControlE.idt, ControlC.idt +########################################################################## + +sub addcustomactions +{ + my ($languageidtdir, $customactions, $filesarray) = @_; + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: addcustomactions start\n"); + + my $customactionidttablename = $languageidtdir . $installer::globals::separator . "CustomAc.idt"; + my $customactionidttable = installer::files::read_file($customactionidttablename); + my $installexecutetablename = $languageidtdir . $installer::globals::separator . "InstallE.idt"; + my $installexecutetable = installer::files::read_file($installexecutetablename); + my $adminexecutetablename = $languageidtdir . $installer::globals::separator . "AdminExe.idt"; + my $adminexecutetable = installer::files::read_file($adminexecutetablename); + my $installuitablename = $languageidtdir . $installer::globals::separator . "InstallU.idt"; + my $installuitable = installer::files::read_file($installuitablename); + my $controleventtablename = $languageidtdir . $installer::globals::separator . "ControlE.idt"; + my $controleventtable = installer::files::read_file($controleventtablename); + my $controlconditiontablename = $languageidtdir . $installer::globals::separator . "ControlC.idt"; + my $controlconditiontable = installer::files::read_file($controlconditiontablename); + + # Iterating over all Windows custom actions + + for ( my $i = 0; $i <= $#{$customactions}; $i++ ) + { + my $customaction = ${$customactions}[$i]; + my $name = $customaction->{'Name'}; + my $typ = $customaction->{'Typ'}; + my $source = $customaction->{'Source'}; + my $target = $customaction->{'Target'}; + my $inbinarytable = $customaction->{'Inbinarytable'}; + my $gid = $customaction->{'gid'}; + + my $styles = ""; + if ( $customaction->{'Styles'} ) { $styles = $customaction->{'Styles'}; } + + my $added_customaction = set_custom_action($customactionidttable, $name, $typ, $source, $target, $inbinarytable, $filesarray, $customactionidttablename, $styles); + + if ( $added_customaction ) + { + # If the CustomAction was added into the CustomAc.idt, it can be connected to the installation. + # There are currently two different ways for doing this: + # 1. Using "add_custom_action_to_install_table", which adds the CustomAction to the install sequences, + # which are saved in InstallE.idt and InstallU.idt + # 2. Using "connect_custom_action_to_control" and "connect_custom_action_to_control". The first method + # connects a CustomAction to a control in ControlE.idt. The second method sets a condition for a control, + # which might be influenced by the CustomAction. This happens in ControlC.idt. + + # Any Windows CustomAction can have a lot of different assignments. + + for ( my $j = 1; $j <= 50; $j++ ) + { + my $key = "Assignment" . $j; + my $value = ""; + if ( $customaction->{$key} ) + { + $value = $customaction->{$key}; + } + else { last; } + + # $value is now a comma separated list + if ( $value =~ /^\s*\(\s*(.*)\s*\);?\s*$/ ) { $value = $1; } + my $assignmentarray = installer::converter::convert_stringlist_into_array(\$value, ","); + my $assignment = create_customaction_assignment_hash($gid, $name, $key, $assignmentarray); + + if ( $assignment->{'parameter1'} eq "InstallExecuteSequence" ) + { + add_custom_action_to_install_table($installexecutetable, $source, $name, $assignment->{'parameter2'}, $assignment->{'parameter3'}, $filesarray, $installexecutetablename, $styles); + } + elsif ( $assignment->{'parameter1'} eq "AdminExecuteSequence" ) + { + add_custom_action_to_install_table($adminexecutetable, $source, $name, $assignment->{'parameter2'}, $assignment->{'parameter3'}, $filesarray, $adminexecutetablename, $styles); + } + elsif ( $assignment->{'parameter1'} eq "InstallUISequence" ) + { + add_custom_action_to_install_table($installuitable, $source, $name, $assignment->{'parameter2'}, $assignment->{'parameter3'}, $filesarray, $installuitablename, $styles); + } + elsif ( $assignment->{'parameter1'} eq "ControlEvent" ) + { + connect_custom_action_to_control($controleventtable, $controleventtablename, $assignment->{'parameter2'}, $assignment->{'parameter3'}, $assignment->{'parameter4'}, $assignment->{'parameter5'}, $assignment->{'parameter6'}, $assignment->{'parameter7'}); + } + elsif ( $assignment->{'parameter1'} eq "ControlCondition" ) + { + connect_condition_to_control($controlconditiontable, $controlconditiontablename, $assignment->{'parameter2'}, $assignment->{'parameter3'}, $assignment->{'parameter4'}, $assignment->{'parameter5'}); + } + else + { + installer::exiter::exit_program("ERROR: gid: $gid, key: $key ! Unknown Windows CustomAction table: $assignmenthashref->{'parameter1'} ! Currently supported: InstallUISequence, InstallESequence, ControlEvent, ControlCondition", "addcustomactions"); + } + } + } + } + + # Setting the positions in the tables + + set_positions_in_table($installexecutetable, $installexecutetablename); + set_positions_in_table($installuitable, $installuitablename); + set_positions_in_table($adminexecutetable, $adminexecutetablename); + + # Saving the files + + installer::files::save_file($customactionidttablename, $customactionidttable); + installer::files::save_file($installexecutetablename, $installexecutetable); + installer::files::save_file($adminexecutetablename, $adminexecutetable); + installer::files::save_file($installuitablename, $installuitable); + installer::files::save_file($controleventtablename, $controleventtable); + installer::files::save_file($controlconditiontablename, $controlconditiontable); + + my $infoline = "Updated idt file: $customactionidttablename\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Updated idt file: $installexecutetablename\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Updated idt file: $adminexecutetablename\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Updated idt file: $installuitablename\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Updated idt file: $controleventtablename\n"; + push(@installer::globals::logfileinfo, $infoline); + $infoline = "Updated idt file: $controlconditiontablename\n"; + push(@installer::globals::logfileinfo, $infoline); + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: addcustomactions end\n"); +} + +########################################################################## +# Setting bidi attributes in idt tables +########################################################################## + +sub setbidiattributes +{ + my ($languageidtdir, $onelanguage) = @_; + + # Editing the files Dialog.idt and Control.idt + + my $dialogfilename = $languageidtdir . $installer::globals::separator . "Dialog.idt"; + my $controlfilename = $languageidtdir . $installer::globals::separator . "Control.idt"; + + my $dialogfile = installer::files::read_file($dialogfilename); + my $controlfile = installer::files::read_file($controlfilename); + + # Searching attributes in Dialog.idt and adding "896". + # Attributes are in column 6 (from 10). + + my $bidiattribute = 896; + for ( my $i = 0; $i <= $#{$dialogfile}; $i++ ) + { + if ( $i < 3 ) { next; } + if ( ${$dialogfile}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $one = $1; + my $two = $2; + my $three = $3; + my $four = $4; + my $five = $5; + my $attribute = $6; + my $seven = $7; + my $eight = $8; + $attribute = $attribute + $bidiattribute; + ${$dialogfile}[$i] = "$one\t$two\t$three\t$four\t$five\t$attribute\t$seven\t$eight\n"; + } + } + + # Searching attributes in Control.idt and adding "224". + # Attributes are in column 8 (from 12). + + $bidiattribute = 224; + for ( my $i = 0; $i <= $#{$controlfile}; $i++ ) + { + if ( $i < 3 ) { next; } + if ( ${$controlfile}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $one = $1; + my $two = $2; + my $three = $3; + my $four = $4; + my $five = $5; + my $six = $6; + my $seven = $7; + my $attribute = $8; + my $nine = $9; + my $ten = $10; + my $eleven = $11; + my $twelve = $12; + $attribute = $attribute + $bidiattribute; + ${$controlfile}[$i] = "$one\t$two\t$three\t$four\t$five\t$six\t$seven\t$attribute\t$nine\t$ten\t$eleven\t$twelve\n"; + } + } + + # Saving the file + + installer::files::save_file($dialogfilename, $dialogfile); + $infoline = "Set bidi support in idt file \"$dialogfilename\" for language $onelanguage\n"; + push(@installer::globals::logfileinfo, $infoline); + + installer::files::save_file($controlfilename, $controlfile); + $infoline = "Set bidi support in idt file \"$controlfilename\" for language $onelanguage\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +############################################### +# Emit custom action 51 for setting standard +# directory variable. Reference to a hash is +# returned, represented the custom action. +# This can be passed in to addcustomaction +# method. +############################################### + +sub emit_custom_action_for_standard_directory +{ + my ($dir, $var) = @_; + my %action = (); + + $action{'Name'} = $dir; + $action{'Typ'} = "51"; + $action{'Source'} = $dir; + $action{'Target'} = "[$var]"; + $action{'Styles'} = "NO_FILE"; + $action{'Assignment1'} = '("AdminExecuteSequence", "", "CostInitialize")'; + $action{'Assignment2'} = '("InstallExecuteSequence", "", "CostInitialize")'; + $action{'Assignment3'} = '("InstallUISequence", "", "CostInitialize")'; + + return \%action; +} + +1; diff --git a/solenv/bin/modules/installer/windows/inifile.pm b/solenv/bin/modules/installer/windows/inifile.pm new file mode 100644 index 000000000..b26e41836 --- /dev/null +++ b/solenv/bin/modules/installer/windows/inifile.pm @@ -0,0 +1,121 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::inifile; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +#################################################### +# Setting the profile for a special profileitem +#################################################### + +sub get_profile_for_profileitem +{ + my ($profileid, $filesref) = @_; + + my ($profile) = grep {$_->{gid} eq $profileid} @{$filesref}; + if (! defined $profile) { + installer::exiter::exit_program("ERROR: Could not find file $profileid in list of files!", "get_profile_for_profileitem"); + } + + return $profile; +} + +#################################################### +# Checking whether profile is part of product +#################################################### + +sub file_is_part_of_product +{ + my ($profilegid, $filesref) = @_; + + my $part_of_product = 0; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $profilegid ) + { + $part_of_product = 1; + last; + } + } + + return $part_of_product; +} + +########################################################################################################### +# Creating the file IniFile.idt dynamically +# Content: +# IniFile\tFileName\tDirProperty\tSection\tKey\tValue\tAction\tComponent_ +########################################################################################################### + +sub create_inifile_table +{ + my ($inifiletableentries, $filesref, $basedir) = @_; + + my @inifiletable = (); + + installer::windows::idtglobal::write_idt_header(\@inifiletable, "inifile"); + + for ( my $i = 0; $i <= $#{$inifiletableentries}; $i++ ) + { + my $profileitem = ${$inifiletableentries}[$i]; + + my $profileid = $profileitem->{'ProfileID'}; + + # Is this profile part of the product? This is not sure, for example in patch process. + # If the profile is not part of the product, this ProfileItem must be ignored. + + if ( ! file_is_part_of_product($profileid, $filesref) ) { next; } + + my $profile = get_profile_for_profileitem($profileid, $filesref); + + my %inifile = (); + + $inifile{'IniFile'} = $profileitem->{'Inifiletablekey'}; + $inifile{'FileName'} = $profile->{'Name'}; + $inifile{'DirProperty'} = $profile->{'uniquedirname'}; + $inifile{'Section'} = $profileitem->{'Section'}; + $inifile{'Key'} = $profileitem->{'Key'}; + $inifile{'Value'} = $profileitem->{'Value'}; + $inifile{'Action'} = $profileitem->{'Inifiletableaction'}; + $inifile{'Component_'} = $profile->{'componentname'}; + + my $oneline = $inifile{'IniFile'} . "\t" . $inifile{'FileName'} . "\t" . $inifile{'DirProperty'} . "\t" + . $inifile{'Section'} . "\t" . $inifile{'Key'} . "\t" . $inifile{'Value'} . "\t" + . $inifile{'Action'} . "\t" . $inifile{'Component_'} . "\n"; + + push(@inifiletable, $oneline); + } + + # Saving the file + + my $inifiletablename = $basedir . $installer::globals::separator . "IniFile.idt"; + installer::files::save_file($inifiletablename ,\@inifiletable); + my $infoline = "Created idt file: $inifiletablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +1; diff --git a/solenv/bin/modules/installer/windows/language.pm b/solenv/bin/modules/installer/windows/language.pm new file mode 100644 index 000000000..2a5be5b64 --- /dev/null +++ b/solenv/bin/modules/installer/windows/language.pm @@ -0,0 +1,41 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::language; + +use installer::exiter; + +#################################################### +# Determining the Windows language (LCID) +# English: 1033 +#################################################### + +sub get_windows_language +{ + my ($language) = @_; + + my $windowslanguage = ""; + + if ( $installer::globals::msilanguage->{$language} ) { $windowslanguage = $installer::globals::msilanguage->{$language}; } + + if ( $windowslanguage eq "" ) { installer::exiter::exit_program("ERROR: Unknown language $language in function get_windows_language", "get_windows_language"); } + + return $windowslanguage; +} + +1; diff --git a/solenv/bin/modules/installer/windows/media.pm b/solenv/bin/modules/installer/windows/media.pm new file mode 100644 index 000000000..e73013ee0 --- /dev/null +++ b/solenv/bin/modules/installer/windows/media.pm @@ -0,0 +1,202 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::media; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +############################################################## +# Returning the diskid for the media table. +############################################################## + +sub get_media_diskid +{ + my ($id) = @_; + + return $id; +} + +############################################################## +# Returning the diskprompt for the media table. +############################################################## + +sub get_media_diskprompt +{ + return 1; +} + +############################################################## +# Returning the volumelabel for the media table. +############################################################## + +sub get_media_volumelabel +{ + return "DISK1"; +} + +############################################################## +# Returning the source for the media table. +############################################################## + +sub get_media_source +{ + return ""; +} + +################################################# +# Creating the cab file name dynamically +################################################# + +sub generate_cab_filename_for_some_cabs +{ + my ( $allvariables, $id ) = @_; + + my $name = $allvariables->{'PRODUCTNAME'}; + + $name = lc($name); + $name =~ s/\.//g; + $name =~ s/\s//g; + + # possibility to overwrite the name with variable CABFILENAME + if ( $allvariables->{'CABFILENAME'} ) { $name = $allvariables->{'CABFILENAME'}; } + + $name = $name . $id . ".cab"; + + if ( $installer::globals::include_cab_in_msi ) { $name = "\#" . $name; } + + return $name; +} + +sub get_maximum_filenumber +{ + my ($allfiles, $maxcabfilenumber) = @_; + + my $maxfile = 0; + + while ( ! ( $allfiles%$maxcabfilenumber == 0 )) + { + $allfiles++; + } + + $maxfile = $allfiles / $maxcabfilenumber; + + $maxfile++; # for security + + return $maxfile; +} + +################################################################################# +# Creating the file Media.idt dynamically +# Content: +# DiskId LastSequence DiskPrompt Cabinet VolumeLabel Source +# Idea: Every component is packed into each own cab file +################################################################################# + +sub create_media_table +{ + my ($filesref, $basedir, $allvariables, $allupdatelastsequences, $allupdatediskids) = @_; + + my @mediatable = (); + + my $diskid = 0; + + installer::windows::idtglobal::write_idt_header(\@mediatable, "media"); + + if ( $installer::globals::fix_number_of_cab_files ) + { + # number of cabfiles + my $maxcabfilenumber = $installer::globals::number_of_cabfiles; + if ( $allvariables->{'CABFILENUMBER'} ) { $maxcabfilenumber = $allvariables->{'CABFILENUMBER'}; } + my $allfiles = $#{$filesref} + 1; + my $maxfilenumber = get_maximum_filenumber($allfiles, $maxcabfilenumber); + my $cabfilenumber = 0; + my $cabfull = 0; + my $counter = 0; + + # Sorting of files collector files required ! + # Attention: The order in the cab file is not guaranteed (especially in update process) + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + if (( $counter >= $maxfilenumber ) || ( $i == $#{$filesref} )) { $cabfull = 1; } + + $counter++; # counting the files in the cab file + + my $onefile = ${$filesref}[$i]; + my $nextfile = ${$filesref}[$i+1]; + + my $filecomponent = ""; + my $nextcomponent = ""; + + if ( $onefile->{'componentname'} ) { $filecomponent = $onefile->{'componentname'}; } + if ( $nextfile->{'componentname'} ) { $nextcomponent = $nextfile->{'componentname'}; } + + if ( $filecomponent eq $nextcomponent ) # all files of one component have to be in one cab file + { + next; # nothing to do, this is not the last file of a component + } + + if ( $cabfull ) + { + my %media = (); + $cabfilenumber++; + + $media{'DiskId'} = get_media_diskid($cabfilenumber); + $media{'LastSequence'} = $i + 1; # This should be correct, also for unsorted files collectors + $media{'DiskPrompt'} = get_media_diskprompt(); + $media{'Cabinet'} = generate_cab_filename_for_some_cabs($allvariables, $cabfilenumber); + $media{'VolumeLabel'} = get_media_volumelabel(); + $media{'Source'} = get_media_source(); + + my $oneline = $media{'DiskId'} . "\t" . $media{'LastSequence'} . "\t" . $media{'DiskPrompt'} . "\t" + . $media{'Cabinet'} . "\t" . $media{'VolumeLabel'} . "\t" . $media{'Source'} . "\n"; + + push(@mediatable, $oneline); + + # Saving the cabinet file name in the file collector + + $media{'Cabinet'} =~ s/^\s*\#//; # removing leading hash + + for ( my $j = 0; $j <= $i; $j++ ) + { + my $onefile = ${$filesref}[$j]; + if ( ! $onefile->{'cabinet'} ) { $onefile->{'cabinet'} = $media{'Cabinet'}; } + } + + $cabfull = 0; + $counter = 0; + } + } + } + else + { + installer::exiter::exit_program("ERROR: No cab file specification in globals.pm !", "create_media_table"); + } + + # Saving the file + + my $mediatablename = $basedir . $installer::globals::separator . "Media.idt"; + installer::files::save_file($mediatablename ,\@mediatable); + my $infoline = "Created idt file: $mediatablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +1; diff --git a/solenv/bin/modules/installer/windows/mergemodule.pm b/solenv/bin/modules/installer/windows/mergemodule.pm new file mode 100644 index 000000000..defd59588 --- /dev/null +++ b/solenv/bin/modules/installer/windows/mergemodule.pm @@ -0,0 +1,1703 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::mergemodule; + +use Cwd; +use Digest::MD5; +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::remover; +use installer::scriptitems; +use installer::systemactions; +use installer::worker; +use installer::windows::idtglobal; +use installer::windows::language; + +################################################################# +# Merging the Windows MergeModules into the msi database. +################################################################# + +sub merge_mergemodules_into_msi_database +{ + my ($mergemodules, $filesref, $msifilename, $languagestringref, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_; + + my $domerge = 0; + if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) { $domerge = 1; } + + if ( $domerge ) + { + installer::logger::include_header_into_logfile("Merging merge modules into msi database"); + installer::logger::print_message( "... merging msm files into msi database ... \n" ); + installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, start"); + + my $msidb = "msidb.exe"; # Has to be in the path + my $cabinetfile = "MergeModule.CABinet"; # the name of each cabinet file in a merge file + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + + # 1. Analyzing the MergeModule (has only to be done once) + # a. -> Extracting cabinet file: msidb.exe -d -x MergeModule.CABinet + # b. -> Number of files in cabinet file: msidb.exe -d -f -e File + # c. -> List of components: msidb.exe -d -f -e Component + + if ( ! $installer::globals::mergemodules_analyzed ) + { + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, start"); + $infoline = "Analyzing all Merge Modules\n\n"; + push( @installer::globals::logfileinfo, $infoline); + + %installer::globals::mergemodules = (); + + my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref); + + my $mergemodule; + foreach $mergemodule ( @{$mergemodules} ) + { + my $filename = $mergemodule->{'Name'}; + my $mergefile = $ENV{'MSM_PATH'} . $filename; + + if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename ($mergefile)!", "merge_mergemodules_into_msi_database"); } + my $completesource = $mergefile; + + my $mergegid = $mergemodule->{'gid'}; + my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid; + if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); } + + $infoline = "Analyzing Merge Module: $filename\n"; + push( @installer::globals::logfileinfo, $infoline); + + # copy msm file into working directory + my $completedest = $workdir . $installer::globals::separator . $filename; + installer::systemactions::copy_one_file($completesource, $completedest); + if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "merge_mergemodules_into_msi_database"); } + + # changing directory + my $from = cwd(); + my $to = $workdir; + chdir($to); + + # remove an existing cabinet file + if ( -f $cabinetfile ) { unlink($cabinetfile); } + + # exclude cabinet file + $systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile; + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not extract cabinet file from merge file: $completedest !", "merge_mergemodules_into_msi_database"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # exclude tables from mergefile + # Attention: All listed tables have to exist in the database. If they not exist, an error window pops up + # and the return value of msidb.exe is not zero. The error window makes it impossible to check the existence + # of a table with the help of the return value. + # Solution: Export of all tables by using "*" . Some tables must exist (File Component Directory), other + # tables do not need to exist (MsiAssembly). + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localworkdir = $workdir; + $localworkdir =~ s/\//\\\\/g; + $systemcall = $msidb . " -d " . $filename . " -f " . $localworkdir . " -e \\\*"; + } + else + { + $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e \*"; + } + + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not exclude tables from merge file: $completedest !", "merge_mergemodules_into_msi_database"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # Determining files + my $idtfilename = "File.idt"; # must exist + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); } + my $filecontent = installer::files::read_file($idtfilename); + my @file_idt_content = (); + my $filecounter = 0; + my %mergefilesequence = (); + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + $filecounter++; + push(@file_idt_content, ${$filecontent}[$i]); + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(\d+?)\s*$/ ) + { + my $filename = $1; + my $filesequence = $8; + $mergefilesequence{$filename} = $filesequence; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database"); + } + } + + # Determining components + $idtfilename = "Component.idt"; # must exist + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); } + $filecontent = installer::files::read_file($idtfilename); + my %componentnames = (); + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $componentnames{$1} = 1; } + } + + # Determining directories + $idtfilename = "Directory.idt"; # must exist + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); } + $filecontent = installer::files::read_file($idtfilename); + my %mergedirectories = (); + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergedirectories{$1} = 1; } + } + + # Determining assemblies + $idtfilename = "MsiAssembly.idt"; # does not need to exist + my $hasmsiassemblies = 0; + my %mergeassemblies = (); + if ( -f $idtfilename ) + { + $filecontent = installer::files::read_file($idtfilename); + $hasmsiassemblies = 1; + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergeassemblies{$1} = 1; } + } + } + + # It is possible, that other tables have to be checked here. This happens, if tables in the + # merge module have to know the "Feature" or the "Directory", under which the content of the + # msm file is integrated into the msi database. + + # Determining name of cabinet file in installation set + my $cabfilename = $mergemodule->{'Cabfilename'}; + if ( $cabfilename ) { installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0); } + + # Analyzing styles + # Flag REMOVE_FILE_TABLE is required for msvc9 Merge-Module, because otherwise msidb.exe + # fails during integration of msm file into msi database. + + my $styles = ""; + my $removefiletable = 0; + if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; } + if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; } + + if ( $removefiletable ) + { + my $removeworkdir = $workdir . $installer::globals::separator . "remove_file_idt"; + if ( ! -d $removeworkdir ) { installer::systemactions::create_directory($removeworkdir); } + my $completeremovedest = $removeworkdir . $installer::globals::separator . $filename; + installer::systemactions::copy_one_file($completedest, $completeremovedest); + if ( ! -f $completeremovedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completeremovedest !", "merge_mergemodules_into_msi_database"); } + + # Unpacking msm file + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localcompleteremovedest = $completeremovedest; + my $localremoveworkdir = $removeworkdir; + $localcompleteremovedest =~ s/\//\\\\/g; + $localremoveworkdir =~ s/\//\\\\/g; + $systemcall = $msidb . " -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -e \\\*"; + } + else + { + $systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e \*"; + } + + $returnvalue = system($systemcall); + + my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt"; + if ( -f $idtfilename ) { unlink $idtfilename; } + unlink $completeremovedest; + + # Packing msm file without "File.idt" + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localcompleteremovedest = $completeremovedest; + my $localremoveworkdir = $removeworkdir; + $localcompleteremovedest =~ s/\//\\\\/g; + $localremoveworkdir =~ s/\//\\\\/g; + $systemcall = $msidb . " -c -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -i \\\*"; + } + else + { + $systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i \*"; + } + $returnvalue = system($systemcall); + + # Using this msm file for merging + if ( -f $completeremovedest ) { $completedest = $completeremovedest; } + else { installer::exiter::exit_program("ERROR: Could not find msm file without File.idt: $completeremovedest !", "merge_mergemodules_into_msi_database"); } + } + + # Saving MergeModule info + + my %onemergemodulehash = (); + $onemergemodulehash{'mergefilepath'} = $completedest; + $onemergemodulehash{'workdir'} = $workdir; + $onemergemodulehash{'cabinetfile'} = $workdir . $installer::globals::separator . $cabinetfile; + $onemergemodulehash{'filenumber'} = $filecounter; + $onemergemodulehash{'componentnames'} = \%componentnames; + $onemergemodulehash{'componentcondition'} = $mergemodule->{'ComponentCondition'}; + $onemergemodulehash{'attributes_add'} = $mergemodule->{'Attributes_Add'}; + $onemergemodulehash{'cabfilename'} = $cabfilename; + $onemergemodulehash{'feature'} = $mergemodule->{'Feature'}; + $onemergemodulehash{'rootdir'} = $mergemodule->{'RootDir'}; + $onemergemodulehash{'name'} = $mergemodule->{'Name'}; + $onemergemodulehash{'mergefilesequence'} = \%mergefilesequence; + $onemergemodulehash{'mergeassemblies'} = \%mergeassemblies; + $onemergemodulehash{'mergedirectories'} = \%mergedirectories; + $onemergemodulehash{'hasmsiassemblies'} = $hasmsiassemblies; + $onemergemodulehash{'removefiletable'} = $removefiletable; + $onemergemodulehash{'fileidtcontent'} = \@file_idt_content; + + $installer::globals::mergemodules{$mergegid} = \%onemergemodulehash; + + # Collecting all cab files, to copy them into installation set + if ( $cabfilename ) { $installer::globals::copy_msm_files{$cabfilename} = $onemergemodulehash{'cabinetfile'}; } + + chdir($from); + } + + $infoline = "All Merge Modules successfully analyzed\n"; + push( @installer::globals::logfileinfo, $infoline); + + $installer::globals::mergemodules_analyzed = 1; + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, stop"); + + $infoline = "\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # 2. Change msi database (has to be done for every msi database -> for every language) + # a. Merge msm file into msi database: msidb.exe -d -m + # b. Extracting tables from msi database: msidb.exe -d -f -e File Media, ... + # c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent + # d. Including tables into msi database: msidb.exe -d -f -i File Media, ... + # e. Copying cabinet file into installation set (later) + + my $counter = 0; + my $mergemodulegid; + foreach $mergemodulegid (keys %installer::globals::mergemodules) + { + my $mergemodulehash = $installer::globals::mergemodules{$mergemodulegid}; + $counter++; + + installer::logger::include_header_into_logfile("Merging Module: $mergemodulehash->{'name'}"); + installer::logger::print_message( "\t... $mergemodulehash->{'name'} ... \n" ); + + $msifilename = installer::converter::make_path_conform($msifilename); + my $workdir = $msifilename; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$workdir); + + # changing directory + my $from = cwd(); + my $to = $workdir; + chdir($to); + + # Saving original msi database + installer::systemactions::copy_one_file($msifilename, "$msifilename\.$counter"); + + # Merging msm file, this is the "real" merge command + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before merging database"); + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localmergemodulepath = $mergemodulehash->{'mergefilepath'}; + my $localmsifilename = $msifilename; + $localmergemodulepath =~ s/\//\\\\/g; + $localmsifilename =~ s/\//\\\\/g; + $systemcall = $msidb . " -d " . $localmsifilename . " -m " . $localmergemodulepath; + } + else + { + $systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'}; + } + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall . Returnvalue: $returnvalue!\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("Could not merge msm file into database: $mergemodulehash->{'mergefilepath'}\n$infoline", "merge_mergemodules_into_msi_database"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database"); + + # Saving original idt files + if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "old.File.idt.$counter"); } + if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "old.Media.idt.$counter"); } + if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "old.Directory.idt.$counter"); } + if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "old.Director.idt.$counter"); } + if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "old.FeatureComponents.idt.$counter"); } + if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "old.FeatureC.idt.$counter"); } + if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "old.MsiAssembly.idt.$counter"); } + if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "old.MsiAssem.idt.$counter"); } + if ( -f "Componen.idt" ) { installer::systemactions::rename_one_file("Componen.idt", "old.Componen.idt.$counter"); } + + # Extracting tables + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables"); + + my $workingtables = "File Media Directory FeatureComponents"; # required tables + # Optional tables can be added now + if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; } + if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) ) { $workingtables = $workingtables . " Component"; } + + # Table "Feature" has to be exported, but it is not necessary to import it. + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localmsifilename = $msifilename; + my $localworkdir = $workdir; + $localmsifilename =~ s/\//\\\\/g; + $localworkdir =~ s/\//\\\\/g; + $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $workingtables; + } + else + { + $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables; + } + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables"); + + # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables + # creates idt-files, that have long names. + + if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); } + if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); } + if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); } + if ( -f "Component.idt" ) { installer::systemactions::rename_one_file("Component.idt", "Componen.idt"); } + + # Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly, Component + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table"); + change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids); + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table"); + $filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid); + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table"); + change_featurecomponent_table($mergemodulehash, $workdir); + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table"); + change_directory_table($mergemodulehash, $workdir); + if ( $mergemodulehash->{'hasmsiassemblies'} ) + { + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table"); + change_msiassembly_table($mergemodulehash, $workdir); + } + + if ( ( $mergemodulehash->{'componentcondition'} ) || ( $mergemodulehash->{'attributes_add'} ) ) + { + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Component table"); + change_component_table($mergemodulehash, $workdir); + } + + # msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates + # new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be + # merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt). + + # Saving original idt files + if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "old.InstallE.idt.$counter"); } + if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "old.InstallU.idt.$counter"); } + if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "old.AdminExe.idt.$counter"); } + if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "old.AdvtExec.idt.$counter"); } + if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "old.ModuleInstallExecuteSequence.idt.$counter"); } + if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "old.ModuleAdminExecuteSequence.idt.$counter"); } + if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "old.ModuleAdvtExecuteSequence.idt.$counter"); } + + # Extracting tables + my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables + my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged + + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localmsifilename = $msifilename; + my $localworkdir = $workdir; + $localmsifilename =~ s/\//\\\\/g; + $localworkdir =~ s/\//\\\\/g; + $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $moduleexecutetables; + } + else + { + $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables; + } + $returnvalue = system($systemcall); + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localmsifilename = $msifilename; + my $localworkdir = $workdir; + $localmsifilename =~ s/\//\\\\/g; + $localworkdir =~ s/\//\\\\/g; + $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $executetables; + } + else + { + $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables; + } + $returnvalue = system($systemcall); + + # Using 8+3 table names, that are used, when tables are integrated into database. The export of tables + # creates idt-files, that have long names. + + if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); } + if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); } + if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); } + if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); } + + # Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence + # into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence + if ( -f "ModuleInstallExecuteSequence.idt" ) + { + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table"); + change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt"); + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table"); + change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt"); + } + + if ( -f "ModuleAdminExecuteSequence.idt" ) + { + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table"); + change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt"); + } + + if ( -f "ModuleAdvtExecuteSequence.idt" ) + { + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table"); + change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt"); + } + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited"); + + # Including tables into msi database + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables"); + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + my $localmsifilename = $msifilename; + my $localworkdir = $workdir; + $localmsifilename =~ s/\//\\\\/g; + $localworkdir =~ s/\//\\\\/g; + foreach $table (split / /, $workingtables . ' ' . $executetables) { + $systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -i " . $table; + my $retval = system($systemcall); + $infoline = "Systemcall returned $retval: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + $returnvalue |= $retval; + } + } + else + { + $systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables; + $returnvalue = system($systemcall); + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + } + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables"); + + chdir($from); + } + + if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language. + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop"); + } + + return $filesref; +} + +######################################################################### +# Analyzing the content of the media table. +######################################################################### + +sub analyze_media_file +{ + my ($filecontent, $workdir) = @_; + + my %filehash = (); + my $linecount = 0; + my $counter = 0; + my $filename = "Media.idt"; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ ) + { + my %line = (); + # Format: DiskId LastSequence DiskPrompt Cabinet VolumeLabel Source + $line{'DiskId'} = $1; + $line{'LastSequence'} = $2; + $line{'DiskPrompt'} = $3; + $line{'Cabinet'} = $4; + $line{'VolumeLabel'} = $5; + $line{'Source'} = $6; + + $counter++; + $filehash{$counter} = \%line; + } + else + { + $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file"); + } + } + + return \%filehash; +} + +######################################################################### +# Setting the DiskID for the new cabinet file +######################################################################### + +sub get_diskid +{ + my ($mediafile, $allupdatediskids, $cabfilename) = @_; + + my $diskid = 0; + my $line; + + if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) )) + { + $diskid = $allupdatediskids->{$cabfilename}; + } + else + { + foreach $line ( keys %{$mediafile} ) + { + if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; } + } + + $diskid++; + } + + return $diskid; +} + +######################################################################### +# Setting the global LastSequence variable +######################################################################### + +sub set_current_last_sequence +{ + my ($mediafile) = @_; + + my $lastsequence = 0; + my $line; + foreach $line ( keys %{$mediafile} ) + { + if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; } + } + + $installer::globals::lastsequence_before_merge = $lastsequence; +} + +######################################################################### +# Setting the LastSequence for the new cabinet file +######################################################################### + +sub get_lastsequence +{ + my ($mergemodulehash, $allupdatelastsequences) = @_; + + my $lastsequence = 0; + + if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) )) + { + $lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}; + } + else + { + $lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'}; + } + + return $lastsequence; +} + +######################################################################### +# Setting the DiskPrompt for the new cabinet file +######################################################################### + +sub get_diskprompt +{ + my ($mediafile) = @_; + + my $diskprompt = ""; + my $line; + foreach $line ( keys %{$mediafile} ) + { + if ( exists($mediafile->{$line}->{'DiskPrompt'}) ) + { + $diskprompt = $mediafile->{$line}->{'DiskPrompt'}; + last; + } + } + + return $diskprompt; +} + +######################################################################### +# Setting the VolumeLabel for the new cabinet file +######################################################################### + +sub get_volumelabel +{ + my ($mediafile) = @_; + + my $volumelabel = ""; + my $line; + foreach $line ( keys %{$mediafile} ) + { + if ( exists($mediafile->{$line}->{'VolumeLabel'}) ) + { + $volumelabel = $mediafile->{$line}->{'VolumeLabel'}; + last; + } + } + + return $volumelabel; +} + +######################################################################### +# Setting the Source for the new cabinet file +######################################################################### + +sub get_source +{ + my ($mediafile) = @_; + + my $source = ""; + my $line; + foreach $line ( keys %{$mediafile} ) + { + if ( exists($mediafile->{$line}->{'Source'}) ) + { + $diskprompt = $mediafile->{$line}->{'Source'}; + last; + } + } + + return $source; +} + +######################################################################### +# For each Merge Module one new line has to be included into the +# media table. +######################################################################### + +sub create_new_media_line +{ + my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_; + + my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'}); + my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences); + my $diskprompt = get_diskprompt($mediafile); + my $cabinet = $mergemodulehash->{'cabfilename'}; + my $volumelabel = get_volumelabel($mediafile); + my $source = get_source($mediafile); + + if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; } + + my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n"; + + return $newline; +} + +######################################################################### +# Setting the last diskid in media table. +######################################################################### + +sub get_last_diskid +{ + my ($mediafile) = @_; + + my $lastdiskid = 0; + my $line; + foreach $line ( keys %{$mediafile} ) + { + if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; } + } + + return $lastdiskid; +} + +######################################################################### +# Setting global variable for last cab file name. +######################################################################### + +sub set_last_cabfile_name +{ + my ($mediafile, $lastdiskid) = @_; + + my $line; + foreach $line ( keys %{$mediafile} ) + { + if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; } + } + my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n"; + push( @installer::globals::logfileinfo, $infoline); +} + +######################################################################### +# In the media table the new cabinet file has to be added or the +# number of the last cabinet file has to be increased. +######################################################################### + +sub change_media_table +{ + my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_; + + my $infoline = "Changing content of table \"Media\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = "Media.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); } + + my $filecontent = installer::files::read_file($filename); + my $mediafile = analyze_media_file($filecontent, $workdir); + set_current_last_sequence($mediafile); + + if ( $installer::globals::fix_number_of_cab_files ) + { + # Determining the line with the highest sequencenumber. That file needs to be updated. + my $lastdiskid = get_last_diskid($mediafile); + if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); } + my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'}; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ ) + { + my $start = $1; + my $final = $2; + $infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n"; + push( @installer::globals::logfileinfo, $infoline); + my $newline = $start . $newmaxsequencenumber . $final . "\n"; + ${$filecontent}[$i] = $newline; + $infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + } + else + { + # the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid) + if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) ) + { + $installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids); + } + + $infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n"; + push( @installer::globals::logfileinfo, $infoline); + + # adding new line + push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid}); + } + + # saving file + installer::files::save_file($filename, $filecontent); +} + +######################################################################### +# Putting the directory table content into a hash. +######################################################################### + +sub analyze_directorytable_file +{ + my ($filecontent, $idtfilename) = @_; + + my %dirhash = (); + # Iterating over the file content + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ ) + { + my %line = (); + # Format: Directory Directory_Parent DefaultDir + $line{'Directory'} = $1; + $line{'Directory_Parent'} = $2; + $line{'DefaultDir'} = $3; + $line{'linenumber'} = $i; # saving also the line number for direct access + + my $uniquekey = $line{'Directory'}; + $dirhash{$uniquekey} = \%line; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file"); + } + } + + return \%dirhash; +} + +######################################################################### +# Putting the msi assembly table content into a hash. +######################################################################### + +sub analyze_msiassemblytable_file +{ + my ($filecontent, $idtfilename) = @_; + + my %assemblyhash = (); + # Iterating over the file content + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ ) + { + my %line = (); + # Format: Component_ Feature_ File_Manifest File_Application Attributes + $line{'Component'} = $1; + $line{'Feature'} = $2; + $line{'File_Manifest'} = $3; + $line{'File_Application'} = $4; + $line{'Attributes'} = $5; + $line{'linenumber'} = $i; # saving also the line number for direct access + + my $uniquekey = $line{'Component'}; + $assemblyhash{$uniquekey} = \%line; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file"); + } + } + + return \%assemblyhash; +} + +######################################################################### +# Putting the file table content into a hash. +######################################################################### + +sub analyze_filetable_file +{ + my ( $filecontent, $idtfilename ) = @_; + + my %filehash = (); + # Iterating over the file content + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ ) + { + my %line = (); + # Format: File Component_ FileName FileSize Version Language Attributes Sequence + $line{'File'} = $1; + $line{'Component'} = $2; + $line{'FileName'} = $3; + $line{'FileSize'} = $4; + $line{'Version'} = $5; + $line{'Language'} = $6; + $line{'Attributes'} = $7; + $line{'Sequence'} = $8; + $line{'linenumber'} = $i; # saving also the line number for direct access + + my $uniquekey = $line{'File'}; + $filehash{$uniquekey} = \%line; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file"); + } + } + + return \%filehash; +} + +######################################################################### +# Creating a new line for the directory table. +######################################################################### + +sub get_new_line_for_directory_table +{ + my ($dir) = @_; + + my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n"; + + return $newline; +} + +######################################################################### +# Creating a new line for the file table. +######################################################################### + +sub get_new_line_for_file_table +{ + my ($file) = @_; + + my $newline = "$file->{'File'}\t$file->{'Component'}\t$file->{'FileName'}\t$file->{'FileSize'}\t$file->{'Version'}\t$file->{'Language'}\t$file->{'Attributes'}\t$file->{'Sequence'}\n"; + + return $newline; +} + +######################################################################### +# Creating a new line for the msiassembly table. +######################################################################### + +sub get_new_line_for_msiassembly_table +{ + my ($assembly) = @_; + + my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n"; + + return $newline; +} + +######################################################################### +# Sorting the files collector, if there are files, following +# the merge module files. +######################################################################### + +sub sort_files_collector_for_sequence +{ + my ($filesref) = @_; + + my @sortarray = (); + my %helphash = (); + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); } + my $sequence = $onefile->{'sequencenumber'}; + $helphash{$sequence} = $onefile; + } + + foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); } + + return \@sortarray; +} + +######################################################################### +# In the file table "Sequence" and "Attributes" have to be changed. +######################################################################### + +sub change_file_table +{ + my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_; + + my $infoline = "Changing content of table \"File\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $idtfilename = "File.idt"; + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); } + + my $filecontent = installer::files::read_file($idtfilename); + + # If File.idt needed to be removed before the msm database was merged into the msi database, + # now it is time to add the content into File.idt + if ( $mergemodulehash->{'removefiletable'} ) + { + for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ ) + { + push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]); + } + } + + # Unpacking the MergeModule.CABinet (only once) + # Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories. + + my $empty = ""; + my $unpackdir = installer::systemactions::create_directories("cab", \$empty); + push(@installer::globals::removedirs, $unpackdir); + $unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid; + + my %newfileshash = (); + if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector )) + { + if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); } + + # changing directory + my $from = cwd(); + my $to = $mergemodulehash->{'workdir'}; + if ( $^O =~ /cygwin/i ) { + $to = qx(cygpath -u "$to"); + chomp $to; + } + + chdir($to) || die "Could not chdir to \"$to\"\n"; + + # Unpack the cab file, so that in can be included into the last office cabinet file. + # Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that + # should be available on every Windows system. + + $infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n"; + push( @installer::globals::logfileinfo, $infoline); + + # Avoid the Cygwin expand command + my $expandfile = "expand.exe"; # Has to be in the path + if ( $^O =~ /cygwin/i ) { + $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe); + chomp $expandfile; + } + + my $cabfilename = "MergeModule.CABinet"; + + my $systemcall = ""; + if ( $^O =~ /cygwin/i ) { + my $localunpackdir = qx(cygpath -m "$unpackdir"); + chomp $localunpackdir; + $systemcall = $expandfile . " " . $cabfilename . " -F:\\\* " . $localunpackdir; + } + else + { + $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " 2\>\&1"; + } + + my $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + chdir($from); + } + + # For performance reasons creating a hash with file names and rows + # The content of File.idt is changed after every merge -> content cannot be saved in global hash + $merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename); + + my $attributes = "16384"; # Always + + my $filename; + foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} ) + { + my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename}; + + if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); } + my $filehash = $merge_filetablehashref->{$filename}; + my $linenumber = $filehash->{'linenumber'}; + + # <- this line has to be changed concerning "Sequence" and "Attributes" + $filehash->{'Attributes'} = $attributes; + + # If this is an update process, the sequence numbers have to be reused. + if ( $installer::globals::updatedatabase ) + { + if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); } + $filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}}; + # Saving all mergemodule sequence numbers. This is important for creating ddf files + $installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1; + } + else + { + # Important saved data: $installer::globals::lastsequence_before_merge. + # This mechanism keeps the correct order inside the new cabinet file. + $filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge; + } + + my $oldline = ${$filecontent}[$linenumber]; + my $newline = get_new_line_for_file_table($filehash); + ${$filecontent}[$linenumber] = $newline; + + $infoline = "Merge, replacing line:\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Old: $oldline\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "New: $newline\n"; + push( @installer::globals::logfileinfo, $infoline); + + # Adding files to the files collector (but only once) + if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector )) + { + # If the number of cabinet files is kept constant, + # all files from the mergemodule cabinet files will + # be integrated into the last office cabinet file + # (installer::globals::lastcabfilename). + # Therefore the files must now be added to the filescollector, + # so that they will be integrated into the ddf files. + + # Problem with very long filenames -> copying to shorter filenames + my $newfilename = "f" . $filehash->{'Sequence'}; + my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'}; + my $completedest = $unpackdir . $installer::globals::separator . $newfilename; + installer::systemactions::copy_one_file($completesource, $completedest); + + my $locallastcabfilename = $installer::globals::lastcabfilename; + if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; } # removing beginning hashes + + # Create new file hash for file collector + my %newfile = (); + $newfile{'sequencenumber'} = $filehash->{'Sequence'}; + $newfile{'assignedsequencenumber'} = $filehash->{'Sequence'}; + $newfile{'cabinet'} = $locallastcabfilename; + $newfile{'sourcepath'} = $completedest; + $newfile{'componentname'} = $filehash->{'Component'}; + $newfile{'uniquename'} = $filehash->{'File'}; + $newfile{'Name'} = $filehash->{'File'}; + + # Saving in globals sequence hash + $installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'}; + + if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); } + + # Collecting all new files. Attention: This files must be included into files collector in correct order! + $newfileshash{$filehash->{'Sequence'}} = \%newfile; + # push(@{$filesref}, \%newfile); -> this is not the correct order + } + } + + # Now the files can be added to the files collector + # In the case of an update process, there can be new files, that have to be added after the merge module files. + # Warning: In multilingual installation sets, the files only have to be added once to the files collector! + + if ( ! $installer::globals::mergefiles_added_into_collector ) + { + foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); } + if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); } + # $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language. + } + + # Saving the idt file (for every language) + installer::files::save_file($idtfilename, $filecontent); + + return $filesref; +} + +######################################################################### +# Reading the file "Director.idt". The Directory, that is defined in scp +# has to be defined in this table. +######################################################################### + +sub collect_directories +{ + my $idtfilename = "Director.idt"; + my $filecontent = installer::files::read_file($idtfilename); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + # Format: Directory Directory_Parent DefaultDir + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ ) + { + $installer::globals::merge_alldirectory_hash{$1} = 1; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories"); + } + } +} + +######################################################################### +# Reading the file "Feature.idt". The Feature, that is defined in scp +# has to be defined in this table. +######################################################################### + +sub collect_feature +{ + my $idtfilename = "Feature.idt"; + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); } + my $filecontent = installer::files::read_file($idtfilename); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + # Format: Feature Feature_Parent Title Description Display Level Directory_ Attributes + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + $installer::globals::merge_allfeature_hash{$1} = 1; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature"); + } + } +} + +######################################################################### +# In the featurecomponent table, the new connections have to be added. +######################################################################### + +sub change_featurecomponent_table +{ + my ($mergemodulehash, $workdir) = @_; + + my $infoline = "Changing content of table \"FeatureComponents\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $idtfilename = "FeatureC.idt"; + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); } + + my $filecontent = installer::files::read_file($idtfilename); + + # Simply adding for each new component one line. The Feature has to be defined in scp project. + my $feature = $mergemodulehash->{'feature'}; + + if ( ! $installer::globals::mergefeaturecollected ) + { + collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash + $installer::globals::mergefeaturecollected = 1; + } + + if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) ) + { + installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table"); + } + + my $component; + foreach $component ( keys %{$mergemodulehash->{'componentnames'}} ) + { + my $line = "$feature\t$component\n"; + push(@{$filecontent}, $line); + $infoline = "Adding line: $line\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # saving file + installer::files::save_file($idtfilename, $filecontent); +} + +############################################################################### +# In the components table, the conditions or attributes of merge modules should be updated +############################################################################### + +sub change_component_table +{ + my ($mergemodulehash, $workdir) = @_; + + my $infoline = "Changing content of table \"Component\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $idtfilename = "Componen.idt"; + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_component_table"); } + + my $filecontent = installer::files::read_file($idtfilename); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + my $component; + foreach $component ( keys %{$mergemodulehash->{'componentnames'}} ) + { + if ( my ( $comp_, $compid_, $dir_, $attr_, $cond_, $keyp_ ) = ${$filecontent}[$i] =~ /^\s*($component)\t(.*?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/) + { + my $newattr_ = ( $attr_ =~ /^\s*0x/ ) ? hex($attr_) : $attr_; + if ( $mergemodulehash->{'attributes_add'} ) + { + $infoline = "Adding attribute(s) ($mergemodulehash->{'attributes_add'}) from scp2 to component $comp_\n"; + push( @installer::globals::logfileinfo, $infoline); + if ( $mergemodulehash->{'attributes_add'} =~ /^\s*0x/ ) + { + $newattr_ = $newattr_ | hex($mergemodulehash->{'attributes_add'}); + } + else + { + $newattr_ = $newattr_ | $mergemodulehash->{'attributes_add'}; + } + $infoline = "Old attribute(s): $attr_\nNew attribute(s): $newattr_\n"; + push( @installer::globals::logfileinfo, $infoline); + } + my $newcond_ = $cond_; + if ( $mergemodulehash->{'componentcondition'} ) + { + $infoline = "Adding condition ($mergemodulehash->{'componentcondition'}) from scp2 to component $comp_\n"; + push( @installer::globals::logfileinfo, $infoline); + if ($cond_) + { + $newcond_ = "($cond_) AND ($mergemodulehash->{'componentcondition'})"; + } + else + { + $newcond_ = "$mergemodulehash->{'componentcondition'}"; + } + $infoline = "Old condition: $cond_\nNew condition: $newcond_\n"; + push( @installer::globals::logfileinfo, $infoline); + } + ${$filecontent}[$i] = "$comp_\t$compid_\t$dir_\t$newattr_\t$newcond_\t$keyp_\n"; + } + } + } + + # saving file + installer::files::save_file($idtfilename, $filecontent); +} + +######################################################################### +# In the directory table, the directory parent has to be changed, +# if it is not TARGETDIR. +######################################################################### + +sub change_directory_table +{ + my ($mergemodulehash, $workdir) = @_; + + # directory for MergeModule has to be defined in scp project + my $scpdirectory = $mergemodulehash->{'rootdir'}; + + if ( $scpdirectory ne "TARGETDIR" ) # TARGETDIR works fine, when using msidb.exe + { + my $infoline = "Changing content of table \"Directory\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $idtfilename = "Director.idt"; + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); } + + my $filecontent = installer::files::read_file($idtfilename); + + if ( ! $installer::globals::mergedirectoriescollected ) + { + collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column! + $installer::globals::mergedirectoriescollected = 1; + } + + if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) ) + { + installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table"); + } + + # If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed + my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename); + + my $directory; + foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} ) + { + if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); } + my $dirhash = $merge_directorytablehashref->{$directory}; + my $linenumber = $dirhash->{'linenumber'}; + + # <- this line has to be changed concerning "Directory_Parent", + # if the current value is "TARGETDIR", which is the default value from msidb.exe + + if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" ) + { + $dirhash->{'Directory_Parent'} = $scpdirectory; + + my $oldline = ${$filecontent}[$linenumber]; + my $newline = get_new_line_for_directory_table($dirhash); + ${$filecontent}[$linenumber] = $newline; + + $infoline = "Merge, replacing line:\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Old: $oldline\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "New: $newline\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + # saving file + installer::files::save_file($idtfilename, $filecontent); + } +} + +######################################################################### +# In the msiassembly table, the feature has to be changed. +######################################################################### + +sub change_msiassembly_table +{ + my ($mergemodulehash, $workdir) = @_; + + my $infoline = "Changing content of table \"MsiAssembly\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $idtfilename = "MsiAssem.idt"; + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); } + + my $filecontent = installer::files::read_file($idtfilename); + + # feature has to be defined in scp project + my $feature = $mergemodulehash->{'feature'}; + + if ( ! $installer::globals::mergefeaturecollected ) + { + collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash + $installer::globals::mergefeaturecollected = 1; + } + + if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) ) + { + installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table"); + } + + my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename); + + my $component; + foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} ) + { + if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); } + my $assemblyhash = $merge_msiassemblytablehashref->{$component}; + my $linenumber = $assemblyhash->{'linenumber'}; + + # <- this line has to be changed concerning "Feature" + $assemblyhash->{'Feature'} = $feature; + + my $oldline = ${$filecontent}[$linenumber]; + my $newline = get_new_line_for_msiassembly_table($assemblyhash); + ${$filecontent}[$linenumber] = $newline; + + $infoline = "Merge, replacing line:\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Old: $oldline\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "New: $newline\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # saving file + installer::files::save_file($idtfilename, $filecontent); +} + +######################################################################### +# Creating file content hash +######################################################################### + +sub make_executeidtcontent_hash +{ + my ($filecontent, $idtfilename) = @_; + + my %newhash = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + # Format for all sequence tables: Action Condition Sequence + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ ) + { + my %onehash = (); + $onehash{'Action'} = $1; + $onehash{'Condition'} = $2; + $onehash{'Sequence'} = $3; + $newhash{$onehash{'Action'}} = \%onehash; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash"); + } + } + + return \%newhash; +} + +######################################################################### +# Creating file content hash +######################################################################### + +sub make_moduleexecuteidtcontent_hash +{ + my ($filecontent, $idtfilename) = @_; + + my %newhash = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + # Format for all module sequence tables: Action Sequence BaseAction After Condition + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my %onehash = (); + $onehash{'Action'} = $1; + $onehash{'Sequence'} = $2; + $onehash{'BaseAction'} = $3; + $onehash{'After'} = $4; + $onehash{'Condition'} = $5; + $newhash{$onehash{'Action'}} = \%onehash; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash"); + } + } + + return \%newhash; +} + +######################################################################### +# ExecuteSequence tables need to be merged with +# ModuleExecuteSequence tables created by msidb.exe. +######################################################################### + +sub change_executesequence_table +{ + my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_; + + my $infoline = "Changing content of table \"$idtfilename\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); } + if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); } + + # Reading file content + my $idtfilecontent = installer::files::read_file($idtfilename); + my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename); + + # Converting to hash + my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename); + my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename); + + # Merging + foreach my $action ( keys %{$moduleidtcontenthash} ) + { + if ( exists($idtcontenthash->{$action}) ) { next; } # Action already exists, can be ignored + + if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table + + my $actionhashref = $moduleidtcontenthash->{$action}; + if ( $actionhashref->{'Sequence'} ne "" ) + { + # Format for all sequence tables: Action Condition Sequence + my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n"; + # Adding to table + push(@{$idtfilecontent}, $newline); + # Also adding to hash + my %idttablehash = (); + $idttablehash{'Action'} = $actionhashref->{'Action'}; + $idttablehash{'Condition'} = $actionhashref->{'Condition'}; + $idttablehash{'Sequence'} = $actionhashref->{'Sequence'}; + $idtcontenthash->{$action} = \%idttablehash; + + } + else # no sequence defined, using syntax "BaseAction" and "After" + { + my $baseactionname = $actionhashref->{'BaseAction'}; + # If this baseactionname is not defined in execute idt file, it is not possible to merge + if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); } + + my $baseaction = $idtcontenthash->{$baseactionname}; + my $sequencenumber = $baseaction->{'Sequence'}; + if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; } + else { $sequencenumber = $sequencenumber - 1; } + + # Format for all sequence tables: Action Condition Sequence + my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\n"; + # Adding to table + push(@{$idtfilecontent}, $newline); + # Also adding to hash + my %idttablehash = (); + $idttablehash{'Action'} = $actionhashref->{'Action'}; + $idttablehash{'Condition'} = $actionhashref->{'Condition'}; + $idttablehash{'Sequence'} = $sequencenumber; + $idtcontenthash->{$action} = \%idttablehash; + } + } + + # saving file + installer::files::save_file($idtfilename, $idtfilecontent); +} + + +1; diff --git a/solenv/bin/modules/installer/windows/msiglobal.pm b/solenv/bin/modules/installer/windows/msiglobal.pm new file mode 100644 index 000000000..f830c6eb0 --- /dev/null +++ b/solenv/bin/modules/installer/windows/msiglobal.pm @@ -0,0 +1,1684 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::msiglobal; + +use Cwd; +use Digest::MD5; +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::remover; +use installer::scriptitems; +use installer::systemactions; +use installer::worker; +use installer::windows::idtglobal; +use installer::windows::language; + +########################################################################### +# Generating the header of the ddf file. +# The usage of ddf files is needed, because makecab.exe can only include +# one sourcefile into a cab file +########################################################################### + +sub write_ddf_file_header +{ + my ($ddffileref, $cabinetfile, $installdir) = @_; + + my $oneline; + + $oneline = ".Set CabinetName1=" . $cabinetfile . "\n"; + push(@{$ddffileref} ,$oneline); + $oneline = ".Set ReservePerCabinetSize=128\n"; # This reserves space for a digital signature. + push(@{$ddffileref} ,$oneline); + $oneline = ".Set MaxDiskSize=2147483648\n"; # This allows the .cab file to get a size of 2 GB. + push(@{$ddffileref} ,$oneline); + $oneline = ".Set CompressionType=LZX\n"; + push(@{$ddffileref} ,$oneline); + $oneline = ".Set Compress=ON\n"; + push(@{$ddffileref} ,$oneline); +# The window size for LZX compression +# CompressionMemory=15 | 16 | ... | 21 +# Reference: http://msdn.microsoft.com/en-us/library/bb417343.aspx + $oneline = ".Set CompressionMemory=$installer::globals::cabfilecompressionlevel\n"; + push(@{$ddffileref} ,$oneline); + $oneline = ".Set Cabinet=ON\n"; + push(@{$ddffileref} ,$oneline); + $oneline = ".Set DiskDirectoryTemplate=" . $installdir . "\n"; + push(@{$ddffileref} ,$oneline); +} + +########################################################################## +# Lines in ddf files must not contain more than 256 characters +########################################################################## + +sub check_ddf_file +{ + my ( $ddffile, $ddffilename ) = @_; + + my $maxlength = 0; + my $maxline = 0; + my $linelength = 0; + my $linenumber = 0; + + for ( my $i = 0; $i <= $#{$ddffile}; $i++ ) + { + my $oneline = ${$ddffile}[$i]; + + $linelength = length($oneline); + $linenumber = $i + 1; + + if ( $linelength > 256 ) + { + installer::exiter::exit_program("ERROR \"$ddffilename\" line $linenumber: Lines in ddf files must not contain more than 256 characters!", "check_ddf_file"); + } + + if ( $linelength > $maxlength ) + { + $maxlength = $linelength; + $maxline = $linenumber; + } + } + + my $infoline = "Check of ddf file \"$ddffilename\": Maximum length \"$maxlength\" in line \"$maxline\" (allowed line length: 256 characters)\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +########################################################################## +# Lines in ddf files must not be longer than 256 characters. +# Therefore it can be useful to use relative paths. Then it is +# necessary to change into temp directory before calling +# makecab.exe. +########################################################################## + +sub make_relative_ddf_path +{ + my ( $sourcepath ) = @_; + + my $windowstemppath = $installer::globals::temppath; + + if ( $^O =~ /cygwin/i ) + { + $windowstemppath = $installer::globals::cyg_temppath; + } + + $sourcepath =~ s/\Q$windowstemppath\E//; + $sourcepath =~ s/^[\\\/]//; + + return $sourcepath; +} + +########################################################################## +# Returning the order of the sequences in the files array. +########################################################################## + +sub get_sequenceorder +{ + my ($filesref) = @_; + + my %order = (); + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + if ( ! $onefile->{'assignedsequencenumber'} ) { installer::exiter::exit_program("ERROR: No sequence number assigned to $onefile->{'gid'} ($onefile->{'uniquename'})!", "get_sequenceorder"); } + $order{$onefile->{'assignedsequencenumber'}} = $i; + } + + return \%order; +} + +########################################################################## +# Generation the list, in which the source of the files is connected +# with the cabinet destination file. Because more than one file needs +# to be included into a cab file, this has to be done via ddf files. +########################################################################## + +sub generate_cab_file_list +{ + my ($filesref, $installdir, $ddfdir, $allvariables) = @_; + + my @cabfilelist = (); + + installer::logger::include_header_into_logfile("Generating ddf files"); + + installer::logger::include_timestamp_into_logfile("Performance Info: ddf file generation start"); + + if ( $^O =~ /cygwin/i ) { installer::worker::generate_cygwin_paths($filesref); } + + if (( $installer::globals::fix_number_of_cab_files ) && ( $installer::globals::updatedatabase )) + { + my $sequenceorder = get_sequenceorder($filesref); + + my $counter = 1; + + while ( ( exists($sequenceorder->{$counter}) ) || ( exists($installer::globals::allmergemodulefilesequences{$counter}) ) ) # Taking care of files from merge modules + { +# if ( exists($installer::globals::allmergemodulefilesequences{$counter}) ) +# { +# # Skipping this sequence, it is not included in $filesref, because it is assigned to a file from a merge module.\n"; +# $counter++; +# next; +# } + + my $onefile = ${$filesref}[$sequenceorder->{$counter}]; + $counter++; + + my $cabinetfile = $onefile->{'cabinet'}; + my $sourcepath = $onefile->{'sourcepath'}; + if ( $^O =~ /cygwin/i ) { $sourcepath = $onefile->{'cyg_sourcepath'}; } + my $uniquename = $onefile->{'uniquename'}; + + my $styles = ""; + my $doinclude = 1; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; }; + if ( $styles =~ /\bDONT_PACK\b/ ) { $doinclude = 0; } + + # to avoid lines with more than 256 characters, it can be useful to use relative paths + $sourcepath = make_relative_ddf_path($sourcepath); + + my @ddffile = (); + + write_ddf_file_header(\@ddffile, $cabinetfile, $installdir); + + my $ddfline = "\"" . $sourcepath . "\" \"" . $uniquename . "\"\n"; + if ( $doinclude ) { push(@ddffile, $ddfline); } + + my $nextfile = ""; + if ( ${$filesref}[$sequenceorder->{$counter}] ) { $nextfile = ${$filesref}[$sequenceorder->{$counter}]; } + + my $nextcabinetfile = ""; + + if ( $nextfile->{'cabinet'} ) { $nextcabinetfile = $nextfile->{'cabinet'}; } + + while ( $nextcabinetfile eq $cabinetfile ) + { + $sourcepath = $nextfile->{'sourcepath'}; + if ( $^O =~ /cygwin/i ) { $sourcepath = $nextfile->{'cyg_sourcepath'}; } + # to avoid lines with more than 256 characters, it can be useful to use relative paths + $sourcepath = make_relative_ddf_path($sourcepath); + $uniquename = $nextfile->{'uniquename'}; + my $localdoinclude = 1; + my $nextfilestyles = ""; + if ( $nextfile->{'Styles'} ) { $nextfilestyles = $nextfile->{'Styles'}; } + if ( $nextfilestyles =~ /\bDONT_PACK\b/ ) { $localdoinclude = 0; } + $ddfline = "\"" . $sourcepath . "\" \"" . $uniquename . "\"\n"; + if ( $localdoinclude ) { push(@ddffile, $ddfline); } + $counter++; + $nextfile = ""; + $nextcabinetfile = "_lastfile_"; + if (( exists($sequenceorder->{$counter}) ) && ( ${$filesref}[$sequenceorder->{$counter}] )) + { + $nextfile = ${$filesref}[$sequenceorder->{$counter}]; + $nextcabinetfile = $nextfile->{'cabinet'}; + } + } + + # creating the DDF file + + my $ddffilename = $cabinetfile; + $ddffilename =~ s/.cab/.ddf/; + $ddfdir =~ s/\Q$installer::globals::separator\E\s*$//; + $ddffilename = $ddfdir . $installer::globals::separator . $ddffilename; + + installer::files::save_file($ddffilename ,\@ddffile); + my $infoline = "Created ddf file: $ddffilename\n"; + push(@installer::globals::logfileinfo, $infoline); + + # lines in ddf files must not be longer than 256 characters + check_ddf_file(\@ddffile, $ddffilename); + + # Writing the makecab system call + + my $oneline = "makecab.exe /V3 /F " . $ddffilename . " 2\>\&1 |" . "\n"; + + push(@cabfilelist, $oneline); + + # collecting all ddf files + push(@installer::globals::allddffiles, $ddffilename); + } + } + elsif ( $installer::globals::fix_number_of_cab_files ) + { + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + my $cabinetfile = $onefile->{'cabinet'}; + my $sourcepath = $onefile->{'sourcepath'}; + if ( $^O =~ /cygwin/i ) { $sourcepath = $onefile->{'cyg_sourcepath'}; } + my $uniquename = $onefile->{'uniquename'}; + + my $styles = ""; + my $doinclude = 1; + if ( $onefile->{'Styles'} ) { $styles = $onefile->{'Styles'}; }; + if ( $styles =~ /\bDONT_PACK\b/ ) { $doinclude = 0; } + + + # to avoid lines with more than 256 characters, it can be useful to use relative paths + $sourcepath = make_relative_ddf_path($sourcepath); + + # all files with the same cabinetfile are directly behind each other in the files collector + + my @ddffile = (); + + write_ddf_file_header(\@ddffile, $cabinetfile, $installdir); + + my $ddfline = "\"" . $sourcepath . "\" \"" . $uniquename . "\"\n"; + if ( $doinclude ) { push(@ddffile, $ddfline); } + + my $nextfile = ${$filesref}[$i+1]; + my $nextcabinetfile = ""; + + if ( $nextfile->{'cabinet'} ) { $nextcabinetfile = $nextfile->{'cabinet'}; } + + while ( $nextcabinetfile eq $cabinetfile ) + { + $sourcepath = $nextfile->{'sourcepath'}; + if ( $^O =~ /cygwin/i ) { $sourcepath = $nextfile->{'cyg_sourcepath'}; } + # to avoid lines with more than 256 characters, it can be useful to use relative paths + $sourcepath = make_relative_ddf_path($sourcepath); + $uniquename = $nextfile->{'uniquename'}; + my $localdoinclude = 1; + my $nextfilestyles = ""; + if ( $nextfile->{'Styles'} ) { $nextfilestyles = $nextfile->{'Styles'}; } + if ( $nextfilestyles =~ /\bDONT_PACK\b/ ) { $localdoinclude = 0; } + $ddfline = "\"" . $sourcepath . "\" \"" . $uniquename . "\"\n"; + if ( $localdoinclude ) { push(@ddffile, $ddfline); } + $i++; # increasing the counter! + $nextfile = ${$filesref}[$i+1]; + if ( $nextfile ) { $nextcabinetfile = $nextfile->{'cabinet'}; } + else { $nextcabinetfile = "_lastfile_"; } + } + + # creating the DDF file + + my $ddffilename = $cabinetfile; + $ddffilename =~ s/.cab/.ddf/; + $ddfdir =~ s/\Q$installer::globals::separator\E\s*$//; + $ddffilename = $ddfdir . $installer::globals::separator . $ddffilename; + + installer::files::save_file($ddffilename ,\@ddffile); + my $infoline = "Created ddf file: $ddffilename\n"; + push(@installer::globals::logfileinfo, $infoline); + + # lines in ddf files must not be longer than 256 characters + check_ddf_file(\@ddffile, $ddffilename); + + # Writing the makecab system call + + my $oneline = "makecab.exe /V3 /F " . $ddffilename . " 2\>\&1 |" . "\n"; + + push(@cabfilelist, $oneline); + + # collecting all ddf files + push(@installer::globals::allddffiles, $ddffilename); + } + } + else + { + installer::exiter::exit_program("ERROR: No cab file specification in globals.pm !", "generate_cab_file_list"); + } + + installer::logger::include_timestamp_into_logfile("Performance Info: ddf file generation end"); + + return \@cabfilelist; # contains all system calls for packaging process +} + +######################################################################## +# For update and patch reasons the pack order needs to be saved. +# The pack order is saved in the ddf files; the names and locations +# of the ddf files are saved in @installer::globals::allddffiles. +# The outputfile "packorder.txt" can be saved in +# $installer::globals::infodirectory . +######################################################################## + +sub save_packorder +{ + installer::logger::include_header_into_logfile("Saving pack order"); + + installer::logger::include_timestamp_into_logfile("Performance Info: saving pack order start"); + + my $packorderfilename = "packorder.txt"; + $packorderfilename = $installer::globals::infodirectory . $installer::globals::separator . $packorderfilename; + + my @packorder = (); + + my $headerline = "\# Syntax\: Filetable_Sequence Cabinetfilename Physical_FileName Unique_FileName\n\n"; + push(@packorder, $headerline); + + for ( my $i = 0; $i <= $#installer::globals::allddffiles; $i++ ) + { + my $ddffilename = $installer::globals::allddffiles[$i]; + my $ddffile = installer::files::read_file($ddffilename); + my $cabinetfile = ""; + + for ( my $j = 0; $j <= $#{$ddffile}; $j++ ) + { + my $oneline = ${$ddffile}[$j]; + + # Getting the Cabinet file name + + if ( $oneline =~ /^\s*\.Set\s+CabinetName.*\=(.*?)\s*$/ ) { $cabinetfile = $1; } + if ( $oneline =~ /^\s*\.Set\s+/ ) { next; } + + if ( $oneline =~ /^\s*\"(.*?)\"\s+\"(.*?)\"\s*$/ ) + { + my $sourcefile = $1; + my $uniquefilename = $2; + + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$sourcefile); + + # Using the hash created in create_files_table for performance reasons to get the sequence number + my $filesequence = ""; + if ( exists($installer::globals::uniquefilenamesequence{$uniquefilename}) ) { $filesequence = $installer::globals::uniquefilenamesequence{$uniquefilename}; } + else { installer::exiter::exit_program("ERROR: No sequence number value for $uniquefilename !", "save_packorder"); } + + my $line = $filesequence . "\t" . $cabinetfile . "\t" . $sourcefile . "\t" . $uniquefilename . "\n"; + push(@packorder, $line); + } + } + } + + installer::files::save_file($packorderfilename ,\@packorder); + + installer::logger::include_timestamp_into_logfile("Performance Info: saving pack order end"); +} + +################################################################# +# Returning the name of the msi database +################################################################# + +sub get_msidatabasename +{ + my ($allvariableshashref, $language) = @_; + + my $databasename = $allvariableshashref->{'PRODUCTNAME'} . $allvariableshashref->{'PRODUCTVERSION'}; + $databasename = lc($databasename); + $databasename =~ s/\.//g; + $databasename =~ s/\-//g; + $databasename =~ s/\s//g; + + # possibility to overwrite the name with variable DATABASENAME + if ( $allvariableshashref->{'DATABASENAME'} ) + { + $databasename = $allvariableshashref->{'DATABASENAME'}; + } + + if ( $language ) + { + if (!($language eq "")) + { + $databasename .= "_$language"; + } + } + + $databasename .= ".msi"; + + return $databasename; +} + +################################################################# +# Creating the msi database +# This works only on Windows +################################################################# + +sub create_msi_database +{ + my ($idtdirbase ,$msifilename) = @_; + + # -f : path containing the idt files + # -d : msi database, including path + # -c : create database + # -i : include the following tables ("*" includes all available tables) + + my $msidb = "msidb.exe"; # Has to be in the path + my $extraslash = ""; # Has to be set for non-ActiveState perl + + installer::logger::include_header_into_logfile("Creating msi database"); + + $idtdirbase = installer::converter::make_path_conform($idtdirbase); + + $msifilename = installer::converter::make_path_conform($msifilename); + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $idtdirbase =~ s/\//\\\\/g; + $msifilename =~ s/\//\\\\/g; + $extraslash = "\\"; + } + if ( $^O =~ /linux/i ) { + $extraslash = "\\"; + } + my $systemcall = $msidb . " -f " . $idtdirbase . " -d " . $msifilename . " -c " . "-i " . $extraslash . "*"; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $msidb!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed $msidb successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +################################################################# +# Returning the msi version for the Summary Information Stream +################################################################# + +sub get_msiversion_for_sis +{ + my $msiversion = "200"; + return $msiversion; +} + +################################################################# +# Returning the word count for the Summary Information Stream +################################################################# + +sub get_wordcount_for_sis +{ + my $wordcount = "0"; + return $wordcount; +} + +################################################################# +# Returning the template for the Summary Information Stream +################################################################# + +sub get_template_for_sis +{ + my ( $language, $allvariables ) = @_; + + my $windowslanguage = installer::windows::language::get_windows_language($language); + + my $architecture = "Intel"; + + if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) { $architecture = "x64"; } + + my $value = "\"" . $architecture . ";" . $windowslanguage; # adding the Windows language + + $value = $value . "\""; # adding ending '"' + + return $value ; +} + +################################################################# +# Returning the PackageCode for the Summary Information Stream +################################################################# + +sub get_packagecode_for_sis +{ + # always generating a new package code for each package + + my $guidref = get_guid_list(1, 1); # only one GUID shall be generated + + ${$guidref}[0] =~ s/\s*$//; # removing ending spaces + + my $guid = "\{" . ${$guidref}[0] . "\}"; + + my $infoline = "PackageCode: $guid\n"; + push( @installer::globals::logfileinfo, $infoline); + + return $guid; +} + +################################################################# +# Returning the author for the Summary Information Stream +################################################################# + +sub get_author_for_sis +{ + my $author = $installer::globals::longmanufacturer; + + $author = "\"" . $author . "\""; + + return $author; +} + +################################################################# +# Returning the subject for the Summary Information Stream +################################################################# + +sub get_subject_for_sis +{ + my ( $allvariableshashref ) = @_; + + my $subject = $allvariableshashref->{'PRODUCTNAME'} . " " . $allvariableshashref->{'PRODUCTVERSION'}; + + $subject = "\"" . $subject . "\""; + + return $subject; +} + +###################################################################### +# Returning the security for the Summary Information Stream +###################################################################### + +sub get_security_for_sis +{ + my $security = "0"; + return $security; +} + +################################################################# +# Writing the Summary information stream into the msi database +# This works only on Windows +################################################################# + +sub write_summary_into_msi_database +{ + my ($msifilename, $language, $languagefile, $allvariableshashref) = @_; + + # -g : required msi version + # -c : codepage + # -p : template + + installer::logger::include_header_into_logfile("Writing summary information stream"); + + my $msiinfo = "msiinfo.exe"; # Has to be in the path + + my $msiversion = get_msiversion_for_sis(); + my $codepage = 0; # PID_CODEPAGE summary property in a signed short, therefore it is impossible to set 65001 here. + my $template = get_template_for_sis($language, $allvariableshashref); + my $guid = get_packagecode_for_sis(); + my $title = "\"Installation database\""; + my $author = get_author_for_sis(); + my $subject = get_subject_for_sis($allvariableshashref); + my $comment = "\"" . $allvariableshashref->{'PRODUCTNAME'} ."\""; + my $keywords = "\"Install,MSI\""; + my $appname = "\"Windows Installer\""; + my $security = get_security_for_sis(); + my $wordcount = get_wordcount_for_sis(); + + $msifilename = installer::converter::make_path_conform($msifilename); + + my $systemcall = $msiinfo . " " . $msifilename . " -g " . $msiversion . " -c " . $codepage + . " -p " . $template . " -v " . $guid . " -t " . $title . " -a " . $author + . " -j " . $subject . " -o " . $comment . " -k " . $keywords . " -n " . $appname + . " -u " . $security . " -w " . $wordcount; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall (return $returnvalue)\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed $msiinfo successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +######################################################################### +# For more than one language in the installation set: +# Use one database and create Transformations for all other languages +######################################################################### + +sub create_transforms +{ + my ($languagesarray, $defaultlanguage, $installdir, $allvariableshashref) = @_; + + installer::logger::include_header_into_logfile("Creating Transforms"); + + my $cscript = "cscript.exe"; # Has to be in the path + my $msitran = "msitran.exe"; # Has to be in the path + my $msidb = "msidb.exe"; # Has to be in the path + my $wilangid = $ENV{WINDOWS_SDK_WILANGID}; + + my $from = cwd(); + + my $templatevalue = "1033"; + + $installdir = installer::converter::make_path_conform($installdir); + + # Syntax for creating a transformation + # msitran.exe -g [} + + my $basedbname = get_msidatabasename($allvariableshashref, $defaultlanguage); + $basedbname = $installdir . $installer::globals::separator . $basedbname; + + my $errorhandling = "f"; # Suppress "change codepage" error + + # Iterating over all files + + foreach ( @{$languagesarray} ) + { + my $onelanguage = $_; + + if ( $onelanguage eq $defaultlanguage ) { next; } + + my $referencedbname = get_msidatabasename($allvariableshashref, $onelanguage); + $referencedbname = $installdir . $installer::globals::separator . $referencedbname; + + my $windowslanguage = installer::windows::language::get_windows_language($onelanguage); + my $transformfile = $installdir . $installer::globals::separator . $windowslanguage; + + my $systemcall = $msitran . " " . " -g " . $basedbname . " " . $referencedbname . " " . $transformfile . " " . $errorhandling; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + # Problem: msitran.exe in version 4.0 always returns "1", even if no failure occurred. + # Therefore it has to be checked, if this is version 4.0. If yes, if the mst file + # exists and if it is larger than 0 bytes. If this is true, then no error occurred. + # File Version of msitran.exe: 4.0.6000.16384 has checksum: "b66190a70145a57773ec769e16777b29". + # Same for msitran.exe from wntmsci12: "aa25d3445b94ffde8ef0c1efb77a56b8" + + if ($returnvalue) + { + $infoline = "WARNING: Returnvalue of $msitran is not 0. Checking version of $msitran!\n"; + push( @installer::globals::logfileinfo, $infoline); + + open(FILE, "<$installer::globals::msitranpath") or die "ERROR: Can't open $installer::globals::msitranpath for creating file hash"; + binmode(FILE); + my $digest = Digest::MD5->new->addfile(*FILE)->hexdigest; + close(FILE); + + my @problemchecksums = ("b66190a70145a57773ec769e16777b29", "aa25d3445b94ffde8ef0c1efb77a56b8", "748206e54fc93efe6a1aaa9d491f3ad1"); + my $isproblemchecksum = 0; + + foreach my $problemchecksum ( @problemchecksums ) + { + $infoline = "Checksum of problematic MsiTran.exe: $problemchecksum\n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Checksum of used MsiTran.exe: $digest\n"; + push( @installer::globals::logfileinfo, $infoline); + if ( $digest eq $problemchecksum ) { $isproblemchecksum = 1; } + } + + if ( $isproblemchecksum ) + { + # Check existence of mst + if ( -f $transformfile ) + { + $infoline = "File $transformfile exists.\n"; + push( @installer::globals::logfileinfo, $infoline); + my $filesize = ( -s $transformfile ); + $infoline = "Size of $transformfile: $filesize\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( $filesize > 0 ) + { + $infoline = "Info: Returnvalue $returnvalue of $msitran is no problem :-) .\n"; + push( @installer::globals::logfileinfo, $infoline); + $returnvalue = 0; # reset the error + } + else + { + $infoline = "Filesize indicates that an error occurred.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + else + { + $infoline = "File $transformfile does not exist -> An error occurred.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + else + { + $infoline = "This is not a problematic version of msitran.exe. Therefore the error is not caused by problematic msitran.exe.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $msitran!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed $msitran successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # The reference database can be deleted + + my $result = unlink($referencedbname); + # $result contains the number of deleted files + + if ( $result == 0 ) + { + $infoline = "ERROR while processing language $onelanguage: Could not remove file $referencedbname!\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program($infoline, "create_transforms"); + } + + chdir($installdir); + $systemcall = $msidb . " " . " -d " . $basedbname . " -r " . $windowslanguage; + system($systemcall); + # fdo#46181 - zh-HK and zh-MO should have fallen back to zh-TW not to zh-CN + # we need to hack zh-HK and zh-MO LCIDs directly into the MSI + if($windowslanguage eq '1028') + { + rename 1028,3076; + $systemcall = $msidb . " " . " -d " . $basedbname . " -r " . 3076; + system($systemcall); + rename 3076,5124; + $systemcall = $msidb . " " . " -d " . $basedbname . " -r " . 5124; + system($systemcall); + $templatevalue = $templatevalue . "," . 3076 . "," . 5124; + rename 5124,1028; + } + chdir($from); + unlink($transformfile); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ( $windowslanguage ne '1033') + { + $templatevalue = $templatevalue . "," . $windowslanguage; + } + } + + $systemcall = "TEMP=$ENV{'TMPDIR'} $cscript \"$wilangid\" $basedbname Package $templatevalue"; + + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: $returnvalue from $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed WiLangId.vbs successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +######################################################################### +# The default language msi database does not need to contain +# the language in the database name. Therefore the file +# is renamed. Example: "openofficeorg20_01.msi" to "openofficeorg20.msi" +######################################################################### + +sub rename_msi_database_in_installset +{ + my ($defaultlanguage, $installdir, $allvariableshashref) = @_; + + installer::logger::include_header_into_logfile("Renaming msi database"); + + my $olddatabasename = get_msidatabasename($allvariableshashref, $defaultlanguage); + $olddatabasename = $installdir . $installer::globals::separator . $olddatabasename; + + my $newdatabasename = get_msidatabasename($allvariableshashref); + + $installer::globals::shortmsidatabasename = $newdatabasename; + + $newdatabasename = $installdir . $installer::globals::separator . $newdatabasename; + + installer::systemactions::rename_one_file($olddatabasename, $newdatabasename); + + $installer::globals::msidatabasename = $newdatabasename; +} + +################################################################# +# Copying MergeModules for the Windows installer into the +# installation set. The list of MergeModules is located +# in %installer::globals::copy_msm_files +################################################################# + +sub copy_merge_modules_into_installset +{ + my ($installdir) = @_; + + installer::logger::include_header_into_logfile("Copying Merge files into installation set"); + + my $cabfile; + foreach $cabfile ( keys %installer::globals::copy_msm_files ) + { + my $sourcefile = $installer::globals::copy_msm_files{$cabfile}; + my $destfile = $installdir . $installer::globals::separator . $cabfile; + + installer::systemactions::copy_one_file($sourcefile, $destfile); + } +} + +################################################################# +# Getting a list of GUID using uuidgen.exe. +# This works only on Windows +################################################################# + +sub get_guid_list +{ + my ($number, $log) = @_; + + if ( $log ) { installer::logger::include_header_into_logfile("Generating $number GUID"); } + + my $uuidgen = $ENV{'UUIDGEN'}; # Has to be in the path + + # "-c" for uppercase output + + my $systemcall = "$uuidgen -n$number |"; + open (UUIDGEN, "$systemcall" ) or die("uuidgen is missing."); + my @uuidlist = ; + close (UUIDGEN); + + my $infoline = "Systemcall: $systemcall\n"; + if ( $log ) { push( @installer::globals::logfileinfo, $infoline); } + + my $comparenumber = $#uuidlist + 1; + + if ( $comparenumber == $number ) + { + $infoline = "Success: Executed $uuidgen successfully!\n"; + if ( $log ) { push( @installer::globals::logfileinfo, $infoline); } + } + else + { + $infoline = "ERROR: Could not execute $uuidgen successfully!\n"; + if ( $log ) { push( @installer::globals::logfileinfo, $infoline); } + } + + # uppercase, no longer "-c", because this is only supported in uuidgen.exe v.1.01 + for ( my $i = 0; $i <= $#uuidlist; $i++ ) { $uuidlist[$i] = uc($uuidlist[$i]); } + + return \@uuidlist; +} + +################################################################# +# Calculating a GUID with a string using md5. +################################################################# + +sub calculate_guid +{ + my ( $string ) = @_; + + my $guid = ""; + + my $md5 = Digest::MD5->new; + $md5->add($string); + my $digest = $md5->hexdigest; + $digest = uc($digest); + + my ($first, $second, $third, $fourth, $fifth) = unpack ('A8 A4 A4 A4 A12', $digest); + $guid = "$first-$second-$third-$fourth-$fifth"; + + return $guid; +} + +################################################################# +# Calculating a ID with a string using md5 (very fast). +################################################################# + +sub calculate_id +{ + my ( $string, $length ) = @_; + + my $id = ""; + + my $md5 = Digest::MD5->new; + $md5->add($string); + my $digest = lc($md5->hexdigest); + $id = substr($digest, 0, $length); + + return $id; +} + +################################################################# +# Filling real component GUID into the component table. +# This works only on Windows +################################################################# + +sub set_uuid_into_component_table +{ + my ($idtdirbase, $allvariables) = @_; + + my $componenttablename = $idtdirbase . $installer::globals::separator . "Componen.idt"; + + my $componenttable = installer::files::read_file($componenttablename); + + # For update and patch reasons (small update) the GUID of an existing component must not change! + # The collection of component GUIDs is saved in the directory $installer::globals::idttemplatepath in the file "components.txt" + + my $infoline = ""; + my $counter = 0; + + for ( my $i = 3; $i <= $#{$componenttable}; $i++ ) # ignoring the first three lines + { + my $oneline = ${$componenttable}[$i]; + my $componentname = ""; + if ( $oneline =~ /^\s*(\S+?)\t/ ) { $componentname = $1; } + + my $uuid = ""; + + if ( exists($installer::globals::calculated_component_guids{$componentname})) + { + $uuid = $installer::globals::calculated_component_guids{$componentname}; + } + else + { + # Calculating new GUID with the help of the component name. + + if ( ! exists($allvariables->{'PRODUCTVERSION'}) ) { installer::exiter::exit_program("ERROR: Could not find variable \"PRODUCTVERSION\" (required value for GUID creation)!", "set_uuid_into_component_table"); } + my $sourcestring = $componentname . "_" . $allvariables->{'PRODUCTVERSION'}; + $uuid = calculate_guid($sourcestring); + $counter++; + + # checking, if there is a conflict with an already created guid + if ( exists($installer::globals::allcalculated_guids{$uuid}) ) { installer::exiter::exit_program("ERROR: \"$uuid\" was already created before!", "set_uuid_into_component_table"); } + $installer::globals::allcalculated_guids{$uuid} = 1; + $installer::globals::calculated_component_guids{$componentname} = $uuid; + } + + ${$componenttable}[$i] =~ s/COMPONENTGUID/$uuid/; + } + + installer::files::save_file($componenttablename, $componenttable); +} + +######################################################################### +# Adding final 64 properties into msi database, if required. +# RegLocator : +16 in type column to search in 64 bit registry. +# All conditions: "VersionNT" -> "VersionNT64" (several tables). +# DrLocator: "SystemFolder" -> "System64Folder" +# Already done: "+256" in Attributes column of table "Component". +# Still following: Setting "x64" instead of "Intel" in Summary +# Information Stream of msi database in "get_template_for_sis". +######################################################################### + +sub prepare_64bit_database +{ + my ($basedir, $allvariables) = @_; + + my $infoline = ""; + + if (( $allvariables->{'64BITPRODUCT'} ) && ( $allvariables->{'64BITPRODUCT'} == 1 )) + { + # 1. Beginning with table "RegLocat.idt". Adding "16" to the type. + + my $reglocatfile = ""; + my $reglocatfilename = $basedir . $installer::globals::separator . "RegLocat.idt"; + + if ( -f $reglocatfilename ) + { + my $saving_required = 0; + $reglocatfile = installer::files::read_file($reglocatfilename); + + for ( my $i = 3; $i <= $#{$reglocatfile}; $i++ ) # ignoring the first three lines + { + my $oneline = ${$reglocatfile}[$i]; + + if ( $oneline =~ /^\s*\#/ ) { next; } # this is a comment line + if ( $oneline =~ /^\s*$/ ) { next; } + + if ( $oneline =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(\d+)\s*$/ ) + { + # Syntax: Signature_ Root Key Name Type + my $sig = $1; + my $root = $2; + my $key = $3; + my $name = $4; + my $type = $5; + + $type = $type + 16; + + my $newline = $sig . "\t" . $root . "\t" . $key . "\t" . $name . "\t" . $type . "\n"; + ${$reglocatfile}[$i] = $newline; + + $saving_required = 1; + } + } + + if ( $saving_required ) + { + # Saving the files + installer::files::save_file($reglocatfilename ,$reglocatfile); + $infoline = "Making idt file 64 bit conform: $reglocatfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + + # 2. Replacing all occurrences of "VersionNT" by "VersionNT64" + + my @versionnt_files = ("Componen.idt", "InstallE.idt", "InstallU.idt", "LaunchCo.idt"); + + foreach my $onefile ( @versionnt_files ) + { + my $fullfilename = $basedir . $installer::globals::separator . $onefile; + + if ( -f $fullfilename ) + { + my $saving_required = 0; + $filecontent = installer::files::read_file($fullfilename); + + for ( my $i = 3; $i <= $#{$filecontent}; $i++ ) # ignoring the first three lines + { + my $oneline = ${$filecontent}[$i]; + + if ( $oneline =~ /\bVersionNT\b/ ) + { + ${$filecontent}[$i] =~ s/\bVersionNT\b/VersionNT64/g; + $saving_required = 1; + } + } + + if ( $saving_required ) + { + # Saving the files + installer::files::save_file($fullfilename ,$filecontent); + $infoline = "Making idt file 64 bit conform: $fullfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } + + # 3. Replacing all occurrences of "SystemFolder" by "System64Folder" in "DrLocato.idt" + + my $drlocatofilename = $basedir . $installer::globals::separator . "DrLocato.idt"; + if ( -f $drlocatofilename ) + { + my $saving_required = 0; + my $drlocatofile = installer::files::read_file($drlocatofilename); + + for ( my $i = 3; $i <= $#{$drlocatofile}; $i++ ) # ignoring the first three lines + { + my $oneline = ${$drlocatofile}[$i]; + + if ( $oneline =~ /\bSystemFolder\b/ ) + { + ${$drlocatofile}[$i] =~ s/\bSystemFolder\b/System64Folder/g; + $saving_required = 1; + } + } + + if ( $saving_required ) + { + # Saving the files + installer::files::save_file($drlocatofilename ,$drlocatofile); + $infoline = "Making idt file 64 bit conform: $drlocatofilename\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } + } + +} + +################################################################# +# Include all cab files into the msi database. +# This works only on Windows +################################################################# + +sub include_cabs_into_msi +{ + my ($installdir) = @_; + + installer::logger::include_header_into_logfile("Including cabs into msi database"); + + my $from = cwd(); + my $to = $installdir; + + chdir($to); + + my $infoline = "Changing into directory: $to"; + push( @installer::globals::logfileinfo, $infoline); + + my $msidb = "msidb.exe"; # Has to be in the path + my $extraslash = ""; # Has to be set for non-ActiveState perl + + my $msifilename = $installer::globals::msidatabasename; + + $msifilename = installer::converter::make_path_conform($msifilename); + + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $msifilename =~ s/\//\\\\/g; + $extraslash = "\\"; + + my $allcabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir); + + for ( my $i = 0; $i <= $#{$allcabfiles}; $i++ ) + { + my $systemcall = $msidb . " -d " . $msifilename . " -a " . ${$allcabfiles}[$i]; + + my $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + # deleting the cab file + + unlink(${$allcabfiles}[$i]); + + $infoline = "Deleted cab file: ${$allcabfiles}[$i]\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + $infoline = "Changing back into directory: $from"; + push( @installer::globals::logfileinfo, $infoline); + + chdir($from); +} + +################################################################# +# Executing the created batch file to pack all files. +# This works only on Windows +################################################################# + +sub execute_packaging +{ + my ($localpackjobref, $loggingdir, $allvariables) = @_; + + installer::logger::include_header_into_logfile("Packaging process"); + + installer::logger::include_timestamp_into_logfile("Performance Info: Execute packaging start"); + + my $infoline = ""; + my $from = cwd(); + my $to = $loggingdir; + + chdir($to); + $infoline = "chdir: $to \n"; + push( @installer::globals::logfileinfo, $infoline); + + # the ddf file contains relative paths, it is necessary to change into the temp directory + $to = $installer::globals::temppath; + chdir($to); + $infoline = "chdir: $to \n"; + push( @installer::globals::logfileinfo, $infoline); + + my $maxmakecabcalls = 3; + my $allmakecabcalls = $#{$localpackjobref} + 1; + + for ( my $i = 0; $i <= $#{$localpackjobref}; $i++ ) + { + my $systemcall = ${$localpackjobref}[$i]; + + my $callscounter = $i + 1; + + installer::logger::print_message( "... makecab.exe ($callscounter/$allmakecabcalls) ... \n" ); + + for ( my $n = 1; $n <= $maxmakecabcalls; $n++ ) + { + my @ddfoutput = (); + + $infoline = "Systemcall: $systemcall"; + push( @installer::globals::logfileinfo, $infoline); + + open (DDF, "$systemcall"); + while () {push(@ddfoutput, $_); } + close (DDF); + + my $returnvalue = $?; # $? contains the return value of the systemcall + + if ($returnvalue) + { + if ( $n < $maxmakecabcalls ) + { + installer::logger::print_message( "makecab_error (Try $n): Trying again \n" ); + $infoline = "makecab_error (Try $n): $systemcall !"; + } + else + { + installer::logger::print_message( "ERROR (Try $n): Abort packing \n" ); + $infoline = "ERROR (Try $n): $systemcall !"; + } + + push( @installer::globals::logfileinfo, $infoline); + + for ( my $m = 0; $m <= $#ddfoutput; $m++ ) + { + if ( $ddfoutput[$m] =~ /(ERROR\:.*?)\s*$/ ) + { + $infoline = $1 . "\n"; + if ( $n < $maxmakecabcalls ) { $infoline =~ s/ERROR\:/makecab_error\:/i; } + installer::logger::print_message( $infoline ); + push( @installer::globals::logfileinfo, $infoline); + } + } + + if ( $n == $maxmakecabcalls ) { installer::exiter::exit_program("ERROR: \"$systemcall\"!", "execute_packaging"); } + } + else + { + $infoline = "Success (Try $n): $systemcall"; + push( @installer::globals::logfileinfo, $infoline); + last; + } + } + } + + installer::logger::include_timestamp_into_logfile("Performance Info: Execute packaging end"); + + chdir($from); + $infoline = "chdir: $from \n"; + push( @installer::globals::logfileinfo, $infoline); +} + +############################################################### +# Setting the global variables ProductCode and the UpgradeCode +############################################################### + +sub set_global_code_variables +{ + my ( $languagesref, $languagestringref, $allvariableshashref, $alloldproperties ) = @_; + + # In the msi template directory a files "codes.txt" has to exist, in which the ProductCode + # and the UpgradeCode for the product are defined. + # The name "codes.txt" can be overwritten in Product definition with CODEFILENAME . + # Default $installer::globals::codefilename is defined in parameter.pm. + + if ( $allvariableshashref->{'CODEFILENAME'} ) + { + $installer::globals::codefilename = $installer::globals::idttemplatepath . $installer::globals::separator . $allvariableshashref->{'CODEFILENAME'}; + installer::files::check_file($installer::globals::codefilename); + } + + my $infoline = "Using Codes file: $installer::globals::codefilename \n"; + push( @installer::globals::logfileinfo, $infoline); + + my $codefile = installer::files::read_file($installer::globals::codefilename); + + my $onelanguage = ""; + + if ( $#{$languagesref} > 0 ) # more than one language + { + if (( $installer::globals::added_english ) && ( $#{$languagesref} == 1 )) # only multilingual because of added English + { + $onelanguage = ${$languagesref}[1]; # setting the first language, that is not english + } + else + { + if (( ${$languagesref}[1] =~ /jp/ ) || + ( ${$languagesref}[1] =~ /ko/ ) || + ( ${$languagesref}[1] =~ /zh/ )) + { + $onelanguage = "multiasia"; + } + else + { + $onelanguage = "multiwestern"; + } + } + } + else # only one language + { + $onelanguage = ${$languagesref}[0]; + } + + # ProductCode must not change, if Windows patches shall be applied + if ( $installer::globals::updatedatabase ) + { + $installer::globals::productcode = $alloldproperties->{'ProductCode'}; + } + elsif ( $installer::globals::prepare_winpatch ) + { + # ProductCode has to be specified in each language + my $searchstring = "PRODUCTCODE"; + my $codeblock = installer::windows::idtglobal::get_language_block_from_language_file($searchstring, $codefile); + $installer::globals::productcode = installer::windows::idtglobal::get_code_from_code_block($codeblock, $onelanguage); + } else { + my $guidref = get_guid_list(1, 1); # only one GUID shall be generated + ${$guidref}[0] =~ s/\s*$//; # removing ending spaces + $installer::globals::productcode = "\{" . ${$guidref}[0] . "\}"; + } + + # UpgradeCode can take english as default, if not defined in specified language + + $searchstring = "UPGRADECODE"; # searching in the codes.txt file + $codeblock = installer::windows::idtglobal::get_language_block_from_language_file($searchstring, $codefile); + $installer::globals::upgradecode = installer::windows::idtglobal::get_language_string_from_language_block($codeblock, $onelanguage, ""); + + if ( $installer::globals::upgradecode eq "" ) { installer::exiter::exit_program("ERROR: UpgradeCode not defined in $installer::globals::codefilename !", "set_global_code_variables"); } + + $infoline = "Setting ProductCode to: $installer::globals::productcode \n"; + push( @installer::globals::logfileinfo, $infoline); + $infoline = "Setting UpgradeCode to: $installer::globals::upgradecode \n"; + push( @installer::globals::logfileinfo, $infoline); + + # Adding both variables into the variables array + + $allvariableshashref->{'PRODUCTCODE'} = $installer::globals::productcode; + $allvariableshashref->{'UPGRADECODE'} = $installer::globals::upgradecode; + + $infoline = "Defined variable PRODUCTCODE: $installer::globals::productcode \n"; + push( @installer::globals::logfileinfo, $infoline); + + $infoline = "Defined variable UPGRADECODE: $installer::globals::upgradecode \n"; + push( @installer::globals::logfileinfo, $infoline); + +} + +############################################################### +# Setting the product version used in property table and +# upgrade table. Saving in global variable $msiproductversion +############################################################### + +sub set_msiproductversion +{ + my ( $allvariables ) = @_; + + my $productversion = $allvariables->{'PACKAGEVERSION'}; + + if ( $productversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ ) + { + $productversion = $1 . "\." . $2 . "\." . $3 . "\." . $installer::globals::buildid; + } + + $installer::globals::msiproductversion = $productversion; + + # Setting $installer::globals::msimajorproductversion, to differ between old version in upgrade table + + if ( $installer::globals::msiproductversion =~ /^\s*(\d+)\./ ) + { + my $major = $1; + $installer::globals::msimajorproductversion = $major . "\.0\.0"; + } +} + +################################################################################# +# Including the msi product version into the bootstrap.ini, Windows only +################################################################################# + +sub put_msiproductversion_into_bootstrapfile +{ + my ($filesref) = @_; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $onefile = ${$filesref}[$i]; + + if ( $onefile->{'gid'} eq "gid_Brand_Profile_Version_Ini" ) + { + my $file = installer::files::read_file($onefile->{'sourcepath'}); + + for ( my $j = 0; $j <= $#{$file}; $j++ ) + { + ${$file}[$j] =~ s/\/$installer::globals::msiproductversion/; + } + + installer::files::save_file($onefile->{'sourcepath'}, $file); + + last; + } + } +} + +#################################################################################### +# Updating the file Property.idt dynamically +# Content: +# Property Value +#################################################################################### + +sub update_reglocat_table +{ + my ($basedir, $allvariables) = @_; + + my $reglocatfilename = $basedir . $installer::globals::separator . "RegLocat.idt"; + + # Only do something, if this file exists + + if ( -f $reglocatfilename ) + { + my $reglocatfile = installer::files::read_file($reglocatfilename); + + my $layername = ""; + if ( $allvariables->{'REGISTRYLAYERNAME'} ) + { + $layername = $allvariables->{'REGISTRYLAYERNAME'}; + } + else + { + for ( my $i = 0; $i <= $#{$reglocatfile}; $i++ ) + { + if ( ${$reglocatfile}[$i] =~ /\bLAYERNAMETEMPLATE\b/ ) + { + installer::exiter::exit_program("ERROR: Variable \"REGISTRYLAYERNAME\" has to be defined", "update_reglocat_table"); + } + } + } + + if ( $layername ne "" ) + { + # Updating the layername in + + for ( my $i = 0; $i <= $#{$reglocatfile}; $i++ ) + { + ${$reglocatfile}[$i] =~ s/\bLAYERNAMETEMPLATE\b/$layername/; + } + + # Saving the file + installer::files::save_file($reglocatfilename ,$reglocatfile); + my $infoline = "Updated idt file: $reglocatfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } +} + + + +#################################################################################### +# Updating the file RemoveRe.idt dynamically (RemoveRegistry.idt) +# The name of the component has to be replaced. +#################################################################################### + +sub update_removere_table +{ + my ($basedir) = @_; + + my $removeregistryfilename = $basedir . $installer::globals::separator . "RemoveRe.idt"; + + # Only do something, if this file exists + + if ( -f $removeregistryfilename ) + { + my $removeregistryfile = installer::files::read_file($removeregistryfilename); + + for ( my $i = 0; $i <= $#{$removeregistryfile}; $i++ ) + { + for ( my $i = 0; $i <= $#{$removeregistryfile}; $i++ ) + { + ${$removeregistryfile}[$i] =~ s/\bREGISTRYROOTCOMPONENT\b/$installer::globals::registryrootcomponent/; + } + } + + # Saving the file + installer::files::save_file($removeregistryfilename ,$removeregistryfile); + my $infoline = "Updated idt file: $removeregistryfilename \n"; + push(@installer::globals::logfileinfo, $infoline); + } +} + +########################################################################## +# Reading saved mappings in Files.idt and Director.idt. +# This is required, if installation sets shall be created, +# that can be used for creation of msp files. +########################################################################## + +sub read_saved_mappings +{ + installer::logger::include_header_into_logfile("Reading saved mappings from older installation sets:"); + + installer::logger::include_timestamp_into_logfile("Performance Info: Reading saved mappings start"); + + if ( $installer::globals::previous_idt_dir ) + { + my @errorlines = (); + my $errorstring = ""; + my $error_occurred = 0; + my $file_error_occurred = 0; + my $dir_error_occurred = 0; + + my $idtdir = $installer::globals::previous_idt_dir; + $idtdir =~ s/\Q$installer::globals::separator\E\s*$//; + + # Reading File.idt + + my $idtfile = $idtdir . $installer::globals::separator . "File.idt"; + push( @installer::globals::globallogfileinfo, "\nAnalyzing file: $idtfile\n" ); + if ( ! -f $idtfile ) { push( @installer::globals::globallogfileinfo, "Warning: File $idtfile does not exist!\n" ); } + + my $n = 0; + open (F, "<$idtfile") || installer::exiter::exit_program("ERROR: Cannot open file $idtfile for reading", "read_saved_mappings"); + ; ; ; + while () + { + m/^([^\t]+)\t([^\t]+)\t((.*)\|)?([^\t]*)/; + print "OUT1: \$1: $1, \$2: $2, \$3: $3, \$4: $4, \$5: $5\n"; + next if ("$1" eq "$5") && (!defined($3)); + my $lc1 = lc($1); + + if ( exists($installer::globals::savedmapping{"$2/$5"})) + { + if ( ! $file_error_occurred ) + { + $errorstring = "\nErrors in $idtfile: \n"; + push(@errorlines, $errorstring); + } + $errorstring = "Duplicate savedmapping{" . "$2/$5}\n"; + push(@errorlines, $errorstring); + $error_occurred = 1; + $file_error_occurred = 1; + } + + if ( exists($installer::globals::savedrevmapping{$lc1})) + { + if ( ! $file_error_occurred ) + { + $errorstring = "\nErrors in $idtfile: \n"; + push(@errorlines, $errorstring); + } + $errorstring = "Duplicate savedrevmapping{" . "$lc1}\n"; + push(@errorlines, $errorstring); + $error_occurred = 1; + $file_error_occurred = 1; + } + + my $shortname = $4 || ''; + + # Don't reuse illegal 8.3 mappings that we used to generate in 2.0.4 + if (index($shortname, '.') > 8 || + (index($shortname, '.') == -1 && length($shortname) > 8)) + { + $shortname = ''; + } + + if (( $shortname ne '' ) && ( index($shortname, '~') > 0 ) && ( exists($installer::globals::savedrev83mapping{$shortname}) )) + { + if ( ! $file_error_occurred ) + { + $errorstring = "\nErrors in $idtfile: \n"; + push(@errorlines, $errorstring); + } + $errorstring = "Duplicate savedrev83mapping{" . "$shortname}\n"; + push(@errorlines, $errorstring); + $error_occurred = 1; + $file_error_occurred = 1; + } + + $installer::globals::savedmapping{"$2/$5"} = "$1;$shortname"; + $installer::globals::savedrevmapping{lc($1)} = "$2/$5"; + $installer::globals::savedrev83mapping{$shortname} = "$2/$5" if $shortname ne ''; + $n++; + } + + close (F); + + push( @installer::globals::globallogfileinfo, "Read $n old file table key or 8.3 name mappings from $idtfile\n" ); + + # Reading Director.idt + + $idtfile = $idtdir . $installer::globals::separator . "Director.idt"; + push( @installer::globals::globallogfileinfo, "\nAnalyzing file $idtfile\n" ); + if ( ! -f $idtfile ) { push( @installer::globals::globallogfileinfo, "Warning: File $idtfile does not exist!\n" ); } + + $n = 0; + open (F, "<$idtfile") || installer::exiter::exit_program("ERROR: Cannot open file $idtfile for reading", "read_saved_mappings"); + ; ; ; + while () + { + m/^([^\t]+)\t([^\t]+)\t(([^~]+~\d.*)\|)?([^\t]*)/; + next if (!defined($3)); + my $lc1 = lc($1); + + print "OUT2: \$1: $1, \$2: $2, \$3: $3\n"; + + if ( exists($installer::globals::saved83dirmapping{$1}) ) + { + if ( ! $dir_error_occurred ) + { + $errorstring = "\nErrors in $idtfile: \n"; + push(@errorlines, $errorstring); + } + $errorstring = "Duplicate saved83dirmapping{" . "$1}\n"; + push(@errorlines, $errorstring); + $error_occurred = 1; + $dir_error_occurred = 1; + } + + $installer::globals::saved83dirmapping{$1} = $4; + $n++; + } + close (F); + + push( @installer::globals::globallogfileinfo, "Read $n old directory 8.3 name mappings from $idtfile\n" ); + + # Analyzing errors + + if ( $error_occurred ) + { + for ( my $i = 0; $i <= $#errorlines; $i++ ) + { + print "$errorlines[$i]"; + push( @installer::globals::globallogfileinfo, "$errorlines[$i]"); + } + installer::exiter::exit_program("ERROR: Duplicate entries in saved mappings!", "read_saved_mappings"); + } + } else { + installer::exiter::exit_program("ERROR: Windows patch shall be prepared, but environment variable PREVIOUS_IDT_DIR is not set!", "read_saved_mappings"); + } + + installer::logger::include_timestamp_into_logfile("Performance Info: Reading saved mappings end"); +} + +1; + +# vim:set shiftwidth=4 softtabstop=4 expandtab: diff --git a/solenv/bin/modules/installer/windows/msishortcutproperty.pm b/solenv/bin/modules/installer/windows/msishortcutproperty.pm new file mode 100644 index 000000000..5436f2565 --- /dev/null +++ b/solenv/bin/modules/installer/windows/msishortcutproperty.pm @@ -0,0 +1,143 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::msishortcutproperty; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +############################################################## +# Returning identifier for msishortcutproperty table. +############################################################## + +sub get_msishortcutproperty_identifier +{ + my ($msishortcutproperty) = @_; + + my $identifier = $msishortcutproperty->{'gid'}; + + return $identifier; +} + +############################################################## +# Returning shortcut for msishortcutproperty table. +############################################################## + +sub get_msishorcutproperty_shortcut +{ + my ($msishortcutproperty, $filesref) = @_; + + my $onefile; + my $shortcut = ""; + my $found = 0; + my $msishortcutproperty_shortcutid = $msishortcutproperty->{'ShortcutID'}; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $msishortcutproperty_shortcutid ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find ShortcutID $msishortcutproperty_shortcutid in file collection for shortcut", "get_msishorcutproperty_shortcut"); + } + + $shortcut = $onefile->{'gid'}; + + return $shortcut; +} + +############################################################## +# Returning the propertykey for msishortcutproperty table. +############################################################## + +sub get_msishortcutproperty_propertykey +{ + my ($msishortcutproperty) = @_; + + my $propertykey = ""; + if ( $msishortcutproperty->{'Key'} ) { $propertykey = $msishortcutproperty->{'Key'}; } + + return $propertykey; +} + +################################################################ +# Returning the propvariantvalue for msishortcutproperty table. +################################################################ + +sub get_msishortcutproperty_propvariantvalue +{ + my ($msishortcutproperty) = @_; + + my $propvariantvalue = ""; + if ( $msishortcutproperty->{'Value'} ) { $propvariantvalue = $msishortcutproperty->{'Value'}; } + + return $propvariantvalue; +} + +################################################################### +# Creating the file MsiShortcutProperty.idt dynamically +# Content: +# MsiShortcutProperty Shortcut_ PropertyKey PropVariantValue +################################################################### + +sub create_msishortcutproperty_table +{ + my ($folderitempropertiesref, $folderitemsref, $basedir) = @_; + + my @msishortcutpropertytable = (); + + installer::windows::idtglobal::write_idt_header(\@msishortcutpropertytable, "msishortcutproperty"); + + # The entries defined in scp as FolderItemProperties + + for ( my $j = 0; $j <= $#{$folderitempropertiesref}; $j++ ) + { + my $onelink = ${$folderitempropertiesref}[$j]; + my %msishortcutproperty = (); + + $msishortcutproperty{'MsiShortcutProperty'} = get_msishortcutproperty_identifier($onelink); + $msishortcutproperty{'Shortcut_'} = get_msishorcutproperty_shortcut($onelink, $folderitemsref); + $msishortcutproperty{'PropertyKey'} = get_msishortcutproperty_propertykey($onelink); + $msishortcutproperty{'PropVariantValue'} = get_msishortcutproperty_propvariantvalue($onelink); + + my $oneline = $msishortcutproperty{'MsiShortcutProperty'} . "\t" . $msishortcutproperty{'Shortcut_'} . "\t" + . $msishortcutproperty{'PropertyKey'} . "\t" . $msishortcutproperty{'PropVariantValue'} . "\n"; + + push(@msishortcutpropertytable, $oneline); + } + + # Saving the file + + my $msishortcutpropertytablename = $basedir . $installer::globals::separator . "MsiShorP.idt"; + installer::files::save_file($msishortcutpropertytablename ,\@msishortcutpropertytable); + my $infoline = "Created idt file: $msishortcutpropertytablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + + +1; diff --git a/solenv/bin/modules/installer/windows/msp.pm b/solenv/bin/modules/installer/windows/msp.pm new file mode 100644 index 000000000..1bbeea8d2 --- /dev/null +++ b/solenv/bin/modules/installer/windows/msp.pm @@ -0,0 +1,1264 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::msp; + +use File::Copy; +use installer::control; +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::systemactions; +use installer::windows::admin; +use installer::windows::idtglobal; +use installer::windows::update; + +################################################################################# +# Making all required administrative installations +################################################################################# + +sub install_installation_sets +{ + my ($installationdir) = @_; + + # Finding the msi database in the new installation set, that is located in $installationdir + + my $msifiles = installer::systemactions::find_file_with_file_extension("msi", $installationdir); + + if ( $#{$msifiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find msi database in directory $installationdir", "create_msp_patch"); } + if ( $#{$msifiles} > 0 ) { installer::exiter::exit_program("ERROR: Did find more than one msi database in directory $installationdir", "create_msp_patch"); } + + my $newinstallsetdatabasepath = $installationdir . $installer::globals::separator . ${$msifiles}[0]; + my $oldinstallsetdatabasepath = $installer::globals::updatedatabasepath; + + # Creating temp directory again + installer::systemactions::create_directory_structure($installer::globals::temppath); + + # Creating old installation directory + my $dirname = "admin"; + my $installpath = $installer::globals::temppath . $installer::globals::separator . $dirname; + if ( ! -d $installpath) { installer::systemactions::create_directory($installpath); } + + my $oldinstallpath = $installpath . $installer::globals::separator . "old"; + my $newinstallpath = $installpath . $installer::globals::separator . "new"; + + if ( ! -d $oldinstallpath) { installer::systemactions::create_directory($oldinstallpath); } + if ( ! -d $newinstallpath) { installer::systemactions::create_directory($newinstallpath); } + + my $olddatabase = installer::windows::admin::make_admin_install($oldinstallsetdatabasepath, $oldinstallpath); + my $newdatabase = installer::windows::admin::make_admin_install($newinstallsetdatabasepath, $newinstallpath); + + if ( $^O =~ /cygwin/i ) { + $olddatabase = qx{cygpath -w "$olddatabase"}; + $olddatabase =~ s/\s*$//g; + $newdatabase = qx{cygpath -w "$newdatabase"}; + $newdatabase =~ s/\s*$//g; + } + + return ($olddatabase, $newdatabase); +} + +################################################################################# +# Extracting all tables from a pcp file +################################################################################# + +sub extract_all_tables_from_pcpfile +{ + my ($fullpcpfilepath, $workdir) = @_; + + my $msidb = "msidb.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + my $extraslash = ""; # Has to be set for non-ActiveState perl + + my $localfullpcpfile = $fullpcpfilepath; + my $localworkdir = $workdir; + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $localfullpcpfile =~ s/\//\\\\/g; + $localworkdir =~ s/\//\\\\/g; + $extraslash = "\\"; + } + if ( $^O =~ /linux/i ) { + $extraslash = "\\"; + } + + # Export of all tables by using "*" + + $systemcall = $msidb . " -d " . $localfullpcpfile . " -f " . $localworkdir . " -e " . $extraslash . "*"; + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not exclude tables from pcp file: $fullpcpfilepath !", "extract_all_tables_from_msidatabase"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +################################################################################# +# Include tables into a pcp file +################################################################################# + +sub include_tables_into_pcpfile +{ + my ($fullpcpfilepath, $workdir, $tables) = @_; + + my $msidb = "msidb.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + + # Make all table 8+3 conform + my $alltables = installer::converter::convert_stringlist_into_array(\$tables, " "); + + for ( my $i = 0; $i <= $#{$alltables}; $i++ ) + { + my $tablename = ${$alltables}[$i]; + $tablename =~ s/\s*$//; + my $namelength = length($tablename); + if ( $namelength > 8 ) + { + my $newtablename = substr($tablename, 0, 8); # name, offset, length + my $oldfile = $workdir . $installer::globals::separator . $tablename . ".idt"; + my $newfile = $workdir . $installer::globals::separator . $newtablename . ".idt"; + if ( -f $newfile ) { unlink $newfile; } + installer::systemactions::copy_one_file($oldfile, $newfile); + } + } + + # Import of tables + + my $localworkdir = $workdir; + my $localfullpcpfilepath = $fullpcpfilepath; + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $localfullpcpfilepath =~ s/\//\\\\/g; + $localworkdir =~ s/\//\\\\/g; + } + + my @tables = split(' ', $tables); # I found that msidb from Windows SDK 7.1 did not accept more than one table. + foreach my $table (@tables) + { + $systemcall = $msidb . " -d " . $localfullpcpfilepath . " -f " . $localworkdir . " -i " . $table; + + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not include tables into pcp file: $fullpcpfilepath !", "include_tables_into_pcpfile"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } +} + +################################################################################# +# Calling msimsp.exe +################################################################################# + +sub execute_msimsp +{ + my ($fullpcpfilename, $mspfilename, $localmspdir) = @_; + + my $msimsp = "msimsp.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + my $logfilename = $localmspdir . $installer::globals::separator . "msimsp.log"; + + # Using a specific temp for each msimsp.exe process + # Creating temp directory again (should already have happened) + installer::systemactions::create_directory_structure($installer::globals::temppath); + + # Creating old installation directory + my $dirname = "msimsptemp"; + my $msimsptemppath = $installer::globals::temppath . $installer::globals::separator . $dirname; + if ( ! -d $msimsptemppath) { installer::systemactions::create_directory($msimsptemppath); } + + # r:\msvc9p\PlatformSDK\v6.1\bin\msimsp.exe -s c:\patch\hotfix_qfe1.pcp -p c:\patch\patch_ooo3_m2_m3.msp -l c:\patch\patch_ooo3_m2_m3.log + + if ( -f $logfilename ) { unlink $logfilename; } + + my $localfullpcpfilename = $fullpcpfilename; + my $localmspfilename = $mspfilename; + my $locallogfilename = $logfilename; + my $localmsimsptemppath = $msimsptemppath; + + if ( $^O =~ /cygwin/i ) { + # msimsp.exe really wants backslashes. (And double escaping because system() expands the string.) + $localfullpcpfilename =~ s/\//\\\\/g; + $locallogfilename =~ s/\//\\\\/g; + + $localmspfilename =~ s/\\/\\\\/g; # path already contains backslash + + $localmsimsptemppath = qx{cygpath -w "$localmsimsptemppath"}; + $localmsimsptemppath =~ s/\\/\\\\/g; + $localmsimsptemppath =~ s/\s*$//g; + } + + $systemcall = $msimsp . " -s " . $localfullpcpfilename . " -p " . $localmspfilename . " -l " . $locallogfilename . " -f " . $localmsimsptemppath; + installer::logger::print_message( "... $systemcall ...\n" ); + + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not execute $systemcall !", "execute_msimsp"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + return $logfilename; +} + +#################################################################### +# Checking existence and saving all tables, that need to be edited +#################################################################### + +sub check_and_save_tables +{ + my ($tablelist, $workdir) = @_; + + my $tables = installer::converter::convert_stringlist_into_array(\$tablelist, " "); + + for ( my $i = 0; $i <= $#{$tables}; $i++ ) + { + my $filename = ${$tables}[$i]; + $filename =~ s/\s*$//; + my $fullfilename = $workdir . $installer::globals::separator . $filename . ".idt"; + + if ( ! -f $fullfilename ) { installer::exiter::exit_program("ERROR: Required idt file could not be found: \"$fullfilename\"!", "check_and_save_tables"); } + + my $savfilename = $fullfilename . ".sav"; + installer::systemactions::copy_one_file($fullfilename, $savfilename); + } +} + +#################################################################### +# Setting the name of the msp database +#################################################################### + +sub set_mspfilename +{ + my ($allvariables, $mspdir, $languagesarrayref) = @_; + + my $databasename = $allvariables->{'PRODUCTNAME'} . "-" . $allvariables->{'PRODUCTVERSION'} . "-" . $allvariables->{'WINDOWSPATCHLEVEL'} . ".msp"; + + my $fullmspname = $mspdir . $installer::globals::separator . $databasename; + + if ( $^O =~ /cygwin/i ) { $fullmspname =~ s/\//\\/g; } + + return $fullmspname; +} + +#################################################################### +# Editing table Properties +#################################################################### + +sub change_properties_table +{ + my ($localmspdir, $mspfilename) = @_; + + my $infoline = "Changing content of table \"Properties\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = $localmspdir . $installer::globals::separator . "Properties.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_properties_table"); } + + my $filecontent = installer::files::read_file($filename); + + + my $guidref = installer::windows::msiglobal::get_guid_list(1, 1); + ${$guidref}[0] =~ s/\s*$//; # removing ending spaces + my $patchcode = "\{" . ${$guidref}[0] . "\}"; + + # Setting "PatchOutputPath" + my $found_patchoutputpath = 0; + my $found_patchguid = 0; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( ${$filecontent}[$i] =~ /^\s*PatchOutputPath\t(.*?)\s*$/ ) + { + my $oldvalue = $1; + ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$mspfilename/; + $found_patchoutputpath = 1; + } + + if ( ${$filecontent}[$i] =~ /^\s*PatchGUID\t(.*?)\s*$/ ) + { + my $oldvalue = $1; + ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$patchcode/; + $found_patchguid = 1; + } + } + + if ( ! $found_patchoutputpath ) + { + my $newline = "PatchOutputPath\t$mspfilename\n"; + push(@{$filecontent}, $newline); + } + + if ( ! $found_patchguid ) + { + my $newline = "PatchGUID\t$patchcode\n"; + push(@{$filecontent}, $newline); + } + + # saving file + installer::files::save_file($filename, $filecontent); +} + +#################################################################### +# Editing table TargetImages +#################################################################### + +sub change_targetimages_table +{ + my ($localmspdir, $olddatabase) = @_; + + my $infoline = "Changing content of table \"TargetImages\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = $localmspdir . $installer::globals::separator . "TargetImages.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_targetimages_table"); } + + my $filecontent = installer::files::read_file($filename); + my @newcontent = (); + + # Copying the header + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } } + + #Adding all targets + my $newline = "T1\t$olddatabase\t\tU1\t1\t0x00000922\t1\n"; + push(@newcontent, $newline); + + # saving file + installer::files::save_file($filename, \@newcontent); +} + +#################################################################### +# Editing table UpgradedImages +#################################################################### + +sub change_upgradedimages_table +{ + my ($localmspdir, $newdatabase) = @_; + + my $infoline = "Changing content of table \"UpgradedImages\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = $localmspdir . $installer::globals::separator . "UpgradedImages.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_upgradedimages_table"); } + + my $filecontent = installer::files::read_file($filename); + my @newcontent = (); + + # Copying the header + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } } + + # Syntax: Upgraded MsiPath PatchMsiPath SymbolPaths Family + + # default values + my $upgraded = "U1"; + my $msipath = $newdatabase; + my $patchmsipath = ""; + my $symbolpaths = ""; + my $family = "22334455"; + + if ( $#{$filecontent} >= 3 ) + { + my $line = ${$filecontent}[3]; + if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + $upgraded = $1; + $patchmsipath = $3; + $symbolpaths = $4; + $family = $5; + } + } + + #Adding sequence line, saving PatchFamily + my $newline = "$upgraded\t$msipath\t$patchmsipath\t$symbolpaths\t$family\n"; + push(@newcontent, $newline); + + # saving file + installer::files::save_file($filename, \@newcontent); +} + +#################################################################### +# Editing table ImageFamilies +#################################################################### + +sub change_imagefamilies_table +{ + my ($localmspdir) = @_; + + my $infoline = "Changing content of table \"ImageFamilies\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = $localmspdir . $installer::globals::separator . "ImageFamilies.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_imagefamilies_table"); } + + my $filecontent = installer::files::read_file($filename); + my @newcontent = (); + + # Copying the header + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } } + + # Syntax: Family MediaSrcPropName MediaDiskId FileSequenceStart DiskPrompt VolumeLabel + # "FileSequenceStart has to be set + + # Default values: + + my $family = "22334455"; + my $mediasrcpropname = "MediaSrcPropName"; + my $mediadiskid = "2"; + my $filesequencestart = get_filesequencestart(); + my $diskprompt = ""; + my $volumelabel = ""; + + if ( $#{$filecontent} >= 3 ) + { + my $line = ${$filecontent}[3]; + if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + $family = $1; + $mediasrcpropname = $2; + $mediadiskid = $3; + $diskprompt = $5; + $volumelabel = $6; + } + } + + #Adding sequence line + my $newline = "$family\t$mediasrcpropname\t$mediadiskid\t$filesequencestart\t$diskprompt\t$volumelabel\n"; + push(@newcontent, $newline); + + # saving file + installer::files::save_file($filename, \@newcontent); +} + +#################################################################### +# Setting start sequence for patch +#################################################################### + +sub get_filesequencestart +{ + my $sequence = 1000; # default + + if ( $installer::globals::updatelastsequence ) { $sequence = $installer::globals::updatelastsequence + 500; } + + return $sequence; +} + +#################################################################### +# Setting time value into pcp file +# Format mm/dd/yyyy hh:mm +#################################################################### + +sub get_patchtime_value +{ + # Syntax: 8/8/2008 11:55 + my $minute = (localtime())[1]; + my $hour = (localtime())[2]; + my $day = (localtime())[3]; + my $month = (localtime())[4]; + my $year = 1900 + (localtime())[5]; + + $month++; # zero based month + if ( $minute < 10 ) { $minute = "0" . $minute; } + if ( $hour < 10 ) { $hour = "0" . $hour; } + + my $timestring = $month . "/" . $day . "/" . $year . " " . $hour . ":" . $minute; + + return $timestring; +} + +################################################################################# +# Checking, if this is the correct database. +################################################################################# + +sub correct_langs +{ + my ($langs, $languagestringref) = @_; + + my $correct_langs = 0; + + # Comparing $langs with $languagestringref + + my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ","); + my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_"); + + my $not_included = 0; + foreach my $onelang ( keys %{$langlisthash} ) + { + if ( ! exists($langstringhash->{$onelang}) ) + { + $not_included = 1; + last; + } + } + + if ( ! $not_included ) + { + foreach my $onelanguage ( keys %{$langstringhash} ) + { + if ( ! exists($langlisthash->{$onelanguage}) ) + { + $not_included = 1; + last; + } + } + + if ( ! $not_included ) { $correct_langs = 1; } + } + + return $correct_langs; +} + +################################################################################# +# Searching for the path to the reference database for this special product. +################################################################################# + +sub get_patchid_from_list +{ + my ($filecontent, $languagestringref, $filename) = @_; + + my $patchid = ""; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + my $line = ${$filecontent}[$i]; + if ( $line =~ /^\s*$/ ) { next; } # empty line + if ( $line =~ /^\s*\#/ ) { next; } # comment line + + if ( $line =~ /^\s*(.+?)\s*=\s*(.+?)\s*$/ ) + { + my $langs = $1; + my $localpatchid = $2; + + if ( correct_langs($langs, $languagestringref) ) + { + $patchid = $localpatchid; + last; + } + } + else + { + installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_patchid_from_list"); + } + } + + return $patchid; +} + +#################################################################### +# Editing table PatchMetadata +#################################################################### + +sub change_patchmetadata_table +{ + my ($localmspdir, $allvariables, $languagestringref) = @_; + + my $infoline = "Changing content of table \"PatchMetadata\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = $localmspdir . $installer::globals::separator . "PatchMetadata.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_patchmetadata_table"); } + + my $filecontent = installer::files::read_file($filename); + my @newcontent = (); + + # Syntax: Company Property Value + # Interesting properties: "Classification" and "CreationTimeUTC" + + my $classification_set = 0; + my $creationtime_set = 0; + my $targetproductname_set = 0; + my $manufacturer_set = 0; + my $displayname_set = 0; + my $description_set = 0; + my $allowremoval_set = 0; + + my $defaultcompany = ""; + + my $classificationstring = "Classification"; + my $classificationvalue = "Hotfix"; + if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $classificationvalue = "ServicePack"; } + + my $allowremovalstring = "AllowRemoval"; + my $allowremovalvalue = "1"; + if (( exists($allvariables->{'MSPALLOWREMOVAL'}) ) && ( $allvariables->{'MSPALLOWREMOVAL'} == 0 )) { $allowremovalvalue = 0; } + + my $timestring = "CreationTimeUTC"; + # Syntax: 8/8/2008 11:55 + my $timevalue = get_patchtime_value(); + + my $targetproductnamestring = "TargetProductName"; + my $targetproductnamevalue = $allvariables->{'PRODUCTNAME'}; + if ( $allvariables->{'PROPERTYTABLEPRODUCTNAME'} ) { $targetproductnamevalue = $allvariables->{'PROPERTYTABLEPRODUCTNAME'}; } + + my $manufacturerstring = "ManufacturerName"; + my $manufacturervalue = $ENV{'OOO_VENDOR'}; + if ( $installer::globals::longmanufacturer ) { $manufacturervalue = $installer::globals::longmanufacturer; } + + my $displaynamestring = "DisplayName"; + my $descriptionstring = "Description"; + my $displaynamevalue = ""; + my $descriptionvalue = ""; + + my $base = $allvariables->{'PRODUCTNAME'} . " " . $allvariables->{'PRODUCTVERSION'}; + if ( $installer::globals::languagepack || $installer::globals::helppack ) { $base = $targetproductnamevalue; } + + my $windowspatchlevel = 0; + if ( $allvariables->{'WINDOWSPATCHLEVEL'} ) { $windowspatchlevel = $allvariables->{'WINDOWSPATCHLEVEL'}; } + + my $displayaddon = ""; + if ( $allvariables->{'PATCHDISPLAYADDON'} ) { $displayaddon = $allvariables->{'PATCHDISPLAYADDON'}; } + + my $patchsequence = get_patchsequence($allvariables); + + if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) + { + $displaynamevalue = $base . " ServicePack " . $windowspatchlevel . " " . $patchsequence . " Build: " . $installer::globals::buildid; + $descriptionvalue = $base . " ServicePack " . $windowspatchlevel . " " . $patchsequence . " Build: " . $installer::globals::buildid; + } + else + { + $displaynamevalue = $base . " Hotfix " . $displayaddon . " " . $patchsequence . " Build: " . $installer::globals::buildid; + $descriptionvalue = $base . " Hotfix " . $displayaddon . " " . $patchsequence . " Build: " . $installer::globals::buildid; + $displaynamevalue =~ s/ / /g; + $descriptionvalue =~ s/ / /g; + $displaynamevalue =~ s/ / /g; + $descriptionvalue =~ s/ / /g; + $displaynamevalue =~ s/ / /g; + $descriptionvalue =~ s/ / /g; + } + + if ( $allvariables->{'MSPPATCHNAMELIST'} ) + { + my $patchnamelistfile = $allvariables->{'MSPPATCHNAMELIST'}; + $patchnamelistfile = $installer::globals::idttemplatepath . $installer::globals::separator . $patchnamelistfile; + if ( ! -f $patchnamelistfile ) { installer::exiter::exit_program("ERROR: Could not find file \"$patchnamelistfile\".", "change_patchmetadata_table"); } + my $filecontent = installer::files::read_file($patchnamelistfile); + + # Get name and path of reference database + my $patchid = get_patchid_from_list($filecontent, $languagestringref, $patchnamelistfile); + + if ( $patchid eq "" ) { installer::exiter::exit_program("ERROR: Could not find file patchid in file \"$patchnamelistfile\" for language(s) \"$$languagestringref\".", "change_patchmetadata_table"); } + + # Setting language specific patch id + } + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ ) + { + my $company = $1; + my $property = $2; + my $value = $3; + + if ( $property eq $classificationstring ) + { + ${$filecontent}[$i] = "$company\t$property\t$classificationvalue\n"; + $classification_set = 1; + } + + if ( $property eq $allowremovalstring ) + { + ${$filecontent}[$i] = "$company\t$property\t$allowremovalvalue\n"; + $allowremoval_set = 1; + } + + if ( $property eq $timestring ) + { + ${$filecontent}[$i] = "$company\t$property\t$timevalue\n"; + $creationtime_set = 1; + } + + if ( $property eq $targetproductnamestring ) + { + ${$filecontent}[$i] = "$company\t$property\t$targetproductnamevalue\n"; + $targetproductname_set = 1; + } + + if ( $property eq $manufacturerstring ) + { + ${$filecontent}[$i] = "$company\t$property\t$manufacturervalue\n"; + $manufacturer_set = 1; + } + + if ( $property eq $displaynamestring ) + { + ${$filecontent}[$i] = "$company\t$property\t$displaynamevalue\n"; + $displayname_set = 1; + } + + if ( $property eq $descriptionstring ) + { + ${$filecontent}[$i] = "$company\t$property\t$descriptionvalue\n"; + $description_set = 1; + } + } + + push(@newcontent, ${$filecontent}[$i]); + } + + if ( ! $classification_set ) + { + my $line = "$defaultcompany\t$classificationstring\t$classificationvalue\n"; + push(@newcontent, $line); + } + + if ( ! $allowremoval_set ) + { + my $line = "$defaultcompany\t$classificationstring\t$allowremovalvalue\n"; + push(@newcontent, $line); + } + + if ( ! $allowremoval_set ) + { + my $line = "$defaultcompany\t$classificationstring\t$allowremovalvalue\n"; + push(@newcontent, $line); + } + + if ( ! $creationtime_set ) + { + my $line = "$defaultcompany\t$timestring\t$timevalue\n"; + push(@newcontent, $line); + } + + if ( ! $targetproductname_set ) + { + my $line = "$defaultcompany\t$targetproductnamestring\t$targetproductnamevalue\n"; + push(@newcontent, $line); + } + + if ( ! $manufacturer_set ) + { + my $line = "$defaultcompany\t$manufacturerstring\t$manufacturervalue\n"; + push(@newcontent, $line); + } + + if ( ! $displayname_set ) + { + my $line = "$defaultcompany\t$displaynamestring\t$displaynamevalue\n"; + push(@newcontent, $line); + } + + if ( ! $description_set ) + { + my $line = "$defaultcompany\t$descriptionstring\t$descriptionvalue\n"; + push(@newcontent, $line); + } + + # saving file + installer::files::save_file($filename, \@newcontent); +} + +#################################################################### +# Editing table PatchSequence +#################################################################### + +sub change_patchsequence_table +{ + my ($localmspdir, $allvariables) = @_; + + my $infoline = "Changing content of table \"PatchSequence\"\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $filename = $localmspdir . $installer::globals::separator . "PatchSequence.idt"; + if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_patchsequence_table"); } + + my $filecontent = installer::files::read_file($filename); + my @newcontent = (); + + # Copying the header + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } } + + # Syntax: PatchFamily Target Sequence Supersede + + my $patchfamily = "SO"; + my $target = ""; + my $patchsequence = get_patchsequence($allvariables); + my $supersede = get_supersede($allvariables); + + if ( $#{$filecontent} >= 3 ) + { + my $line = ${$filecontent}[3]; + if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\s$/ ) + { + $patchfamily = $1; + $target = $2; + } + } + + #Adding sequence line, saving PatchFamily + my $newline = "$patchfamily\t$target\t$patchsequence\t$supersede\n"; + push(@newcontent, $newline); + + # saving file + installer::files::save_file($filename, \@newcontent); +} + +#################################################################### +# Setting supersede, "0" for Hotfixes, "1" for ServicePack +#################################################################### + +sub get_supersede +{ + my ( $allvariables ) = @_; + + my $supersede = 0; # if not defined, this is a Hotfix + + if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $supersede = 1; } + + return $supersede; +} + +#################################################################### +# Setting the sequence of the patch +#################################################################### + +sub get_patchsequence +{ + my ( $allvariables ) = @_; + + my $patchsequence = "1.0"; + + if ( ! $allvariables->{'PACKAGEVERSION'} ) { installer::exiter::exit_program("ERROR: PACKAGEVERSION must be set for msp patch creation!", "get_patchsequence"); } + + my $packageversion = $allvariables->{'PACKAGEVERSION'}; + + if ( $packageversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\.(\d+)\s*$/ ) + { + my $major = $1; + my $minor = $2; + my $micro = $3; + my $patch = $4; + $patchsequence = $major . "\." . $minor . "\." . $micro . "\." . $patch; + } + + return $patchsequence; +} + +#################################################################### +# Editing all tables from pcp file, that need to be edited +#################################################################### + +sub edit_tables +{ + my ($tablelist, $localmspdir, $olddatabase, $newdatabase, $mspfilename, $allvariables, $languagestringref) = @_; + + # table list contains: my $tablelist = "Properties TargetImages UpgradedImages ImageFamilies PatchMetadata PatchSequence"; + + change_properties_table($localmspdir, $mspfilename); + change_targetimages_table($localmspdir, $olddatabase); + change_upgradedimages_table($localmspdir, $newdatabase); + change_imagefamilies_table($localmspdir); + change_patchmetadata_table($localmspdir, $allvariables, $languagestringref); + change_patchsequence_table($localmspdir, $allvariables); +} + +################################################################################# +# Checking, if this is the correct database. +################################################################################# + +sub correct_patch +{ + my ($product, $pro, $langs, $languagestringref) = @_; + + my $correct_patch = 0; + + # Comparing $product with $installer::globals::product and + # $pro with $installer::globals::pro and + # $langs with $languagestringref + + my $product_is_good = 0; + + my $localproduct = $installer::globals::product; + if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; } + elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; } + + if ( $product eq $localproduct ) { $product_is_good = 1; } + + if ( $product_is_good ) + { + my $pro_is_good = 0; + + if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; } + + if ( $pro_is_good ) + { + my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ","); + my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_"); + + my $not_included = 0; + foreach my $onelang ( keys %{$langlisthash} ) + { + if ( ! exists($langstringhash->{$onelang}) ) + { + $not_included = 1; + last; + } + } + + if ( ! $not_included ) + { + foreach my $onelanguage ( keys %{$langstringhash} ) + { + if ( ! exists($langlisthash->{$onelanguage}) ) + { + $not_included = 1; + last; + } + } + + if ( ! $not_included ) { $correct_patch = 1; } + } + } + } + + return $correct_patch; +} + +################################################################################# +# Searching for the path to the required patch for this special product. +################################################################################# + +sub get_requiredpatchfile_from_list +{ + my ($filecontent, $languagestringref, $filename) = @_; + + my $patchpath = ""; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + my $line = ${$filecontent}[$i]; + if ( $line =~ /^\s*$/ ) { next; } # empty line + if ( $line =~ /^\s*\#/ ) { next; } # comment line + + if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ ) + { + my $product = $1; + my $pro = $2; + my $langs = $3; + my $path = $4; + + if (( $pro ne "pro" ) && ( $pro ne "nonpro" )) { installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename. Only \"pro\" or \"nonpro\" allowed in column 1! Line: \"$line\"", "get_databasename_from_list"); } + + if ( correct_patch($product, $pro, $langs, $languagestringref) ) + { + $patchpath = $path; + last; + } + } + else + { + installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_requiredpatchfile_from_list"); + } + } + + return $patchpath; +} + +################################################################## +# Converting unicode file to ascii +# to be more precise: uft-16 little endian to ascii +################################################################## + +sub convert_unicode_to_ascii +{ + my ( $filename ) = @_; + + my @localfile = (); + + my $savfilename = $filename . "_before.unicode"; + installer::systemactions::copy_one_file($filename, $savfilename); + + open( IN, "<:encoding(UTF16-LE)", $filename ) || installer::exiter::exit_program("ERROR: Cannot open file $filename for reading", "convert_unicode_to_ascii"); + while ( $line = ) { + push @localfile, $line; + } + close( IN ); + + if ( open( OUT, ">", $filename ) ) + { + print OUT @localfile; + close(OUT); + } +} + +#################################################################### +# Analyzing the log file created by msimsp.exe to find all +# files included into the patch. +#################################################################### + +sub analyze_msimsp_logfile +{ + my ($logfile, $filesarray) = @_; + + # Reading log file after converting from utf-16 (LE) to ascii + convert_unicode_to_ascii($logfile); + my $logfilecontent = installer::files::read_file($logfile); + + # Creating hash from $filesarray: unique file name -> destination of file + my %filehash = (); + my %destinationcollector = (); + + for ( my $i = 0; $i <= $#{$filesarray}; $i++ ) + { + my $onefile = ${$filesarray}[$i]; + + # Only collecting files with "uniquename" and "destination" + if (( exists($onefile->{'uniquename'}) ) && ( exists($onefile->{'uniquename'}) )) + { + my $uniquefilename = $onefile->{'uniquename'}; + my $destpath = $onefile->{'destination'}; + $filehash{$uniquefilename} = $destpath; + } + } + + # Analyzing log file of msimsp.exe, finding all changed files + # and searching all destinations of unique file names. + # Content in log file: "INFO File Key: is modified" + # Collecting content in @installer::globals::patchfilecollector + + for ( my $i = 0; $i <= $#{$logfilecontent}; $i++ ) + { + if ( ${$logfilecontent}[$i] =~ /Key\:\s*(.*?) is modified\s*$/ ) + { + my $filekey = $1; + if ( exists($filehash{$filekey}) ) { $destinationcollector{$filehash{$filekey}} = 1; } + else { installer::exiter::exit_program("ERROR: Could not find file key \"$filekey\" in file collector.", "analyze_msimsp_logfile"); } + } + } + + foreach my $onedest ( sort keys %destinationcollector ) { push(@installer::globals::patchfilecollector, "$onedest\n"); } + +} + +#################################################################### +# Creating msp patch files for Windows +#################################################################### + +sub create_msp_patch +{ + my ($installationdir, $includepatharrayref, $allvariables, $languagestringref, $languagesarrayref, $filesarray) = @_; + + my $force = 1; # print this message even in 'quiet' mode + installer::logger::print_message( "\n******************************************\n" ); + installer::logger::print_message( "... creating msp installation set ...\n", $force ); + installer::logger::print_message( "******************************************\n" ); + + $installer::globals::creating_windows_installer_patch = 1; + + my @needed_files = ("msimsp.exe"); # only required for patch creation process + installer::control::check_needed_files_in_path(\@needed_files); + + installer::logger::include_header_into_logfile("Creating msp installation sets:"); + + my $firstdir = $installationdir; + installer::pathanalyzer::get_path_from_fullqualifiedname(\$firstdir); + + my $lastdir = $installationdir; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$lastdir); + + if ( $lastdir =~ /\./ ) { $lastdir =~ s/\./_msp_inprogress\./ } + else { $lastdir = $lastdir . "_msp_inprogress"; } + + # Removing existing directory "_native_packed_inprogress" and "_native_packed_witherror" and "_native_packed" + + my $mspdir = $firstdir . $lastdir; + if ( -d $mspdir ) { installer::systemactions::remove_complete_directory($mspdir); } + + my $olddir = $mspdir; + $olddir =~ s/_inprogress/_witherror/; + if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); } + + $olddir = $mspdir; + $olddir =~ s/_inprogress//; + if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); } + + # Creating the new directory for new installation set + installer::systemactions::create_directory($mspdir); + + $installer::globals::saveinstalldir = $mspdir; + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting product installation"); + + # Installing both installation sets + installer::logger::print_message( "... installing products ...\n" ); + my ($olddatabase, $newdatabase) = install_installation_sets($installationdir); + + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting pcp file creation"); + + # Create pcp file + installer::logger::print_message( "... creating pcp file ...\n" ); + + my $localmspdir = installer::systemactions::create_directories("msp", $languagestringref); + + if ( ! $allvariables->{'PCPFILENAME'} ) { installer::exiter::exit_program("ERROR: Property \"PCPFILENAME\" has to be defined.", "create_msp_patch"); } + my $pcpfilename = $allvariables->{'PCPFILENAME'}; + + if ( $installer::globals::languagepack ) { $pcpfilename =~ s/.pcp\s*$/languagepack.pcp/; } + elsif ( $installer::globals::helppack ) { $pcpfilename =~ s/.pcp\s*$/helppack.pcp/; } + + # Searching the pcp file in the include paths + my $fullpcpfilenameref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$pcpfilename, $includepatharrayref, 1); + if ( $$fullpcpfilenameref eq "" ) { installer::exiter::exit_program("ERROR: pcp file not found: $pcpfilename !", "create_msp_patch"); } + my $fullpcpfilenamesource = $$fullpcpfilenameref; + + # Copying pcp file + my $fullpcpfilename = $localmspdir . $installer::globals::separator . $pcpfilename; + installer::systemactions::copy_one_file($fullpcpfilenamesource, $fullpcpfilename); + + # a. Extracting tables from msi database: msidb.exe -d -f -e File Media, ... + # b. Changing content of msi database in tables: File, Media, Directory, FeatureComponent + # c. Including tables into msi database: msidb.exe -d -f -i File Media, ... + + # Unpacking tables from pcp file + extract_all_tables_from_pcpfile($fullpcpfilename, $localmspdir); + + # Tables, that need to be edited + my $tablelist = "Properties TargetImages UpgradedImages ImageFamilies PatchMetadata PatchSequence"; # required tables + + # Saving all tables + check_and_save_tables($tablelist, $localmspdir); + + # Setting the name of the new msp file + my $mspfilename = set_mspfilename($allvariables, $mspdir, $languagesarrayref); + + # Editing tables + edit_tables($tablelist, $localmspdir, $olddatabase, $newdatabase, $mspfilename, $allvariables, $languagestringref); + + # Adding edited tables into pcp file + include_tables_into_pcpfile($fullpcpfilename, $localmspdir, $tablelist); + + # Start msimsp.exe + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting msimsp.exe"); + my $msimsplogfile = execute_msimsp($fullpcpfilename, $mspfilename, $localmspdir); + + # Sign .msp file + if ( defined($ENV{'WINDOWS_BUILD_SIGNING'}) && ($ENV{'WINDOWS_BUILD_SIGNING'} eq 'TRUE') ) + { + my $localmspfilename = $mspfilename; + $localmspfilename =~ s/\\/\\\\/g; + my $systemcall = "signtool.exe sign "; + if ( defined($ENV{'PFXFILE'}) ) { $systemcall .= "-f $ENV{'PFXFILE'} "; } + if ( defined($ENV{'PFXPASSWORD'}) ) { $systemcall .= "-p $ENV{'PFXPASSWORD'} "; } + if ( defined($ENV{'TIMESTAMPURL'}) ) { $systemcall .= "-t $ENV{'TIMESTAMPURL'} "; } else { $systemcall .= "-t http://timestamp.globalsign.com/scripts/timestamp.dll "; } + $systemcall .= "-d \"" . $allvariables->{'PRODUCTNAME'} . " " . $allvariables->{'PRODUCTVERSION'} . " Patch " . $allvariables->{'WINDOWSPATCHLEVEL'} . "\" "; + $systemcall .= $localmspfilename; + installer::logger::print_message( "... code signing and timestamping with signtool.exe ...\n" ); + + my $returnvalue = system($systemcall); + + # do not print password to log + if ( defined($ENV{'PFXPASSWORD'}) ) { $systemcall =~ s/$ENV{'PFXPASSWORD'}/********/; } + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute \"$systemcall\"!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Success: Executed \"$systemcall\" successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + + # Copy final installation set next to msp file + installer::logger::include_timestamp_into_logfile("\nPerformance Info: Copying installation set"); + installer::logger::print_message( "... copying installation set ...\n" ); + + my $oldinstallationsetpath = $installer::globals::updatedatabasepath; + + if ( $^O =~ /cygwin/i ) { $oldinstallationsetpath =~ s/\\/\//g; } + + installer::pathanalyzer::get_path_from_fullqualifiedname(\$oldinstallationsetpath); + installer::systemactions::copy_complete_directory($oldinstallationsetpath, $mspdir); + + # Copying additional patches into the installation set, if required + if (( $allvariables->{'ADDITIONALREQUIREDPATCHES'} ) && ( $allvariables->{'ADDITIONALREQUIREDPATCHES'} ne "" ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) + { + my $filename = $allvariables->{'ADDITIONALREQUIREDPATCHES'}; + + my $fullfilenameref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$filename, $includepatharrayref, 1); + if ( $$fullfilenameref eq "" ) { installer::exiter::exit_program("ERROR: Could not find file with required patches, although it is defined: $filename !", "create_msp_patch"); } + my $fullfilename = $$fullfilenameref; + + # Reading list file + my $listfile = installer::files::read_file($fullfilename); + + # Get name and path of reference database + my $requiredpatchfile = get_requiredpatchfile_from_list($listfile, $languagestringref, $fullfilename); + if ( $requiredpatchfile eq "" ) { installer::exiter::exit_program("ERROR: Could not find path to required patch in file $fullfilename for language(s) $$languagestringref!", "create_msp_patch"); } + + # Copying patch file + installer::systemactions::copy_one_file($requiredpatchfile, $mspdir); + # my $infoline = "Copy $requiredpatchfile to $mspdir\n"; + # push( @installer::globals::logfileinfo, $infoline); + } + + # Find all files included into the patch + # Analyzing the msimsp log file $msimsplogfile + analyze_msimsp_logfile($msimsplogfile, $filesarray); + + # Done + installer::logger::include_timestamp_into_logfile("\nPerformance Info: msp creation done"); + + return $mspdir; +} + +1; diff --git a/solenv/bin/modules/installer/windows/property.pm b/solenv/bin/modules/installer/windows/property.pm new file mode 100644 index 000000000..a385e59a8 --- /dev/null +++ b/solenv/bin/modules/installer/windows/property.pm @@ -0,0 +1,566 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::property; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; +use installer::windows::language; + +############################################# +# Setting the properties dynamically +# for the table Property.idt +############################################# + +sub get_arpcomments_for_property_table +{ + my ( $allvariables, $languagestringref ) = @_; + + my $name = $allvariables->{'PRODUCTNAME'}; + my $version = $allvariables->{'PRODUCTVERSION'}; + my $comment = $name . " " . $version; + + my $postversionextension = ""; + if ( $allvariables->{'POSTVERSIONEXTENSION'} ) + { + $postversionextension = $allvariables->{'POSTVERSIONEXTENSION'}; + $comment = $comment . " " . $postversionextension; + } + + if ( $installer::globals::languagepack ) { $comment = $comment . " " . "Language Pack"; } + elsif ( $installer::globals::helppack ) { $comment = $comment . " " . "Help Pack"; } + + my $languagestring = $$languagestringref; + $languagestring =~ s/\_/\,/g; + if ( length($languagestring) > 30 ) { $languagestring = "multilanguage"; } # fdo#64053 + + $comment = $comment . " ($languagestring)"; + + return $comment; +} + +sub get_installlevel_for_property_table +{ + my $installlevel = "100"; + return $installlevel; +} + +sub get_ischeckforproductupdates_for_property_table +{ + my $ischeckforproductupdates = "1"; + return $ischeckforproductupdates; +} + +sub get_manufacturer_for_property_table +{ + return $installer::globals::manufacturer; +} + +sub get_productlanguage_for_property_table +{ + my ($language) = @_; + my $windowslanguage = installer::windows::language::get_windows_language($language); + return $windowslanguage; +} + +sub get_language_string +{ + my $langstring = ""; + + for ( my $i = 0; $i <= $#installer::globals::languagenames; $i++ ) + { + $langstring = $langstring . $installer::globals::languagenames[$i] . ", "; + } + + $langstring =~ s/\,\s*$//; + $langstring = "(" . $langstring . ")"; + + return $langstring; +} + +sub get_english_language_string +{ + my $langstring = ""; + + # Sorting value not keys, therefore collecting all values + my %helper = (); + foreach my $lang ( keys %installer::globals::all_required_english_languagestrings ) + { + $helper{$installer::globals::all_required_english_languagestrings{$lang}} = 1; + } + + foreach my $lang ( sort keys %helper ) + { + $langstring = $langstring . $lang . ", "; + } + + $langstring =~ s/\,\s*$//; + $langstring = "(" . $langstring . ")"; + + return $langstring; +} + +sub get_productname($$) +{ + my ( $language, $allvariables ) = @_; + + my $name = $allvariables->{'PRODUCTNAME'}; + + return $name; +} + +sub get_productname_for_property_table($$) +{ + my ( $language, $allvariables ) = @_; + + my $name = get_productname ($language, $allvariables); + my $version = $allvariables->{'PRODUCTVERSION'}; + my $productname = $name . " " . $version; + + my $productextension = ""; + if ( $allvariables->{'PRODUCTEXTENSION'} ) + { + $productextension = $allvariables->{'PRODUCTEXTENSION'}; + $productname = $productname . $productextension; + } + + my $postversionextension = ""; + if ( $allvariables->{'POSTVERSIONEXTENSION'} ) + { + $postversionextension = $allvariables->{'POSTVERSIONEXTENSION'}; + $productname = $productname . " " . $postversionextension; + } + + if ( $installer::globals::languagepack ) + { + my $langstring = get_english_language_string(); # Example: (English, German) + $productname = $name . " " . $version . " Language Pack" . " " . $langstring; + } + elsif ( $installer::globals::helppack ) + { + my $langstring = get_english_language_string(); # New: (English, German) + $productname = $name . " " . $version . " Help Pack" . " " . $langstring; + } + + # Saving this name in hash $allvariables for further usage + $allvariables->{'PROPERTYTABLEPRODUCTNAME'} = $productname; + my $infoline = "Defined variable PROPERTYTABLEPRODUCTNAME: $productname\n"; + push(@installer::globals::logfileinfo, $infoline); + + return $productname; +} + +sub get_quickstarterlinkname_for_property_table($$) +{ + my ( $language, $allvariables ) = @_; + + # no usage of POSTVERSIONEXTENSION for Quickstarter link name! + my $name = get_productname ($language, $allvariables); + my $version = $allvariables->{'PRODUCTVERSION'}; + my $quickstartername = $name . " " . $version; + + my $infoline = "Defined Quickstarter Link name: $quickstartername\n"; + push(@installer::globals::logfileinfo, $infoline); + + return $quickstartername; +} + +sub get_productversion_for_property_table +{ + return $installer::globals::msiproductversion; +} + +####################################################### +# Setting some important properties +# (for finding the product in deinstallation process) +####################################################### + +sub set_important_properties +{ + my ($propertyfile, $allvariables, $languagestringref) = @_; + + # Setting new variables with the content of %PRODUCTNAME and %PRODUCTVERSION + if ( $allvariables->{'PRODUCTNAME'} ) + { + my $onepropertyline = "DEFINEDPRODUCT" . "\t" . $allvariables->{'PRODUCTNAME'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'PRODUCTVERSION'} ) + { + my $onepropertyline = "DEFINEDVERSION" . "\t" . $allvariables->{'PRODUCTVERSION'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if (( $allvariables->{'PRODUCTNAME'} ) && ( $allvariables->{'PRODUCTVERSION'} ) && ( $allvariables->{'REGISTRYLAYERNAME'} )) + { + my $onepropertyline = "FINDPRODUCT" . "\t" . "Software\\LibreOffice" . "\\" . $allvariables->{'REGISTRYLAYERNAME'} . "\\" . $allvariables->{'PRODUCTNAME'} . "\\" . $allvariables->{'PRODUCTVERSION'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'PRODUCTMAJOR'} ) + { + my $onepropertyline = "PRODUCTMAJOR" . "\t" . $allvariables->{'PRODUCTMAJOR'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'PRODUCTBUILDID'} ) + { + my $onepropertyline = "PRODUCTBUILDID" . "\t" . $allvariables->{'PRODUCTBUILDID'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'URELAYERVERSION'} ) + { + my $onepropertyline = "URELAYERVERSION" . "\t" . $allvariables->{'URELAYERVERSION'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'BRANDPACKAGEVERSION'} ) + { + my $onepropertyline = "BRANDPACKAGEVERSION" . "\t" . $allvariables->{'BRANDPACKAGEVERSION'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'EXCLUDE_FROM_REBASE'} ) + { + my $onepropertyline = "EXCLUDE_FROM_REBASE" . "\t" . $allvariables->{'EXCLUDE_FROM_REBASE'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $allvariables->{'PREREQUIREDPATCH'} ) + { + my $onepropertyline = "PREREQUIREDPATCH" . "\t" . $allvariables->{'PREREQUIREDPATCH'} . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + my $onepropertyline = "IGNOREPREREQUIREDPATCH" . "\t" . "1" . "\n"; + push(@{$propertyfile}, $onepropertyline); + + $onepropertyline = "DONTOPTIMIZELIBS" . "\t" . "0" . "\n"; + push(@{$propertyfile}, $onepropertyline); + + if ( $installer::globals::officedirhostname ) + { + my $onepropertyline = "OFFICEDIRHOSTNAME" . "\t" . $installer::globals::officedirhostname . "\n"; + push(@{$propertyfile}, $onepropertyline); + + my $localofficedirhostname = $installer::globals::officedirhostname; + $localofficedirhostname =~ s/\//\\/g; + $onepropertyline = "OFFICEDIRHOSTNAME_" . "\t" . $localofficedirhostname . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + if ( $installer::globals::desktoplinkexists ) + { + my $onepropertyline = "DESKTOPLINKEXISTS" . "\t" . "1" . "\n"; + push(@{$propertyfile}, $onepropertyline); + + $onepropertyline = "CREATEDESKTOPLINK" . "\t" . "1" . "\n"; # Setting the default + push(@{$propertyfile}, $onepropertyline); + } + + if ( $installer::globals::languagepack ) + { + my $onepropertyline = "ISLANGUAGEPACK" . "\t" . "1" . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + elsif ( $installer::globals::helppack ) + { + my $onepropertyline = "ISHELPPACK" . "\t" . "1" . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + my $languagesline = "PRODUCTALLLANGUAGES" . "\t" . $$languagestringref . "\n"; + push(@{$propertyfile}, $languagesline); + + if (( $allvariables->{'PRODUCTEXTENSION'} ) && ( $allvariables->{'PRODUCTEXTENSION'} eq "Beta" )) + { + # my $registryline = "WRITE_REGISTRY" . "\t" . "0" . "\n"; + # push(@{$propertyfile}, $registryline); + my $betainfoline = "BETAPRODUCT" . "\t" . "1" . "\n"; + push(@{$propertyfile}, $betainfoline); + } + elsif ( $allvariables->{'DEVELOPMENTPRODUCT'} ) + { + my $registryline = "WRITE_REGISTRY" . "\t" . "0" . "\n"; + push(@{$propertyfile}, $registryline); + } + else + { + my $registryline = "WRITE_REGISTRY" . "\t" . "1" . "\n"; # Default: Write complete registry + push(@{$propertyfile}, $registryline); + } + + # Adding also used tree conditions for multilayer products. + # These are saved in %installer::globals::usedtreeconditions + foreach my $treecondition (keys %installer::globals::usedtreeconditions) + { + my $onepropertyline = $treecondition . "\t" . "1" . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + # No more license dialog for selected products + if ( $allvariables->{'HIDELICENSEDIALOG'} ) + { + my $onepropertyline = "HIDEEULA" . "\t" . "1" . "\n"; + + my $already_defined = 0; + + for ( my $i = 0; $i <= $#{$propertyfile}; $i++ ) + { + if ( ${$propertyfile}[$i] =~ /^\s*HIDEEULA\t/ ) + { + ${$propertyfile}[$i] = $onepropertyline; + $already_defined = 1; + last; + } + } + + if ( ! $already_defined ) + { + push(@{$propertyfile}, $onepropertyline); + } + } +} + +####################################################### +# Setting properties needed for ms file type registration +####################################################### + +sub set_ms_file_types_properties +{ + my ($propertyfile) = @_; + +# we do not register PPSM, PPAM, and XLAM file types in +# setup_native\source\win32\customactions\reg4allmsdoc\reg4allmsi.cxx +# (probably because LibreOffice can't deal with them properly (?) + + push(@{$propertyfile}, "REGISTER_PPS" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_PPSX" . "\t" . "0" . "\n"); +# push(@{$propertyfile}, "REGISTER_PPSM" . "\t" . "0" . "\n"); +# push(@{$propertyfile}, "REGISTER_PPAM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_PPT" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_PPTX" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_PPTM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_POT" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_POTX" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_POTM" . "\t" . "0" . "\n"); + + push(@{$propertyfile}, "REGISTER_DOC" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_DOCX" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_DOCM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_DOT" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_DOTX" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_DOTM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_RTF" . "\t" . "0" . "\n"); + + push(@{$propertyfile}, "REGISTER_XLS" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_XLSX" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_XLSM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_XLSB" . "\t" . "0" . "\n"); +# push(@{$propertyfile}, "REGISTER_XLAM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_XLT" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_XLTX" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_XLTM" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_IQY" . "\t" . "0" . "\n"); + + push(@{$propertyfile}, "REGISTER_NO_MSO_TYPES" . "\t" . "0" . "\n"); + push(@{$propertyfile}, "REGISTER_ALL_MSO_TYPES" . "\t" . "0" . "\n"); +} + +#################################################################################### +# Updating the file Property.idt dynamically +# Content: +# Property Value +#################################################################################### + +sub update_property_table +{ + my ($basedir, $language, $allvariables, $languagestringref) = @_; + + my $properyfilename = $basedir . $installer::globals::separator . "Property.idt"; + + my $propertyfile = installer::files::read_file($properyfilename); + + my $hasarpnomodify = 0; + + # Getting the new values + # Some values (arpcomments, arpcontacts, ...) are inserted from the Property.mlf + + my $arpcomments = get_arpcomments_for_property_table($allvariables, $languagestringref); + my $installlevel = get_installlevel_for_property_table(); + my $ischeckforproductupdates = get_ischeckforproductupdates_for_property_table(); + my $manufacturer = get_manufacturer_for_property_table(); + my $productlanguage = get_productlanguage_for_property_table($language); + my $productname = get_productname_for_property_table($language, $allvariables); + my $productversion = get_productversion_for_property_table(); + my $quickstarterlinkname = get_quickstarterlinkname_for_property_table($language, $allvariables); + my $windowsminversiontext = "Windows 7 SP1"; + my $windowsminversionnumber = "601"; + my $windowsminspnumber = "1"; + + # Updating the values + + for ( my $i = 0; $i <= $#{$propertyfile}; $i++ ) + { + ${$propertyfile}[$i] =~ s/\bARPCOMMENTSTEMPLATE\b/$arpcomments/; + ${$propertyfile}[$i] =~ s/\bINSTALLLEVELTEMPLATE\b/$installlevel/; + ${$propertyfile}[$i] =~ s/\bISCHECKFORPRODUCTUPDATESTEMPLATE\b/$ischeckforproductupdates/; + ${$propertyfile}[$i] =~ s/\bMANUFACTURERTEMPLATE\b/$manufacturer/; + ${$propertyfile}[$i] =~ s/\bPRODUCTLANGUAGETEMPLATE\b/$productlanguage/; + ${$propertyfile}[$i] =~ s/\bPRODUCTNAMETEMPLATE\b/$productname/; + ${$propertyfile}[$i] =~ s/\bPRODUCTVERSIONTEMPLATE\b/$productversion/; + ${$propertyfile}[$i] =~ s/\bQUICKSTARTERLINKNAMETEMPLATE\b/$quickstarterlinkname/; + ${$propertyfile}[$i] =~ s/\bWINDOWSMINVERSIONTEXTTEMPLATE\b/$windowsminversiontext/; + ${$propertyfile}[$i] =~ s/\bWINDOWSMINVERSIONNUMBERTEMPLATE\b/$windowsminversionnumber/; + ${$propertyfile}[$i] =~ s/\bWINDOWSMINSPNUMBERTEMPLATE\b/$windowsminspnumber/; + if ( ${$propertyfile}[$i] =~ m/\bARPNOMODIFY\b/ ) { $hasarpnomodify = 1; } + } + + # Check if are building silent MSI + if ( $ENV{ENABLE_SILENT_MSI} eq "TRUE" ) + { + push(@{$propertyfile}, "LIMITUI" . "\t" . "1" . "\n"); + if ( !($hasarpnomodify) ) + { + push(@{$propertyfile}, "ARPNOMODIFY" . "\t" . "1" . "\n"); + } + } + + # Setting variables into propertytable + set_important_properties($propertyfile, $allvariables, $languagestringref); + + # Setting variables for register for ms file types + set_ms_file_types_properties($propertyfile); + + # Saving the file + + installer::files::save_file($properyfilename ,$propertyfile); + my $infoline = "Updated idt file: $properyfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +#################################################################################### +# Setting language specific Properties in file Property.idt dynamically +# Adding: +# isMulti = 1 +#################################################################################### + +sub set_languages_in_property_table +{ + my ($basedir, $languagesarrayref) = @_; + + my $properyfilename = $basedir . $installer::globals::separator . "Property.idt"; + my $propertyfile = installer::files::read_file($properyfilename); + + # Setting the info about multilingual installation in property "isMulti" + + my $propertyname = "isMulti"; + my $ismultivalue = 0; + + if ( $installer::globals::ismultilingual ) { $ismultivalue = 1; } + + my $onepropertyline = $propertyname . "\t" . $ismultivalue . "\n"; + push(@{$propertyfile}, $onepropertyline); + + # setting the ARPPRODUCTICON + + if ($installer::globals::sofficeiconadded) # set in shortcut.pm + { + $onepropertyline = "ARPPRODUCTICON" . "\t" . "soffice.ico" . "\n"; + push(@{$propertyfile}, $onepropertyline); + } + + # Saving the file + + installer::files::save_file($properyfilename ,$propertyfile); + my $infoline = "Added language content into idt file: $properyfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +############################################################ +# Setting the ProductCode and the UpgradeCode +# into the Property table. Both have to be stored +# in the global file $installer::globals::codefilename +############################################################ + +sub set_codes_in_property_table +{ + my ($basedir) = @_; + + # Reading the property file + + my $properyfilename = $basedir . $installer::globals::separator . "Property.idt"; + my $propertyfile = installer::files::read_file($properyfilename); + + # Updating the values + + for ( my $i = 0; $i <= $#{$propertyfile}; $i++ ) + { + ${$propertyfile}[$i] =~ s/\bPRODUCTCODETEMPLATE\b/$installer::globals::productcode/; + ${$propertyfile}[$i] =~ s/\bUPGRADECODETEMPLATE\b/$installer::globals::upgradecode/; + } + + # Saving the property file + + installer::files::save_file($properyfilename ,$propertyfile); + my $infoline = "Added language content into idt file: $properyfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +############################################################ +# Changing default for MS file type registration +# in Beta products. +############################################################ + +sub update_checkbox_table +{ + my ($basedir, $allvariables) = @_; + + if (( $allvariables->{'PRODUCTEXTENSION'} ) && ( $allvariables->{'PRODUCTEXTENSION'} eq "Beta" )) + { + my $checkboxfilename = $basedir . $installer::globals::separator . "CheckBox.idt"; + + if ( -f $checkboxfilename ) + { + my $checkboxfile = installer::files::read_file($checkboxfilename); + + my $checkboxline = "SELECT_WORD" . "\t" . "0" . "\n"; + push(@{$checkboxfile}, $checkboxline); + $checkboxline = "SELECT_EXCEL" . "\t" . "0" . "\n"; + push(@{$checkboxfile}, $checkboxline); + $checkboxline = "SELECT_POWERPOINT" . "\t" . "0" . "\n"; + push(@{$checkboxfile}, $checkboxline); + $checkboxline = "SELECT_VISIO" . "\t" . "0" . "\n"; + push(@{$checkboxfile}, $checkboxline); + + # Saving the property file + installer::files::save_file($checkboxfilename ,$checkboxfile); + my $infoline = "Added ms file type defaults into idt file: $checkboxfilename\n"; + push(@installer::globals::logfileinfo, $infoline); + } + } +} + +1; diff --git a/solenv/bin/modules/installer/windows/registry.pm b/solenv/bin/modules/installer/windows/registry.pm new file mode 100644 index 000000000..f7136b887 --- /dev/null +++ b/solenv/bin/modules/installer/windows/registry.pm @@ -0,0 +1,407 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::registry; + +use installer::files; +use installer::globals; +use installer::worker; +use installer::windows::msiglobal; +use installer::windows::idtglobal; + +##################################################### +# Generating the component name from a registryitem +##################################################### + +sub get_registry_component_name +{ + my ($registryref, $allvariables) = @_; + + # In this function exists the rule to create components from registryitems + # Rule: + # The componentname can be directly taken from the ModuleID. + # All registryitems belonging to one module can get the same component. + + my $componentname = ""; + my $isrootmodule = 0; + + if ( $registryref->{'ModuleID'} ) { $componentname = $registryref->{'ModuleID'}; } + + $componentname =~ s/\\/\_/g; + $componentname =~ s/\//\_/g; + $componentname =~ s/\-/\_/g; + $componentname =~ s/\_\s*$//g; + + $componentname = lc($componentname); # componentnames always lowercase + + if ( $componentname eq "gid_module_root" ) { $isrootmodule = 1; } + + # Attention: Maximum length for the componentname is 72 + + # identifying this component as registryitem component + $componentname = "registry_" . $componentname; + + $componentname =~ s/gid_module_/g_m_/g; + $componentname =~ s/_optional_/_o_/g; + + # This componentname must be more specific + my $addon = "_"; + if ( $allvariables->{'PRODUCTNAME'} ) { $addon = $addon . $allvariables->{'PRODUCTNAME'}; } + if ( $allvariables->{'PRODUCTVERSION'} ) { $addon = $addon . $allvariables->{'PRODUCTVERSION'}; } + $addon = lc($addon); + $addon =~ s/ //g; + $addon =~ s/-//g; + $addon =~ s/\.//g; + + my $styles = ""; + if ( $registryref->{'Styles'} ) { $styles = $registryref->{'Styles'}; } + + # Layer links must have unique Component GUID for all products. This is necessary, because only the + # uninstallation of the last product has to delete registry keys. + if ( $styles =~ /\bLAYER_REGISTRY\b/ ) + { + $componentname = "g_m_root_registry_layer_ooo_reglayer"; + # Styles USE_URELAYERVERSION, USE_PRODUCTVERSION + if ( $styles =~ /\bUSE_URELAYERVERSION\b/ ) { $addon = "_ure_" . $allvariables->{'URELAYERVERSION'}; } + if ( $styles =~ /\bUSE_PRODUCTVERSION\b/ ) { $addon = "_basis_" . $allvariables->{'PRODUCTVERSION'}; } + $addon =~ s/\.//g; + } + + $componentname = $componentname . $addon; + + if (( $styles =~ /\bLANGUAGEPACK\b/ ) && ( $installer::globals::languagepack )) { $componentname = $componentname . "_lang"; } + elsif (( $styles =~ /\bHELPPACK\b/ ) && ( $installer::globals::helppack )) { $componentname = $componentname . "_help"; } + if ( $styles =~ /\bALWAYS_REQUIRED\b/ ) { $componentname = $componentname . "_forced"; } + + # Attention: Maximum length for the componentname is 72 + # %installer::globals::allregistrycomponents_in_this_database_ : resetted for each database + # %installer::globals::allregistrycomponents_ : not resetted for each database + # Component strings must be unique for the complete product, because they are used for + # the creation of the globally unique identifier. + + my $fullname = $componentname; # This can be longer than 72 + + if (( exists($installer::globals::allregistrycomponents_{$fullname}) ) && ( ! exists($installer::globals::allregistrycomponents_in_this_database_{$fullname}) )) + { + # This is not allowed: One component cannot be installed with different packages. + installer::exiter::exit_program("ERROR: Windows registry component \"$fullname\" is already included into another package. This is not allowed.", "get_registry_component_name"); + } + + if ( exists($installer::globals::allregistrycomponents_{$fullname}) ) + { + $componentname = $installer::globals::allregistrycomponents_{$fullname}; + } + else + { + if ( length($componentname) > 70 ) + { + $componentname = generate_new_short_registrycomponentname($componentname); # This has to be unique for the complete product, not only one package + } + + $installer::globals::allregistrycomponents_{$fullname} = $componentname; + $installer::globals::allregistrycomponents_in_this_database_{$fullname} = 1; + } + + if ( $isrootmodule ) { $installer::globals::registryrootcomponent = $componentname; } + + return $componentname; +} + +######################################################### +# Create a shorter version of a long component name, +# because maximum length in msi database is 72. +# Attention: In multi msi installation sets, the short +# names have to be unique over all packages, because +# this string is used to create the globally unique id +# -> no resetting of +# %installer::globals::allshortregistrycomponents +# after a package was created. +######################################################### + +sub generate_new_short_registrycomponentname +{ + my ($componentname) = @_; + + my $startversion = substr($componentname, 0, 60); # taking only the first 60 characters + my $subid = installer::windows::msiglobal::calculate_id($componentname, 9); # taking only the first 9 digits + my $shortcomponentname = $startversion . "_" . $subid; + + if ( exists($installer::globals::allshortregistrycomponents{$shortcomponentname}) ) { installer::exiter::exit_program("Failed to create unique component name: \"$shortcomponentname\"", "generate_new_short_registrycomponentname"); } + + $installer::globals::allshortregistrycomponents{$shortcomponentname} = 1; + + return $shortcomponentname; +} + +############################################################## +# Returning identifier for registry table. +############################################################## + +sub get_registry_identifier +{ + my ($registry) = @_; + + my $identifier = ""; + + if ( $registry->{'gid'} ) { $identifier = $registry->{'gid'}; } + + $identifier = lc($identifier); # always lower case + + # Attention: Maximum length is 72 + + $identifier =~ s/gid_regitem_/g_r_/; + $identifier =~ s/_soffice_/_s_/; + $identifier =~ s/_clsid_/_c_/; + $identifier =~ s/_currentversion_/_cv_/; + $identifier =~ s/_microsoft_/_ms_/; + $identifier =~ s/_manufacturer_/_mf_/; + $identifier =~ s/_productname_/_pn_/; + $identifier =~ s/_productversion_/_pv_/; + $identifier =~ s/_staroffice_/_so_/; + $identifier =~ s/_software_/_sw_/; + $identifier =~ s/_capabilities_/_cap_/; + $identifier =~ s/_classpath_/_cp_/; + $identifier =~ s/_extension_/_ex_/; + $identifier =~ s/_fileassociations_/_fa_/; + $identifier =~ s/_propertysheethandlers_/_psh_/; + $identifier =~ s/__/_/g; + + # Saving this in the registry collector + + $registry->{'uniquename'} = $identifier; + + return $identifier; +} + +################################################################## +# Returning root value for registry table. +################################################################## + +sub get_registry_root +{ + my ($registry) = @_; + + my $rootvalue = 0; # Default: Parent is KKEY_CLASSES_ROOT + my $scproot = ""; + + if ( $registry->{'ParentID'} ) { $scproot = $registry->{'ParentID'}; } + + if ( $scproot eq "PREDEFINED_HKEY_LOCAL_MACHINE" ) { $rootvalue = -1; } + + if ( $scproot eq "PREDEFINED_HKEY_CLASSES_ROOT" ) { $rootvalue = 0; } + + if ( $scproot eq "PREDEFINED_HKEY_CURRENT_USER_ONLY" ) { $rootvalue = 1; } + + if ( $scproot eq "PREDEFINED_HKEY_LOCAL_MACHINE_ONLY" ) { $rootvalue = 2; } + + return $rootvalue; +} + +############################################################## +# Returning key for registry table. +############################################################## + +sub get_registry_key +{ + my ($registry, $allvariableshashref) = @_; + + my $key = ""; + + if ( $registry->{'Subkey'} ) { $key = $registry->{'Subkey'}; } + + if ( $key =~ /\%/ ) { $key = installer::worker::replace_variables_in_string($key, $allvariableshashref); } + + return $key; +} + +############################################################## +# Returning name for registry table. +############################################################## + +sub get_registry_name +{ + my ($registry, $allvariableshashref) = @_; + + my $name = ""; + + if ( $registry->{'Name'} ) { $name = $registry->{'Name'}; } + + if ( $name =~ /\%/ ) { $name = installer::worker::replace_variables_in_string($name, $allvariableshashref); } + + return $name; +} + +############################################################## +# Returning value for registry table. +############################################################## + +sub get_registry_value +{ + my ($registry, $allvariableshashref) = @_; + + my $value = ""; + + if ( $registry->{'Value'} ) { $value = $registry->{'Value'}; } + + $value =~ s/\\\"/\"/g; # no more masquerading of '"' + $value =~ s/\\\\\s*$/\\/g; # making "\\" at end of value to "\" + $value =~ s/\/\[INSTALLLOCATION\]/; + $value =~ s/\[INSTALLLOCATION\]\\/\[INSTALLLOCATION\]/; # removing "\" after "[INSTALLLOCATION]" + + if ( $value =~ /\%/ ) { $value = installer::worker::replace_variables_in_string($value, $allvariableshashref); } + + return $value; +} + +############################################################## +# Returning component for registry table. +############################################################## + +sub get_registry_component +{ + my ($registry, $allvariables) = @_; + + # All registry items belonging to one module can + # be included into one component + + my $componentname = get_registry_component_name($registry, $allvariables); + + # saving componentname in the registryitem collector + + $registry->{'componentname'} = $componentname; + + return $componentname; +} + +###################################################### +# Adding the content of +# @installer::globals::userregistrycollector +# to the registry table. The content was collected +# in create_files_table() in file.pm. +###################################################### + +sub add_userregs_to_registry_table +{ + my ( $registrytable, $allvariables ) = @_; + + for ( my $i = 0; $i <= $#installer::globals::userregistrycollector; $i++ ) + { + my $onefile = $installer::globals::userregistrycollector[$i]; + + my %registry = (); + + $registry{'Registry'} = $onefile->{'userregkeypath'}; + $registry{'Root'} = "1"; # always HKCU + $registry{'Key'} = "Software\\$allvariables->{'MANUFACTURER'}\\$allvariables->{'PRODUCTNAME'} $allvariables->{'PRODUCTVERSION'}\\"; + if ( $onefile->{'needs_user_registry_key'} ) { $registry{'Key'} = $registry{'Key'} . "StartMenu"; } + $registry{'Name'} = $onefile->{'Name'}; + $registry{'Value'} = "1"; + $registry{'Component_'} = $onefile->{'componentname'}; + + my $oneline = $registry{'Registry'} . "\t" . $registry{'Root'} . "\t" . $registry{'Key'} . "\t" + . $registry{'Name'} . "\t" . $registry{'Value'} . "\t" . $registry{'Component_'} . "\n"; + + push(@{$registrytable}, $oneline); + } +} + +###################################################### +# Creating the file Registry.idt dynamically +# Content: +# Registry Root Key Name Value Component_ +###################################################### + +sub create_registry_table +{ + my ($registryref, $allregistrycomponentsref, $basedir, $languagesarrayref, $allvariableshashref) = @_; + + for ( my $m = 0; $m <= $#{$languagesarrayref}; $m++ ) + { + my $onelanguage = ${$languagesarrayref}[$m]; + + my @registrytable = (); + + installer::windows::idtglobal::write_idt_header(\@registrytable, "registry"); + + for ( my $i = 0; $i <= $#{$registryref}; $i++ ) + { + my $oneregistry = ${$registryref}[$i]; + + # Controlling the language! + # Only language independent folderitems or folderitems with the correct language + # will be included into the table + + if (! (!(( $oneregistry->{'ismultilingual'} )) || ( $oneregistry->{'specificlanguage'} eq $onelanguage )) ) { next; } + + my %registry = (); + + $registry{'Registry'} = get_registry_identifier($oneregistry); + $registry{'Root'} = get_registry_root($oneregistry); + $registry{'Key'} = get_registry_key($oneregistry, $allvariableshashref); + $registry{'Name'} = get_registry_name($oneregistry, $allvariableshashref); + $registry{'Value'} = get_registry_value($oneregistry, $allvariableshashref); + $registry{'Component_'} = get_registry_component($oneregistry, $allvariableshashref); + + # Collecting all components + if (! grep {$_ eq $registry{'Component_'}} @{$allregistrycomponentsref}) + { + push(@{$allregistrycomponentsref}, $registry{'Component_'}); + } + + my $style = ""; + if ( $oneregistry->{'Styles'} ) { $style = $oneregistry->{'Styles'}; } + # Collecting all registry components with ALWAYS_REQUIRED style + if ( ! ( $style =~ /\bALWAYS_REQUIRED\b/ )) + { + # Setting a component condition for unforced registry components! + # Only write into registry, if WRITE_REGISTRY is set. + if ( $oneregistry->{'ComponentCondition'} ) { $oneregistry->{'ComponentCondition'} = "(" . $oneregistry->{'ComponentCondition'} . ") AND (WRITE_REGISTRY=1)"; } + else { $oneregistry->{'ComponentCondition'} = "WRITE_REGISTRY=1"; } + } + + # Collecting all component conditions + if ( $oneregistry->{'ComponentCondition'} ) + { + if ( ! exists($installer::globals::componentcondition{$registry{'Component_'}})) + { + $installer::globals::componentcondition{$registry{'Component_'}} = $oneregistry->{'ComponentCondition'}; + } + } + + my $oneline = $registry{'Registry'} . "\t" . $registry{'Root'} . "\t" . $registry{'Key'} . "\t" + . $registry{'Name'} . "\t" . $registry{'Value'} . "\t" . $registry{'Component_'} . "\n"; + + push(@registrytable, $oneline); + } + + # If there are added user registry keys for files collected in + # @installer::globals::userregistrycollector (file.pm), then + # this registry keys have to be added now. + + if ( $installer::globals::addeduserregitrykeys ) { add_userregs_to_registry_table(\@registrytable, $allvariableshashref); } + + # Saving the file + + my $registrytablename = $basedir . $installer::globals::separator . "Registry.idt" . "." . $onelanguage; + installer::files::save_file($registrytablename ,\@registrytable); + my $infoline = "Created idt file: $registrytablename\n"; + push(@installer::globals::logfileinfo, $infoline); + } +} + +1; diff --git a/solenv/bin/modules/installer/windows/removefile.pm b/solenv/bin/modules/installer/windows/removefile.pm new file mode 100644 index 000000000..21197f694 --- /dev/null +++ b/solenv/bin/modules/installer/windows/removefile.pm @@ -0,0 +1,141 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::removefile; + +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +######################################################################## +# Returning the FileKey for a folderitem for removefile table. +######################################################################## + +sub get_removefile_filekey +{ + my ($folderitem) = @_; + + # returning the unique identifier + + my $identifier = "remove_" . $folderitem->{'directory'}; + + $identifier = lc($identifier); + + return $identifier; +} + +######################################################################## +# Returning the Component for a folderitem for removefile table. +######################################################################## + +sub get_removefile_component +{ + my ($folderitem) = @_; + + return $folderitem->{'component'}; +} + +######################################################################## +# Returning the FileName for a folderitem for removefile table. +######################################################################## + +sub get_removefile_filename +{ + my ($folderitem) = @_; + + # return nothing: The assigned directory will be removed + + return ""; +} + +######################################################################## +# Returning the DirProperty for a folderitem for removefile table. +######################################################################## + +sub get_removefile_dirproperty +{ + my ($folderitem) = @_; + + return $folderitem->{'directory'}; +} + +######################################################################## +# Returning the InstallMode for a folderitem for removefile table. +######################################################################## + +sub get_removefile_installmode +{ + my ($folderitem) = @_; + + # always returning "2": The file is only removed, if the assigned + # component is removed. Name: msidbRemoveFileInstallModeOnRemove + + return 2; +} + +########################################################################################################### +# Creating the file RemoveFi.idt dynamically +# Content: +# FileKey Component_ FileName DirProperty InstallMode +########################################################################################################### + +sub create_removefile_table +{ + my ($folderitemsref, $basedir) = @_; + + # Only the directories created for the FolderItems have to be deleted + # with the information in the table RemoveFile + + my @directorycollector = (); + + for ( my $i = 0; $i <= $#{$folderitemsref}; $i++ ) + { + my $onelink = ${$folderitemsref}[$i]; + + if ( $onelink->{'used'} == 0 ) { next; } + + next if grep {$_ eq $onelink->{'directory'}} @directorycollector; + + push(@directorycollector, $onelink->{'directory'}); + + my %removefile = (); + + $removefile{'FileKey'} = get_removefile_filekey($onelink); + $removefile{'Component_'} = get_removefile_component($onelink); + $removefile{'FileName'} = get_removefile_filename($onelink); + $removefile{'DirProperty'} = get_removefile_dirproperty($onelink); + # fdo#44565 do not remove empty Desktop folder + if ( $removefile{'DirProperty'} eq $installer::globals::desktopfolder ) { next; } + $removefile{'InstallMode'} = get_removefile_installmode($onelink); + + my $oneline = $removefile{'FileKey'} . "\t" . $removefile{'Component_'} . "\t" . $removefile{'FileName'} . "\t" + . $removefile{'DirProperty'} . "\t" . $removefile{'InstallMode'} . "\n"; + + push(@installer::globals::removefiletable, $oneline); + } + + # Saving the file + + my $removefiletablename = $basedir . $installer::globals::separator . "RemoveFi.idt"; + installer::files::save_file($removefiletablename ,\@installer::globals::removefiletable); + my $infoline = "Created idt file: $removefiletablename\n"; + push(@installer::globals::logfileinfo, $infoline); + +} + +1; diff --git a/solenv/bin/modules/installer/windows/shortcut.pm b/solenv/bin/modules/installer/windows/shortcut.pm new file mode 100644 index 000000000..c3469085c --- /dev/null +++ b/solenv/bin/modules/installer/windows/shortcut.pm @@ -0,0 +1,659 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::shortcut; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +############################################################## +# Returning identifier for shortcut table. +############################################################## + +sub get_shortcut_identifier +{ + my ($shortcut) = @_; + + my $identifier = $shortcut->{'gid'}; + + return $identifier; +} + +############################################################## +# Returning directory for shortcut table. +############################################################## + +sub get_shortcut_directory +{ + my ($shortcut, $dirref) = @_; + + # For shortcuts it is easy to convert the gid_Dir_Abc into the unique name in + # the directory table, for instance help_en_simpressidx. + # For files (components) this is not so easy, because files can be included + # in zip files with subdirectories that are not defined in scp. + + my $onedir; + my $shortcutdirectory = $shortcut->{'Dir'}; + my $directory = ""; + my $found = 0; + + for ( my $i = 0; $i <= $#{$dirref}; $i++ ) + { + $onedir = ${$dirref}[$i]; + my $directorygid = $onedir->{'Dir'}; + + if ( $directorygid eq $shortcutdirectory ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find DirectoryID $shortcutdirectory in directory collection for shortcut", "get_shortcut_directory"); + } + + $directory = $onedir->{'uniquename'}; + + if ($directory eq "") { $directory = "INSTALLLOCATION"; } # Shortcuts in the root directory + + return $directory; +} + +############################################################## +# Returning name for shortcut table. +############################################################## + +sub get_shortcut_name +{ + my ($shortcut, $shortnamesref, $onelanguage) = @_; + + my $returnstring; + + my $name = $shortcut->{'Name'}; + + my $shortstring = installer::windows::idtglobal::make_eight_three_conform($name, "shortcut", $shortnamesref); + $shortstring =~ s/\s/\_/g; # replacing white spaces with underline + + if ( $shortstring eq $name ) { $returnstring = $name; } # nothing changed + else {$returnstring = $shortstring . "\|" . $name; } + + return $returnstring; +} + +############################################################## +# Returning component for shortcut table. +############################################################## + +sub get_shortcut_component +{ + my ($shortcut, $filesref) = @_; + + my $onefile; + my $component = ""; + my $found = 0; + my $shortcut_fileid = $shortcut->{'FileID'}; + + my $absolute_filename = 0; + if ( $shortcut->{'Styles'} ) { $styles = $shortcut->{'Styles'}; } + if ( $styles =~ /\bABSOLUTE_FILENAME\b/ ) { $absolute_filename = 1; } # FileID contains an absolute filename + if ( $styles =~ /\bUSE_HELPER_FILENAME\b/ ) { $absolute_filename = 1; } # ComponentIDFile contains id of a helper file + + # if the FileID contains an absolute filename, therefore the entry for "ComponentIDFile" has to be used. + if ( $absolute_filename ) { $shortcut_fileid = $shortcut->{'ComponentIDFile'}; } + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $shortcut_fileid ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find FileID $shortcut_fileid in file collection for shortcut", "get_shortcut_component"); + } + + $component = $onefile->{'componentname'}; + + # finally saving the componentname in the folderitem collector + + $shortcut->{'component'} = $component; + + return $component; +} + +############################################################## +# Returning target for shortcut table. +############################################################## + +sub get_shortcut_target +{ + my ($shortcut, $filesref) = @_; + + my $target = ""; + my $found = 0; + my $shortcut_fileid = $shortcut->{'FileID'}; + my $onefile; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $shortcut_fileid ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find FileID $shortcut_fileid in file collection for shortcut", "get_shortcut_target"); + } + + if ( $onefile->{'Name'} ) + { + $target = $onefile->{'Name'}; + } + + $target = "\[\#" . $target . "\]"; # format for Non-Advertised shortcuts + + return $target; +} + +############################################################## +# Returning arguments for shortcut table. +############################################################## + +sub get_shortcut_arguments +{ + my ($shortcut) = @_; + + return ""; +} + +############################################################## +# Returning the localized description for shortcut table. +############################################################## + +sub get_shortcut_description +{ + my ($shortcut, $onelanguage) = @_; + + my $description = ""; + if ( $shortcut->{'Tooltip'} ) { $description = $shortcut->{'Tooltip'}; } + $description =~ s/\\\"/\"/g; # no more masquerading of '"' + + return $description; +} + +############################################################## +# Returning hotkey for shortcut table. +############################################################## + +sub get_shortcut_hotkey +{ + my ($shortcut) = @_; + + return ""; +} + +############################################################## +# Returning icon for shortcut table. +############################################################## + +sub get_shortcut_icon +{ + my ($shortcut) = @_; + + return ""; +} + +############################################################## +# Returning iconindex for shortcut table. +############################################################## + +sub get_shortcut_iconindex +{ + my ($shortcut) = @_; + + return ""; +} + +############################################################## +# Returning show command for shortcut table. +############################################################## + +sub get_shortcut_showcmd +{ + my ($shortcut) = @_; + + return ""; +} + +############################################################## +# Returning working directory for shortcut table. +############################################################## + +sub get_shortcut_wkdir +{ + my ($shortcut) = @_; + + return ""; +} + +#################################################################### +# Returning working directory for shortcut table for FolderItems. +#################################################################### + +sub get_folderitem_wkdir +{ + my ($onelink, $dirref) = @_; + + # For shortcuts it is easy to convert the gid_Dir_Abc into the unique name in + # the directory table, for instance help_en_simpressidx. + + my $onedir; + my $workingdirectory = ""; + if ( $onelink->{'WkDir'} ) { $workingdirectory = $onelink->{'WkDir'}; } + my $directory = ""; + + if ( $workingdirectory ) + { + my $found = 0; + + for ( my $i = 0; $i <= $#{$dirref}; $i++ ) + { + $onedir = ${$dirref}[$i]; + my $directorygid = $onedir->{'Dir'}; + + if ( $directorygid eq $workingdirectory ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find DirectoryID $workingdirectory in directory collection for FolderItem", "get_folderitem_wkdir"); + } + + $directory = $onedir->{'uniquename'}; + + if ($directory eq "") { $directory = "INSTALLLOCATION"; } + } + + return $directory; +} + +################################################################### +# Returning the directory for a folderitem for shortcut table. +################################################################### + +sub get_folderitem_directory +{ + my ($shortcut) = @_; + + my $directory = "$installer::globals::officemenufolder"; # default + + # The default is not correct for the + # PREDEFINED folders, like PREDEFINED_AUTOSTART + + if ( $shortcut->{'FolderID'} eq "PREDEFINED_AUTOSTART" ) + { + $directory = $installer::globals::startupfolder; + } + + if ( $shortcut->{'FolderID'} eq "PREDEFINED_DESKTOP" ) + { + $directory = $installer::globals::desktopfolder; + $installer::globals::desktoplinkexists = 1; + } + + if ( $shortcut->{'FolderID'} eq "PREDEFINED_STARTMENU" ) + { + $directory = $installer::globals::programmenufolder; + } + + # saving the directory in the folderitems collector + + $shortcut->{'directory'} = $directory; + + return $directory; +} + +######################################################################## +# Returning the target (feature) for a folderitem for shortcut table. +# For non-advertised shortcuts this is a formatted string. +######################################################################## + +sub get_folderitem_target +{ + my ($shortcut, $filesref) = @_; + + my $onefile; + my $target = ""; + my $found = 0; + my $shortcut_fileid = $shortcut->{'FileID'}; + + my $styles = ""; + my $nonadvertised = 0; + my $absolute_filename = 0; + if ( $shortcut->{'Styles'} ) { $styles = $shortcut->{'Styles'}; } + if ( $styles =~ /\bNON_ADVERTISED\b/ ) { $nonadvertised = 1; } # this is a non-advertised shortcut + if ( $styles =~ /\bABSOLUTE_FILENAME\b/ ) { $absolute_filename = 1; } # FileID contains an absolute filename + + # if the FileID contains an absolute filename this can simply be returned as target for the shortcut table. + if ( $absolute_filename ) + { + $shortcut->{'target'} = $shortcut_fileid; + return $shortcut_fileid; + } + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $shortcut_fileid ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find FileID $shortcut_fileid in file collection for folderitem", "get_folderitem_target"); + } + + # Non advertised shortcuts do not return the feature, but the path to the file + if ( $nonadvertised ) + { + $target = "\[" . $onefile->{'uniquedirname'} . "\]" . "\\" . $onefile->{'Name'}; + $shortcut->{'target'} = $target; + return $target; + } + + # the rest only for advertised shortcuts, which contain the feature in the shortcut table. + + if ( $onefile->{'modules'} ) { $target = $onefile->{'modules'}; } + + # If modules contains a list of modules, only taking the first one. + # But this should never be needed + + if ( $target =~ /^\s*(.*?)\,/ ) { $target = $1; } + + # Attention: Maximum feature length is 38! + installer::windows::idtglobal::shorten_feature_gid(\$target); + + # and finally saving the target in the folderitems collector + + $shortcut->{'target'} = $target; + + return $target; +} + +######################################################################## +# Returning the arguments for a folderitem for shortcut table. +######################################################################## + +sub get_folderitem_arguments +{ + my ($shortcut) = @_; + + my $parameter = ""; + + if ( $shortcut->{'Parameter'} ) { $parameter = $shortcut->{'Parameter'}; } + + return $parameter; +} + +######################################################################## +# Returning the icon for a folderitem for shortcut table. +# The returned value has to be defined in the icon table. +######################################################################## + +sub get_folderitem_icon +{ + my ($shortcut, $filesref, $iconfilecollector) = @_; + + my $styles = ""; + if ( $shortcut->{'Styles'} ) { $styles = $shortcut->{'Styles'}; } + if ( $styles =~ /\bNON_ADVERTISED\b/ ) { return ""; } # no icon for non-advertised shortcuts + + my $iconfilegid = ""; + + if ( $shortcut->{'IconFile'} ) { $iconfilegid = $shortcut->{'IconFile'}; } + else { $iconfilegid = $shortcut->{'FileID'}; } + + my $onefile; + my $found = 0; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $iconfilegid ) + { + $found = 1; + last; + } + } + + if (!($found)) + { + installer::exiter::exit_program("ERROR: Did not find FileID $iconfilegid in file collection", "get_folderitem_icon"); + } + + $iconfile = $onefile->{'Name'}; + + # collecting all icon files to copy them into the icon directory + + my $sourcepath = $onefile->{'sourcepath'}; + + if (! grep {$_ eq $sourcepath} @{$iconfilecollector}) + { + push(@{$iconfilecollector}, $sourcepath); + } + + return $iconfile; +} + +######################################################################## +# Returning the iconindex for a folderitem for shortcut table. +######################################################################## + +sub get_folderitem_iconindex +{ + my ($shortcut) = @_; + + my $styles = ""; + if ( $shortcut->{'Styles'} ) { $styles = $shortcut->{'Styles'}; } + if ( $styles =~ /\bNON_ADVERTISED\b/ ) { return ""; } # no iconindex for non-advertised shortcuts + + my $iconid = 0; + + if ( $shortcut->{'IconID'} ) { $iconid = $shortcut->{'IconID'}; } + + return $iconid; +} + +######################################################################## +# Returning the show command for a folderitem for shortcut table. +######################################################################## + +sub get_folderitem_showcmd +{ + my ($shortcut) = @_; + + return "1"; +} + +########################################################################################################### +# Creating the file Shortcut.idt dynamically +# Content: +# Shortcut Directory_ Name Component_ Target Arguments Description Hotkey Icon_ IconIndex ShowCmd WkDir +########################################################################################################### + +sub create_shortcut_table +{ + my ($filesref, $linksref, $folderref, $folderitemsref, $dirref, $basedir, $languagesarrayref, $includepatharrayref, $iconfilecollector) = @_; + + for ( my $m = 0; $m <= $#{$languagesarrayref}; $m++ ) + { + my $onelanguage = ${$languagesarrayref}[$m]; + + my @shortcuttable = (); + + my @shortnames = (); # to collect all short names + + installer::windows::idtglobal::write_idt_header(\@shortcuttable, "shortcut"); + + # First the links, defined in scp as ShortCut + + for ( my $i = 0; $i <= $#{$linksref}; $i++ ) + { + my $onelink = ${$linksref}[$i]; + + # Controlling the language! + # Only language independent folderitems or folderitems with the correct language + # will be included into the table + + if (! (!(( $onelink->{'ismultilingual'} )) || ( $onelink->{'specificlanguage'} eq $onelanguage )) ) { next; } + + my %shortcut = (); + + $shortcut{'Shortcut'} = get_shortcut_identifier($onelink); + $shortcut{'Directory_'} = get_shortcut_directory($onelink, $dirref); + $shortcut{'Name'} = get_shortcut_name($onelink, \@shortnames, $onelanguage); # localized name + $shortcut{'Component_'} = get_shortcut_component($onelink, $filesref); + $shortcut{'Target'} = get_shortcut_target($onelink, $filesref); + $shortcut{'Arguments'} = get_shortcut_arguments($onelink); + $shortcut{'Description'} = get_shortcut_description($onelink, $onelanguage); # localized description + $shortcut{'Hotkey'} = get_shortcut_hotkey($onelink); + $shortcut{'Icon_'} = get_shortcut_icon($onelink); + $shortcut{'IconIndex'} = get_shortcut_iconindex($onelink); + $shortcut{'ShowCmd'} = get_shortcut_showcmd($onelink); + $shortcut{'WkDir'} = get_shortcut_wkdir($onelink); + + my $oneline = $shortcut{'Shortcut'} . "\t" . $shortcut{'Directory_'} . "\t" . $shortcut{'Name'} . "\t" + . $shortcut{'Component_'} . "\t" . $shortcut{'Target'} . "\t" . $shortcut{'Arguments'} . "\t" + . $shortcut{'Description'} . "\t" . $shortcut{'Hotkey'} . "\t" . $shortcut{'Icon_'} . "\t" + . $shortcut{'IconIndex'} . "\t" . $shortcut{'ShowCmd'} . "\t" . $shortcut{'WkDir'} . "\n"; + + push(@shortcuttable, $oneline); + } + + # Second the entries into the start menu, defined in scp as Folder and Folderitem + # These shortcuts will fill the icons table. + + for ( my $i = 0; $i <= $#{$folderref}; $i++ ) + { + my $foldergid = ${$folderref}[$i]->{'gid'}; + + # iterating over all folderitems for this folder + + for ( my $j = 0; $j <= $#{$folderitemsref}; $j++ ) + { + my $onelink = ${$folderitemsref}[$j]; + + # Controlling the language! + # Only language independent folderitems or folderitems with the correct language + # will be included into the table + + if (! (!(( $onelink->{'ismultilingual'} )) || ( $onelink->{'specificlanguage'} eq $onelanguage )) ) { next; } + + # controlling the folder + + my $localused = 0; + + if ( $onelink->{'used'} ) { $localused = $onelink->{'used'}; } + + if (!($localused == 1)) { $onelink->{'used'} = "0"; } # no resetting + + if (!( $onelink->{'FolderID'} eq $foldergid )) { next; } + + $onelink->{'used'} = "1"; + + my %shortcut = (); + + $shortcut{'Shortcut'} = get_shortcut_identifier($onelink); + $shortcut{'Directory_'} = get_folderitem_directory($onelink); + $shortcut{'Name'} = get_shortcut_name($onelink, \@shortnames, $onelanguage); # localized name + $shortcut{'Component_'} = get_shortcut_component($onelink, $filesref); + $shortcut{'Target'} = get_folderitem_target($onelink, $filesref); + $shortcut{'Arguments'} = get_folderitem_arguments($onelink); + $shortcut{'Description'} = get_shortcut_description($onelink, $onelanguage); # localized description + $shortcut{'Hotkey'} = get_shortcut_hotkey($onelink); + $shortcut{'Icon_'} = get_folderitem_icon($onelink, $filesref, $iconfilecollector); + $shortcut{'IconIndex'} = get_folderitem_iconindex($onelink); + $shortcut{'ShowCmd'} = get_folderitem_showcmd($onelink); + $shortcut{'WkDir'} = get_folderitem_wkdir($onelink, $dirref); + + my $oneline = $shortcut{'Shortcut'} . "\t" . $shortcut{'Directory_'} . "\t" . $shortcut{'Name'} . "\t" + . $shortcut{'Component_'} . "\t" . $shortcut{'Target'} . "\t" . $shortcut{'Arguments'} . "\t" + . $shortcut{'Description'} . "\t" . $shortcut{'Hotkey'} . "\t" . $shortcut{'Icon_'} . "\t" + . $shortcut{'IconIndex'} . "\t" . $shortcut{'ShowCmd'} . "\t" . $shortcut{'WkDir'} . "\n"; + + push(@shortcuttable, $oneline); + } + } + + # The soffice.ico has to be included into the icon table + # as icon for the ARP applet + + my $onefile = ""; + my $sofficefile = "soffice.ico"; + + my $sourcepathref = $ENV{'SRCDIR'} . "/sysui/desktop/icons/" . $sofficefile; + + if (! -f $sourcepathref) { installer::exiter::exit_program("ERROR: Could not find $sofficefile ($sourcepathref) as icon!", "create_shortcut_table"); } + + if (! grep {$_ eq $sourcepathref} @{$iconfilecollector}) + { + unshift(@{$iconfilecollector}, $sourcepathref); + $installer::globals::sofficeiconadded = 1; + } + + my $localinfoline = "Added icon file $sourcepathref for language pack into icon file collector.\n"; + push(@installer::globals::logfileinfo, $localinfoline); + + # Saving the file + + my $shortcuttablename = $basedir . $installer::globals::separator . "Shortcut.idt" . "." . $onelanguage; + installer::files::save_file($shortcuttablename ,\@shortcuttable); + my $infoline = "Created idt file: $shortcuttablename\n"; + push(@installer::globals::logfileinfo, $infoline); + } +} + + +1; diff --git a/solenv/bin/modules/installer/windows/strip.pm b/solenv/bin/modules/installer/windows/strip.pm new file mode 100644 index 000000000..d7f95499d --- /dev/null +++ b/solenv/bin/modules/installer/windows/strip.pm @@ -0,0 +1,149 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::strip; + +use File::Temp qw(tmpnam); +use installer::converter; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::systemactions; + +##################################################################### +# Checking whether a file has to be stripped +##################################################################### + +sub need_to_strip +{ + my ( $filename ) = @_; + + my $strip = 0; + + # Check using the "nm" command + + $filename =~ s/\\/\\\\/g; + + open (FILE, "nm $filename 2>&1 |"); + my $nmoutput = ; + close (FILE); + + if ( $nmoutput && !( $nmoutput =~ /no symbols/i || $nmoutput =~ /not recognized/i )) { $strip = 1; } + + return $strip +} + +##################################################################### +# Checking whether a file has to be stripped +##################################################################### + +sub do_strip +{ + my ( $filename ) = @_; + + my $systemcall = "strip" . " " . $filename; + + my $returnvalue = system($systemcall); + + my $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not strip $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "SUCCESS: Stripped library $filename!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +##################################################################### +# Resolving all variables in the packagename. +##################################################################### + +sub strip_binaries +{ + my ( $filelist, $languagestringref ) = @_; + + installer::logger::include_header_into_logfile("Stripping files:"); + + my $strippeddirbase = installer::systemactions::create_directories("stripped", $languagestringref); + + if (! grep {$_ eq $strippeddirbase} @installer::globals::removedirs) + { + push(@installer::globals::removedirs, $strippeddirbase); + } + + my ($tmpfilehandle, $tmpfilename) = tmpnam(); + open SOURCEPATHLIST, ">$tmpfilename" or die "oops...\n"; + for ( my $i = 0; $i <= $#{$filelist}; $i++ ) + { + print SOURCEPATHLIST "${$filelist}[$i]->{'sourcepath'}\n"; + } + close SOURCEPATHLIST; + my @filetypelist = qx{file -f "$tmpfilename"}; + chomp @filetypelist; + unlink "$tmpfilename" or die "oops\n"; + for ( my $i = 0; $i <= $#{$filelist}; $i++ ) + { + ${$filelist}[$i]->{'is_executable'} = ( $filetypelist[$i] =~ /:.*PE executable/ ); + } + + if ( $^O =~ /cygwin/i ) { installer::worker::generate_cygwin_paths($filelist); } + + for ( my $i = 0; $i <= $#{$filelist}; $i++ ) + { + my $sourcefilename = ${$filelist}[$i]->{'cyg_sourcepath'}; + + if ( ${$filelist}[$i]->{'is_executable'} && need_to_strip($sourcefilename) ) + { + my $shortfilename = $sourcefilename; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$shortfilename); + + $infoline = "Strip: $shortfilename\n"; + push( @installer::globals::logfileinfo, $infoline); + + # copy file into directory for stripped libraries + + my $onelanguage = ${$filelist}[$i]->{'specificlanguage'}; + + # files without language into directory "00" + + if ($onelanguage eq "") { $onelanguage = "00"; } + + my $strippeddir = $strippeddirbase . $installer::globals::separator . $onelanguage; + installer::systemactions::create_directory($strippeddir); # creating language specific subdirectories + + my $destfilename = $strippeddir . $installer::globals::separator . $shortfilename; + installer::systemactions::copy_one_file($sourcefilename, $destfilename); + + # change sourcepath in files collector + + ${$filelist}[$i]->{'sourcepath'} = $destfilename; + + # strip file + + do_strip($destfilename); + } + } +} + +1; diff --git a/solenv/bin/modules/installer/windows/update.pm b/solenv/bin/modules/installer/windows/update.pm new file mode 100644 index 000000000..45c47ed7a --- /dev/null +++ b/solenv/bin/modules/installer/windows/update.pm @@ -0,0 +1,620 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::update; + +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::pathanalyzer; +use installer::systemactions; + +################################################################################# +# Extracting all tables from an msi database +################################################################################# + +sub extract_all_tables_from_msidatabase +{ + my ($fulldatabasepath, $workdir) = @_; + + my $msidb = "msidb.exe"; # Has to be in the path + my $infoline = ""; + my $systemcall = ""; + my $returnvalue = ""; + my $extraslash = ""; # Has to be set for non-ActiveState perl + + # Export of all tables by using "*" + + if ( $^O =~ /cygwin/i ) { + # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) + $fulldatabasepath =~ s/\//\\\\/g; + $workdir =~ s/\//\\\\/g; + $extraslash = "\\"; + } + if ( $^O =~ /linux/i) { + $extraslash = "\\"; + } + + $systemcall = $msidb . " -d " . $fulldatabasepath . " -f " . $workdir . " -e " . $extraslash . "*"; + $returnvalue = system($systemcall); + + $infoline = "Systemcall: $systemcall\n"; + push( @installer::globals::logfileinfo, $infoline); + + if ($returnvalue) + { + $infoline = "ERROR: Could not execute $systemcall !\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $fulldatabasepath !", "extract_all_tables_from_msidatabase"); + } + else + { + $infoline = "Success: Executed $systemcall successfully!\n"; + push( @installer::globals::logfileinfo, $infoline); + } +} + +################################################################################# +# Collecting the keys from the first line of the idt file +################################################################################# + +sub collect_all_keys +{ + my ($line) = @_; + + my @allkeys = (); + my $rownumber = 0; + my $onekey = ""; + + while ( $line =~ /^\s*(\S+?)\t(.*)$/ ) + { + $onekey = $1; + $line = $2; + $rownumber++; + push(@allkeys, $onekey); + } + + # and the last key + + $onekey = $line; + $onekey =~ s/^\s*//g; + $onekey =~ s/\s*$//g; + + $rownumber++; + push(@allkeys, $onekey); + + return (\@allkeys, $rownumber); +} + +################################################################################# +# Analyzing the content of one line of an idt file +################################################################################# + +sub get_oneline_hash +{ + my ($line, $allkeys, $rownumber) = @_; + + my $counter = 0; + my %linehash = (); + + $line =~ s/^\s*//; + $line =~ s/\s*$//; + + my $value = ""; + my $onekey = ""; + + while ( $line =~ /^(.*?)\t(.*)$/ ) + { + $value = $1; + $line = $2; + $onekey = ${$allkeys}[$counter]; + $linehash{$onekey} = $value; + $counter++; + } + + # the last column + + $value = $line; + $onekey = ${$allkeys}[$counter]; + + $linehash{$onekey} = $value; + + return \%linehash; +} + +################################################################################# +# Analyzing the content of an idt file +################################################################################# + +sub analyze_idt_file +{ + my ($filecontent) = @_; + + my %table = (); + # keys are written in first line + my ($allkeys, $rownumber) = collect_all_keys(${$filecontent}[0]); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } + + my $onelinehash = get_oneline_hash(${$filecontent}[$i], $allkeys, $rownumber); + my $linekey = $i - 2; # ! : The linenumber is the unique key !? Always decrease by two, because of removed first three lines. + $table{$linekey} = $onelinehash; + } + + return \%table; +} + +################################################################################# +# Reading all idt files in a specified directory +################################################################################# + +sub read_all_tables_from_msidatabase +{ + my ($workdir) = @_; + + my %database = (); + + my $ext = "idt"; + + my $allidtfiles = installer::systemactions::find_file_with_file_extension($ext, $workdir); + + for ( my $i = 0; $i <= $#{$allidtfiles}; $i++ ) + { + my $onefilename = ${$allidtfiles}[$i]; + my $longonefilename = $workdir . $installer::globals::separator . $onefilename; + if ( ! -f $longonefilename ) { installer::exiter::exit_program("ERROR: Could not find idt file: $longonefilename!", "read_all_tables_from_msidatabase"); } + my $filecontent = installer::files::read_file($longonefilename); + my $idtcontent = analyze_idt_file($filecontent); + if ($onefilename eq "Directory.idt") { + collect_directories($filecontent); + } + my $key = $onefilename; + $key =~ s/\.idt\s*$//; + $database{$key} = $idtcontent; + } + + return \%database; +} + +################################################################################# +# Checking, if this is the correct database. +################################################################################# + +sub correct_database +{ + my ($product, $pro, $langs, $languagestringref) = @_; + + my $correct_database = 0; + + # Comparing $product with $installer::globals::product and + # $pro with $installer::globals::pro and + # $langs with $languagestringref + + my $product_is_good = 0; + + my $localproduct = $installer::globals::product; + if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; } + elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; } + + if ( $product eq $localproduct ) { $product_is_good = 1; } + + if ( $product_is_good ) + { + my $pro_is_good = 0; + + if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; } + + if ( $pro_is_good ) + { + my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ","); + my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_"); + + my $not_included = 0; + foreach my $onelang ( keys %{$langlisthash} ) + { + if ( ! exists($langstringhash->{$onelang}) ) + { + $not_included = 1; + last; + } + } + + if ( ! $not_included ) + { + foreach my $onelanguage ( keys %{$langstringhash} ) + { + if ( ! exists($langlisthash->{$onelanguage}) ) + { + $not_included = 1; + last; + } + } + + if ( ! $not_included ) { $correct_database = 1; } + } + } + } + + return $correct_database; +} + +################################################################################# +# Searching for the path to the reference database for this special product. +################################################################################# + +sub get_databasename_from_list +{ + my ($filecontent, $languagestringref, $filename) = @_; + + my $databasepath = ""; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + my $line = ${$filecontent}[$i]; + if ( $line =~ /^\s*$/ ) { next; } # empty line + if ( $line =~ /^\s*\#/ ) { next; } # comment line + + if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ ) + { + my $product = $1; + my $pro = $2; + my $langs = $3; + my $path = $4; + + if (( $pro ne "pro" ) && ( $pro ne "nonpro" )) { installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename. Only \"pro\" or \"nonpro\" allowed in column 1! Line: \"$line\"", "get_databasename_from_list"); } + + if ( correct_database($product, $pro, $langs, $languagestringref) ) + { + $databasepath = $path; + last; + } + } + else + { + installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_databasename_from_list"); + } + } + + return $databasepath; +} + +################################################################################# +# Reading an existing database completely +################################################################################# + +sub readdatabase +{ + my ($allvariables, $languagestringref, $includepatharrayref) = @_; + + my $database = ""; + my $infoline = ""; + + if ( ! $allvariables->{'UPDATE_DATABASE_LISTNAME'} ) { installer::exiter::exit_program("ERROR: If \"UPDATE_DATABASE\" is set, \"UPDATE_DATABASE_LISTNAME\" is required.", "Main"); } + my $listfilename = $allvariables->{'UPDATE_DATABASE_LISTNAME'}; + + # Searching the list in the include paths + my $listname = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$listfilename, $includepatharrayref, 1); + if ( $$listname eq "" ) { installer::exiter::exit_program("ERROR: List file not found: $listfilename !", "readdatabase"); } + my $completelistname = $$listname; + + # Reading list file + my $listfile = installer::files::read_file($completelistname); + + # Get name and path of reference database + my $databasename = get_databasename_from_list($listfile, $languagestringref, $completelistname); + + # If the correct database was not found, this is not necessarily an error. But in this case, this is not an update packaging process! + if (( $databasename ) && ( $databasename ne "" )) # This is an update packaging process! + { + $installer::globals::updatedatabase = 1; + installer::logger::print_message( "... update process, using database $databasename ...\n" ); + $infoline = "\nDatabase found in $completelistname: \"$databasename\"\n\n"; + # Saving in global variable + $installer::globals::updatedatabasepath = $databasename; + } + else + { + $infoline = "\nNo database found in $completelistname. This is no update process!\n\n"; + } + push( @installer::globals::logfileinfo, $infoline); + + if ( $installer::globals::updatedatabase ) + { + if ( ! -f $databasename ) { installer::exiter::exit_program("ERROR: Could not find reference database: $databasename!", "readdatabase"); } + + my $msifilename = $databasename; + installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename); + + installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase start"); + + # create directory for unpacking + my $databasedir = installer::systemactions::create_directories("database", $languagestringref); + + # copy database + my $fulldatabasepath = $databasedir . $installer::globals::separator . $msifilename; + installer::systemactions::copy_one_file($databasename, $fulldatabasepath); + + installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before extracting tables"); + + # extract all tables from database + extract_all_tables_from_msidatabase($fulldatabasepath, $databasedir); + + installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before reading tables"); + + # read all tables + $database = read_all_tables_from_msidatabase($databasedir); + + # Test output: + + # foreach my $key1 ( keys %{$database} ) + # { + # print "Test1: $key1\n"; + # foreach my $key2 ( keys %{$database->{$key1}} ) + # { + # print "\tTest2: $key2\n"; + # foreach my $key3 ( keys %{$database->{$key1}->{$key2}} ) + # { + # print "\t\tTest3: $key3: $database->{$key1}->{$key2}->{$key3}\n"; + # } + # } + # } + + # Example: File table + + # my $filetable = $database->{'File'}; + # foreach my $linenumber ( keys %{$filetable} ) + # { + # print "Test Filenumber: $linenumber\n"; + # foreach my $key ( keys %{$filetable->{$linenumber}} ) + # { + # print "\t\tTest: $key: $filetable->{$linenumber}->{$key}\n"; + # } + # } + + # Example: Searching for ProductCode in table Property + + # my $column1 = "Property"; + # my $column2 = "Value"; + # my $searchkey = "ProductCode"; + # my $propertytable = $database->{'Property'}; + # foreach my $linenumber ( keys %{$propertytable} ) + # { + # if ( $propertytable->{$linenumber}->{$column1} eq $searchkey ) + # { + # print("Test: $searchkey : $propertytable->{$linenumber}->{$column2}\n"); + # } + # } + + installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase end"); + } + + return $database; +} + +######################################################################### +# Reading the file "Directory.idt". +######################################################################### + +sub collect_directories +{ + my ($filecontent) = @_; + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + if ( $i <= 2 ) { next; } # ignoring first three lines + if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines + # Format: Directory Directory_Parent DefaultDir + if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ ) + { + $installer::globals::merge_directory_hash{$1} = 1; + } + else + { + my $linecount = $i + 1; + installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories"); + } + } +} + +################################################################################# +# Files can be included in merge modules. This is also important for update. +################################################################################# + +sub readmergedatabase +{ + my ( $mergemodules, $languagestringref, $includepatharrayref ) = @_; + + installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase start"); + + my $mergemoduledir = installer::systemactions::create_directories("mergedatabase", $languagestringref); + + my %allmergefiles = (); + + foreach my $mergemodule ( @{$mergemodules} ) + { + my $filename = $mergemodule->{'Name'}; + my $mergefile = $ENV{'MSM_PATH'} . $filename; + + if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename !", "readmergedatabase"); } + my $completesource = $mergefile; + + my $mergegid = $mergemodule->{'gid'}; + my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid; + if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); } + + my $completedest = $workdir . $installer::globals::separator . $filename; + installer::systemactions::copy_one_file($completesource, $completedest); + if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "readmergedatabase"); } + + # extract all tables from database + extract_all_tables_from_msidatabase($completedest, $workdir); + + # read all tables + my $onemergefile = read_all_tables_from_msidatabase($workdir); + + $allmergefiles{$mergegid} = $onemergefile; + } + + foreach my $mergefilegid ( keys %allmergefiles ) + { + my $onemergefile = $allmergefiles{$mergefilegid}; + my $filetable = $onemergefile->{'File'}; + + foreach my $linenumber ( keys %{$filetable} ) + { + # Collecting all files from merge modules in global hash + $installer::globals::mergemodulefiles{$filetable->{$linenumber}->{'File'}} = 1; + } + } + + installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase end"); +} + +################################################################################# +# Creating several useful hashes from old database +################################################################################# + +sub create_database_hashes +{ + my ( $database ) = @_; + + # 1. Hash ( Component -> UniqueFileName ), required in File table. + # Read from File table. + + my %uniquefilename = (); + my %allupdatesequences = (); + my %allupdatecomponents = (); + my %allupdatefileorder = (); + my %allupdatecomponentorder = (); + my %revuniquefilename = (); + my %revshortfilename = (); + my %shortdirname = (); + my %componentid = (); + my %componentidkeypath = (); + my %alloldproperties = (); + my %allupdatelastsequences = (); + my %allupdatediskids = (); + + my $filetable = $database->{'File'}; + + foreach my $linenumber ( keys %{$filetable} ) + { + my $comp = $filetable->{$linenumber}->{'Component_'}; + my $uniquename = $filetable->{$linenumber}->{'File'}; + my $filename = $filetable->{$linenumber}->{'FileName'}; + my $sequence = $filetable->{$linenumber}->{'Sequence'}; + + my $shortname = ""; + if ( $filename =~ /^\s*(.*?)\|\s*(.*?)\s*$/ ) + { + $shortname = $1; + $filename = $2; + } + + # unique is the combination of $component and $filename + my $key = "$comp/$filename"; + + if ( exists($uniquefilename{$key}) ) { installer::exiter::exit_program("ERROR: Component/FileName \"$key\" is not unique in table \"File\" !", "create_database_hashes"); } + + my $value = $uniquename; + if ( $shortname ne "" ) { $value = "$uniquename;$shortname"; } + $uniquefilename{$key} = $value; # saving the unique keys and short names in hash + + # Saving reverse keys too + $revuniquefilename{$uniquename} = $key; + if ( $shortname ne "" ) { $revshortfilename{$shortname} = $key; } + + # Saving Sequences for unique names (and also components) + $allupdatesequences{$uniquename} = $sequence; + $allupdatecomponents{$uniquename} = $comp; + + # Saving unique names and components for sequences + $allupdatefileorder{$sequence} = $uniquename; + $allupdatecomponentorder{$sequence} = $comp; + } + + # 2. Hash, required in Directory table. + + my $dirtable = $database->{'Directory'}; + + foreach my $linenumber ( keys %{$dirtable} ) + { + my $dir = $dirtable->{$linenumber}->{'Directory'}; # this is a unique name + my $defaultdir = $dirtable->{$linenumber}->{'DefaultDir'}; + + my $shortname = ""; + if ( $defaultdir =~ /^\s*(.*?)\|\s*(.*?)\s*$/ ) + { + $shortname = $1; + $shortdirname{$dir} = $shortname; # collecting only the short names + } + } + + # 3. Hash, collecting info from Component table. + # ComponentID and KeyPath have to be reused. + + my $comptable = $database->{'Component'}; + + foreach my $linenumber ( keys %{$comptable} ) + { + my $comp = $comptable->{$linenumber}->{'Component'}; + my $compid = $comptable->{$linenumber}->{'ComponentId'}; + my $keypath = $comptable->{$linenumber}->{'KeyPath'}; + + $componentid{$comp} = $compid; + $componentidkeypath{$comp} = $keypath; + } + + # 4. Hash, property table, required for ProductCode and Installlocation. + + my $proptable = $database->{'Property'}; + + foreach my $linenumber ( keys %{$proptable} ) + { + my $prop = $proptable->{$linenumber}->{'Property'}; + my $value = $proptable->{$linenumber}->{'Value'}; + + $alloldproperties{$prop} = $value; + } + + # 5. Media table, getting last sequence + + my $mediatable = $database->{'Media'}; + $installer::globals::updatelastsequence = 0; + + foreach my $linenumber ( keys %{$mediatable} ) + { + my $cabname = $mediatable->{$linenumber}->{'Cabinet'}; + my $lastsequence = $mediatable->{$linenumber}->{'LastSequence'}; + my $diskid = $mediatable->{$linenumber}->{'DiskId'}; + $allupdatelastsequences{$cabname} = $lastsequence; + $allupdatediskids{$cabname} = $diskid; + + if ( $lastsequence > $installer::globals::updatelastsequence ) { $installer::globals::updatelastsequence = $lastsequence; } + } + + $installer::globals::updatesequencecounter = $installer::globals::updatelastsequence; + + return (\%uniquefilename, \%revuniquefilename, \%revshortfilename, \%allupdatesequences, \%allupdatecomponents, \%allupdatefileorder, \%allupdatecomponentorder, \%shortdirname, \%componentid, \%componentidkeypath, \%alloldproperties, \%allupdatelastsequences, \%allupdatediskids); +} + + +1; diff --git a/solenv/bin/modules/installer/windows/upgrade.pm b/solenv/bin/modules/installer/windows/upgrade.pm new file mode 100644 index 000000000..548382124 --- /dev/null +++ b/solenv/bin/modules/installer/windows/upgrade.pm @@ -0,0 +1,80 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::windows::upgrade; + +use installer::exiter; +use installer::files; +use installer::globals; +use installer::windows::idtglobal; + +#################################################################################### +# Creating the file Upgrade.idt dynamically +# Content: +# UpgradeCode VersionMin VersionMax Language Attributes Remove ActionProperty +#################################################################################### + +sub create_upgrade_table +{ + my ($basedir, $allvariableshashref) = @_; + + my @upgradetable = (); + + installer::windows::idtglobal::write_idt_header(\@upgradetable, "upgrade"); + + # Setting all products, that must be removed. + my $newline = $installer::globals::upgradecode . "\t" . "\t" . $installer::globals::msiproductversion . "\t" . "\t" . "513" . "\t" . "\t" . "OLDPRODUCTS" . "\n"; + push(@upgradetable, $newline); + + # preventing downgrading + $newline = $installer::globals::upgradecode . "\t" . $installer::globals::msiproductversion . "\t" . "\t" . "\t" . "2" . "\t" . "\t" . "NEWPRODUCTS" . "\n"; + push(@upgradetable, $newline); + + # Saving the file + + my $upgradetablename = $basedir . $installer::globals::separator . "Upgrade.idt"; + installer::files::save_file($upgradetablename ,\@upgradetable); + my $infoline = "Created idt file: $upgradetablename\n"; + push(@installer::globals::logfileinfo, $infoline); +} + +############################################################## +# Reading the file with UpgradeCodes of old products, +# that can be removed, if the user wants to remove them. +############################################################## + +sub analyze_file_for_upgrade_table +{ + my ($filecontent) = @_; + + my @allnewlines = (); + + for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) + { + my $line = ${$filecontent}[$i]; + if ( $line =~ /^\s*$/ ) { next; } # empty lines can be ignored + if ( $line =~ /^\s*\#/ ) { next; } # comment lines starting with a hash + + if ( $line =~ /^(.*)\t(.*)\t(.*)\t(.*)\t(.*)\t(.*)\t(.*)$/ ) { push(@allnewlines, $line); } + else { installer::exiter::exit_program("ERROR: Wrong syntax in file for upgrade table", "analyze_file_for_upgrade_table"); } + } + + return \@allnewlines; +} + +1; diff --git a/solenv/bin/modules/installer/worker.pm b/solenv/bin/modules/installer/worker.pm new file mode 100644 index 000000000..fb2969f77 --- /dev/null +++ b/solenv/bin/modules/installer/worker.pm @@ -0,0 +1,921 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::worker; + +use Cwd; +use File::Copy; +use File::stat; +use File::Temp qw(tmpnam); +use File::Path; +use File::Basename; +use installer::control; +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::pathanalyzer; +use installer::scpzipfiles; +use installer::scriptitems; +use installer::systemactions; +use installer::windows::language; + +######################################### +# Saving the patchlist file +######################################### + +sub _save_patchlist_file +{ + my ($installlogdir, $patchlistfilename) = @_; + + my $installpatchlistdir = installer::systemactions::create_directory_next_to_directory($installlogdir, "patchlist"); + $patchlistfilename =~ s/log\_/patchfiles\_/; + $patchlistfilename =~ s/\.log/\.txt/; + installer::files::save_file($installpatchlistdir . $installer::globals::separator . $patchlistfilename, \@installer::globals::patchfilecollector); + installer::logger::print_message( "... creating patchlist file $patchlistfilename \n" ); + +} + +############################################################### +# Removing all directories of a special language +# in the directory $basedir +############################################################### + +sub remove_old_installation_sets +{ + my ($basedir) = @_; + + installer::logger::print_message( "... removing old installation directories ...\n" ); + + my $removedir = $basedir; + + if ( -d $removedir ) { installer::systemactions::remove_complete_directory($removedir, 1); } + + # looking for non successful old installation sets + + $removedir = $basedir . "_witherror"; + if ( -d $removedir ) { installer::systemactions::remove_complete_directory($removedir, 1); } + + $removedir = $basedir . "_inprogress"; + if ( -d $removedir ) { installer::systemactions::remove_complete_directory($removedir, 1); } + + # finally the $basedir can be created empty + + if ( $installer::globals::localinstalldirset ) { installer::systemactions::create_directory_structure($basedir); } + + installer::systemactions::create_directory($basedir); +} + +############################################################### +# Creating the installation directory structure +############################################################### + +sub create_installation_directory +{ + my ($shipinstalldir, $languagestringref, $current_install_number_ref) = @_; + + my $installdir = ""; + + my $languageref = $languagestringref; + + $installdir = installer::systemactions::create_directories("install", $languageref); + installer::logger::print_message( "... creating installation set in $installdir ...\n" ); + remove_old_installation_sets($installdir); + my $inprogressinstalldir = $installdir . "_inprogress"; + installer::systemactions::rename_directory($installdir, $inprogressinstalldir); + $installdir = $inprogressinstalldir; + + $installer::globals::saveinstalldir = $installdir; # saving directory globally, in case of exiting + + return $installdir; +} + +############################################################### +# Analyzing and creating the log file +############################################################### + +sub analyze_and_save_logfile +{ + my ($loggingdir, $installdir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number) = @_; + + my $is_success = 1; + my $finalinstalldir = ""; + + installer::logger::print_message( "... checking log file " . $loggingdir . $installer::globals::logfilename . "\n" ); + + my $contains_error = installer::control::check_logfile(\@installer::globals::logfileinfo); + + # Dependent from the success, the installation directory can be renamed. + + if ( $contains_error ) + { + my $errordir = installer::systemactions::rename_string_in_directory($installdir, "_inprogress", "_witherror"); + # Error output to STDERR + for ( my $j = 0; $j <= $#installer::globals::errorlogfileinfo; $j++ ) + { + my $line = $installer::globals::errorlogfileinfo[$j]; + $line =~ s/\s*$//g; + installer::logger::print_error( $line ); + } + $is_success = 0; + + $finalinstalldir = $errordir; + } + else + { + my $destdir = ""; + + $destdir = installer::systemactions::rename_string_in_directory($installdir, "_inprogress", ""); + + $finalinstalldir = $destdir; + } + + # Saving the logfile in the log file directory and additionally in a log directory in the install directory + + my $numberedlogfilename = $installer::globals::logfilename; + installer::logger::print_message( "... creating log file $numberedlogfilename \n" ); + installer::files::save_file($loggingdir . $numberedlogfilename, \@installer::globals::logfileinfo); + installer::files::save_file($installlogdir . $installer::globals::separator . $numberedlogfilename, \@installer::globals::logfileinfo); + + # Saving the list of patchfiles in a patchlist directory in the install directory + if ( $installer::globals::creating_windows_installer_patch ) { _save_patchlist_file($installlogdir, $numberedlogfilename); } + + if ( $installer::globals::creating_windows_installer_patch ) { $installer::globals::creating_windows_installer_patch = 0; } + + # Exiting the packaging process, if an error occurred. + # This is important, to get an error code "-1", if an error was found in the log file, + # that did not break the packaging process + + if ( ! $is_success) { installer::exiter::exit_program("ERROR: Found an error in the logfile " . $loggingdir . $installer::globals::logfilename . ". Packaging failed.", "analyze_and_save_logfile"); } + + return ($is_success, $finalinstalldir); +} + +############################################################### +# Removing all directories that are saved in the +# global directory @installer::globals::removedirs +############################################################### + +sub clean_output_tree +{ + installer::logger::print_message( "... cleaning the output tree ...\n" ); + + for ( my $i = 0; $i <= $#installer::globals::removedirs; $i++ ) + { + if ( -d $installer::globals::removedirs[$i] ) + { + installer::logger::print_message( "... removing directory $installer::globals::removedirs[$i] ...\n" ); + installer::systemactions::remove_complete_directory($installer::globals::removedirs[$i], 1); + } + } + + # Last try to remove the ship test directory + + if ( $installer::globals::shiptestdirectory ) + { + if ( -d $installer::globals::shiptestdirectory ) + { + my $infoline = "Last try to remove $installer::globals::shiptestdirectory . \n"; + push(@installer::globals::logfileinfo, $infoline); + my $systemcall = "rmdir $installer::globals::shiptestdirectory"; + my $returnvalue = system($systemcall); + } + } +} + +########################################################### +# Setting one language in the language independent +# array of include paths with $(LANG) +########################################################### + +sub get_language_specific_include_paths +{ + my ( $patharrayref, $onelanguage ) = @_; + + my @patharray = (); + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $line = ${$patharrayref}[$i]; + $line =~ s/\$\(LANG\)/$onelanguage/g; + push(@patharray ,$line); + } + + return \@patharray; +} + +############################################################## +# Collecting all items with a defined flag +############################################################## + +sub collect_all_items_with_special_flag +{ + my ($itemsref, $flag) = @_; + + my @allitems = (); + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + my $oneitem = ${$itemsref}[$i]; + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'} }; + + if ( $styles =~ /\b$flag\b/ ) + { + push( @allitems, $oneitem ); + } + } + + return \@allitems; +} + +############################################################## +# Removing all items with a defined flag from collector +############################################################## + +sub remove_all_items_with_special_flag +{ + my ($itemsref, $flag) = @_; + + my @allitems = (); + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + my $oneitem = ${$itemsref}[$i]; + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'} }; + if ( $styles =~ /\b$flag\b/ ) + { + my $infoline = "Attention: Removing from collector: $oneitem->{'Name'} !\n"; + push( @installer::globals::logfileinfo, $infoline); + if ( $flag eq "BINARYTABLE_ONLY" ) { push(@installer::globals::binarytableonlyfiles, $oneitem); } + next; + } + push( @allitems, $oneitem ); + } + + return \@allitems; +} + +########################################################### +# Mechanism for simple installation without packing +########################################################### + +sub install_simple ($$$$$$) +{ + my ($packagename, $languagestring, $directoriesarray, $filesarray, $linksarray, $unixlinksarray) = @_; + + installer::logger::print_message( "... installing module $packagename ...\n" ); + + my $destdir = $installer::globals::destdir; + my @lines = (); + + installer::logger::print_message( "DestDir: $destdir \n" ); + installer::logger::print_message( "Rootpath: $installer::globals::rootpath \n" ); + + `mkdir -p $destdir` if $destdir ne ""; + `mkdir -p $destdir$installer::globals::rootpath`; + + # Create Directories + for ( my $i = 0; $i <= $#{$directoriesarray}; $i++ ) + { + my $onedir = ${$directoriesarray}[$i]; + my $dir = ""; + + if ( $onedir->{'Dir'} ) { $dir = $onedir->{'Dir'}; } + + if ((!($dir =~ /\bPREDEFINED_/ )) || ( $dir =~ /\bPREDEFINED_PROGDIR\b/ )) + { + my $hostname = $onedir->{'HostName'}; + + # ignore '.' subdirectories + next if ( $hostname =~ m/\.$/ ); + # remove './' from the path + $hostname =~ s/\.\///g; + + # printf "mkdir $destdir$hostname\n"; + mkdir $destdir . $hostname; + push @lines, "%dir " . $hostname . "\n"; + } + } + + for ( my $i = 0; $i <= $#{$filesarray}; $i++ ) + { + my $onefile = ${$filesarray}[$i]; + my $unixrights = $onefile->{'UnixRights'}; + my $destination = $onefile->{'destination'}; + my $sourcepath = $onefile->{'sourcepath'}; + + # This is necessary to install SDK that includes files with $ in its name + # Otherwise, the following shell commands does not work and the file list + # is not correct + $destination =~ s/\$\$/\$/; + $sourcepath =~ s/\$\$/\$/; + + # remove './' from the path + $sourcepath =~ s/\.\///g; + $destination =~ s/\.\///g; + + push @lines, "$destination\n"; + if(-d "$destdir$destination"){ + rmtree("$destdir$destination"); + } + if(-e "$destdir$destination") { + unlink "$destdir$destination"; + } + + if ( -l "$sourcepath" ) { + symlink (readlink ("$sourcepath"), "$destdir$destination") || die "Can't symlink $destdir$destination -> " . readlink ("$sourcepath") . "$!"; + } + elsif ( -d $sourcepath ) { + `mkdir -p "$destdir$destination"`; + } + else { + copy ("$sourcepath", "$destdir$destination") || die "Can't copy file: $sourcepath -> $destdir$destination $!"; + my $sourcestat = stat($sourcepath); + utime ($sourcestat->atime, $sourcestat->mtime, "$destdir$destination"); + chmod (oct($unixrights), "$destdir$destination") || die "Can't change permissions: $!"; + } + push @lines, "$destination\n"; + } + + for ( my $i = 0; $i <= $#{$linksarray}; $i++ ) + { + my $onelink = ${$linksarray}[$i]; + my $destination = $onelink->{'destination'}; + my $destinationfile = $onelink->{'destinationfile'}; + + if(-e "$destdir$destination") { + unlink "$destdir$destination"; + } + symlink ("$destinationfile", "$destdir$destination") || die "Can't create symlink: $!"; + push @lines, "$destination\n"; + } + + for ( my $i = 0; $i <= $#{$unixlinksarray}; $i++ ) + { + my $onelink = ${$unixlinksarray}[$i]; + my $target = $onelink->{'Target'}; + my $destination = $onelink->{'destination'}; + my $cmd = "mkdir -p '" . dirname($destdir . $destination) . "'"; + system($cmd) && die "Failed to execute \"$cmd\""; + $cmd = "ln -sf '$target' '$destdir$destination'"; + + system($cmd) && die "Failed \"$cmd\""; + push @lines, "$destination\n"; + } + + if ( $destdir ne "" ) + { + my $filelist; + my $fname = $installer::globals::destdir . "/$packagename"; + open ($filelist, ">$fname") || die "Can't open $fname: $!"; + print $filelist @lines; + close ($filelist); + } + +} + +########################################################### +# Selecting langpack items +########################################################### + +sub select_langpack_items +{ + my ( $itemsref, $itemname ) = @_; + + installer::logger::include_header_into_logfile("Selecting RegistryItems for Language Packs"); + + my @itemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + my $oneitem = ${$itemsref}[$i]; + + # Items with style "LANGUAGEPACK" have to be included into the patch + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + if (( $styles =~ /\bLANGUAGEPACK\b/ ) || ( $styles =~ /\bFORCELANGUAGEPACK\b/ )) { push(@itemsarray, $oneitem); } + } + + return \@itemsarray; +} + +########################################################### +# Selecting helppack items +########################################################### + +sub select_helppack_items +{ + my ( $itemsref, $itemname ) = @_; + + installer::logger::include_header_into_logfile("Selecting RegistryItems for Help Packs"); + + my @itemsarray = (); + + for ( my $i = 0; $i <= $#{$itemsref}; $i++ ) + { + my $oneitem = ${$itemsref}[$i]; + + # Items with style "HELPPACK" have to be included into the patch + my $styles = ""; + if ( $oneitem->{'Styles'} ) { $styles = $oneitem->{'Styles'}; } + if (( $styles =~ /\bHELPPACK\b/ ) || ( $styles =~ /\bFORCEHELPPACK\b/ )) { push(@itemsarray, $oneitem); } + } + + return \@itemsarray; +} + +########################################################### +# Replacing %-variables with the content +# of $allvariableshashref +########################################################### + +sub replace_variables_in_string +{ + my ( $string, $variableshashref ) = @_; + + if ( $string =~ /^.*\%\w+.*$/ ) + { + my $key; + + # we want to substitute FOO_BR before FOO to avoid floating _BR suffixes + foreach $key (sort { length ($b) <=> length ($a) } keys %{$variableshashref}) + { + my $value = $variableshashref->{$key}; + $key = "\%" . $key; + $string =~ s/\Q$key\E/$value/g; + } + } + + return $string; +} + +################################################################# +# Copying the files defined as ScpActions into the +# installation set. +################################################################# + +sub put_scpactions_into_installset +{ + my ($installdir) = @_; + + installer::logger::include_header_into_logfile("Start: Copying scp action files into installation set"); + + for ( my $i = 0; $i <= $#installer::globals::allscpactions; $i++ ) + { + my $onescpaction = $installer::globals::allscpactions[$i]; + + my $subdir = ""; + if ( $onescpaction->{'Subdir'} ) { $subdir = $onescpaction->{'Subdir'}; } + + if ( $onescpaction->{'Name'} eq "loader.exe" ) { next; } # do not copy this ScpAction loader + + my $destdir = $installdir; + $destdir =~ s/\Q$installer::globals::separator\E\s*$//; + if ( $subdir ) { $destdir = $destdir . $installer::globals::separator . $subdir; } + + my $sourcefile = $onescpaction->{'sourcepath'}; + my $destfile = $destdir . $installer::globals::separator . $onescpaction->{'DestinationName'}; + + if (( $subdir =~ /\// ) || ( $subdir =~ /\\/ )) + { + installer::systemactions::create_directory_structure($destdir); + } + else + { + installer::systemactions::create_directory($destdir); + } + + installer::systemactions::copy_one_file($sourcefile, $destfile); + + if ( $onescpaction->{'UnixRights'} ) + { + chmod oct($onescpaction->{'UnixRights'}), $destfile; + } + + } + + installer::logger::include_header_into_logfile("End: Copying scp action files into installation set"); + +} + +################################################################# +# Collecting scp actions for all languages +################################################################# + +sub collect_scpactions +{ + my ($allscpactions) = @_; + + for ( my $i = 0; $i <= $#{$allscpactions}; $i++ ) + { + push(@installer::globals::allscpactions, ${$allscpactions}[$i]); + } +} + +########################################################### +# Adding additional variables into the variableshashref, +# that are defined in include files in the source tree. The +# names of the include files are stored in +# ADD_INCLUDE_FILES (comma separated list). +########################################################### + +sub add_variables_from_inc_to_hashref +{ + my ($allvariables, $includepatharrayref) = @_; + + my $infoline = ""; + my $includefilelist = $allvariables->{'ADD_INCLUDE_FILES'} || ""; + + for my $includefilename (split /,\s*/, $includefilelist) + { + $includefilename =~ s/^\s*//; + $includefilename =~ s/\s*$//; + $includefilenameref = $ENV{'SRCDIR'} . "/" . $includefilename; + if ( ! -f $includefilenameref ) { installer::exiter::exit_program("Include file $includefilename ($includefilenameref) not found!\nADD_INCLUDE_FILES = $allvariables->{'ADD_INCLUDE_FILES'}", "add_variables_from_inc_to_hashref"); } + + $infoline = "Including inc file: $includefilenameref \n"; + push( @installer::globals::globallogfileinfo, $infoline); + + my $includefile = installer::files::read_file($includefilenameref); + + for ( my $j = 0; $j <= $#{$includefile}; $j++ ) + { + # Analyzing all "key=value" lines + my $oneline = ${$includefile}[$j]; + + if ( $oneline =~ /^\s*(\S+)\s*\=\s*(.*?)\s*$/ ) # no white space allowed in key + { + my $key = $1; + my $value = $2; + $allvariables->{$key} = $value; + $infoline = "Setting of variable: $key = $value\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + } + } +} + +############################################## +# Collecting all files from include paths +############################################## + +sub collect_all_files_from_includepaths +{ + my ($patharrayref) = @_; + + installer::logger::globallog("Reading all directories: Start"); + installer::logger::print_message( "... reading include paths ...\n" ); + # empty the global + + @installer::globals::allincludepaths =(); + my $infoline; + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + $includepath = ${$patharrayref}[$i]; + installer::remover::remove_leading_and_ending_whitespaces(\$includepath); + + if ( ! -d $includepath ) + { + $infoline = "$includepath does not exist. (Can be removed from include path list?)\n"; + push( @installer::globals::globallogfileinfo, $infoline); + next; + } + + my @sourcefiles = (); + my $pathstring = ""; + installer::systemactions::read_full_directory($includepath, $pathstring, \@sourcefiles); + + if ( ! ( $#sourcefiles > -1 )) + { + $infoline = "$includepath is empty. (Can be removed from include path list?)\n"; + push( @installer::globals::globallogfileinfo, $infoline); + } + else + { + my $number = $#sourcefiles + 1; + $infoline = "Directory $includepath contains $number files (including subdirs)\n"; + push( @installer::globals::globallogfileinfo, $infoline); + + my %allfileshash = (); + $allfileshash{'includepath'} = $includepath; + + for ( my $j = 0; $j <= $#sourcefiles; $j++ ) + { + $allfileshash{$sourcefiles[$j]} = 1; + } + + push(@installer::globals::allincludepaths, \%allfileshash); + } + } + + $installer::globals::include_paths_read = 1; + + installer::logger::globallog("Reading all directories: End"); + push( @installer::globals::globallogfileinfo, "\n"); +} + +############################################## +# Searching for a file with the gid +############################################## + +sub find_file_by_id +{ + my ( $filesref, $gid ) = @_; + + my $foundfile = 0; + my $onefile; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + $onefile = ${$filesref}[$i]; + my $filegid = $onefile->{'gid'}; + + if ( $filegid eq $gid ) + { + $foundfile = 1; + last; + } + } + + if (! $foundfile ) { $onefile = ""; } + + return $onefile; +} + +################################################# +# Generating paths for cygwin (second version) +# This function generates smaller files for +################################################# + +sub generate_cygwin_paths +{ + my ($filesref) = @_; + + installer::logger::include_timestamp_into_logfile("Starting generating cygwin paths"); + + my $infoline = "Generating cygwin paths (generate_cygwin_paths)\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $max = 5000; # number of paths in one file + + my @pathcollector = (); + my $startnumber = 0; + my $counter = 0; + + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + my $line = ${$filesref}[$i]->{'sourcepath'} . "\n"; + push(@pathcollector, $line); + $counter++; + + if (( $i == $#{$filesref} ) || ((( $counter % $max ) == 0 ) && ( $i > 0 ))) + { + my $tmpfilename = "cygwinhelper_" . $i . ".txt"; + my $temppath = $installer::globals::temppath; + $temppath =~ s/\Q$installer::globals::separator\E\s*$//; + $tmpfilename = $temppath . $installer::globals::separator . $tmpfilename; + $infoline = "Creating temporary file for cygwin conversion: $tmpfilename (contains $counter paths)\n"; + push( @installer::globals::logfileinfo, $infoline); + if ( -f $tmpfilename ) { unlink $tmpfilename; } + + installer::files::save_file($tmpfilename, \@pathcollector); + + my $success = 0; + my @cyg_sourcepathlist = qx{cygpath -w -f "$tmpfilename"}; + chomp @cyg_sourcepathlist; + + # Validating the array, it has to contain the correct number of values + my $new_paths = $#cyg_sourcepathlist + 1; + if ( $new_paths == $counter ) { $success = 1; } + + if ($success) + { + $infoline = "Success: Successfully converted to cygwin paths!\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "ERROR: Failed to convert to cygwin paths!\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: Failed to convert to cygwin paths!", "generate_cygwin_paths"); + } + + for ( my $j = 0; $j <= $#cyg_sourcepathlist; $j++ ) + { + my $number = $startnumber + $j; + ${$filesref}[$number]->{'cyg_sourcepath'} = $cyg_sourcepathlist[$j]; + } + + if ( -f $tmpfilename ) { unlink $tmpfilename; } + + @pathcollector = (); + $startnumber = $startnumber + $max; + $counter = 0; + } + } + + # Checking existence of cyg_sourcepath for every file + for ( my $i = 0; $i <= $#{$filesref}; $i++ ) + { + if (( ! exists(${$filesref}[$i]->{'cyg_sourcepath'}) ) || ( ${$filesref}[$i]->{'cyg_sourcepath'} eq "" )) + { + $infoline = "ERROR: No cygwin sourcepath defined for file ${$filesref}[$i]->{'sourcepath'}\n"; + push( @installer::globals::logfileinfo, $infoline); + installer::exiter::exit_program("ERROR: No cygwin sourcepath defined for file ${$filesref}[$i]->{'sourcepath'}!", "generate_cygwin_paths"); + } + } + + installer::logger::include_timestamp_into_logfile("Ending generating cygwin paths"); +} + +###################################################### +# Getting the first entry from a list of languages +###################################################### + +sub get_first_from_list +{ + my ( $list ) = @_; + + my $first = $list; + + if ( $list =~ /^\s*(.+?),(.+)\s*$/) # "?" for minimal matching + { + $first = $1; + } + + return $first; +} + +################################################ +# Setting all spellchecker languages +################################################ + +sub set_spellcheckerlanguages +{ + my ( $productlanguagesarrayref, $allvariables ) = @_; + + my %productlanguages = (); + for ( my $i = 0; $i <= $#{$productlanguagesarrayref}; $i++ ) { $productlanguages{${$productlanguagesarrayref}[$i]} = 1; } + + my $spellcheckfilename = $allvariables->{'SPELLCHECKERFILE'}; + + my $spellcheckfileref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$spellcheckfilename, "", 1); + + if ($$spellcheckfileref eq "") { installer::exiter::exit_program("ERROR: Could not find $spellcheckfilename!", "set_spellcheckerlanguages"); } + + my $infoline = "Using spellchecker file: $$spellcheckfileref \n"; + push( @installer::globals::globallogfileinfo, $infoline); + + my $spellcheckfile = installer::files::read_file($$spellcheckfileref); + my %spellcheckhash = (); + + for ( my $j = 0; $j <= $#{$spellcheckfile}; $j++ ) + { + # Analyzing all "key=value" lines + my $oneline = ${$spellcheckfile}[$j]; + + if ( $oneline =~ /^\s*(\S+)\s*\=\s*\"(.*?)\"\s*$/ ) # no white space allowed in key + { + my $onelang = $1; + my $languagelist = $2; + + # Special handling for language packs. Only include the first language of the language list. + # If no spellchecker shall be included, the keyword "EMPTY" can be used. + + if ( $installer::globals::languagepack ) + { + my $first = get_first_from_list($languagelist); + + if ( $first eq "EMPTY" ) # no spellchecker into language pack + { + $languagelist = ""; + } + else + { + $languagelist = $first; + } + } + else # no language pack, so EMPTY is not required + { + $languagelist =~ s/^\s*EMPTY\s*,//; # removing the entry EMPTY + } + + $spellcheckhash{$onelang} = $languagelist; + } + } + + # Collecting all required languages in %installer::globals::spellcheckerlanguagehash + + foreach my $lang (keys %productlanguages) + { + my $languagelist = ""; + if ( exists($spellcheckhash{$lang}) ) { $languagelist = $spellcheckhash{$lang}; } + else { $languagelist = ""; } # no dictionary unless defined in SPELLCHECKERFILE + + my $langlisthash = installer::converter::convert_stringlist_into_hash(\$languagelist, ","); + foreach my $onelang ( keys %{$langlisthash} ) { $installer::globals::spellcheckerlanguagehash{$onelang} = 1; } + } + + $installer::globals::analyze_spellcheckerlanguage = 1; + + # Logging + + my $langstring = ""; + foreach my $lang (sort keys %installer::globals::spellcheckerlanguagehash) { $langstring = $langstring . "," . $lang } + $langstring =~ s/^\s*,//; + + $infoline = "Collected spellchecker languages for spellchecker: $langstring \n"; + push( @installer::globals::globallogfileinfo, $infoline); +} + +################################################ +# Including a license text into setup script +################################################ + +sub put_license_into_setup +{ + my ($installdir, $includepatharrayref) = @_; + + # find and read the license file + my $licenselanguage = "en-US"; # always english ! + my $licensefilename = "license"; + # my $licensefilename = "LICENSE" . ".txt"; + my $licenseincludepatharrayref = get_language_specific_include_paths($includepatharrayref, $licenselanguage); + + my $licenseref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$licensefilename, $licenseincludepatharrayref, 0); + if ($$licenseref eq "") { installer::exiter::exit_program("ERROR: Could not find License file $licensefilename!", "put_license_into_setup"); } + my $licensefile = installer::files::read_file($$licenseref); + + # Read setup + my $setupfilename = $installdir . $installer::globals::separator . "setup"; + my $setupfile = installer::files::read_file($setupfilename); + + # Replacement + my $infoline = "Adding licensefile into setup script\n"; + push( @installer::globals::logfileinfo, $infoline); + + my $includestring = ""; + for ( my $i = 0; $i <= $#{$licensefile}; $i++ ) { $includestring = $includestring . ${$licensefile}[$i]; } + for ( my $i = 0; $i <= $#{$setupfile}; $i++ ) { ${$setupfile}[$i] =~ s/LICENSEFILEPLACEHOLDER/$includestring/; } + + # Write setup + installer::files::save_file($setupfilename, $setupfile); +} + +######################################################### +# Collecting all pkgmap files from an installation set +######################################################### + +sub collectpackagemaps +{ + my ( $installdir, $languagestringref, $allvariables ) = @_; + + installer::logger::include_header_into_logfile("Collecting all packagemaps (pkgmap):"); + + my $pkgmapdir = installer::systemactions::create_directories("pkgmap", $languagestringref); + my $subdirname = $allvariables->{'UNIXPRODUCTNAME'} . "_pkgmaps"; + my $pkgmapsubdir = $pkgmapdir . $installer::globals::separator . $subdirname; + if ( -d $pkgmapsubdir ) { installer::systemactions::remove_complete_directory($pkgmapsubdir); } + if ( ! -d $pkgmapsubdir ) { installer::systemactions::create_directory($pkgmapsubdir); } + + $installdir =~ s/\/\s*$//; + # Collecting all packages in $installdir and its sub package ("packages") + my $searchdir = $installdir . $installer::globals::separator . $installer::globals::epmoutpath; + + my $allpackages = installer::systemactions::get_all_directories_without_path($searchdir); + + for ( my $i = 0; $i <= $#{$allpackages}; $i++ ) + { + my $pkgmapfile = $searchdir . $installer::globals::separator . ${$allpackages}[$i] . $installer::globals::separator . "pkgmap"; + my $destfilename = $pkgmapsubdir . $installer::globals::separator . ${$allpackages}[$i] . "_pkgmap"; + installer::systemactions::copy_one_file($pkgmapfile, $destfilename); + } + + # Create a tar gz file with all package maps + my $tarfilename = $subdirname . ".tar"; + my $targzname = $tarfilename . ".gz"; + $systemcall = "cd $pkgmapdir; tar -cf - $subdirname | $installer::globals::packertool > $targzname"; + installer::systemactions::make_systemcall($systemcall); + installer::systemactions::remove_complete_directory($pkgmapsubdir, 1); +} + +1; diff --git a/solenv/bin/modules/installer/ziplist.pm b/solenv/bin/modules/installer/ziplist.pm new file mode 100644 index 000000000..b76c62fc7 --- /dev/null +++ b/solenv/bin/modules/installer/ziplist.pm @@ -0,0 +1,828 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +package installer::ziplist; + +use base 'Exporter'; + +use File::Spec::Functions qw(rel2abs); + +use installer::converter; +use installer::exiter; +use installer::files; +use installer::globals; +use installer::logger; +use installer::remover; +use installer::systemactions; + +our @EXPORT_OK = qw(read_ziplist); + +sub read_ziplist { + my $ziplistname = shift; + + installer::logger::globallog("zip list file: $ziplistname"); + + my $ziplistref = installer::files::read_file($ziplistname); + + installer::logger::print_message( "... analyzing $ziplistname ... \n" ); + + my ($productblockref, $parent) = getproductblock($ziplistref, $installer::globals::product, 1); # product block from zip.lst + + my ($settingsblockref, undef) = getproductblock($productblockref, "Settings", 0); # settings block from zip.lst + $settingsblockref = analyze_settings_block($settingsblockref); # select data from settings block in zip.lst + + my $allsettingsarrayref = get_settings_from_ziplist($settingsblockref); + my $allvariablesarrayref = get_variables_from_ziplist($settingsblockref); + + my ($globalproductblockref, undef) = getproductblock($ziplistref, $installer::globals::globalblock, 0); # global product block from zip.lst + + while (defined $parent) { + my $parentproductblockref; + ($parentproductblockref, $parent) = getproductblock($ziplistref, $parent, 1); + my ($parentsettingsblockref, undef) = getproductblock($parentproductblockref, "Settings", 0); + $parentsettingsblockref = analyze_settings_block($parentsettingsblockref); + my $allparentsettingsarrayref = get_settings_from_ziplist($parentsettingsblockref); + my $allparentvariablesarrayref = get_variables_from_ziplist($parentsettingsblockref); + $allsettingsarrayref = + installer::converter::combine_arrays_from_references_first_win( + $allsettingsarrayref, $allparentsettingsarrayref) + if $#{$allparentsettingsarrayref} > -1; + $allvariablesarrayref = + installer::converter::combine_arrays_from_references_first_win( + $allvariablesarrayref, $allparentvariablesarrayref) + if $#{$allparentvariablesarrayref} > -1; + } + + if ( @{$globalproductblockref} ) { + my ($globalsettingsblockref, undef) = getproductblock($globalproductblockref, "Settings", 0); # settings block from zip.lst + + $globalsettingsblockref = analyze_settings_block($globalsettingsblockref); # select data from settings block in zip.lst + + my $allglobalsettingsarrayref = get_settings_from_ziplist($globalsettingsblockref); + + my $allglobalvariablesarrayref = get_variables_from_ziplist($globalsettingsblockref); + + if ( @{$allglobalsettingsarrayref} ) { + $allsettingsarrayref = installer::converter::combine_arrays_from_references_first_win($allsettingsarrayref, $allglobalsettingsarrayref); + } + if ( @{$allglobalvariablesarrayref} ) { + $allvariablesarrayref = installer::converter::combine_arrays_from_references_first_win($allvariablesarrayref, $allglobalvariablesarrayref); + } + } + + $allsettingsarrayref = remove_multiples_from_ziplist($allsettingsarrayref); + $allvariablesarrayref = remove_multiples_from_ziplist($allvariablesarrayref); + + replace_variables_in_ziplist_variables($allvariablesarrayref); + + my $allvariableshashref = installer::converter::convert_array_to_hash($allvariablesarrayref); + + set_default_productversion_if_required($allvariableshashref); + add_variables_to_allvariableshashref($allvariableshashref); + overwrite_branding( $allvariableshashref ); + + return $allsettingsarrayref, $allvariableshashref; +} + +################################################# +# Getting data from path file and zip list file +################################################# + +sub getproductblock +{ + my ($fileref, $search, $inheritance) = @_; + + my @searchblock = (); + my $searchexists = 0; + my $record = 0; + my $count = 0; + my $line; + my $inh = $inheritance ? '(?::\s*(\S+)\s*)?' : ""; + my $parent; + + for ( my $i = 0; $i <= $#{$fileref}; $i++ ) + { + $line = ${$fileref}[$i]; + + if ( $line =~ /^\s*\Q$search\E\s*$inh$/i ) # case insensitive + { + $record = 1; + $searchexists = 1; + $parent = $1 if $inheritance; + } + + if ($record) + { + push(@searchblock, $line); + } + + if ( ($record) && ($line =~ /\{/) ) + { + $count++; + } + + if ( ($record) && ($line =~ /\}/) ) + { + $count--; + } + + if ( ($record) && ($line =~ /\}/) && ( $count == 0 ) ) + { + $record = 0; + } + } + + if (( ! $searchexists ) && ( $search ne $installer::globals::globalblock )) + { + if ($search eq $installer::globals::product ) + { + installer::exiter::exit_program("ERROR: Product $installer::globals::product not defined in $installer::globals::ziplistname", "getproductblock"); + } + else # this is not possible + { + installer::exiter::exit_program("ERROR: Unknown value for $search in getproductblock()", "getproductblock"); + } + } + + return (\@searchblock, $parent); +} + +############################################### +# Analyzing the settings in the zip list file +############################################### + +sub analyze_settings_block +{ + my ($blockref) = @_; + + my @newsettingsblock = (); + my $record = 1; + my $counter = 0; + + # Allowed values in settings block: + # "Settings", "Variables", "unix" (for destination path and logfile) + + # Comment line in settings block begin with "#" or ";" + + for ( my $i = 0; $i <= $#{$blockref}; $i++ ) + { + my $line = ${$blockref}[$i]; + my $nextline = ""; + + if ( ${$blockref}[$i+1] ) { $nextline = ${$blockref}[$i+1]; } + + # removing comment lines + + if (($line =~ /^\s*\#/) || ($line =~ /^\s*\;/)) + { + next; + } + + # complete blocks of unknown strings are not recorded + + if ((!($line =~ /^\s*\Q$installer::globals::build\E\s*$/i)) && + (!($line =~ /^\s*\bSettings\b\s*$/i)) && + (!($line =~ /^\s*\bVariables\b\s*$/i)) && + (!($line =~ /^\s*\bunix\b\s*$/i)) && + ($nextline =~ /^\s*\{\s*$/i)) + { + $record = 0; + next; # continue with next $i + } + + if (!( $record )) + { + if ($line =~ /^\s*\{\s*$/i) + { + $counter++; + } + + if ($line =~ /^\s*\}\s*$/i) + { + $counter--; + } + + if ($counter == 0) + { + $record = 1; + next; # continue with next $i + } + } + + if ($record) + { + push(@newsettingsblock, $line); + } + } + + return \@newsettingsblock; +} + +######################################## +# Settings in zip list file +######################################## + +sub get_settings_from_ziplist +{ + my ($blockref) = @_; + + my @allsettings = (); + my $isvariables = 0; + my $counter = 0; + my $variablescounter = 0; + + # Take all settings from the settings block + # Do not take the variables from the settings block + # If a setting is defined more than once, take the + # setting with the largest counter (open brackets) + + for ( my $i = 0; $i <= $#{$blockref}; $i++ ) + { + my $line = ${$blockref}[$i]; + my $nextline = ""; + + if ( ${$blockref}[$i+1] ) { $nextline = ${$blockref}[$i+1]; } + + if (($line =~ /^\s*\S+\s*$/i) && + ($nextline =~ /^\s*\{\s*$/i) && + (!($line =~ /^\s*Variables\s*$/i))) + { + next; + } + + if ($line =~ /^\s*Variables\s*$/i) + { + # This is a block of variables + + $isvariables = 1; + next; + } + + if ($line =~ /^\s*\{\s*$/i) + { + if ($isvariables) + { + $variablescounter++; + } + else + { + $counter++; + } + + next; + } + + if ($line =~ /^\s*\}\s*$/i) + { + if ($isvariables) + { + $variablescounter--; + + if ($variablescounter == 0) + { + $isvariables = 0; + } + } + else + { + $counter--; + } + + next; + } + + if ($isvariables) + { + next; + } + + installer::remover::remove_leading_and_ending_whitespaces(\$line); + + $line .= "\t##$counter##\n"; + + push(@allsettings, $line); + } + + return \@allsettings; +} + +####################################### +# Variables from zip list file +####################################### + +sub get_variables_from_ziplist +{ + my ($blockref) = @_; + + my @allvariables = (); + my $isvariables = 0; + my $counter = 0; + my $variablescounter = 0; + my $countersum = 0; + + # Take all variables from the settings block + # Do not take the other settings from the settings block + # If a variable is defined more than once, take the + # variable with the largest counter (open brackets) + + for ( my $i = 0; $i <= $#{$blockref}; $i++ ) + { + my $line = ${$blockref}[$i]; + my $nextline = ${$blockref}[$i+1]; + + if ($line =~ /^\s*Variables\s*$/i) + { + # This is a block of variables + + $isvariables = 1; + next; + } + + if ($line =~ /^\s*\{\s*$/i) + { + if ($isvariables) + { + $variablescounter++; + } + else + { + $counter++; + } + + next; + } + + if ($line =~ /^\s*\}\s*$/i) + { + if ($isvariables) + { + $variablescounter--; + + if ($variablescounter == 0) + { + $isvariables = 0; + } + } + else + { + $counter--; + } + + next; + } + + if (!($isvariables)) + { + next; + } + + $countersum = $counter + $variablescounter; + + installer::remover::remove_leading_and_ending_whitespaces(\$line); + + $line .= "\t##$countersum##\n"; + + push(@allvariables, $line); + } + + return \@allvariables; +} + +####################################################################### +# Removing multiple variables and settings, defined in zip list file +####################################################################### + +sub remove_multiples_from_ziplist +{ + my ($blockref) = @_; + + # remove all definitions of settings and variables + # that occur more than once in the zip list file. + # Take the one with the most open brackets. This + # number is stored at the end of the string. + + my @newarray = (); + my @itemarray = (); + my ($line, $itemname, $itemnumber); + + # first collecting all variables and settings names + + for ( my $i = 0; $i <= $#{$blockref}; $i++ ) + { + $line = ${$blockref}[$i]; + + if ($line =~ /^\s*\b(\S*)\b\s+.*\#\#\d+\#\#\s*$/i) + { + $itemname = $1; + } + + if (! grep {$_ eq $itemname} @itemarray) + { + push(@itemarray, $itemname); + } + } + + # and now all $items can be selected with the highest number + + for ( my $i = 0; $i <= $#itemarray; $i++ ) + { + $itemname = $itemarray[$i]; + + my $itemnumbermax = 0; + my $printline = ""; + + for ( my $j = 0; $j <= $#{$blockref}; $j++ ) + { + $line = ${$blockref}[$j]; + + if ($line =~ /^\s*\Q$itemname\E\s+.*\#\#(\d+)\#\#\s*$/) + { + $itemnumber = $1; + + if ($itemnumber >= $itemnumbermax) + { + $printline = $line; + $itemnumbermax = $itemnumber; + } + } + } + + # removing the ending number from the printline + # and putting it into the array + + $printline =~ s/\#\#\d+\#\#//; + installer::remover::remove_leading_and_ending_whitespaces(\$line); + push(@newarray, $printline); + } + + return \@newarray; +} + +######################################################### +# Reading one variable defined in the zip list file +######################################################### + +sub getinfofromziplist +{ + my ($blockref, $variable) = @_; + + my $searchstring = ""; + my $line; + + for ( my $i = 0; $i <= $#{$blockref}; $i++ ) + { + $line = ${$blockref}[$i]; + + if ( $line =~ /^\s*\Q$variable\E\s+(.+?)\s*$/ ) # "?" for minimal matching + { + $searchstring = $1; + last; + } + } + + return \$searchstring; +} + +#################################################### +# Replacing variables in include path +#################################################### + +sub replace_all_variables_in_paths +{ + my ( $patharrayref, $variableshashref ) = @_; + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $line = ${$patharrayref}[$i]; + + my $key; + + foreach $key (sort { length ($b) <=> length ($a) } keys %{$variableshashref}) + { + my $value = $variableshashref->{$key}; + + if (( $line =~ /\{$key\}/ ) && ( $value eq "" )) { $line = ".\n"; } + + $line =~ s/\{\Q$key\E\}/$value/g; + } + + ${$patharrayref}[$i] = $line; + } +} + +#################################################### +# Replacing minor in include path +#################################################### + +sub replace_minor_in_paths +{ + my ( $patharrayref ) = @_; + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $line = ${$patharrayref}[$i]; + + $line =~ s/\.\{minor\}//g; + $line =~ s/\.\{minornonpre\}//g; + + ${$patharrayref}[$i] = $line; + } +} + +#################################################### +# Replacing packagetype in include path +#################################################### + +sub replace_packagetype_in_paths +{ + my ( $patharrayref ) = @_; + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $line = ${$patharrayref}[$i]; + + if (( $installer::globals::installertypedir ) && ( $line =~ /\{pkgtype\}/ )) + { + $line =~ s/\{pkgtype\}/$installer::globals::installertypedir/g; + } + + ${$patharrayref}[$i] = $line; + } +} + +#################################################### +# Removing ending separators in paths +#################################################### + +sub remove_ending_separator +{ + my ( $patharrayref ) = @_; + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $line = ${$patharrayref}[$i]; + + installer::remover::remove_ending_pathseparator(\$line); + + $line =~ s/\s*$//; + $line = $line . "\n"; + + ${$patharrayref}[$i] = $line; + } +} + +#################################################### +# Replacing languages in include path +#################################################### + +sub replace_languages_in_paths +{ + my ( $patharrayref, $languagesref ) = @_; + + installer::logger::include_header_into_logfile("Replacing languages in include paths:"); + + my @patharray = (); + my $infoline = ""; + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $line = ${$patharrayref}[$i]; + + if ( $line =~ /\$\(LANG\)/ ) + { + my $originalline = $line; + my $newline = ""; + + for ( my $j = 0; $j <= $#{$languagesref}; $j++ ) + { + my $language = ${$languagesref}[$j]; + $line =~ s/\$\(LANG\)/$language/g; + push(@patharray ,$line); + $newdir = $line; + $line = $originalline; + + installer::remover::remove_leading_and_ending_whitespaces(\$newline); + + # Is it necessary to refresh the global array, containing all files of all include paths? + if ( -d $newdir ) + { + # Checking if $newdir is empty + if ( ! installer::systemactions::is_empty_dir($newdir) ) + { + $installer::globals::refresh_includepaths = 1; + $infoline = "Directory $newdir exists and is not empty. Refreshing global file array is required.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + else + { + $infoline = "Directory $newdir is empty. No refresh of global file array required.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + else + { + $infoline = "Directory $newdir does not exist. No refresh of global file array required.\n"; + push( @installer::globals::logfileinfo, $infoline); + } + } + } + else # not language dependent include path + { + push(@patharray ,$line); + } + } + + return \@patharray; +} + +##################################################### +# Collecting all files from all include paths +##################################################### + +sub list_all_files_from_include_path +{ + my ( $patharrayref) = @_; + + installer::logger::include_header_into_logfile("Include paths:"); + + for ( my $i = 0; $i <= $#{$patharrayref}; $i++ ) + { + my $path = ${$patharrayref}[$i]; + installer::remover::remove_leading_and_ending_whitespaces(\$path); + my $infoline = "$path\n"; + push( @installer::globals::logfileinfo, $infoline); + } + + push( @installer::globals::logfileinfo, "\n"); + + return \@filesarray; +} + +##################################################### +# Collecting all files from all include paths +##################################################### + +sub set_manufacturer +{ + my ($allvariables) = @_; + my $manufacturer; + + if( defined $ENV{'OOO_VENDOR'} && $ENV{'OOO_VENDOR'} ne "" ) + { + $manufacturer = $ENV{'OOO_VENDOR'}; + } + elsif( defined $ENV{'USERNAME'} && $ENV{'USERNAME'} ne "" ) + { + $manufacturer = $ENV{'USERNAME'}; + } + elsif( defined $ENV{'USER'} && $ENV{'USER'} ne "" ) + { + $manufacturer = $ENV{'USER'}; + } + else + { + $manufacturer = "default"; + } + + $installer::globals::manufacturer = $manufacturer; + $installer::globals::longmanufacturer = $manufacturer; + + $allvariables->{'MANUFACTURER'} = $installer::globals::manufacturer; +} + +############################################################## +# A ProductVersion has to be defined. If it is not set in +# zip.lst, it is set now to "1" +############################################################## + +sub set_default_productversion_if_required +{ + my ($allvariables) = @_; + + if (!($allvariables->{'PRODUCTVERSION'})) + { + $allvariables->{'PRODUCTVERSION'} = 1; # FAKE + } +} + +#################################################### +# Removing .. in paths +#################################################### + +sub simplify_path +{ + my ( $pathref ) = @_; + + my $oldpath = $$pathref; + + my $change = 0; + + while ( $oldpath =~ /(^.*)(\Q$installer::globals::separator\E.*\w+?)(\Q$installer::globals::separator\E\.\.)(\Q$installer::globals::separator\E.*$)/ ) + { + my $part1 = $1; + my $part2 = $4; + $oldpath = $part1 . $part2; + $change = 1; + } + + if ( $change ) { $$pathref = $oldpath . "\n"; } +} + +#################################################### +# Removing ending separators in paths +#################################################### + +sub resolve_relative_paths +{ + my ( $patharrayref ) = @_; + + for my $path ( @{$patharrayref} ) + { + $path = rel2abs($path); + simplify_path(\$path); + } +} + +#################################################### +# Replacing variables inside zip list variables +# Example: {milestone} to be replaced by +# $installer::globals::lastminor +#################################################### + +sub replace_variables_in_ziplist_variables +{ + my ($blockref) = @_; + + for ( my $i = 0; $i <= $#{$blockref}; $i++ ) + { + ${$blockref}[$i] =~ s/\{milestone\}//; + ${$blockref}[$i] =~ s/\{minor\}//; + if ( $installer::globals::buildid ) { ${$blockref}[$i] =~ s/\{buildid\}/$installer::globals::buildid/; } + else { ${$blockref}[$i] =~ s/\{buildid\}//; } + if ( $installer::globals::build ) { ${$blockref}[$i] =~ s/\{buildsource\}/$installer::globals::build/; } + else { ${$blockref}[$i] =~ s/\{build\}//; } + } +} + +########################################################### +# Overwrite branding data in openoffice.lst that is defined in configure +########################################################### + +sub overwrite_branding +{ + my ($variableshashref) = @_; + $variableshashref->{'PROGRESSBARCOLOR'} = $ENV{'PROGRESSBARCOLOR'} , if( defined $ENV{'PROGRESSBARCOLOR'} && $ENV{'PROGRESSBARCOLOR'} ne "" ); + $variableshashref->{'PROGRESSSIZE'} = $ENV{'PROGRESSSIZE'} , if( defined $ENV{'PROGRESSSIZE'} && $ENV{'PROGRESSSIZE'} ne "" ); + $variableshashref->{'PROGRESSPOSITION'} = $ENV{'PROGRESSPOSITION'} , if( defined $ENV{'PROGRESSPOSITION'} && $ENV{'PROGRESSPOSITION'} ne "" ); + $variableshashref->{'PROGRESSFRAMECOLOR'} = $ENV{'PROGRESSFRAMECOLOR'} , if( defined $ENV{'PROGRESSFRAMECOLOR'} && $ENV{'PROGRESSFRAMECOLOR'} ne "" ); + $variableshashref->{'PROGRESSTEXTCOLOR'} = $ENV{'PROGRESSTEXTCOLOR'} , if( defined $ENV{'PROGRESSTEXTCOLOR'} && $ENV{'PROGRESSTEXTCOLOR'} ne "" ); + $variableshashref->{'PROGRESSTEXTBASELINE'} = $ENV{'PROGRESSTEXTBASELINE'} , if( defined $ENV{'PROGRESSTEXTBASELINE'} && $ENV{'PROGRESSTEXTBASELINE'} ne "" ); +} + +########################################################### +# Adding the lowercase variables into the variableshashref +########################################################### + +sub add_variables_to_allvariableshashref +{ + my ($variableshashref) = @_; + + my $lcvariable = lc($variableshashref->{'PRODUCTNAME'}); + $variableshashref->{'LCPRODUCTNAME'} = $lcvariable; + + if ($variableshashref->{'PRODUCTEXTENSION'}) + { + $variableshashref->{'LCPRODUCTEXTENSION'} = "\-" . lc($variableshashref->{'PRODUCTEXTENSION'}); # including the "-" ! + } + else + { + $variableshashref->{'LCPRODUCTEXTENSION'} = ""; + } + + if ( $installer::globals::languagepack ) { $variableshashref->{'PRODUCTADDON'} = $installer::globals::languagepackaddon; } + elsif ( $installer::globals::helppack ) { $variableshashref->{'PRODUCTADDON'} = $installer::globals::helppackaddon; } + else { $variableshashref->{'PRODUCTADDON'} = ""; } + + my $localbuild = $installer::globals::build; + if ( $localbuild =~ /^\s*(\w+?)(\d+)\s*$/ ) { $localbuild = $2; } # using "680" instead of "src680" + $variableshashref->{'PRODUCTMAJOR'} = $localbuild; + + $variableshashref->{'PRODUCTBUILDID'} = $installer::globals::buildid; +} + +1; -- cgit v1.2.3