summaryrefslogtreecommitdiffstats
path: root/solenv/bin
diff options
context:
space:
mode:
Diffstat (limited to 'solenv/bin')
-rwxr-xr-xsolenv/bin/add-modelines169
-rwxr-xr-xsolenv/bin/assemble-flatpak-appdata.sh96
-rwxr-xr-xsolenv/bin/assemble-flatpak-desktop.sh42
-rwxr-xr-xsolenv/bin/assemble-flatpak.sh71
-rwxr-xr-xsolenv/bin/bin_library_info.sh188
-rwxr-xr-xsolenv/bin/call_installer.sh47
-rw-r--r--solenv/bin/clipatchconfig.pl122
-rw-r--r--solenv/bin/concat-deps.c1231
-rwxr-xr-xsolenv/bin/constructors.py27
-rwxr-xr-xsolenv/bin/create-ids18
-rwxr-xr-xsolenv/bin/create-tags47
-rw-r--r--solenv/bin/createcomponent.xslt87
-rw-r--r--solenv/bin/dbgsv.ini20
-rw-r--r--solenv/bin/desktop-translate.py172
-rw-r--r--solenv/bin/exectest.pl103
-rwxr-xr-xsolenv/bin/finish-gbuild-trace.py137
-rwxr-xr-xsolenv/bin/fix-includes.pl95
-rwxr-xr-xsolenv/bin/gdb-core-bt.sh71
-rw-r--r--solenv/bin/gdb_cxa-atexit_trace-stdout8
-rw-r--r--solenv/bin/gdbtrycatchtrace21
-rw-r--r--solenv/bin/gdbtrycatchtrace-stdout14
-rwxr-xr-xsolenv/bin/generate-flatpak-manifest.sh23
-rw-r--r--solenv/bin/generate-tokens.py80
-rw-r--r--solenv/bin/gentoken.py54
-rw-r--r--solenv/bin/getcompver.awk84
-rwxr-xr-xsolenv/bin/hrcex39
-rw-r--r--solenv/bin/id-lang.map114
-rw-r--r--solenv/bin/image-sort.py155
-rwxr-xr-xsolenv/bin/install-gdb-printers143
-rwxr-xr-xsolenv/bin/install-sh3
-rw-r--r--solenv/bin/job-limiter.cpp116
-rwxr-xr-xsolenv/bin/localestr20
-rw-r--r--solenv/bin/macosx-change-install-names.pl95
-rwxr-xr-xsolenv/bin/macosx-codesign-app-bundle152
-rw-r--r--solenv/bin/macosx_menubar_modification.xsl43
-rwxr-xr-xsolenv/bin/make-raspbian-root-tarball81
-rw-r--r--solenv/bin/make_installer.pl26
-rw-r--r--solenv/bin/mkdocs.Makefile46
-rwxr-xr-xsolenv/bin/mkdocs.sh281
-rwxr-xr-xsolenv/bin/mkdocs_portal.sh157
-rwxr-xr-xsolenv/bin/mkonedoc.sh45
-rw-r--r--solenv/bin/modules/RepositoryHelper.pm161
-rw-r--r--solenv/bin/modules/installer.pm1700
-rw-r--r--solenv/bin/modules/installer/control.pm478
-rw-r--r--solenv/bin/modules/installer/converter.pm187
-rw-r--r--solenv/bin/modules/installer/copyproject.pm105
-rw-r--r--solenv/bin/modules/installer/download.pm666
-rw-r--r--solenv/bin/modules/installer/environment.pm134
-rw-r--r--solenv/bin/modules/installer/epmfile.pm2665
-rw-r--r--solenv/bin/modules/installer/exiter.pm33
-rw-r--r--solenv/bin/modules/installer/filelists.pm157
-rw-r--r--solenv/bin/modules/installer/files.pm120
-rw-r--r--solenv/bin/modules/installer/globals.pm294
-rw-r--r--solenv/bin/modules/installer/helppack.pm530
-rw-r--r--solenv/bin/modules/installer/languagepack.pm512
-rw-r--r--solenv/bin/modules/installer/languages.pm145
-rw-r--r--solenv/bin/modules/installer/logger.pm256
-rw-r--r--solenv/bin/modules/installer/packagelist.pm843
-rw-r--r--solenv/bin/modules/installer/parameter.pm545
-rw-r--r--solenv/bin/modules/installer/pathanalyzer.pm69
-rw-r--r--solenv/bin/modules/installer/profiles.pm226
-rw-r--r--solenv/bin/modules/installer/remover.pm53
-rw-r--r--solenv/bin/modules/installer/scpzipfiles.pm146
-rw-r--r--solenv/bin/modules/installer/scriptitems.pm2373
-rw-r--r--solenv/bin/modules/installer/setupscript.pm489
-rw-r--r--solenv/bin/modules/installer/simplepackage.pm764
-rw-r--r--solenv/bin/modules/installer/strip.pm136
-rw-r--r--solenv/bin/modules/installer/systemactions.pm1332
-rw-r--r--solenv/bin/modules/installer/windows/admin.pm518
-rw-r--r--solenv/bin/modules/installer/windows/assembly.pm282
-rw-r--r--solenv/bin/modules/installer/windows/binary.pm70
-rw-r--r--solenv/bin/modules/installer/windows/component.pm508
-rw-r--r--solenv/bin/modules/installer/windows/createfolder.pm157
-rw-r--r--solenv/bin/modules/installer/windows/directory.pm637
-rw-r--r--solenv/bin/modules/installer/windows/feature.pm406
-rw-r--r--solenv/bin/modules/installer/windows/featurecomponent.pm168
-rw-r--r--solenv/bin/modules/installer/windows/file.pm1023
-rw-r--r--solenv/bin/modules/installer/windows/font.pm72
-rw-r--r--solenv/bin/modules/installer/windows/icon.pm71
-rw-r--r--solenv/bin/modules/installer/windows/idtglobal.pm1865
-rw-r--r--solenv/bin/modules/installer/windows/inifile.pm124
-rw-r--r--solenv/bin/modules/installer/windows/language.pm44
-rw-r--r--solenv/bin/modules/installer/windows/media.pm205
-rw-r--r--solenv/bin/modules/installer/windows/mergemodule.pm1707
-rw-r--r--solenv/bin/modules/installer/windows/msiglobal.pm1687
-rw-r--r--solenv/bin/modules/installer/windows/msishortcutproperty.pm146
-rw-r--r--solenv/bin/modules/installer/windows/property.pm570
-rw-r--r--solenv/bin/modules/installer/windows/registry.pm410
-rw-r--r--solenv/bin/modules/installer/windows/removefile.pm144
-rw-r--r--solenv/bin/modules/installer/windows/shortcut.pm665
-rw-r--r--solenv/bin/modules/installer/windows/strip.pm152
-rw-r--r--solenv/bin/modules/installer/windows/update.pm623
-rw-r--r--solenv/bin/modules/installer/windows/upgrade.pm83
-rw-r--r--solenv/bin/modules/installer/worker.pm924
-rw-r--r--solenv/bin/modules/installer/ziplist.pm829
-rw-r--r--solenv/bin/modules/par2script/check.pm337
-rw-r--r--solenv/bin/modules/par2script/converter.pm52
-rw-r--r--solenv/bin/modules/par2script/exiter.pm112
-rw-r--r--solenv/bin/modules/par2script/files.pm64
-rw-r--r--solenv/bin/modules/par2script/globals.pm67
-rw-r--r--solenv/bin/modules/par2script/module.pm255
-rw-r--r--solenv/bin/modules/par2script/parameter.pm145
-rw-r--r--solenv/bin/modules/par2script/remover.pm42
-rw-r--r--solenv/bin/modules/par2script/undefine.pm135
-rw-r--r--solenv/bin/modules/par2script/work.pm414
-rw-r--r--solenv/bin/modules/pre2par/directory.pm45
-rw-r--r--solenv/bin/modules/pre2par/exiter.pm61
-rw-r--r--solenv/bin/modules/pre2par/files.pm55
-rw-r--r--solenv/bin/modules/pre2par/globals.pm51
-rw-r--r--solenv/bin/modules/pre2par/language.pm135
-rw-r--r--solenv/bin/modules/pre2par/parameter.pm144
-rw-r--r--solenv/bin/modules/pre2par/pathanalyzer.pm67
-rw-r--r--solenv/bin/modules/pre2par/remover.pm34
-rw-r--r--solenv/bin/modules/pre2par/systemactions.pm81
-rw-r--r--solenv/bin/modules/pre2par/work.pm297
-rw-r--r--solenv/bin/modules/t/installer-packagelist.t40
-rw-r--r--solenv/bin/modules/t/installer-profiles.t43
-rw-r--r--solenv/bin/modules/t/installer-scpzipfiles.t54
-rw-r--r--solenv/bin/modules/t/installer-setupscript.t58
-rwxr-xr-xsolenv/bin/native-code.py969
-rwxr-xr-xsolenv/bin/ooinstall118
-rw-r--r--solenv/bin/optionalimplementations.xslt21
-rw-r--r--solenv/bin/pack_images.py612
-rw-r--r--solenv/bin/packcomponents.xslt45
-rw-r--r--solenv/bin/packregistry.xslt80
-rw-r--r--solenv/bin/par2script.pl106
-rw-r--r--solenv/bin/polib.py1868
-rw-r--r--solenv/bin/pre2par.pl62
-rwxr-xr-xsolenv/bin/run-configure9
-rwxr-xr-xsolenv/bin/uiex38
-rw-r--r--solenv/bin/uiimagelist.xsl36
-rwxr-xr-xsolenv/bin/version.py49
-rwxr-xr-xsolenv/bin/write_classpath.sh16
133 files changed, 40569 insertions, 0 deletions
diff --git a/solenv/bin/add-modelines b/solenv/bin/add-modelines
new file mode 100755
index 0000000000..a3f59fe8ea
--- /dev/null
+++ b/solenv/bin/add-modelines
@@ -0,0 +1,169 @@
+#!/usr/bin/env bash
+#
+# 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/.
+#
+
+# add-modelines, a simple script to add comments to
+# the beginning and end of source files for LibreOffice devs
+
+# Blame goes to Jesse Adelman (at least at first)
+# someone AT boldandbusted dotty-dot com
+# http://www.boldandbusted.com/
+# (c) 2010 Bold and Busted LLC
+
+# NOTE: At present, this script only works for files with C-like comments.
+# NOTE: If you don't specify -p, the script will act on the current working directory.
+# NOTE: If no arguments are specified, the definitions below are in effect.
+
+# TO DO
+# - Deuglify?
+# - Make source file type agnostic modelines?
+# - Too many/too few comments?
+# - Handle top level source directories with whitespace names? (Do they exist?)
+
+# Turn off globbing, helps with SourceFiles
+set -f
+
+# POSIX
+set -o posix
+
+# Change these to taste
+FirstLine='/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */'
+LastLine='/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */'
+SourceFiles='*.cxx *.cpp *.hxx *.hpp *.c *.h *.m *.mm *.idl *.src *.hrc'
+
+# Set defaults (don't change these)
+ModelineReplace="false"
+
+# Functions
+
+function SetEnvironment()
+{
+ if [ -n "$(which tail)" ] && [ -n "$(which head)" ]; then
+ {
+ headCMD=$(which head)
+ tailCMD=$(which tail)
+ }
+ else
+ {
+ echo "Missing head or tail, exiting..."
+ exit 1
+ }
+ fi
+ if [ -n "$(which find)" ]; then
+ findCMD=$(which find)
+ else
+ {
+ echo "Missing find, exiting..."
+ exit 1
+ }
+ fi
+}
+
+function EditFile()
+{
+ local FileToEdit
+ local currentFirstLine
+ local currentLastLine
+
+ FileToEdit="$1"
+
+ currentFirstLine=$($headCMD -1 "$FileToEdit")
+ currentLastLine=$($tailCMD -1 "$FileToEdit")
+
+ case "$ModelineReplace" in
+ "true" )
+ if [ "${currentFirstLine:0:6}" = "${FirstLine:0:6}" ]; then
+ {
+ echo "$FirstLine" > "$FileToEdit".new
+ $tailCMD -n +2 "$FileToEdit" >> "$FileToEdit".new
+ }
+ fi
+ if [ -e "$FileToEdit.new" ]; then
+ {
+ echo "$LastLine" >> "$FileToEdit".new
+ }
+ fi
+ if [ "${currentLastLine:0:6}" = "${LastLine:0:6}" ]; then
+ {
+ $headCMD -n -1 "$FileToEdit" > "$FileToEdit".new
+ echo "$LastLine" >> "$FileToEdit".new
+ }
+ fi
+ mv "$FileToEdit".new "$FileToEdit"
+ echo "$FileToEdit updated" ;;
+ "false" )
+ if [ "${currentFirstLine:0:6}" != "${FirstLine:0:6}" ]; then
+ if [ "${currentLastLine:0:6}" != "${LastLine:0:6}" ]; then
+ {
+ echo "$FirstLine" > "$FileToEdit".new
+ cat "$FileToEdit" >> "$FileToEdit".new
+ if [ "x${currentLastLine}" != "x" ] ; then
+ echo "" >> "$FileToEdit".new
+ fi
+ echo "$LastLine" >> "$FileToEdit".new
+ mv "$FileToEdit".new "$FileToEdit"
+ echo "$FileToEdit updated"
+ }
+ fi
+ fi ;;
+ esac
+}
+
+function PrintUsage()
+{
+ echo "Usage: $0 [-z] [-s \"<sourcefile glob>\"] [-p <path to source>]"
+}
+
+# Main
+
+SetEnvironment
+
+# Get command line options
+
+while getopts "zs:p:" opt; do
+ case $opt in
+ z) ModelineReplace="true" ;;
+ s) SourceFiles="$OPTARG" ;;
+ p) findPath="$OPTARG" ;;
+ *) PrintUsage
+ exit 1 ;;
+ esac
+done
+
+if [ $OPTIND -gt 1 ]; then
+ shift $((OPTIND - 1))
+fi
+
+if [ $# -gt 1 ]; then
+{
+ PrintUsage
+ echo "Remember to quote the source file globs after -s"
+ exit 1
+}
+fi
+
+# Create GNU find expressions that traverse the filesystem once and only once
+if [ -z "$findPath" ]; then
+ findArgs='.'
+ else
+ findArgs="$findPath"
+fi
+
+for FileType in ${SourceFiles}; do
+ findArgs="$findArgs"' ( -iname '"$FileType"' -print -o -true ) -a '
+done
+
+# This gets rid of the final " -a " in the find argument list
+findArgs=(${findArgs:0:(${#findArgs}-3)})
+
+for file in $($findCMD "${findArgs[@]}"); do
+ EditFile "$file"
+ echo "Completed: " "$file"
+done
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/solenv/bin/assemble-flatpak-appdata.sh b/solenv/bin/assemble-flatpak-appdata.sh
new file mode 100755
index 0000000000..4896cf3f20
--- /dev/null
+++ b/solenv/bin/assemble-flatpak-appdata.sh
@@ -0,0 +1,96 @@
+#! /bin/bash
+#
+# 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/.
+#
+
+# Part of solenv/bin/assemble-flatpak.sh that is shared with a downstream mechanism of building a
+# Flatpak from a Fedora libreoffice.spec file.
+#
+# Arguments:
+# $1 pathname, ending in a slash, of the directory into which to put the target
+# org.libreoffice.LibreOffice.appdata.xml file
+# $2 "1" if a <releases> section shall be included in the target
+# org.libreoffice.LibreOffice.appdata.xml file, "0" if not
+
+set -e
+
+## org.libreoffice.LibreOffice.appdata.xml is manually derived from the various
+## inst/share/metainfo/libreoffice-*.appdata.xml (at least recent GNOME Software
+## doesn't show more than five screenshots anyway, so restrict to one each from
+## the five libreoffice-*.appdata.xml: Writer, Calc, Impress, Draw, Base):
+cat <<\EOF >"${1?}"org.libreoffice.LibreOffice.appdata.xml
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop">
+ <id>org.libreoffice.LibreOffice.desktop</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>MPL-2.0</project_license>
+ <name>LibreOffice</name>
+ <summary>The LibreOffice productivity suite</summary>
+ <description>
+ <p>LibreOffice is a powerful office suite. Its clean interface and
+ feature-rich tools help you unleash your creativity and enhance your
+ productivity. LibreOffice includes several applications that make it the most
+ powerful Free and Open Source office suite on the market: Writer (word
+ processing), Calc (spreadsheets), Impress (presentations), Draw (vector
+ graphics and flowcharts), Base (databases), and Math (formula editing).</p>
+ <p>LibreOffice supports opening and saving into a wide variety of formats, so
+ you can easily share documents with users of other popular office suites
+ without worrying about compatibility.</p>
+ </description>
+ <url type="homepage">http://www.libreoffice.org/discover/libreoffice/</url>
+ <url type="bugtracker">https://bugs.documentfoundation.org/</url>
+ <url type="donation">https://donate.libreoffice.org/</url>
+ <url type="faq">https://wiki.documentfoundation.org/Faq</url>
+ <url type="help">http://www.libreoffice.org/get-help/documentation/</url>
+ <url type="translate">https://wiki.documentfoundation.org/Translating_LibreOffice</url>
+ <screenshots>
+ <screenshot type="default">
+ <image>https://hub.libreoffice.org/screenshots/writer-01.png</image>
+ <caption><!-- Describe this screenshot in less than ~10 words --></caption>
+ </screenshot>
+ <screenshot>
+ <image>https://hub.libreoffice.org/screenshots/calc-02.png</image>
+ <caption><!-- Describe this screenshot in less than ~10 words --></caption>
+ </screenshot>
+ <screenshot>
+ <image>https://hub.libreoffice.org/screenshots/impress-01.png</image>
+ <caption><!-- Describe this screenshot in less than ~10 words --></caption>
+ </screenshot>
+ <screenshot>
+ <image>https://hub.libreoffice.org/screenshots/draw-02.png</image>
+ <caption><!-- Describe this screenshot in less than ~10 words --></caption>
+ </screenshot>
+ <screenshot>
+ <image>https://hub.libreoffice.org/screenshots/base-02.png</image>
+ <caption><!-- Describe this screenshot in less than ~10 words --></caption>
+ </screenshot>
+ </screenshots>
+ <developer_name>The Document Foundation</developer_name>
+ <update_contact>libreoffice_at_lists.freedesktop.org</update_contact>
+ <kudos>
+ <kudo>HiDpiIcon</kudo>
+ <kudo>HighContrast</kudo>
+ <kudo>ModernToolkit</kudo>
+ <kudo>UserDocs</kudo>
+ </kudos>
+ <content_rating type="oars-1.0"/>
+EOF
+
+if [ "${2?}" = 1 ]
+then
+ cat <<EOF >>"${1?}"org.libreoffice.LibreOffice.appdata.xml
+ <releases>
+ <release
+ version="${LIBO_VERSION_MAJOR?}.${LIBO_VERSION_MINOR?}.${LIBO_VERSION_MICRO?}.${LIBO_VERSION_PATCH?}"
+ date="$(date +%Y-%m-%d)"/>
+ </releases>
+EOF
+fi
+
+cat <<\EOF >>"${1?}"org.libreoffice.LibreOffice.appdata.xml
+</component>
+EOF
diff --git a/solenv/bin/assemble-flatpak-desktop.sh b/solenv/bin/assemble-flatpak-desktop.sh
new file mode 100755
index 0000000000..6d06de4fbb
--- /dev/null
+++ b/solenv/bin/assemble-flatpak-desktop.sh
@@ -0,0 +1,42 @@
+#! /bin/bash
+#
+# 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/.
+#
+
+# Part of solenv/bin/assemble-flatpak.sh that is shared with a downstream mechanism of building a
+# Flatpak from a Fedora libreoffice.spec file.
+#
+# Arguments:
+# $1 pathname, ending in a slash, of the directory containing the source libreoffice-*.desktop
+# files
+# $2 pathname, ending in a slash, of the directory into which to put the target
+# org.libreoffice.LibreOffice.*.desktop files
+
+set -e
+
+## libreoffice-*.desktop -> org.libreoffice.LibreOffice.*.desktop:
+for i in "${1?}"libreoffice-*.desktop
+do
+ sed -e 's/^Icon=libreoffice-/Icon=org.libreoffice.LibreOffice./' "$i" \
+ >"${2?}"org.libreoffice.LibreOffice."${i#"${1?}"libreoffice-}"
+done
+mv "${2?}"org.libreoffice.LibreOffice.startcenter.desktop "${2?}"org.libreoffice.LibreOffice.desktop
+
+# Flatpak .desktop exports take precedence over system ones due to
+# the order of XDG_DATA_DIRS - re-associating text/plain seems a bit much
+sed -i "s/text\/plain;//" "${2?}"org.libreoffice.LibreOffice.writer.desktop
+
+desktop-file-edit --set-key=X-Endless-Alias --set-value=libreoffice-startcenter \
+ --set-key=X-Flatpak-RenamedFrom --set-value='libreoffice-startcenter.desktop;' \
+ "${2?}"org.libreoffice.LibreOffice.desktop
+for i in base calc draw impress math writer xsltfilter
+do
+ desktop-file-edit --set-key=X-Endless-Alias --set-value=libreoffice-"$i" \
+ --set-key=X-Flatpak-RenamedFrom \
+ --set-value="libreoffice-$i.desktop;org.libreoffice.LibreOffice-$i.desktop;" \
+ "${2?}"org.libreoffice.LibreOffice."$i".desktop
+done
diff --git a/solenv/bin/assemble-flatpak.sh b/solenv/bin/assemble-flatpak.sh
new file mode 100755
index 0000000000..8d45da6cc1
--- /dev/null
+++ b/solenv/bin/assemble-flatpak.sh
@@ -0,0 +1,71 @@
+#! /bin/bash
+#
+# 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/.
+#
+
+# Assemble Flatpak app files and metadata under /app/, copying from the
+# installation tree generated by 'make distro-pack-install' (at
+# $PREFIXDIR):
+
+set -e
+
+cp -r "${PREFIXDIR?}"/lib/libreoffice /app/
+ln -s /app/libreoffice/program/soffice /app/bin/libreoffice
+
+mkdir -p /app/share/applications
+"${SRCDIR?}"/solenv/bin/assemble-flatpak-desktop.sh "${PREFIXDIR?}"/share/applications/ \
+ /app/share/applications/
+
+## icons/hicolor/*/apps/libreoffice-* ->
+## icons/hicolor/*/apps/org.libreoffice.LibreOffice-*:
+mkdir -p /app/share/icons
+for i in "${PREFIXDIR?}"/share/icons/hicolor/*/apps/libreoffice-*
+do
+ mkdir -p \
+ "$(dirname /app/share/icons/hicolor/"${i#"${PREFIXDIR?}"/share/icons/hicolor/}")"
+ cp -a "$i" \
+ "$(dirname /app/share/icons/hicolor/"${i#"${PREFIXDIR?}"/share/icons/hicolor/}")"/"$(basename "$i")"
+ cp -a "$i" \
+ "$(dirname /app/share/icons/hicolor/"${i#"${PREFIXDIR?}"/share/icons/hicolor/}")"/org.libreoffice.LibreOffice."${i##*/apps/libreoffice-}"
+done
+
+mkdir -p /app/share/runtime/locale
+for i in $(ls /app/libreoffice/program/resource)
+do
+ lang="${i%[_@]*}"
+ mkdir -p /app/share/runtime/locale/"${lang}"/resource
+ mv /app/libreoffice/program/resource/"${i}" /app/share/runtime/locale/"${lang}"/resource
+ ln -s ../../../share/runtime/locale/"${lang}"/resource/"${i}" /app/libreoffice/program/resource
+done
+
+for i in /app/libreoffice/share/registry/Langpack-*.xcd /app/libreoffice/share/registry/res/{fcfg_langpack,registry}_*.xcd
+do
+ basename="$(basename "${i}" .xcd)"
+ lang="${basename#Langpack-}"
+ lang="${lang#fcfg_langpack_}"
+ lang="${lang#registry_}"
+
+ # ship the base app with at least one Langpack/fcfg_langpack
+ if [ "${lang}" = "en-US" ]
+ then
+ continue
+ fi
+
+ lang="${lang%-*}"
+ mkdir -p /app/share/runtime/locale/"${lang}"/registry
+ mv "${i}" /app/share/runtime/locale/"${lang}"/registry
+ ln -rs /app/share/runtime/locale/"${lang}"/registry/"${basename}".xcd "${i}"
+done
+
+mkdir -p /app/share/appdata
+"${SRCDIR?}"/solenv/bin/assemble-flatpak-appdata.sh /app/share/appdata/ 1
+
+## see <https://github.com/flatpak/flatpak/blob/master/app/
+## flatpak-builtins-build-finish.c> for further places where build-finish would
+## look for data:
+## cp ... /app/share/dbus-1/services/
+## cp ... /app/share/gnome-shell/search-providers/
diff --git a/solenv/bin/bin_library_info.sh b/solenv/bin/bin_library_info.sh
new file mode 100755
index 0000000000..fcd68c0e86
--- /dev/null
+++ b/solenv/bin/bin_library_info.sh
@@ -0,0 +1,188 @@
+#!/usr/bin/env bash
+#
+# Copyright (C) 2013 Norbert Thiebaud
+# License: GPLv3
+#
+
+do_help()
+{
+cat <<EOF
+bin_library_info.sh is a tool that create a unique filename for a binary tar file that
+contain the build of the given source tarfile. the unicity is based on the source tarfile which contains
+a md5 already and the calculated sha1 of config_host_.mk and of the tree object associated with the top_level_module
+in git.
+
+syntax: bin_library_info.sh -m|--module <top_level_module> -l|--location <TARFILE_LOCATION> -s|--srcdir <SRCDIR> -b <BUILDDIR> -r|--tarfile <LIBRARY_TARFILE> [ -m|--mode verify|name ]
+
+the default mode is 'name' which just print the associated binary tarfile name.
+in 'verify' mode the program print the name if the associated binary tarfile exist
+and print nothing and return an error code if the file does not exist
+
+Note: --location --builddir and --srcdir are optional if they are already in the env in the form of TARFILE_LOCATION and BUILDDIR SRCDIR respectively
+EOF
+
+exit 0;
+}
+
+die()
+{
+ [ "$V" ] && echo "Error:" "$@"
+ exit -1;
+}
+
+
+get_config_sha()
+{
+ pushd "${SRCDIR?}" > /dev/null
+ git hash-object "${BUILDDIR?}"/config_host.mk
+ popd > /dev/null
+}
+
+get_library_gbuild_sha()
+{
+ local module="$1"
+
+ pushd "${SRCDIR?}" > /dev/null
+ if [ -d "${SRCDIR}/external/${module?}" ] ; then
+ git ls-tree -d HEAD "external/${module?}" | cut -f 1 | cut -d " " -f 3
+ else
+ git ls-tree -d HEAD | "{module?}" | cut -f 1 | cut -d " " -f 3
+ fi
+ popd > /dev/null
+}
+
+
+determine_binary_package_name()
+{
+ local module="$1"
+ local tarball="$2"
+ local csha=""
+ local gsha=""
+ local binfile=""
+
+ csha=$(get_config_sha)
+ gsha=$(get_library_gbuild_sha "${module?}")
+ if [ -n "${csha?}" -a -n "${gsha}" ] ; then
+ binfile="${csha?}_${gsha?}_${tarball?}.${PLATFORM?}.tar.gz"
+ fi
+ echo "${binfile}"
+
+}
+
+MODULE=""
+SOURCE_TARFILE=""
+MODE="name"
+V=1
+
+while [ "${1}" != "" ]; do
+ parm=${1%%=*}
+ arg=${1#*=}
+ has_arg=
+ if [ "${1}" != "${parm?}" ] ; then
+ has_arg=1
+ else
+ arg=""
+ fi
+
+ case "${parm}" in
+ -h|--help) # display help
+ do_help
+ exit
+ ;;
+ -b|--builddir)
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ BUILDDIR="${arg}"
+ ;;
+ -o|--module)
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ MODULE="${arg}"
+ ;;
+
+ -l|--location)
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ TARFILE_LOCATION="${arg}"
+ ;;
+ -m|--mode)
+ # test if the binary package exist
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ MODE="$arg"
+ ;;
+ -p|--platform)
+ # test if the binary package exist
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ PLATFORM="$arg"
+ ;;
+ -q)
+ V=0
+ ;;
+ -s|--srcdir) # do not override the local autogen.lastrun if present
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ SRCDIR="${arg}"
+ ;;
+
+ -t|--tarfile)
+ if [ -z "${has_arg}" ] ; then
+ shift;
+ arg="$1"
+ fi
+ SOURCE_TARFILE="${arg}"
+ ;;
+ -*)
+ die "Invalid option $1"
+ ;;
+ *)
+ die "Invalid argument $1"
+ ;;
+ esac
+ shift
+done
+
+if [ -z "${MODULE?}" ] ; then
+ die "Missing --module"
+fi
+if [ -z "${TARFILE_LOCATION}" ] ; then
+ die "Missing --location"
+fi
+if [ -z "${SOURCE_TARFILE}" ] ; then
+ die "Missing --tarfile"
+fi
+if [ -z "${SRCDIR}" ] ; then
+ die "Missing --srcdir"
+fi
+
+
+BINARY_TARFILE="$(determine_binary_package_name ${MODULE?} ${SOURCE_TARFILE?})"
+
+if [ -z "${BINARY_TARFILE}" ] ; then
+ exit 2
+fi
+
+if [ "${MODE?}" = "verify" ] ; then
+ if [ -f "${TARFILE_LOCATION?}/${BINARY_TARFILE?}" ] ; then
+ echo "${BINARY_TARFILE?}"
+ else
+ exit 1
+ fi
+else
+ echo "${BINARY_TARFILE?}"
+fi
+
+exit 0
diff --git a/solenv/bin/call_installer.sh b/solenv/bin/call_installer.sh
new file mode 100755
index 0000000000..8b8032f7f3
--- /dev/null
+++ b/solenv/bin/call_installer.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# unpack parameters
+VERBOSITY=$1; shift
+MSITEMPL=$(echo "$@" | cut -d ':' -f 1)
+LANG=$(echo "$@" | cut -d ':' -f 2)
+PRODNAME=$(echo "$@" | cut -d ':' -f 3)
+EXTENSION=$(echo "$@" | cut -d ':' -f 4)
+PKGFORMAT=$(echo "$@" | cut -d ':' -f 5)
+STRIP=$(echo "$@" | cut -d ':' -f 6)
+
+# need to hack buildid?
+if [ "${PKGFORMAT}${LIBO_VERSION_PATCH}" = "deb0" ] || \
+ [ "${PKGFORMAT}${LIBO_VERSION_PATCH}" = "rpm0" ] ; then
+ LIBO_VERSION_PATCH=1
+fi
+
+# switch to verbose?
+if [ "${VERBOSITY}" = "-verbose" ] ; then
+ set -x
+fi
+
+# add extra params for Windows
+EXTRA_PARAMS=
+if [ "${OS}" = "WNT" ] && [ -n "${MSITEMPL}" ]; then
+ EXTRA_PARAMS="${EXTRA_PARAMS} -msitemplate ${WORKDIR}/CustomTarget/instsetoo_native/install/msi_templates/${MSITEMPL}"
+ EXTRA_PARAMS="${EXTRA_PARAMS} -msilanguage ${WORKDIR}/CustomTarget/instsetoo_native/install/win_ulffiles"
+fi
+
+# need to strip?
+if [ "${STRIP}" = "strip" ] ; then
+ export ENABLE_STRIP=1
+fi
+
+# shellcheck disable=SC2086
+# shellcheck disable=SC2154
+${PERL} -w "${SRCDIR}"/solenv/bin/make_installer.pl \
+ -f "${BUILDDIR}"/instsetoo_native/util/openoffice.lst \
+ -l "${LANG}" \
+ -p "${PRODUCTNAME_WITHOUT_SPACES}${PRODNAME}" \
+ -u "${instsetoo_OUT}" \
+ -packer "${COMPRESSIONTOOL}" \
+ -buildid "${LIBO_VERSION_PATCH}" \
+ ${EXTRA_PARAMS:+$EXTRA_PARAMS} \
+ ${EXTENSION:+"$EXTENSION"} \
+ -format "${PKGFORMAT}" \
+ "${VERBOSITY}"
diff --git a/solenv/bin/clipatchconfig.pl b/solenv/bin/clipatchconfig.pl
new file mode 100644
index 0000000000..389fffc5cf
--- /dev/null
+++ b/solenv/bin/clipatchconfig.pl
@@ -0,0 +1,122 @@
+#
+# 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 .
+#
+
+use warnings;
+use strict;
+
+sub trim;
+sub readRedirectionValues($);
+
+my $usage =
+ "Usage is: \n clipatchconfig.pl configTemplate redirections policyConfig
+
+ configTemplate: The config file which is used for the policy assembly. It
+ contains place holders for the binding redirection.
+
+ redirections: file containing the values for oldVersion and newVersion tags
+ which are used in the BindingRedirect element of the config files.
+
+ policyConfig: Name of the file in which we want to write the config file.
+";
+
+
+if (scalar @ARGV < 3) {
+ print $usage;
+ exit -1;
+}
+
+
+my %redirectionValue = readRedirectionValues($ARGV[1]);
+
+
+#Read config file in which we will replace the versions
+$/ = undef;
+open TEMPLATE, $ARGV[0] or die $!;
+my $templ = <TEMPLATE>;
+
+#Open the config file we are goint to write to
+open CONFIG, "> $ARGV[2]" or die "Cannot write to $ARGV[2] $!";
+
+#No substitute the place holders for oldVersion and new Version in the config template with
+#the values obtained from the redirections file
+for (keys %redirectionValue) {
+ $templ=~ s/\b$_\b/$redirectionValue{$_}/;
+}
+#Write the config file
+print CONFIG $templ;
+
+#Reads the key value pairs from the files, which name must be passed in
+#the parameter. The file contains lines of the form name=value, for example
+#CLI_URETYPES_OLD_VERSION=1.1.0.0-1.1.1.0
+sub readRedirectionValues($)
+{
+ #Read in the values for the version redirection
+ open REDIR, $_[0] or die $!;
+
+ my %redirectionValues;
+
+ while (<REDIR>)
+ {
+ chomp;
+ my $trimmed;
+ #Skip empty lines
+ if (length($trimmed = trim($_)) == 0) {
+ next;
+ }
+
+ #Skip comment symbol: #
+ if ($trimmed =~ /^#/) {
+ next;
+ }
+
+ my @lineParts = split /=/,$_;
+
+ #Check if we have valid name value pairs.
+ if (scalar @lineParts != 2) {
+ print "Error: Values in $ARGV[1] are not correct (Entries must have the form name=value). Invalid line: \n$_\n";
+ exit -1;
+ }
+
+ #Trim the strings and check if they still contain characters
+ my $name = trim($lineParts[0]);
+ my $value = trim($lineParts[1]);
+ if (length($name) == 0 || length($value) == 0) {
+ print "Error: Values in $ARGV[1] are not correct. Invalid line: \n$_\n";
+ exit -1;
+ }
+
+ #Check if we have duplicate key names
+ for (keys %redirectionValues) {
+ if ( $name eq $_) {
+ print "Error: Values in $ARGV[1] are not correct. The name $_ is not unique.\n";
+ exit -1;
+ }
+ }
+
+ $redirectionValues{$name} = $value;
+ }
+ return %redirectionValues;
+}
+
+sub trim($)
+{
+ my $string = shift;
+ $string =~ s/^\s+//;
+ $string =~ s/\s+$//;
+ return $string;
+} \ No newline at end of file
diff --git a/solenv/bin/concat-deps.c b/solenv/bin/concat-deps.c
new file mode 100644
index 0000000000..996f874bf6
--- /dev/null
+++ b/solenv/bin/concat-deps.c
@@ -0,0 +1,1231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2011 Norbert Thiebaud
+ * License: GPLv3
+ */
+
+/* define to activate stats reporting on hash usage*/
+/* #define HASH_STAT */
+
+/* ===============================================
+ * Set-up: defines to identify the system and system related properties
+ * ===============================================
+ */
+
+#ifdef __APPLE__
+#ifdef __x86_64__
+#undef CORE_BIG_ENDIAN
+#define CORE_LITTLE_ENDIAN
+#else
+#define CORE_BIG_ENDIAN
+#undef CORE_LITTLE_ENDIAN
+#endif
+
+#endif
+
+#ifdef _MSC_VER
+#undef CORE_BIG_ENDIAN
+#define CORE_LITTLE_ENDIAN
+#endif /* Def _MSC_VER */
+
+#if defined(__linux) || defined(__FreeBSD__)
+#include <sys/param.h>
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#undef CORE_BIG_ENDIAN
+#define CORE_LITTLE_ENDIAN
+#else /* !(__BYTE_ORDER == __LITTLE_ENDIAN) */
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define CORE_BIG_ENDIAN
+#undef CORE_LITTLE_ENDIAN
+#endif /* __BYTE_ORDER == __BIG_ENDIAN */
+#endif /* !(__BYTE_ORDER == __LITTLE_ENDIAN) */
+#endif /* Def __linux */
+
+#if defined(__OpenBSD__) || defined(__FreeBSD__) || \
+ defined(__NetBSD__) || defined(__DragonFly__)
+#include <machine/endian.h>
+#if _BYTE_ORDER == _LITTLE_ENDIAN
+#undef CORE_BIG_ENDIAN
+#define CORE_LITTLE_ENDIAN
+#else /* !(_BYTE_ORDER == _LITTLE_ENDIAN) */
+#if _BYTE_ORDER == _BIG_ENDIAN
+#define CORE_BIG_ENDIAN
+#undef CORE_LITTLE_ENDIAN
+#endif /* _BYTE_ORDER == _BIG_ENDIAN */
+#endif /* !(_BYTE_ORDER == _LITTLE_ENDIAN) */
+#endif /* Def *BSD */
+
+#if defined(__HAIKU__)
+#include <endian.h>
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#undef CORE_BIG_ENDIAN
+#define CORE_LITTLE_ENDIAN
+#else /* !(__BYTE_ORDER == __LITTLE_ENDIAN) */
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define CORE_BIG_ENDIAN
+#undef CORE_LITTLE_ENDIAN
+#endif /* __BYTE_ORDER == __BIG_ENDIAN */
+#endif /* !(__BYTE_ORDER == __LITTLE_ENDIAN) */
+#endif /* Def __HAIKU__ */
+
+#ifdef __sun
+#ifdef __sparc
+#define CORE_BIG_ENDIAN
+#undef CORE_LITTLE_ENDIAN
+#else /* Ndef __sparc */
+#undef CORE_BIG_ENDIAN
+#define CORE_LITTLE_ENDIAN
+#endif /* Ndef __sparc */
+#endif /* Def __sun */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef _MSC_VER
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#include <config_options.h>
+
+/* modes */
+#ifdef _MSC_VER
+#define FILE_O_RDONLY _O_RDONLY
+#define FILE_O_BINARY _O_BINARY
+#define PATHNCMP _strnicmp /* MSVC converts paths to lower-case sometimes? */
+#define ssize_t long
+#define S_ISREG(mode) (((mode) & _S_IFMT) == (_S_IFREG)) /* MSVC does not have this macro */
+#else /* not windaube */
+#define FILE_O_RDONLY O_RDONLY
+#define FILE_O_BINARY 0
+#define PATHNCMP strncmp
+#endif /* not windaube */
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+static int internal_boost = 0;
+static char* base_dir;
+static char* work_dir;
+static size_t work_dir_len;
+
+#ifdef __GNUC__
+#define clz __builtin_clz
+#else
+static int clz(unsigned int value)
+{
+ int result = 32;
+
+ while(value)
+ {
+ value >>= 1;
+ result -= 1;
+ }
+ return result;
+}
+#endif
+
+static unsigned int get_unaligned_uint(const unsigned char* cursor)
+{
+ unsigned int result;
+
+ memcpy(&result, cursor, sizeof(unsigned int));
+ return result;
+}
+
+/* ===============================================
+ * memory pool for fast fix-size allocation (non-thread-safe)
+ * ===============================================
+ */
+struct pool
+{
+ void* head_free; /**< head of a linked list of freed element */
+ char* fresh; /**< top of a memory block to dig new element */
+ char* tail; /**< to detect end of extent... when fresh pass tail */
+ void* extent; /**< pointer to the primary extent block */
+ int size_elem; /**< size of an element. */
+ int primary; /**< primary allocation in bytes */
+ int secondary; /**< secondary allocation in bytes */
+};
+#define POOL_ALIGN_INCREMENT 8 /**< alignment, must be a power of 2 and of size > to sizeof(void*) */
+
+
+static void* pool_take_extent(struct pool* pool, int allocate)
+{
+ unsigned int size = 0;
+ void* extent;
+ void* data = NULL;
+
+ if(pool->extent)
+ {
+ /* we already have an extent, so this is a secondary */
+ if(pool->secondary)
+ {
+ size = pool->secondary;
+ }
+ }
+ else
+ {
+ assert(pool->primary);
+ size = pool->primary;
+ }
+ if(size)
+ {
+ extent = malloc(size);
+ if(extent)
+ {
+ *(void**)extent = pool->extent;
+ pool->extent = extent;
+ if(allocate)
+ {
+ data = ((char*)extent) + POOL_ALIGN_INCREMENT;
+ pool->fresh = ((char*)data) + pool->size_elem;
+ pool->tail = pool->fresh + (size - pool->size_elem);
+ }
+ else
+ {
+ pool->fresh = ((char*)extent) + POOL_ALIGN_INCREMENT;
+ pool->tail = pool->fresh + (size - pool->size_elem);
+ }
+ }
+ }
+ return data;
+}
+
+/* Create a memory pool for fix size objects
+ * this is a simplified implementation that
+ * is _not_ thread safe.
+ */
+static struct pool* pool_create(int size_elem, int primary, int secondary)
+{
+ struct pool* pool;
+
+ assert(primary > 0);
+ assert(secondary >= 0);
+ assert(size_elem > 0);
+
+ pool = (struct pool*)calloc(1, sizeof(struct pool));
+ if(!pool) return NULL;
+ /* Adjust the element size so that it be aligned, and so that an element could
+ * at least contain a void*
+ */
+ pool->size_elem = size_elem = (size_elem + POOL_ALIGN_INCREMENT - 1) & ~(POOL_ALIGN_INCREMENT - 1);
+
+ pool->primary = (size_elem * primary) + POOL_ALIGN_INCREMENT;
+ pool->secondary = secondary > 0 ? (size_elem * secondary) + POOL_ALIGN_INCREMENT : 0;
+ pool_take_extent(pool, FALSE);
+
+ return pool;
+
+}
+
+static void pool_destroy(struct pool* pool)
+{
+ void* extent;
+ void* next;
+
+ if(pool != NULL)
+ {
+ extent = pool->extent;
+ while(extent)
+ {
+ next = *(void**)extent;
+ free(extent);
+ extent = next;
+ }
+ free(pool);
+ }
+}
+
+static void* pool_alloc(struct pool* pool)
+{
+ void* data;
+
+ data = pool->head_free;
+ if(data == NULL)
+ {
+ /* we have no old-freed elem */
+ if(pool->fresh <= pool->tail)
+ {
+ /* pick a slice of the current extent */
+ data = (void*)pool->fresh;
+ pool->fresh += pool->size_elem;
+ }
+ else
+ {
+ /* allocate a new extent */
+ data = pool_take_extent(pool, TRUE);
+ }
+ }
+ else
+ {
+ /* re-used old freed element by chopping the head of the free list */
+ pool->head_free = *(void**)data;
+ }
+
+ return data;
+}
+
+
+/* ===============================================
+ * Hash implementation customized to be just tracking
+ * a unique list of string (i.e no data associated
+ * with the key, no need for retrieval, etc...
+ *
+ * This is tuned for the particular use-case we have here
+ * measures in tail_build showed that
+ * we can get north of 4000 distinct values stored in a hash
+ * the collision rate is at worse around 2%
+ * the collision needing an expensive memcmp to resolve
+ * have a rate typically at 1 per 1000
+ * for tail_build we register 37229 unique key
+ * with a total of 377 extra memcmp needed
+ * which is completely negligible compared to the
+ * number of memcmp required to eliminate duplicate
+ * entry (north of 2.5 millions for tail_build)
+ * ===============================================
+ */
+
+struct hash_elem
+{
+ struct hash_elem* next;
+ const char* key;
+ int key_len;
+};
+
+struct hash
+{
+ struct hash_elem** array;
+ struct pool* elems_pool;
+ unsigned int used;
+ unsigned int size;
+ unsigned int load_limit;
+#ifdef HASH_STAT
+ int stored;
+ int collisions;
+ int cost;
+ int memcmp;
+#endif
+};
+
+/* The following hash_compute function was adapted from :
+ * lookup3.c, by Bob Jenkins, May 2006, Public Domain.
+ *
+ * The changes from the original are mostly cosmetic
+ */
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+
+#if defined CORE_BIG_ENDIAN
+#define MASK_C1 0xFFFFFF00
+#define MASK_C2 0xFFFF0000
+#define MASK_C3 0xFF000000
+#elif defined CORE_LITTLE_ENDIAN
+#define MASK_C1 0xFFFFFF
+#define MASK_C2 0xFFFF
+#define MASK_C3 0xFF
+#else
+#error "Missing Endianness definition"
+#endif
+
+
+#define mix(a,b,c) \
+{ \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+}
+#define final(a,b,c) \
+{ \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+}
+
+static unsigned int hash_compute( struct hash const * hash, const char* key, int length)
+{
+ unsigned int a;
+ unsigned int b;
+ unsigned int c; /* internal state */
+ const unsigned char* uk = (const unsigned char*)key;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + (length << 2);
+
+ /* we use this to 'hash' full path with mostly a common root
+ * let's now waste too much cycles hashing mostly constant stuff
+ */
+ if(length > 36)
+ {
+ uk += length - 36;
+ length = 36;
+ }
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += get_unaligned_uint(uk);
+ b += get_unaligned_uint(uk+4);
+ c += get_unaligned_uint(uk+8);
+ mix(a,b,c);
+ length -= 12;
+ uk += 12;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /* Note: we possibly over-read, which would trigger complaint from VALGRIND
+ * but we mask the undefined stuff if any, so we are still good, thanks
+ * to alignment of memory allocation and tail-memory management overhead
+ * we always can read 3 bytes past the official end without triggering
+ * a segfault -- if you find a platform/compiler couple for which that postulate
+ * is false, then you just need to over-allocate by 2 more bytes in file_load()
+ * file_load already over-allocate by 1 to stick a \0 at the end of the buffer.
+ */
+ switch(length)
+ {
+ case 12: c+=get_unaligned_uint(uk+8); b+=get_unaligned_uint(uk+4); a+=get_unaligned_uint(uk); break;
+ case 11: c+=get_unaligned_uint(uk+8) & MASK_C1; b+=get_unaligned_uint(uk+4); a+=get_unaligned_uint(uk); break;
+ case 10: c+=get_unaligned_uint(uk+8) & MASK_C2; b+=get_unaligned_uint(uk+4); a+=get_unaligned_uint(uk); break;
+ case 9 : c+=get_unaligned_uint(uk+8) & MASK_C3; b+=get_unaligned_uint(uk+4); a+=get_unaligned_uint(uk); break;
+ case 8 : b+=get_unaligned_uint(uk+4); a+=get_unaligned_uint(uk); break;
+ case 7 : b+=get_unaligned_uint(uk+4) & MASK_C1; a+=get_unaligned_uint(uk); break;
+ case 6 : b+=get_unaligned_uint(uk+4) & MASK_C2; a+=get_unaligned_uint(uk); break;
+ case 5 : b+=get_unaligned_uint(uk+4) & MASK_C3; a+=get_unaligned_uint(uk); break;
+ case 4 : a+=get_unaligned_uint(uk); break;
+ case 3 : a+=get_unaligned_uint(uk) & MASK_C1; break;
+ case 2 : a+=get_unaligned_uint(uk) & MASK_C2; break;
+ case 1 : a+=get_unaligned_uint(uk) & MASK_C3; break;
+ case 0 : return c & hash->size; /* zero length strings require no mixing */
+ }
+
+ final(a,b,c);
+ return c & hash->size;
+}
+
+static void hash_destroy(struct hash* hash)
+{
+ if(hash)
+ {
+ if(hash->array)
+ {
+ free(hash->array);
+ }
+ if(hash->elems_pool)
+ {
+ pool_destroy(hash->elems_pool);
+ }
+ free(hash);
+ }
+}
+
+static struct hash* hash_create(unsigned int size)
+{
+ struct hash* hash;
+
+ assert(size > 0);
+ hash = (struct hash*)(calloc(1, sizeof(struct hash)));
+ if(hash)
+ {
+ size += (size >> 2) + 1; /* ~ 75% load factor */
+ if(size >= 15)
+ {
+ hash->size = (((unsigned int)0xFFFFFFFF) >> clz((unsigned int)size));
+ }
+ else
+ {
+ hash->size = size = 15;
+ }
+ hash->load_limit = hash->size - (hash->size >> 2);
+ hash->used = 0;
+ hash->array = (struct hash_elem**)calloc(hash->size + 1, sizeof(struct hash_elem*));
+ if(hash->array == NULL)
+ {
+ hash_destroy(hash);
+ hash = NULL;
+ }
+ }
+ if(hash)
+ {
+ hash->elems_pool = pool_create(sizeof(struct hash_elem),
+ size, size << 1);
+ if(!hash->elems_pool)
+ {
+ hash_destroy(hash);
+ hash = NULL;
+ }
+ }
+ return hash;
+}
+
+static void hash_resize(struct hash* hash)
+{
+ unsigned int old_size = hash->size;
+ unsigned int hashed;
+ struct hash_elem* hash_elem;
+ struct hash_elem* next;
+ struct hash_elem** array;
+ unsigned int i;
+
+ hash->size = (old_size << 1) + 1;
+ /* we really should avoid to get there... so print a message to alert of the condition */
+ fprintf(stderr, "resize hash %u -> %u\n", old_size, hash->size);
+ if(hash->size == old_size)
+ {
+ return;
+ }
+ array = (struct hash_elem**)calloc(hash->size + 1, sizeof(struct hash_elem*));
+ if(array)
+ {
+ hash->load_limit = hash->size - (hash->size >> 2);
+ for(i=0; i <= old_size; i++)
+ {
+ hash_elem = (struct hash_elem*)hash->array[i];
+ while(hash_elem)
+ {
+ next = hash_elem->next;
+
+ hashed = hash_compute(hash, hash_elem->key, hash_elem->key_len);
+ hash_elem->next = array[hashed];
+ array[hashed] = hash_elem;
+ hash_elem = next;
+ }
+ }
+ free(hash->array);
+ hash->array = (struct hash_elem**)array;
+ }
+ else
+ {
+ hash->size = old_size;
+ }
+}
+
+static int compare_key(struct hash const * hash, const char* a, const char* b, int len, int const * cost)
+{
+#ifdef HASH_STAT
+ *cost += 1;
+ hash->memcmp += 1;
+#else
+ (void) hash;
+ (void) cost;
+#endif
+ return memcmp(a,b, len);
+}
+
+/* a customized hash_store function that just store the key and return
+ * TRUE if the key was effectively stored, or FALSE if the key was already there
+ */
+static int hash_store(struct hash* hash, const char* key, int key_len)
+{
+ unsigned int hashed;
+ struct hash_elem* hash_elem;
+ int cost = 0;
+
+ (void) cost;
+ hashed = hash_compute(hash, key, key_len);
+#ifdef HASH_STAT
+ hash->stored += 1;
+#endif
+ hash_elem = (struct hash_elem*)hash->array[hashed];
+ while(hash_elem && (hash_elem->key_len != key_len || compare_key(hash, hash_elem->key, key, key_len, &cost)))
+ {
+ hash_elem = hash_elem->next;
+ }
+
+ if(!hash_elem)
+ {
+ hash_elem = (struct hash_elem*)pool_alloc(hash->elems_pool);
+ if(hash_elem)
+ {
+ hash_elem->key = key;
+ hash_elem->key_len = key_len;
+ hash_elem->next = hash->array[hashed];
+
+#ifdef HASH_STAT
+ if(hash_elem->next)
+ {
+ hash->collisions += 1;
+ hash->cost += cost;
+ }
+#endif
+ hash->array[hashed] = hash_elem;
+ hash->used += 1;
+ if(hash->used > hash->load_limit)
+ {
+ hash_resize(hash);
+ }
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int file_stat(const char* name, struct stat* buffer_stat, int* rc)
+{
+ int rc_local = stat(name, buffer_stat);
+ if (rc_local < 0)
+ {
+ *rc = errno;
+ }
+ return rc_local;
+}
+
+static off_t file_get_size(const char* name, int* rc)
+{
+ struct stat buffer_stat;
+ off_t size = -1;
+
+ if (!file_stat(name, &buffer_stat, rc))
+ {
+ if(S_ISREG(buffer_stat.st_mode))
+ {
+ size = buffer_stat.st_size;
+ }
+ else
+ {
+ *rc = EINVAL;
+ }
+ }
+ return size;
+}
+
+#if !ENABLE_RUNTIME_OPTIMIZATIONS
+static void * file_load_buffers[100000];
+static size_t file_load_buffer_count = 0;
+#endif
+
+static char* file_load(const char* name, off_t* size, int* return_rc)
+{
+ off_t local_size = 0;
+ int rc = 0;
+ char* buffer = NULL;
+ int fd;
+
+ assert(name != NULL);
+
+ if(!size)
+ {
+ size = &local_size;
+ }
+ *size = file_get_size(name, &rc);
+ if (!rc && *size >= 0)
+ {
+ fd = open(name, FILE_O_RDONLY | FILE_O_BINARY);
+ if (!(fd == -1))
+ {
+ buffer = (char*)malloc((size_t)(*size + 1));
+#if !ENABLE_RUNTIME_OPTIMIZATIONS
+ if (buffer != NULL)
+ {
+ if (file_load_buffer_count == 100000)
+ {
+ free(buffer);
+ buffer = NULL;
+ }
+ else
+ {
+ file_load_buffers[file_load_buffer_count++] = buffer;
+ }
+ }
+#endif
+ if (buffer == NULL)
+ {
+ rc = ENOMEM;
+ }
+ else
+ {
+ ssize_t i;
+
+ REDO:
+ i = read(fd, buffer, (size_t)(*size));
+ if(i == -1)
+ {
+ if(errno == EINTR)
+ {
+ goto REDO;
+ }
+ else
+ {
+ rc = errno;
+ }
+ }
+ else
+ {
+ if (i != *size)
+ {
+ rc = EIO;
+ }
+ }
+ buffer[*size] = 0;
+ }
+ close(fd);
+ }
+ }
+
+ if(rc && buffer)
+ {
+ free(buffer);
+ buffer = NULL;
+ }
+ if(return_rc)
+ {
+ *return_rc = rc;
+ }
+ return buffer;
+}
+
+static void cancel_relative(char const * base, char** ref_cursor, char** ref_cursor_out, char const * end)
+{
+ char* cursor = *ref_cursor;
+ char* cursor_out = *ref_cursor_out;
+
+ do
+ {
+ cursor += 3;
+ while(cursor_out > base && cursor_out[-1] == '/')
+ cursor_out--;
+ while(cursor_out > base && *--cursor_out != '/');
+ }
+ while(cursor + 3 < end && !memcmp(cursor, "/../", 4));
+ *ref_cursor = cursor;
+ *ref_cursor_out = cursor_out;
+}
+
+static void eat_space(char ** token)
+{
+ while ((' ' == **token) || ('\t' == **token)) {
+ ++(*token);
+ }
+}
+
+/*
+ * Prune LibreOffice specific duplicate dependencies to improve
+ * gnumake startup time, and shrink the disk-space footprint.
+ */
+static int
+elide_dependency(const char* key, int key_len, const char **unpacked_end)
+{
+#if 0
+ {
+ int i;
+ fprintf (stderr, "elide?%d!: '", internal_boost);
+ for (i = 0; i < key_len; i++) {
+ fprintf (stderr, "%c", key[i]);
+ }
+ fprintf (stderr, "'\n");
+ }
+#endif
+
+ /* boost brings a plague of header files */
+ int i;
+ int unpacked = 0;
+ /* walk down path elements */
+ for (i = 0; i < key_len - 1; i++)
+ {
+ if (key[i] == '/')
+ {
+ if (0 == unpacked)
+ {
+ if (!PATHNCMP(key + i + 1, "workdir/", 8))
+ {
+ unpacked = 1;
+ continue;
+ }
+ }
+ else
+ {
+ if (!PATHNCMP(key + i + 1, "UnpackedTarball/", 16))
+ {
+ if (unpacked_end)
+ *unpacked_end = strchr(key + i + 17, '/');
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * We collapse tens of internal boost headers to the unpacked target, such
+ * that you can re-compile / install boost and all is well.
+ */
+static void emit_single_boost_header(void)
+{
+#define BOOST_TARGET "/UnpackedTarball/boost.done"
+ fprintf(stdout, "%s" BOOST_TARGET " ", work_dir);
+}
+
+static void emit_unpacked_target(const char* token, const char* end)
+{
+ fwrite(token, 1, end-token, stdout);
+ fputs(".done ", stdout);
+}
+
+/* prefix paths to absolute */
+static void print_fullpaths(char* line)
+{
+ char* token;
+ char* end;
+ int boost_count = 0;
+ int token_len;
+ const char * unpacked_end = NULL; /* end of UnpackedTarget match (if any) */
+ /* for UnpackedTarget the target is GenC{,xx}Object, don't mangle! */
+ int target_seen = 0;
+
+ token = line;
+ eat_space(&token);
+ while (*token)
+ {
+ end = token;
+ /* hard to believe that in this day and age drive letters still exist */
+ if (*end && (':' == *(end+1)) &&
+ (('\\' == *(end+2)) || ('/' == *(end+2))) &&
+ isalpha((unsigned char)*end))
+ {
+ end = end + 3; /* only one cross, err drive letter per filename */
+ }
+ while (*end && (' ' != *end) && ('\t' != *end) && (':' != *end)) {
+ ++end;
+ }
+ token_len = end - token;
+ if (target_seen &&
+ elide_dependency(token, token_len, &unpacked_end))
+ {
+ if (unpacked_end)
+ {
+ if (internal_boost && !PATHNCMP(unpacked_end - 5, "boost", 5))
+ {
+ ++boost_count;
+ if (boost_count == 1)
+ emit_single_boost_header();
+ else
+ {
+ /* don't output, and swallow trailing \\\n if any */
+ token = end;
+ eat_space(&token);
+ if (token[0] == '\\' && token[1] == '\n')
+ end = token + 2;
+ }
+ }
+ else
+ {
+ emit_unpacked_target(token, unpacked_end);
+ }
+ unpacked_end = NULL;
+ }
+ }
+ else
+ {
+ if (fwrite(token, token_len, 1, stdout) != 1)
+ abort();
+ fputc(' ', stdout);
+ }
+ token = end;
+ eat_space(&token);
+ if (!target_seen && ':' == *token)
+ {
+ target_seen = 1;
+ fputc(':', stdout);
+ ++token;
+ eat_space(&token);
+ }
+ }
+}
+
+static char * eat_space_at_end(char * end)
+{
+ char * real_end;
+ assert('\0' == *end);
+ real_end = end - 1;
+ while (' ' == *real_end || '\t' == *real_end || '\n' == *real_end
+ || ':' == *real_end)
+ { /* eat colon and whitespace at end */
+ --real_end;
+ }
+ return real_end;
+}
+
+static char* phony_content_buffer;
+static char* generate_phony_line(char const * phony_target, char const * extension)
+{
+ char const * src;
+ char* dest;
+ char* last_dot = NULL;
+ //fprintf(stderr, "generate_phony_line called with phony_target %s and extension %s\n", phony_target, extension);
+ for(dest = phony_content_buffer+work_dir_len+1, src = phony_target; *src != 0; ++src, ++dest)
+ {
+ *dest = *src;
+ if(*dest == '.')
+ {
+ last_dot = dest;
+ }
+ }
+ //fprintf(stderr, "generate_phony_line after phony_target copy: %s\n", phony_content_buffer);
+ for(dest = last_dot+1, src = extension; *src != 0; ++src, ++dest)
+ {
+ *dest = *src;
+ }
+ //fprintf(stderr, "generate_phony_line after extension add: %s\n", phony_content_buffer);
+ strcpy(dest, ": $(gb_Helper_PHONY)\n");
+ //fprintf(stderr, "generate_phony_line after phony add: %s\n", phony_content_buffer);
+ return phony_content_buffer;
+}
+
+static int generate_phony_file(char* fn, char const * content)
+{
+ FILE* depfile;
+ depfile = fopen(fn, "w");
+ if(!depfile)
+ {
+ fprintf(stderr, "Could not open '%s' for writing: %s\n", fn, strerror(errno));
+ }
+ else
+ {
+ fputs(content, depfile);
+ fclose(depfile);
+ }
+ return !depfile;
+}
+
+static int process(struct hash* dep_hash, char* fn)
+{
+ int rc;
+ char* buffer;
+ char* end;
+ char* cursor;
+ char* cursor_out;
+ char* base;
+ char* created_line = NULL;
+ char* src_relative;
+ int continuation = 0;
+ char last_ns = 0;
+ off_t size;
+
+ buffer = file_load(fn, &size, &rc);
+ if(!rc)
+ {
+ base = cursor_out = cursor = end = buffer;
+ end += size;
+
+ /* first eat unneeded space at the beginning of file
+ */
+ while(cursor < end && (*cursor == ' ' || *cursor == '\\'))
+ ++cursor;
+
+ while(cursor < end)
+ {
+ if(*cursor == '\\')
+ {
+ continuation = 1;
+ *cursor_out++ = *cursor++;
+ }
+ else if(*cursor == '/')
+ {
+ if(cursor + 2 < end)
+ {
+ if(!memcmp(cursor, "/./", 3))
+ {
+ cursor += 2;
+ continue;
+ }
+ }
+ if(cursor + 3 < end)
+ {
+ if(!memcmp(cursor, "/../", 4))
+ {
+ cancel_relative(base, &cursor, &cursor_out, end);
+ }
+ }
+ *cursor_out++ = *cursor++;
+ }
+ else if(*cursor == '\n')
+ {
+ if(!continuation)
+ {
+ *cursor_out = 0;
+ if(base < cursor)
+ {
+ /* here we have a complete rule */
+ if(last_ns == ':')
+ {
+ /* if the rule ended in ':' that is a no-dep rule
+ * these are the one for which we want to filter
+ * duplicate out
+ */
+ int key_len = eat_space_at_end(cursor_out) - base;
+ if (!elide_dependency(base,key_len + 1, NULL)
+ && hash_store(dep_hash, base, key_len))
+ {
+ /* DO NOT modify base after it has been added
+ as key by hash_store */
+ print_fullpaths(base);
+ putc('\n', stdout);
+ }
+ }
+ else
+ {
+ /* rule with dep, just write it */
+ print_fullpaths(base);
+ putc('\n', stdout);
+ }
+ last_ns = ' '; // cannot hurt to reset it
+ }
+ cursor += 1;
+ base = cursor_out = cursor;
+ }
+ else
+ {
+ /* here we have a '\' followed by \n this is a continuation
+ * i.e not a complete rule yet
+ */
+ *cursor_out++ = *cursor++;
+ continuation = 0; // cancel current one (empty lines!)
+ }
+ }
+ else
+ {
+ continuation = 0;
+ /* not using isspace() here save 25% of I refs and 75% of D refs based on cachegrind */
+ if(*cursor != ' ' && *cursor != '\n' && *cursor != '\t' )
+ {
+ last_ns = *cursor;
+ }
+ *cursor_out++ = *cursor++;
+ }
+ }
+
+ /* just in case the file did not end with a \n, there may be a pending rule */
+ if(base < cursor_out)
+ {
+ if(last_ns == ':')
+ {
+ int key_len = eat_space_at_end(cursor_out) - base;
+ if (!elide_dependency(base,key_len + 1, NULL) &&
+ hash_store(dep_hash, base, key_len))
+ {
+ puts(base);
+ putc('\n', stdout);
+ }
+ }
+ else
+ {
+ puts(base);
+ putc('\n', stdout);
+ }
+ }
+ }
+ else
+ {
+ if(strncmp(fn, work_dir, work_dir_len) == 0)
+ {
+ if(strncmp(fn+work_dir_len, "/Dep/", 5) == 0)
+ {
+ src_relative = fn+work_dir_len+5;
+ // cases ordered by frequency
+ if(strncmp(src_relative, "CxxObject/", 10) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "GenCxxObject/", 13) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "CObject/", 8) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "GenCObject/", 11) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "SdiObject/", 10) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "AsmObject/", 10) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "ObjCxxObject/", 13) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "ObjCObject/", 11) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "GenObjCxxObject/", 16) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "GenObjCObject/", 14) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "GenNasmObject/", 14) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "CxxClrObject/", 13) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else if(strncmp(src_relative, "GenCxxClrObject/", 16) == 0)
+ {
+ created_line = generate_phony_line(src_relative, "o");
+ rc = generate_phony_file(fn, created_line);
+ }
+ else
+ {
+ fprintf(stderr, "no magic for %s(%s) in %s\n", fn, src_relative, work_dir);
+ }
+ }
+ if(!rc)
+ {
+ puts(created_line);
+ }
+ }
+ }
+ /* Note: yes we are going to leak 'buffer'
+ * this is on purpose, to avoid cloning the 'key' out of it and our special
+ * 'hash' just store the pointer to the key inside of buffer, hence it need
+ * to remain allocated
+ */
+ // coverity[leaked_storage] - this is on purpose
+ return rc;
+}
+
+static void usage(void)
+{
+ fputs("Usage: concat-deps <file that contains dep_files>\n", stderr);
+}
+
+#define kDEFAULT_HASH_SIZE 4096
+#define PHONY_TARGET_BUFFER 4096
+
+static int get_var(char **var, const char *name)
+{
+ *var = (char *)getenv(name);
+ if(!*var)
+ {
+ fprintf(stderr,"Error: %s is missing in the environment\n", name);
+ return 1;
+ }
+ return 0;
+}
+
+int main(int argc, char** argv)
+{
+ int rc = 0;
+ off_t in_list_size = 0;
+ char* in_list;
+ char* in_list_cursor;
+ char* in_list_base;
+ struct hash* dep_hash = NULL;
+ const char *env_str;
+
+ if(argc < 2)
+ {
+ usage();
+ return 1;
+ }
+ if(get_var(&base_dir, "SRCDIR") || get_var(&work_dir, "WORKDIR"))
+ return 1;
+ work_dir_len = strlen(work_dir);
+ phony_content_buffer = (char*)malloc(PHONY_TARGET_BUFFER);
+ assert(phony_content_buffer); // Don't handle OOM conditions
+ strcpy(phony_content_buffer, work_dir);
+ phony_content_buffer[work_dir_len] = '/';
+
+ env_str = getenv("SYSTEM_BOOST");
+ internal_boost = !env_str || strcmp(env_str,"TRUE");
+
+ in_list = file_load(argv[1], &in_list_size, &rc);
+ if(!rc)
+ {
+ dep_hash = hash_create( kDEFAULT_HASH_SIZE);
+ in_list_base = in_list_cursor = in_list;
+
+ /* extract filename of dep file from a 'space' separated list */
+ while(*in_list_cursor)
+ {
+ /* the input here may contain Win32 \r\n EOL */
+ if(*in_list_cursor == ' '
+ || *in_list_cursor == '\n' || *in_list_cursor == '\r')
+ {
+ *in_list_cursor = 0;
+ if(in_list_base < in_list_cursor)
+ {
+ rc = process(dep_hash, in_list_base);
+ if(rc)
+ {
+ break;
+ }
+ }
+ in_list_cursor += 1;
+ in_list_base = in_list_cursor;
+ }
+ else
+ {
+ in_list_cursor += 1;
+ }
+ }
+ if(!rc)
+ {
+ /* catch the last entry in case the input did not terminate with a 'space' */
+ if(in_list_base < in_list_cursor)
+ {
+ rc = process(dep_hash, in_list_base);
+ }
+ }
+#ifdef HASH_STAT
+ fprintf(stderr, "stats: u:%d s:%d l:%d t:%d c:%d m:%d $:%d\n",
+ dep_hash->used, dep_hash->size, dep_hash->load_limit, dep_hash->stored,
+ dep_hash->collisions, dep_hash->memcmp, dep_hash->cost);
+#endif
+ }
+#if !ENABLE_RUNTIME_OPTIMIZATIONS
+ {
+ size_t i;
+ hash_destroy(dep_hash);
+ for (i = 0; i != file_load_buffer_count; ++i)
+ {
+ free(file_load_buffers[i]);
+ }
+ }
+#endif
+ return rc;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/solenv/bin/constructors.py b/solenv/bin/constructors.py
new file mode 100755
index 0000000000..1237da1714
--- /dev/null
+++ b/solenv/bin/constructors.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+# Call $0 <file with a list of component file paths>
+# Dumps all the implementing constructors to stdout
+
+import xml.sax
+import os.path
+import sys
+
+constructors = list()
+
+class ComponentHandler(xml.sax.ContentHandler):
+ def startElement(self, tag, attributes):
+ if tag == "implementation" and "constructor" in attributes:
+ constructors.append(attributes["constructor"])
+
+if __name__ == "__main__":
+ parser = xml.sax.make_parser()
+ parser.setFeature(xml.sax.handler.feature_namespaces, 0)
+ parser.setContentHandler(ComponentHandler())
+ for filename in sys.argv[1:]:
+ with open(filename, "r") as components_listfile:
+ for line in components_listfile:
+ for component_filename in line.strip().split():
+ parser.parse(component_filename)
+ constructors.sort()
+ print("\n".join(constructors))
diff --git a/solenv/bin/create-ids b/solenv/bin/create-ids
new file mode 100755
index 0000000000..67f35d17c9
--- /dev/null
+++ b/solenv/bin/create-ids
@@ -0,0 +1,18 @@
+#!/bin/sh
+#
+# 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/.
+#
+
+# create ID file for the whole LibO tree. run it in toplevel dir
+
+if [ "$1" = "--with-outpath" ]; then
+ # Include workdir generated files but without workdir/UnpackedTarball workdir/UnpackedTarget
+ dirs="$(ls -d ./*/* | sed -e '/\(\/\(cscope\.\|tags\|ID\)\)\|^\.\/\(workdir\/UnpackedTar\|\(instdir\/\)\)/d')"
+else
+ dirs="$(ls -d ./*/* | sed -e '/\(\/\(cscope\.\|tags\|ID\)\)\|^\.\/\(workdir\|instdir\)\//d')"
+fi
+mkid --lang-map="$(dirname "$0")"/id-lang.map --include='C C++ asm perl java make' --statistics $dirs
diff --git a/solenv/bin/create-tags b/solenv/bin/create-tags
new file mode 100755
index 0000000000..4adf3db114
--- /dev/null
+++ b/solenv/bin/create-tags
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+#
+# 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/.
+#
+
+ctags="ctags $@"
+saloptions="-ISAL_DELETED_FUNCTION -ISAL_OVERRIDE -ISAL_FINAL"
+omnicppoptions="--c++-kinds=+p --fields=+iaS --extra=+q"
+
+if LC_ALL=C ${ctags} -D "foo=bar" --license 2>&1 >/dev/null | grep -q -- 'ctags: Unknown option: -D'; then
+ echo "note: your ctags does not support defining macros with -D"
+ no_defines=" "
+fi
+
+$ctags -h "+.hdl.hrc" --langmap=c:+.hrc.src,c++:+.hdl $saloptions $omnicppoptions \
+ ${no_defines:-"-D CPPUNIT_TEST_FIXTURE(TestClass, TestName)=class TestName : public TestClass {};"} \
+ --languages=-HTML,Java,JavaScript \
+ --langdef=UNOIDL \
+ --langmap=UNOIDL:.idl \
+ --regex-UNOIDL="/^[ \t]*(published)?[ \t]*interface[ \t]*([a-zA-Z0-9_]+[ \t]*::[ \t]*)*([a-zA-Z0-9_]+)[ \t]*(:[^:]|\{|$)/\3/i,interface/" \
+ --regex-UNOIDL="/^[ \t]*([a-zA-Z0-9:]+)[ \t]+([a-zA-Z0-9]+)\(.*\)/\2/f,function/" \
+ --regex-UNOIDL="/^[ \t]*\[.*property.*\][ \t]+([a-zA-Z0-9_]+)[ \t]+([a-zA-Z0-9]+);/\2/p,property/" \
+ --regex-UNOIDL="/^[ \t]*(published)?[ \t]*service[ \t]+([a-zA-Z0-9_]+)[ \t]*(:[^:]|\{|$)/\2/g,service/" \
+ --regex-UNOIDL="/^[ \t]*(published)?[ \t]*singleton[ \t]+([a-zA-Z0-9_]+)[ \t]*(:[^:]|\{|$)/\2/g,singleton/" \
+ --regex-UNOIDL="/^[ \t]*(published)?[ \t]*struct[ \t]+([a-zA-Z0-9_]+)[ \t]*(:[^:]|\{|\<|$)/\2/s,struct/" \
+ --regex-UNOIDL="/^[ \t]*(published)?[ \t]*enum[ \t]+([a-zA-Z0-9_]+)[ \t]*(\{|$)/\2/e,enum/" \
+ --regex-UNOIDL="/^[ \t]*(published)?[ \t]*exception[ \t]+([a-zA-Z0-9_]+)[ \t]*(:[^:]|\{|$)/\2/x,exception/" \
+ --regex-UNOIDL="/^[ \t]*([a-zA-Z0-9_]+)[ \t]+([a-zA-Z0-9_]+);/\2/m,member/" \
+ -R --exclude=instdir --exclude=instdir_for_build --exclude=workdir --exclude=workdir_for_build \
+ --exclude=external --totals=yes ${SRCDIR:-*}
+
+if test -d workdir_for_build; then
+ w=workdir_for_build
+else
+ w=workdir
+fi
+
+$ctags -h "+.hdl.hrc" --langmap=c:+.hrc.src,c++:+.hdl $saloptions $omnicppoptions \
+ --languages=-HTML,Java,JavaScript \
+ -R --append=yes --totals=yes \
+ $w/UnoApiHeadersTarget/udkapi/normal \
+ $w/UnoApiHeadersTarget/offapi/normal \
+ $w/CustomTarget/officecfg/registry
diff --git a/solenv/bin/createcomponent.xslt b/solenv/bin/createcomponent.xslt
new file mode 100644
index 0000000000..ee60221dfc
--- /dev/null
+++ b/solenv/bin/createcomponent.xslt
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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 .
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ exclude-result-prefixes="uc"
+ xmlns="http://openoffice.org/2010/uno-components"
+ xmlns:uc="http://openoffice.org/2010/uno-components">
+ <xsl:param name="uri"/>
+ <xsl:param name="cppu_env"/>
+ <xsl:param name="filtered"/>
+ <xsl:strip-space elements="*"/>
+ <xsl:template match="uc:component">
+ <components>
+ <xsl:copy>
+ <xsl:apply-templates select="@*"/>
+ <xsl:attribute name="uri">
+ <xsl:value-of select="$uri"/>
+ </xsl:attribute>
+ <xsl:apply-templates/>
+ </xsl:copy>
+ </components>
+ </xsl:template>
+ <xsl:template match="uc:implementation">
+ <xsl:if test="not(contains($filtered,@name))">
+ <xsl:copy>
+ <xsl:apply-templates select="@*"/>
+ <xsl:apply-templates select="uc:optional"/>
+ <xsl:apply-templates/>
+ </xsl:copy>
+ </xsl:if>
+ </xsl:template>
+ <xsl:template match="uc:optional"/>
+ <xsl:template match="*">
+ <xsl:copy>
+ <xsl:apply-templates select="@*"/>
+ <xsl:apply-templates/>
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="@environment">
+ <xsl:attribute name="environment">
+ <xsl:call-template name="replace">
+ <xsl:with-param name="input" select="current()"/>
+ <xsl:with-param name="pattern" select="'@CPPU_ENV@'"/>
+ <xsl:with-param name="replace" select="$cppu_env"/>
+ </xsl:call-template>
+ </xsl:attribute>
+ </xsl:template>
+ <xsl:template match="@*">
+ <xsl:copy/>
+ </xsl:template>
+ <xsl:template name="replace">
+ <xsl:param name="input"/>
+ <xsl:param name="pattern"/>
+ <xsl:param name="replace"/>
+ <xsl:choose>
+ <xsl:when test="contains($input, $pattern)">
+ <xsl:value-of select="substring-before($input, $pattern)"/>
+ <xsl:value-of select="$replace"/>
+ <xsl:call-template name="replace">
+ <xsl:with-param
+ name="input" select="substring-after($input, $pattern)"/>
+ <xsl:with-param name="pattern" select="$pattern"/>
+ <xsl:with-param name="replace" select="$replace"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$input"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/solenv/bin/dbgsv.ini b/solenv/bin/dbgsv.ini
new file mode 100644
index 0000000000..31bc03a046
--- /dev/null
+++ b/solenv/bin/dbgsv.ini
@@ -0,0 +1,20 @@
+=
+= 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 .
+=
+
+[output]
+error=shell
diff --git a/solenv/bin/desktop-translate.py b/solenv/bin/desktop-translate.py
new file mode 100644
index 0000000000..7075a51f9d
--- /dev/null
+++ b/solenv/bin/desktop-translate.py
@@ -0,0 +1,172 @@
+#
+# 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 .
+#
+
+"""Translates multiple .desktop files at once with strings from .ulf
+files; if you add new translatable .ulf files please add them to
+l10ntools/source/localize.cxx in case the module is not already listed."""
+
+import os
+import sys
+import argparse
+import io
+
+
+def encode_desktop_string(s_value):
+ """Function encoding strings to be used as values in .desktop files."""
+ # <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#
+ # value-types> says "The escape sequences \s, \n, \t, \r, and \\ are supported for values of
+ # type string and localestring, meaning ASCII space, newline, tab, carriage return, and
+ # backslash, respectively." <https://specifications.freedesktop.org/desktop-entry-spec/
+ # desktop-entry-spec-1.1.html#basic-format> says "A file is interpreted as a series of lines
+ # that are separated by linefeed characters", so it is apparently necessary to escape at least
+ # linefeed and backslash characters. It is unclear why that spec talks about "linefeed" in
+ # one place and about "newline" ("\n") and "carriage return" ("\r") in another, and how they are
+ # supposed to relate, so just escape any U+000A LINE FEED as "\n" and any U+000D CARRIAGE RETURN
+ # as "\r"; it is unclear exactly which occurrences of U+0020 SPACE and U+0009 CHARACTER
+ # TABULATION would need to be escaped, so they are mostly left unescaped, for readability:
+ s_value = s_value.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r")
+ if s_value.startswith(" "):
+ # <https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.1.html#
+ # entries> says "Space before and after the equals sign should be ignored", so escape a
+ # leading U+0020 SPACE as "\s" (while it is not clear whether "space" there means just
+ # U+0020 SPACE or any kind of white space, in which case at least a leading U+0009 CHARACTER
+ # TABULATION should similarly be escaped as "\t"; also, it is unclear whether such
+ # characters should also be escaped at the end):
+ s_value = "\\s" + s_value[1:]
+ return s_value
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-p", dest="productname", default="LibreOffice")
+parser.add_argument("-d", dest="workdir", default=".")
+parser.add_argument("--prefix", dest="prefix", default="")
+parser.add_argument("--ext", dest="ext")
+parser.add_argument("--template-dir", dest="template_dir", default=None)
+parser.add_argument("ifile")
+
+o = parser.parse_args()
+
+if o.template_dir is None:
+ template_dir = f"{o.workdir}/{o.prefix}"
+else:
+ template_dir = o.template_dir
+
+templates = {}
+
+# open input file
+source = io.open(o.ifile, encoding="utf-8")
+
+template = None
+
+# read ulf file
+for line in source:
+ if line.strip() == "":
+ continue
+ # the headings in the ulf files for .desktop files are in the form [filename_Key]
+ if line[0] == "[":
+ heading = line.split("]", 1)[0][1:]
+ template = heading.split("_", 1)[0]
+ key = heading.split("_", 1)[1]
+ entry = {}
+ # For every section in the specified ulf file there should exist
+ # a template file in $workdir ..
+ entry["outfile"] = f"{template_dir}{template}.{o.ext}"
+ entry["translations"] = {}
+ entry["key"] = key
+ templates[heading] = entry
+ else:
+ # split locale = "value" into 2 strings
+ if " = " not in line:
+ continue
+ locale, value = line.split(" = ")
+
+ if locale != line:
+ # replace en-US with en
+ locale = locale.replace("en-US", "en")
+
+ # use just anything inside the ""
+ assert value[0] == '"'
+ # Some entries span multiple lines.
+ # An entry will always end on a double quote.
+ while not value.endswith('"\n'):
+ value += source.readline()
+ value = value[1:-2]
+
+ # replace resource placeholder
+ value = value.replace("%PRODUCTNAME", o.productname)
+
+ locale = locale.replace("-", "_")
+
+ templates[heading]["translations"][locale] = value
+
+source.close()
+
+processed = 0
+# process templates
+for template, entries in templates.items():
+ outfilename = entries["outfile"]
+
+ # open the template file - ignore sections for which no
+ # templates exist
+ try:
+ template_file = io.open(outfilename, encoding="utf-8")
+ except OSError:
+ # string files processed one by one
+ if o.ext == "str":
+ continue
+ sys.exit(
+ f"Warning: No template found for item '{template}' : '{outfilename}'\n"
+ )
+ processed += 1
+
+ # open output file
+ tmpfilename = f"{outfilename}.tmp"
+ outfile = io.open(tmpfilename, "w", encoding="utf-8")
+
+ # emit the template to the output file
+ for line in template_file:
+ keyline = line
+ if keyline.startswith(entries["key"]):
+ # hack for Unity section
+ if entries["key"] == "UnityQuickList":
+ OUTKEY = "Name"
+ else:
+ OUTKEY = entries["key"]
+ keyline = OUTKEY + keyline[len(entries["key"]) :]
+ outfile.write(keyline)
+ if entries["key"] in line:
+ translations = entries["translations"]
+ for locale in sorted(translations.keys()):
+ value = translations.get(locale, None)
+ if value:
+ if o.ext in ("desktop", "str"):
+ if o.ext == "desktop":
+ value = encode_desktop_string(value)
+ outfile.write(f"{OUTKEY}[{locale}]={value}\n")
+ else:
+ outfile.write(f"\t[{locale}]{OUTKEY}={value}\n")
+
+ template_file.close()
+
+ outfile.close()
+ if os.path.exists(outfilename):
+ os.unlink(outfilename)
+ os.rename(tmpfilename, outfilename)
+
+if o.ext == "str" and processed == 0:
+ sys.exit("Warning: No matching templates processed")
diff --git a/solenv/bin/exectest.pl b/solenv/bin/exectest.pl
new file mode 100644
index 0000000000..248cd7b0c8
--- /dev/null
+++ b/solenv/bin/exectest.pl
@@ -0,0 +1,103 @@
+#
+# 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 .
+#
+
+sub encode($)
+{
+ my ($arg) = @_;
+ $arg =~ s/'/'\\''/g;
+ return $arg
+}
+
+$#ARGV >= 3
+ or die "Usage: $0 <input file> <temp file> <new?> <command> <arguments...>";
+open INPUT, '<', $ARGV[0] or die "cannot open $ARGV[0]: $!";
+shift @ARGV;
+$temp = $ARGV[0];
+shift @ARGV;
+$new = $ARGV[0];
+shift @ARGV;
+$failed = 0;
+$open = 0;
+while (1) {
+ $eof = eof INPUT;
+ $in = <INPUT> unless $eof;
+ if ($eof
+ || $in =~ /^EXPECT (SUCCESS|FAILURE|NEW-FAILURE|OLD-FAILURE) "([^"]*)"?:\n$/)
+ {
+ if ($open)
+ {
+ close OUTPUT;
+ my $prog = '';
+ my $assigns = 1;
+ for ($i = 0; $i != scalar(@ARGV); ++$i)
+ {
+ $prog .= ' ' unless $i == 0;
+ if ($assigns && $ARGV[$i] =~ /^([A-Za-z_][A-Za-z0-9_]+)=(.*)$/)
+ {
+ $prog .= $1 . "='" . encode($2) . "'";
+ }
+ else
+ {
+ if ($ARGV[$i] =~ /^{}$/)
+ {
+ $prog .= "'" . encode($temp) . "'";
+ }
+ else
+ {
+ $prog .= "'" . encode($ARGV[$i]) . "'";
+ }
+ $assigns = 0;
+ }
+ }
+ system("$prog");
+ unlink $temp;
+ if ($? % 256 == 0)
+ {
+ $exit = $? / 256;
+ $ok = ($? == 0) == ($expect eq "SUCCESS");
+ }
+ else
+ {
+ $exit = "signal";
+ $ok = 0;
+ }
+ print "\"$title\" expected $expect, got $exit ($?): ";
+ if ($ok)
+ {
+ print "ok\n";
+ }
+ else
+ {
+ print "FAILED!\n";
+ exit(1);
+ }
+ }
+ last if $eof;
+ $expect = $1;
+ $expect = ($new ? 'FAILURE' : 'SUCCESS') if $expect eq 'NEW-FAILURE';
+ $expect = ($new ? 'SUCCESS' : 'FAILURE') if $expect eq 'OLD-FAILURE';
+ $title = $2;
+ open OUTPUT, '>', $temp or die "cannot open $temp: $!";
+ $open = 1;
+ }
+ elsif ($open)
+ {
+ print OUTPUT $in or die "cannot write to $temp: $!";
+ }
+}
+exit(0);
diff --git a/solenv/bin/finish-gbuild-trace.py b/solenv/bin/finish-gbuild-trace.py
new file mode 100755
index 0000000000..fcd31fc23f
--- /dev/null
+++ b/solenv/bin/finish-gbuild-trace.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+
+# Process file generated by using GBUILD_TRACE=file with make.
+# The file needs adjusting for viewing in Chromium's "chrome://tracing' URL.
+# See solenv/gbuild/Trace.mk for details.
+
+import os
+import re
+import sys
+
+if len(sys.argv) != 2:
+ print ("Usage: " + sys.argv[0] + " [trace.json]", file=sys.stderr)
+ sys.exit(1)
+
+filename=sys.argv[1]
+
+with open(filename) as infile:
+ lines = [ line.rstrip('\n') for line in infile ]
+
+if len(lines) == 0 :
+ print( "Empty file?", file=sys.stderr)
+ sys.exit(1)
+
+if lines[0] == '{"traceEvents": [':
+ print( "File already processed", file=sys.stderr)
+ sys.exit(3)
+
+# sort items by time (parallel writes may not write them in time order)
+def linekey(line):
+ match = re.match( r'^.*, "ts": ([0-9]*)[0-9][0-9][0-9],.*$', line )
+ assert match, "Unknown line: " + line
+ return int(match.group(1))
+lines.sort( key=linekey )
+
+# 'chrome://tracing' shows several rows, we use those to show build parallelism,
+# but we need to assign the proper ids by allocating them as slots.
+slots = []
+# start time of each slot
+slot_start_time = []
+
+def make_slot_string(type, detail):
+ return type + '#' + detail
+
+def allocate_slot(type, detail):
+ for index in range(len(slots)):
+ if slots[index] == "":
+ slots[index] = make_slot_string(type, detail)
+ return index + 1
+ index = len(slots)
+ slots.append(make_slot_string(type, detail))
+ slot_start_time.append(0)
+ return index + 1
+
+def free_slot(type, detail):
+ for index in range(len(slots)):
+ if slots[index] == make_slot_string(type, detail):
+ slots[index] = ""
+ return index + 1
+ assert False, "free_slot(" + type + "," + detail + ") not found"
+
+# key: Type (e.g. CXX), value: time total
+totals_time = {}
+totals_count = {}
+
+# time of the first item, to rebase all times to 0
+start_time = 0
+
+with open(filename + ".tmp", "w") as outfile:
+ print( '{"traceEvents": [', file=outfile)
+ for iline in range(len(lines)):
+ line = lines[iline]
+ # "ts" needs converting nanoseconds -> milliseconds
+ match = re.match( r'^{"name": "([^"]*)", "ph": "(.)",.*"ts": ([0-9]*)[0-9][0-9][0-9],"args":{"message":"([^"]*)"}.*$', line )
+ if not match:
+ print( "Unknown line: " + line, file=sys.stderr)
+ sys.exit(2)
+ if start_time == 0:
+ start_time = int(match.group(3))
+ # "tid" needs replacing with proper slot
+ tid = "0"
+ # "ph"
+ if match.group(2) == 'B':
+ tid = allocate_slot(match.group(1), match.group(4)) # "name", "args"
+ slot_start_time[tid-1] = int(match.group(3))
+ elif match.group(2) == 'E':
+ tid = free_slot(match.group(1), match.group(4)) # "name", "args"
+ if not match.group(1) in totals_time:
+ totals_time[match.group(1)] = 0
+ totals_count[match.group(1)] = 0
+ totals_time[match.group(1)] += int(match.group(3)) - slot_start_time[tid-1]
+ totals_count[match.group(1)] += 1
+ line = re.sub( r'"ts": [0-9]+,', '"ts": ' + str(int(match.group(3)) - start_time) + ",", line)
+ line = re.sub( r'"tid": 1,', '"tid": ' + str(tid) + ",", line)
+ if match.group(2) == 'i':
+ rline = line
+ # mark as affecting all slots
+ line = re.sub( r'}},$', '}, "s": "p"},', line)
+ print(line, file=outfile)
+ # Chromium search doesn't find 'i' items, add extra 'R' for that
+ rline = re.sub( r', "ph": "i",', ', "ph": "R",', rline)
+ rline = re.sub( r', "tid": [0-9]+,', ',', rline)
+ print(rline, file=outfile)
+ else:
+ print(line, file=outfile)
+ # TODO: By the first time "[DEP]: LNK:Executable/makedepend.exe" is invoked the build tools
+ # are not built yet, so the invocation fails, doesn't abort the build for some reason,
+ # but the matching line about it ending is missing. So add the missing end line if it is
+ # by another start line for it instead of an end line.
+ if match.group(1) == "DEP" and match.group(4) == "[DEP]: LNK:Executable/makedepend.exe" and match.group(2) == "B":
+ for iline2 in range(iline+1,len(lines)): # search following lines
+ line2 = lines[iline2]
+ match2 = re.match( r'^{"name": "([^"]*)", "ph": "(.)",.*"ts": ([0-9]*)[0-9][0-9][0-9],"args":{"message":"([^"]*)"}.*$', line2 )
+ if match2.group(1) == "DEP" and match2.group(4) == "[DEP]: LNK:Executable/makedepend.exe":
+ if match2.group(2) == "E":
+ break # it has a matching close
+ if match2.group(2) == "B":
+ print(re.sub( r', "ph": "B",', ', "ph": "E",', line), file=outfile) # close the starting line
+ free_slot(match.group(1), match.group(4))
+ break
+
+ total_num = 0
+ for total in sorted(totals_time, key=totals_time.get, reverse=True):
+ note = ""
+ if total == "EXTERNAL":
+ note = ',"note": "minimum (cannot detect parallelism)"'
+ print( '{"pid":2,"tid":' + str(total_num) + ',"ts":0,"dur":' + str(totals_time[total]) + ',"ph":"X","name":"' + total
+ + '","args":{"count":"' + str(totals_count[total]) + '"' + note + '}},', file=outfile)
+ total_num += 1
+
+ print( '{"pid":1,"tid":0,"ts":0,"ph":"M","name":"process_name","args":{"name":"gbuild"}},', file=outfile)
+ print( '{"pid":2,"tid":0,"ts":0,"ph":"M","name":"process_name","args":{"name":"totals"}}]}', file=outfile)
+
+for index in range(len(slots)):
+ if slots[index] != "":
+ print( "Unclosed range: " + slots[index], file=sys.stderr)
+
+os.rename(filename + ".tmp", filename)
diff --git a/solenv/bin/fix-includes.pl b/solenv/bin/fix-includes.pl
new file mode 100755
index 0000000000..837fcf2d8c
--- /dev/null
+++ b/solenv/bin/fix-includes.pl
@@ -0,0 +1,95 @@
+#!/usr/bin/env perl
+# 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/.
+#
+#
+# fix-includes, a simple script replace local includes which should be global
+# , to global includes. And global includes which should be local, to local includes.
+# The script is expected to run in the root of the git repo, so it can fetch all the include directory's.
+#
+use strict;
+use warnings;
+use File::Basename;
+use File::Find;
+use IO::All;
+
+my $dirname = "include";
+
+# Fetch the list of includes
+my @subdirs = map {basename $_} grep {-d} glob("$dirname/*");
+
+# Add boost
+push(@subdirs,"boost");
+
+# Simple function to check and replace headers
+sub check_headers
+{
+ my ($dir,$file, @includes) = @_;
+ open(my $fh,"+<",$file) or die "Couldn't open file $file $!\n";
+ my @content = <$fh>;
+ my $line;
+
+ # seek to the first line, so we can replace then lines correctly
+ seek $fh,0,0;
+ foreach $line (@content){
+ if($line =~ m/#include "(\w*)\//){
+ # If an include is local and it should be global, make it global
+ if($1 ~~ @includes){
+ print "local header $line\n";
+ $line =~ s/"/</;
+ $line =~ s/"/>/;
+ print $fh $line;
+ print "converted to global header $line\n";
+ }
+ else {
+ print $fh $line;
+ }
+ }
+ # If a local file is defined global, make it local
+ elsif($line =~ /#include <((\w*)\.(hxx|h|hrc|src))>/){
+ # check if file exists, then it must be local so replace the <> to ""
+ if(-e "$dir/$1" ){
+ print "global header $line\n";
+ $line =~ s/</"/g;
+ $line =~ s/>/"/g;
+ print $fh $line;
+ print "converted to local header $line\n";
+ }
+ else {
+ print $fh $line;
+ }
+ }
+ else {
+ print $fh $line;
+ }
+ }
+ close($fh);
+}
+
+# routine that checks the headers of every cxx,hxx,c,h,hrc,src file in a directory
+sub check_routine
+{
+ my ($dir) = @_;
+ opendir(my $fh, $dir) or die "Program stopping, couldn't open directory \n";
+ while(my $file = readdir($fh)){
+ if($file =~ m/\.(cxx|hxx|c|h|hrc|src)$/i ){
+ check_headers($dir,"$dir/$file",@subdirs);
+ }
+ }
+ closedir($fh);
+}
+
+# Expect ARGV[0] to be a directory, then fetch all subdirectory's and check the header files.
+if(-d $ARGV[0]){
+ my @directories = io->dir($ARGV[0])->All_Dirs;
+ foreach my $dir (@directories){
+ print "checking header files in $dir\n";
+ check_routine($dir);
+ }
+}
+else{
+ print "$ARGV[0] isn't a directory\n";
+}
diff --git a/solenv/bin/gdb-core-bt.sh b/solenv/bin/gdb-core-bt.sh
new file mode 100755
index 0000000000..0276a70f4f
--- /dev/null
+++ b/solenv/bin/gdb-core-bt.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+#
+# 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/.
+#
+#
+
+EXECUTABLE=${1}
+COREDIR=${2}
+EXITCODE=${3}
+
+if test -n "$(which gdb)"
+then
+ found=
+ for COREFILE in "$COREDIR"/core*
+ do
+ if [ -f "$COREFILE" ]
+ then
+ guess=$(file "$COREFILE")
+ guess=${guess#* execfn: \'}
+ guess=${guess%%\'*}
+ if [ ! -x "$guess" ]; then guess=$EXECUTABLE; fi
+ printf '\nIt looks like %s generated %s\nBacktraces:\n' \
+ "$guess" "$COREFILE"
+ GDBCOMMANDFILE=$(mktemp)
+ printf "info registers\nthread apply all backtrace full\n" \
+ >"$GDBCOMMANDFILE"
+ PYTHONWARNINGS=default gdb -iex "add-auto-load-safe-path ${INSTDIR?}" \
+ -x "$GDBCOMMANDFILE" --batch "$guess" "$COREFILE" && found=x
+ rm "$GDBCOMMANDFILE"
+ echo
+ fi
+ done
+ if test -n "$WITH_COREDUMPCTL"; then
+ # Unfortunately `coredumpctl debug` only operates on the most recent core dump matching any
+ # given criteria, not on all core dumps matching those criteria; so get the PIDs of all core
+ # dumps matching the given COREDUMP_USER_UNIT (and for which a core dump is still present)
+ # first, and then iterate over them (though this introduces possibilities for some,
+ # hopefully unlikely and mostly harmless, races, like when core dumps disappear in between,
+ # or multiple matching core dumps have identical PIDs):
+ for i in $($COREDUMPCTL --json=short list COREDUMP_USER_UNIT="$LIBO_TEST_UNIT".scope | \
+ $JQ -r 'map(select(.corefile=="present"))|map(.pid)|join(" ")')
+ do
+ GDBCOMMANDFILE=$(mktemp)
+ printf 'info registers\nthread apply all backtrace full\n' >"$GDBCOMMANDFILE"
+ PYTHONWARNINGS=default $COREDUMPCTL debug \
+ COREDUMP_USER_UNIT="$LIBO_TEST_UNIT".scope COREDUMP_PID="$i" \
+ --debugger-arguments="-iex 'add-auto-load-safe-path ${INSTDIR?}' \
+ -x '$GDBCOMMANDFILE' --batch"
+ rm "$GDBCOMMANDFILE"
+ found=x
+ done
+ fi
+ if [ -z "$found" -a "$EXITCODE" -ge 128 ]; then
+ echo
+ echo "No core file identified in directory ${COREDIR}"
+ echo "To show backtraces for crashes during test execution,"
+ echo "enable core files with:"
+ echo
+ echo " ulimit -c unlimited"
+ echo
+ exit 1
+ fi
+else
+ echo "You need gdb in your path to show backtraces"
+ exit 1
+fi
diff --git a/solenv/bin/gdb_cxa-atexit_trace-stdout b/solenv/bin/gdb_cxa-atexit_trace-stdout
new file mode 100644
index 0000000000..25cfe98974
--- /dev/null
+++ b/solenv/bin/gdb_cxa-atexit_trace-stdout
@@ -0,0 +1,8 @@
+set pagination off
+break __cxa_atexit
+commands
+ bt
+ cont
+end
+run
+quit
diff --git a/solenv/bin/gdbtrycatchtrace b/solenv/bin/gdbtrycatchtrace
new file mode 100644
index 0000000000..99b1500d9d
--- /dev/null
+++ b/solenv/bin/gdbtrycatchtrace
@@ -0,0 +1,21 @@
+set pagination off
+catch throw
+commands
+ bt
+ cont
+end
+catch catch
+commands
+ bt
+ cont
+end
+echo log will be saved as gdbtrace.log, this will take some time, patience...\n
+set logging redirect on
+set logging file gdbtrace.log
+set logging enabled on
+set logging overwrite on
+run
+bt
+quit
+set logging enabled off
+echo log is saved as gdbtrace.log\n
diff --git a/solenv/bin/gdbtrycatchtrace-stdout b/solenv/bin/gdbtrycatchtrace-stdout
new file mode 100644
index 0000000000..e606a4f12f
--- /dev/null
+++ b/solenv/bin/gdbtrycatchtrace-stdout
@@ -0,0 +1,14 @@
+set pagination off
+catch throw
+commands
+ bt
+ cont
+end
+catch catch
+commands
+ bt
+ cont
+end
+run
+bt
+quit
diff --git a/solenv/bin/generate-flatpak-manifest.sh b/solenv/bin/generate-flatpak-manifest.sh
new file mode 100755
index 0000000000..2e0f8e3141
--- /dev/null
+++ b/solenv/bin/generate-flatpak-manifest.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# usage:
+# make -s cmd cmd='${SRCDIR}/solenv/bin/generate-flatpak-manifest.sh master' > org.libreoffice.LibreOffice.json
+
+set -euo pipefail
+
+# convert FOO := BAR$(MICRO) to export FOO=BAR$MICRO
+source <(sed -e's#\([^ ]\{1,\}\) := #export \1=#g' ${SRCDIR}/download.lst | sed -e 's#[)(]##g')
+
+my_gitbranch="${1?}"
+subst="-e s!@BRANCH@!${my_gitbranch?}!"
+
+subst="${subst} $(
+ < ${SRCDIR}/solenv/flatpak-manifest.in \
+ sed ${subst} | \
+ grep -o '@[A-Z0-9_]*@' | while read var; do
+ temp=${var:1:-1}
+ echo -n " -e s/${var}/${!temp}/"
+ done
+)"
+
+exec sed ${subst} < "${SRCDIR}"/solenv/flatpak-manifest.in
diff --git a/solenv/bin/generate-tokens.py b/solenv/bin/generate-tokens.py
new file mode 100644
index 0000000000..2dc3c3fd4c
--- /dev/null
+++ b/solenv/bin/generate-tokens.py
@@ -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 .
+#
+
+import sys, re
+
+infile_name = sys.argv[1]
+idfile_out_name = sys.argv[2]
+namefile_out_name = sys.argv[3]
+gperffile_out_name = sys.argv[4]
+
+idfile = open(idfile_out_name, 'w')
+namefile = open(namefile_out_name, 'w')
+gperffile = open(gperffile_out_name, 'w')
+
+gperffile.write("""%language=C++
+%global-table
+%null-strings
+%struct-type
+struct xmltoken {
+ const char *name;
+ sal_Int32 nToken;
+};
+%%
+""")
+
+token_count = 0;
+tokens = {}
+
+with open(infile_name) as infile:
+ for line in infile:
+ line = line.strip()
+ # check for valid characters
+ if not re.match(r'[a-zA-Z0-9-_]+$', line):
+ sys.exit("Error: invalid character in token '{}'".format(line));
+ cur_id = "XML_" + line;
+ # we have two ids with similar names("cut-offs" and "cut_offs")
+ if cur_id == "XML_cut_offs":
+ cur_id = "cut_offs2";
+ cur_id = cur_id.replace('-', '_')
+ tokens[line] = cur_id
+ idfile.write("const sal_Int32 {} = {};\n".format(cur_id, token_count))
+ namefile.write("\"{}\",\n".format(line));
+ gperffile.write("{},{}\n".format(line, cur_id));
+ token_count += 1
+
+idfile.write("const sal_Int32 XML_TOKEN_COUNT = {};\n".format(token_count))
+gperffile.write("%%\n")
+
+idfile.close()
+namefile.close()
+gperffile.close()
+
+def fix_linefeeds(fname):
+ # Gperf requires LF newlines, not CRLF, even on Windows.
+ # Making this work on both Python 2 and 3 is difficult.
+ # When Python 2 is dropped, delete this and add
+ # newline = '\n' to the open() calls above.
+ with open(fname, 'rb') as ifile:
+ d = ifile.read()
+ d = d.replace(b'\r', b'')
+ with open(fname, 'wb') as ofile:
+ ofile.write(d)
+
+fix_linefeeds(idfile_out_name)
+fix_linefeeds(namefile_out_name)
+fix_linefeeds(gperffile_out_name)
diff --git a/solenv/bin/gentoken.py b/solenv/bin/gentoken.py
new file mode 100644
index 0000000000..0fa793ea34
--- /dev/null
+++ b/solenv/bin/gentoken.py
@@ -0,0 +1,54 @@
+#
+# 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 .
+#
+
+import sys
+
+gperf_header = """%language=C++
+%global-table
+%null-strings
+%struct-type
+struct xmltoken
+{
+ const char *name; XMLTokenEnum nToken;
+};
+%%
+"""
+
+token_input_file = sys.argv[1]
+gperf_output_file = sys.argv[2]
+
+tokens = {}
+
+with open(token_input_file) as ifile:
+ for line in ifile:
+ line = line.strip()
+ if line:
+ token = "XML_" + line
+ token = token.replace("-", "_").replace(".", "_").replace(":", "_")
+ token = token.replace("+", "PLUS")
+ tokens[line] = token.upper()
+
+with open(gperf_output_file, "wb") as gperf:
+ gperf.write(gperf_header.encode("utf-8"))
+
+ for token in sorted(tokens.keys()):
+ gperf.write("{},{}\n".format(token, tokens[token]).encode("utf-8"))
+
+ gperf.write("%%\n".encode("utf-8"))
+
+# vim: set noet sw=4 ts=4:
diff --git a/solenv/bin/getcompver.awk b/solenv/bin/getcompver.awk
new file mode 100644
index 0000000000..6744c4dc5c
--- /dev/null
+++ b/solenv/bin/getcompver.awk
@@ -0,0 +1,84 @@
+#
+# 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 .
+#
+BEGIN {
+ CCversion = 0
+ compiler_matched = 0
+}
+# Sun c++ compiler
+/Sun WorkShop/ || /Forte Developer/ || /Sun/{
+ compiler_matched = 1
+ # version number right after "C++"
+ x = match( $0, /C\+\+ .*/ )
+ btwn = substr( $0, RSTART, RLENGTH)
+ # extract version, whitespaces get striped later
+ x = match( btwn, / .*\..*[ $\t]/)
+ CCversion = substr( btwn, RSTART, RLENGTH)
+}
+# Microsoft c++ compiler
+/Microsoft/ && /..\...\...../ {
+ compiler_matched = 1
+ # match on the format of the ms versions ( dd.dd.dddd )
+ x = match( $0, /..\...\...../ )
+ CCversion = substr( $0, RSTART, RLENGTH)
+}
+# Java
+/java version/ || /openjdk version/ {
+ compiler_matched = 1
+ # match on the format of the java versions ( d[d].d[d].d[d] )
+ if (match($0, /[0-9]+\.[0-9]+\.[0-9]+/)) {
+ CCversion = substr($0, RSTART, RLENGTH)
+ } else if (match($0, /[0-9]+\.[0-9]+/)) {
+ CCversion = substr($0, RSTART, RLENGTH) "."
+ } else if (match($0, /[0-9]+/)) {
+ CCversion = substr($0, RSTART, RLENGTH) ".."
+ }
+}
+/^[0-9]*[.][0-9]*\r*$/ {
+ if ( compiler_matched == 0 ) {
+# need to blow to x.xx.xx for comparing
+ CCversion = $0 ".0"
+ }
+}
+/^[0-9]*[.][0-9]*[.][0-9]*\r*$/ {
+ if ( compiler_matched == 0 ) {
+ CCversion = $0
+ }
+}
+/^[0-9]*[.][0-9]*[.][0-9]*-[0-9a-z]*$/ {
+ if ( compiler_matched == 0 ) {
+ CCversion = substr($0, 0, index($0, "-") - 1)
+ }
+}
+# NDK r8b has "4.6.x-google"
+/^[0-9]*[.][0-9]*[.][a-z]*-[0-9a-z]*$/ {
+ if ( compiler_matched == 0 ) {
+ # Include the second period in the match so that
+ # we will get a micro version of zero
+ x = match( $0, /^[0-9]*[.][0-9]*[.]/ )
+ CCversion = substr($0, RSTART, RLENGTH)
+ }
+}
+END {
+ if ( num == "true" ) {
+ tokencount = split (CCversion,vertoken,".")
+ for ( i = 1 ; i <= tokencount ; i++ ) {
+ printf ("%04d",vertoken[i] )
+ }
+ } else
+ print CCversion
+}
diff --git a/solenv/bin/hrcex b/solenv/bin/hrcex
new file mode 100755
index 0000000000..0645f79fcb
--- /dev/null
+++ b/solenv/bin/hrcex
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+import polib
+import binascii
+import getopt
+import sys
+import os.path
+from subprocess import check_output, Popen, PIPE
+
+try:
+ myopts, args = getopt.getopt(sys.argv[1:], "i:o:")
+except getopt.GetoptError as e:
+ print(" Syntax: hrcex -i FileIn -o FileOut")
+ print(" FileIn: Source files (*.hrc)")
+ print(" FileOut: Destination file (*.*)")
+ sys.exit(2)
+
+for o, a in myopts:
+ if o == '-i':
+ ifile = a
+ elif o == '-o':
+ ofile = a
+
+with open(ofile, "a") as output:
+ xgettext = Popen(["xgettext", "-C", "--add-comments", "--keyword=NC_:1c,2", "--keyword=NNC_:1c,2,3", "--from-code=UTF-8", "--no-wrap", ifile, "-o", "-"], stdout=PIPE, encoding="UTF-8")
+ # while overall format is c++, all of the strings use custom placeholders and don't follow c-format
+ # esp. plain percent sign never is escaped explicitly
+ input = check_output(['sed', '-e', '/^#, c-format$/d'], stdin=xgettext.stdout, encoding="UTF-8")
+ xgettext.wait()
+ xgettext.stdout.close()
+ po = polib.pofile(input)
+ if len(po) != 0:
+ print("", file=output)
+ for entry in po:
+ keyid = entry.msgctxt + '|' + entry.msgid
+ print('#. ' + polib.genKeyId(keyid), file=output)
+ for i, occurrence in enumerate(entry.occurrences):
+ entry.occurrences[i] = os.path.relpath(occurrence[0], os.environ['SRCDIR']), occurrence[1]
+ print(entry, file=output)
diff --git a/solenv/bin/id-lang.map b/solenv/bin/id-lang.map
new file mode 100644
index 0000000000..050388e8b2
--- /dev/null
+++ b/solenv/bin/id-lang.map
@@ -0,0 +1,114 @@
+#
+# 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/.
+#
+# Welcome to the mkid language mapper.
+#
+# The format of each line is:
+#
+# <pattern> <language> [options]
+#
+# Filenames are matched top-to-bottom against the patterns, and the
+# first match is chosen. The special language `IGNORE' means that
+# this file should be ignored by mkid. The options are
+# language-specific command-line options to mkid.
+#
+# If a file name doesn't match any pattern, it is assigned the default
+# language. The default language may be specified here with the
+# special pattern `**', or overridden from the mkid command-line with
+# the `--default-lang=LANG' option.
+#
+# The special pattern `***' means to include the named file that
+# immediately follows. If no file is named, then the default system
+# language mapper file (i.e., this file) is included.
+
+# Default language
+** IGNORE # Although this is listed first,
+ # the default language pattern is
+ # logically matched last.
+
+# Backup files
+*~ IGNORE
+*.bak IGNORE
+*.bk[0-9] IGNORE
+
+# SCCS files
+[sp].* IGNORE
+
+# C dependencies created by automake
+*/.deps/* IGNORE
+
+*.h C
+*.h.in C
+*.H C++
+*.hh C++
+*.hpp C++
+*.hxx C++
+
+*.l C
+*.lex C
+*.y C
+*.yacc C
+
+*.c C
+*.C C++
+*.cc C++
+*.cpp C++
+*.cxx C++
+
+*.java Java
+
+ChangeLog* Cdoc
+
+*.[sS] asm --comment=;
+*.asm asm --comment=;
+
+# [nt]roff
+*.[0-9] roff
+*.ms roff
+*.me roff
+*.mm roff
+
+*.tex TeX
+*.ltx TeX
+*.texi texinfo
+*.texinfo texinfo
+
+# portable object (i18n)
+*.po po
+
+*.el lisp
+*.elc lisp
+*.lisp lisp
+*.scm lisp
+
+*.am make
+Makefile make
+Makefile.* make
+
+*.doc text
+*.txt text
+
+*.m4 m4
+
+*.pl perl
+*.pm perl
+
+*.gz FILTER gzip -d <%s
+*.Z FILTER gzip -d <%s
+
+######### LibO-specific stuff #######################################
+
+# Treat LibO resource header files as C files
+*.hrc C
+# Treat LibO header files generated from *.idl as C++ files
+*.hdl C++
+# Treat LibO IDL files as C++ files, not exactly a header file, but ...
+*.idl C++
+# Treat LibO resource files as C files
+*.src C
+# Treat LibO *.mk files as makefiles
+*.mk make
diff --git a/solenv/bin/image-sort.py b/solenv/bin/image-sort.py
new file mode 100644
index 0000000000..5d248684b4
--- /dev/null
+++ b/solenv/bin/image-sort.py
@@ -0,0 +1,155 @@
+# -*- Mode: Python; tab-width: 4; indent-tabs-mode: nil -*-
+#
+# 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 .
+#
+
+import sys, os, re
+import argparse
+
+global_list = []
+global_hash = {}
+args = None
+
+def read_icons(fname):
+ global args
+ images = []
+ full_path = os.path.join(args.base_path, fname)
+ if not os.path.exists(full_path):
+ if not args.quiet:
+ print("Skipping non-existent {}".format(full_path), file=sys.stderr)
+ return images
+ with open(full_path) as fp:
+ for line in fp:
+ m = re.search(r'xlink:href="\.uno:(\S+)"\s+', line)
+ if m:
+ images.append(m.group(1).lower())
+ return images
+
+# filter out already seen icons & do prefixing
+def read_new_icons(fname, prefix):
+ images = read_icons(fname)
+ new_icons_arr = []
+ new_icons_d = {}
+ for icon in images:
+ iname = "cmd/" + prefix + icon + ".png"
+ if iname not in global_hash and \
+ iname not in new_icons_d:
+ new_icons_arr.append(iname)
+ new_icons_d[iname] = 1
+ return new_icons_arr
+
+def process_group(prefix, uiconfigs):
+ global global_list, global_hash
+ group = {}
+ cur_max = 1.0
+
+ # a very noddy sorting algorithm
+ for uiconfig in uiconfigs:
+ images = read_new_icons(uiconfig, prefix)
+ prev = ''
+ for icon in images:
+ if icon not in group:
+ if prev not in group:
+ group[icon] = cur_max
+ cur_max += 1.0
+ else:
+ group[icon] = group[prev] + (1.0 - 0.5 / cur_max)
+ def intvalue(i):
+ return group[i]
+ for icon in sorted(group.keys(), key=intvalue):
+ global_list.append(icon)
+ global_hash[icon] = 1
+
+def process_file(fname, prefix):
+ global global_list, global_hash
+ images = read_new_icons(fname, prefix)
+
+ for icon in images:
+ global_list.append(icon)
+ global_hash[icon] = 1
+
+def chew_controlfile(ifile):
+ global global_list, global_hash
+ filelist = []
+ for line in ifile:
+ line = line.strip()
+ if line.startswith('#'):
+ continue
+ if not line:
+ continue
+
+ m = re.match(r'-- (\S+)\s*', line)
+ if m:
+ # control code
+ code = m.group(1)
+ small = line.lower().endswith(' small')
+ if code.lower() == 'group':
+ if not small:
+ process_group("lc_", filelist)
+ process_group ("sc_", filelist)
+ elif code.lower() == 'ordered':
+ if not small:
+ for f in filelist:
+ process_file(f, "lc_")
+ for f in filelist:
+ process_file(f, "sc_")
+ elif code.lower() == 'literal':
+ for f in filelist:
+ if f not in global_hash:
+ global_list.append(f)
+ global_hash[f] = 1
+ else:
+ sys.exit("Unknown code '{}'".format(code))
+ filelist = []
+ else:
+ filelist.append(line)
+
+parser = argparse.ArgumentParser()
+# where the control file lives
+parser.add_argument('control_file', metavar='image-sort.lst',
+ help='the sort control file')
+# where the uiconfigs live
+parser.add_argument('base_path', metavar='directory',
+ help='path to the UIConfigs directory')
+parser.add_argument('output', metavar='output file', type=argparse.FileType('w'),
+ nargs='?', default=None, help='optionally write to this output file')
+parser.add_argument("-q", "--quiet", action="store_true",
+ help="don't print status messages to stdout")
+
+args = parser.parse_args()
+
+if args.output is not None:
+ close_output = True
+else:
+ args.output = sys.stdout
+ close_output = False
+
+with open(args.control_file) as cf:
+ chew_controlfile(cf)
+
+for icon in global_list:
+ if not icon.startswith('sc_'):
+ args.output.write(icon + "\n")
+
+for icon in global_list:
+ if icon.startswith('sc_'):
+ args.output.write(icon + "\n")
+
+if close_output:
+ args.output.close()
+
+# dnl vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/solenv/bin/install-gdb-printers b/solenv/bin/install-gdb-printers
new file mode 100755
index 0000000000..3eae4d9efe
--- /dev/null
+++ b/solenv/bin/install-gdb-printers
@@ -0,0 +1,143 @@
+#!/usr/bin/env bash
+#
+# 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/.
+#
+
+GDBDIR="${SRCDIR}/solenv/gdb"
+INSTALLDIR="${TESTINSTALLDIR}"
+DYLIB=so
+if [ "$(uname)" = Darwin ]; then
+ INSTALLDIR=$INSTALLDIR/LibreOffice.app/Contents
+ DYLIB=dylib
+fi
+
+die() {
+ echo "$1" >&2
+ exit 1
+}
+
+usage() {
+ cat <<EOT
+Install gdb pretty printers and autoloaders for them.
+
+Usage:
+install-gdb-printers [ -a dir ] [ -i dir ] [ -p dir ] [ -c ]
+install-gdb-printers -h
+
+Options:
+-a dir The dir where autoloaders will be placed. Defaults to whatever -i
+ is.
+-c Create the autoloader's dir if it does not exist. This option only
+ makes sense if both -a and -i are used.
+-h Show this help text.
+-i dir The dir where libreoffice is installed. Defaults to whatever -a is.
+-p dir The dir where pretty printers are placed.
+
+Env. variables:
+DESTDIR If set, it is prepended to all dir arguments.
+
+Examples:
+1) Install pretty printers into /usr/share/libreoffice/gdb, with
+ autoloaders in /usr/share/gdb/auto-load (run
+ "info gdb 'Extending GDB' Python Auto-loading" to learn more) and
+ installation in /usr/lib64/libreoffice (this is what Fedora does):
+
+install-gdb-printers -a /usr/share/gdb/auto-load/usr/lib64/libreoffice -c \\
+ -i /usr/lib64/libreoffice -p /usr/share/libreoffice/gdb
+EOT
+}
+
+make_autoload() {
+ local lib="${DESTDIR}${autoloaddir}/$2/$3"
+ local merged="$4"
+
+ local resolved;
+ resolved=$(readlink "${DESTDIR}${installdir}/$2/$3")
+ [ -n "$resolved" ] && lib=$resolved
+ local dir="${lib%/*}"
+
+ if ${create}; then
+ mkdir -p "${dir}" || die "cannot create dir '${dir}'"
+ fi
+
+ [[ -f ${lib}-gdb.py ]] && rm -f "${lib}-gdb.py"
+ if [[ -n "${merged}" ]]; then
+ sed -e "s!%PYTHONDIR%!${pythondir}!" -e "s!%MODULES%!${*:5}!" \
+ "${GDBDIR}/autoload.template" > "${lib}-gdb.py"
+ else
+ sed -e "s!%PYTHONDIR%!${pythondir}!" -e "s!%MODULES%!$1!" \
+ "${GDBDIR}/autoload.template" > "${lib}-gdb.py"
+ fi
+}
+
+# dir where the autoloaders will be placed
+autoloaddir=
+# The installation dir. If only one of these is set, the other is set to
+# the same value.
+installdir=
+# dir where the pretty printers will be placed
+pythondir="${GDBDIR}"
+# Create autoload dir if it does not exist. This only makes sense when
+# installing into system gdb dir, so $autoloaddir must be absolute path.
+create=false
+
+# b de g jklmno qrstuvwxyzABCDEFGHIJK MNOPQRSTUVWXYZ0123456789
+while getopts :a:cfhi:p:L opt; do
+ case ${opt} in
+ a) autoloaddir="${OPTARG}" ;;
+ c) create=true ;;
+ h) usage; exit ;;
+ i) installdir="${OPTARG}" ;;
+ p) pythondir="${OPTARG}" ;;
+ *) die "unknown option ${OPTARG}" ;;
+ esac
+done
+
+if [[ -z ${autoloaddir} && -z ${installdir} ]]; then
+ autoloaddir="${INSTALLDIR}"
+ installdir="${INSTALLDIR}"
+elif [[ -n ${autoloaddir} && -z ${installdir} ]]; then
+ installdir="${autoloaddir}"
+elif [[ -z ${autoloaddir} && -n ${installdir} ]]; then
+ autoloaddir="${installdir}"
+fi
+
+if [[ -n ${DESTDIR} ]]; then
+ [[ ${autoloaddir:0:1} = / ]] || die 'the arg to -a must be an absolute path'
+ [[ ${pythondir:0:1} = / ]] || die 'the arg to -p must be an absolute path'
+fi
+if ${create}; then
+ [[ ${autoloaddir:0:1} = / ]] || die 'the arg to -a must be an absolute path'
+else
+ [[ ! -d ${DESTDIR}${autoloaddir} ]] && die "directory '${DESTDIR}${autoloaddir}' does not exist"
+fi
+[[ ! -d ${DESTDIR}${installdir} ]] && die "directory '${DESTDIR}${installdir}' does not exist"
+[[ ! -d ${GDBDIR} ]] && die "directory '${GDBDIR}' does not exist"
+
+if [[ ${DESTDIR}${pythondir} != ${GDBDIR} ]]; then
+ mkdir -p "${DESTDIR}${pythondir}" || die "cannot create dir '${DESTDIR}${pythondir}'"
+ cp -pr "${GDBDIR}/libreoffice" "${DESTDIR}${pythondir}"
+fi
+
+if [[ -n "${MERGELIBS}" ]]; then
+ make_autoload merged program libmergedlo."$DYLIB" merge svl tl basegfx vcl utl
+ make_autoload cppu program libuno_cppu."$DYLIB".3
+ make_autoload sal program libuno_sal."$DYLIB".3
+ make_autoload sw program libswlo."$DYLIB"
+else
+ make_autoload basegfx program libbasegfxlo."$DYLIB"
+ make_autoload cppu program libuno_cppu."$DYLIB".3
+ make_autoload sal program libuno_sal."$DYLIB".3
+ make_autoload svl program libsvllo."$DYLIB"
+ make_autoload sw program libswlo."$DYLIB"
+ make_autoload tl program libtllo."$DYLIB"
+ make_autoload utl program libutllo."$DYLIB"
+ make_autoload vcl program libvcllo."$DYLIB"
+fi
+make_autoload writerfilter program libwriterfilterlo."$DYLIB"
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/solenv/bin/install-sh b/solenv/bin/install-sh
new file mode 100755
index 0000000000..fb942ef716
--- /dev/null
+++ b/solenv/bin/install-sh
@@ -0,0 +1,3 @@
+;; This file is automatically created by diff_all_filter.pl
+;; Fri Mar 27 08:02:00 2009
+
diff --git a/solenv/bin/job-limiter.cpp b/solenv/bin/job-limiter.cpp
new file mode 100644
index 0000000000..9111ead351
--- /dev/null
+++ b/solenv/bin/job-limiter.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+// Helper to reduce parallelism, specifically for msi installset packaging
+//
+// Ideally it would hook into make's jobserver, but it is too easy to deadlock the build at the
+// packaging stage (all jobs are started at once, consuming all available slots as part of general
+// parallelism, and then each job waiting for an additional job token but since all jobs are in
+// waiting stage: you have a deadlock).
+// That in turn is an advantage for a simple approach with separate lock and release commands, since
+// everything is started at once, and all jobs that will be queued are all jobs that use the helper,
+// there will be jobs in waiting state and keeping the semaphore active as long as it matters.
+
+#include <iostream>
+#include <windows.h>
+
+using namespace std;
+
+int grab(HANDLE& semaphore_handle)
+{
+ DWORD waitresult = WaitForSingleObject(semaphore_handle, INFINITE);
+
+ switch (waitresult)
+ {
+ case WAIT_OBJECT_0:
+ return 0;
+ case WAIT_TIMEOUT:
+ fprintf(stderr, "grabbing a slot failed with timeout despite waiting for INFINITE?\n");
+ break;
+ case WAIT_FAILED:
+ fprintf(stderr, "grabbing a slot failed with error: %d\n", GetLastError());
+ break;
+ default:
+ fprintf(stderr, "grabbing a slot failed with status %d - error %d", waitresult,
+ GetLastError());
+ }
+ return 1;
+}
+
+int release(HANDLE& semaphore_handle)
+{
+ if (ReleaseSemaphore(semaphore_handle, 1, NULL))
+ {
+ return 0;
+ }
+ else
+ {
+ fprintf(stderr, "something went wrong trying to release a slot: %d\n", GetLastError());
+ return 1;
+ }
+}
+
+int wmain(int argc, wchar_t* argv[])
+{
+ if (argc != 2)
+ {
+ fprintf(stderr, "Invalid number of arguments!\n");
+ fprintf(stderr, "call with 'grab' or 'release'\n");
+ exit(1);
+ }
+ // since the reason for limiting the parallelism is the instability with cscript/WiLangId.vbs
+ // calls and high parallelism, not having that unique/potentially sharing the limit semaphore
+ // with another build is not a problem but considered a feature...
+ LPCWSTR semaphorename = L"lo_installset_limiter";
+ HANDLE semaphore_handle
+ = OpenSemaphoreW(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, false, semaphorename);
+ if (semaphore_handle == NULL)
+ {
+ fprintf(stdout, "no existing semaphore, creating new one\n");
+ // Need to have a buffer slot, since the helper isn't persistent.
+ // When it is called to release a slot it might be that all holders of the semaphore
+ // are gone already and thus the semaphore also gets closed and is no longer present on the
+ // system. So when it creates a new one to use, and then releases one it would hit the max
+ // limit otherwise. This only happens when nothing else is waiting for a slot anymore,
+ // i.e. when there are already fewer jobs than imposed by the limiter.
+ // A limit of four (main installer and 3 controlled by this limiter) was chosen because that
+ // won't affect overall build time (creating the main installer with multiple languages
+ // takes much longer than all the helppackages and the single sdk package combined, even
+ // when those are limited to three jobs), and seems to be low enough to avoid the random
+ // cscript/WiLangId.vbs failures.
+ semaphore_handle = CreateSemaphoreW(NULL, 3, 4, semaphorename);
+ // keep this process alive for other jobs to grab the semaphore, otherwise it is gone too
+ // quickly and everything creates their own semaphore that immediately has enough slots,
+ // completely bypassing the point of having a limiter...
+ Sleep(500);
+ }
+ if (semaphore_handle == NULL)
+ {
+ fprintf(stderr, "Error creating/opening the semaphore - bailing out (%d)\n",
+ GetLastError());
+ exit(1);
+ }
+
+ if (wcscmp(argv[1], L"grab") == 0)
+ {
+ exit(grab(semaphore_handle));
+ }
+ else if (wcscmp(argv[1], L"release") == 0)
+ {
+ exit(release(semaphore_handle));
+ }
+ else
+ {
+ fwprintf(stderr, L"invalid action '%s'\nSupported actions are 'grab' or 'release'\n",
+ argv[1]);
+ }
+ exit(1);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/solenv/bin/localestr b/solenv/bin/localestr
new file mode 100755
index 0000000000..c66d40cbfd
--- /dev/null
+++ b/solenv/bin/localestr
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+case "$1" in
+ ca-valencia)
+ echo ca@valencia
+ ;;
+ kmr-Latn)
+ echo kmr@latin
+ ;;
+ sr-Latn)
+ echo sr@latin
+ ;;
+ hu-Hung)
+ echo hu@hung
+ ;;
+ *)
+ echo $1 | tr '-' '_'
+ ;;
+esac
+
diff --git a/solenv/bin/macosx-change-install-names.pl b/solenv/bin/macosx-change-install-names.pl
new file mode 100644
index 0000000000..3d393a0123
--- /dev/null
+++ b/solenv/bin/macosx-change-install-names.pl
@@ -0,0 +1,95 @@
+#
+# 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 .
+#
+
+# The install names of our dynamic libraries contain a special segment token
+# that denotes where the dynamic library is located in the installation set.
+# The segment token consists of "@", optionally followed by ".", followed by 50
+# "_", followed by a location token (one of "URELIB", "OOO", "OXT", or "NONE").
+#
+# Typically, the segment token is the first segment of a relative install name.
+# But the segment token may also appear within an absolute install name. That
+# is useful when tunnelling the segment token into the external build process
+# via a --prefix configure switch, for example.
+#
+# When another dynamic library or an executable links against such a dynamic
+# library, the path recorded in the former to locate the latter is rewritten
+# according to the below %action table. The result path consists of the prefix
+# from the action table followed by the suffix of the dynamic library's install
+# name. If the special segment token does not contain the optional "." after
+# the "@", the suffix consists of all segments after the special token segment.
+# If the special token segment does contain the optional ".", then the suffix
+# consists of just the last segment of the original install name.
+#
+# That latter case is useful for libraries from external modules, where the
+# external build process locates them in some sub-directory.
+
+sub action($$$)
+{
+ # The @__VIA_LIBRARY_PATH__ thing has no magic meaning anywhere
+ # (here in LO or to the dynamic linker), it is effectively a
+ # comment telling that this library is supposed to have been found
+ # by the dynamic linker already in DYLD_LIBRARY_PATH.
+
+ my %action =
+ ('app/UREBIN/URELIB' => '@executable_path/../Frameworks',
+ 'app/OOO/URELIB' => '@executable_path/../Frameworks',
+ 'app/OOO/OOO' => '@executable_path/../Frameworks',
+ 'app/SDKBIN/URELIB' => '@__VIA_LIBRARY_PATH__',
+ 'app/NONE/URELIB' => '@__VIA_LIBRARY_PATH__',
+ 'app/NONE/OOO' => '@__VIA_LIBRARY_PATH__',
+ 'app/NONE/NONE' => '@__VIA_LIBRARY_PATH__',
+ 'shl/URELIB/URELIB' => '@loader_path',
+ 'shl/OOO/URELIB' => '@loader_path',
+ 'shl/OOO/OOO' => '@loader_path',
+ 'shl/URELIB/OOO' => '@loader_path',
+ 'shl/OXT/URELIB' => '@executable_path/urelibs',
+ 'shl/NONE/URELIB' => '@__VIA_LIBRARY_PATH__',
+ 'shl/NONE/OOO' => '@__VIA_LIBRARY_PATH__',
+ 'shl/NONE/NONE' => '@loader_path');
+ my ($type, $loc1, $loc2) = @_;
+ my $act = $action{"$type/$loc1/$loc2"};
+ die "illegal combination $type/$loc1/$loc2" unless defined $act;
+ return $act;
+}
+
+@ARGV >= 2 or die 'Usage: app|shl UREBIN|URELIB|OOO|SDKBIN|OXT|NONE <filepath>*';
+$type = shift @ARGV;
+$loc = shift @ARGV;
+foreach $file (@ARGV)
+{
+ my $call = "otool -L $file";
+ open(IN, "-|", $call) or die "cannot $call";
+ my $change = "";
+ while (<IN>)
+ {
+ if (m'^\s*(((/.*)?/)?@_{50}([^/]+)(/.+)) \(compatibility version \d+\.\d+\.\d+, current version \d+\.\d+\.\d+\)\n$')
+ {
+ $change .= " -change $1 " . action($type, $loc, $4) . $5;
+ }
+ elsif (m'^\s*(((/.*)?/)?@\._{50}([^/]+)(/.+)?(/[^/]+)) \(compatibility version \d+\.\d+\.\d+, current version \d+\.\d+\.\d+\)\n$')
+ {
+ $change .= " -change $1 " . action($type, $loc, $4) . $6;
+ }
+ }
+ close(IN) or die "got $? from $call";
+ if ($change ne "")
+ {
+ $call = "$ENV{'INSTALL_NAME_TOOL'} $change $file";
+ system($call) == 0 or die "cannot $call";
+ }
+}
diff --git a/solenv/bin/macosx-codesign-app-bundle b/solenv/bin/macosx-codesign-app-bundle
new file mode 100755
index 0000000000..695b3ae979
--- /dev/null
+++ b/solenv/bin/macosx-codesign-app-bundle
@@ -0,0 +1,152 @@
+#!/usr/bin/env bash
+
+# Use of unset variable is an error
+set -u
+# If any part of a pipeline of commands fails, the whole pipeline fails
+set -o pipefail
+
+# Script to sign executables, dylibs and frameworks in an app bundle plus the bundle itself. Called
+# from installer::simplepackage::create_package() in solenv/bin/modules/installer/simplepackage.pm
+# and the test-install target in Makefile.in.
+
+test `uname` = Darwin || { echo This is for macOS only; exit 1; }
+
+test $# = 1 || { echo Usage: $0 app-bundle; exit 1; }
+
+for V in \
+ BUILDDIR \
+ MACOSX_BUNDLE_IDENTIFIER; do
+ if test -z "$(eval echo '$'$V)"; then
+ echo No '$'$V "environment variable! This should be run in a build only"
+ exit 1
+ fi
+done
+
+APP_BUNDLE="$1"
+entitlements=
+entitlements_helper=
+application_identifier=
+if test -n "$ENABLE_MACOSX_SANDBOX"; then
+ # In a sandboxed build executables need the entitlements
+ entitlements="--entitlements $BUILDDIR/lo.xcent"
+ # helper utilities must be signed with only the sandbox and inherit entitlements
+ entitlements_helper="--entitlements $SRCDIR/sysui/desktop/macosx/sandbox_inherit.entitlements"
+ application_identifier=`/usr/libexec/PlistBuddy -c "print com.apple.application-identifier" $BUILDDIR/lo.xcent`
+ # remove the key from the entitlement - only use it when signing the whole bundle in the final step
+ /usr/libexec/PlistBuddy -c "delete com.apple.application-identifier" $BUILDDIR/lo.xcent
+ # All data files are in Resources and included in the app bundle signature
+ other_files=''
+ # HACK: remove donate menu entries, need to support apple-pay and be verified
+ # as non profit as a bare minimum to allow asking....
+ sed -I "" -e '\#<menu:menuitem menu:id=".uno:Donation"/>#d' $APP_BUNDLE/Contents/Resources/config/soffice.cfg/modules/*/menubar/menubar.xml
+else
+ # We then want to sign data files, too, hmm.
+ entitlements="--entitlements $BUILDDIR/hardened_runtime.xcent"
+ entitlements_helper=$entitlements
+ other_files="\
+ -or -name '*.fodt' -or -name 'schema.strings' -or -name 'schema.xml' \
+ -or -name '*.jar' -or -name 'LICENSE' -or -name 'LICENSE.html' \
+ -or -name '*.applescript' -or -name '*.odt'"
+fi
+
+if test -z "$MACOSX_CODESIGNING_IDENTITY"; then
+ if test -n "$ENABLE_RELEASE_BUILD"; then
+ echo "This is a release build! This should be run in a non-release build only"
+ exit 1
+ fi
+
+ # Skip codesigning for non-release builds if there is no identity set but
+ # set entitlements to allow Xcode's Instruments application to connect to
+ # the application. Note: the following command fails on some Mac Intel
+ # machines, and since this not a release build, ignore any failures.
+ codesign --force --identifier="${MACOSX_BUNDLE_IDENTIFIER}" --sign - $entitlements "$APP_BUNDLE"
+ exit 0
+fi
+
+# Sign jnilibs first as workaround for signing issue on old baseline
+# order matters/screws things up otherwise
+find -d "$APP_BUNDLE" \( -name '*.jnilib' \) ! -type l |
+ while read file; do
+ id=`echo ${file#${APP_BUNDLE}/Contents/} | sed -e 's,/,.,g'`
+ codesign --force --identifier=$MACOSX_BUNDLE_IDENTIFIER.$id --sign "$MACOSX_CODESIGNING_IDENTITY" "$file" || exit 1
+done
+
+# Sign dylibs
+#
+# The dylibs in the Python framework are called *.so. Go figure
+#
+# On Mavericks also would like to have data files signed...
+# add some where it makes sense. Make a depth-first search to sign the contents
+# of e.g. the spotlight plugin before attempting to sign the plugin itself
+
+find "$APP_BUNDLE" \( -name '*.dylib' -or -name '*.dylib.*' -or -name '*.so' \
+ $other_files \) ! -type l |
+while read file; do
+ id=`echo ${file#${APP_BUNDLE}/Contents/} | sed -e 's,/,.,g'`
+ codesign --force --identifier=$MACOSX_BUNDLE_IDENTIFIER.$id --sign "$MACOSX_CODESIGNING_IDENTITY" "$file" || exit 1
+done
+
+# Sign included bundles. First .app ones (i.e. the Python.app inside
+# the LibreOfficePython.framework. Be generic for kicks...)
+
+find "$APP_BUNDLE"/Contents -name '*.app' -type d |
+while read app; do
+ # Assume the app has a XML (and not binary) Info.plist
+ id=`grep -A 1 '<key>CFBundleIdentifier</key>' "$app/Contents/Info.plist" | tail -1 | sed -e 's,.*<string>,,' -e 's,</string>.*,,'`
+ codesign --timestamp --options=runtime --force --identifier=$id --sign "$MACOSX_CODESIGNING_IDENTITY" $entitlements "$app" || exit 1
+done
+
+# Then .framework ones. Again, be generic just for kicks.
+
+find "$APP_BUNDLE" -name '*.framework' -type d |
+while read framework; do
+ for version in "$framework"/Versions/*; do
+ if test ! -L "$version" -a -d "$version"; then
+ # Assume the framework has a XML (and not binary) Info.plist
+ id=`grep -A 1 '<key>CFBundleIdentifier</key>' $version/Resources/Info.plist | tail -1 | sed -e 's,.*<string>,,' -e 's,</string>.*,,'`
+ if test -d $version/bin; then
+ # files in bin are not covered by signing the framework...
+ for scriptorexecutable in $(find $version/bin/ -type f); do
+ codesign --timestamp --options=runtime --force --identifier=$id --sign "$MACOSX_CODESIGNING_IDENTITY" "$scriptorexecutable" || exit 1
+ done
+ fi
+ codesign --force --identifier=$id --sign "$MACOSX_CODESIGNING_IDENTITY" "$version" || exit 1
+ fi
+ done
+done
+
+# Then mdimporters
+
+find "$APP_BUNDLE" -name '*.mdimporter' -type d |
+while read bundle; do
+ codesign --force --prefix=$MACOSX_BUNDLE_IDENTIFIER. --sign "$MACOSX_CODESIGNING_IDENTITY" "$bundle" || exit 1
+done
+
+# Sign executables
+
+find "$APP_BUNDLE/Contents/MacOS" -type f |
+while read file; do
+ case "$file" in
+ */soffice)
+ ;;
+ *)
+ id=`echo ${file#${APP_BUNDLE}/Contents/} | sed -e 's,/,.,g'`
+ codesign --force --timestamp --options=runtime --identifier=$MACOSX_BUNDLE_IDENTIFIER.$id --sign "$MACOSX_CODESIGNING_IDENTITY" $entitlements_helper "$file" || exit 1
+ ;;
+ esac
+done
+
+# Sign the app bundle as a whole which means (re-)signing the
+# CFBundleExecutable from Info.plist, i.e. soffice, plus the contents
+# of the Resources tree.
+#
+# See also https://developer.apple.com/library/mac/technotes/tn2206/
+
+if test -n "$ENABLE_MACOSX_SANDBOX" && test -n "$application_identifier"; then
+ # add back the application-identifier to the entitlements
+ # testflight/beta-testing won't work if that key is used when signing the other executables
+ /usr/libexec/PlistBuddy -c "add com.apple.application-identifier string $application_identifier" $BUILDDIR/lo.xcent
+fi
+codesign --force --timestamp --options=runtime --identifier="${MACOSX_BUNDLE_IDENTIFIER}" --sign "$MACOSX_CODESIGNING_IDENTITY" $entitlements "$APP_BUNDLE" || exit 1
+
+exit 0
diff --git a/solenv/bin/macosx_menubar_modification.xsl b/solenv/bin/macosx_menubar_modification.xsl
new file mode 100644
index 0000000000..14a8be07c3
--- /dev/null
+++ b/solenv/bin/macosx_menubar_modification.xsl
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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 .
+-->
+<xsl:stylesheet version='1.0'
+ xmlns:menu="http://openoffice.org/2001/menu"
+ xmlns:xsl='http://www.w3.org/1999/XSL/Transform' >
+
+ <!-- identity template, does reproduce every IN node on the output -->
+ <xsl:template match="node()|@*">
+ <xsl:copy>
+ <xsl:apply-templates select="node()|@*"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <!-- filtering template : removes the concerned nodes -->
+ <!-- removes the separator just before the expected item -->
+ <xsl:template match="menu:menuseparator[following-sibling::menu:menuitem[1]/@menu:id='.uno:Quit']"/>
+ <!-- suppression of the Quit item -->
+ <xsl:template match="menu:menuitem[@menu:id='.uno:Quit']"/>
+
+ <xsl:template match="menu:menuseparator[following-sibling::menu:menuitem[1]/@menu:id='.uno:About']"/>
+ <!-- suppression of the About item -->
+ <xsl:template match="menu:menuitem[@menu:id='.uno:About']"/>
+
+ <!-- suppression of the OptionsTreeDialog item -->
+ <xsl:template match="menu:menuitem[@menu:id='.uno:OptionsTreeDialog']"/>
+
+</xsl:stylesheet>
diff --git a/solenv/bin/make-raspbian-root-tarball b/solenv/bin/make-raspbian-root-tarball
new file mode 100755
index 0000000000..7105409571
--- /dev/null
+++ b/solenv/bin/make-raspbian-root-tarball
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+# When lacking a proper cross-compilation package system from Linux
+# (or some other Unix) to Raspbian, instead create a tarball of
+# headers, libraries and pkg-config files on a Raspbian system and
+# unpack that then on the build system, and pass in a -sysroot switch
+# to the cross-compiler.
+
+cd /
+
+# Exclude irrelevant stuff, like shared libraries that actually are
+# "modules" loaded at run-time by some software.
+
+EXCLUDE='lib/ld-linux \
+lib/klibc- \
+lib/arm-linux-gnueabihf/security/ \
+usr/lib/arm-linux-gnueabihf/ImageMagick- \
+usr/lib/arm-linux-gnueabihf/autofs/ \
+usr/lib/arm-linux-gnueabihf/directfb- \
+usr/lib/arm-linux-gnueabihf/gconv/ \
+usr/lib/arm-linux-gnueabihf/gdbus- \
+usr/lib/arm-linux-gnueabihf/gdk-pixbuf- \
+usr/lib/arm-linux-gnueabihf/gio/ \
+usr/lib/arm-linux-gnueabihf/gvfs/ \
+usr/lib/arm-linux-gnueabihf/jack/ \
+usr/lib/arm-linux-gnueabihf/libgphoto2/ \
+usr/lib/arm-linux-gnueabihf/libgphoto2_port/ \
+usr/lib/arm-linux-gnueabihf/libgtk-2.0/ \
+usr/lib/arm-linux-gnueabihf/libgtk-3.0/ \
+usr/lib/arm-linux-gnueabihf/libproxy/ \
+usr/lib/arm-linux-gnueabihf/odbc/ \
+usr/lib/arm-linux-gnueabihf/pango/ \
+usr/lib/arm-linux-gnueabihf/plymouth/ \
+usr/lib/arm-linux-gnueabihf/qt4/ \
+usr/lib/arm-linux-gnueabihf/sane \
+usr/lib/libblas.so \
+usr/lib/liblapack.so'
+
+EXCLUDE=`echo "$EXCLUDE" | tr -d '
+' | sed -e 's/ /|/g'`
+
+FILELIST=`mktemp`
+STAGINGDIR=`mktemp -d`
+
+find lib/*.so* \
+ lib/arm-linux-gnueabihf \
+ usr/include \
+ usr/lib/liblpsolve*.a \
+ usr/lib/*.so* \
+ usr/lib/arm-linux-gnueabihf \
+ usr/lib/jvm/java-6-openjdk-armhf/include \
+ usr/lib/jvm/java-6-openjdk-armhf/jre/lib/arm \
+ usr/lib/pkgconfig \
+ usr/share/pkgconfig \
+ -type f -o -type l |
+ grep -v -E "^($EXCLUDE)" >$FILELIST
+
+tar -c --files-from=$FILELIST -f - | (cd $STAGINGDIR && tar xf -)
+
+rm $FILELIST
+
+cd $STAGINGDIR
+# Change absolute symlinks to relative
+find . -type l -print0 | xargs -0 ls -ld | grep -- '-> /' |
+ while read mode links user group size month day yearortime link arrow target; do
+ target=`echo "$target" | sed -e 's,/,..;,'`
+ while test `expr index $target /` -gt 0; do
+ target=`echo "$target" | sed -e 's,/,;,'`
+ target="..;$target"
+ done
+ target=`echo "$target" | sed -e 's,;,/,g'`
+ ln -f -s $target $link
+ done
+
+RESULT=/tmp/raspbian-root-`date +%Y%m%d`.tar.gz
+tar czf $RESULT .
+
+cd /
+rm -rf $STAGINGDIR
+
+echo === Result in $RESULT ===
diff --git a/solenv/bin/make_installer.pl b/solenv/bin/make_installer.pl
new file mode 100644
index 0000000000..48aaadac2b
--- /dev/null
+++ b/solenv/bin/make_installer.pl
@@ -0,0 +1,26 @@
+#
+# 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 .
+#
+
+use strict;
+use warnings;
+
+use lib ("$ENV{SRCDIR}/solenv/bin/modules");
+
+use installer qw(main);
+
+exit main();
diff --git a/solenv/bin/mkdocs.Makefile b/solenv/bin/mkdocs.Makefile
new file mode 100644
index 0000000000..731b79ab9a
--- /dev/null
+++ b/solenv/bin/mkdocs.Makefile
@@ -0,0 +1,46 @@
+.SUFFIXES:
+
+gb_Side=host
+BUILDDIR=$(shell pwd)
+SRCDIR:=$(abspath $(dir $(firstword $(MAKEFILE_LIST)))/../..)
+$(info SRCDIR:$(SRCDIR))
+include $(SRCDIR)/config_host.mk
+
+.PHONY: init_doxygen generate_tag generate_doc
+.DEFAULT: generate_doc
+
+prefered_modules := sw sc sd
+gbuild_modules := $(prefered_modules) $(filter-out $(prefered_modules),$(patsubst $(SRCDIR)/%/,%,$(dir $(wildcard $(SRCDIR)/*/Module_*.mk))))
+$(info gbuild_modules:$(gbuild_modules))
+
+DOXYGEN_REF_TAGFILES=$(foreach m,$(gbuild_modules), $(BUILDDIR)/docs/$(m)/$(m).tags=./$(m)/html)
+export DOXYGEN_REF_TAGFILES
+
+$(BUILDDIR)/docs/%.tag: init_doxygen
+ @echo "doxygen tag of $(basename $(notdir $@))"
+ @export DOXYGEN_INCLUDE_PATH=`echo $(SOLARINC) | sed -e 's/-I\.//g' -e 's/ -I/ /'g -e 's/ -isystem/ /g' -e 's|/usr/[^ ]*| |g'` ; \
+ $(SRCDIR)/solenv/bin/mkonedoc.sh "$(basename $(notdir $@))" "tag" "$(SRCDIR)" "$(BUILDDIR)/docs"
+ @touch $@
+
+$(BUILDDIR)/docs/%.doc: generate_tag
+ @echo "doxygen doc of $(basename $(notdir $@))"
+ @export DOXYGEN_INCLUDE_PATH=`echo $(SOLARINC) | sed -e 's/-I\.//g' -e 's/ -I/ /'g -e 's/ -isystem/ /g' -e 's|/usr/[^ ]*| |g'` ; \
+ $(SRCDIR)/solenv/bin/mkonedoc.sh "$(basename $(notdir $@))" "doc" "$(SRCDIR)" "$(BUILDDIR)/docs"
+ @touch $@
+
+
+
+all: generate_doc
+
+generate_tag: $(foreach m,$(gbuild_modules), $(BUILDDIR)/docs/$(m).tag)
+ @echo "all tag generated"
+
+generate_doc: $(foreach m,$(gbuild_modules), $(BUILDDIR)/docs/$(m).doc)
+ @$(SRCDIR)/solenv/bin/mkdocs_portal.sh "$(SRCDIR)" "$(BUILDDIR)/docs"
+ @echo "Done."
+
+init_doxygen:
+ @echo "init"
+ @rm -fr $(BUILDDIR)/docs
+ @mkdir $(BUILDDIR)/docs
+
diff --git a/solenv/bin/mkdocs.sh b/solenv/bin/mkdocs.sh
new file mode 100755
index 0000000000..b61d53a29c
--- /dev/null
+++ b/solenv/bin/mkdocs.sh
@@ -0,0 +1,281 @@
+#!/usr/bin/env bash
+#
+# 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/.
+#
+# Doxygen / README doc generation
+#
+# See git for contributors
+#
+
+function header {
+ title=$1
+ breadcrumb=$2
+ output=$3
+
+ cat - > $output <<EOF
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+ <title>$title</title>
+
+ <style>
+ * { margin: 0; padding: 0; }
+ body { font-family: sans-serif; font-size: 12px; }
+ #head { padding: 20px; background: #00A500; }
+ #head a { color: #000; }
+ #body { padding: 20px; }
+ #foot { padding: 10px; font-size: 9px; border-top: 1px #18A303 solid; margin-top: 25px; }
+ p { line-height: 1.7em; margin-bottom: 1em; }
+ pre { margin-bottom: 0.5em; }
+ .multi-col { -moz-column-width: 20em; -webkit-column-width: 20em; -moz-column-gap: 1em; -webkit-column-gap: 1em; }
+ h1 { margin-bottom: 0.5em; }
+ h2,h3,h4 { margin: 1.3em 0 0.5em 0; }
+ ul, ol { margin: 0.5em 1.5em; }
+ </style>
+</head>
+<body>
+ <div id="head">
+ <h1>$title</h1>
+ <p>$breadcrumb</p>
+ </div>
+ <div id="body" style="multi-col">
+EOF
+}
+
+function footer {
+ output=$1
+
+ cat - >> $output <<EOF
+
+</div>
+<div id="foot">
+ <small>
+ <p>Generated by Libreoffice <a href="https://git.libreoffice.org/core/+/refs/heads/master/solenv/bin/mkdocs.sh">Module Description Tool</a></p>
+ <p>Last updated:
+EOF
+LANG= date >> $output
+cat - >> $output <<EOF
+ </p>
+ </small>
+</div>
+</body>
+</html>
+EOF
+
+}
+
+function proc_text {
+ # Local links: [[...]]
+ # Git links: [git:...]
+ # Other remote links: [...]
+ # Headings: == bleh ==
+ # Paragraphs: \n\n
+ sed -re ' s/\[\[([-_a-zA-Z0-9]+)\]\]/<a href="\1.html">\1<\/a>/g' - \
+ | sed -re ' s/\[git:([^]]+)\]/<a href="https:\/\/git.libreoffice.org\/core\/+\/refs\/heads\/master\/\1">\1<\/a>/g' \
+ | sed -re ' s/\[([^]]+)\]/<a href="\1">\1<\/a>/g' \
+ | sed -re ' s/====([^=]+)====/<h4>\1<\/h4>/g' \
+ | sed -re ' s/===([^=]+)===/<h3>\1<\/h3>/g' \
+ | sed -re ' s/==([^=]+)==/<h2>\1<\/h2>/g' \
+ | sed -re ':a;N;$!ba;s/\n\n/<\/p><p>/g' \
+ | awk 'BEGIN { print "<p>" } { print } END { print "</p>" }'
+}
+
+function proc_text_markdown {
+ sed -re ' s/\[git:([^]]+)\]/<a href="\.\.\/\1">\1<\/a>/g'
+}
+
+function check_cmd {
+ cmds_needed="$1"
+ error_msg="$2"
+
+ found=0; cmd=
+ for cmd_needed in $cmds_needed; do
+ which $cmd_needed > /dev/null 2>&1 && { found=1; cmd=$cmd_needed; }
+ done
+ if [ $found = 0 ]; then
+ echo "$error_msg" >&2
+ exit 1
+ fi
+ export "${cmds_needed%% *}"=$cmd
+}
+
+function setup {
+ parm=$1
+ if [ -z "${!parm}" ] ; then
+ echo "grep \"${parm}=\" ./config_host.mk | sed -re \" s/${parm}=//\")"
+ echo "$parm=$(grep \"${parm}=\" ./config_host.mk | sed -re \" s/${parm}=//\")"
+ eval "$parm=$(grep \"${parm}=\" ./config_host.mk | sed -re \" s/${parm}=//\")"
+ fi
+ if [ -z "${!parm}" ] ; then
+ echo "could not determine $parm" >&2
+ exit 1
+ fi
+}
+
+# binaries that we need
+check_cmd doxygen "You need doxygen for doc generation"
+check_cmd dot "You need the graphviz tools to create the nice inheritance graphs"
+check_cmd "markdown markdown2 markdown2-3" "You need either markdown or markdown2 in order to convert README.md into html"
+
+# suck setup
+setup "SOLARINC"
+shopt -s nullglob
+
+# Title of the documentation
+DOXYGEN_PROJECT_PREFIX="LibreOffice"
+
+# get list of modules
+if [ -z "$INPUT_PROJECTS" ]; then
+ INPUT_PROJECTS="`ls */Module_*.mk | sed 's#/.*##'`"
+fi
+
+
+# output directory for generated documentation
+BASE_OUTPUT="$1"
+mkdir -p "$BASE_OUTPUT" || {
+ echo "Cannot create $BASE_OUTPUT"
+ exit 1
+}
+
+# paths for binary and configuration file
+BASE_PATH=`pwd`
+DOXYGEN_CFG="$2"
+if test ! -f "$DOXYGEN_CFG"; then
+ echo "doxygen.cfg not found"
+ exit 1
+fi
+
+# strip -I. and bin -I prefix; exclude system headers
+DOXYGEN_INCLUDE_PATH=`echo $SOLARINC | sed -e 's/-I\.//g' -e 's/ -I/ /'g -e 's/ -isystem/ /g' -e 's|/usr/[^ ]*| |g'`
+
+# setup version string
+DOXYGEN_VERSION="master"
+
+
+###################################################
+#
+# Generate docs
+#
+###################################################
+
+# cleanup
+echo "cleaning up" && rm -rf $BASE_OUTPUT/*
+
+# make the stuff world-readable
+umask 022
+
+# generate docs
+echo "generating doxygen docs"
+DOXYGEN_REF_TAGFILES=""
+for PROJECT in $INPUT_PROJECTS;
+do
+ # avoid processing of full project subdirs, only add source and inc
+
+ # project header files can be in $PROJECT/inc and/or include/$PROJECT
+ if [ -d "$PROJECT/inc" ]; then
+ PROJECT_INCLUDE="$PROJECT/inc"
+ else
+ PROJECT_INCLUDE=""
+ fi
+
+ if [ -d "include/$PROJECT" ]; then
+ PROJECT_INCLUDE="$PROJECT_INCLUDE include/$PROJECT"
+ if [ "$PROJECT" = "sal" ]; then
+ PROJECT_INCLUDE="$PROJECT_INCLUDE include/osl include/rtl"
+ fi
+ fi
+
+ DOXYGEN_INPUT=`printf "%s" "$PROJECT/source $PROJECT_INCLUDE"`
+
+ DOXYGEN_OUTPUT="$BASE_OUTPUT/$PROJECT"
+ DOXYGEN_OUR_TAGFILE="$DOXYGEN_OUTPUT/$PROJECT.tags"
+ DOXYGEN_PROJECTNAME="$DOXYGEN_PROJECT_PREFIX Module $PROJECT"
+
+ # export variables referenced in doxygen config file
+ export DOXYGEN_INPUT
+ export DOXYGEN_OUTPUT
+ export DOXYGEN_INCLUDE_PATH
+ export DOXYGEN_VERSION
+ export DOXYGEN_OUR_TAGFILE
+ export DOXYGEN_REF_TAGFILES
+ export DOXYGEN_PROJECTNAME
+
+ # debug
+ echo "Calling $DOXYGEN_PATH/doxygen $DOXYGEN_CFG with"
+ echo "Input: $DOXYGEN_INPUT"
+ echo "Output: $DOXYGEN_OUTPUT"
+ echo "Include: $DOXYGEN_INCLUDE_PATH"
+ echo "Version: $DOXYGEN_VERSION"
+ echo "Tagfile: $DOXYGEN_OUR_TAGFILE"
+ echo "Ref-Tags: $DOXYGEN_REF_TAGFILES"
+ echo "Title: $DOXYGEN_PROJECTNAME"
+
+ nice -15 doxygen "$DOXYGEN_CFG" >>$BASE_OUTPUT/doxygen.log 2>&1 || exit 1
+
+ # setup referenced tagfiles for next round
+ DOXYGEN_REF_TAGFILES="$DOXYGEN_REF_TAGFILES $DOXYGEN_OUR_TAGFILE=$BASE_URL/$PROJECT/html"
+done
+
+# generate entry page
+echo "generating index page"
+header "LibreOffice Modules" " " "$BASE_OUTPUT/index.html"
+for module_name in *; do
+ if [ -d $module_name ]; then
+ cur_file=$(echo $module_name/README.md)
+ if [ -f "$cur_file" ]; then
+ # write index.html entry
+ text=$(echo -e "<h2><a href=\"${module_name}.html\">${module_name}</a></h2>\n")
+
+ if [ ${cur_file: -3} == ".md" ]; then
+ # This is a markdown file.
+ header_text="$(head -n1 $cur_file)"
+ header_text="$(echo ${header_text} | sed -e 's/^\#*//g')"
+ text="${text}${header_text}"
+ else
+ text="${text}$(head -n1 $cur_file | proc_text)"
+ fi
+ echo -e "$text" >> "$BASE_OUTPUT/index.html"
+
+ # write detailed module content
+ header "$module_name" "<a href=\"index.html\">LibreOffice</a> &raquo; ${module_name}" "$BASE_OUTPUT/${module_name}.html"
+ text="<p><b>View module in:</b>"
+ text="${text} &nbsp; <a href=\"https://git.libreoffice.org/core/+/refs/heads/master/${module_name}\">git</a>"
+ if $(echo $INPUT_PROJECTS | grep -q $module_name); then
+ text="${text} &nbsp; <a href=\"${module_name}/html/classes.html\">Doxygen</a>"
+ fi
+ text="${text} </p><p>&nbsp;</p>"
+ echo -e "$text" >> "$BASE_OUTPUT/${module_name}.html"
+
+ if [ ${cur_file: -3} == ".md" ]; then
+ # This is a markdown file.
+ text="$(${markdown} $cur_file | proc_text_markdown)"
+ echo -e "$text" >> "$BASE_OUTPUT/${module_name}.html"
+ else
+ proc_text < $cur_file >> "$BASE_OUTPUT/${module_name}.html"
+ fi
+ footer "$BASE_OUTPUT/${module_name}.html"
+ else
+ empty_modules[${#empty_modules[*]}]=$module_name
+ fi
+ fi
+done
+
+if [ ${#empty_modules[*]} -gt 10 ]; then
+ echo -e "<p>&nbsp;</p><p>READMEs were not available for these modules:</p><ul>\n" >> "$BASE_OUTPUT/index.html"
+ for module_name in "${empty_modules[@]}"; do
+ # Do not process these directories
+ if [[ "$module_name" =~ ^(autom4te.cache|dictionaries|docs|helpcompiler|helpcontent2|include|instdir|lo|translations|workdir)$ ]]; then
+ continue
+ fi
+ echo -e "<li><a href=\"https://git.libreoffice.org/core/+/refs/heads/master/${module_name}\">${module_name}</a></li>\n" >> "$BASE_OUTPUT/index.html"
+ done
+ echo -e "</ul>\n" >> "$BASE_OUTPUT/index.html"
+fi
+
+footer "$BASE_OUTPUT/index.html"
+
+## done
diff --git a/solenv/bin/mkdocs_portal.sh b/solenv/bin/mkdocs_portal.sh
new file mode 100755
index 0000000000..8fa120a772
--- /dev/null
+++ b/solenv/bin/mkdocs_portal.sh
@@ -0,0 +1,157 @@
+#!/usr/bin/env bash
+
+if [ -n "$debug" ] ; then
+ set -x
+fi
+
+markdown="markdown"
+SRCDIR="$1"
+BASE_OUTPUT="$2"
+
+pushd "$SRCDIR" > /dev/null
+
+
+function header
+{
+ local title="$1"
+ local breadcrumb="$2"
+ local output="$3"
+
+ cat - > $output <<EOF
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+ <title>$title</title>
+
+ <style>
+ * { margin: 0; padding: 0; }
+ body { font-family: sans-serif; font-size: 12px; }
+ #head { padding: 20px; background: #00A500; }
+ #head a { color: #000; }
+ #body { padding: 20px; }
+ #foot { padding: 10px; font-size: 9px; border-top: 1px #18A303 solid; margin-top: 25px; }
+ p { line-height: 1.7em; margin-bottom: 1em; }
+ pre { margin-bottom: 0.5em; }
+ .multi-col { -moz-column-width: 20em; -webkit-column-width: 20em; -moz-column-gap: 1em; -webkit-column-gap: 1em; }
+ h1 { margin-bottom: 0.5em; }
+ h2,h3,h4 { margin: 1.3em 0 0.5em 0; }
+ ul, ol { margin: 0.5em 1.5em; }
+ </style>
+</head>
+<body>
+ <div id="head">
+ <h1>${title}</h1>
+ <p>${breadcrumb}</p>
+ </div>
+ <div id="body" style="multi-col">
+EOF
+}
+
+function footer
+{
+local output="$1"
+
+ cat - >> $output <<EOF
+
+</div>
+<div id="foot">
+ <small>
+ <p>Generated by Libreoffice CI on $(hostname)</p>
+ <p>Last updated:
+EOF
+
+date '+%F %T' >> $output
+cat - >> $output <<EOF
+ | <a href="http://www.documentfoundation.org/privacy">Privacy Policy</a> | <a href="http://www.documentfoundation.org/imprint">Impressum (Legal Info)</a>
+ </p>
+ </small>
+</div>
+</body>
+</html>
+EOF
+
+}
+
+function proc_text
+{
+ # Local links: [[...]]
+ # Git links: [git:...]
+ # Other remote links: [...]
+ # Headings: == bleh ==
+ # Paragraphs: \n\n
+ sed -re ' s/\[\[([-_a-zA-Z0-9]+)\]\]/<a href="\1.html">\1<\/a>/g' - \
+ | sed -re ' s/\[git:([^]]+)\]/<a href="https:\/\/git.libreoffice.org\/core\/+\/refs\/heads\/master\/\1">\1<\/a>/g' \
+ | sed -re ' s/\[([^]]+)\]/<a href="\1">\1<\/a>/g' \
+ | sed -re ' s/====([^=]+)====/<h4>\1<\/h4>/g' \
+ | sed -re ' s/===([^=]+)===/<h3>\1<\/h3>/g' \
+ | sed -re ' s/==([^=]+)==/<h2>\1<\/h2>/g' \
+ | sed -re ':a;N;$!ba;s/\n\n/<\/p><p>/g' \
+ | awk 'BEGIN { print "<p>" } { print } END { print "</p>" }'
+}
+
+function proc_text_markdown {
+ sed -re ' s/\[git:([^]]+)\]/<a href="https:\/\/git.libreoffice.org\/core\/+\/refs\/heads\/master\/\1">\1<\/a>/g'
+}
+
+# generate entry page
+
+echo "generating index page"
+header "LibreOffice Modules" " " "$BASE_OUTPUT/index.html"
+
+for module_name in *; do
+ if [ -d $module_name ]; then
+ cur_file=$(echo $module_name/README.md)
+ if [ -f "$cur_file" ]; then
+ # write index.html entry
+ text=$(echo -e "<h2><a href=\"${module_name}.html\">${module_name}</a></h2>\n")
+ if [ ${cur_file: -3} == ".md" ]; then
+ # This is a markdown file.
+ header_text="$(head -n1 $cur_file)"
+ header_text="$(echo ${header_text} | sed -e 's/^\#*//g')"
+ text="${text}${header_text}"
+ else
+ text="${text}$(head -n1 $cur_file | proc_text)"
+ fi
+ echo -e "$text" >> "$BASE_OUTPUT/index.html"
+
+ # write detailed module content
+ header "$module_name" "<a href=\"index.html\">LibreOffice</a> &raquo; ${module_name}" "$BASE_OUTPUT/${module_name}.html"
+ text="<p><b>View module in:</b>"
+ text="${text} &nbsp; <a href=\"https://git.libreoffice.org/core/+/refs/heads/master/${module_name}\">git</a>"
+ if $(echo $INPUT_PROJECTS | grep -q $module_name); then
+ text="${text} &nbsp; <a href=\"${module_name}/html/classes.html\">Doxygen</a>"
+ fi
+ text="${text} </p><p>&nbsp;</p>"
+ echo -e "$text" >> "$BASE_OUTPUT/${module_name}.html"
+
+ if [ ${cur_file: -3} == ".md" ]; then
+ # This is a markdown file.
+ text="$(${markdown} $cur_file | proc_text_markdown)"
+ echo -e "$text" >> "$BASE_OUTPUT/${module_name}.html"
+ else
+ proc_text < $cur_file >> "$BASE_OUTPUT/${module_name}.html"
+ fi
+ footer "$BASE_OUTPUT/${module_name}.html"
+ else
+ empty_modules[${#empty_modules[*]}]=$module_name
+ fi
+ fi
+done
+
+if [ ${#empty_modules[*]} -gt 10 ]; then
+ echo -e "<p>&nbsp;</p><p>READMEs were not available for these modules:</p><ul>\n" >> "$BASE_OUTPUT/index.html"
+ for module_name in "${empty_modules[@]}"; do
+ if [[ "$module_name" =~ ^(autom4te.cache|dictionaries|docs|helpcompiler|helpcontent2|include|instdir|lo|translations|workdir)$ ]]
+ then
+ continue
+ fi
+ echo -e "<li><a href=\"https://git.libreoffice.org/core/+/refs/heads/master/${module_name}\">${module_name}</a></li>\n" >> "$BASE_OUTPUT/index.html"
+ done
+ echo -e "</ul>\n" >> "$BASE_OUTPUT/index.html"
+fi
+
+footer "$BASE_OUTPUT/index.html"
+
+popd > /dev/null
+
+## done
diff --git a/solenv/bin/mkonedoc.sh b/solenv/bin/mkonedoc.sh
new file mode 100755
index 0000000000..654ce56819
--- /dev/null
+++ b/solenv/bin/mkonedoc.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+module="$1"
+mode="$2"
+SRCDIR="$3"
+BASE_OUTPUT="$4"
+
+
+module_include=""
+if [ -d "$module/inc" ]; then
+ module_include="$module/inc"
+fi
+
+if [ -d "include/$module" ]; then
+ module_include="$module_include include/$module"
+fi
+
+DOXYGEN_INPUT=`printf "%s" "$module/source $module_include"`
+DOXYGEN_OUTPUT="$BASE_OUTPUT/$module"
+DOXYGEN_VERSION="master"
+DOXYGEN_PROJECTNAME="LibreOffice Module $module"
+if [ "$mode" = "tag" ] ; then
+ DOXYGEN_OUR_TAGFILE="$DOXYGEN_OUTPUT/$module.tags"
+ DOXYGEN_REF_TAGFILES=
+else
+ DOXYGEN_OUR_TAGFILE="$DOXYGEN_OUTPUT/$module.tags2"
+ DOXYGEN_REF_TAGFILES="$(echo $DOXYGEN_REF_TAGFILES | sed -e "s@$DOXYGEN_OUTPUT/$module.tags@@")"
+fi
+
+# export variables referenced in doxygen config file
+export DOXYGEN_INPUT
+export DOXYGEN_OUTPUT
+export DOXYGEN_INCLUDE_PATH
+export DOXYGEN_VERSION
+export DOXYGEN_OUR_TAGFILE
+export DOXYGEN_REF_TAGFILES
+export DOXYGEN_PROJECTNAME
+
+
+echo "Calling doxygen mode $mode for $module"
+echo "using tagfiles : $DOXYGEN_REF_TAGFILES" >> $BASE_OUTPUT/$module.log 2>&1
+doxygen "$SRCDIR/solenv/inc/doxygen_$mode.cfg" >> $BASE_OUTPUT/$module.log 2>&1
+echo "$(date "+%F %T") doxygen $module.$mode rc:$?"
+
+
diff --git a/solenv/bin/modules/RepositoryHelper.pm b/solenv/bin/modules/RepositoryHelper.pm
new file mode 100644
index 0000000000..29001f1783
--- /dev/null
+++ b/solenv/bin/modules/RepositoryHelper.pm
@@ -0,0 +1,161 @@
+# -*- Mode: Perl; tab-width: 4; indent-tabs-mode: nil; -*-
+#
+# 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 .
+#
+
+#*************************************************************************
+#
+# RepositoryHelper - Perl for working with repositories
+#
+# usage: see below
+#
+#*************************************************************************
+
+package RepositoryHelper;
+
+use strict;
+
+
+use Carp;
+use Cwd qw (cwd);
+use File::Basename;
+#use File::Temp qw(tmpnam);
+
+my $debug = 0;
+
+##### profiling #####
+
+##### ctor #####
+
+sub new {
+ my $proto = shift;
+ my $class = ref($proto) || $proto;
+ my $initial_directory = shift;
+ if ($initial_directory) {
+ $initial_directory = Cwd::realpath($initial_directory);
+ } else {
+ if ( defined $ENV{PWD} ) {
+ $initial_directory = $ENV{PWD};
+ } elsif (defined $ENV{_cwd}) {
+ $initial_directory = $ENV{_cwd};
+ } else {
+ $initial_directory = cwd();
+ };
+ };
+ my $self = {};
+ $self->{INITIAL_DIRECTORY} = $initial_directory;
+ $self->{REPOSITORY_ROOT} = undef;
+ if (! search_via_build_lst($self))
+ {
+ croak('Cannot determine source directory/repository for ' . $self->{INITIAL_DIRECTORY});
+ }
+ bless($self, $class);
+ return $self;
+}
+
+##### methods #####
+sub get_repository_root
+{
+ my $self = shift;
+ return $self->{REPOSITORY_ROOT};
+}
+
+sub get_initial_directory
+{
+ my $self = shift;
+ return $self->{INITIAL_DIRECTORY};
+}
+
+sub search_via_build_lst {
+ my $self = shift;
+ my $previous_dir = '';
+ my $rep_root_candidate = $self->{INITIAL_DIRECTORY};
+ do {
+ my $test_file;
+ if ($rep_root_candidate eq '/') {
+ $test_file = '/prj/build.lst';
+ } else {
+ $test_file = $rep_root_candidate . '/prj/build.lst';
+ };
+ if (-e $test_file) {
+ $self->{REPOSITORY_ROOT} = File::Basename::dirname($rep_root_candidate);
+ return 1;
+ };
+ $previous_dir = $rep_root_candidate;
+ $rep_root_candidate = File::Basename::dirname($rep_root_candidate);
+ return 0 if ((!$rep_root_candidate) || ($rep_root_candidate eq $previous_dir));
+ }
+ while (chdir "$rep_root_candidate");
+};
+
+##### finish #####
+
+1; # needed by use or require
+
+__END__
+
+=head1 NAME
+
+RepositoryHelper - Perl module for working with repositories
+
+=head1 SYNOPSIS
+
+ # example that will analyze sources and return the source root directory
+
+ use RepositoryHelper;
+
+ # Create a new instance:
+ $a = RepositoryHelper->new();
+
+ # Get repositories for the actual workspace:
+ $a->get_repository_root();
+
+
+=head1 DESCRIPTION
+
+RepositoryHelper is a perlPerl module for working with repositories
+in the database.
+
+Methods:
+
+RepositoryHelper::new()
+
+Creates a new instance of RepositoryHelper. Can be initialized by: some path which likely to belong to a repository, default - empty, the current dir will be taken.
+
+RepositoryHelper::get_repository_root()
+
+Returns the repository root, retrieved by educated guess...
+
+RepositoryHelper::get_initial_directory()
+
+Returns full path to the initialisation directory.
+
+=head2 EXPORT
+
+RepositoryHelper::new()
+RepositoryHelper::get_repository_root()
+RepositoryHelper::get_initial_directory()
+
+=head1 AUTHOR
+
+Vladimir Glazunov, vg@openoffice.org
+
+=head1 SEE ALSO
+
+perl(1).
+
+=cut
diff --git a/solenv/bin/modules/installer.pm b/solenv/bin/modules/installer.pm
new file mode 100644
index 0000000000..a22385924b
--- /dev/null
+++ b/solenv/bin/modules/installer.pm
@@ -0,0 +1,1700 @@
+#
+# 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;
+
+use strict;
+use warnings;
+
+use base 'Exporter';
+
+use Cwd;
+use Data::Dumper;
+use File::Copy;
+use List::Util qw(shuffle);
+use installer::control;
+use installer::converter;
+use installer::copyproject;
+use installer::download;
+use installer::environment;
+use installer::epmfile;
+use installer::files;
+use installer::filelists;
+use installer::globals;
+use installer::helppack;
+use installer::languagepack;
+use installer::languages;
+use installer::logger;
+use installer::packagelist;
+use installer::parameter;
+use installer::pathanalyzer;
+use installer::profiles;
+use installer::scpzipfiles;
+use installer::scriptitems;
+use installer::setupscript;
+use installer::simplepackage;
+use installer::strip qw(strip_libraries);
+use installer::systemactions;
+use installer::windows::assembly;
+use installer::windows::binary;
+use installer::windows::component;
+use installer::windows::createfolder;
+use installer::windows::directory;
+use installer::windows::feature;
+use installer::windows::featurecomponent;
+use installer::windows::file;
+use installer::windows::font;
+use installer::windows::icon;
+use installer::windows::idtglobal;
+use installer::windows::inifile;
+use installer::windows::media;
+use installer::windows::mergemodule;
+use installer::windows::msiglobal;
+use installer::windows::msishortcutproperty;
+use installer::windows::property;
+use installer::windows::removefile;
+use installer::windows::registry;
+use installer::windows::shortcut;
+use installer::windows::strip;
+use installer::windows::update;
+use installer::windows::upgrade;
+use installer::worker;
+use installer::ziplist qw(read_ziplist);
+
+our @EXPORT_OK = qw(main);
+
+sub main {
+ installer::logger::starttime();
+
+ my $exit_code = 0;
+
+ eval {
+ run();
+ };
+ if ($@) {
+ my $message = "ERROR: $@";
+
+ warn "ERROR: Failure in installer.pm\n";
+ warn "$message\n";
+ $exit_code = -1;
+
+ cleanup_on_error($message);
+ }
+
+ installer::logger::stoptime();
+
+ return $exit_code;
+}
+
+sub run {
+ installer::logger::print_message( "... checking environment variables ...\n" );
+ my $environmentvariableshashref = installer::control::check_system_environment();
+
+ installer::environment::set_global_environment_variables($environmentvariableshashref);
+
+ #################################
+ # Check and output of parameter
+ #################################
+
+ installer::parameter::saveparameter();
+ installer::parameter::getparameter();
+
+ installer::parameter::control_fundamental_parameter();
+ installer::parameter::setglobalvariables();
+ installer::parameter::control_required_parameter();
+
+ if (!($installer::globals::languages_defined_in_productlist)) { installer::languages::analyze_languagelist(); }
+ installer::parameter::outputparameter();
+
+ $installer::globals::build = uc($installer::globals::build); # using "SRC680" instead of "src680"
+
+ ######################################
+ # Creating the log directory
+ ######################################
+
+ my $empty = "";
+ my $loggingdir = installer::systemactions::create_directories("logging", \$empty);
+ $loggingdir = $loggingdir . $installer::globals::separator;
+ $installer::globals::exitlog = $loggingdir;
+
+ my $installdir = "";
+ my $currentdir = cwd();
+ my $shipinstalldir = "";
+ my $current_install_number = "";
+
+ ######################################
+ # Checking the system requirements
+ ######################################
+
+ installer::logger::print_message( "... checking required files ...\n" );
+ installer::control::check_system_path();
+
+ my $pathvariableshashref = installer::environment::create_pathvariables($environmentvariableshashref);
+
+ ###############################################
+ # Checking saved setting for Windows patches
+ ###############################################
+
+ if (( $installer::globals::iswindowsbuild ) && ( $installer::globals::prepare_winpatch )) { installer::windows::msiglobal::read_saved_mappings(); }
+
+ ###################################################
+ # Analyzing the settings and variables in zip.lst
+ ###################################################
+
+ my ($allsettingsarrayref, $allvariableshashref) = read_ziplist($installer::globals::ziplistname);
+
+ ########################################################
+ # Check if this is simple packaging mechanism
+ ########################################################
+
+ installer::simplepackage::check_simple_packager_project($allvariableshashref);
+
+ ####################################################################
+ # setting global variables
+ ####################################################################
+
+ installer::control::set_addsystemintegration($allvariableshashref);
+
+ ########################################################
+ # Re-define logging dir, after all variables are set
+ ########################################################
+
+ my $oldloggingdir = $loggingdir;
+ # FIXME: It would be better to use installer::systemactions::remove_complete_directory
+ # Though, we would need to remove only the lang-specific subdirectory for langpacks and helppacks
+ rmdir $oldloggingdir;
+ $loggingdir = installer::systemactions::create_directories("logging", \$empty);
+ $loggingdir = $loggingdir . $installer::globals::separator;
+ $installer::globals::exitlog = $loggingdir;
+
+ # checking, whether this is an opensource product
+
+ if (!($installer::globals::is_copy_only_project)) { installer::ziplist::set_manufacturer($allvariableshashref); }
+
+ ##############################################
+ # Checking version of makecab.exe
+ ##############################################
+
+ if ( $installer::globals::iswindowsbuild && (!defined($ENV{'CROSS_COMPILING'}) || $ENV{'CROSS_COMPILING'} ne 'TRUE' || $installer::globals::packageformat eq 'msi')) { installer::control::check_makecab_version(); }
+
+ ##########################################################
+ # Getting the include path from the settings in zip list
+ ##########################################################
+
+ my $includepathref = installer::ziplist::getinfofromziplist($allsettingsarrayref, "include");
+ if ( $$includepathref eq "" )
+ {
+ die 'Definition for "include" not found in ' . $installer::globals::ziplistname;
+ }
+
+ my $includepatharrayref = installer::converter::convert_stringlist_into_array($includepathref, ",");
+
+ installer::ziplist::replace_all_variables_in_paths($includepatharrayref, $pathvariableshashref);
+
+ installer::ziplist::replace_minor_in_paths($includepatharrayref);
+
+ installer::ziplist::replace_packagetype_in_paths($includepatharrayref);
+
+ installer::ziplist::resolve_relative_paths($includepatharrayref);
+
+ installer::ziplist::remove_ending_separator($includepatharrayref);
+
+ ##############################################
+ # Collecting all files from all include
+ # paths in global hashes.
+ ##############################################
+
+ installer::worker::collect_all_files_from_includepaths($includepatharrayref);
+
+ ##############################################
+ # Analyzing languages in zip.lst if required
+ # Probably no longer used.
+ ##############################################
+
+ if ($installer::globals::languages_defined_in_productlist) { installer::languages::get_info_about_languages($allsettingsarrayref); }
+
+ #####################################
+ # Windows requires the LCID list
+ #####################################
+
+ if ( $installer::globals::iswindowsbuild ) { installer::control::read_lcidlist($includepatharrayref); }
+
+ #####################################################################
+ # Including additional inc files for variable settings, if defined
+ #####################################################################
+
+ if ( $allvariableshashref->{'ADD_INCLUDE_FILES'} ) { installer::worker::add_variables_from_inc_to_hashref($allvariableshashref, $includepatharrayref); }
+
+ #####################################
+ # Analyzing the setup script
+ #####################################
+
+ if ($installer::globals::setupscript_defined_in_productlist) { installer::setupscript::set_setupscript_name($allsettingsarrayref, $includepatharrayref); }
+
+ installer::logger::globallog("setup script file: $installer::globals::setupscriptname");
+
+ installer::logger::print_message( "... analyzing script: $installer::globals::setupscriptname ... \n" );
+
+ my $setupscriptref = installer::files::read_file($installer::globals::setupscriptname); # Reading the setup script file
+
+ # Resolving variables defined in the zip list file into setup script
+ # All the variables are defined in $allvariablesarrayref
+
+ installer::scpzipfiles::replace_all_ziplistvariables_in_file($setupscriptref, $allvariableshashref);
+
+ # Resolving %variables defined in the installation object
+
+ my $allscriptvariablesref = installer::setupscript::get_all_scriptvariables_from_installation_object($setupscriptref);
+
+ installer::setupscript::add_lowercase_productname_setupscriptvariable($allscriptvariablesref);
+
+ installer::setupscript::resolve_lowercase_productname_setupscriptvariable($allscriptvariablesref);
+
+ $setupscriptref = installer::setupscript::replace_all_setupscriptvariables_in_script($setupscriptref, $allscriptvariablesref);
+
+ # Adding all variables defined in the installation object into the hash of all variables.
+ # 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
+
+ installer::setupscript::add_installationobject_to_variables($allvariableshashref, $allscriptvariablesref);
+
+ # Replacing preset properties, not using the default mechanisms (for example for UNIXPRODUCTNAME)
+ installer::setupscript::replace_preset_properties($allvariableshashref);
+
+ installer::scpzipfiles::replace_all_ziplistvariables_in_file($setupscriptref, $allvariableshashref);
+
+
+ installer::logger::log_hashref($allvariableshashref);
+
+ installer::logger::print_message( "... analyzing directories ... \n" );
+
+ # Collect all directories in the script to get the destination dirs
+
+ my $dirsinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "Directory");
+
+ if ( $allvariableshashref->{'SHIFT_BASIS_INTO_BRAND_LAYER'} ) { $dirsinproductarrayref = installer::scriptitems::shift_basis_directory_parents($dirsinproductarrayref); }
+ if ( $allvariableshashref->{'OFFICEDIRECTORYNAME'} ) { installer::scriptitems::set_officedirectory_name($dirsinproductarrayref, $allvariableshashref->{'OFFICEDIRECTORYNAME'}); }
+
+
+ installer::scriptitems::resolve_all_directory_names($dirsinproductarrayref);
+
+ installer::logger::print_message( "... analyzing files ... \n" );
+
+ my $filesinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "File");
+
+ if (( ! $installer::globals::iswindowsbuild ) &&
+ ( ! $installer::globals::isrpmbuild ) &&
+ ( ! $installer::globals::isdebbuild ) &&
+ ( ! $installer::globals::issolarispkgbuild ) &&
+ ( $installer::globals::packageformat ne "installed" ) &&
+ ( $installer::globals::packageformat ne "dmg" ) &&
+ ( $installer::globals::packageformat ne "archive" ))
+ { installer::control::check_oxtfiles($filesinproductarrayref); }
+
+ if (! $installer::globals::languagepack)
+ {
+ $filesinproductarrayref = installer::scriptitems::remove_Languagepacklibraries_from_Installset($filesinproductarrayref);
+ }
+
+ if (! $installer::globals::helppack)
+ {
+ $filesinproductarrayref = installer::scriptitems::remove_Helppacklibraries_from_Installset($filesinproductarrayref);
+ }
+
+ installer::logger::print_message( "... analyzing scpactions ... \n" );
+
+ my $scpactionsinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "ScpAction");
+
+ if ( $installer::globals::languagepack ) { installer::scriptitems::use_langpack_copy_scpaction($scpactionsinproductarrayref); }
+ elsif ( $installer::globals::helppack ) { installer::scriptitems::use_langpack_copy_scpaction($scpactionsinproductarrayref); }
+ # TODO: why is this not done in scp2 based on the value of $(ENABLE_RELEASE_BUILD)?
+ elsif ( $allvariableshashref->{'PRODUCTNAME'} eq "LibreOfficeDev" ) { installer::scriptitems::use_devversion_copy_scpaction($scpactionsinproductarrayref); }
+
+ installer::scriptitems::change_keys_of_scpactions($scpactionsinproductarrayref);
+
+ installer::logger::print_message( "... analyzing shortcuts ... \n" );
+
+ my $linksinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "Shortcut");
+
+ installer::logger::print_message( "... analyzing unix links ... \n" );
+
+ my $unixlinksinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "Unixlink");
+
+ installer::logger::print_message( "... analyzing profile ... \n" );
+
+ my $profilesinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "Profile");
+
+ installer::logger::print_message( "... analyzing profileitems ... \n" );
+
+ my $profileitemsinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "ProfileItem");
+
+ my $folderinproductarrayref;
+ my $folderitemsinproductarrayref;
+ my $folderitempropertiesinproductarrayref;
+ my $registryitemsinproductarrayref;
+ my $windowscustomactionsarrayref;
+ my $mergemodulesarrayref;
+
+ if ( $installer::globals::iswindowsbuild ) # Windows specific items: Folder, FolderItem, RegistryItem, WindowsCustomAction
+ {
+ installer::logger::print_message( "... analyzing folders ... \n" );
+
+ $folderinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "Folder");
+
+ installer::logger::print_message( "... analyzing folderitems ... \n" );
+
+ $folderitemsinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "FolderItem");
+
+ installer::setupscript::add_predefined_folder($folderitemsinproductarrayref, $folderinproductarrayref);
+
+ installer::logger::print_message( "... analyzing folderitemproperties ... \n" );
+
+ $folderitempropertiesinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "FolderItemProperty");
+
+ installer::setupscript::prepare_non_advertised_files($folderitemsinproductarrayref, $filesinproductarrayref);
+
+ installer::logger::print_message( "... analyzing registryitems ... \n" );
+
+ $registryitemsinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "RegistryItem");
+
+ $registryitemsinproductarrayref = installer::scriptitems::remove_uninstall_regitems_from_script($registryitemsinproductarrayref);
+
+ installer::logger::print_message( "... analyzing Windows custom actions ... \n" );
+
+ $windowscustomactionsarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "WindowsCustomAction");
+
+ installer::logger::print_message( "... analyzing Windows merge modules ... \n" );
+
+ $mergemodulesarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "MergeModule");
+ }
+
+ my $modulesinproductarrayref;
+
+ if (!($installer::globals::is_copy_only_project))
+ {
+ installer::logger::print_message( "... analyzing modules ... \n" );
+
+ $modulesinproductarrayref = installer::setupscript::get_all_items_from_script($setupscriptref, "Module");
+
+ installer::scriptitems::resolve_assigned_modules($modulesinproductarrayref);
+
+ $modulesinproductarrayref = installer::scriptitems::remove_template_modules($modulesinproductarrayref);
+
+ installer::scriptitems::set_children_flag($modulesinproductarrayref);
+
+ installer::scriptitems::collect_all_languagemodules($modulesinproductarrayref);
+
+ # Assigning the modules to the items
+
+ installer::scriptitems::assigning_modules_to_items($modulesinproductarrayref, $filesinproductarrayref, "Files");
+
+ installer::scriptitems::assigning_modules_to_items($modulesinproductarrayref, $unixlinksinproductarrayref, "Unixlinks");
+
+ installer::scriptitems::assigning_modules_to_items($modulesinproductarrayref, $dirsinproductarrayref, "Dirs");
+ }
+
+ #################################################
+ # Part 1b: The language dependent part
+ # (still platform independent)
+ #################################################
+
+ # Now starts the language dependent part, if more than one product is defined on the command line
+ # Example -l en-US,de#es,fr,it defines two multilingual products
+
+ ###############################################################################
+ # Beginning of language dependent part
+ # The for iterates over all products, separated by an # in the language list
+ ###############################################################################
+
+ for ( my $n = 0; $n <= $#installer::globals::languageproducts; $n++ )
+ {
+ my $languagesarrayref = installer::languages::get_all_languages_for_one_product($installer::globals::languageproducts[$n], $allvariableshashref);
+ my @setuplanguagesarray = @{ $languagesarrayref };
+
+ my $languagestringref = installer::languages::get_language_string($languagesarrayref);
+ installer::logger::print_message( "------------------------------------\n" );
+ installer::logger::print_message( "... languages $$languagestringref ... \n" );
+
+ if ( $installer::globals::languagepack )
+ {
+ $installer::globals::addsystemintegration = 0;
+ $installer::globals::addlicensefile = 0;
+ $installer::globals::makedownload = 1;
+ }
+
+ if ( $installer::globals::helppack )
+ {
+ $installer::globals::addsystemintegration = 0;
+ $installer::globals::addlicensefile = 0;
+ $installer::globals::makedownload = 1;
+ @setuplanguagesarray = grep { $_ ne "en-US" } @setuplanguagesarray;
+ unshift(@setuplanguagesarray, "en-US");
+ }
+
+ ############################################################
+ # Beginning of language specific logging mechanism
+ # Until now only global logging into default: logfile.txt
+ ############################################################
+
+ @installer::globals::logfileinfo = (); # new logfile array and new logfile name
+ installer::logger::copy_globalinfo_into_logfile();
+
+ my $loglanguagestring = $$languagestringref;
+ my $loglanguagestring_orig = $loglanguagestring;
+ if (length($loglanguagestring) > $installer::globals::max_lang_length)
+ {
+ my $number_of_languages = installer::systemactions::get_number_of_langs($loglanguagestring);
+ #replace this in the same it was done in installer/windows/directory.pm
+ #chomp(my $shorter = `echo $loglanguagestring | $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($loglanguagestring, 8); # taking only the first 8 digits
+ $loglanguagestring = "lang_" . $number_of_languages . "_id_" . $id;
+ }
+
+ $installer::globals::logfilename = "log_" . $installer::globals::build;
+ $installer::globals::logfilename .= "_" . $loglanguagestring;
+ $installer::globals::logfilename .= ".log";
+ $loggingdir = $loggingdir . $loglanguagestring . $installer::globals::separator;
+ installer::systemactions::create_directory($loggingdir);
+
+ if ($loglanguagestring ne $loglanguagestring_orig) {
+ (my $dir = $loggingdir) =~ s!/$!!;
+ open(my $F1, "> $dir.dir");
+ open(my $F2, "> " . $loggingdir . $installer::globals::logfilename . '.file');
+ my @s = map { "$_\n" } split('_', $loglanguagestring_orig);
+ print $F1 @s;
+ print $F2 @s;
+ }
+
+ $installer::globals::exitlog = $loggingdir;
+
+ ###################################################################
+ # Reading an existing msi database, to prepare update and patch
+ ###################################################################
+
+ my $refdatabase = "";
+ my $uniquefilename = "";
+ my $revuniquefilename = "";
+ my $revshortfilename = "";
+ my $allupdatesequences = "";
+ my $allupdatecomponents = "";
+ my $allupdatefileorder = "";
+ my $allupdatecomponentorder = "";
+ my $shortdirname = "";
+ my $componentid = "";
+ my $componentidkeypath = "";
+ my $alloldproperties = "";
+ my $allupdatelastsequences = "";
+ my $allupdatediskids = "";
+
+ if ( $installer::globals::packageformat eq 'msi' )
+ {
+ if ( $allvariableshashref->{'UPDATE_DATABASE'} )
+ {
+ installer::logger::print_message( "... analyzing update database ...\n" );
+ $refdatabase = installer::windows::update::readdatabase($allvariableshashref, $languagestringref, $includepatharrayref);
+
+ if ( $installer::globals::updatedatabase )
+ {
+ ($uniquefilename, $revuniquefilename, $revshortfilename, $allupdatesequences, $allupdatecomponents, $allupdatefileorder, $allupdatecomponentorder, $shortdirname, $componentid, $componentidkeypath, $alloldproperties, $allupdatelastsequences, $allupdatediskids) = installer::windows::update::create_database_hashes($refdatabase);
+ }
+ }
+
+ # Always analyze the merge module.
+ # We need to investigate the directory table in merge module to emit
+ # custom action for directory names that start with standard prefix
+ if ( $mergemodulesarrayref > -1 ) {
+ installer::windows::update::readmergedatabase($mergemodulesarrayref, $languagestringref, $includepatharrayref);
+ }
+ }
+
+ ##############################################
+ # Setting global code variables for Windows
+ ##############################################
+
+ if (!($installer::globals::is_copy_only_project))
+ {
+ if ((( $installer::globals::iswindowsbuild ) && ( $installer::globals::packageformat ne "archive" ) && ( $installer::globals::packageformat ne "installed" ) ) || $installer::globals::packageformat eq 'msi' )
+ {
+ installer::windows::msiglobal::set_global_code_variables($languagesarrayref, $languagestringref, $allvariableshashref, $alloldproperties);
+ }
+ }
+
+ ################################################
+ # Resolving include paths (language dependent)
+ ################################################
+
+ my $includepatharrayref_lang = installer::ziplist::replace_languages_in_paths($includepatharrayref, \@setuplanguagesarray);
+
+ if ( $installer::globals::refresh_includepaths ) { installer::worker::collect_all_files_from_includepaths($includepatharrayref_lang); }
+
+ installer::ziplist::list_all_files_from_include_path($includepatharrayref_lang);
+
+ ##############################################
+ # Analyzing spellchecker languages
+ ##############################################
+
+ if ( $allvariableshashref->{'SPELLCHECKERFILE'} ) { installer::worker::set_spellcheckerlanguages($languagesarrayref, $allvariableshashref); }
+
+ #####################################
+ # Language dependent directory part
+ #####################################
+
+ my $dirsinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($dirsinproductarrayref, $languagesarrayref);
+
+ # A new directory array is needed ($dirsinproductlanguageresolvedarrayref instead of $dirsinproductarrayref)
+ # because $dirsinproductarrayref is needed in get_Destination_Directory_For_Item_From_Directorylist
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($dirsinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::checking_directories_with_corrupt_hostname($dirsinproductlanguageresolvedarrayref, $languagesarrayref);
+
+ installer::scriptitems::set_global_directory_hostnames($dirsinproductlanguageresolvedarrayref, $allvariableshashref);
+
+ #####################################
+ # files part, language dependent
+ #####################################
+
+ installer::logger::print_message( "... analyzing files ...\n" );
+
+ my $filesinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($filesinproductarrayref, $languagesarrayref);
+
+ if ( ! $installer::globals::set_office_start_language )
+ {
+ $filesinproductlanguageresolvedarrayref = installer::scriptitems::remove_office_start_language_files($filesinproductlanguageresolvedarrayref);
+ }
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($filesinproductlanguageresolvedarrayref);
+
+ if ( $installer::globals::iswin and $^O =~ /MSWin/i ) { installer::converter::convert_slash_to_backslash($filesinproductlanguageresolvedarrayref); }
+
+ $filesinproductlanguageresolvedarrayref = installer::scriptitems::remove_non_existent_languages_in_productlists($filesinproductlanguageresolvedarrayref, $languagestringref, "Name", "file");
+
+ installer::scriptitems::get_Destination_Directory_For_Item_From_Directorylist($filesinproductlanguageresolvedarrayref, $dirsinproductarrayref);
+
+ installer::scriptitems::get_Source_Directory_For_Files_From_Includepathlist($filesinproductlanguageresolvedarrayref, $includepatharrayref_lang, $dirsinproductlanguageresolvedarrayref, "Files", $allvariableshashref);
+
+ $filesinproductlanguageresolvedarrayref = installer::scriptitems::remove_Files_Without_Sourcedirectory($filesinproductlanguageresolvedarrayref);
+
+ if ($installer::globals::languagepack)
+ {
+ $filesinproductlanguageresolvedarrayref = installer::scriptitems::remove_Files_For_Languagepacks($filesinproductlanguageresolvedarrayref);
+ }
+
+ if ( ! $allvariableshashref->{'NO_README_IN_ROOTDIR'} )
+ {
+ $filesinproductlanguageresolvedarrayref = installer::scriptitems::add_License_Files_into_Installdir($filesinproductlanguageresolvedarrayref, $dirsinproductlanguageresolvedarrayref, $languagesarrayref);
+ }
+
+ installer::scriptitems::make_filename_language_specific($filesinproductlanguageresolvedarrayref);
+
+ ######################################################################################
+ # Processing files with flag FILELIST and putting listed files into the file list
+ ######################################################################################
+
+ installer::logger::print_message( "... analyzing files with flag FILELIST ...\n" );
+
+ ($filesinproductlanguageresolvedarrayref, $unixlinksinproductarrayref) = installer::filelists::resolve_filelist_flag($filesinproductlanguageresolvedarrayref, $unixlinksinproductarrayref, $ENV{'INSTDIR'});
+
+ # packed files sometimes contain a "$" in their name: HighlightText$1.class. For epm the "$" has to be quoted by "$$"
+
+ if (!( $installer::globals::iswindowsbuild || $installer::globals::simple ) )
+ {
+ installer::scriptitems::quoting_illegal_filenames($filesinproductlanguageresolvedarrayref);
+ }
+
+ #####################################
+ # Files with flag SCPZIP_REPLACE
+ #####################################
+
+ installer::logger::print_message( "... analyzing files with flag SCPZIP_REPLACE ...\n" );
+
+ # Editing files with flag SCPZIP_REPLACE.
+
+ installer::scpzipfiles::resolving_scpzip_replace_flag($filesinproductlanguageresolvedarrayref, $allvariableshashref, "File", $languagestringref);
+
+ #########################################################
+ # language dependent unix links part
+ #########################################################
+
+ installer::logger::print_message( "... analyzing unix links ...\n" );
+
+ my $unixlinksinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($unixlinksinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($unixlinksinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::get_Destination_Directory_For_Item_From_Directorylist($unixlinksinproductlanguageresolvedarrayref, $dirsinproductarrayref);
+
+ ############################################
+ # Collecting directories for epm list file
+ ############################################
+
+ installer::logger::print_message( "... analyzing all directories for this product ...\n" );
+
+ # There are two ways for a directory to be included into the epm directory list:
+ # 1. Looking for all destination paths in the files array
+ # 2. Looking for directories with CREATE flag in the directory array
+ # Advantage: Many paths are hidden in zip files, they are not defined in the setup script.
+ # It will be possible, that in the setup script only those directories have to be defined,
+ # that have a CREATE flag. All other directories are created, if they contain at least one file.
+
+ my $alldirectoryhash = installer::scriptitems::collect_directories_from_filesarray($filesinproductlanguageresolvedarrayref, $unixlinksinproductlanguageresolvedarrayref);
+ my $directoriesforepmarrayref = installer::scriptitems::collect_directories_with_create_flag_from_directoryarray($dirsinproductlanguageresolvedarrayref, $alldirectoryhash);
+
+ #########################################################
+ # language dependent scpactions part
+ #########################################################
+
+ my $scpactionsinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($scpactionsinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($scpactionsinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::get_Source_Directory_For_Files_From_Includepathlist($scpactionsinproductlanguageresolvedarrayref, $includepatharrayref_lang, $dirsinproductlanguageresolvedarrayref, "ScpActions", $allvariableshashref);
+
+ # Editing scpactions with flag SCPZIP_REPLACE.
+
+ installer::scpzipfiles::resolving_scpzip_replace_flag($scpactionsinproductlanguageresolvedarrayref, $allvariableshashref, "ScpAction", $languagestringref);
+
+ #########################################################
+ # language dependent links part
+ #########################################################
+
+ installer::logger::print_message( "... analyzing links ...\n" );
+
+ my $linksinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($linksinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($linksinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::get_destination_file_path_for_links($linksinproductlanguageresolvedarrayref, $filesinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::get_Destination_Directory_For_Item_From_Directorylist($linksinproductlanguageresolvedarrayref, $dirsinproductarrayref);
+
+ # Now taking all links that have no FileID but a ShortcutID, linking to another link
+
+ installer::scriptitems::get_destination_link_path_for_links($linksinproductlanguageresolvedarrayref);
+
+ $linksinproductlanguageresolvedarrayref = installer::scriptitems::remove_workstation_only_items($linksinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::resolve_links_with_flag_relative($linksinproductlanguageresolvedarrayref);
+
+ #########################################################
+ # language dependent part for profiles and profileitems
+ #########################################################
+
+ my $profilesinproductlanguageresolvedarrayref;
+ my $profileitemsinproductlanguageresolvedarrayref;
+
+ if ((!($installer::globals::is_copy_only_project)) && (!($installer::globals::languagepack)) && (!($installer::globals::helppack)))
+ {
+ installer::logger::print_message( "... creating profiles ...\n" );
+
+ $profilesinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($profilesinproductarrayref, $languagesarrayref);
+
+ $profileitemsinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($profileitemsinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($profilesinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($profileitemsinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::replace_setup_variables($profileitemsinproductlanguageresolvedarrayref, $languagestringref, $allvariableshashref);
+
+ # Note that patch_user_dir is not related to the killed
+ # ancient (not MSP) "patch" thing, I think.
+ if ( $installer::globals::patch_user_dir )
+ {
+ installer::scriptitems::replace_userdir_variable($profileitemsinproductlanguageresolvedarrayref, $allvariableshashref);
+ }
+
+ installer::scriptitems::get_Destination_Directory_For_Item_From_Directorylist($profilesinproductlanguageresolvedarrayref, $dirsinproductarrayref);
+
+ # Now the Profiles can be created
+
+ installer::profiles::create_profiles($profilesinproductlanguageresolvedarrayref, $profileitemsinproductlanguageresolvedarrayref, $filesinproductlanguageresolvedarrayref, $languagestringref, $allvariableshashref);
+ }
+
+ my $registryitemsinproductlanguageresolvedarrayref; # cannot be defined in the following "if ( $installer::globals::iswindowsbuild )"
+ my $folderinproductlanguageresolvedarrayref; # cannot be defined in the following "if ( $installer::globals::iswindowsbuild )"
+ my $folderitemsinproductlanguageresolvedarrayref; # cannot be defined in the following "if ( $installer::globals::iswindowsbuild )"
+
+ if ( $installer::globals::iswindowsbuild ) # Windows specific items: Folder, FolderItem, RegistryItem
+ {
+ #########################################################
+ # language dependent part for folder
+ #########################################################
+
+ installer::logger::print_message( "... analyzing folder ...\n" );
+
+ $folderinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($folderinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($folderinproductlanguageresolvedarrayref);
+
+ #########################################################
+ # language dependent part for folderitems
+ #########################################################
+
+ installer::logger::print_message( "... analyzing folderitems ...\n" );
+
+ $folderitemsinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($folderitemsinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($folderitemsinproductlanguageresolvedarrayref);
+
+ #########################################################
+ # language dependent part for registryitems
+ #########################################################
+
+ installer::logger::print_message( "... analyzing registryitems ...\n" );
+
+ $registryitemsinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($registryitemsinproductarrayref, $languagesarrayref);
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($registryitemsinproductlanguageresolvedarrayref);
+ }
+
+ #########################################################
+ # language dependent part for modules
+ #########################################################
+
+ my $modulesinproductlanguageresolvedarrayref;
+
+ if (!($installer::globals::is_copy_only_project))
+ {
+ installer::logger::print_message( "... analyzing modules ...\n" );
+
+ $modulesinproductlanguageresolvedarrayref = installer::scriptitems::resolving_all_languages_in_productlists($modulesinproductarrayref, $languagesarrayref);
+
+ $modulesinproductlanguageresolvedarrayref = installer::scriptitems::remove_not_required_language_modules($modulesinproductlanguageresolvedarrayref, $languagesarrayref);
+
+ if ( $installer::globals::analyze_spellcheckerlanguage )
+ {
+ $modulesinproductlanguageresolvedarrayref = installer::scriptitems::remove_not_required_spellcheckerlanguage_modules($modulesinproductlanguageresolvedarrayref);
+
+ $filesinproductlanguageresolvedarrayref = installer::scriptitems::remove_not_required_spellcheckerlanguage_files($filesinproductlanguageresolvedarrayref);
+ $directoriesforepmarrayref = installer::scriptitems::remove_not_required_spellcheckerlanguage_files($directoriesforepmarrayref);
+ }
+
+ installer::scriptitems::changing_name_of_language_dependent_keys($modulesinproductlanguageresolvedarrayref);
+
+ installer::scriptitems::select_required_language_strings($modulesinproductlanguageresolvedarrayref); # using english strings
+
+ }
+
+ # Copy-only projects can now start to copy all items File and ScpAction
+ if ( $installer::globals::is_copy_only_project ) { installer::copyproject::copy_project($filesinproductlanguageresolvedarrayref, $scpactionsinproductlanguageresolvedarrayref, $loggingdir, $languagestringref, $shipinstalldir, $allsettingsarrayref); }
+
+ # Language pack projects can now start to select the required information
+ if ( $installer::globals::languagepack )
+ {
+ $filesinproductlanguageresolvedarrayref = installer::languagepack::select_language_items($filesinproductlanguageresolvedarrayref, $languagesarrayref, "File");
+ $scpactionsinproductlanguageresolvedarrayref = installer::languagepack::select_language_items($scpactionsinproductlanguageresolvedarrayref, $languagesarrayref, "ScpAction");
+ $linksinproductlanguageresolvedarrayref = installer::languagepack::select_language_items($linksinproductlanguageresolvedarrayref, $languagesarrayref, "Shortcut");
+ $unixlinksinproductlanguageresolvedarrayref = installer::languagepack::select_language_items($unixlinksinproductlanguageresolvedarrayref, $languagesarrayref, "Unixlink");
+ @{$folderitemsinproductlanguageresolvedarrayref} = (); # no folderitems in languagepacks
+
+ # Collecting the directories again, to include only the language specific directories
+ $alldirectoryhash = installer::scriptitems::collect_directories_from_filesarray($filesinproductlanguageresolvedarrayref, $unixlinksinproductlanguageresolvedarrayref);
+ $directoriesforepmarrayref = installer::scriptitems::collect_directories_with_create_flag_from_directoryarray($dirsinproductlanguageresolvedarrayref, $alldirectoryhash);
+ @$directoriesforepmarrayref = sort { $a->{"HostName"} cmp $b->{"HostName"} } @$directoriesforepmarrayref;
+
+ if ( $installer::globals::iswindowsbuild )
+ {
+ $registryitemsinproductlanguageresolvedarrayref = installer::worker::select_langpack_items($registryitemsinproductlanguageresolvedarrayref, "RegistryItem");
+ }
+
+ }
+
+ # Help pack projects can now start to select the required information
+ if ( $installer::globals::helppack )
+ {
+ $filesinproductlanguageresolvedarrayref = installer::helppack::select_help_items($filesinproductlanguageresolvedarrayref, $languagesarrayref, "File");
+ $scpactionsinproductlanguageresolvedarrayref = installer::helppack::select_help_items($scpactionsinproductlanguageresolvedarrayref, $languagesarrayref, "ScpAction");
+ $linksinproductlanguageresolvedarrayref = installer::helppack::select_help_items($linksinproductlanguageresolvedarrayref, $languagesarrayref, "Shortcut");
+ $unixlinksinproductlanguageresolvedarrayref = installer::helppack::select_help_items($unixlinksinproductlanguageresolvedarrayref, $languagesarrayref, "Unixlink");
+ @{$folderitemsinproductlanguageresolvedarrayref} = (); # no folderitems in helppacks
+
+ # Collecting the directories again, to include only the language specific directories
+ $alldirectoryhash = installer::scriptitems::collect_directories_from_filesarray($filesinproductlanguageresolvedarrayref, $unixlinksinproductlanguageresolvedarrayref);
+ $directoriesforepmarrayref = installer::scriptitems::collect_directories_with_create_flag_from_directoryarray($dirsinproductlanguageresolvedarrayref, $alldirectoryhash);
+ @$directoriesforepmarrayref = sort { $a->{"HostName"} cmp $b->{"HostName"} } @$directoriesforepmarrayref;
+
+ if ( $installer::globals::iswindowsbuild )
+ {
+ $registryitemsinproductlanguageresolvedarrayref = installer::worker::select_helppack_items($registryitemsinproductlanguageresolvedarrayref, "RegistryItem");
+ }
+
+ }
+
+ #########################################################
+ # Collecting all scp actions
+ #########################################################
+
+ installer::worker::collect_scpactions($scpactionsinproductlanguageresolvedarrayref);
+
+ ###########################################################
+ # Simple package projects can now start to create the
+ # installation structure by creating Directories, Files
+ # Links and ScpActions. This is the last platform
+ # independent part.
+ ###########################################################
+
+ if ( $installer::globals::is_simple_packager_project )
+ {
+ installer::simplepackage::create_simple_package($filesinproductlanguageresolvedarrayref, $directoriesforepmarrayref, $scpactionsinproductlanguageresolvedarrayref, $linksinproductlanguageresolvedarrayref, $unixlinksinproductlanguageresolvedarrayref, $loggingdir, $languagestringref, $shipinstalldir, $allsettingsarrayref, $allvariableshashref, $includepatharrayref);
+ next; # ! leaving the current loop, because no further packaging required.
+ }
+
+ ###########################################################
+ # Analyzing the package structure
+ ###########################################################
+
+ installer::logger::print_message( "... analyzing package list ...\n" );
+
+ my $packages = installer::packagelist::collectpackages($modulesinproductlanguageresolvedarrayref, $languagesarrayref);
+ installer::packagelist::check_packagelist($packages);
+
+ $packages = installer::packagelist::analyze_list($packages, $modulesinproductlanguageresolvedarrayref);
+ installer::packagelist::remove_multiple_modules_packages($packages);
+
+ # printing packages content:
+ installer::packagelist::log_packages_content($packages);
+ installer::packagelist::create_module_destination_hash($packages, $allvariableshashref);
+
+ #################################################
+ # Part 2: The platform dependent part
+ #################################################
+
+ #################################################
+ # Part 2a: All non-Windows platforms
+ #################################################
+
+ #########################################################
+ # ... creating epm list file ...
+ # Only for non-Windows platforms
+ #########################################################
+
+ if (!( $installer::globals::iswindowsbuild ))
+ {
+ ####################################################
+ # Writing log file before packages are packed
+ ####################################################
+
+ installer::logger::print_message( "... creating log file " . $loggingdir . $installer::globals::logfilename . "\n" );
+ installer::files::save_file($loggingdir . $installer::globals::logfilename, \@installer::globals::logfileinfo);
+
+ ####################################################
+ # Creating directories
+ ####################################################
+
+ installer::download::set_download_filename($languagestringref, $allvariableshashref);
+
+ $installdir = installer::worker::create_installation_directory($shipinstalldir, $languagestringref, \$current_install_number);
+
+ my $listfiledir = installer::systemactions::create_directories("listfile", $languagestringref);
+ my $installlogdir = installer::systemactions::create_directory_next_to_directory($installdir, "log");
+
+ ####################################################
+ # Reading for Solaris all package descriptions
+ # from file defined in property PACKAGEMAP
+ ####################################################
+
+ if ( $installer::globals::issolarisbuild ) { installer::epmfile::read_packagemap($allvariableshashref, $includepatharrayref, $languagesarrayref); }
+
+ ###########################################
+ # Checking epm state
+ ###########################################
+
+ my $epmexecutable = "";
+ if ( $installer::globals::call_epm )
+ {
+ $epmexecutable = installer::epmfile::find_epm_on_system($includepatharrayref);
+ installer::epmfile::set_patch_state($epmexecutable); # setting $installer::globals::is_special_epm
+ }
+
+ # shuffle array to reduce parallel packaging process in pool
+ @{$packages} = shuffle @{$packages}
+ unless $installer::globals::simple;
+
+ # iterating over all packages
+ for my $onepackage ( @{$packages} )
+ {
+ # checking, if this is a language pack or a project pack.
+ # Creating language packs only, if $installer::globals::languagepack is set. Parameter: -languagepack
+
+ if ( $installer::globals::languagepack ) { installer::languagepack::replace_languagestring_variable($onepackage, $languagestringref); }
+
+ # checking, if this is a help pack
+ # Creating help packs only, if $installer::globals::helppack is set. Parameter: -helppack
+
+ if ( $installer::globals::helppack ) { installer::helppack::replace_languagestring_variable($onepackage, $languagestringref); }
+
+ my $onepackagename = $onepackage->{'module'}; # name of the top module (required)
+
+ my $shellscriptsfilename = "";
+ if ( $onepackage->{'script'} ) { $shellscriptsfilename = $onepackage->{'script'}; }
+
+ ###########################
+ # package name
+ ###########################
+
+ my $packagename = "";
+
+ if ( $installer::globals::issolarisbuild ) # only for Solaris
+ {
+ if ( $onepackage->{'solarispackagename'} ) { $packagename = $onepackage->{'solarispackagename'}; }
+ }
+ else # not Solaris
+ {
+ if ( $onepackage->{'packagename'} ) { $packagename = $onepackage->{'packagename'}; }
+ }
+
+ if (!($packagename eq ""))
+ {
+ installer::packagelist::resolve_packagevariables(\$packagename, $allvariableshashref, 0);
+ }
+
+ # Debian allows no underline in package name
+ if ( $installer::globals::debian ) { $packagename =~ s/_/-/g; }
+
+ ####################################################
+ # Header for this package into log file
+ ####################################################
+
+ installer::logger::include_header_into_logfile("Creating package: $packagename");
+
+ ###########################################
+ # Root path, can be defined as parameter
+ ###########################################
+
+ my $packagerootpath = "";
+
+ if ($installer::globals::rootpath eq "")
+ {
+ $packagerootpath = $onepackage->{'destpath'};
+ installer::packagelist::resolve_packagevariables(\$packagerootpath, $allvariableshashref, 1);
+ # we put branding and common stuff into the same prefix on unixes => $packagerootpath must be the whole prefix, including the product name
+ if ($installer::globals::isunix) { $packagerootpath .= "/$allvariableshashref->{'UNIXBASISROOTNAME'}"; }
+ if ( $^O =~ /darwin/i ) { $packagerootpath =~ s/\/opt\//\/Applications\//; }
+ }
+ else
+ {
+ $packagerootpath = $installer::globals::rootpath;
+ }
+
+ #################################
+ # collecting items for package
+ #################################
+
+ my $filesinpackage = installer::packagelist::find_files_for_package($filesinproductlanguageresolvedarrayref, $onepackage);
+ my $unixlinksinpackage = installer::packagelist::find_files_for_package($unixlinksinproductlanguageresolvedarrayref, $onepackage);
+ my $linksinpackage = installer::packagelist::find_links_for_package($linksinproductlanguageresolvedarrayref, $onepackage);
+ my $dirsinpackage = installer::packagelist::find_dirs_for_package($directoriesforepmarrayref, $onepackage);
+
+ #############################################
+ # copying the collectors for each package
+ #############################################
+
+ $filesinpackage = installer::converter::copy_collector($filesinpackage);
+ $linksinpackage = installer::converter::copy_collector($linksinpackage);
+ $unixlinksinpackage = installer::converter::copy_collector($unixlinksinpackage);
+ $dirsinpackage = installer::converter::copy_collector($dirsinpackage);
+
+ ###########################################
+ # setting the root path for the packages
+ ###########################################
+
+ installer::scriptitems::add_rootpath_to_directories($dirsinpackage, $packagerootpath);
+ installer::scriptitems::add_rootpath_to_files($filesinpackage, $packagerootpath);
+ installer::scriptitems::add_rootpath_to_links($linksinpackage, $packagerootpath);
+ installer::scriptitems::add_rootpath_to_files($unixlinksinpackage, $packagerootpath);
+
+ ###############################################
+ # nothing to do, if $filesinpackage is empty
+ ###############################################
+
+ if ( ! ( $#{$filesinpackage} > -1 ))
+ {
+ my $infoline = "\n\nNo file in package: $packagename \-\> Skipping\n\n";
+ push(@installer::globals::logfileinfo, $infoline);
+ next; # next package, end of loop !
+ }
+
+ ###########################################
+ # Stripping libraries
+ ###########################################
+
+ # Building for non Windows platforms in cws requires, that all files are stripped before packaging:
+ # 1. copy all files that need to be stripped locally
+ # 2. strip all these files
+
+ if ( $installer::globals::strip )
+ {
+ strip_libraries($filesinpackage, $languagestringref);
+ }
+
+ if ( $installer::globals::simple ) {
+ installer::worker::install_simple($onepackagename, $$languagestringref, $dirsinpackage, $filesinpackage, $linksinpackage, $unixlinksinpackage);
+ }
+ else {
+ # epm list file format:
+ # type mode owner group destination source options
+ # Example for a file: f 755 root sys /usr/bin/foo foo
+ # Example for a directory: d 755 root sys /var/spool/foo -
+ # Example for a link: l 000 root sys /usr/bin/linkname filename
+ # The source field specifies the file to link to
+
+ my $epmfilename = "epm_" . $onepackagename . ".lst";
+
+ installer::logger::print_message( "... creating epm list file $epmfilename ... \n" );
+
+ my $completeepmfilename = $listfiledir . $installer::globals::separator . $epmfilename;
+
+ my @epmfile = ();
+
+ my $epmheaderref = installer::epmfile::create_epm_header($allvariableshashref, $filesinproductlanguageresolvedarrayref, $languagesarrayref, $onepackage);
+ installer::epmfile::adding_header_to_epm_file(\@epmfile, $epmheaderref);
+
+ # adding directories, files and links into epm file
+
+ installer::epmfile::put_directories_into_epmfile($dirsinpackage, \@epmfile, $allvariableshashref, $packagerootpath);
+ installer::epmfile::put_files_into_epmfile($filesinpackage, \@epmfile );
+ installer::epmfile::put_links_into_epmfile($linksinpackage, \@epmfile );
+ installer::epmfile::put_unixlinks_into_epmfile($unixlinksinpackage, \@epmfile );
+
+ if ((!( $shellscriptsfilename eq "" )) && (!($installer::globals::iswindowsbuild))) { installer::epmfile::adding_shellscripts_to_epm_file(\@epmfile, $shellscriptsfilename, $packagerootpath, $allvariableshashref, $filesinpackage); }
+
+ installer::files::save_file($completeepmfilename ,\@epmfile);
+
+ # ... splitting the rootpath into a relocatable part and a static part, if possible
+
+ my $staticpath = "";
+ my $relocatablepath = "";
+ # relocatable path can be defined in package list
+ if ( $onepackage->{'relocatablepath'} ) { $relocatablepath = $onepackage->{'relocatablepath'}; }
+ # setting fix part and variable part of destination path
+ installer::epmfile::analyze_rootpath($packagerootpath, \$staticpath, \$relocatablepath, $allvariableshashref);
+
+ # ... replacing the variable PRODUCTDIRECTORYNAME in the shellscriptfile by $staticpath
+
+ installer::epmfile::resolve_path_in_epm_list_before_packaging(\@epmfile, $completeepmfilename, "PRODUCTDIRECTORYNAME", $staticpath);
+ installer::epmfile::resolve_path_in_epm_list_before_packaging(\@epmfile, $completeepmfilename, "SOLSUREPACKAGEPREFIX", $allvariableshashref->{'SOLSUREPACKAGEPREFIX'});
+ installer::epmfile::resolve_path_in_epm_list_before_packaging(\@epmfile, $completeepmfilename, "UREPACKAGEPREFIX", $allvariableshashref->{'UREPACKAGEPREFIX'});
+ installer::files::save_file($completeepmfilename ,\@epmfile);
+
+ {
+ # changing into the "install" directory to create installation sets
+
+ $currentdir = cwd(); # $currentdir is global in this file
+
+ chdir($installdir); # changing into install directory ($installdir is global in this file)
+
+ ###########################################
+ # Starting epm
+ ###########################################
+
+ # With a patched epm, it is now possible to set the relocatable directory, change
+ # the directory in which the packages are created, setting "requires" and "provides"
+ # (Linux) or creating the "depend" file (Solaris) and finally to begin
+ # the packaging process with standard tooling and standard parameter
+ # Linux: Adding into the spec file: Prefix: /opt
+ # Solaris: Adding into the pkginfo file: BASEDIR=/opt
+ # Attention: Changing of the path can influence the shell scripts
+
+ if (( $installer::globals::is_special_epm ) && ( ($installer::globals::isrpmbuild) || ($installer::globals::issolarispkgbuild) )) # special handling only for Linux RPMs and Solaris Packages
+ {
+ if ( $installer::globals::call_epm ) # only do something, if epm is really executed
+ {
+ # ... now epm can be started, to create the installation sets
+
+ installer::logger::print_message( "... starting patched epm ... \n" );
+
+ installer::epmfile::call_epm($epmexecutable, $completeepmfilename, $packagename, $includepatharrayref);
+
+ my $newepmdir = installer::epmfile::prepare_packages($loggingdir, $packagename, $staticpath, $relocatablepath, $onepackage, $allvariableshashref, $filesinpackage, $languagestringref); # adding the line for Prefix / Basedir, include rpmdir
+
+ installer::epmfile::create_packages_without_epm($newepmdir, $packagename, $includepatharrayref, $allvariableshashref, $languagestringref); # start to package
+
+ # finally removing all temporary files
+
+ installer::epmfile::remove_temporary_epm_files($newepmdir, $loggingdir, $packagename);
+
+ # Installation:
+ # Install: pkgadd -a myAdminfile -d ./SUNWso8m34.pkg
+ # Install: rpm -i --prefix=/opt/special --nodeps so8m35.rpm
+
+ installer::epmfile::create_new_directory_structure($newepmdir);
+ $installer::globals::postprocess_specialepm = 1;
+
+ # solaris patch not needed anymore
+ }
+ }
+
+ else # this is the standard epm (not relocatable) or ( nonlinux and nonsolaris )
+ {
+ installer::epmfile::resolve_path_in_epm_list_before_packaging(\@epmfile, $completeepmfilename, "\$\$PRODUCTINSTALLLOCATION", $relocatablepath);
+ installer::files::save_file($completeepmfilename ,\@epmfile); # Warning for pool, content of epm file is changed.
+
+ if ( $installer::globals::call_epm )
+ {
+ # ... now epm can be started, to create the installation sets
+
+ if ( $installer::globals::is_special_epm )
+ {
+ installer::logger::print_message( "... starting patched epm ... \n" );
+ }
+ else
+ {
+ installer::logger::print_message( "... starting unpatched epm ... \n" );
+ }
+
+ if ( $installer::globals::call_epm ) { installer::epmfile::call_epm($epmexecutable, $completeepmfilename, $packagename, $includepatharrayref); }
+
+ if (($installer::globals::isrpmbuild) || ($installer::globals::issolarispkgbuild) || ($installer::globals::debian))
+ {
+ $installer::globals::postprocess_standardepm = 1;
+ }
+ }
+ }
+
+ chdir($currentdir); # changing back into start directory
+
+ }
+
+ } # end of "if ( ! $installer::globals::simple )
+
+ } # end of "for ( @{$packages} )"
+
+ ##############################################################
+ # Post epm functionality, after the last package is packed
+ ##############################################################
+
+ if ( $installer::globals::postprocess_specialepm )
+ {
+ installer::logger::include_header_into_logfile("Post EPM processes (Patched EPM):");
+
+ chdir($installdir);
+
+ # Copying the cde, kde and gnome packages into the installation set
+ if ( $installer::globals::addsystemintegration ) { installer::epmfile::put_systemintegration_into_installset($installer::globals::epmoutpath, $includepatharrayref, $allvariableshashref, $modulesinproductarrayref); }
+
+ # Adding license and readme into installation set
+ if ($installer::globals::addlicensefile) { installer::worker::put_scpactions_into_installset("."); }
+
+ # Adding license file into setup
+ if ( $allvariableshashref->{'PUT_LICENSE_INTO_SETUP'} ) { installer::worker::put_license_into_setup(".", $includepatharrayref); }
+
+ # Creating installation set for Unix language packs, that are not part of multi lingual installation sets
+ if ( ( $installer::globals::languagepack ) && ( ! $installer::globals::debian ) && ( ! $installer::globals::makedownload ) ) { installer::languagepack::build_installer_for_languagepack($installer::globals::epmoutpath, $allvariableshashref, $includepatharrayref, $languagesarrayref, $languagestringref); }
+
+ # Creating installation set for Unix help packs, that are not part of multi lingual installation sets
+ if ( ( $installer::globals::helppack ) && ( ! $installer::globals::debian ) && ( ! $installer::globals::makedownload ) ) { installer::helppack::build_installer_for_helppack($installer::globals::epmoutpath, $allvariableshashref, $includepatharrayref, $languagesarrayref, $languagestringref); }
+
+ chdir($currentdir); # changing back into start directory
+ }
+
+ if ( $installer::globals::postprocess_standardepm )
+ {
+ installer::logger::include_header_into_logfile("Post EPM processes (Standard EPM):");
+
+ chdir($installdir);
+
+ # determine the destination directory
+ my $newepmdir = installer::epmfile::determine_installdir_ooo();
+
+ # Copying the cde, kde and gnome packages into the installation set
+ if ( $installer::globals::addsystemintegration ) { installer::epmfile::put_systemintegration_into_installset($newepmdir, $includepatharrayref, $allvariableshashref, $modulesinproductarrayref); }
+
+ # Adding license and readme into installation set
+ if ($installer::globals::addlicensefile) { installer::worker::put_scpactions_into_installset("."); }
+
+ # Adding license file into setup
+ if ( $allvariableshashref->{'PUT_LICENSE_INTO_SETUP'} ) { installer::worker::put_license_into_setup(".", $includepatharrayref); }
+
+ # Creating installation set for Unix language packs, that are not part of multi lingual installation sets
+ if ( ( $installer::globals::languagepack ) && ( ! $installer::globals::debian ) && ( ! $installer::globals::makedownload ) ) { installer::languagepack::build_installer_for_languagepack($newepmdir, $allvariableshashref, $includepatharrayref, $languagesarrayref, $languagestringref); }
+
+ # Creating installation set for Unix help packs, that are not part of multi lingual installation sets
+ if ( ( $installer::globals::helppack ) && ( ! $installer::globals::debian ) && ( ! $installer::globals::makedownload ) ) { installer::helppack::build_installer_for_helpepack($newepmdir, $allvariableshashref, $includepatharrayref, $languagesarrayref, $languagestringref); }
+
+ chdir($currentdir); # changing back into start directory
+ }
+
+ if (( $installer::globals::issolarispkgbuild ) && ( $allvariableshashref->{'COLLECT_PKGMAP'} )) { installer::worker::collectpackagemaps($installdir, $languagestringref, $allvariableshashref); }
+
+ #######################################################
+ # Analyzing the log file
+ #######################################################
+
+ my $is_success = 0;
+ my $finalinstalldir = "";
+
+ installer::worker::clean_output_tree(); # removing directories created in the output tree
+ ($is_success, $finalinstalldir) = installer::worker::analyze_and_save_logfile($loggingdir, $installdir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number);
+ my $downloadname = installer::ziplist::getinfofromziplist($allsettingsarrayref, "downloadname");
+
+ #######################################################
+ # Creating download installation set
+ #######################################################
+
+ if ( $installer::globals::makedownload )
+ {
+ my $create_download = 0;
+ if ( $$downloadname ne "" ) { $create_download = 1; }
+ if ( $installer::globals::iswindowsbuild ) { $create_download = 0; }
+ if (( $is_success ) && ( $create_download ) && ( $ENV{'ENABLE_DOWNLOADSETS'} ))
+ {
+ my $downloaddir = installer::download::create_download_sets($finalinstalldir, $includepatharrayref, $allvariableshashref, $$downloadname, $languagestringref, $languagesarrayref);
+ installer::worker::analyze_and_save_logfile($loggingdir, $downloaddir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number);
+ }
+ }
+ } # end of "if (!( $installer::globals::iswindowsbuild ))"
+
+ #################################################
+ # Part 2b: The Windows platform
+ #################################################
+
+ #####################################################################
+ # ... creating idt files ...
+ # Only for Windows builds
+ #####################################################################
+
+ if ( $installer::globals::iswindowsbuild )
+ {
+ ###########################################
+ # Stripping libraries
+ ###########################################
+
+ # Building for gcc build in cws requires, that all files are stripped before packaging:
+ # 1. copy all files that need to be stripped locally
+ # 2. strip all these files
+
+ if ( $installer::globals::com eq 'GCC')
+ {
+ installer::windows::strip::strip_binaries($filesinproductlanguageresolvedarrayref, $languagestringref);
+ }
+
+ $installdir = installer::worker::create_installation_directory($shipinstalldir, $languagestringref, \$current_install_number);
+
+ my $idtdirbase = installer::systemactions::create_directories("idt_files", $languagestringref);
+ $installer::globals::infodirectory = installer::systemactions::create_directories("info_files", $languagestringref);
+ my $installlogdir = installer::systemactions::create_directory_next_to_directory($installdir, "log");
+
+ #################################################################################
+ # Preparing cabinet files from package definitions
+ #################################################################################
+
+ installer::packagelist::prepare_cabinet_files($packages, $allvariableshashref);
+ # printing packages content:
+ installer::packagelist::log_cabinet_assignments();
+
+ #################################################################################
+ # Begin of functions that are used for the creation of idt files (Windows only)
+ #################################################################################
+
+ installer::logger::print_message( "... creating idt files ...\n" );
+
+ installer::logger::include_header_into_logfile("Creating idt files:");
+
+ my $newidtdir = $idtdirbase . $installer::globals::separator . "00"; # new files into language independent directory "00"
+ installer::systemactions::create_directory($newidtdir);
+
+ my @allfilecomponents = ();
+ my @allregistrycomponents = ();
+
+ # Collecting all files with flag "BINARYTABLE"
+ my $binarytablefiles = installer::worker::collect_all_items_with_special_flag($filesinproductlanguageresolvedarrayref ,"BINARYTABLE");
+
+ # Removing all files with flag "BINARYTABLE_ONLY"
+ @installer::globals::binarytableonlyfiles = ();
+ $filesinproductlanguageresolvedarrayref = installer::worker::remove_all_items_with_special_flag($filesinproductlanguageresolvedarrayref ,"BINARYTABLE_ONLY");
+
+ # Collecting all profileitems with flag "INIFILETABLE" for table "IniFile"
+ my $inifiletableentries = installer::worker::collect_all_items_with_special_flag($profileitemsinproductlanguageresolvedarrayref ,"INIFILETABLE");
+
+ # Creating the important dynamic idt files
+ installer::windows::msiglobal::set_msiproductversion($allvariableshashref);
+ installer::windows::msiglobal::put_msiproductversion_into_bootstrapfile($filesinproductlanguageresolvedarrayref);
+
+ # Add cabinet assignments to files
+ installer::windows::file::assign_cab_to_files($filesinproductlanguageresolvedarrayref);
+ installer::windows::file::assign_sequencenumbers_to_files($filesinproductlanguageresolvedarrayref);
+
+ # Collection all available directory trees
+ installer::windows::directory::collectdirectorytrees($directoriesforepmarrayref);
+
+ # Attention: The table "Director.idt" contains language specific strings -> parameter: \@setuplanguagesarray !
+ installer::windows::directory::create_directory_table($directoriesforepmarrayref, \@setuplanguagesarray, $newidtdir, $allvariableshashref, $shortdirname, $loggingdir);
+
+ $filesinproductlanguageresolvedarrayref = installer::windows::file::create_files_table($filesinproductlanguageresolvedarrayref, $directoriesforepmarrayref, \@allfilecomponents, $newidtdir, $allvariableshashref, $uniquefilename, $allupdatesequences, $allupdatecomponents, $allupdatefileorder);
+ if ( $installer::globals::updatedatabase ) { installer::windows::file::check_file_sequences($allupdatefileorder, $allupdatecomponentorder); }
+
+ # Attention: The table "Registry.idt" contains language specific strings -> parameter: \@setuplanguagesarray !
+ installer::windows::registry::create_registry_table($registryitemsinproductlanguageresolvedarrayref, \@allregistrycomponents, $newidtdir, \@setuplanguagesarray, $allvariableshashref);
+
+ installer::windows::component::create_component_table($filesinproductlanguageresolvedarrayref, $registryitemsinproductlanguageresolvedarrayref, $directoriesforepmarrayref, \@allfilecomponents, \@allregistrycomponents, $newidtdir, $componentid, $componentidkeypath, $allvariableshashref);
+
+ # Attention: The table "Feature.idt" contains language specific strings -> parameter: \@setuplanguagesarray !
+ installer::windows::feature::add_uniquekey($modulesinproductlanguageresolvedarrayref);
+ $modulesinproductlanguageresolvedarrayref = installer::windows::feature::sort_feature($modulesinproductlanguageresolvedarrayref);
+ installer::windows::feature::create_feature_table($modulesinproductlanguageresolvedarrayref, $newidtdir, \@setuplanguagesarray, $allvariableshashref);
+
+ installer::windows::featurecomponent::create_featurecomponent_table($filesinproductlanguageresolvedarrayref, $registryitemsinproductlanguageresolvedarrayref, $newidtdir);
+
+ installer::windows::media::create_media_table($filesinproductlanguageresolvedarrayref, $newidtdir, $allvariableshashref, $allupdatelastsequences, $allupdatediskids);
+
+ installer::windows::font::create_font_table($filesinproductlanguageresolvedarrayref, $newidtdir);
+
+ # Attention: The table "Shortcut.idt" contains language specific strings -> parameter: \@setuplanguagesarray !
+ # Attention: Shortcuts (Folderitems) have icon files, that have to be copied into the Icon directory (last parameter)
+ my @iconfilecollector = ();
+
+ installer::windows::shortcut::create_shortcut_table($filesinproductlanguageresolvedarrayref, $linksinproductlanguageresolvedarrayref, $folderinproductlanguageresolvedarrayref, $folderitemsinproductlanguageresolvedarrayref, $directoriesforepmarrayref, $newidtdir, \@setuplanguagesarray, $includepatharrayref, \@iconfilecollector);
+
+ installer::windows::inifile::create_inifile_table($inifiletableentries, $filesinproductlanguageresolvedarrayref, $newidtdir);
+
+ installer::windows::icon::create_icon_table(\@iconfilecollector, $newidtdir); # creating the icon table with all iconfiles used as shortcuts (FolderItems)
+
+ installer::windows::createfolder::create_createfolder_table($directoriesforepmarrayref, $filesinproductlanguageresolvedarrayref, $newidtdir, $allvariableshashref);
+
+ installer::windows::upgrade::create_upgrade_table($newidtdir, $allvariableshashref);
+
+ installer::windows::msishortcutproperty::create_msishortcutproperty_table($folderitempropertiesinproductarrayref, $folderitemsinproductarrayref, $newidtdir);
+
+ if (( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack )) # the following tables not for language packs or help packs
+ {
+ installer::windows::removefile::create_removefile_table($folderitemsinproductlanguageresolvedarrayref, $newidtdir);
+
+ # Adding Assemblies into the tables MsiAssembly and MsiAssemblyName dynamically
+ installer::windows::assembly::create_msiassembly_table($filesinproductlanguageresolvedarrayref, $newidtdir);
+ installer::windows::assembly::create_msiassemblyname_table($filesinproductlanguageresolvedarrayref, $newidtdir);
+ installer::windows::assembly::add_assembly_condition_into_component_table($filesinproductlanguageresolvedarrayref, $newidtdir);
+ }
+
+ my $infoline = "\n";
+ push(@installer::globals::logfileinfo, $infoline);
+
+ # Localizing the language dependent idt files
+ # For every language there will be a localized msi database
+ # For multilingual installation sets, the differences of this
+ # databases have to be stored in transforms.
+
+ for ( my $m = 0; $m <= $#{\@setuplanguagesarray}; $m++ )
+ {
+ my $onelanguage = ${\@setuplanguagesarray}[$m];
+
+ my $is_rtl = 0;
+ my @rtllanguages = ("ar", "fa", "he", "ug", "ky-CN");
+ if ( grep {$_ eq $onelanguage} @rtllanguages ) { $is_rtl = 1; }
+
+ my $languageidtdir = $idtdirbase . $installer::globals::separator . $onelanguage;
+ if ( -d $languageidtdir ) { installer::systemactions::remove_complete_directory($languageidtdir, 1); }
+ installer::systemactions::create_directory($languageidtdir);
+
+ # Copy the template idt files and the new created idt files into this language directory
+
+ installer::logger::print_message( "... copying idt files ...\n" );
+
+ installer::logger::include_header_into_logfile("Copying idt files to $languageidtdir:");
+
+ installer::windows::idtglobal::prepare_language_idt_directory($languageidtdir, $newidtdir, $onelanguage, $filesinproductlanguageresolvedarrayref, \@iconfilecollector, $binarytablefiles, $allvariableshashref);
+
+ # Now all files are copied into a language specific directory
+ # The template idt files can be translated
+
+ installer::logger::print_message( "... localizing idt files (language: $onelanguage) ...\n" );
+
+ installer::logger::include_header_into_logfile("Localizing idt files (Language: $onelanguage):");
+
+ my @translationfiles = (); # all idt files, that need a translation
+ push(@translationfiles, "ActionTe.idt");
+ push(@translationfiles, "Control.idt");
+ push(@translationfiles, "CustomAc.idt");
+ push(@translationfiles, "Error.idt");
+ push(@translationfiles, "LaunchCo.idt");
+ push(@translationfiles, "RadioBut.idt");
+ push(@translationfiles, "Property.idt");
+ push(@translationfiles, "UIText.idt");
+
+ my $oneidtfilename;
+ my $oneidtfile;
+
+ foreach $oneidtfilename (@translationfiles)
+ {
+ my $languagefilename = installer::windows::idtglobal::get_languagefilename($oneidtfilename, $installer::globals::idtlanguagepath);
+ my $languagefile = installer::files::read_file($languagefilename);
+
+ $oneidtfilename = $languageidtdir . $installer::globals::separator . $oneidtfilename;
+ $oneidtfile = installer::files::read_file($oneidtfilename);
+
+ # Now the substitution can start
+ installer::windows::idtglobal::translate_idtfile($oneidtfile, $languagefile, $onelanguage);
+
+ installer::files::save_file($oneidtfilename, $oneidtfile);
+
+ $infoline = "Translated idt file: $oneidtfilename into language $onelanguage\n";
+ push(@installer::globals::logfileinfo, $infoline);
+ $infoline = "Used languagefile: $languagefilename\n";
+ push(@installer::globals::logfileinfo, $infoline);
+ }
+
+ # setting bidi attributes, if required
+ if ( $is_rtl ) { installer::windows::idtglobal::setbidiattributes($languageidtdir, $onelanguage); }
+
+ # setting the condition, that at least one module is selected
+ installer::windows::idtglobal::set_multilanguageonly_condition($languageidtdir);
+
+ # include the license text into the table Control.idt
+
+ if ( ! $allvariableshashref->{'HIDELICENSEDIALOG'} )
+ {
+ my $licensefilesource = installer::windows::idtglobal::get_rtflicensefilesource($onelanguage, $includepatharrayref_lang);
+ my $licensefile = installer::files::read_file($licensefilesource);
+ installer::scpzipfiles::replace_all_ziplistvariables_in_rtffile($licensefile, $allvariableshashref);
+ my $controltablename = $languageidtdir . $installer::globals::separator . "Control.idt";
+ my $controltable = installer::files::read_file($controltablename);
+ installer::windows::idtglobal::add_licensefile_to_database($licensefile, $controltable);
+ installer::files::save_file($controltablename, $controltable);
+
+ $infoline = "Added licensefile $licensefilesource into database $controltablename\n";
+ push(@installer::globals::logfileinfo, $infoline);
+ }
+
+ # include a component into environment table if required
+
+ installer::windows::component::set_component_in_environment_table($languageidtdir, $filesinproductlanguageresolvedarrayref);
+
+ # include the ProductCode and the UpgradeCode from codes-file into the Property.idt
+
+ installer::windows::property::set_codes_in_property_table($languageidtdir);
+
+ # the language specific properties can now be set in the Property.idt
+
+ installer::windows::property::update_property_table($languageidtdir, $onelanguage, $allvariableshashref, $languagestringref);
+
+ # replacing variables in RegLocat.idt
+
+ installer::windows::msiglobal::update_reglocat_table($languageidtdir, $allvariableshashref);
+
+ # replacing variables in RemoveRe.idt (RemoveRegistry.idt)
+
+ installer::windows::msiglobal::update_removere_table($languageidtdir);
+
+ # adding language specific properties for multilingual installation sets
+
+ installer::windows::property::set_languages_in_property_table($languageidtdir, \@setuplanguagesarray);
+
+ # adding settings into CheckBox.idt
+ installer::windows::property::update_checkbox_table($languageidtdir, $allvariableshashref);
+
+ # adding the files from the binary directory into the binary table
+ installer::windows::binary::update_binary_table($languageidtdir, $filesinproductlanguageresolvedarrayref, $binarytablefiles);
+
+ # Adding Windows Installer CustomActions
+
+ installer::windows::idtglobal::addcustomactions($languageidtdir, $windowscustomactionsarrayref, $filesinproductlanguageresolvedarrayref);
+
+ installer::logger::print_message( "... Analyze if custom action table must be patched with merge module directory names ...\n" );
+
+ my @customactions = ();
+ for my $e (keys %installer::globals::merge_directory_hash) {
+ my $var;
+ installer::logger::print_message( "... analyzing directory from merge module: $e\n");
+ if (installer::windows::directory::has_standard_directory_prefix($e, \$var)) {
+ installer::logger::print_message( "... emitting custom action to set the var $e to directory: $var\n");
+ push(@customactions, installer::windows::idtglobal::emit_custom_action_for_standard_directory($e, $var));
+ }
+ }
+
+ if (@customactions) {
+ installer::logger::print_message( "... Patching custom action table with merge module directory names ...\n" );
+ #print Dumper(@customactions);
+ installer::windows::idtglobal::addcustomactions($languageidtdir, \@customactions, $filesinproductlanguageresolvedarrayref);
+ }
+
+ # Then the language specific msi database can be created
+
+ if ( $installer::globals::iswin || $installer::globals::packageformat eq 'msi' )
+ {
+ my $msidatabasename = installer::windows::msiglobal::get_msidatabasename($allvariableshashref, $onelanguage);
+ my $msifilename = $languageidtdir . $installer::globals::separator . $msidatabasename;
+
+ installer::logger::print_message( "... creating msi database (language $onelanguage) ... \n" );
+
+ installer::windows::msiglobal::set_uuid_into_component_table($languageidtdir, $allvariableshashref); # setting new GUID for the components using the tool uuidgen.exe
+ installer::windows::msiglobal::prepare_64bit_database($languageidtdir, $allvariableshashref); # making last 64 bit changes
+ installer::windows::msiglobal::create_msi_database($languageidtdir ,$msifilename);
+
+ # validating the database # ToDo
+
+ installer::windows::msiglobal::write_summary_into_msi_database($msifilename, $onelanguage, $allvariableshashref);
+
+ # copy msi database into installation directory
+
+ my $msidestfilename = $installdir . $installer::globals::separator . $msidatabasename;
+ installer::systemactions::copy_one_file($msifilename, $msidestfilename);
+ }
+ }
+
+ # Creating transforms, if the installation set has more than one language
+ # renaming the msi database
+
+ my $defaultlanguage = installer::languages::get_default_language(\@setuplanguagesarray);
+
+ if ( $installer::globals::iswin || $installer::globals::packageformat eq 'msi' )
+ {
+ if ( $#{\@setuplanguagesarray} > 0 )
+ {
+ installer::windows::msiglobal::create_transforms(\@setuplanguagesarray, $defaultlanguage, $installdir, $allvariableshashref);
+ }
+ # if there are Merge Modules, they have to be integrated now
+ my $mergedbname = installer::windows::msiglobal::get_msidatabasename($allvariableshashref, $defaultlanguage);
+ my $mergeidtdir = $idtdirbase . $installer::globals::separator . "mergemodules";
+ if ( -d $mergeidtdir ) { installer::systemactions::remove_complete_directory($mergeidtdir, 1); }
+ installer::systemactions::create_directory($mergeidtdir);
+ installer::systemactions::copy_one_file($installdir . $installer::globals::separator . $mergedbname, $mergeidtdir . $installer::globals::separator . $mergedbname);
+ $filesinproductlanguageresolvedarrayref = installer::windows::mergemodule::merge_mergemodules_into_msi_database($mergemodulesarrayref, $filesinproductlanguageresolvedarrayref, $mergeidtdir . $installer::globals::separator . $mergedbname, $languagestringref, $allvariableshashref, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids);
+ installer::systemactions::copy_one_file($mergeidtdir . $installer::globals::separator . $mergedbname, $installdir . $installer::globals::separator . $mergedbname);
+
+ installer::windows::msiglobal::rename_msi_database_in_installset($defaultlanguage, $installdir, $allvariableshashref);
+ }
+
+ # Analyzing the ScpActions and copying the files into the installation set
+
+ installer::logger::print_message( "... copying files into installation set ...\n" );
+
+ installer::worker::put_scpactions_into_installset($installdir);
+
+ # ... copying MergeModules into installation set
+
+ if ( ! $installer::globals::fix_number_of_cab_files ) { installer::windows::msiglobal::copy_merge_modules_into_installset($installdir); }
+
+ installer::logger::print_message( "... creating ddf files ...\n" );
+
+ # Creating all needed ddf files and generating a list
+ # for the package process containing all system calls
+
+ my $ddfdir = installer::systemactions::create_directories("ddf", $languagestringref);
+
+ my $packjobref = installer::windows::msiglobal::generate_cab_file_list($filesinproductlanguageresolvedarrayref, $installdir, $ddfdir, $allvariableshashref);
+
+ # Update and patch reasons the pack order needs to be saved
+ installer::windows::msiglobal::save_packorder();
+
+ $infoline = "\n";
+ push(@installer::globals::logfileinfo, $infoline);
+
+ ####################################
+ # Writing log file
+ # before cab files are packed
+ ####################################
+
+ installer::logger::print_message( "... creating log file $installer::globals::logfilename \n" );
+
+ installer::files::save_file($loggingdir . $installer::globals::logfilename, \@installer::globals::logfileinfo);
+
+ #######################################################
+ # Finally really create the installation packages,
+ # Only for Windows and only on a windows platform.
+ #######################################################
+
+ if ( $installer::globals::iswin || $installer::globals::packageformat eq 'msi' )
+ {
+ installer::logger::print_message( "... packaging installation set ... \n" );
+ installer::windows::msiglobal::execute_packaging($packjobref, $loggingdir, $allvariableshashref);
+ if ( $installer::globals::include_cab_in_msi ) { installer::windows::msiglobal::include_cabs_into_msi($installdir); }
+
+ ####################################
+ # Writing log file
+ # after cab files are packed
+ ####################################
+
+ installer::logger::print_message( "\n... creating log file $installer::globals::logfilename \n" );
+ installer::files::save_file($loggingdir . $installer::globals::logfilename, \@installer::globals::logfileinfo);
+ }
+
+ #######################################################
+ # Analyzing the log file
+ #######################################################
+
+ my $is_success = 0;
+ my $finalinstalldir = "";
+ installer::worker::clean_output_tree(); # removing directories created in the output tree
+ ($is_success, $finalinstalldir) = installer::worker::analyze_and_save_logfile($loggingdir, $installdir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number);
+
+ #######################################################
+ # Creating download installation set
+ #######################################################
+
+ my $create_download = 0;
+ my $downloadname = installer::ziplist::getinfofromziplist($allsettingsarrayref, "downloadname");
+ if ( $installer::globals::languagepack ) { $downloadname = installer::ziplist::getinfofromziplist($allsettingsarrayref, "langpackdownloadname"); }
+ if ( $installer::globals::helppack ) { $downloadname = installer::ziplist::getinfofromziplist($allsettingsarrayref, "helppackdownloadname"); }
+
+ if ( $$downloadname ne "" ) { $create_download = 1; }
+ if ( $installer::globals::iswindowsbuild )
+ {
+ $create_download = 0;
+ $$downloadname = installer::download::set_download_filename($languagestringref, $allvariableshashref);
+ installer::systemactions::rename_one_file( $finalinstalldir . $installer::globals::separator . $installer::globals::shortmsidatabasename, $finalinstalldir . $installer::globals::separator . $$downloadname . ".msi" );
+ }
+ if (( $is_success ) && ( $create_download ) && ( $ENV{'ENABLE_DOWNLOADSETS'} ))
+ {
+ my $downloaddir = installer::download::create_download_sets($finalinstalldir, $includepatharrayref, $allvariableshashref, $$downloadname, $languagestringref, $languagesarrayref);
+ installer::worker::analyze_and_save_logfile($loggingdir, $downloaddir, $installlogdir, $allsettingsarrayref, $languagestringref, $current_install_number);
+ }
+
+ } # end of "if ( $installer::globals::iswindowsbuild )"
+
+ # saving file_info file for later analysis
+ my $speciallogfilename = $loggingdir . "fileinfo_" . $installer::globals::product . "\.log";
+ open my $log_fh, '>', $speciallogfilename
+ or die "Could not open $speciallogfilename for writing: $!";
+ print $log_fh Dumper($filesinproductlanguageresolvedarrayref);
+
+ } # end of iteration for one language group
+}
+
+sub cleanup_on_error {
+ my $message = shift;
+
+ # If an installation set is currently created, the directory name
+ # is saved in $installer::globals::saveinstalldir. If this
+ # directory name contains "_inprogress", it has to be renamed to
+ # "_witherror".
+ if ( $installer::globals::saveinstalldir =~ /_inprogress/ ) {
+ installer::systemactions::rename_string_in_directory($installer::globals::saveinstalldir, "_inprogress", "_witherror");
+ }
+
+ # Removing directories created in the output tree.
+ installer::worker::clean_output_tree();
+
+ $installer::globals::logfilename = $installer::globals::exitlog . $installer::globals::logfilename;
+
+ my @log = (@installer::globals::logfileinfo, @installer::globals::globallogfileinfo);
+
+ push(@log, "\n" . '*' x 65 . "\n");
+ push(@log, $message);
+ push(@log, '*' x 65 . "\n");
+
+ installer::files::save_file($installer::globals::logfilename, \@log);
+
+ print("ERROR, saved logfile $installer::globals::logfilename is:\n");
+ open(my $log, "<", $installer::globals::logfilename);
+ print ": $_" while (<$log>);
+ print "\n";
+ close($log);
+}
+
+1;
diff --git a/solenv/bin/modules/installer/control.pm b/solenv/bin/modules/installer/control.pm
new file mode 100644
index 0000000000..d126e917b5
--- /dev/null
+++ b/solenv/bin/modules/installer/control.pm
@@ -0,0 +1,478 @@
+#
+# 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 strict;
+use warnings;
+
+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 ) = @_;
+ my $error = 0;
+
+ foreach my $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 { my $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 (<CAB>) { push(@makecaboutput, $_); }
+ close (CAB);
+
+ my $returnvalue = $?; # $? contains the return value of the systemcall
+
+ if ($returnvalue)
+ {
+ my $infoline = "ERROR: Could not execute \"$systemcall\"!\n";
+ push( @installer::globals::globallogfileinfo, $infoline);
+ }
+ else
+ {
+ my $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 0000000000..f825e0439b
--- /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 0000000000..309636657a
--- /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 0000000000..ad5f945607
--- /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 = <SUM>;
+ 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 = '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 0000000000..c04971431f
--- /dev/null
+++ b/solenv/bin/modules/installer/environment.pm
@@ -0,0 +1,134 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..0b2caf1fff
--- /dev/null
+++ b/solenv/bin/modules/installer/epmfile.pm
@@ -0,0 +1,2665 @@
+#
+# 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 strict;
+use warnings;
+
+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($variableshashref) }
+
+ # 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 (<EPMPATCH>)
+ {
+ 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 (<EPM>) {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; }
+ my $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 (<FINDREQUIRES>) { $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*$//;
+ my $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 = <LS>;
+ 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 = <RPM>;
+ 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 (<RPM>) {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 (<PKGMK>) {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;
+ my $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
+ {
+ my $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" );
+
+ 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);
+ }
+
+
+ ######################
+ # 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::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\" --define \"_build_id_links none\" $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 (<RPM>) {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 (<RPM>) {push(@helperrpmoutput, $_); }
+ close (RPM);
+
+ my $helperreturnvalue = $?; # $? contains the return value of the systemcall
+
+ my $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
+ my $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
+ my $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);
+
+
+ $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::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 ) && ( ! $allvariables->{'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=<myselectdir>/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 ($variableshashref) = @_;
+
+ 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) = @_;
+
+ my $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 = <SUM>;
+ 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) = @_;
+
+ my $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 = <WC>;
+ 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 0000000000..c6e49f9cee
--- /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 0000000000..b623478a30
--- /dev/null
+++ b/solenv/bin/modules/installer/filelists.pm
@@ -0,0 +1,157 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..1eb41482cf
--- /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 = <IN>" 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 = <IN> ) {
+ 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 0000000000..d210cefa17
--- /dev/null
+++ b/solenv/bin/modules/installer/globals.pm
@@ -0,0 +1,294 @@
+#
+# 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;
+
+use strict;
+use warnings;
+
+############################################
+# Global settings
+############################################
+
+BEGIN
+{
+ $installer::globals::ziplistname = "";
+ $installer::globals::pathfilename = "";
+ $installer::globals::setupscriptname = "";
+ $installer::globals::product = "";
+ $installer::globals::languagelist = "";
+ $installer::globals::added_english = 0;
+ $installer::globals::set_office_start_language = 0;
+
+ $installer::globals::destdir = "";
+ $installer::globals::rootpath = "";
+
+ @installer::globals::languageproducts = ();
+ $installer::globals::build = "";
+ $installer::globals::os = "";
+ $installer::globals::cpuname = "";
+ $installer::globals::com = "";
+ $installer::globals::platformid = "";
+ $installer::globals::pro = 0;
+ $installer::globals::dounzip = 1;
+ $installer::globals::languages_defined_in_productlist = 0;
+ $installer::globals::setupscript_defined_in_productlist = 0;
+ $installer::globals::iswindowsbuild = 0;
+ $installer::globals::islinuxbuild = 0;
+ $installer::globals::isrpmbuild = 0;
+ $installer::globals::isdebbuild = 0;
+ $installer::globals::issolarisbuild = 0;
+ $installer::globals::issolarispkgbuild = 0;
+ $installer::globals::issolarissparcbuild = 0;
+ $installer::globals::issolarisx86build = 0;
+ $installer::globals::isfreebsdbuild = 0;
+ $installer::globals::isfreebsdpkgbuild = 0;
+ $installer::globals::ismacbuild = 0;
+ $installer::globals::ismacdmgbuild = 0;
+ $installer::globals::unpackpath = "";
+ $installer::globals::workpath = ""; # installation working dir; some helper scripts are
+ # placed here by gbuild
+ $installer::globals::idttemplatepath = "";
+ $installer::globals::idtlanguagepath = "";
+ $installer::globals::buildid = "Not set";
+ $installer::globals::fontsfolder = "FontsFolder";
+ $installer::globals::fontsfoldername = "Fonts";
+ $installer::globals::fontsdirparent = "";
+ $installer::globals::fontsdirname = "";
+ $installer::globals::fontsdirhostname = "truetype";
+ $installer::globals::officemenufolder = "OfficeMenuFolder";
+ $installer::globals::startupfolder = "StartupFolder";
+ $installer::globals::startmenufolder = "StartMenuFolder";
+ $installer::globals::desktopfolder = "DesktopFolder";
+ $installer::globals::programfilesfolder = "ProgramFilesFolder";
+ $installer::globals::commonfilesfolder = "CommonFilesFolder";
+ $installer::globals::commonappdatafolder = "CommonAppDataFolder";
+ $installer::globals::localappdatafolder = "LocalAppDataFolder";
+ $installer::globals::templatefolder = "TemplateFolder";
+ $installer::globals::templatefoldername = "Templates";
+ $installer::globals::programmenufolder = "ProgramMenuFolder";
+ $installer::globals::lcidlistname = $ENV{'SRCDIR'} . "/l10ntools/source/ulfconv/msi-encodinglist.txt";
+ $installer::globals::msilanguage = ""; # hash reference for msi languages LCID
+ $installer::globals::sofficeiconadded = 0;
+ $installer::globals::temppath = "";
+ $installer::globals::cyg_temppath = "";
+ $installer::globals::temppathdefined = 0;
+ $installer::globals::packageversion = 1;
+ $installer::globals::packagerevision = 1;
+ $installer::globals::rpm = "";
+ $installer::globals::rpminfologged = 0;
+ $installer::globals::debian = "";
+ $installer::globals::installertypedir = "";
+ $installer::globals::controlledmakecabversion = "5";
+ $installer::globals::max_lang_length = 50;
+ $installer::globals::globalblock = "Globals";
+ $installer::globals::rootmodulegid = "";
+ %installer::globals::alllangmodules = ();
+ $installer::globals::englishlicenseset = 0;
+ $installer::globals::englishlicense = "";
+ $installer::globals::englishsolarislicensename = "LICENSE"; # _en-US";
+ $installer::globals::solarisdontcompress = 0;
+ $installer::globals::patharray = "";
+
+ $installer::globals::is_special_epm = 0;
+ $installer::globals::epm_in_path = 0;
+ $installer::globals::epm_path = "";
+ $installer::globals::epmoutpath = "";
+ $installer::globals::simple = 0;
+ $installer::globals::simpledefaultuserdir = "\$ORIGIN/..";
+ $installer::globals::call_epm = 1;
+ $installer::globals::packageformat = "";
+ $installer::globals::packagename = "";
+ $installer::globals::packagelist = "";
+ $installer::globals::shiptestdirectory = "";
+ $installer::globals::archiveformat = "";
+ $installer::globals::updatelastsequence = 0;
+ $installer::globals::updatesequencecounter = 0;
+ $installer::globals::updatedatabase = 0;
+ $installer::globals::updatedatabasepath = "";
+ $installer::globals::pffcabfilename = "ooobasis3.0_pff.cab";
+ %installer::globals::allmergemodulefilesequences = ();
+ %installer::globals::newupdatefiles = ();
+ %installer::globals::allusedupdatesequences = ();
+ %installer::globals::mergemodulefiles = ();
+ $installer::globals::mergefiles_added_into_collector = 0;
+ $installer::globals::creating_windows_installer_patch = 0;
+
+ $installer::globals::strip = 0;
+
+ $installer::globals::packertool = "gzip"; # the default package compression tool for *NIX
+
+ $installer::globals::logfilename = "logfile.log"; # the default logfile name for global errors
+ @installer::globals::logfileinfo = ();
+ @installer::globals::errorlogfileinfo = ();
+ @installer::globals::globallogfileinfo = ();
+ $installer::globals::ignore_error_in_logfile = 0;
+ $installer::globals::exitlog = "";
+ $installer::globals::quiet = 0;
+
+ $installer::globals::ismultilingual = 0;
+ %installer::globals::alluniquefilenames = ();
+ %installer::globals::alllcuniquefilenames = ();
+ %installer::globals::uniquefilenamesequence = ();
+ %installer::globals::dependfilenames = ();
+ $installer::globals::manufacturer = "";
+ $installer::globals::longmanufacturer = "";
+ $installer::globals::codefilename = "codes.txt";
+ $installer::globals::componentfilename = "components.txt";
+ $installer::globals::productcode = "";
+ $installer::globals::upgradecode = "";
+ $installer::globals::msiproductversion = "";
+ $installer::globals::msimajorproductversion = "";
+ @installer::globals::allddffiles = ();
+ $installer::globals::infodirectory = "";
+
+ %installer::globals::mergemodules = ();
+ %installer::globals::merge_media_line = ();
+ %installer::globals::merge_allfeature_hash = ();
+ %installer::globals::merge_alldirectory_hash = ();
+ %installer::globals::merge_directory_hash = ();
+ %installer::globals::copy_msm_files = ();
+ $installer::globals::mergefeaturecollected = 0;
+ $installer::globals::mergedirectoriescollected = 0;
+ $installer::globals::lastsequence_before_merge = 0;
+ $installer::globals::lastcabfilename = "";
+
+ $installer::globals::defaultlanguage = "";
+ $installer::globals::addlicensefile = 1;
+ $installer::globals::addsystemintegration = 0;
+ $installer::globals::makedownload = 1;
+ @installer::globals::binarytableonlyfiles = ();
+ @installer::globals::allscpactions = ();
+ $installer::globals::languagepackaddon = "LanguagePack";
+ $installer::globals::helppackaddon = "HelpPack";
+ $installer::globals::ooodownloadfilename = "";
+ $installer::globals::downloadfilename = "";
+ $installer::globals::downloadfileextension = "";
+ %installer::globals::multilingual_only_modules = ();
+ %installer::globals::application_modules = ();
+
+ $installer::globals::is_copy_only_project = 0;
+ $installer::globals::is_simple_packager_project = 0;
+ $installer::globals::patch_user_dir = 0;
+ $installer::globals::record_archive_metadata = 0;
+ $installer::globals::languagepack = 0;
+ $installer::globals::helppack = 0;
+ $installer::globals::refresh_includepaths = 0;
+ $installer::globals::include_paths_read = 0;
+ @installer::globals::patchfilecollector = ();
+ @installer::globals::userregistrycollector = ();
+ $installer::globals::addeduserregitrykeys = 0;
+ $installer::globals::desktoplinkexists = 0;
+ $installer::globals::analyze_spellcheckerlanguage = 0;
+ %installer::globals::spellcheckerlanguagehash = ();
+ %installer::globals::spellcheckerfilehash = ();
+ $installer::globals::registryrootcomponent = "";
+ %installer::globals::allcomponents = ();
+ %installer::globals::allcomponents_in_this_database = ();
+ %installer::globals::allshortcomponents = ();
+ %installer::globals::allregistrycomponents_ = ();
+ %installer::globals::allregistrycomponents_in_this_database_ = ();
+ %installer::globals::allshortregistrycomponents = ();
+
+ $installer::globals::installlocationdirectory = "";
+ $installer::globals::installlocationdirectoryset = 0;
+ $installer::globals::vendordirectory = "";
+ $installer::globals::officeinstalldirectory = "";
+ $installer::globals::rootbrandpackage = "";
+ $installer::globals::rootbrandpackageset = 0;
+ $installer::globals::officedirhostname = "";
+ $installer::globals::officedirgid = "";
+
+ %installer::globals::treestyles = ();
+ %installer::globals::treelayername = ();
+ %installer::globals::hostnametreestyles = ();
+ %installer::globals::treeconditions = ();
+ %installer::globals::usedtreeconditions = ();
+ %installer::globals::moduledestination = ();
+
+ $installer::globals::fix_number_of_cab_files = 1;
+ $installer::globals::cabfilecompressionlevel = 21; # Using LZX compression, possible values are: 15 | 16 | ... | 21 (best compression)
+ $installer::globals::number_of_cabfiles = 1; # only for $fix_number_of_cab_files = 1
+ $installer::globals::include_cab_in_msi = 1;
+ $installer::globals::msidatabasename = "";
+ $installer::globals::prepare_winpatch = 0;
+ $installer::globals::previous_idt_dir = "";
+ $installer::globals::msitranpath = "";
+ $installer::globals::insert_file_at_end = 0;
+ $installer::globals::newfilesexist = 0;
+ $installer::globals::usesharepointpath = 0;
+ %installer::globals::newfilescollector = ();
+
+ $installer::globals::saveinstalldir = "";
+ $installer::globals::csp_installdir = ""; # global installdir of createsimplepackage() in simplepackage.pm
+ $installer::globals::csp_installlogdir = ""; # global installlogdir of createsimplepackage() in simplepackage.pm
+ $installer::globals::csp_languagestring = ""; # global languagestring of createsimplepackage() in simplepackage.pm
+ $installer::globals::localunpackdir = "";
+ $installer::globals::localinstalldirset = 0;
+ $installer::globals::localinstalldir = "";
+
+ $installer::globals::postprocess_specialepm = 0;
+ $installer::globals::postprocess_standardepm = 0;
+ $installer::globals::mergemodules_analyzed = 0;
+
+ @installer::globals::packagelistitems = ("module", "solarispackagename", "packagename", "copyright", "vendor", "description" );
+ @installer::globals::featurecollector =();
+ $installer::globals::msiassemblyfiles = "";
+ $installer::globals::macinstallfilename = $ENV{'WORKDIR'} . "/CustomTarget/setup_native/mac/macinstall.ulf";
+ $installer::globals::extensioninstalldir = "gid_Dir_Share_Extension_Install";
+ @installer::globals::languagenames = ();
+ %installer::globals::componentcondition = ();
+ %installer::globals::componentid = ();
+ %installer::globals::allcabinets = ();
+ %installer::globals::allcabinetassigns = ();
+ %installer::globals::cabfilecounter = ();
+ %installer::globals::lastsequence = ();
+ %installer::globals::allcalculated_guids = ();
+ %installer::globals::calculated_component_guids = ();
+ %installer::globals::all_english_languagestrings = ();
+ %installer::globals::all_required_english_languagestrings = ();
+
+ @installer::globals::removedirs = ();
+ @installer::globals::removefiletable = ();
+
+ if ( $^O =~ /cygwin/i )
+ {
+ $installer::globals::zippath = "zip"; # Has to be in the path: /usr/bin/zip
+ $installer::globals::separator = "/";
+ $installer::globals::pathseparator = "\:";
+ $installer::globals::isunix = 0;
+ $installer::globals::iswin = 1;
+ $installer::globals::archiveformat = ".zip";
+ %installer::globals::savedmapping = ();
+ %installer::globals::savedrevmapping = ();
+ %installer::globals::savedrev83mapping = ();
+ %installer::globals::saved83dirmapping = ();
+ }
+ else
+ {
+ $installer::globals::zippath = "zip"; # Has to be in the path: /usr/bin/zip
+ $installer::globals::separator = "/";
+ $installer::globals::pathseparator = "\:";
+ $installer::globals::archiveformat = ".tar.gz";
+ $installer::globals::isunix = 1;
+ $installer::globals::iswin = 0;
+ }
+
+}
+
+1;
diff --git a/solenv/bin/modules/installer/helppack.pm b/solenv/bin/modules/installer/helppack.pm
new file mode 100644
index 0000000000..456e91ecfd
--- /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 0000000000..134e0e6c2b
--- /dev/null
+++ b/solenv/bin/modules/installer/languagepack.pm
@@ -0,0 +1,512 @@
+#
+# 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 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;
+
+####################################################
+# 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";
+ 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 $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 0000000000..a03aafb00c
--- /dev/null
+++ b/solenv/bin/modules/installer/languages.pm
@@ -0,0 +1,145 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..24fe5fc7be
--- /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 0000000000..aef4f94015
--- /dev/null
+++ b/solenv/bin/modules/installer/packagelist.pm
@@ -0,0 +1,843 @@
+#
+# 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 strict;
+use warnings;
+
+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) = @_;
+
+ my @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 )
+ {
+ my $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 0000000000..b183057eac
--- /dev/null
+++ b/solenv/bin/modules/installer/parameter.pm
@@ -0,0 +1,545 @@
+#
+# 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 strict;
+use warnings;
+
+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 <<End;
+--------------------------------------------------------------------------------
+The following parameter are needed:
+-f: Path to the product list (required)
+-s: Path to the setup script (optional, if defined in product list)
+-i: Install path of the product (/opt/openofficeorg20) (optional)
+-p: Product from product list to be created (required)
+-l: Language of the product (comma and hash) (optional, defined in productlist)
+-simple: Path to do a simple install to
+-u: Path, in which zipfiles are unpacked (optional)
+-msitemplate: Source of the msi file templates (Windows compiler only)
+-msilanguage: Source of the msi file templates (Windows compiler only)
+-buildid: Current BuildID (optional)
+-pro: Product version
+-format: Package format
+-debian: Create Debian packages for Linux
+-dontunzip: do not unzip all files with flag ARCHIVE
+-dontcallepm : do not call epm to create install sets (opt., non-Windows only)
+-ispatchedepm : Usage of a patched (non-standard) epm (opt., non-Windows only)
+-copyproject : is set for projects that are only used for copying (optional)
+-languagepack : do create a languagepack, no product pack (optional)
+-helppack : do create a helppack, no product pack (optional)
+-strip: Stripping files (Unix only)
+-packer: Path and parameters for tarball packaging tool (default: gzip (Unix only))
+-log : Logging all available information (optional)
+
+Examples for Windows:
+
+perl make_epmlist.pl -f zip.lst -p OfficeFAT -l en-US
+ -u /export/unpack -buildid 8712
+ -msitemplate /export/msi_files
+ -msilanguage /export/msi_languages
+
+Examples for Non-Windows:
+
+perl make_epmlist.pl -f zip.lst -p OfficeFAT -l en-US -format rpm
+ -u /export/unpack -buildid 8712 -ispatchedepm
+--------------------------------------------------------------------------------
+End
+ exit(-1);
+}
+
+#########################################
+# Writing all parameter into logfile
+#########################################
+
+sub saveparameter
+{
+ my $include = "";
+
+ installer::logger::globallog("Command line arguments:");
+
+ for ( my $i = 0; $i <= $#ARGV; $i++ )
+ {
+ $include = $ARGV[$i] . "\n";
+ push(@installer::globals::globallogfileinfo, $include);
+ }
+
+ # also saving global settings:
+
+ $include = "Separator: $installer::globals::separator\n";
+ push(@installer::globals::globallogfileinfo, $include);
+
+}
+
+#####################################
+# Reading parameter
+#####################################
+
+sub getparameter
+{
+ while ( $#ARGV >= 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 '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 0000000000..de2ac21262
--- /dev/null
+++ b/solenv/bin/modules/installer/pathanalyzer.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::pathanalyzer;
+
+use strict;
+use warnings;
+
+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 0000000000..5e46b5c85b
--- /dev/null
+++ b/solenv/bin/modules/installer/profiles.pm
@@ -0,0 +1,226 @@
+#
+# 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 strict;
+use warnings;
+
+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
+ my $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 0000000000..c9a069ab84
--- /dev/null
+++ b/solenv/bin/modules/installer/remover.pm
@@ -0,0 +1,53 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..08cc8aeecb
--- /dev/null
+++ b/solenv/bin/modules/installer/scpzipfiles.pm
@@ -0,0 +1,146 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..18ccd81d2c
--- /dev/null
+++ b/solenv/bin/modules/installer/scriptitems.pm
@@ -0,0 +1,2373 @@
+#
+# 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 strict;
+no strict 'refs';
+use warnings;
+
+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
+# <productkey>, <buildid>, <sequence_languages>, <productcode>, <upgradecode>, <productupdate>
+################################################################################
+
+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 <buildid>
+
+ 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 = 'LOOnlineUpdater';
+
+ for ( my $i = 0; $i <= $#{$itemsarrayref}; $i++ )
+ {
+ my $oneitem = ${$itemsarrayref}[$i];
+ my $value = $oneitem->{'Value'};
+
+ $value =~ s/\<buildid\>/$buildidstring/;
+ $value =~ s/\<sequence_languages\>/$languagesstring/;
+ $value =~ s/\<productkey\>/$productkey/;
+ $value =~ s/\<productcode\>/$installer::globals::productcode/;
+ $value =~ s/\<upgradecode\>/$installer::globals::upgradecode/;
+ $value =~ s/\<alllanguages\>/$languagesstring/;
+ $value =~ s/\<sourceid\>/$installer::globals::build/;
+ $value =~ s/\<updateid\>/$updateid/;
+ $value =~ s/\<updatechannel\>/$updatechannel/;
+ $value =~ s/\<pkgformat\>/$installer::globals::packageformat/;
+ $ENV{'OOO_VENDOR'} = "" if !defined $ENV{'OOO_VENDOR'};
+ $value =~ s/\<vendor\>/$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, $allvariableshashref) = @_;
+
+ 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 = "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);
+
+ $missingfiles = "$missingfiles $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);
+
+ $missingfiles = "$missingfiles $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);
+
+ $missingfiles = "$missingfiles $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 )
+ {
+ installer::exiter::exit_program($missingfiles, "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);
+}
+
+###############################################################################
+# Add a directory with CREATE flag; save styles for already added directories
+###############################################################################
+
+sub add_directory_with_create_flag_hash
+{
+ my ($alldirectoryhash, $directoryname, $specificlanguage, $gid, $styles, $modules) = @_;
+ if ( ! $directoryname ) { installer::exiter::exit_program("No directory name (HostName) set for specified language in gid $gid", "add_directory_with_create_flag_hash"); }
+ my $origdirectoryname = $directoryname;
+ my $newdirincluded = 0;
+ if ( $styles =~ /\bCREATE\b/ )
+ {
+ if ( ! $modules ) { installer::exiter::exit_program("No assigned modules found for directory $directoryname", "add_directory_with_create_flag_hash"); }
+ if (!(exists($alldirectoryhash->{$directoryname})))
+ {
+ my %directoryhash = ();
+ $directoryhash{'HostName'} = $directoryname;
+ $directoryhash{'specificlanguage'} = $specificlanguage;
+ $directoryhash{'Dir'} = $gid;
+ $directoryhash{'Styles'} = $styles;
+
+ # saving also the modules
+ $directoryhash{'modules'} = $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;
+
+ if (!(exists($alldirectoryhash->{$directoryname})))
+ {
+ my %directoryhash = ();
+
+ $directoryhash{'HostName'} = $directoryname;
+ $directoryhash{'specificlanguage'} = $specificlanguage;
+ $directoryhash{'Dir'} = $gid;
+ if ( ! $installer::globals::iswindowsbuild ) { $directoryhash{'Styles'} = "(CREATE)"; } # Exception for Windows?
+
+ # saving also the modules
+ $directoryhash{'modules'} = $modules;
+
+ $alldirectoryhash->{$directoryname} = \%directoryhash;
+ }
+ else
+ {
+ # Adding the modules to the module list!
+ $alldirectoryhash->{$directoryname}->{'modules'} = $alldirectoryhash->{$directoryname}->{'modules'} . "," . $modules;
+ }
+ }
+ }
+ else
+ {
+ # Adding the modules to the module list!
+ $alldirectoryhash->{$directoryname}->{'modules'} = $alldirectoryhash->{$directoryname}->{'modules'} . "," . $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'} . "," . $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
+ {
+ $directoryname = $origdirectoryname;
+
+ if ( exists($alldirectoryhash->{$directoryname}) )
+ {
+ $alldirectoryhash->{$directoryname}->{'Styles'} = $styles;
+ }
+ }
+ }
+
+ return $alldirectoryhash;
+}
+
+#######################################################################
+# 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 %alldirectoryhash = ();
+ my @filteredfilesarray = (); # without empty directories
+ foreach my $onefile (@{$filesarrayref})
+ {
+ # The "file" can actually be an empty directory added e.g. by gb_Package_add_empty_directory.
+ # TODO/LATER: it would be better if gb_Package_add_empty_directory added empty directories to
+ # "directories with CREATE flag" instead of a filelist.
+ if (-d $onefile->{'sourcepath'})
+ {
+ # Do the same as collect_directories_with_create_flag_from_directoryarray does
+ %alldirectoryhash = %{add_directory_with_create_flag_hash(\%alldirectoryhash, $onefile->{'destination'}, $onefile->{'specificlanguage'}, $onefile->{'gid'}, "(CREATE)", $onefile->{'modules'})};
+ }
+ else { push(@filteredfilesarray, $onefile); }
+ }
+
+ # 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)
+
+ foreach my $onefile (@filteredfilesarray, @{$unixlinksarrayref})
+ {
+ 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'};
+
+ $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
+ }
+
+ # Creating directory array
+ foreach my $destdir ( keys %alldirectoryhash )
+ {
+ $alldirectoryhash{$destdir}->{'modules'} = optimize_list($alldirectoryhash{$destdir}->{'modules'});
+ }
+
+ @_[0] = \@filteredfilesarray; # out argument
+ return \%alldirectoryhash;
+}
+
+##################################
+# Collecting directories: Part 2
+##################################
+
+sub collect_directories_with_create_flag_from_directoryarray
+{
+ my ($directoryarrayref, $alldirectoryhash) = @_;
+
+ my @alldirectories = ();
+
+ foreach my $onedir (@{$directoryarrayref})
+ {
+ $alldirectoryhash = add_directory_with_create_flag_hash($alldirectoryhash, $onedir->{'HostName'}, $onedir->{'specificlanguage'}, $onedir->{'gid'}, $onedir->{'Styles'}, $onedir->{'modules'});
+ }
+
+ # Creating directory array
+ foreach my $destdir ( sort keys %{$alldirectoryhash} )
+ {
+ $alldirectoryhash->{$destdir}->{'modules'} = optimize_list($alldirectoryhash->{$destdir}->{'modules'});
+ push(@alldirectories, $alldirectoryhash->{$destdir});
+ }
+
+ return \@alldirectories;
+}
+
+#################################################
+# 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];
+ my $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;
+ my $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 my $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 0000000000..042e854804
--- /dev/null
+++ b/solenv/bin/modules/installer/setupscript.pm
@@ -0,0 +1,489 @@
+#
+# 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 strict;
+use warnings;
+
+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 my $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 0000000000..3a876c4a36
--- /dev/null
+++ b/solenv/bin/modules/installer/simplepackage.pm
@@ -0,0 +1,764 @@
+#
+# 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 strict;
+use warnings;
+
+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::is_simple_packager_project = 1;
+ $installer::globals::patch_user_dir = 1;
+ }
+ elsif( $installer::globals::packageformat eq "archive" )
+ {
+ $installer::globals::is_simple_packager_project = 1;
+ $installer::globals::record_archive_metadata = 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);
+ my $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); }
+ # For non-release builds where no identity is, set entitlements
+ # to allow Xcode's Instruments application to connect to the
+ # application
+ if ( $ENV{'MACOSX_CODESIGNING_IDENTITY'} || !$ENV{'ENABLE_RELEASE_BUILD'} )
+ {
+ $systemcall = "$ENV{'SRCDIR'}/solenv/bin/macosx-codesign-app-bundle $localtempdir/$folder/$volume_name_classic_app.app";
+ print "... $systemcall ...\n";
+ my $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);
+ 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);
+ 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 $systemcall_output = `$systemcall`;
+ my $returnvalue = $? >> 8;
+ my $infoline = "Systemcall: $systemcall\n";
+ push( @installer::globals::logfileinfo, $infoline);
+
+ if ($returnvalue)
+ {
+ $infoline = "ERROR: Could not execute \"$systemcall\" - exitcode: $returnvalue - output:\n$systemcall_output\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); }
+
+ my @archive_metadata_skip;
+ my @archive_metadata_cond;
+
+ # 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";
+ }
+ else
+ {
+ $infoline = "ERROR: Could not copy $source to $destination $!\n";
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+
+ if ($installer::globals::record_archive_metadata)
+ {
+ if ($onefile->{'Styles'} =~ /\b(ASSEMBLY|ASSIGNCOMPONENT|FONT)\b/)
+ {
+ push(@archive_metadata_skip, $onefile->{'destination'});
+ }
+ elsif ($onefile->{'modules'} =~ /^gid_Module_(Langpack|Optional)_/)
+ {
+ push(@archive_metadata_cond, $onefile->{'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");
+ }
+
+ if ($installer::globals::record_archive_metadata)
+ {
+ open(HANDLE, '>', "$installer::globals::csp_installdir/../metadata") or die $!;
+ print HANDLE "skip $_\n" foreach (sort(@archive_metadata_skip));
+ print HANDLE "cond $_\n" foreach (sort(@archive_metadata_cond));
+ close HANDLE;
+ }
+
+ # 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 0000000000..2074973828
--- /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 = <FILE>;
+ 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 0000000000..c036c117ca
--- /dev/null
+++ b/solenv/bin/modules/installer/systemactions.pm
@@ -0,0 +1,1332 @@
+#
+# 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 strict;
+use warnings;
+
+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;
+ }
+ }
+
+ my $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*$//;
+
+ my $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);
+ my @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) )
+ {
+ my $infoline = "\nMoved directory from $olddir to $newdir\n";
+ push(@installer::globals::logfileinfo, $infoline);
+ $returndir = $newdir;
+ }
+ else
+ {
+ my $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;
+ my $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 0000000000..3bc2330d43
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/admin.pm
@@ -0,0 +1,518 @@
+#
+# 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 strict;
+use warnings;
+
+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: <yyyy/mm/dd hh:mm:ss>
+ 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 <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
+ # -l <person_making_admin_installation>, 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 0000000000..d7014144ce
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/assembly.pm
@@ -0,0 +1,282 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..2c63b7bdf7
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/binary.pm
@@ -0,0 +1,70 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..c4dde4ec33
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/component.pm
@@ -0,0 +1,508 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..4669ddd4ac
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/createfolder.pm
@@ -0,0 +1,157 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..6590376520
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/directory.pm
@@ -0,0 +1,637 @@
+#
+# 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 strict;
+use warnings;
+
+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 = $productname;
+ }
+ 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 0000000000..6dff3dead1
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/feature.pm
@@ -0,0 +1,406 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..65ddfff22c
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/featurecomponent.pm
@@ -0,0 +1,168 @@
+#
+# 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 strict;
+use warnings;
+
+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'});
+
+ my $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'});
+
+ my $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 0000000000..9c5e76c526
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/file.pm
@@ -0,0 +1,1023 @@
+#
+# 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 strict;
+use warnings;
+
+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; }
+ my $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;
+ my $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;
+ my $exedata;
+ {local $/ = undef; $exedata = <EXE>;}
+ 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 my $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 0000000000..929d188e79
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/font.pm
@@ -0,0 +1,72 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..41faae012e
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/icon.pm
@@ -0,0 +1,71 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..f0e59c29ef
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/idtglobal.pm
@@ -0,0 +1,1865 @@
+#
+# 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 strict;
+use warnings;
+
+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;
+
+ my $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;
+
+ my $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 =
+ my $line = "$newuniquename\t$newparent\t$newname\n";
+ push(@{$directorytable}, $line);
+ installer::remover::remove_leading_and_ending_whitespaces(\$line);
+ my $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);
+ my $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;
+ $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: $assignment->{'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);
+ my $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 0000000000..a00e4758ef
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/inifile.pm
@@ -0,0 +1,124 @@
+#
+# 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 strict;
+use warnings;
+
+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++ )
+ {
+ my $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 0000000000..4a6f78721a
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/language.pm
@@ -0,0 +1,44 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..5b5147b46e
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/media.pm
@@ -0,0 +1,205 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..f737c2b905
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/mergemodule.pm
@@ -0,0 +1,1707 @@
+#
+# 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 strict;
+use warnings;
+
+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 <msmfile> -x MergeModule.CABinet
+ # b. -> Number of files in cabinet file: msidb.exe -d <msmfile> -f <directory> -e File
+ # c. -> List of components: msidb.exe -d <msmfile> -f <directory> -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 <msifile> -m <mergefile>
+ # b. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
+ # c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
+ # d. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -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 my $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'}) )
+ {
+ $source = $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
+ my $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 ($workdir) = @_;
+ 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($workdir); # 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($workdir); # 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 0000000000..6d4282709a
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/msiglobal.pm
@@ -0,0 +1,1687 @@
+#
+# 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 strict;
+use warnings;
+
+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, $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 <baseDB> <referenceDB> <transformfile> [<errorhandling>}
+
+ 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;
+ }
+ }
+
+ my $systemcall = "TEMP=$ENV{'TMPDIR'} $cscript \"$wilangid\" $basedbname Package $templatevalue";
+
+ my $returnvalue = system($systemcall);
+
+ my $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 = <UUIDGEN>;
+ 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;
+ my $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 (<DDF>) {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
+
+ my $searchstring = "UPGRADECODE"; # searching in the codes.txt file
+ my $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/\<msiproductversion\>/$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");
+ <F>; <F>; <F>;
+ while (<F>)
+ {
+ 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");
+ <F>; <F>; <F>;
+ while (<F>)
+ {
+ 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 0000000000..91729f8d3f
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/msishortcutproperty.pm
@@ -0,0 +1,146 @@
+#
+# 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 strict;
+use warnings;
+
+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/property.pm b/solenv/bin/modules/installer/windows/property.pm
new file mode 100644
index 0000000000..701086af3a
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/property.pm
@@ -0,0 +1,570 @@
+#
+# 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 strict;
+use warnings;
+
+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_XLR" . "\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 0000000000..609116afc7
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/registry.pm
@@ -0,0 +1,410 @@
+#
+# 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 strict;
+use warnings;
+
+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/\<progpath\>/\[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 0000000000..a4d953e64c
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/removefile.pm
@@ -0,0 +1,144 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..0ba801b51f
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/shortcut.pm
@@ -0,0 +1,665 @@
+#
+# 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 strict;
+use warnings;
+
+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'} )
+ {
+ my $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");
+ }
+
+ my $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 0000000000..0a59be7891
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/strip.pm
@@ -0,0 +1,152 @@
+#
+# 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 strict;
+use warnings;
+
+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 = <FILE>;
+ 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);
+
+ 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/windows/update.pm b/solenv/bin/modules/installer/windows/update.pm
new file mode 100644
index 0000000000..ad89d4065b
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/update.pm
@@ -0,0 +1,623 @@
+#
+# 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 strict;
+use warnings;
+
+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, $longonefilename);
+ }
+ 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, $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_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 0000000000..6f198316d8
--- /dev/null
+++ b/solenv/bin/modules/installer/windows/upgrade.pm
@@ -0,0 +1,83 @@
+#
+# 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 strict;
+use warnings;
+
+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 0000000000..191ce01eeb
--- /dev/null
+++ b/solenv/bin/modules/installer/worker.pm
@@ -0,0 +1,924 @@
+#
+# 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 strict;
+use warnings;
+
+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*$//;
+ my $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++ )
+ {
+ my $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";
+ my $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 0000000000..ca94f764f9
--- /dev/null
+++ b/solenv/bin/modules/installer/ziplist.pm
@@ -0,0 +1,829 @@
+#
+# 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 strict;
+use warnings;
+
+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);
+ my $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");
+}
+
+#####################################################
+# 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;
diff --git a/solenv/bin/modules/par2script/check.pm b/solenv/bin/modules/par2script/check.pm
new file mode 100644
index 0000000000..727cd7c832
--- /dev/null
+++ b/solenv/bin/modules/par2script/check.pm
@@ -0,0 +1,337 @@
+#
+# 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 par2script::check;
+
+use par2script::globals;
+
+################################
+# Checks of the setup script
+################################
+
+########################################################
+# Checking if all defined directories are needed
+########################################################
+
+sub check_needed_directories
+{
+ my $allfiles = $par2script::globals::definitions{'File'};
+ my $alldirs = $par2script::globals::definitions{'Directory'};
+
+ # checking if all defined directories are needed
+
+ my $dir;
+ foreach $dir ( keys %{$alldirs} )
+ {
+ # I. directory has create flag
+ if (( exists($alldirs->{$dir}->{'Styles'}) ) && ( $alldirs->{$dir}->{'Styles'} =~ /\bCREATE\b/ )) { next; }
+
+ # II. there is at least one file in the directory
+ my $fileinside = 0;
+ my $file;
+ foreach $file ( keys %{$allfiles} )
+ {
+ if (( $allfiles->{$file}->{'Dir'} eq $dir ) || ( $allfiles->{$file}->{'NetDir'} eq $dir ))
+ {
+ $fileinside = 1;
+ last;
+ }
+ }
+ if ( $fileinside ) { next; }
+
+ # III. the directory is parent for another directory
+ my $isparent = 0;
+ my $onedir;
+ foreach $onedir ( keys %{$alldirs} )
+ {
+ if ( $alldirs->{$onedir}->{'ParentID'} eq $dir )
+ {
+ $isparent = 1;
+ last;
+ }
+ }
+ if ( $isparent ) { next; }
+
+ # no condition is true -> directory definition is superfluous
+ my $infoline = "\tINFO: Directory definition $dir is superfluous\n";
+ # print $infoline;
+ push(@par2script::globals::logfileinfo, $infoline);
+ }
+}
+
+##################################################
+# Checking if the directories in the item
+# definitions are defined.
+##################################################
+
+sub check_directories_in_item_definitions
+{
+ my $item;
+ foreach $item ( @par2script::globals::items_with_directories )
+ {
+ my $allitems = $par2script::globals::definitions{$item};
+
+ my $onegid;
+ foreach $onegid ( keys %{$allitems} )
+ {
+ if ( ! exists($allitems->{$onegid}->{'Dir'}) ) { die "\nERROR: No directory defined for item: $onegid!\n\n"; }
+ my $dir = $allitems->{$onegid}->{'Dir'};
+ if (( $dir eq "PD_PROGDIR" ) || ( $dir =~ /PREDEFINED_/ )) { next; }
+
+ # checking if this directoryid is defined
+ if ( ! exists($par2script::globals::definitions{'Directory'}->{$dir}) )
+ {
+ die "\nERROR: Directory $dir in item $onegid not defined!\n\n";
+ }
+ }
+ }
+}
+
+########################################################
+# Checking for all Items, that know their modules,
+# whether these modules exist.
+########################################################
+
+sub check_module_existence
+{
+ my $item;
+ foreach $item ( @par2script::globals::items_with_moduleid )
+ {
+ my $allitems = $par2script::globals::definitions{$item};
+
+ my $onegid;
+ foreach $onegid ( keys %{$allitems} )
+ {
+ if ( ! exists($allitems->{$onegid}->{'ModuleID'}) ) { die "\nERROR: No ModuleID defined for item: $onegid!\n\n"; }
+ my $moduleid = $allitems->{$onegid}->{'ModuleID'};
+
+ # checking if this directoryid is defined
+ if ( ! exists($par2script::globals::definitions{'Module'}->{$moduleid}) )
+ {
+ die "\nERROR: ModuleID $moduleid in item $onegid not defined!\n\n";
+ }
+ }
+ }
+}
+
+########################################################
+# Every script has to contain exactly one root module.
+# This module has no ParentID or an empty ParentID.
+########################################################
+
+sub check_rootmodule
+{
+ my $rootgid = "";
+ my $foundroot = 0;
+
+ my $allmodules = $par2script::globals::definitions{'Module'};
+
+ my $modulegid = "";
+ foreach $modulegid (keys %{$allmodules} )
+ {
+ if (( ! exists($allmodules->{$modulegid}->{'ParentID'}) ) || ( $allmodules->{$modulegid}->{'ParentID'} eq "" ))
+ {
+ if ( $foundroot )
+ {
+ die "\nERROR: More than one Root module. Only one module without ParentID or with empty ParentID allowed ($rootgid and $modulegid).\n";
+ }
+ $rootgid = $modulegid;
+ $foundroot = 1;
+ }
+ }
+
+ if ( ! $foundroot )
+ {
+ die "\nERROR: Could not find Root module. Did not find module without ParentID or with empty ParentID.\n";
+ }
+
+ print " $rootgid\n" if $par2script::globals::verbose;
+
+}
+
+########################################################
+# File, Shortcut, Directory, Unixlink must not
+# contain a ModuleID
+########################################################
+
+sub check_moduleid_at_items
+{
+ my $item;
+ foreach $item ( @par2script::globals::items_without_moduleid )
+ {
+ my $allitems = $par2script::globals::definitions{$item};
+
+ my $onegid;
+ foreach $onegid ( keys %{$allitems} )
+ {
+ if ( exists($allitems->{$onegid}->{'ModuleID'}) )
+ {
+ die "\nERROR: ModuleID assigned to $onegid! No module assignment to $item!\n\n";
+ }
+ }
+ }
+}
+
+########################################################
+# Controlling existence of multi assignments
+########################################################
+
+sub check_multiple_assignments
+{
+ my @multiassignments = ();
+ my $error;
+
+ my $topitem;
+ foreach $topitem ( keys %par2script::globals::assignedgids )
+ {
+ my $item;
+ foreach $item ( keys %{$par2script::globals::assignedgids{$topitem}} )
+ {
+ if ( $par2script::globals::assignedgids{$topitem}->{$item} > 1 )
+ {
+ $error = 1;
+ my $string = "\tGID: $item Assignments: $par2script::globals::assignedgids{$topitem}->{$item}";
+ push(@multiassignments, $string);
+ }
+ }
+ }
+
+ if ( $error ) { par2script::exiter::multiassignmenterror(\@multiassignments); }
+}
+
+########################################################
+# Check, if a defined directory has a flag CREATE
+########################################################
+
+sub contains_create_flag
+{
+ my ($gid) = @_;
+
+ my $createflag = 0;
+
+ if (( exists($par2script::globals::definitions{'Directory'}->{$gid}->{'Styles'}) ) &&
+ ( $par2script::globals::definitions{'Directory'}->{$gid}->{'Styles'} =~ /\bCREATE\b/ ))
+ {
+ $createflag = 1;
+ }
+
+ return $createflag;
+}
+
+########################################################
+# Controlling existence of definitions without
+# any assignment
+########################################################
+
+sub check_missing_assignments
+{
+ # If defined gids for "File", "Directory" or "Unixlink" are not assigned,
+ # this causes an error.
+ # Directories only have to be assigned, if they have the flag "CREATE".
+
+ my @missingassignments = ();
+ $error = 0;
+
+ my $item;
+ foreach $item ( @par2script::globals::items_assigned_at_modules )
+ {
+ my $assignedgids = $par2script::globals::assignedgids{$item};
+ my $definedgids = $par2script::globals::definitions{$item};
+
+ my $gid;
+ foreach $gid ( keys %{$definedgids} )
+ {
+ if ( $item eq "Directory" ) { if ( ! contains_create_flag($gid) ) { next; } }
+
+ if ( ! exists( $assignedgids->{$gid} ))
+ {
+ $error = 1;
+ push(@missingassignments, $gid);
+ }
+ }
+ }
+
+ if ( $error ) { par2script::exiter::missingassignmenterror(\@missingassignments); }
+}
+
+#############################################################
+# Controlling if for all shortcuts with file assignment
+# the file is defined. And for all shortcuts with
+# shortcut assignment the shortcut has to be defined.
+#############################################################
+
+sub check_shortcut_assignments
+{
+ my $allshortcuts = $par2script::globals::definitions{'Shortcut'};
+ my $allfiles = $par2script::globals::definitions{'File'};
+
+ my $shortcut;
+ foreach $shortcut ( keys %{$allshortcuts} )
+ {
+ if (( exists($allshortcuts->{$shortcut}->{'FileID'}) ) &&
+ ( ! exists($allfiles->{$allshortcuts->{$shortcut}->{'FileID'}}) ))
+ {
+ die "\nERROR: FileID $allshortcuts->{$shortcut}->{'FileID'} has no definition at shortcut $shortcut !\n";
+ }
+
+ if (( exists($allshortcuts->{$shortcut}->{'ShortcutID'}) ) &&
+ ( ! exists($allshortcuts->{$allshortcuts->{$shortcut}->{'ShortcutID'}}) ))
+ {
+ die "\nERROR: ShortcutID $allshortcuts->{$shortcut}->{'ShortcutID'} has no definition at shortcut $shortcut !\n";
+ }
+
+ if (( ! exists($allshortcuts->{$shortcut}->{'ShortcutID'}) ) &&
+ ( ! exists($allshortcuts->{$shortcut}->{'FileID'}) ))
+ {
+ die "\nERROR: Shortcut requires assignment to \"ShortcutID\" or \"FileID\". Missing at shortcut $shortcut !\n";
+ }
+ }
+}
+
+#############################################################
+# Controlling if for Modules and Directories, the parents
+# are defined. If not, this can lead to a problem during
+# script creation, because only recursively added
+# Modules or Directories are added to the script.
+#############################################################
+
+sub check_missing_parents
+{
+ my @parentitems = ("Module", "Directory");
+ my %rootparents = ("PREDEFINED_PROGDIR" => "1");
+
+ my $oneitem;
+ foreach $oneitem ( @parentitems )
+ {
+ my $alldefinitions = $par2script::globals::definitions{$oneitem};
+
+ my $onegid;
+ foreach $onegid ( keys %{$alldefinitions} )
+ {
+ # If there is a ParentID used, it must be defined
+ if (( exists($alldefinitions->{$onegid}->{'ParentID'}) ) &&
+ ( ! exists($alldefinitions->{$alldefinitions->{$onegid}->{'ParentID'}}) ) &&
+ ( ! exists($rootparents{$alldefinitions->{$onegid}->{'ParentID'}}) ))
+ {
+ die "\nERROR: Parent \"$alldefinitions->{$onegid}->{'ParentID'}\" at $oneitem \"$onegid\" is not defined!\n";
+ }
+ }
+ }
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/converter.pm b/solenv/bin/modules/par2script/converter.pm
new file mode 100644
index 0000000000..43c3a751f1
--- /dev/null
+++ b/solenv/bin/modules/par2script/converter.pm
@@ -0,0 +1,52 @@
+#
+# 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 par2script::converter;
+
+use par2script::remover;
+
+#############################
+# Converter
+#############################
+
+sub convert_stringlist_into_array_2
+{
+ my ( $input, $separator ) = @_;
+
+ my @newarray = ();
+ my $first = "";
+ my $last = "";
+
+ $last = $input;
+
+ while ( $last =~ /^\s*(.+?)\s*\Q$separator\E\s*(.+)\s*$/) # "$" for minimal matching
+ {
+ $first = $1;
+ $last = $2;
+ par2script::remover::remove_leading_and_ending_whitespaces(\$first);
+ if ( $first ) { push(@newarray, $first); }
+ }
+
+ par2script::remover::remove_leading_and_ending_whitespaces(\$last);
+ if ( $last ) { push(@newarray, $last); }
+
+ return \@newarray;
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/exiter.pm b/solenv/bin/modules/par2script/exiter.pm
new file mode 100644
index 0000000000..f2d2ec649b
--- /dev/null
+++ b/solenv/bin/modules/par2script/exiter.pm
@@ -0,0 +1,112 @@
+#
+# 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 par2script::exiter;
+
+use par2script::files;
+use par2script::globals;
+
+############################################
+# Exiting the program with an error
+# This function is used instead of "die"
+############################################
+
+sub exit_program
+{
+ my ($message, $function) = @_;
+
+ my $infoline;
+
+ $infoline = "\n***************************************************************\n";
+ push(@par2script::globals::logfileinfo, $infoline);
+ print("$infoline");
+
+ $infoline = "$message\n";
+ push(@par2script::globals::logfileinfo, $infoline);
+ print("$infoline");
+
+ $infoline = "in function: $function\n";
+ push(@par2script::globals::logfileinfo, $infoline);
+ print("$infoline");
+
+ $infoline = "***************************************************************\n";
+ push(@par2script::globals::logfileinfo, $infoline);
+
+ if ($par2script::globals::logging)
+ {
+ par2script::files::save_file($par2script::globals::logfilename, \@par2script::globals::logfileinfo);
+ print("Saved logfile: $par2script::globals::logfilename\n");
+ }
+
+ print("$infoline");
+
+ exit(-1);
+}
+
+#####################################
+# Error, because a gid is defined
+# more than once
+#####################################
+
+sub multidefinitionerror
+{
+ my ( $multidefinitiongids ) = @_;
+ print "************************************************\n";
+ print "ERROR: multiple definition of gids:\n";
+ print "************************************************\n";
+
+ my $gid;
+ foreach $gid ( @{$multidefinitiongids} ) { print "\t$gid\n"; }
+ exit(-1);
+}
+
+#####################################
+# Error, because a gid is assigned
+# more than once
+#####################################
+
+sub multiassignmenterror
+{
+ my ( $multiassignmentgids ) = @_;
+ print "************************************************\n";
+ print "WARNING: multiple assignments of gids:\n";
+ print "************************************************\n";
+
+ my $line;
+ foreach $line ( @{$multiassignmentgids} ) { print "\t$line\n"; }
+}
+
+#####################################
+# Error, because a defined gid
+# is not assigned
+#####################################
+
+sub missingassignmenterror
+{
+ my ( $missingassignmentgids ) = @_;
+ print "********************************************************\n";
+ print "ERROR: Missing assignments for the following GIDs:\n";
+ print "********************************************************\n";
+
+ my $gid;
+ foreach $gid ( @{$missingassignmentgids} ) { print "\t$gid\n"; }
+ exit(-1);
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/files.pm b/solenv/bin/modules/par2script/files.pm
new file mode 100644
index 0000000000..1e0aa6c441
--- /dev/null
+++ b/solenv/bin/modules/par2script/files.pm
@@ -0,0 +1,64 @@
+#
+# 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 par2script::files;
+
+use par2script::exiter;
+
+############################################
+# File Operations
+############################################
+
+sub check_file
+{
+ my ($arg) = @_;
+
+ if(!( -f $arg ))
+ {
+ par2script::exiter::exit_program("ERROR: Cannot find file $arg", "check_file");
+ }
+}
+
+sub read_file
+{
+ my ($localfile) = @_;
+
+ my @localfile = ();
+
+ open( IN, "<$localfile" ) || par2script::exiter::exit_program("ERROR: Cannot open file: $localfile", "read_file");
+ while ( <IN> ) { push(@localfile, $_); }
+ close( IN );
+
+ return \@localfile;
+}
+
+###########################################
+# Saving files
+###########################################
+
+sub save_file
+{
+ my ($savefile, $savecontent) = @_;
+ open( OUT, ">$savefile" );
+ print OUT @{$savecontent};
+ close( OUT);
+ if (! -f $savefile) { pre2par::exiter::exit_program("ERROR: Cannot write file: $savefile", "save_file"); }
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/globals.pm b/solenv/bin/modules/par2script/globals.pm
new file mode 100644
index 0000000000..02a399767b
--- /dev/null
+++ b/solenv/bin/modules/par2script/globals.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 par2script::globals;
+
+############################################
+# Global settings
+############################################
+
+BEGIN
+{
+ $prog="par2script";
+
+ $includepathlist = "";
+ $scriptname = "";
+ $parfilelistorig = "";
+ $parfilelist = "";
+
+ @allitems = ("Installation", "ScpAction", "Directory", "File",
+ "Shortcut", "Unixlink", "Module", "Profile", "ProfileItem",
+ "Folder", "FolderItem", "FolderItemProperty", "RegistryItem",
+ "WindowsCustomAction", "MergeModule");
+
+ @items_assigned_at_modules = ("File", "Directory", "Unixlink");
+ @items_with_directories = ("File", "Profile", "Shortcut", "Unixlink");
+ @items_with_moduleid = ("Profile", "ProfileItem", "FolderItem", "RegistryItem");
+ @items_without_moduleid = ("File", "FolderItemProperty", "Directory", "Shortcut", "Unixlink");
+
+ %searchkeys = ("File" => "Files", "Directory" => "Dirs", "Unixlink" => "Unixlinks");
+
+ $logging = 0;
+ $logfilename = "logfile.log"; # the default logfile name for global errors
+ @logfileinfo = ();
+
+ $verbose = 1;
+
+ $multidefinitionerror = 0;
+ $multiassignmenterror = 0;
+
+ %definitions;
+ %assignedgids;
+
+ $plat = $^O;
+
+ $separator = "/";
+ $pathseparator = "\:";
+ $isunix = 1;
+ $iswin = 0;
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/module.pm b/solenv/bin/modules/par2script/module.pm
new file mode 100644
index 0000000000..ef916650b4
--- /dev/null
+++ b/solenv/bin/modules/par2script/module.pm
@@ -0,0 +1,255 @@
+#
+# 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 par2script::module;
+
+use par2script::converter;
+use par2script::exiter;
+
+###########################################
+# Removing undefined gids
+# from modules
+###########################################
+
+sub remove_from_modules
+{
+ my ($gid, $item) = @_;
+
+ my $counter = 0;
+
+ if ( ! exists($par2script::globals::searchkeys{$item}) ) { par2script::exiter::exit_program("ERROR: Unknown type \"$item\" at modules.", "remove_from_modules"); }
+ my $searchkey = $par2script::globals::searchkeys{$item};
+
+ my $allmodules = $par2script::globals::definitions{'Module'};
+
+ my $onemodule;
+ foreach $onemodule (keys %{$allmodules})
+ {
+ if (( exists($allmodules->{$onemodule}->{$searchkey}) ) && ( $allmodules->{$onemodule}->{$searchkey} =~ /\b$gid\b/ ))
+ {
+ my $infoline = "WARNING: Removing $gid because of missing definition\n";
+ # print $infoline;
+ push(@par2script::globals::logfileinfo, $infoline);
+
+ $allmodules->{$onemodule}->{$searchkey} =~ s/\b$gid\b//;
+ $allmodules->{$onemodule}->{$searchkey} =~ s/\,\s*\,/\,/;
+ $allmodules->{$onemodule}->{$searchkey} =~ s/\(\s*\,\s*/\(/;
+ $allmodules->{$onemodule}->{$searchkey} =~ s/\s*\,\s*\)/\)/;
+
+ if (( $allmodules->{$onemodule}->{$searchkey} =~ /\(\s*\,\s*\)/ ) ||
+ ( $allmodules->{$onemodule}->{$searchkey} =~ /\(\s*\)/ ))
+ {
+ delete($allmodules->{$onemodule}->{$searchkey});
+ }
+
+ $counter++;
+ }
+ }
+
+ return $counter;
+}
+
+###########################################
+# Removing undefined gids automatically
+# from modules
+###########################################
+
+sub remove_undefined_gids_from_modules
+{
+ # If assigned gids for "File", "Directory" or "Unixlink" are not defined,
+ # they are automatically removed from the module
+
+ foreach $item ( @par2script::globals::items_assigned_at_modules )
+ {
+ my $assignedgids = $par2script::globals::assignedgids{$item};
+ my $definedgids = $par2script::globals::definitions{$item};
+
+ my $gid;
+ foreach $gid ( keys %{$assignedgids} )
+ {
+ if ( ! exists( $definedgids->{$gid} ))
+ {
+ # deleting entry in module definition
+ my $number_of_removals = remove_from_modules($gid, $item);
+ # decreasing counter in assignments
+ if ( $assignedgids->{$gid} > $number_of_removals ) { $assignedgids->{$gid} = $assignedgids->{$gid} - $number_of_removals; }
+ else { delete($assignedgids->{$gid}); }
+ }
+ }
+ }
+}
+
+############################################
+# Getting the gid of the root module. The
+# root module has no ParentID or an empty
+# ParentID.
+############################################
+
+sub get_rootmodule_gid
+{
+ my $rootgid = "";
+ my $foundroot = 0;
+
+ my $allmodules = $par2script::globals::definitions{'Module'};
+
+ my $modulegid = "";
+ foreach $modulegid (keys %{$allmodules} )
+ {
+ # print "Module $modulegid\n";
+ # my $content = "";
+ # foreach $content (sort keys %{$allmodules->{$modulegid}}) { print "\t$content = $allmodules->{$modulegid}->{$content};\n"; }
+ # print "End\n";
+ # print "\n";
+
+ if (( ! exists($allmodules->{$modulegid}->{'ParentID'})) || ( $allmodules->{$modulegid}->{'ParentID'} eq "" ))
+ {
+ if ( $foundroot ) { par2script::exiter::exit_program("ERROR: More than one Root module. Only one module without ParentID or with empty ParentID allowed ($rootgid and $modulegid).", "get_rootmodule_gid"); }
+ $rootgid = $modulegid;
+ $foundroot = 1;
+ }
+ }
+
+ if ( ! $foundroot ) { par2script::exiter::exit_program("ERROR: Could not find Root module. Did not find module without ParentID or with empty ParentID.", "get_rootmodule_gid"); }
+
+ return $rootgid;
+}
+
+####################################
+# Adding defined items without
+# assignment to the root module.
+####################################
+
+sub add_to_root_module
+{
+ # If defined gids for "File", "Directory" or "Unixlink" are not assigned,
+ # they are automatically assigned to the root module
+
+ my $rootmodulegid = get_rootmodule_gid();
+
+ my $item;
+ foreach $item ( @par2script::globals::items_assigned_at_modules )
+ {
+ my $assignedgids = $par2script::globals::assignedgids{$item};
+ my $definedgids = $par2script::globals::definitions{$item};
+
+ my $gidstring = "";
+
+ # Perhaps there are already items assigned to the root
+ if ( ! exists($par2script::globals::searchkeys{$item}) ) { par2script::exiter::exit_program("ERROR: Unknown type \"$item\" at modules.", "remove_from_modules"); }
+ my $modulekey = $par2script::globals::searchkeys{$item};
+ if ( exists($par2script::globals::definitions{'Module'}->{$rootmodulegid}->{$modulekey}) )
+ {
+ $gidstring = $par2script::globals::definitions{'Module'}->{$rootmodulegid}->{$modulekey};
+ $gidstring =~ s/\(//;
+ $gidstring =~ s/\)//;
+ }
+
+ my $gid;
+ foreach $gid ( keys %{$definedgids} )
+ {
+ if ( ! exists( $assignedgids->{$gid} ))
+ {
+ if ( $gidstring eq "" )
+ {
+ $gidstring = $gid;
+ }
+ else
+ {
+ $gidstring = "$gidstring,$gid";
+ }
+
+ $assignedgids->{$gid} = 1;
+ }
+ }
+
+ if ( $gidstring ne "" )
+ {
+ $gidstring = "\($gidstring\)";
+ $par2script::globals::definitions{'Module'}->{$rootmodulegid}->{$modulekey} = $gidstring;
+ }
+ }
+}
+
+###################################################
+# Including \n in a very long string
+###################################################
+
+sub include_linebreaks
+{
+ my ($allgidstring) = @_;
+
+ my $newline = "";
+ my $newlength = 0;
+
+ $allgidstring =~ s/\(//;
+ $allgidstring =~ s/\)//;
+
+ my $allgids = par2script::converter::convert_stringlist_into_array_2($allgidstring, ",");
+
+ if ( $#{$allgids} > -1 )
+ {
+ my $onegid;
+ foreach $onegid ( @{$allgids} )
+ {
+ $newline = "$newline$onegid,";
+ $newlength = $newlength + length($onegid) + 1; # +1 for the comma
+
+ if ( $newlength > 80 )
+ {
+ $newline = $newline . "\n\t\t\t\t";
+ $newlength = 0;
+ }
+ }
+ }
+
+ $newline =~ s/,\s*$//;
+ $newline = "($newline)";
+
+ return $newline;
+}
+
+###################################################
+# Shorten the lines that belong to modules, if
+# the length of the line is greater 100
+###################################################
+
+sub shorten_lines_at_modules
+{
+ my $item;
+ foreach $item ( @par2script::globals::items_assigned_at_modules )
+ {
+ if ( ! exists($par2script::globals::searchkeys{$item}) ) { par2script::exiter::exit_program("ERROR: Unknown type \"$item\" at modules.", "shorten_lines_at_modules"); }
+ my $searchkey = $par2script::globals::searchkeys{$item};
+
+ my $allmodules = $par2script::globals::definitions{'Module'};
+
+ my $onemodule;
+ foreach $onemodule (keys %{$allmodules})
+ {
+ if (( exists($allmodules->{$onemodule}->{$searchkey}) ) &&
+ ( length($allmodules->{$onemodule}->{$searchkey}) > 100 ))
+ {
+ # including "\n\t\t\t\t"
+ my $newstring = include_linebreaks($allmodules->{$onemodule}->{$searchkey});
+ $allmodules->{$onemodule}->{$searchkey} = $newstring;
+ }
+ }
+ }
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/parameter.pm b/solenv/bin/modules/par2script/parameter.pm
new file mode 100644
index 0000000000..86416cd9e2
--- /dev/null
+++ b/solenv/bin/modules/par2script/parameter.pm
@@ -0,0 +1,145 @@
+#
+# 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 par2script::parameter;
+
+use Cwd;
+use par2script::files;
+use par2script::globals;
+
+############################################
+# Parameter Operations
+############################################
+
+###############################################################################
+# Usage:
+# perl par2script.pl -i ..\wntmsci8.pro\par,o:\SRX645\wntmsci8.pro\par.m24
+# @@C:\DOCUMEN~1\is\LOCALS~1\Temp\mk6pd
+# -o ..\wntmsci8.pro\bin\osl\setup_osl.inf
+###############################################################################
+
+sub usage
+{
+ print <<End;
+
+--------------------------------------------------------------
+$par2script::globals::prog
+The following parameter are needed:
+-i: include paths, comma separated list
+-o: setup script file name
+-v: writing logfile.txt (optional)
+\@\@list: list of all par files
+
+Example:
+ perl par2script.pl -i ..\\wntmsci8\\par\,o\:\\SRX645\\wntmsci8\\par.m24
+ \@\@C\:\\DOCUMEN\~1\\is\\LOCALS\~1\\Temp\\mk6pd
+ -o ..\\wntmsci8.pro\\bin\\osl\\setup_osl.inf \[-v\]
+
+--------------------------------------------------------------
+End
+ exit(-1);
+}
+
+#####################################
+# Reading parameter
+#####################################
+
+sub getparameter
+{
+ while ( $#ARGV >= 0 )
+ {
+ my $param = shift(@ARGV);
+
+ if ($param eq "-o") { $par2script::globals::scriptname = shift(@ARGV); }
+ elsif ($param eq "-q") { $par2script::globals::verbose = 0; }
+ elsif ($param eq "-v") { $par2script::globals::logging = 1; }
+ elsif ($param =~ /\@\@/) { $par2script::globals::parfilelistorig = $param; }
+ elsif ($param eq "-i") { $par2script::globals::includepathlist = shift(@ARGV); }
+ elsif (($param =~ /\//) || ($param =~ /\\/)) # another include parameter!
+ {
+ $par2script::globals::includepathlist = $par2script::globals::includepathlist . "," . $param;
+ }
+ else
+ {
+ print("\n*************************************\n");
+ print("Sorry, unknown parameter: $param");
+ print("\n*************************************\n");
+ usage();
+ exit(-1);
+ }
+ }
+}
+
+############################################
+# Controlling the fundamental parameter
+# (required for every process)
+############################################
+
+sub control_parameter
+{
+ if ($par2script::globals::includepathlist eq "")
+ {
+ print "\n************************************************\n";
+ print "Error: Include paths not set not set (-i)!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ if ($par2script::globals::scriptname eq "")
+ {
+ print "\n************************************************\n";
+ print "Error: Name of the setup script not set (-o)!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ if ($par2script::globals::parfilelistorig eq "")
+ {
+ print "\n************************************************\n";
+ print "Error: List of par files not set!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ # The par file list has to exist
+
+ $par2script::globals::parfilelist = $par2script::globals::parfilelistorig;
+ $par2script::globals::parfilelist =~ s/\@\@//;
+ par2script::files::check_file($par2script::globals::parfilelist);
+}
+
+#####################################
+# Writing parameter to shell
+#####################################
+
+sub outputparameter
+{
+ my $outputline = "\n$par2script::globals::prog -i $par2script::globals::includepathlist $par2script::globals::parfilelistorig -o $par2script::globals::scriptname";
+
+ if ($par2script::globals::logging) { $outputline .= " -v"; }
+
+ $outputline .= "\n";
+
+ print $outputline;
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/remover.pm b/solenv/bin/modules/par2script/remover.pm
new file mode 100644
index 0000000000..8b40a8ff46
--- /dev/null
+++ b/solenv/bin/modules/par2script/remover.pm
@@ -0,0 +1,42 @@
+#
+# 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 par2script::remover;
+
+############################################
+# Remover
+############################################
+
+sub remove_leading_and_ending_whitespaces
+{
+ my ( $stringref ) = @_;
+
+ $$stringref =~ s/^\s*//g;
+ $$stringref =~ s/\s*$//g;
+}
+
+sub remove_leading_and_ending_comma
+{
+ my ( $stringref ) = @_;
+
+ $$stringref =~ s/^\s*\,//g;
+ $$stringref =~ s/\,\s*$//g;
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/undefine.pm b/solenv/bin/modules/par2script/undefine.pm
new file mode 100644
index 0000000000..43cbe26193
--- /dev/null
+++ b/solenv/bin/modules/par2script/undefine.pm
@@ -0,0 +1,135 @@
+#
+# 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 par2script::undefine;
+
+use par2script::globals;
+
+##########################################################
+# Removing in the script all the gids, that are listed
+# in undefine scp files
+##########################################################
+
+sub undefine_gids
+{
+ my ($parfilecontent) = @_;
+
+ my $item;
+ foreach $item ( @par2script::globals::allitems )
+ {
+ my $unitem = "Un$item";
+
+ for ( my $i = 0; $i <= $#{$parfilecontent}; $i++ )
+ {
+ if ( ${$parfilecontent}[$i] =~ /^\s*$unitem\s*(\w+?)\s*$/ )
+ {
+ my $gid = $1;
+ delete($par2script::globals::definitions{$item}->{$gid});
+ }
+ }
+ }
+}
+
+##########################################################
+# Collecting all subdirectories of a specified directory
+##########################################################
+
+sub collect_children_dirs
+{
+ my ($gid, $collector) = @_;
+
+ my $diritem = "Directory";
+ my $parentkey = "ParentID";
+
+ if ( exists($par2script::globals::definitions{$diritem}) )
+ {
+ my $onedefinition;
+
+ foreach $onedefinition (keys %{$par2script::globals::definitions{$diritem}})
+ {
+ if ( $par2script::globals::definitions{$diritem}->{$onedefinition}->{$parentkey} eq $gid )
+ {
+ push(@{$collector}, $onedefinition);
+ collect_children_dirs($onedefinition, $collector);
+ }
+ }
+ }
+}
+
+##########################################################
+# Removing in the script complete profiles.
+# This includes the Profile and its ProfileItems.
+##########################################################
+
+sub remove_complete_item
+{
+ my ($item, $parfilecontent) = @_;
+
+ my $removeitem = "Remove$item";
+ my $dependentkey = "";
+ my $collect_children = 0;
+ my @gidcollector = ();
+ my @dependentitems = ();
+
+ if ( $item eq "Profile" )
+ {
+ @dependentitems = ("ProfileItem");
+ $dependentkey = "ProfileID";
+ }
+ elsif ( $item eq "Directory" )
+ {
+ @dependentitems = ("File", "Shortcut", "Unixlink");
+ $dependentkey = "Dir";
+ $collect_children = 1;
+ }
+
+ for ( my $i = 0; $i <= $#{$parfilecontent}; $i++ )
+ {
+ if ( ${$parfilecontent}[$i] =~ /^\s*$removeitem\s*(\w+?)\s*$/ )
+ {
+ my $onegid = $1;
+ push(@gidcollector, $onegid);
+ if ( $collect_children ) { collect_children_dirs($onegid, \@gidcollector); }
+
+ my $gid;
+ foreach $gid (@gidcollector)
+ {
+ delete($par2script::globals::definitions{$item}->{$gid});
+
+ # also deleting all dependent items, for example "ProfileItems" whose "ProfileID" is this "Profile"
+ my $depitem;
+ foreach $depitem ( @dependentitems )
+ {
+ if ( exists($par2script::globals::definitions{$depitem}) )
+ {
+ my $onedefinition;
+ foreach $onedefinition (keys %{$par2script::globals::definitions{$depitem}})
+ {
+ if ( $par2script::globals::definitions{$depitem}->{$onedefinition}->{$dependentkey} eq $gid )
+ {
+ delete($par2script::globals::definitions{$depitem}->{$onedefinition});
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+1;
diff --git a/solenv/bin/modules/par2script/work.pm b/solenv/bin/modules/par2script/work.pm
new file mode 100644
index 0000000000..cc1f1e4d56
--- /dev/null
+++ b/solenv/bin/modules/par2script/work.pm
@@ -0,0 +1,414 @@
+#
+# 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 par2script::work;
+
+use par2script::globals;
+use par2script::remover;
+
+############################################
+# par2script working module
+############################################
+
+sub analyze_comma_separated_list
+{
+ my ($list, $listref) = @_; # second parameter is optional
+
+ my @list = ();
+ my $locallistref;
+
+ if (!( $listref )) { $locallistref = \@list; }
+ else { $locallistref = $listref; }
+
+ par2script::remover::remove_leading_and_ending_comma(\$list);
+ par2script::remover::remove_leading_and_ending_whitespaces(\$list);
+
+ while ( $list =~ /^\s*(.*?)\s*\,\s*(.*)\s*$/ )
+ {
+ my $oneentry = $1;
+ $list = $2;
+ par2script::remover::remove_leading_and_ending_whitespaces(\$oneentry);
+ push(@{$locallistref}, $oneentry);
+ }
+
+ # the last entry
+
+ par2script::remover::remove_leading_and_ending_whitespaces(\$list);
+ push(@{$locallistref}, $list);
+
+ return $locallistref;
+}
+
+############################################
+# setting list of include paths
+############################################
+
+sub setincludes
+{
+ my ($list) = @_;
+
+ # input is the comma separated list of include paths
+
+ my $includes = analyze_comma_separated_list($list);
+
+ return $includes;
+}
+
+############################################
+# setting list of all par files
+############################################
+
+sub setparfiles
+{
+ my ($filename) = @_;
+
+ # input is the name of the list file
+ my $filecontent = par2script::files::read_file($filename);
+
+ my @parfiles = ();
+ my $parfilesref = \@parfiles;
+
+ foreach ( @{$filecontent} ) { $parfilesref = analyze_comma_separated_list($_, $parfilesref); }
+
+ return $parfilesref;
+}
+
+############################################
+# finding the correct include path
+# for the par files
+############################################
+
+sub make_complete_paths_for_parfiles
+{
+ my ($parfiles, $includes) = @_;
+
+ my $oneparfile;
+
+ foreach $oneparfile ( @{$parfiles} )
+ {
+ my $foundparfile = 0;
+ my $includepath;
+
+ foreach $includepath ( @{$includes} )
+ {
+ my $parfile = "$includepath/$oneparfile";
+
+ if ( -f $parfile )
+ {
+ $foundparfile = 1;
+ $oneparfile = $parfile;
+ last;
+ }
+ }
+
+ if ( ! $foundparfile )
+ {
+ die "ERROR: Could not find parfile ${$parfiles}[$i] in includes paths: $par2script::globals::includepathlist !\n";
+ }
+ }
+}
+
+######################################################
+# collecting one special item in the par files and
+# including it into the "definitions" hash
+######################################################
+
+sub collect_definitions
+{
+ my ($parfilecontent) = @_;
+
+ my $multidefinitionerror = 0;
+ my @multidefinitiongids = ();
+
+ my %itemhash;
+
+ # create empty item hashes
+ foreach $oneitem ( @par2script::globals::allitems ) {
+ my %items;
+ $par2script::globals::definitions{$oneitem} = \%items;
+ }
+
+ for ( my $i = 0; $i <= $#{$parfilecontent}; $i++ )
+ {
+ my $line = ${$parfilecontent}[$i];
+ my $oneitem, $gid;
+
+ $line =~ /^\s*$/ && next; # skip blank lines
+
+ # lines should be well formed:
+ if ($line =~ m/^\s*(\w+)\s+(\w+)\s*$/)
+ {
+ $oneitem = $1;
+ $gid = $2;
+ } else {
+ chomp ($line);
+ my $invalid = $line;
+ $invalid =~ s/[\s\w]*//g;
+ par2script::exiter::exit_program("ERROR: malformed par file, invalid character '$invalid', expecting <token> <gid> but saw '$line'", "test_par_syntax");
+ }
+# print STDERR "line '$line' -> '$oneitem' '$gid'\n";
+
+ # hunt badness variously
+ if ( ! defined $par2script::globals::definitions{$oneitem} )
+ {
+ par2script::exiter::exit_program("ERROR: invalid scp2 fragment item type '$oneitem' in line: '$line'", "test_par_syntax");
+ }
+
+ # no hyphen allowed in gids -> cannot happen here because (\w+) is required for gids
+ if ( $gid =~ /-/ ) { par2script::exiter::exit_program("ERROR: No hyphen allowed in global id: $gid", "test_of_hyphen"); }
+
+ my %oneitemhash;
+
+ while (! ( ${$parfilecontent}[$i] =~ /^\s*End\s*$/i ) )
+ {
+ if ( ${$parfilecontent}[$i] =~ /^\s*(.+?)\s*\=\s*(.+?)\s*\;\s*$/ ) # only oneliner!
+ {
+ $itemkey = $1;
+ $itemvalue = $2;
+
+ if ( $oneitem eq "Directory" ) { if ( $itemkey =~ "DosName" ) { $itemkey =~ s/DosName/HostName/; } }
+ if (( $oneitem eq "Directory" ) || ( $oneitem eq "File" ) || ( $oneitem eq "Unixlink" )) { if ( $itemvalue eq "PD_PROGDIR" ) { $itemvalue = "PREDEFINED_PROGDIR"; }}
+ if (( $itemkey eq "Styles" ) && ( $itemvalue =~ /^\s*(\w+)(\s*\;\s*)$/ )) { $itemvalue = "($1)$2"; }
+ elsif ( $itemkey eq "Files" ) # filter out empty file records, as they mess up assignment to modules
+ {
+ $itemvalue =~ /^\(([^)]*)\)$/;
+ $itemvalue = '(' . join( ',', grep( !/^$/, split( ',', $1 ) ) ) . ')';
+ }
+
+ $oneitemhash{$itemkey} = $itemvalue;
+ }
+ $i++;
+ }
+
+ # test of uniqueness
+ if ( defined ($par2script::globals::definitions{$oneitem}->{$gid}) )
+ {
+ $multidefinitionerror = 1;
+ push(@multidefinitiongids, $gid);
+ }
+
+ $par2script::globals::definitions{$oneitem}->{$gid} = \%oneitemhash;
+ }
+
+ if ( $multidefinitionerror ) { par2script::exiter::multidefinitionerror(\@multidefinitiongids); }
+
+ # foreach $key (keys %par2script::globals::definitions)
+ # {
+ # print "Key: $key \n";
+ #
+ # foreach $key (keys %{$par2script::globals::definitions{$key}})
+ # {
+ # print "\t$key \n";
+ # }
+ # }
+}
+
+######################################################
+# Filling content into the script
+######################################################
+
+sub put_oneitem_into_script
+{
+ my ( $script, $item, $itemhash, $itemkey ) = @_;
+
+ push(@{$script}, "$item $itemkey\n" );
+ my $content = "";
+ foreach $content (sort keys %{$itemhash->{$itemkey}}) { push(@{$script}, "\t$content = $itemhash->{$itemkey}->{$content};\n" ); }
+ push(@{$script}, "End\n" );
+ push(@{$script}, "\n" );
+}
+
+######################################################
+# Creating the script
+######################################################
+
+sub create_script
+{
+ my @script = ();
+ my $oneitem;
+
+ foreach $oneitem ( @par2script::globals::allitems )
+ {
+ if ( exists($par2script::globals::definitions{$oneitem}) )
+ {
+ if ( $oneitem eq "Shortcut" ) { next; } # "Shortcuts" after "Files"
+
+ if (( $oneitem eq "Module" ) || ( $oneitem eq "Directory" )) { write_sorted_items(\@script, $oneitem); }
+ else { write_unsorted_items(\@script, $oneitem); }
+ }
+ }
+
+ return \@script;
+}
+
+######################################################
+# Adding script content for the unsorted items
+######################################################
+
+sub write_unsorted_items
+{
+ my ( $script, $oneitem ) = @_;
+
+ my $itemhash = $par2script::globals::definitions{$oneitem};
+
+ my $itemkey = "";
+ foreach $itemkey (sort keys %{$itemhash})
+ {
+ put_oneitem_into_script($script, $oneitem, $itemhash, $itemkey);
+
+ # special handling for Shortcuts after Files
+ if (( $oneitem eq "File" ) && ( exists($par2script::globals::definitions{"Shortcut"}) ))
+ {
+ my $shortcutkey;
+ foreach $shortcutkey ( keys %{$par2script::globals::definitions{"Shortcut"}} )
+ {
+ if ( $par2script::globals::definitions{"Shortcut"}->{$shortcutkey}->{'FileID'} eq $itemkey )
+ {
+ put_oneitem_into_script($script, "Shortcut", $par2script::globals::definitions{"Shortcut"}, $shortcutkey);
+
+ # and Shortcut to Shortcut also
+ my $internshortcutkey;
+ foreach $internshortcutkey ( keys %{$par2script::globals::definitions{"Shortcut"}} )
+ {
+ if ( $par2script::globals::definitions{"Shortcut"}->{$internshortcutkey}->{'ShortcutID'} eq $shortcutkey )
+ {
+ put_oneitem_into_script($script, "Shortcut", $par2script::globals::definitions{"Shortcut"}, $internshortcutkey);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+######################################################
+# Collecting all children of a specified parent
+######################################################
+
+sub collect_children
+{
+ my ( $itemhash, $parent, $order ) = @_;
+
+ my $item;
+ foreach $item ( sort keys %{$itemhash} )
+ {
+ if ( $itemhash->{$item}->{'ParentID'} eq $parent )
+ {
+ push(@{$order}, $item);
+ my $newparent = $item;
+ collect_children($itemhash, $newparent, $order);
+ }
+ }
+}
+
+######################################################
+# Adding script content for the sorted items
+######################################################
+
+sub write_sorted_items
+{
+ my ( $script, $oneitem ) = @_;
+
+ my $itemhash = $par2script::globals::definitions{$oneitem};
+
+ my @itemorder = ();
+ my @startparents = ();
+
+ if ( $oneitem eq "Module" ) { push(@startparents, ""); }
+ elsif ( $oneitem eq "Directory" ) { push(@startparents, "PREDEFINED_PROGDIR"); }
+ else { die "ERROR: No root parent defined for item type $oneitem !\n"; }
+
+ # supporting more than one toplevel item
+ my $parent;
+ foreach $parent ( @startparents ) { collect_children($itemhash, $parent, \@itemorder); }
+
+ my $itemkey;
+ foreach $itemkey ( @itemorder ) { put_oneitem_into_script($script, $oneitem, $itemhash, $itemkey); }
+}
+
+#######################################################################
+# Collecting all assigned gids of the type "item" from the modules
+# in the par files. Using a hash!
+#######################################################################
+
+sub collect_assigned_gids
+{
+ my $allmodules = $par2script::globals::definitions{'Module'};
+
+ my $item;
+ foreach $item ( @par2script::globals::items_assigned_at_modules )
+ {
+ if ( ! exists($par2script::globals::searchkeys{$item}) ) { par2script::exiter::exit_program("ERROR: Unknown type \"$item\" at modules.", "collect_assigned_gids"); }
+
+ my $searchkey = $par2script::globals::searchkeys{$item};
+
+ my %assignitems = ();
+ my $modulegid = "";
+
+ foreach $modulegid (keys %{$allmodules} )
+ {
+ # print "Module $modulegid\n";
+ # my $content = "";
+ # foreach $content (sort keys %{$allmodules->{$modulegid}}) { print "\t$content = $allmodules->{$modulegid}->{$content};\n"; }
+ # print "End\n";
+ # print "\n";
+
+ if ( exists($allmodules->{$modulegid}->{$searchkey}) )
+ {
+ my $list = $allmodules->{$modulegid}->{$searchkey};
+ if ( $list =~ /^\s*\((.*?)\)\s*(.*?)\s*$/ ) { $list = $1; }
+ else { par2script::exiter::exit_program("ERROR: Invalid module list: $list", "collect_assigned_gids"); }
+ my $allassigneditems = par2script::converter::convert_stringlist_into_array_2($list, ",");
+
+ my $gid;
+ foreach $gid ( @{$allassigneditems} )
+ {
+ if ( exists($assignitems{$gid}) ) { $assignitems{$gid} = $assignitems{$gid} + 1; }
+ else { $assignitems{$gid} = 1; }
+ }
+ }
+ }
+
+ $par2script::globals::assignedgids{$item} = \%assignitems;
+ }
+}
+
+##################################################
+# Collecting the content of all par files.
+# Then the files do not need to be opened twice.
+##################################################
+
+sub read_all_parfiles
+{
+ my ($parfiles) = @_;
+
+ my @parfilecontent = ();
+ my $parfilename;
+
+ foreach $parfilename ( @{$parfiles} )
+ {
+ my $parfile = par2script::files::read_file($parfilename);
+ foreach ( @{$parfile} ) { push(@parfilecontent, $_); }
+ push(@parfilecontent, "\n");
+ }
+
+ return \@parfilecontent;
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/directory.pm b/solenv/bin/modules/pre2par/directory.pm
new file mode 100644
index 0000000000..02cfed6f99
--- /dev/null
+++ b/solenv/bin/modules/pre2par/directory.pm
@@ -0,0 +1,45 @@
+#
+# 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 pre2par::directory;
+
+use pre2par::pathanalyzer;
+use pre2par::systemactions;
+
+############################################
+# Checking, whether the output directories
+# exist. If not, they are created.
+############################################
+
+sub check_directory
+{
+ my ($parfilename) = @_;
+
+ my $productdirectory = $parfilename;
+ pre2par::pathanalyzer::get_path_from_fullqualifiedname(\$productdirectory);
+ $productdirectory =~ s/\Q$pre2par::globals::separator\E\s*$//;
+
+ my $pardirectory = $productdirectory;
+ pre2par::pathanalyzer::get_path_from_fullqualifiedname(\$pardirectory);
+ $pardirectory =~ s/\Q$pre2par::globals::separator\E\s*$//;
+
+ if ( ! -d $pardirectory ) { pre2par::systemactions::create_directory($pardirectory); }
+ if ( ! -d $productdirectory ) { pre2par::systemactions::create_directory($productdirectory); }
+}
+
+1; \ No newline at end of file
diff --git a/solenv/bin/modules/pre2par/exiter.pm b/solenv/bin/modules/pre2par/exiter.pm
new file mode 100644
index 0000000000..d07675ac44
--- /dev/null
+++ b/solenv/bin/modules/pre2par/exiter.pm
@@ -0,0 +1,61 @@
+#
+# 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 pre2par::exiter;
+
+use pre2par::files;
+use pre2par::globals;
+
+############################################
+# Exiting the program with an error
+# This function is used instead of "die"
+############################################
+
+sub exit_program
+{
+ my ($message, $function) = @_;
+
+ my $infoline;
+
+ $infoline = "\n***************************************************************\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+ print("$infoline");
+
+ $infoline = "$message\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+ print("$infoline");
+
+ $infoline = "in function: $function\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+ print("$infoline");
+
+ $infoline = "***************************************************************\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+
+ if ($pre2par::globals::logging)
+ {
+ pre2par::files::save_file($pre2par::globals::logfilename ,\@pre2par::globals::logfileinfo);
+ print("Saved logfile: $pre2par::globals::logfilename\n");
+ }
+
+ print("$infoline");
+
+ exit(-1);
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/files.pm b/solenv/bin/modules/pre2par/files.pm
new file mode 100644
index 0000000000..ddc8d9157e
--- /dev/null
+++ b/solenv/bin/modules/pre2par/files.pm
@@ -0,0 +1,55 @@
+#
+# 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 pre2par::files;
+
+use pre2par::exiter;
+
+############################################
+# File Operations
+############################################
+
+sub read_file
+{
+ my ($localfile) = @_;
+
+ my @localfile = ();
+
+ open( IN, "<$localfile" ) || pre2par::exiter::exit_program("ERROR: Cannot open file: $localfile", "read_file");
+ while ( <IN> ) { push(@localfile, $_); }
+ close( IN );
+
+ return \@localfile;
+}
+
+###########################################
+# Saving files
+###########################################
+
+sub save_file
+{
+ my ($savefile, $savecontent) = @_;
+ if (-f $savefile) { unlink $savefile };
+ if (-f $savefile) { pre2par::exiter::exit_program("ERROR: Cannot delete existing file: $savefile", "save_file"); };
+ open( OUT, ">$savefile" );
+ print OUT @{$savecontent};
+ close( OUT);
+ if (! -f $savefile) { pre2par::exiter::exit_program("ERROR: Cannot write file: $savefile", "save_file"); }
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/globals.pm b/solenv/bin/modules/pre2par/globals.pm
new file mode 100644
index 0000000000..08d79b06b9
--- /dev/null
+++ b/solenv/bin/modules/pre2par/globals.pm
@@ -0,0 +1,51 @@
+#
+# 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 pre2par::globals;
+
+############################################
+# Global settings
+############################################
+
+BEGIN
+{
+ $prog="pre2par";
+
+ $prefilename = "";
+ $parfilename = "";
+ $langfilename = "";
+
+ @allitems = ("Installation", "ScpAction", "HelpText", "Directory", "DataCarrier", "StarRegistry", "File",
+ "Shortcut", "Custom", "Unixlink", "Procedure", "Module", "Profile", "ProfileItem",
+ "Folder", "FolderItem", "FolderItemProperty", "RegistryItem", "StarRegistryItem",
+ "WindowsCustomAction", "MergeModule");
+
+ $logging = 0;
+ $logfilename = "logfile.log"; # the default logfile name for global errors
+ @logfileinfo = ();
+
+ $plat = $^O;
+
+ $separator = "/";
+ $pathseparator = "\:";
+ $isunix = 1;
+ $iswin = 0;
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/language.pm b/solenv/bin/modules/pre2par/language.pm
new file mode 100644
index 0000000000..332bb985f8
--- /dev/null
+++ b/solenv/bin/modules/pre2par/language.pm
@@ -0,0 +1,135 @@
+#
+# 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 pre2par::language;
+
+use strict;
+
+##############################################################
+# 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;
+ $newstring = "\"" . $newstring . "\"";
+ last;
+ }
+ }
+
+ # defaulting to english!
+
+ 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;
+}
+
+############################################
+# collecting all replace variables
+# in a language file
+############################################
+
+sub get_all_replace_variables
+{
+ my ($langfile) = @_;
+
+ my %allvars = ();
+
+ for ( my $i = 0; $i <= $#{$langfile}; $i++ )
+ {
+ if ( ${$langfile}[$i] =~ /^\s*\[\s*(.*?)\s*\]\s*$/ )
+ {
+ my $variable = $1;
+# print "lang block '$variable'\n";
+ my @lang_block = ();
+ my $counter;
+
+ # Store the complete block in all languages for a specified variable
+ for ( $counter = $i + 1; $counter <= $#{$langfile}; $counter++ ) {
+ my $line = ${$langfile}[$counter];
+ last if ($line =~ /^s*\[/); # next decl.
+ push @lang_block, $line;
+ }
+# print "$variable = '@lang_block'\n";
+ $allvars{$variable} = \@lang_block;
+ $i = $counter - 1;
+ }
+ }
+
+ return \%allvars;
+}
+
+############################################
+# localizing the par file with the
+# corresponding language file
+############################################
+
+sub localize
+{
+ my ($parfile, $langfile) = @_;
+
+ my $replace_hash = get_all_replace_variables($langfile);
+
+ # parse lines of the form Name (st) = STR_NAME_MODULE_HELPPACK_OC;
+ # for variable substitution
+ my $langlinere = qr/^\s*\w+\s*\(([\w-]+)\)\s*\=\s*([\w-]+)\s*;/;
+ for ( my $i = 0; $i <= $#{$parfile}; $i++ )
+ {
+ my $oneline = ${$parfile}[$i];
+
+ if ( $oneline =~ $langlinere) {
+ my $language = $1; # can be "01" or "en" or "en-US" or ...
+ my $variable = $2;
+
+# print "line '$oneline' split to '$language' '$variable'\n";
+
+ if (defined $replace_hash->{$variable}) {
+ my $newstring = get_language_string_from_language_block($replace_hash->{$variable}, $language);
+ if ( $newstring eq "" ) { $newstring = "\"" . $variable . "\""; }
+
+ $oneline =~ s/$variable/$newstring/g;
+
+ ${$parfile}[$i] = $oneline;
+ }
+ }
+ }
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/parameter.pm b/solenv/bin/modules/pre2par/parameter.pm
new file mode 100644
index 0000000000..aa9fe3399a
--- /dev/null
+++ b/solenv/bin/modules/pre2par/parameter.pm
@@ -0,0 +1,144 @@
+#
+# 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 pre2par::parameter;
+
+use Cwd;
+use pre2par::globals;
+use pre2par::systemactions;
+
+############################################
+# Parameter Operations
+############################################
+
+sub usage
+{
+ print <<End;
+---------------------------------------------------------
+$pre2par::globals::prog
+The following parameter are needed:
+-s: path to the pre file
+-o: path to the par file
+-l: path to the ulf file (mlf or jlf file)
+-v: log process (optional)
+
+Example:
+
+perl pre2par.pl -l test.mlf -s readme.pre -o readme.par -v
+
+---------------------------------------------------------
+End
+ exit(-1);
+}
+
+#####################################
+# Reading parameter
+#####################################
+
+sub getparameter
+{
+ while ( $#ARGV >= 0 )
+ {
+ my $param = shift(@ARGV);
+
+ if ($param eq "-s") { $pre2par::globals::prefilename = shift(@ARGV); }
+ elsif ($param eq "-o") { $pre2par::globals::parfilename = shift(@ARGV); }
+ elsif ($param eq "-l") { $pre2par::globals::langfilename = shift(@ARGV); }
+ elsif ($param eq "-v") { $pre2par::globals::logging = 1; }
+ else
+ {
+ print("\n*************************************\n");
+ print("Sorry, unknown parameter: $param");
+ print("\n*************************************\n");
+ usage();
+ exit(-1);
+ }
+ }
+}
+
+############################################
+# Controlling the fundamental parameter
+# (required for every process)
+############################################
+
+sub control_parameter
+{
+ if ($pre2par::globals::prefilename eq "")
+ {
+ print "\n************************************************\n";
+ print "Error: Name of the input file not set (-s)!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ if (!(-f $pre2par::globals::prefilename))
+ {
+ print "\n************************************************\n";
+ print "Error: Input file does not exist!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ if ($pre2par::globals::parfilename eq "")
+ {
+ print "\n************************************************\n";
+ print "Error: Name of the output file not set (-o)!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ if (!($pre2par::globals::prefilename =~ /\.pre\s*$/))
+ {
+ print "\n************************************************\n";
+ print "Error: Input file is no .pre file!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+
+ if (!($pre2par::globals::parfilename =~ /\.par\s*$/))
+ {
+ print "\n************************************************\n";
+ print "Error: Output file is no .par file!";
+ print "\n************************************************\n";
+ usage();
+ exit(-1);
+ }
+}
+
+#####################################
+# Writing parameter to shell
+#####################################
+
+sub outputparameter
+{
+ $pre2par::globals::logging ? ($logoption = " -v") : ($logoption = "");
+ print "\n$pre2par::globals::prog -l $pre2par::globals::langfilename -s $pre2par::globals::prefilename -o $pre2par::globals::parfilename$logoption\n";
+
+# print "\n********************************************************\n";
+# print "This is $pre2par::globals::prog, version 1.0\n";
+# print "Input file: $pre2par::globals::prefilename\n";
+# print "Output file: $pre2par::globals::parfilename\n";
+# print "********************************************************\n";
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/pathanalyzer.pm b/solenv/bin/modules/pre2par/pathanalyzer.pm
new file mode 100644
index 0000000000..4d2eb31ae0
--- /dev/null
+++ b/solenv/bin/modules/pre2par/pathanalyzer.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 pre2par::pathanalyzer;
+
+use pre2par::globals;
+
+###########################################
+# Path analyzer
+###########################################
+
+sub get_path_from_fullqualifiedname
+{
+ my ($longfilenameref) = @_;
+
+ if ( $$longfilenameref =~ /\Q$pre2par::globals::separator\E/ ) # Is there a separator in the path? Otherwise the path is empty.
+ {
+ if ( $$longfilenameref =~ /^\s*(\S.*\S\Q$pre2par::globals::separator\E)(\S.+?\S)/ )
+ {
+ $$longfilenameref = $1;
+ }
+ }
+ else
+ {
+ $$longfilenameref = ""; # there is no path
+ }
+}
+
+sub make_absolute_filename_to_relative_filename
+{
+ my ($longfilenameref) = @_;
+
+ if ( $pre2par::globals::isunix )
+ {
+ if ( $$longfilenameref =~ /^.*\/(?=\S)([^\/]+)(?<=\S)/ )
+ {
+ $$longfilenameref = $1;
+ }
+ }
+
+ if ( $pre2par::globals::iswin )
+ {
+ # Either '/' or '\'.
+ if ( $$longfilenameref =~ /^.*[\/\\](?=\S)([^\/\\]+)(?<=\S)/ )
+ {
+ $$longfilenameref = $1;
+ }
+ }
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/remover.pm b/solenv/bin/modules/pre2par/remover.pm
new file mode 100644
index 0000000000..029d09a4e4
--- /dev/null
+++ b/solenv/bin/modules/pre2par/remover.pm
@@ -0,0 +1,34 @@
+#
+# 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 pre2par::remover;
+
+############################################
+# Remover
+############################################
+
+sub remove_leading_and_ending_whitespaces
+{
+ my ( $stringref ) = @_;
+
+ $$stringref =~ s/^\s*//g;
+ $$stringref =~ s/\s*$//g;
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/systemactions.pm b/solenv/bin/modules/pre2par/systemactions.pm
new file mode 100644
index 0000000000..7848b64950
--- /dev/null
+++ b/solenv/bin/modules/pre2par/systemactions.pm
@@ -0,0 +1,81 @@
+#
+# 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 pre2par::systemactions;
+
+use File::Copy;
+use pre2par::exiter;
+use pre2par::globals;
+
+######################################################
+# Creating a new directory
+######################################################
+
+sub create_directory
+{
+ my ($directory) = @_;
+
+ my $returnvalue = 1;
+ my $infoline = "";
+
+ if ($directory eq "" )
+ {
+ return 0;
+ }
+
+ if (!(-d $directory))
+ {
+ $returnvalue = mkdir($directory, 0775);
+
+ if ($returnvalue)
+ {
+ $infoline = "Created directory: $directory\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+
+ if ($pre2par::globals::isunix)
+ {
+ my $localcall = "chmod 775 $directory \>\/dev\/null 2\>\&1";
+ system($localcall);
+ }
+ }
+ 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.
+
+ if (!(-d $directory))
+ {
+ pre2par::exiter::exit_program("Error: Could not create directory: $directory", "create_directory");
+ }
+ else
+ {
+ $infoline = "\nAnother process created this directory in exactly this moment :-) : $directory\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+ }
+ }
+ }
+ else
+ {
+ $infoline = "\nAlready existing directory, did not create: $directory\n";
+ push(@pre2par::globals::logfileinfo, $infoline);
+ }
+}
+
+1;
diff --git a/solenv/bin/modules/pre2par/work.pm b/solenv/bin/modules/pre2par/work.pm
new file mode 100644
index 0000000000..6ee87ec378
--- /dev/null
+++ b/solenv/bin/modules/pre2par/work.pm
@@ -0,0 +1,297 @@
+#
+# 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 pre2par::work;
+
+use pre2par::exiter;
+use pre2par::remover;
+use pre2par::pathanalyzer;
+
+############################################
+# pre2par working module
+############################################
+
+############################################
+# procedure to split a line, that contains
+# more than one par file lines
+############################################
+
+sub split_line
+{
+ my ($line, $parfile) = @_;
+
+ while ( $line =~ /^((?:[^"]|\"(?:[^"\\]|\\.)*\")*?\;\s+)\s*(.*)$/ )
+ {
+ my $oneline = $1;
+ $line = $2;
+ pre2par::remover::remove_leading_and_ending_whitespaces(\$oneline);
+ $oneline = $oneline . "\n";
+ push(@{$parfile}, $oneline);
+
+ if ( $line =~ /^\s*End\s+(\w+.*$)/i )
+ {
+ $line = $1;
+ push(@{$parfile}, "End\n\n");
+ }
+ }
+
+ # the last line
+
+ pre2par::remover::remove_leading_and_ending_whitespaces(\$line);
+ $line = $line . "\n";
+ push(@{$parfile}, $line);
+
+ if ( $line =~ /^\s*End\s*$/i ) { push(@{$parfile}, "\n"); }
+}
+
+###################################################################
+# Preprocessing the pre file to split all lines with semicolon
+###################################################################
+
+sub preprocess_macros
+{
+ my ($prefile) = @_;
+
+ my @newprefile = ();
+
+ for ( my $i = 0; $i <= $#{$prefile}; $i++ )
+ {
+ my $oneline = ${$prefile}[$i];
+ if ( $oneline =~ /\;\s*\w+/ )
+ {
+ split_line($oneline, \@newprefile);
+ }
+ else
+ {
+ push(@newprefile, $oneline);
+ }
+ }
+
+ return \@newprefile;
+}
+
+############################################
+# main working procedure
+############################################
+
+sub convert
+{
+ my ($prefile) = @_;
+
+ my @parfile = ();
+
+ my $iscodesection = 0;
+ my $ismultiliner = 0;
+ my $globalline = "";
+
+ # Preprocessing the pre file to split all lines with semicolon
+ $prefile = preprocess_macros($prefile);
+
+ for ( my $i = 0; $i <= $#{$prefile}; $i++ )
+ {
+ my $oneline = ${$prefile}[$i];
+
+ if ($iscodesection)
+ {
+ if ( $oneline =~ /^\s*\}\;\s*$/ )
+ {
+ $iscodesection = 0;
+ }
+ else # nothing to do for code inside a code section
+ {
+ push(@parfile, $oneline);
+ next;
+ }
+ }
+
+ if ( $oneline =~ /^\s*$/ ) { next; }
+
+ if ( $oneline =~ /^\s*Code\s+\=\s+\{/ )
+ {
+ $iscodesection = 1;
+ }
+
+ pre2par::remover::remove_leading_and_ending_whitespaces(\$oneline);
+
+ my $insertemptyline = 0;
+
+ if ( $oneline =~ /^\s*End\s*$/i ) { $insertemptyline = 1; }
+
+ # Sometimes the complete file is in one line, then the gid line has to be separated
+
+ if ( $oneline =~ /^\s*(\w+\s+\w+)\s+(\w+\s+\=.*$)/ ) # three words before the equal sign
+ {
+ my $gidline = $1;
+ $oneline = $2;
+ $gidline = $gidline . "\n";
+
+ push(@parfile, $gidline);
+ }
+
+ if ( $oneline =~ /\;\s*\w+/ )
+ {
+ split_line($oneline, \@parfile);
+ next;
+ }
+
+ # searching for lines with brackets, like Customs = { ..., which can be parted above several lines
+
+ if ( $oneline =~ /^\s*\w+\s+\=\s*\(.*\)\s*\;\s*$/ ) # only one line
+ {
+ if (( ! ( $oneline =~ /^\s*Assignment\d+\s*\=/ )) && ( ! ( $oneline =~ /^\s*PatchAssignment\d+\s*\=/ )))
+ {
+ $oneline =~ s/\s//g; # removing whitespaces in lists
+ $oneline =~ s/\=/\ \=\ /; # adding whitespace around equals sign
+ }
+ }
+
+ if ( $oneline =~ /^\s*\w+\s+\=\s*$/ )
+ {
+ $oneline =~ s/\s*$//;
+ pre2par::exiter::exit_program("Error: Illegal syntax, no line break after eqals sign allowed. Line: \"$oneline\"", "convert");
+ }
+
+ if (( $oneline =~ /^\s*\w+\s+\=\s*\(/ ) && (!( $oneline =~ /\)\s*\;\s*$/ ))) # several lines
+ {
+ $ismultiliner = 1;
+ $oneline =~ s/\s//g;
+ $globalline .= $oneline;
+ next; # not including yet
+ }
+
+ if ( $ismultiliner )
+ {
+ $oneline =~ s/\s//g;
+ $globalline .= $oneline;
+
+ if ( $oneline =~ /\)\s*\;\s*$/ ) { $ismultiliner = 0; }
+
+ if (! ( $ismultiliner ))
+ {
+ $globalline =~ s/\=/\ \=\ /; # adding whitespace around equals sign
+ $globalline .= "\n";
+ push(@parfile, $globalline);
+ $globalline = "";
+ }
+
+ next;
+ }
+
+ $oneline = $oneline . "\n";
+
+ $oneline =~ s/\s*\=\s*/ \= /; # nice, to have only one whitespace around equal signs
+
+ # Concatenate adjacent string literals:
+ while ($oneline =~
+ s/^((?:[^"]*
+ \"(?:[^\\"]|\\.)*\"
+ (?:[^"]*[^[:blank:]"][^"]*\"(?:[^\\"]|\\.)*\")*)*
+ [^"]*
+ \"(?:[^\\"]|\\.)*)
+ \"[[:blank:]]*\"
+ ((?:[^\\"]|\\.)*\")
+ /\1\2/x)
+ {}
+
+ push(@parfile, $oneline);
+
+ if ($insertemptyline) { push(@parfile, "\n"); }
+
+ }
+
+ return \@parfile;
+}
+
+############################################
+# formatting the par file
+############################################
+
+sub formatter
+{
+ my ($parfile) = @_;
+
+ my $iscodesection = 0;
+
+ my $tabcounter = 0;
+ my $isinsideitem = 0;
+ my $currentitem;
+
+ for ( my $i = 0; $i <= $#{$parfile}; $i++ )
+ {
+ my $oneline = ${$parfile}[$i];
+ my $isitemline = 0;
+
+ if (! $isinsideitem )
+ {
+ for ( my $j = 0; $j <= $#pre2par::globals::allitems; $j++ )
+ {
+ if ( $oneline =~ /^\s*$pre2par::globals::allitems[$j]\s+\w+\s*$/ )
+ {
+ $currentitem = $pre2par::globals::allitems[$j];
+ $isitemline = 1;
+ $isinsideitem = 1;
+ $tabcounter = 0;
+ last;
+ }
+ }
+ }
+
+ if ( $isitemline )
+ {
+ next; # nothing to do
+ }
+
+ if ( $oneline =~ /^\s*end\s*$/i )
+ {
+ $isinsideitem = 0;
+ $tabcounter--;
+ }
+
+ if ( $isinsideitem )
+ {
+ $oneline = "\t" . $oneline;
+ ${$parfile}[$i] = $oneline;
+ }
+ }
+}
+
+############################################
+# Checking that the pre file has content
+############################################
+
+sub check_content
+{
+ my ($filecontent, $filename) = @_;
+
+ if ( $#{$filecontent} < 0 ) { pre2par::exiter::exit_program("Error: $filename has no content!", "check_content"); }
+}
+
+############################################
+# Checking content of par files.
+# Currently only size.
+############################################
+
+sub diff_content
+{
+ my ($content1, $content2, $filename) = @_;
+
+ if ( $#{$content1} != $#{$content2} ) { pre2par::exiter::exit_program("Error: $filename was not saved correctly!", "diff_content"); }
+}
+
+1;
diff --git a/solenv/bin/modules/t/installer-packagelist.t b/solenv/bin/modules/t/installer-packagelist.t
new file mode 100644
index 0000000000..c51683b14c
--- /dev/null
+++ b/solenv/bin/modules/t/installer-packagelist.t
@@ -0,0 +1,40 @@
+#
+# 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/.
+#
+
+use strict;
+use warnings;
+
+use lib '.';
+
+use Test::More;
+
+BEGIN {
+ use_ok('installer::packagelist');
+}
+
+my @packagemodules = (
+ { allmodules => [qw(a b c d)] },
+ { allmodules => [qw(a b c)] },
+ { allmodules => [qw(e f g)] },
+ { allmodules => [qw(h)] },
+ { allmodules => [qw(a b g)] },
+);
+
+my @expected_packagemodules = (
+ { allmodules => [qw(d)] },
+ { allmodules => [qw(c)] },
+ { allmodules => [qw(e f)] },
+ { allmodules => [qw(h)] },
+ { allmodules => [qw(a b g)] },
+);
+
+installer::packagelist::remove_multiple_modules_packages(\@packagemodules);
+
+is_deeply(\@packagemodules, \@expected_packagemodules);
+
+done_testing();
diff --git a/solenv/bin/modules/t/installer-profiles.t b/solenv/bin/modules/t/installer-profiles.t
new file mode 100644
index 0000000000..7817925986
--- /dev/null
+++ b/solenv/bin/modules/t/installer-profiles.t
@@ -0,0 +1,43 @@
+#
+# 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/.
+#
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use lib '.';
+
+use installer::profiles;
+
+my @input = map { "$_\n" } split "\n", <<'END';
+ [foo]
+1
+NOT SEEN
+ [bar]
+3
+ [foo]
+2
+[bar]
+4
+END
+
+my @expected = map { "$_\n" } split "\n", <<'END';
+[foo]
+1
+2
+[bar]
+3
+4
+END
+
+my $result = installer::profiles::sorting_profile(\@input);
+
+is_deeply($result, \@expected);
+
+done_testing();
diff --git a/solenv/bin/modules/t/installer-scpzipfiles.t b/solenv/bin/modules/t/installer-scpzipfiles.t
new file mode 100644
index 0000000000..656e15f658
--- /dev/null
+++ b/solenv/bin/modules/t/installer-scpzipfiles.t
@@ -0,0 +1,54 @@
+#
+# 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/.
+#
+
+use strict;
+use warnings;
+
+use lib '.';
+
+use Test::More;
+
+use installer::scpzipfiles;
+
+my $vars = { foo => "bar" };
+
+my %lines;
+my $i = 0;
+while (<DATA>) {
+ push @{ $lines{$i++ % 3} }, $_;
+}
+
+my @file1 = @{ $lines{0} };
+my @file2 = @{ $lines{0} };
+
+# FIXME - Some of the files acted on by these methods contain variables
+# of the form "${foo}" which are currently ignored - but if "foo" was ever
+# added to the product list variables, they would suddenly start to be
+# replaced.
+#
+# We ought to come up with a better escaping mechanism, and change those
+# files to use it...
+
+installer::scpzipfiles::replace_all_ziplistvariables_in_file(\@file1, $vars);
+installer::scpzipfiles::replace_all_ziplistvariables_in_rtffile(\@file2, $vars);
+
+is_deeply(\@file1, $lines{1}, 'replace_all_ziplistvariables_in_file works');
+is_deeply(\@file2, $lines{2}, 'replace_all_ziplistvariables_in_rtffile works');
+
+done_testing();
+
+__DATA__
+This is a test
+This is a test
+This is a test
+A test of ${foo} replacement ${foo} but not ${bar}.
+A test of bar replacement bar but not ${bar}.
+A test of ${foo} replacement ${foo} but not ${bar}.
+A test of RTF $\{foo\} replacement $\{foo\} but not $\{bar\} or ${bar}.
+A test of RTF $\{foo\} replacement $\{foo\} but not $\{bar\} or ${bar}.
+A test of RTF bar replacement bar but not $\{bar\} or ${bar}.
diff --git a/solenv/bin/modules/t/installer-setupscript.t b/solenv/bin/modules/t/installer-setupscript.t
new file mode 100644
index 0000000000..2994288e2a
--- /dev/null
+++ b/solenv/bin/modules/t/installer-setupscript.t
@@ -0,0 +1,58 @@
+#
+# 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/.
+#
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use lib '.';
+
+BEGIN {
+ use_ok('installer::setupscript', 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
+ ));
+}
+
+my @folders = (
+ { gid => '123' },
+ { gid => 'PREDEFINED_FOO' },
+ { gid => 'PREDEFINED_BAR' },
+);
+
+my @folderitems = (
+ { FolderID => 'PREDEFINED_AUTOSTART' },
+ { FolderID => 'PREDEFINED_BAR' },
+ { FolderID => '456' },
+);
+
+my @expected_folders = (
+ { gid => '123' },
+ { gid => 'PREDEFINED_FOO' },
+ { gid => 'PREDEFINED_BAR' },
+ {
+ ismultilingual => 0,
+ Name => "",
+ gid => 'PREDEFINED_AUTOSTART'
+ },
+);
+
+add_predefined_folder(\@folderitems, \@folders);
+
+is_deeply(\@folders, \@expected_folders);
+
+done_testing();
diff --git a/solenv/bin/native-code.py b/solenv/bin/native-code.py
new file mode 100755
index 0000000000..955317ebc0
--- /dev/null
+++ b/solenv/bin/native-code.py
@@ -0,0 +1,969 @@
+#!/usr/bin/env python3
+# 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/.
+
+from optparse import OptionParser
+
+import re
+import sys
+import xml.etree.ElementTree as ET
+
+# foo_component_getFactory functions are split into groups, so that you could
+# choose e.g. 'core' and 'writer' functionality and through factory_map,
+# relevant function sections will be referenced in lo_get_factory_map().
+# That prevents garbage collector to ignore them as unused.
+
+# The same groups are used for constructor based implementations
+# referenced in lo_get_constructor_map().
+
+core_factory_list = [
+ ("libi18npoollo.a", "i18npool_component_getFactory"),
+ ("libvcllo.a", "vcl_component_getFactory"),
+ ("libsvtlo.a", "svt_component_getFactory"),
+ ]
+
+core_constructor_list = [
+# animations/source/animcore/animcore.component
+ "com_sun_star_animations_AnimatePhysics_get_implementation",
+# basic/util/sb.component
+ ("com_sun_star_comp_sfx2_DialogLibraryContainer_get_implementation","#if HAVE_FEATURE_SCRIPTING"),
+ ("com_sun_star_comp_sfx2_ScriptLibraryContainer_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+# UnoControls/util/ctl.component
+ "stardiv_UnoControls_FrameControl_get_implementation",
+ "stardiv_UnoControls_ProgressBar_get_implementation",
+ "stardiv_UnoControls_ProgressMonitor_get_implementation",
+ "stardiv_UnoControls_StatusIndicator_get_implementation",
+# canvas/source/factory/canvasfactory.component
+ "com_sun_star_comp_rendering_CanvasFactory_get_implementation",
+# canvas/source/vcl/vclcanvas.component
+ "com_sun_star_comp_rendering_Canvas_VCL_get_implementation",
+ "com_sun_star_comp_rendering_SpriteCanvas_VCL_get_implementation",
+# chart2/source/chartcore.component
+ "com_sun_star_chart2_ExponentialScaling_get_implementation",
+ "com_sun_star_chart2_LinearScaling_get_implementation",
+ "com_sun_star_chart2_LogarithmicScaling_get_implementation",
+ "com_sun_star_chart2_PowerScaling_get_implementation",
+ "com_sun_star_comp_chart_AreaChartType_get_implementation",
+ "com_sun_star_comp_chart_BarChartType_get_implementation",
+ "com_sun_star_comp_chart_BubbleChartType_get_implementation",
+ "com_sun_star_comp_chart_CachedDataSequence_get_implementation",
+ "com_sun_star_comp_chart_CandleStickChartType_get_implementation",
+ "com_sun_star_comp_chart_ChartTypeManager_get_implementation",
+ "com_sun_star_comp_chart_ColumnChartType_get_implementation",
+ "com_sun_star_comp_chart_DataSeries_get_implementation",
+ "com_sun_star_comp_chart_DataSource_get_implementation",
+ "com_sun_star_comp_chart_FilledNetChartType_get_implementation",
+ "com_sun_star_comp_chart_FormattedString_get_implementation",
+ "com_sun_star_comp_chart_InternalDataProvider_get_implementation",
+ "com_sun_star_comp_chart_LineChartType_get_implementation",
+ "com_sun_star_comp_chart_NetChartType_get_implementation",
+ "com_sun_star_comp_chart_PieChartType_get_implementation",
+ "com_sun_star_comp_chart_ScatterChartType_get_implementation",
+ "com_sun_star_comp_chart2_Axis_get_implementation",
+ "com_sun_star_comp_chart2_CartesianCoordinateSystem2d_get_implementation",
+ "com_sun_star_comp_chart2_CartesianCoordinateSystem3d_get_implementation",
+ "com_sun_star_comp_chart2_ChartModel_get_implementation",
+ "com_sun_star_comp_chart2_ChartView_get_implementation",
+ "com_sun_star_comp_chart2_ConfigDefaultColorScheme_get_implementation",
+ "com_sun_star_comp_chart2_Diagram_get_implementation",
+ "com_sun_star_comp_chart2_ErrorBar_get_implementation",
+ "com_sun_star_comp_chart2_ExponentialRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_GridProperties_get_implementation",
+ "com_sun_star_comp_chart2_LabeledDataSequence_get_implementation",
+ "com_sun_star_comp_chart2_Legend_get_implementation",
+ "com_sun_star_comp_chart2_LinearRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_LogarithmicRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_MeanValueRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_MovingAverageRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_PageBackground_get_implementation",
+ "com_sun_star_comp_chart2_PolarCoordinateSystem2d_get_implementation",
+ "com_sun_star_comp_chart2_PolarCoordinateSystem3d_get_implementation",
+ "com_sun_star_comp_chart2_PolynomialRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_PotentialRegressionCurve_get_implementation",
+ "com_sun_star_comp_chart2_RegressionEquation_get_implementation",
+ "com_sun_star_comp_chart2_Title_get_implementation",
+ "com_sun_star_comp_chart2_XMLFilter_get_implementation",
+# chart2/source/controller/chartcontroller.component
+ "com_sun_star_comp_chart2_ChartDocumentWrapper_get_implementation",
+ "com_sun_star_comp_chart2_ChartFrameLoader_get_implementation",
+ "com_sun_star_comp_chart2_WizardDialog_get_implementation",
+ "org_libreoffice_chart2_Chart2ToolboxController",
+ "org_libreoffice_comp_chart2_sidebar_ChartPanelFactory",
+# comphelper/util/comphelp.component
+ "com_sun_star_comp_MemoryStream",
+ "com_sun_star_comp_task_OfficeRestartManager",
+ "AnyCompareFactory_get_implementation",
+ "IndexedPropertyValuesContainer_get_implementation",
+ "NamedPropertyValuesContainer_get_implementation",
+ "com_sun_star_comp_comphelper_OPropertyBag",
+ "com_sun_star_comp_SequenceInputStreamService",
+ "com_sun_star_comp_SequenceOutputStreamService",
+ "com_sun_star_comp_util_OfficeInstallationDirectories",
+ "org_openoffice_comp_comphelper_EnumerableMap",
+# configmgr/source/configmgr.component
+ "com_sun_star_comp_configuration_ConfigurationProvider_get_implementation",
+ "com_sun_star_comp_configuration_ConfigurationRegistry_get_implementation",
+ "com_sun_star_comp_configuration_DefaultProvider_get_implementation",
+ "com_sun_star_comp_configuration_ReadOnlyAccess_get_implementation",
+ "com_sun_star_comp_configuration_ReadWriteAccess_get_implementation",
+ "com_sun_star_comp_configuration_Update_get_implementation",
+# connectivity/source/manager/sdbc2.component
+ ("connectivity_OSDBCDriverManager_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY"),
+# connectivity/source/drivers/dbase/dbase.component
+ ("connectivity_dbase_ODriver", "#if HAVE_FEATURE_DBCONNECTIVITY"),
+# cppcanvas/source/uno/mtfrenderer.component
+ "com_sun_star_comp_rendering_MtfRenderer_get_implementation",
+# cui/util/cui.component
+ "com_sun_star_cui_ColorPicker_get_implementation",
+# dbaccess/util/dba.component
+ "com_sun_star_comp_dba_DataAccessDescriptorFactory",
+ "com_sun_star_comp_dba_OCommandDefinition",
+ "com_sun_star_comp_dba_OComponentDefinition",
+ "com_sun_star_comp_dba_ODatabaseContext_get_implementation",
+ "com_sun_star_comp_dba_ODatabaseDocument",
+ "com_sun_star_comp_dba_ODatabaseSource",
+ "com_sun_star_comp_dba_ORowSet_get_implementation",
+# desktop/lokclipboard.component
+ ("desktop_LOKClipboard_get_implementation", "#ifndef IOS"),
+# drawinglayer/drawinglayer.component
+ "drawinglayer_XPrimitive2DRenderer",
+# embeddedobj/util/embobj.component
+ "embeddedobj_UNOEmbeddedObjectCreator_get_implementation",
+ "embeddedobj_OOoEmbeddedObjectFactory_get_implementation",
+ "embeddedobj_OOoSpecialEmbeddedObjectFactory_get_implementation",
+# emfio/emfio.component
+ "emfio_emfreader_XEmfParser_get_implementation",
+# eventattacher/source/evtatt.component
+ "eventattacher_EventAttacher",
+# extensions/source/logging/log.component
+ ("com_sun_star_comp_extensions_FileHandler", "#ifdef ANDROID"),
+ ("com_sun_star_comp_extensions_LoggerPool", "#ifdef ANDROID"),
+ ("com_sun_star_comp_extensions_PlainTextFormatter", "#ifdef ANDROID"),
+ ("com_sun_star_comp_extensions_SimpleTextFormatter", "#ifdef ANDROID"),
+# extensions/source/bibliography/bib.component
+ "extensions_BibliographyLoader_get_implementation",
+# filter/source/config/cache/filterconfig1.component
+ "filter_ConfigFlush_get_implementation",
+ "filter_TypeDetection_get_implementation",
+ "filter_FrameLoaderFactory_get_implementation",
+ "filter_FilterFactory_get_implementation",
+ "filter_ContentHandlerFactory_get_implementation",
+# filter/source/odfflatxml/odfflatxml.component
+ "filter_OdfFlatXml_get_implementation",
+# filter/source/pdf/pdffilter.component
+ "filter_PdfDecomposer_get_implementation",
+ "filter_PDFExportInteractionHandler_get_implementation",
+ "filter_PDFFilter_get_implementation",
+ "filter_PDFDialog_get_implementation",
+# filter/source/xmlfilterdetect/xmlfd.component
+ "filter_XMLFilterDetect_get_implementation",
+# filter/source/xmlfilteradaptor/xmlfa.component
+ "filter_XmlFilterAdaptor_get_implementation",
+# filter/source/storagefilterdetect/storagefd.component
+ "filter_StorageFilterDetect_get_implementation",
+# forms/util/frm.component
+ ("com_sun_star_comp_forms_FormOperations_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_comp_forms_ODatabaseForm_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_comp_forms_OFormattedFieldWrapper_ForcedFormatted_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_comp_form_ORichTextControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_comp_forms_ORichTextModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_comp_forms_OScrollBarModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_comp_forms_OSpinButtonModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_Model_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OButtonControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OButtonModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OCheckBoxControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OCheckBoxModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OComboBoxControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OComboBoxModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_ODateControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_ODateModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OEditControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OEditModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OFixedTextModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OFormsCollection_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OGridControlModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OGroupBoxModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OListBoxControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_OListBoxModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_ONumericModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_ONumericControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_ORadioButtonControl_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_ORadioButtonModel_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+ ("com_sun_star_form_XForms_get_implementation", "#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS"),
+# framework/util/fwk.component
+ "com_sun_star_comp_framework_AutoRecovery_get_implementation",
+ "com_sun_star_comp_framework_Desktop_get_implementation",
+ "com_sun_star_comp_framework_DocumentAcceleratorConfiguration_get_implementation",
+ "com_sun_star_comp_framework_Frame_get_implementation",
+ "com_sun_star_comp_framework_GlobalAcceleratorConfiguration_get_implementation",
+ "com_sun_star_comp_framework_JobExecutor_get_implementation",
+ "com_sun_star_comp_framework_jobs_JobDispatch_get_implementation",
+ "com_sun_star_comp_framework_LayoutManager_get_implementation",
+ "com_sun_star_comp_framework_ModuleManager_get_implementation",
+ "com_sun_star_comp_framework_ModuleUIConfigurationManager_get_implementation",
+ "com_sun_star_comp_framework_ModuleUIConfigurationManagerSupplier_get_implementation",
+ "com_sun_star_comp_framework_PathSettings_get_implementation",
+ "com_sun_star_comp_framework_PathSubstitution_get_implementation",
+ "com_sun_star_comp_framework_ObjectMenuController_get_implementation",
+ "com_sun_star_comp_framework_PopupMenuControllerFactory_get_implementation",
+ "com_sun_star_comp_framework_ControlMenuController_get_implementation",
+ "com_sun_star_comp_framework_ThesaurusMenuController_get_implementation",
+ "com_sun_star_comp_framework_ToolbarAsMenuController_get_implementation",
+ "com_sun_star_comp_framework_ResourceMenuController_get_implementation",
+ "com_sun_star_comp_framework_StatusIndicatorFactory_get_implementation",
+ "com_sun_star_comp_framework_TaskCreator_get_implementation",
+ "com_sun_star_comp_framework_ToolBarControllerFactory_get_implementation",
+ "com_sun_star_comp_framework_UIConfigurationManager_get_implementation",
+ "com_sun_star_comp_framework_UIElementFactoryManager_get_implementation",
+ "com_sun_star_comp_framework_URLTransformer_get_implementation",
+ "com_sun_star_comp_framework_WindowStateConfiguration_get_implementation",
+ "com_sun_star_comp_framework_ModuleAcceleratorConfiguration_get_implementation",
+ "org_apache_openoffice_comp_framework_ContextChangeEventMultiplexer_get_implementation",
+# i18npool/util/i18npool.component
+ "com_sun_star_i18n_BreakIterator_get_implementation",
+ "com_sun_star_i18n_BreakIterator_Unicode_get_implementation",
+ "com_sun_star_i18n_CharacterClassification_get_implementation",
+ "com_sun_star_i18n_CharacterClassification_Unicode_get_implementation",
+ "com_sun_star_i18n_Collator_get_implementation",
+ "com_sun_star_i18n_LocaleDataImpl_get_implementation",
+ "com_sun_star_i18n_NativeNumberSupplier_get_implementation",
+ "com_sun_star_i18n_NumberFormatCodeMapper_get_implementation",
+ "com_sun_star_i18n_Transliteration_get_implementation",
+ "com_sun_star_i18n_Transliteration_IGNORE_CASE_get_implementation",
+ "com_sun_star_i18n_Transliteration_IGNORE_KANA_get_implementation",
+ "com_sun_star_i18n_Transliteration_IGNORE_WIDTH_get_implementation",
+ "com_sun_star_text_DefaultNumberingProvider_get_implementation",
+ ("i18npool_BreakIterator_ja_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_BreakIterator_ko_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ko"),
+ ("i18npool_BreakIterator_th_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_th"),
+ ("i18npool_BreakIterator_zh_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ ("i18npool_BreakIterator_zh_TW_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ "i18npool_CalendarImpl_get_implementation",
+ "i18npool_Calendar_ROC_get_implementation",
+ "i18npool_Calendar_dangi_get_implementation",
+ "i18npool_Calendar_buddhist_get_implementation",
+ "i18npool_Calendar_gengou_get_implementation",
+ "i18npool_Calendar_gregorian_get_implementation",
+ "i18npool_Calendar_hanja_get_implementation",
+ "i18npool_Calendar_hanja_yoil_get_implementation",
+ "i18npool_Calendar_hijri_get_implementation",
+ "i18npool_Calendar_jewish_get_implementation",
+ "i18npool_ChapterCollator_get_implementation",
+ "i18npool_Collator_Unicode_get_implementation",
+ "i18npool_IndexEntrySupplier_get_implementation",
+ "i18npool_IndexEntrySupplier_Unicode_get_implementation",
+ "i18npool_IndexEntrySupplier_asian_get_implementation",
+ ("i18npool_IndexEntrySupplier_ja_phonetic_alphanumeric_first_by_syllable_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_IndexEntrySupplier_ja_phonetic_alphanumeric_first_by_consonant_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_IndexEntrySupplier_ja_phonetic_alphanumeric_last_by_syllable_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_IndexEntrySupplier_ja_phonetic_alphanumeric_last_by_consonant_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_InputSequenceChecker_hi_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_hi"),
+ ("i18npool_InputSequenceChecker_th_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_th"),
+ ("i18npool_TextConversion_ko_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ko"),
+ ("i18npool_TextConversion_zh_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ "i18npool_CharToNumEastIndic_ar_get_implementation",
+ ("i18npool_CharToNumFullwidth_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_CharToNumHangul_ko_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ko"),
+ "i18npool_CharToNumIndic_ar_get_implementation",
+ ("i18npool_CharToNumIndic_hi_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_hi"),
+ ("i18npool_CharToNumKanjiShort_ja_JP_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_CharToNumKanjiTraditional_ja_JP_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ja"),
+ ("i18npool_CharToNumLower_ko_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ko"),
+ ("i18npool_CharToNumUpper_ko_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_ko"),
+ ("i18npool_CharToNum_th_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_th"),
+ ("i18npool_NumToTextFullwidth_zh_CN_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ ("i18npool_NumToTextFullwidth_zh_TW_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ ("i18npool_NumToTextLower_zh_CN_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ ("i18npool_NumToTextLower_zh_TW_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ ("i18npool_NumToTextUpper_zh_CN_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+ ("i18npool_NumToTextUpper_zh_TW_get_implementation", "#if WITH_LOCALE_ALL || WITH_LOCALE_zh"),
+# i18nsearch/sourceh/search/i18nsearch.component
+ "i18npool_TextSearch_get_implementation",
+# io/source/io.component
+ "io_Pump_get_implementation",
+ "io_ODataInputStream_get_implementation",
+ "io_ODataOutputStream_get_implementation",
+ "io_OMarkableInputStream_get_implementation",
+ "io_OMarkableOutputStream_get_implementation",
+ "io_OObjectInputStream_get_implementation",
+ "io_OObjectOutputStream_get_implementation",
+ "io_OPipeImpl_get_implementation",
+ "io_OAcceptor_get_implementation",
+ "io_OConnector_get_implementation",
+ "io_OTextInputStream_get_implementation",
+ "io_OTextOutputStream_get_implementation",
+# linguistic/source/lng.component
+ "linguistic_ConvDicList_get_implementation",
+ "linguistic_DicList_get_implementation",
+ "linguistic_LinguProps_get_implementation",
+ "linguistic_LngSvcMgr_get_implementation",
+ "linguistic_GrammarCheckingIterator_get_implementation",
+# linguistic/source/spellcheck/MacOSXSpellMacOSXSpell.component
+ ("lingucomponent_MacSpellChecker_get_implementation", "#ifdef IOS"),
+# lingucomponent/source/spellcheck/languagetool/LanguageTool.component
+ ("lingucomponent_LanguageToolGrammarChecker_get_implementation", "#if !ENABLE_FUZZERS"),
+ ("lingucomponent_NumberText_get_implementation", "#if !ENABLE_FUZZERS"),
+# lingucomponent/source/thesaurus/libnth/lnth.component
+ "lingucomponent_Thesaurus_get_implementation",
+ "lingucomponent_SpellChecker_get_implementation",
+ "lingucomponent_LangGuess_get_implementation",
+ "lingucomponent_Hyphenator_get_implementation",
+# package/source/xstor/xstor.component
+ "package_OStorageFactory_get_implementation",
+# package/util/package2.component
+ "package_OZipFileAccess_get_implementation",
+ "package_ZipPackage_get_implementation",
+ "package_ManifestReader_get_implementation",
+ "package_ManifestWriter_get_implementation",
+# sax/source/expatwrap/expwrap.component
+ "com_sun_star_comp_extensions_xml_sax_FastParser_get_implementation",
+ "com_sun_star_comp_extensions_xml_sax_ParserExpat_get_implementation",
+ "com_sun_star_extensions_xml_sax_Writer_get_implementation",
+# scripting/util/scriptframe.component
+ ("scripting_BrowseNodeFactoryImpl_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("scripting_MasterScriptProvider_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("scripting_MasterScriptProviderFactory_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("scripting_ScriptingFrameworkURIHelper_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+# scripting/source/basprov/basprov.component
+ ("scripting_BasicProviderImpl_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+# sfx2/util/sfx.component
+ "SfxDocumentMetaData_get_implementation",
+ "com_sun_star_comp_office_FrameLoader_get_implementation",
+ "com_sun_star_comp_sfx2_AppDispatchProvider_get_implementation",
+ "com_sun_star_comp_sfx2_DocumentTemplates_get_implementation",
+ "com_sun_star_comp_sfx2_GlobalEventBroadcaster_get_implementation",
+# shell/source/backends/localebe/localebe1.component
+ "shell_LocaleBackend_get_implementation",
+# sot/util/sot.component
+ "com_sun_star_comp_embed_OLESimpleStorage",
+# stoc/source/inspect/introspection.component
+ "com_sun_star_comp_stoc_Introspection_get_implementation",
+# stoc/source/invocation_adapter/invocadapt.component
+ "stoc_invocation_adapter_get_implementation",
+# stoc/source/corereflection/reflection.component
+ "com_sun_star_comp_stoc_CoreReflection_get_implementation",
+# stoc/source/proxy_factory/proxyfac.component
+ "stoc_FactoryImpl_get_implementation",
+# stoc/util/stocservices.component
+ "com_sun_star_comp_stoc_OServiceManagerWrapper_get_implementation",
+ "com_sun_star_comp_stoc_TypeConverter_get_implementation",
+ "com_sun_star_comp_uri_ExternalUriReferenceTranslator_get_implementation",
+ "com_sun_star_comp_uri_UriReferenceFactory_get_implementation",
+ "com_sun_star_comp_uri_UriSchemeParser_vndDOTsunDOTstarDOTexpand_get_implementation",
+ "com_sun_star_comp_uri_UriSchemeParser_vndDOTsunDOTstarDOTscript_get_implementation",
+# starmath/util/sm.component
+ "Math_FormulaDocument_get_implementation",
+ "Math_XMLContentExporter_get_implementation",
+ "Math_XMLExporter_get_implementation",
+ "Math_XMLImporter_get_implementation",
+ "Math_XMLMetaExporter_get_implementation",
+ "Math_XMLOasisMetaExporter_get_implementation",
+ "Math_XMLOasisMetaImporter_get_implementation",
+ "Math_XMLOasisSettingsExporter_get_implementation",
+ "Math_XMLOasisSettingsImporter_get_implementation",
+ "Math_XMLSettingsExporter_get_implementation",
+ "com_sun_star_comp_Math_MathTypeFilter_get_implementation",
+# svl/source/fsstor/fsstorage.component
+ "svl_FSStorageFactory_get_implementation",
+# vcl/vcl.android.component
+ "com_sun_star_graphic_GraphicObject_get_implementation",
+ "com_sun_star_comp_graphic_GraphicMapper_get_implementation",
+ "com_sun_star_comp_graphic_GraphicProvider_get_implementation",
+ "com_sun_star_frame_VCLSessionManagerClient_get_implementation",
+ "vcl_FontIdentificator_get_implementation",
+# svgio/svgio.component
+ "svgio_XSvgParser_get_implementation",
+# svx/util/svx.component
+ "com_sun_star_comp_svx_CharacterSpacingToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_CTLToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_LineSpacingToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_NumberingToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_SmartTagMenuController_get_implementation",
+ "com_sun_star_comp_svx_UnderlineToolBoxControl_get_implementation",
+ "com_sun_star_drawing_EnhancedCustomShapeEngine_get_implementation",
+ "com_sun_star_drawing_SvxShapeCollection_get_implementation",
+ "com_sun_star_svx_FontHeightToolBoxController_get_implementation",
+ "org_apache_openoffice_comp_svx_sidebar_PanelFactory_get_implementation",
+# svx/util/svxcore.component
+ "com_sun_star_comp_graphic_PrimitiveFactory2D_get_implementation",
+ "com_sun_star_comp_Draw_GraphicExporter_get_implementation",
+ "com_sun_star_comp_svx_ColorToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_FontNameToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_FrameToolBoxControl_get_implementation",
+ "com_sun_star_comp_Svx_GraphicExportHelper_get_implementation",
+ "com_sun_star_comp_Svx_GraphicImportHelper_get_implementation",
+ "com_sun_star_comp_svx_LineEndToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_LineStyleToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_StyleToolBoxControl_get_implementation",
+ "com_sun_star_comp_svx_StylesPreviewToolBoxControl_get_implementation",
+# toolkit/util/tk.component
+ "com_sun_star_comp_embed_HatchWindowFactory_get_implementation",
+ "stardiv_Toolkit_StdTabController_get_implementation",
+ "stardiv_Toolkit_UnoButtonControl_get_implementation",
+ "stardiv_Toolkit_UnoCheckBoxControl_get_implementation",
+ "stardiv_Toolkit_UnoComboBoxControl_get_implementation",
+ "stardiv_Toolkit_UnoControlButtonModel_get_implementation",
+ "stardiv_Toolkit_UnoControlCheckBoxModel_get_implementation",
+ "stardiv_Toolkit_UnoControlComboBoxModel_get_implementation",
+ "stardiv_Toolkit_UnoControlContainer_get_implementation",
+ "stardiv_Toolkit_UnoControlContainerModel_get_implementation",
+ "stardiv_Toolkit_UnoControlDateFieldModel_get_implementation",
+ "stardiv_Toolkit_UnoControlDialogModel_get_implementation",
+ "stardiv_Toolkit_UnoControlFixedTextModel_get_implementation",
+ "stardiv_Toolkit_UnoControlFormattedFieldModel_get_implementation",
+ "stardiv_Toolkit_UnoControlGroupBoxModel_get_implementation",
+ "stardiv_Toolkit_UnoControlListBoxModel_get_implementation",
+ "stardiv_Toolkit_UnoControlNumericFieldModel_get_implementation",
+ "stardiv_Toolkit_UnoControlRadioButtonModel_get_implementation",
+ "stardiv_Toolkit_UnoControlScrollBarModel_get_implementation",
+ "stardiv_Toolkit_UnoDateFieldControl_get_implementation",
+ "stardiv_Toolkit_UnoGroupBoxControl_get_implementation",
+ "stardiv_Toolkit_UnoListBoxControl_get_implementation",
+ "stardiv_Toolkit_UnoNumericFieldControl_get_implementation",
+ "stardiv_Toolkit_UnoRadioButtonControl_get_implementation",
+ "stardiv_Toolkit_UnoSpinButtonModel_get_implementation",
+ "stardiv_Toolkit_VCLXPointer_get_implementation",
+ "stardiv_Toolkit_VCLXPopupMenu_get_implementation",
+ "stardiv_Toolkit_VCLXToolkit_get_implementation",
+# ucb/source/core/ucb1.component
+ "ucb_UcbCommandEnvironment_get_implementation",
+ "ucb_UcbContentProviderProxyFactory_get_implementation",
+ "ucb_UcbPropertiesManager_get_implementation",
+ "ucb_UcbStore_get_implementation",
+ "ucb_UniversalContentBroker_get_implementation",
+ "ucb_OFileAccess_get_implementation",
+# ucb/source/ucp/file/ucpfile1.component
+ "ucb_file_FileProvider_get_implementation",
+# ucb/source/ucp/expand/ucpexpand1.component
+ ("ucb_expand_ExpandContentProviderImpl_get_implementation", "#ifdef ANDROID"),
+# ucb/source/sorter/srtrs1.component
+ "ucb_SortedDynamicResultSetFactory_get_implementation",
+# ucb/source/tdoc/ucptdoc1.component
+ "ucb_tdoc_ContentProvider_get_implementation",
+ "ucb_tdoc_DocumentContentFactory_get_implementation",
+# ucb/source/ucp/ucphier1.component
+ "ucb_HierarchyContentProvider_get_implementation",
+ "ucb_HierarchyDataSource_get_implementation",
+# ucb/source/ucp/package/ucppkg1
+ "ucb_package_ContentProvider_get_implementation",
+# unotools/util/utl.component
+ "unotools_ServiceDocument_get_implementation",
+ "unotools_OTempFileService_get_implementation",
+# unoxml/source/rdf/unordf.component
+ "unoxml_rdfRepository_get_implementation",
+ "unoxml_CURI_get_implementation",
+ "unoxml_CLiteral_get_implementation",
+ "unoxml_CBlankNode_get_implementation",
+# unoxml/source/service/unoxml.component
+ "unoxml_CXPathAPI_get_implementation",
+ "unoxml_CSAXDocumentBuilder_get_implementation",
+ "unoxml_CDocumentBuilder_get_implementation",
+# uui/util/uui.component
+ "com_sun_star_comp_uui_UUIInteractionHandler_get_implementation",
+ "com_sun_star_comp_uui_UUIInteractionRequestStringResolver_get_implementation",
+# vcl/*.component
+ "dtrans_CMimeContentTypeFactory_get_implementation",
+ "vcl_SystemClipboard_get_implementation",
+# xmloff/source/transform/xof.component
+ "xmloff_XMLCalcContentImportOOO_get_implementation",
+ "xmloff_XMLCalcImportOOO_get_implementation",
+ "xmloff_XMLCalcMetaImportOOO_get_implementation",
+ "xmloff_XMLCalcSettingsImportOOO_get_implementation",
+ "xmloff_XMLCalcStylesImportOOO_get_implementation",
+ "xmloff_XMLChartContentImportOOO_get_implementation",
+ "xmloff_XMLChartImportOOO_get_implementation",
+ "xmloff_XMLChartStylesImportOOO_get_implementation",
+ "xmloff_XMLDrawContentImportOOO_get_implementation",
+ "xmloff_XMLDrawImportOOO_get_implementation",
+ "xmloff_XMLDrawMetaImportOOO_get_implementation",
+ "xmloff_XMLDrawSettingsImportOOO_get_implementation",
+ "xmloff_XMLDrawStylesImportOOO_get_implementation",
+ "xmloff_XMLImpressContentImportOOO_get_implementation",
+ "xmloff_XMLImpressImportOOO_get_implementation",
+ "xmloff_XMLImpressMetaImportOOO_get_implementation",
+ "xmloff_XMLImpressSettingsImportOOO_get_implementation",
+ "xmloff_XMLImpressStylesImportOOO_get_implementation",
+ "xmloff_XMLMathMetaImportOOO_get_implementation",
+ "xmloff_XMLMathSettingsImportOOO_get_implementation",
+ "xmloff_OOo2OasisTransformer_get_implementation",
+ "xmloff_Oasis2OOoTransformer_get_implementation",
+ "xmloff_XMLAutoTextEventImportOOO_get_implementation",
+ "xmloff_XMLWriterContentImportOOO_get_implementation",
+ "xmloff_XMLWriterImportOOO_get_implementation",
+ "xmloff_XMLWriterMetaImportOOO_get_implementation",
+ "xmloff_XMLWriterSettingsImportOOO_get_implementation",
+ "xmloff_XMLWriterStylesImportOOO_get_implementation",
+ "xmloff_XMLMetaImportOOO_get_implementation",
+# xmloff/util/xo.component
+ "XMLMetaExportComponent_get_implementation",
+ "XMLMetaExportOOo_get_implementation",
+ "XMLMetaImportComponent_get_implementation",
+ "XMLVersionListPersistence_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisImporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisExporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisStylesExporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisContentExporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisMetaExporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisMetaImporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisContentImporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisStylesImporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisSettingsExporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLOasisSettingsImporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisSettingsExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisMetaExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisContentExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisStylesExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisExporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisImporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisStylesImporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisContentImporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisMetaImporter_get_implementation",
+ "com_sun_star_comp_Draw_XMLOasisSettingsImporter_get_implementation",
+ "com_sun_star_comp_Xmloff_AnimationsImport",
+ "com_sun_star_comp_DrawingLayer_XMLExporter_get_implementation",
+ "com_sun_star_comp_Impress_XMLClipboardExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisImporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisMetaImporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisMetaExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisStylesExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisContentExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLStylesExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLContentExporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisStylesImporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisContentImporter_get_implementation",
+ "com_sun_star_comp_Chart_XMLOasisExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisAutotextEventsExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLAutotextEventsExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisAutotextEventsImporter_get_implementation",
+# xmlscript/util/xmlscript.component
+ "com_sun_star_comp_xml_input_SaxDocumentHandler_get_implementation",
+ "com_sun_star_comp_xmlscript_XMLBasicExporter",
+ "com_sun_star_comp_xmlscript_XMLOasisBasicExporter",
+# xmlsecurity/util/xmlsecurity.component
+ ("com_sun_star_security_CertificateContainer_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_security_DocumentDigitalSignatures_get_implementation", "#if HAVE_FEATURE_NSS"),
+# xmlsecurity/util/xsec_xmlsec.component
+ ("com_sun_star_xml_crypto_NSSInitializer_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_xml_crypto_SEInitializer_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_xml_security_SEInitializer_Gpg_get_implementation", "#if HAVE_FEATURE_GPGME"),
+ ("com_sun_star_xml_crypto_SecurityEnvironment_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_xml_wrapper_XMLDocumentWrapper_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_xml_wrapper_XMLElementWrapper_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_xml_crypto_XMLSecurityContext_get_implementation", "#if HAVE_FEATURE_NSS"),
+ ("com_sun_star_xml_crypto_XMLSignature_get_implementation", "#if HAVE_FEATURE_NSS"),
+# oox/util/oox.component
+ "com_sun_star_comp_oox_core_FastTokenHandler_get_implementation",
+ "com_sun_star_comp_oox_FormatDetector_get_implementation",
+ "com_sun_star_comp_oox_docprop_DocumentPropertiesImporter_get_implementation",
+ "com_sun_star_comp_oox_ppt_PowerPointImport_get_implementation",
+ "com_sun_star_comp_oox_crypto_StrongEncryptionDataSpace_get_implementation",
+ ]
+
+# edit group for apps, where you can edit documents
+edit_factory_list = [
+ ]
+
+edit_constructor_list = [
+# framework/util/fwk.component
+ "com_sun_star_comp_framework_GlobalAcceleratorConfiguration_get_implementation",
+ "com_sun_star_comp_framework_UICommandDescription_get_implementation",
+# i18npool/util/i18npool.component
+ "com_sun_star_i18n_InputSequenceChecker_get_implementation",
+ "com_sun_star_i18n_OrdinalSuffix_get_implementation",
+# sc/util/sc.component
+ "Calc_XMLOasisContentExporter_get_implementation",
+ "Calc_XMLOasisExporter_get_implementation",
+ "Calc_XMLOasisMetaExporter_get_implementation",
+ "Calc_XMLOasisSettingsExporter_get_implementation",
+ "Calc_XMLOasisStylesExporter_get_implementation",
+ "Calc_FilterOptionsDialog_get_implementation",
+# starmath/util/sm.component
+ "Math_XMLContentExporter_get_implementation",
+ "Math_XMLOasisMetaExporter_get_implementation",
+ "Math_XMLOasisSettingsExporter_get_implementation",
+ "Math_XMLImporter_get_implementation",
+ "Math_XMLOasisMetaImporter_get_implementation",
+ "Math_XMLOasisSettingsImporter_get_implementation",
+# starmath/util/smd.component
+ "math_FormatDetector_get_implementation",
+# sw/util/sw.component
+ "com_sun_star_comp_Writer_XMLOasisContentExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisMetaExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisSettingsExporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisStylesExporter_get_implementation",
+ "com_sun_star_comp_Writer_WriterModule_get_implementation",
+ "org_apache_openoffice_comp_sw_sidebar_SwPanelFactory_get_implementation",
+ ]
+
+# math
+math_factory_list = [
+ ]
+
+math_constructor_list = [
+# starmath/util/sm.component
+ "Math_XMLOasisMetaExporter_get_implementation",
+ "Math_XMLOasisSettingsExporter_get_implementation",
+ ]
+
+calc_factory_list = [
+ ]
+
+calc_constructor_list = [
+# avmedia/util/avmedia.component
+ ("com_sun_star_comp_framework_SoundHandler_get_implementation", "#if HAVE_FEATURE_AVMEDIA"),
+# sc/util/sc.component
+ "ScPanelFactory_get_implementation",
+ "Calc_SpreadsheetDocument_get_implementation",
+ "Calc_XMLOasisContentImporter_get_implementation",
+ "Calc_XMLOasisImporter_get_implementation",
+ "Calc_XMLOasisMetaImporter_get_implementation",
+ "Calc_XMLOasisSettingsImporter_get_implementation",
+ "Calc_XMLOasisStylesImporter_get_implementation",
+ "Calc_ScSpreadsheetSettings_get_implementation",
+# sc/util/scd.component
+ "com_sun_star_comp_calc_ExcelBiffFormatDetector_get_implementation",
+ "com_sun_star_comp_calc_FormatDetector_get_implementation",
+# sc/util/scfilt.component
+ "com_sun_star_comp_oox_xls_ExcelFilter_get_implementation",
+ "com_sun_star_comp_oox_xls_FormulaParser_get_implementation",
+# sc/util/vbaobj.component
+ ("Calc_ScVbaApplication_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("ScVbaEventsHelper_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("ScVbaGlobals_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("Calc_ScVbaHyperlink_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("ScVbaTextFrame_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("Calc_ScVbaWindow_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("Calc_ScVbaWorkbook_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("Calc_ScVbaWorksheet_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("Calc_ScVbaRange_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+# scaddins/source/analysis/analysis.component
+ "scaddins_AnalysisAddIn_get_implementation",
+# scaddins/source/datefunc/date.component
+ "scaddins_ScaDateAddIn_get_implementation",
+# scaddins/source/pricing/pricing.component
+ "scaddins_ScaPricingAddIn_get_implementation",
+# svl/util/svl.component
+ "com_sun_star_uno_util_numbers_SvNumberFormatsSupplierServiceObject_get_implementation",
+ "com_sun_star_uno_util_numbers_SvNumberFormatterServiceObject_get_implementation",
+# scripting/source/vbaevents/vbaevents.component
+ ("ooo_vba_VBAToOOEventDesc_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ("ooo_vba_EventListener_get_implementation", "#if HAVE_FEATURE_SCRIPTING"),
+ ]
+
+draw_factory_list = [
+ ]
+
+draw_constructor_list = [
+# animations/source/animcore/animcore.component
+ "com_sun_star_animations_AnimateColor_get_implementation",
+ "com_sun_star_animations_AnimateMotion_get_implementation",
+ "com_sun_star_animations_AnimateSet_get_implementation",
+ "com_sun_star_animations_AnimateTransform_get_implementation",
+ "com_sun_star_animations_Animate_get_implementation",
+ "com_sun_star_animations_Audio_get_implementation",
+ "com_sun_star_animations_Command_get_implementation",
+ "com_sun_star_animations_IterateContainer_get_implementation",
+ "com_sun_star_animations_ParallelTimeContainer_get_implementation",
+ "com_sun_star_animations_SequenceTimeContainer_get_implementation",
+ "com_sun_star_animations_TransitionFilter_get_implementation",
+# desktop/source/deployment/deployment.component
+ "com_sun_star_comp_deployment_ExtensionManager_get_implementation",
+ "com_sun_star_comp_deployment_PackageInformationProvider_get_implementation",
+ "com_sun_star_comp_deployment_PackageManagerFactory_get_implementation",
+ "com_sun_star_comp_deployment_ProgressLog_get_implementation",
+ "com_sun_star_comp_deployment_component_PackageRegistryBackend_get_implementation",
+ "com_sun_star_comp_deployment_configuration_PackageRegistryBackend_get_implementation",
+ "com_sun_star_comp_deployment_executable_PackageRegistryBackend_get_implementation",
+ "com_sun_star_comp_deployment_help_PackageRegistryBackend_get_implementation",
+ "com_sun_star_comp_deployment_script_PackageRegistryBackend_get_implementation",
+ "com_sun_star_comp_deployment_sfwk_PackageRegistryBackend_get_implementation",
+# embedded/source/msole/emboleobj.windows.component
+ "embeddedobj_OleEmbeddedObjectFactory_get_implementation",
+# filter/source/svg/svgfilter.component
+ "filter_SVGFilter_get_implementation",
+ "filter_SVGWriter_get_implementation",
+# sd/util/sd.component
+ "sd_DrawingDocument_get_implementation",
+ "com_sun_star_comp_Draw_DrawingModule_get_implementation",
+ "sd_PresentationDocument_get_implementation",
+ "com_sun_star_comp_Draw_PresenterHelper_get_implementation",
+ "com_sun_star_comp_Draw_PresenterPreviewCache_get_implementation",
+ "com_sun_star_comp_Draw_SlideRenderer_get_implementation",
+ "com_sun_star_comp_sd_InsertSlideController_get_implementation",
+ "com_sun_star_comp_sd_SlideLayoutController_get_implementation",
+ "com_sun_star_comp_sd_DisplayModeController_get_implementation",
+ "RandomAnimationNode_get_implementation",
+ "com_sun_star_comp_Draw_framework_ResourceID_get_implementation",
+ "org_openoffice_comp_Draw_framework_PanelFactory_get_implementation",
+ "css_comp_Impress_oox_PowerPointExport",
+# sd/util/sdd.component
+ "com_sun_star_comp_draw_FormatDetector_get_implementation",
+# writerperfect/source/draw/wpftdraw.component
+ "com_sun_star_comp_Draw_VisioImportFilter_get_implementation",
+# sdext/source/pdfimport/pdfimport.component
+ ("sdext_PDFIHybridAdaptor_get_implementation", "#if HAVE_FEATURE_PDFIMPORT"),
+ ("sdext_PDFIRawAdaptor_Writer_get_implementation", "#if HAVE_FEATURE_PDFIMPORT"),
+ ("sdext_PDFIRawAdaptor_Draw_get_implementation", "#if HAVE_FEATURE_PDFIMPORT"),
+ ("sdext_PDFIRawAdaptor_Impress_get_implementation", "#if HAVE_FEATURE_PDFIMPORT"),
+ ("sdext_PDFDetector_get_implementation", "#if HAVE_FEATURE_PDFIMPORT"),
+ ]
+
+writer_factory_list = [
+ ]
+
+writer_constructor_list = [
+# filter/source/textfilterdetect/textfd.component
+ "com_sun_star_comp_filters_PlainTextFilterDetect_get_implementation",
+# sw/util/sw.component
+ "Writer_SwTextDocument_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisContentImporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisImporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisMetaImporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisSettingsImporter_get_implementation",
+ "com_sun_star_comp_Writer_XMLOasisStylesImporter_get_implementation",
+ "com_sun_star_util_comp_FinalThreadManager_get_implementation",
+# sw/util/swd.component
+ "com_sun_star_comp_writer_FormatDetector_get_implementation",
+# sw/util/msword.component
+ "com_sun_star_comp_Writer_RtfExport_get_implementation",
+ "com_sun_star_comp_Writer_DocxExport_get_implementation",
+# writerfilter/util/writerfilter.component
+ "com_sun_star_comp_Writer_RtfFilter_get_implementation",
+ "com_sun_star_comp_Writer_WriterFilter_get_implementation",
+# writerperfect/source/writer/wpftwriter.component
+ "com_sun_star_comp_Writer_EPUBExportFilter_get_implementation",
+ ]
+
+factory_map = {
+ 'core' : core_factory_list,
+ 'edit' : edit_factory_list,
+ 'math' : math_factory_list,
+ 'calc' : calc_factory_list,
+ 'draw' : draw_factory_list,
+ 'writer' : writer_factory_list,
+ }
+
+constructor_map = {
+ 'core' : core_constructor_list,
+ 'edit' : edit_constructor_list,
+ 'math' : math_constructor_list,
+ 'calc' : calc_constructor_list,
+ 'draw' : draw_constructor_list,
+ 'writer' : writer_constructor_list,
+ }
+
+custom_widgets = [
+ 'NotebookbarTabControl',
+ 'NotebookbarToolBox',
+ ]
+
+def get_constructor_guard(constructor):
+ if type(full_constructor_map[constructor]) is bool:
+ return None
+ else:
+ return full_constructor_map[constructor]
+
+# instead of outputting native-code.cxx, reduce the services.rdb according to
+# the constraints, so that we can easily emulate what services do we need to
+# add for a fully functional file loading / saving / ...
+def limit_rdb(services_rdb, full_factory_map, full_constructor_map):
+ ET.register_namespace('','http://openoffice.org/2010/uno-components')
+ tree = ET.parse(services_rdb[0])
+ root = tree.getroot()
+
+ for component in root.findall('{http://openoffice.org/2010/uno-components}component'):
+ # direct
+ uri = component.get('uri')
+ component_name = None
+ if uri != None:
+ component_name = re.sub(r'^vnd.sun.star.expand:\$LO_LIB_DIR/([^.]*).so$', r'\1.a', uri)
+ if component_name in full_factory_map:
+ continue
+
+ # via a constructor - limit only to those we have
+ has_constructor = False
+ for implementation in component.findall('{http://openoffice.org/2010/uno-components}implementation'):
+ constructor = implementation.get('constructor')
+ if constructor in full_constructor_map:
+ has_constructor = True
+ else:
+ component.remove(implementation)
+
+ if not has_constructor:
+ root.remove(component)
+
+ tree.write(services_rdb[0] + '.out', xml_declaration = True, method = 'xml')
+
+
+# do the actual work
+opts = OptionParser()
+opts.add_option("-j", "--java-guard", action="store_true", help="include external java functions", dest="java", default=False)
+opts.add_option("-g", "--group", action="append", help="group of implementations to make available in application", dest="groups")
+opts.add_option("-r", "--limit-rdb", action="append", help="instead of outputting native-code.cxx, limit the services.rdb only to the services defined by the groups", dest="services")
+opts.add_option("-C", "--pure-c", action="store_true", help="do not print extern \"C\"", dest="pure_c", default=False)
+opts.add_option("-c", "--constructors", help="file with the list of constructors", dest="constructors_file")
+
+(options, args) = opts.parse_args()
+
+# dict of all the constructors that we need according to -g's
+full_constructor_map = {}
+if options.groups:
+ for constructor_group in options.groups:
+ for constructor in constructor_map[constructor_group]:
+ if type(constructor) is tuple:
+ full_constructor_map[constructor[0]] = constructor[1]
+ else:
+ full_constructor_map[constructor] = True
+
+if not options.groups and options.constructors_file:
+ options.groups = factory_map.keys()
+
+# dict of all the factories that we need according to -g's
+full_factory_map = {}
+if options.groups:
+ for factory_group in options.groups:
+ for entry in factory_map[factory_group]:
+ factory_guard = None
+ if len(entry) > 2:
+ factory_guard = entry[2]
+ map_entry = { 'function': entry[1], 'guard': factory_guard }
+ full_factory_map[entry[0]] = map_entry
+
+# are we only shuffling the services.rdb?
+if options.services:
+ limit_rdb(options.services, full_factory_map, full_constructor_map)
+ exit(0)
+
+if options.constructors_file:
+ with open(options.constructors_file, "r") as constructors:
+ for line in constructors:
+ full_constructor_map[line.strip()] = True
+
+print ("""/*
+ * This is a generated file. Do not edit.
+ *
+ * File generated by solenv/bin/native-code.py
+ */
+
+#include <config_crypto.h>
+#include <config_features.h>
+#include <config_fuzzers.h>
+#include <config_gpgme.h>
+#include <config_locales.h>
+#include <osl/detail/component-mapping.h>
+#include <string.h>
+
+""")
+if not options.pure_c:
+ print ("""extern "C" {""")
+
+for entry in sorted(full_factory_map.keys()):
+ factory_function = full_factory_map[entry]['function']
+ factory_guard = full_factory_map[entry]['guard']
+ if factory_guard:
+ print (factory_guard)
+ print('void * ' + factory_function + '( const char* , void* , void* );')
+ if factory_guard:
+ print ('#endif')
+
+print ('')
+for constructor in sorted(full_constructor_map.keys()):
+ constructor_guard = get_constructor_guard(constructor)
+ if constructor_guard:
+ print (constructor_guard)
+ print ('void * '+constructor+'( void *, void * );')
+ if constructor_guard:
+ print ('#endif')
+
+print ('')
+for entry in sorted(custom_widgets):
+ print ('void make' + entry + '();')
+print ('typedef void (*custom_widget_func)();')
+print ('#if !ENABLE_FUZZERS')
+print ('static struct { const char *name; custom_widget_func func; } custom_widgets[] = {')
+for entry in sorted(custom_widgets):
+ print (' { "make' + entry + '", make' + entry + ' },')
+print ('};')
+print ('#endif')
+print ('')
+print ("""
+custom_widget_func lo_get_custom_widget_func(const char* name)
+{
+#if ENABLE_FUZZERS
+ (void)name;
+ return nullptr;
+#else
+ for (size_t i = 0; i < sizeof(custom_widgets) / sizeof(custom_widgets[0]); i++)
+ if (strcmp(name, custom_widgets[i].name) == 0)
+ return custom_widgets[i].func;
+ return nullptr;
+#endif
+}
+""")
+
+print ("""
+const lib_to_factory_mapping *
+lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = {""")
+
+for entry in sorted(full_factory_map.keys()):
+ factory_function = full_factory_map[entry]['function']
+ factory_guard = full_factory_map[entry]['guard']
+ if factory_guard:
+ print (factory_guard)
+ print(' { "' + entry + '", ' + factory_function + ' },')
+ if factory_guard:
+ print ('#endif')
+
+print ("""
+ { 0, 0 }
+ };""")
+
+if options.java:
+ print ("""
+ extern void Java_org_libreoffice_kit_LibreOfficeKit_initializeNative();
+ volatile void *p = (void *) Java_org_libreoffice_kit_LibreOfficeKit_initializeNative;
+
+ extern void Java_org_libreoffice_kit_Office_getError();
+ p = (void *) Java_org_libreoffice_kit_Office_getError;
+
+ """)
+
+print ("""
+ return map;
+}
+
+const lib_to_constructor_mapping *
+lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = {""")
+
+for constructor in sorted(full_constructor_map.keys()):
+ constructor_guard = get_constructor_guard(constructor)
+ if constructor_guard:
+ print (constructor_guard)
+ print (' { "' +constructor+ '", ' +constructor+ ' },')
+ if constructor_guard:
+ print ('#endif')
+
+print ("""
+ { 0, 0 }
+ };
+
+ return map;
+}
+
+""")
+if not options.pure_c:
+ print("""}""")
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/solenv/bin/ooinstall b/solenv/bin/ooinstall
new file mode 100755
index 0000000000..71f10c7390
--- /dev/null
+++ b/solenv/bin/ooinstall
@@ -0,0 +1,118 @@
+#!/usr/bin/env perl
+#
+# 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 script has three uses:
+# 1. From the command line to install straight into a given directory:
+# bin/ooinstall /opt/Foo
+# 2. From the command line to link into a given directory:
+# bin/ooinstall -l /opt/FooLinked
+# 3. When packaging (called from package-ooo), to install to DESTDIR
+
+use File::Find;
+use File::Path qw(mkpath);
+use Cwd;
+
+$path = '';
+$strip = '';
+$is_windows = 0;
+my $tmp_dir;
+
+# FIXME: really we should hunt and parse / source the config_host.mk magic I guess.
+die "You need your environment setup right, eg. run make cmd cmd='ooinstall /path/to/install'" if (!defined $ENV{SRC_ROOT});
+
+if ($ENV{OS} eq 'WNT') {
+ $is_windows = 1;
+}
+
+if (defined($ENV{TMPDIR})) {
+ $tmp_dir = $ENV{TMPDIR};
+}
+if (!-d $tmp_dir) {die "Set TMPDIR!\n";}
+
+for $arg (@ARGV) {
+ if ($arg eq '-s' || $arg eq '--strip') {
+ $strip = "-strip";
+ } elsif ($arg eq '-h' || $arg eq '--help') {
+ $help = 1;
+ } else {
+ # Cwd::realpath does not work if the path does not exist
+ mkpath($ENV{DESTDIR} . $arg) unless -d $ENV{DESTDIR} . $arg;
+ $path = Cwd::realpath($ENV{DESTDIR} . $arg);
+ }
+}
+
+$help = 1 if $path eq '';
+
+if ($help) {
+ print "ooinstall [-s] <prefix to install to>\n";
+ print " -s/--strip - strip the installed binaries\n";
+ exit 1;
+}
+
+my $BUILD=$ENV{LIBO_VERSION_PATCH};
+$ENV{OUT} = "../FIXME";
+$ENV{LOCAL_OUT} = $ENV{OUT};
+$ENV{LOCAL_COMMON_OUT} = $ENV{OUT};
+
+my @larr;
+$langs=$ENV{WITH_LANG_LIST};
+@larr = grep { $_ ne '' } split(/ /, $langs);
+$langs = join (",", @larr);
+
+$destdir='';
+if (defined $ENV{DESTDIR} &&
+ $ENV{DESTDIR} ne "" ) {
+ $destdir = "-destdir \"$ENV{DESTDIR}\"";
+}
+
+$msi='';
+if ($is_windows) {
+ $msi = "-msitemplate $ENV{SRC_ROOT}/workdir/CustomTarget/instsetoo_native/install/msi_templates " .
+ "-msilanguage $ENV{SRC_ROOT}/workdir/CustomTarget/instsetoo_native/install/win_ulffiles";
+}
+
+# FIXME: a better solution would be to fix installer to deal with broken symlinks
+# make distro-pack-install shuffle with the SDK installation to make it LSB compliant
+# it creates symlinks from the original path to /usr/share; they are not valid with $DESTDIR
+# and potential second ooinstall call fails with strange error messages if the old tree is still there
+if ($destdir && "$ENV{DESTDIR}" ne "/" && -d "$ENV{DESTDIR}") {
+ print "Cleaning destdir...\n";
+ system ("rm -rf \"$ENV{DESTDIR}\"") && die "Failed to clean up destdir: $!";
+}
+
+print "Running LibreOffice installer\n";
+
+my $PRODUCTNAME_no_spaces = $ENV{PRODUCTNAME};
+$PRODUCTNAME_no_spaces =~ s/ //g;
+
+system ("cd $ENV{SRC_ROOT}/instsetoo_native/util ; " .
+ "perl " .
+ (scalar keys(%DB::sub) ? "-d " : "") .
+ "-w $ENV{SRCDIR}/solenv/bin/make_installer.pl " .
+ "-f $ENV{BUILDDIR}/instsetoo_native/util/openoffice.lst -l $langs -p $PRODUCTNAME_no_spaces " .
+ "-u $tmp_dir " .
+ "-buildid $BUILD $destdir $strip $msi " .
+ "-simple $path") && die "Failed to install: $!";
+
+if ($ENV{BUILD_TYPE} =~ m/ODK/) {
+ print "Running SDK installer\n";
+ system ("cd $ENV{SRC_ROOT}/instsetoo_native/util ; " .
+ "perl -w $ENV{SRCDIR}/solenv/bin/make_installer.pl " .
+ "-f $ENV{BUILDDIR}/instsetoo_native/util/openoffice.lst -l en-US -p LibreOffice_SDK " .
+ "-u $tmp_dir " .
+ "-buildid $BUILD $destdir $strip $msi " .
+ "-simple $path") && die "Failed to install: $!";
+}
+print "Installer finished\n";
+
+# Local Variables:
+# cperl-indent-level: 4
+# indent-tabs-mode: nil
+# End:
+# vim:set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/solenv/bin/optionalimplementations.xslt b/solenv/bin/optionalimplementations.xslt
new file mode 100644
index 0000000000..fa8ca5b1c7
--- /dev/null
+++ b/solenv/bin/optionalimplementations.xslt
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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 "misuses" xsl:message to dump all optional components
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns="http://openoffice.org/2010/uno-components"
+ xmlns:uc="http://openoffice.org/2010/uno-components">
+<xsl:template match="uc:component">
+ <xsl:for-each select="uc:implementation">
+ <xsl:if test="uc:optional">
+ <xsl:message><xsl:value-of select="@name"/></xsl:message>
+ </xsl:if>
+ </xsl:for-each>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/solenv/bin/pack_images.py b/solenv/bin/pack_images.py
new file mode 100644
index 0000000000..5ccc194e63
--- /dev/null
+++ b/solenv/bin/pack_images.py
@@ -0,0 +1,612 @@
+# -*- coding: utf-8 -*-
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
+#
+# 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/.
+#
+""" Pack images into archives. """
+
+from __future__ import with_statement
+
+import argparse
+from collections import OrderedDict
+import contextlib
+import logging
+import os
+import shutil
+import sys
+import tempfile
+import zipfile
+
+
+logging.basicConfig(format='%(message)s')
+LOGGER = logging.getLogger()
+
+
+def main(args):
+ """ Main function. """
+
+ tmp_output_file = "%s.%d.tmp" % (args.output_file, os.getpid())
+
+ # Sanity checks
+ if not os.path.exists(args.imagelist_file):
+ LOGGER.error("imagelist_file '%s' doesn't exists", args.imagelist_file)
+ sys.exit(2)
+
+ if args.links_file is not None and not os.path.exists(args.links_file):
+ LOGGER.error("link_file '%s' doesn't exists", args.links_file)
+ sys.exit(2)
+
+ out_path = os.path.dirname(args.output_file)
+ for path in (out_path, args.global_path, args.module_path):
+ if not os.path.exists(path):
+ LOGGER.error("Path '%s' doesn't exists", path)
+ sys.exit(2)
+ if not os.access(path, os.X_OK):
+ LOGGER.error("Unable to search path %s", path)
+ sys.exit(2)
+
+ if not os.access(out_path, os.W_OK):
+ LOGGER.error("Unable to write into path %s", out_path)
+
+ custom_paths = []
+ for path in args.custom_path:
+ if not os.path.exists(path):
+ LOGGER.warning("Skipping non-existing path: %s", path)
+ continue
+ elif not os.access(path, os.X_OK):
+ LOGGER.error("Unable to search path %s", path)
+ sys.exit(2)
+
+ custom_paths.append(path)
+
+ imagelist_filenames = get_imagelist_filenames(args.imagelist_file)
+ global_image_list, module_image_list = parse_image_list(imagelist_filenames)
+ custom_image_list = find_custom(custom_paths)
+
+ links = {}
+ if args.links_file is not None:
+ read_links(links, args.links_file)
+ else:
+ read_links(links, os.path.join(ARGS.global_path, "links.txt"))
+ for path in custom_paths:
+ read_links(links, os.path.join(path, "links.txt"))
+ check_links(links)
+
+ zip_list = create_zip_list(global_image_list, module_image_list, custom_image_list,
+ args.global_path, args.module_path)
+ remove_links_from_zip_list(zip_list, links)
+
+ if check_rebuild(args.output_file, imagelist_filenames, custom_paths, zip_list, args.links_file):
+ tmp_dir = copy_images(zip_list)
+ create_zip_archive(zip_list, links, tmp_dir, tmp_output_file, args.sort_file)
+
+ replace_zip_file(tmp_output_file, args.output_file)
+ try:
+ LOGGER.info("Remove temporary directory %s", tmp_dir)
+ shutil.rmtree(tmp_dir)
+ except Exception as e:
+ LOGGER.error("Unable to remove temporary directory %s", tmp_dir)
+ sys.exit(2)
+ else:
+ LOGGER.info("No rebuild needed. %s is up to date.", args.output_file)
+
+
+def check_rebuild(zip_file, imagelist_filenames, custom_paths, zip_list, links_file):
+ """ Check if a rebuild is needed.
+
+ :type zip_file: str
+ :param zip_file: File to check against (use st_mtime).
+
+ :type imagelist_filenames: dict
+ :param imagelist_filenames: List of imagelist filename.
+
+ :type custom_paths: list
+ :param custom_paths: List of paths to use with links.txt files.
+
+ :type zip_list: dict
+ :param zip_list: List of filenames to create the zip archive.
+
+ :type links_file: str
+ :param links_file: filename to read the links from
+
+ :rtype: bool
+ :return: True if rebuild is needed and False if not.
+ """
+
+ if not os.path.exists(zip_file):
+ return True
+
+ zip_file_stat = os.stat(zip_file)
+
+ def compare_modtime(filenames):
+ """ Check if modification time of zip archive is older the provided
+ list of files.
+
+ :type filenames: dict
+ :param filenames: List of filenames to check against.
+
+ :rtype: bool
+ :return: True if zip archive is older and False if not.
+ """
+ for filename, path in filenames.items():
+ filename = os.path.join(path, filename) if filename else path
+ if os.path.exists(filename) and zip_file_stat.st_mtime < os.stat(filename).st_mtime:
+ return True
+ return False
+
+ if compare_modtime(imagelist_filenames):
+ return True
+ if compare_modtime(zip_list):
+ return True
+ if links_file is not None:
+ if zip_file_stat.st_mtime < os.stat(links_file).st_mtime:
+ return True
+ else:
+ for path in custom_paths:
+ link_file = os.path.join(path, 'links.txt')
+ if os.path.exists(link_file):
+ if zip_file_stat.st_mtime < os.stat(link_file).st_mtime:
+ return True
+
+ return False
+
+
+def replace_zip_file(src, dst):
+ """ Replace the old archive file with the newly created one.
+
+ :type src: str
+ :param src: Source file name.
+
+ :type dst: str
+ :param dst: Destination file name.
+ """
+ LOGGER.info("Replace old zip archive with new archive")
+ if os.path.exists(dst):
+ try:
+ os.unlink(dst)
+ except OSError as e:
+ if os.path.exists(src):
+ os.unlink(src)
+
+ LOGGER.error("Unable to unlink old archive '%s'", dst)
+ LOGGER.error(str(e))
+ sys.exit(1)
+
+ try:
+ LOGGER.info("Copy archive '%s' to '%s'", src, dst)
+ shutil.move(src, dst)
+ except (shutil.SameFileError, OSError) as e:
+ os.unlink(src)
+ LOGGER.error("Cannot copy file '%s' to %s: %s", src, dst, str(e))
+ sys.exit(1)
+
+
+def optimize_zip_layout(zip_list, sort_file=None):
+ """ Optimize the zip layout by ordering the list of filename alphabetically
+ or with provided sort file (can be partly).
+
+ :type zip_list: dict
+ :param zip_list: List of files to include in the zip archive.
+
+ :type sort_file: str
+ :param sort_file: Path/Filename to file with sort order.
+
+ :rtype: OrderedDict
+ :return: Dict with right sort order.
+ """
+ if sort_file is None:
+ LOGGER.info("No sort file provided")
+ LOGGER.info("Sorting entries alphabetically")
+
+ return OrderedDict(sorted(zip_list.items(), key=lambda t: t[0]))
+
+ LOGGER.info("Sort entries from file '%s'", sort_file)
+ sorted_zip_list = OrderedDict()
+ try:
+ fh = open(sort_file)
+ except IOError:
+ LOGGER.error("Cannot open file %s", sort_file)
+ sys.exit(1)
+ else:
+ with fh:
+ for line in fh:
+ line = line.strip()
+ if line == '' or line.startswith('#'):
+ continue
+
+ if line in zip_list:
+ sorted_zip_list[line] = ''
+ else:
+ LOGGER.warning("Unknown file '%s'", line)
+
+ for key in sorted(zip_list.keys()):
+ if key not in sorted_zip_list:
+ sorted_zip_list[key] = ''
+
+ return sorted_zip_list
+
+
+def create_zip_archive(zip_list, links, tmp_dir, tmp_zip_file, sort_file=None):
+ """ Create the zip archive.
+
+ :type zip_list: dict
+ :param zip_list: All filenames to be included in the archive.
+
+ :type links: dict
+ :param links: All filenames to create links.txt file.
+
+ :type tmp_dir: str
+ :param tmp_dir: Path to temporary saved files.
+
+ :type tmp_zip_file: str
+ :param tmp_zip_file: Filename/Path of temporary zip archive.
+
+ :type sort_file: str|None
+ :param sort_file: Optional filename with sort order to apply.
+ """
+ LOGGER.info("Creating image archive")
+
+ old_pwd = os.getcwd()
+ os.chdir(tmp_dir)
+
+ ordered_zip_list = optimize_zip_layout(zip_list, sort_file)
+
+ with contextlib.closing(zipfile.ZipFile(tmp_zip_file, 'w')) as tmp_zip:
+ if links.keys():
+ LOGGER.info("Add file 'links.txt' to zip archive")
+ create_links_file(tmp_dir, links)
+ tmp_zip.write('links.txt', compress_type=zipfile.ZIP_DEFLATED)
+
+ for link in ordered_zip_list:
+ LOGGER.info("Add file '%s' from path '%s' to zip archive", link, tmp_dir)
+ try:
+ if (link.endswith(".svg")):
+ tmp_zip.write(link, compress_type=zipfile.ZIP_DEFLATED)
+ else:
+ tmp_zip.write(link)
+ except OSError:
+ LOGGER.warning("Unable to add file '%s' to zip archive", link)
+
+ os.chdir(old_pwd)
+
+
+def create_links_file(path, links):
+ """ Create file links.txt. Contains all links.
+
+ :type path: str
+ :param path: Path where to create the file.
+
+ :type links: dict
+ :param links: Dict with links (source -> target).
+ """
+ LOGGER.info("Create file links.txt")
+
+ try:
+ filename = os.path.join(path, 'links.txt')
+ fh = open(filename, 'w')
+ except IOError:
+ LOGGER.error("Cannot open file %s", filename)
+ sys.exit(1)
+ else:
+ with fh:
+ for key in sorted(links.keys()):
+ fh.write("%s %s\n" % (key, links[key]))
+
+
+def copy_images(zip_list):
+ """ Create a temporary directory and copy images to that directory.
+
+ :type zip_list: dict
+ :param zip_list: Dict with all files.
+
+ :rtype: str
+ :return: Path of the temporary directory.
+ """
+ LOGGER.info("Copy image files to temporary directory")
+
+ tmp_dir = tempfile.mkdtemp()
+ for key, value in zip_list.items():
+ path = os.path.join(value, key)
+ out_path = os.path.join(tmp_dir, key)
+
+ LOGGER.debug("Copying '%s' to '%s'", path, out_path)
+ if os.path.exists(path):
+ dirname = os.path.dirname(out_path)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ try:
+ shutil.copyfile(path, out_path)
+ except (shutil.SameFileError, OSError) as e:
+ LOGGER.error("Cannot add file '%s' to image dir: %s", path, str(e))
+ sys.exit(1)
+
+ LOGGER.debug("Temporary directory '%s'", tmp_dir)
+ return tmp_dir
+
+
+def remove_links_from_zip_list(zip_list, links):
+ """ Remove any files from zip list that are linked.
+
+ :type zip_list: dict
+ :param zip_list: Files to include in the zip archive.
+
+ :type links: dict
+ :param links: Images which are linked.
+ """
+ LOGGER.info("Remove linked files from zip list")
+
+ for link in links.keys():
+ if link in zip_list:
+ module = zip_list[link]
+ del zip_list[link]
+ # tdf#135369 if we removed something that is a link to a real file
+ # from our list of images to pack, but that real image is not in
+ # the list of images to pack, add it in instead now
+ if links[link] not in zip_list:
+ zip_list[links[link]] = module
+
+ LOGGER.debug("Cleaned zip list:\n%s", "\n".join(zip_list))
+
+
+def create_zip_list(global_image_list, module_image_list, custom_image_list, global_path, module_path):
+ """ Create list of images for the zip archive.
+
+ :type global_image_list: dict
+ :param global_image_list: Global images.
+
+ :type module_image_list: dict
+ :param module_image_list: Module images.
+
+ :type custom_image_list: dict
+ :param custom_image_list: Custom images.
+
+ :type global_path: str
+ :param global_path: Global path (use when no custom path is available).
+
+ :type module_path: str
+ :param module_path: Module path (use when no custom path is available).
+
+ :rtype: dict
+ :return: List of images to include in zip archive.
+ """
+ LOGGER.info("Assemble image list")
+
+ zip_list = {}
+ duplicates = []
+
+ for key in global_image_list.keys():
+ if key in module_image_list:
+ duplicates.append(key)
+ continue
+
+ if key in custom_image_list:
+ zip_list[key] = custom_image_list[key]
+ continue
+
+ zip_list[key] = global_path
+
+ for key in module_image_list.keys():
+ if key in custom_image_list:
+ zip_list[key] = custom_image_list[key]
+ continue
+
+ zip_list[key] = module_path
+
+ if duplicates:
+ LOGGER.warning("Found duplicate entries in 'global' and 'module' list")
+ LOGGER.warning("\n".join(duplicates))
+
+ LOGGER.debug("Assembled image list:\n%s", "\n".join(zip_list))
+ return zip_list
+
+
+def check_links(links):
+ """ Check that no link points to another link.
+
+ :type links: dict
+ :param links: Links to icons
+ """
+
+ stop = False
+
+ for link, target in links.items():
+ if target in links.keys():
+ LOGGER.error("Link %s -> %s -> %s", link, target, links[target])
+ stop = True
+
+ if stop:
+ LOGGER.error("Some icons in links.txt were found to link to other linked icons")
+ sys.exit(1)
+
+
+def read_links(links, filename):
+ """ Read links from file.
+
+ :type links: dict
+ :param links: Hash to store all links
+
+ :type filename: str
+ :param filename: filename to read the links from
+ """
+
+ LOGGER.info("Read links from file '%s'", filename)
+ if not os.path.isfile(filename):
+ LOGGER.info("No file to read")
+ return
+
+ try:
+ fh = open(filename)
+ except IOError:
+ LOGGER.error("Cannot open file %s", filename)
+ sys.exit(1)
+ else:
+ with fh:
+ for line in fh:
+ line = line.strip()
+ if line == '' or line.startswith('#'):
+ continue
+
+ tmp = line.split(' ')
+ if len(tmp) == 2:
+ links[tmp[0]] = tmp[1]
+ else:
+ LOGGER.error("Malformed links line '%s' in file %s", line, filename)
+ sys.exit(1)
+
+ LOGGER.debug("Read links:\n%s", "\n".join(links))
+
+
+def find_custom(custom_paths):
+ """ Find all custom images.
+
+ :type custom_paths: list
+ :param custom_paths: List of custom paths to search within.
+
+ :rtype: dict
+ :return: List of all custom images.
+ """
+ LOGGER.info("Find all images in custom paths")
+
+ custom_image_list = {}
+ for path in custom_paths:
+ # find all png files under the path including subdirs
+ custom_files = [val for sublist in [
+ [os.path.join(i[0], j) for j in i[2]
+ if j.endswith('.png') and os.path.isfile(os.path.join(i[0], j))]
+ for i in os.walk(path)] for val in sublist]
+
+ for filename in custom_files:
+ if filename.startswith(path):
+ key = filename.replace(os.path.join(path, ''), '')
+ if key not in custom_image_list:
+ custom_image_list[key] = path
+
+ LOGGER.debug("Custom image list:\n%s", "\n".join(custom_image_list.keys()))
+ return custom_image_list
+
+
+def parse_image_list(imagelist_filenames):
+ """ Parse and extract filename from the imagelist files.
+
+ :type imagelist_filenames: list
+ :param imagelist_filenames: List of imagelist files.
+
+ :rtype: tuple
+ :return: Tuple with dicts containing the global or module image list.
+ """
+
+ global_image_list = {}
+ module_image_list = {}
+ for imagelist_filename in imagelist_filenames.keys():
+ LOGGER.info("Parsing '%s'", imagelist_filename)
+
+ try:
+ fh = open(imagelist_filename)
+ except IOError as e:
+ LOGGER.error("Cannot open imagelist file %s", imagelist_filename)
+ sys.exit(2)
+ else:
+ line_count = 0
+ with fh:
+ for line in fh:
+ line_count += 1
+ line = line.strip()
+ if line == '' or line.startswith('#'):
+ continue
+ # clean up backslashes and double slashes
+ line = line.replace('\\', '/')
+ line = line.replace('//', '/')
+
+ if line.startswith('%GLOBALRES%'):
+ key = "res/%s" % line.replace('%GLOBALRES%/', '')
+ global_image_list[key] = True
+ if key.endswith('.png'):
+ global_image_list[key[:-4] + '.svg'] = True
+ continue
+
+ if line.startswith('%MODULE%'):
+ key = line.replace('%MODULE%/', '')
+ module_image_list[key] = True
+ if key.endswith('.png'):
+ module_image_list[key[:-4] + '.svg'] = True
+ continue
+
+ LOGGER.error("Cannot parse line %s:%d", imagelist_filename, line_count)
+ sys.exit(1)
+
+ LOGGER.debug("Global image list:\n%s", "\n".join(global_image_list))
+ LOGGER.debug("Module image list:\n%s", "\n".join(module_image_list))
+ return global_image_list, module_image_list
+
+
+def get_imagelist_filenames(filename):
+ """ Extract a list of imagelist filenames.
+
+ :type filename: str
+ :param filename: Name of file from extracting the list.
+
+ :rtype: dict
+ :return: List of imagelist filenames.
+ """
+ LOGGER.info("Extract the imagelist filenames")
+
+ imagelist_filenames = {}
+ try:
+ fh = open(filename)
+ except IOError:
+ LOGGER.error("Cannot open imagelist file %s", filename)
+ sys.exit(1)
+ else:
+ with fh:
+ for line in fh:
+ line = line.strip()
+ if not line or line == '':
+ continue
+
+ for line_split in line.split(' '):
+ line_split.strip()
+ imagelist_filenames[line_split] = ''
+
+ LOGGER.debug("Extracted imagelist filenames:\n%s", "\n".join(imagelist_filenames.keys()))
+ return imagelist_filenames
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser("Pack images into archives")
+ parser.add_argument('-o', '--output-file', dest='output_file',
+ action='store', required=True,
+ help='path to output archive')
+ parser.add_argument('-l', '--imagelist-file', dest='imagelist_file',
+ action='store', required=True,
+ help='file containing list of image list file')
+ parser.add_argument('-L', '--links-file', dest='links_file',
+ action='store', required=False,
+ help='file containing linked images')
+ parser.add_argument('-s', '--sort-file', dest='sort_file',
+ action='store', required=True, default=None,
+ help='image sort order file')
+ parser.add_argument('-c', '--custom-path', dest='custom_path',
+ action='append', required=True,
+ help='path to custom path directory')
+ parser.add_argument('-g', '--global-path', dest='global_path',
+ action='store', required=True,
+ help='path to global images directory')
+ parser.add_argument('-m', '--module-path', dest='module_path',
+ action='store', required=True,
+ help='path to module images directory')
+ parser.add_argument('-v', '--verbose', dest='verbose',
+ action='count', default=0,
+ help='set the verbosity (can be used multiple times)')
+
+ ARGS = parser.parse_args()
+ LOGGER.setLevel(logging.ERROR - (10 * ARGS.verbose if ARGS.verbose <= 3 else 3))
+
+ main(ARGS)
+ sys.exit(0)
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/solenv/bin/packcomponents.xslt b/solenv/bin/packcomponents.xslt
new file mode 100644
index 0000000000..afe2d7d476
--- /dev/null
+++ b/solenv/bin/packcomponents.xslt
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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 .
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ exclude-result-prefixes="uc"
+ xmlns="http://openoffice.org/2010/uno-components"
+ xmlns:uc="http://openoffice.org/2010/uno-components">
+ <xsl:param name="prefix"/>
+ <xsl:strip-space elements="*"/>
+ <xsl:template match="/">
+ <xsl:element name="components"
+ namespace="http://openoffice.org/2010/uno-components">
+ <xsl:for-each select="list/filename">
+ <xsl:variable name="doc" select="document(concat($prefix, .))"/>
+ <xsl:choose>
+ <xsl:when test="count($doc/uc:components/uc:component) = 1">
+ <xsl:copy-of select="$doc/uc:components/uc:component"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>cannot process </xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:element>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/solenv/bin/packregistry.xslt b/solenv/bin/packregistry.xslt
new file mode 100644
index 0000000000..cf89c1a7dc
--- /dev/null
+++ b/solenv/bin/packregistry.xslt
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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 .
+-->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:oor="http://openoffice.org/2001/registry">
+ <xsl:param name="prefix"/>
+ <xsl:strip-space elements="*"/>
+ <xsl:preserve-space elements="value it"/>
+ <!-- TODO: strip space from "value" elements that have "it" children -->
+ <xsl:template match="/">
+ <oor:data xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <xsl:copy-of select="list/dependency"/>
+<!--
+ <xsl:copy-of select="document(list/filename)/oor:component-schema"/>
+ <xsl:copy-of select="document(list/filename)/oor:component-data"/>
+
+ instead of the below for-each would only issue warnings, not errors, for
+ non-existing or otherwise bad input files; it is important that the input
+ filename list is already sorted in an order suitable for the configmgr
+ (e.g., xcs files preceding xcu files).
+-->
+ <xsl:for-each select="list/filename">
+ <xsl:variable name="doc" select="document(concat($prefix, .))"/>
+ <xsl:choose>
+ <xsl:when test="count($doc/oor:component-schema) = 1">
+ <xsl:apply-templates select="$doc/oor:component-schema"/>
+ </xsl:when>
+ <xsl:when test="count($doc/oor:component-data) = 1">
+ <xsl:apply-templates select="$doc/oor:component-data"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes">
+ <xsl:text>cannot process </xsl:text>
+ <xsl:value-of select="."/>
+ </xsl:message>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </oor:data>
+ </xsl:template>
+ <xsl:template
+ match="oor:component-schema|oor:component-data|templates|component|group|
+ set|node-ref|prop|item|value|it|unicode|node|info">
+ <xsl:copy copy-namespaces="no">
+ <!-- prune oor:component-data xmlns:install="..." namespaces (would only
+ work in XSLT 2.0, however) -->
+ <xsl:apply-templates select="@*"/>
+ <xsl:apply-templates/>
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="value[it]">
+ <xsl:copy copy-namespaces="no">
+ <xsl:apply-templates select="@*"/>
+ <xsl:apply-templates select="*"/>
+ <!-- ignore text elements (which must be whitespace only) -->
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="import|uses|constraints"/>
+ <!-- TODO: no longer strip elements when they are eventually read by
+ configmgr implementation -->
+ <xsl:template match="@*">
+ <xsl:copy/>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/solenv/bin/par2script.pl b/solenv/bin/par2script.pl
new file mode 100644
index 0000000000..000da995b4
--- /dev/null
+++ b/solenv/bin/par2script.pl
@@ -0,0 +1,106 @@
+#
+# 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 .
+#
+
+use lib ("$ENV{SRCDIR}/solenv/bin/modules");
+
+use Cwd;
+use par2script::check;
+use par2script::files;
+use par2script::globals;
+use par2script::parameter;
+use par2script::module;
+use par2script::undefine;
+use par2script::work;
+
+####################################
+# Main program
+####################################
+
+par2script::parameter::getparameter();
+par2script::parameter::control_parameter();
+par2script::parameter::outputparameter() if $par2script::globals::verbose;
+
+my $includes = par2script::work::setincludes($par2script::globals::includepathlist);
+my $parfiles = par2script::work::setparfiles($par2script::globals::parfilelist);
+
+par2script::work::make_complete_paths_for_parfiles($parfiles, $includes);
+
+print "Reading par files\n" if $par2script::globals::verbose;
+my $parfilecontent = par2script::work::read_all_parfiles($parfiles);
+
+print "Collecting items\n" if $par2script::globals::verbose;
+par2script::work::collect_definitions($parfilecontent);
+
+print "Collecting assigned items\n" if $par2script::globals::verbose;
+par2script::work::collect_assigned_gids();
+
+# print "First control of multiple assignments\n";
+# par2script::check::check_multiple_assignments();
+
+print "Searching for Undefinitions\n" if $par2script::globals::verbose;
+par2script::undefine::undefine_gids($parfilecontent);
+par2script::undefine::remove_complete_item("Directory", $parfilecontent);
+par2script::undefine::remove_complete_item("Profile", $parfilecontent);
+
+print "Removing assigned GIDs without definitions\n" if $par2script::globals::verbose;
+par2script::module::remove_undefined_gids_from_modules();
+
+print "Adding definitions without assignment to the root\n" if $par2script::globals::verbose;
+par2script::module::add_to_root_module();
+
+print "Control of multiple assignments\n" if $par2script::globals::verbose;
+par2script::check::check_multiple_assignments();
+
+print "Control of definitions with missing assignments\n" if $par2script::globals::verbose;
+par2script::check::check_missing_assignments();
+
+# checking the setup script
+print "Checking directory definitions ...\n" if $par2script::globals::verbose;
+par2script::check::check_needed_directories();
+par2script::check::check_directories_in_item_definitions();
+print "Checking module definitions ...\n" if $par2script::globals::verbose;
+par2script::check::check_module_existence();
+print "Checking module assignments ...\n" if $par2script::globals::verbose;
+par2script::check::check_moduleid_at_items();
+print "Checking Root Module ..." if $par2script::globals::verbose;
+par2script::check::check_rootmodule();
+print "Checking Shortcut assignments ...\n" if $par2script::globals::verbose;
+par2script::check::check_shortcut_assignments();
+print "Checking missing parents ...\n" if $par2script::globals::verbose;
+par2script::check::check_missing_parents();
+
+print "Shorten lines at modules\n" if $par2script::globals::verbose;
+par2script::module::shorten_lines_at_modules();
+
+# Now the script can be created
+print "Creating setup script\n" if $par2script::globals::verbose;
+my $setupscript = par2script::work::create_script();
+
+print "Saving script\n" if $par2script::globals::verbose;
+par2script::files::save_file($par2script::globals::scriptname, $setupscript);
+
+# logging, if set
+if ($par2script::globals::logging)
+{
+ par2script::files::save_file($par2script::globals::logfilename, \@par2script::globals::logfileinfo);
+ print "Log file written: $par2script::globals::logfilename\n";
+}
+
+####################################
+# End main program
+####################################
diff --git a/solenv/bin/polib.py b/solenv/bin/polib.py
new file mode 100644
index 0000000000..e1511cdb97
--- /dev/null
+++ b/solenv/bin/polib.py
@@ -0,0 +1,1868 @@
+# -* coding: utf-8 -*-
+#
+# License: MIT (see LICENSE file provided)
+# vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
+8
+"""
+**polib** allows you to manipulate, create, modify gettext files (pot, po and
+mo files). You can load existing files, iterate through it's entries, add,
+modify entries, comments or metadata, etc. or create new po files from scratch.
+
+**polib** provides a simple and pythonic API via the :func:`~polib.pofile` and
+:func:`~polib.mofile` convenience functions.
+"""
+
+__author__ = 'David Jean Louis <izimobil@gmail.com>'
+__version__ = '1.0.8'
+__all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry',
+ 'default_encoding', 'escape', 'unescape', 'detect_encoding', ]
+
+import array
+import codecs
+import os
+import re
+import struct
+import sys
+import textwrap
+import binascii
+
+try:
+ import io
+except ImportError:
+ # replacement of io.open() for python < 2.6
+ # we use codecs instead
+ class io(object):
+ @staticmethod
+ def open(fpath, mode='r', encoding=None):
+ return codecs.open(fpath, mode, encoding)
+
+
+# the default encoding to use when encoding cannot be detected
+default_encoding = 'utf-8'
+
+# python 2/3 compatibility helpers {{{
+
+
+if sys.version_info < (3, 0):
+ PY3 = False
+ text_type = unicode
+
+ def b(s):
+ return s
+
+ def u(s):
+ return unicode(s, "unicode_escape")
+
+else:
+ PY3 = True
+ text_type = str
+
+ def b(s):
+ return s.encode("latin-1")
+
+ def u(s):
+ return s
+# }}}
+# _pofile_or_mofile {{{
+
+
+def _pofile_or_mofile(f, type, **kwargs):
+ """
+ Internal function used by :func:`polib.pofile` and :func:`polib.mofile` to
+ honor the DRY concept.
+ """
+ # get the file encoding
+ enc = kwargs.get('encoding')
+ if enc is None:
+ enc = detect_encoding(f, type == 'mofile')
+
+ # parse the file
+ kls = type == 'pofile' and _POFileParser or _MOFileParser
+ parser = kls(
+ f,
+ encoding=enc,
+ check_for_duplicates=kwargs.get('check_for_duplicates', False),
+ klass=kwargs.get('klass')
+ )
+ instance = parser.parse()
+ instance.wrapwidth = kwargs.get('wrapwidth', 78)
+ return instance
+# }}}
+# _is_file {{{
+
+
+def _is_file(filename_or_contents):
+ """
+ Safely returns the value of os.path.exists(filename_or_contents).
+
+ Arguments:
+
+ ``filename_or_contents``
+ either a filename, or a string holding the contents of some file.
+ In the latter case, this function will always return False.
+ """
+ try:
+ return os.path.exists(filename_or_contents)
+ except (ValueError, UnicodeEncodeError):
+ return False
+# }}}
+# function pofile() {{{
+
+
+def pofile(pofile, **kwargs):
+ """
+ Convenience function that parses the po or pot file ``pofile`` and returns
+ a :class:`~polib.POFile` instance.
+
+ Arguments:
+
+ ``pofile``
+ string, full or relative path to the po/pot file or its content (data).
+
+ ``wrapwidth``
+ integer, the wrap width, only useful when the ``-w`` option was passed
+ to xgettext (optional, default: ``78``).
+
+ ``encoding``
+ string, the encoding to use (e.g. "utf-8") (default: ``None``, the
+ encoding will be auto-detected).
+
+ ``check_for_duplicates``
+ whether to check for duplicate entries when adding entries to the
+ file (optional, default: ``False``).
+
+ ``klass``
+ class which is used to instantiate the return value (optional,
+ default: ``None``, the return value with be a :class:`~polib.POFile`
+ instance).
+ """
+ return _pofile_or_mofile(pofile, 'pofile', **kwargs)
+# }}}
+# function mofile() {{{
+
+
+def mofile(mofile, **kwargs):
+ """
+ Convenience function that parses the mo file ``mofile`` and returns a
+ :class:`~polib.MOFile` instance.
+
+ Arguments:
+
+ ``mofile``
+ string, full or relative path to the mo file or its content (data).
+
+ ``wrapwidth``
+ integer, the wrap width, only useful when the ``-w`` option was passed
+ to xgettext to generate the po file that was used to format the mo file
+ (optional, default: ``78``).
+
+ ``encoding``
+ string, the encoding to use (e.g. "utf-8") (default: ``None``, the
+ encoding will be auto-detected).
+
+ ``check_for_duplicates``
+ whether to check for duplicate entries when adding entries to the
+ file (optional, default: ``False``).
+
+ ``klass``
+ class which is used to instantiate the return value (optional,
+ default: ``None``, the return value with be a :class:`~polib.POFile`
+ instance).
+ """
+ return _pofile_or_mofile(mofile, 'mofile', **kwargs)
+# }}}
+# function detect_encoding() {{{
+
+
+def detect_encoding(file, binary_mode=False):
+ """
+ Try to detect the encoding used by the ``file``. The ``file`` argument can
+ be a PO or MO file path or a string containing the contents of the file.
+ If the encoding cannot be detected, the function will return the value of
+ ``default_encoding``.
+
+ Arguments:
+
+ ``file``
+ string, full or relative path to the po/mo file or its content.
+
+ ``binary_mode``
+ boolean, set this to True if ``file`` is a mo file.
+ """
+ PATTERN = r'"?Content-Type:.+? charset=([\w_\-:\.]+)'
+ rxt = re.compile(u(PATTERN))
+ rxb = re.compile(b(PATTERN))
+
+ def charset_exists(charset):
+ """Check whether ``charset`` is valid or not."""
+ try:
+ codecs.lookup(charset)
+ except LookupError:
+ return False
+ return True
+
+ if not _is_file(file):
+ match = rxt.search(file)
+ if match:
+ enc = match.group(1).strip()
+ if charset_exists(enc):
+ return enc
+ else:
+ # For PY3, always treat as binary
+ if binary_mode or PY3:
+ mode = 'rb'
+ rx = rxb
+ else:
+ mode = 'r'
+ rx = rxt
+ f = open(file, mode)
+ for l in f.readlines():
+ match = rx.search(l)
+ if match:
+ f.close()
+ enc = match.group(1).strip()
+ if not isinstance(enc, text_type):
+ enc = enc.decode('utf-8')
+ if charset_exists(enc):
+ return enc
+ f.close()
+ return default_encoding
+# }}}
+# function escape() {{{
+
+
+def escape(st):
+ """
+ Escapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in
+ the given string ``st`` and returns it.
+ """
+ return st.replace('\\', r'\\')\
+ .replace('\t', r'\t')\
+ .replace('\r', r'\r')\
+ .replace('\n', r'\n')\
+ .replace('\"', r'\"')
+# }}}
+# function unescape() {{{
+
+
+def unescape(st):
+ """
+ Unescapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in
+ the given string ``st`` and returns it.
+ """
+ def unescape_repl(m):
+ m = m.group(1)
+ if m == 'n':
+ return '\n'
+ if m == 't':
+ return '\t'
+ if m == 'r':
+ return '\r'
+ if m == '\\':
+ return '\\'
+ return m # handles escaped double quote
+ return re.sub(r'\\(\\|n|t|r|")', unescape_repl, st)
+# }}}
+# function natural_sort() {{{
+
+
+def natural_sort(lst):
+ """
+ Sort naturally the given list.
+ Credits: http://stackoverflow.com/a/4836734
+ """
+ convert = lambda text: int(text) if text.isdigit() else text.lower()
+ alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
+ return sorted(lst, key = alphanum_key)
+# }}}
+# class _BaseFile {{{
+
+
+class _BaseFile(list):
+ """
+ Common base class for the :class:`~polib.POFile` and :class:`~polib.MOFile`
+ classes. This class should **not** be instantiated directly.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor, accepts the following keyword arguments:
+
+ ``pofile``
+ string, the path to the po or mo file, or its content as a string.
+
+ ``wrapwidth``
+ integer, the wrap width, only useful when the ``-w`` option was
+ passed to xgettext (optional, default: ``78``).
+
+ ``encoding``
+ string, the encoding to use, defaults to ``default_encoding``
+ global variable (optional).
+
+ ``check_for_duplicates``
+ whether to check for duplicate entries when adding entries to the
+ file, (optional, default: ``False``).
+ """
+ list.__init__(self)
+ # the opened file handle
+ pofile = kwargs.get('pofile', None)
+ if pofile and _is_file(pofile):
+ self.fpath = pofile
+ else:
+ self.fpath = kwargs.get('fpath')
+ # the width at which lines should be wrapped
+ self.wrapwidth = kwargs.get('wrapwidth', 78)
+ # the file encoding
+ self.encoding = kwargs.get('encoding', default_encoding)
+ # whether to check for duplicate entries or not
+ self.check_for_duplicates = kwargs.get('check_for_duplicates', False)
+ # header
+ self.header = ''
+ # both po and mo files have metadata
+ self.metadata = {}
+ self.metadata_is_fuzzy = 0
+
+ def __unicode__(self):
+ """
+ Returns the unicode representation of the file.
+ """
+ ret = []
+ entries = [self.metadata_as_entry()] + \
+ [e for e in self if not e.obsolete]
+ for entry in entries:
+ ret.append(entry.__unicode__(self.wrapwidth))
+ for entry in self.obsolete_entries():
+ ret.append(entry.__unicode__(self.wrapwidth))
+ ret = u('\n').join(ret)
+
+ assert isinstance(ret, text_type)
+ #if type(ret) != text_type:
+ # return unicode(ret, self.encoding)
+ return ret
+
+ if PY3:
+ def __str__(self):
+ return self.__unicode__()
+ else:
+ def __str__(self):
+ """
+ Returns the string representation of the file.
+ """
+ return unicode(self).encode(self.encoding)
+
+ def __contains__(self, entry):
+ """
+ Overridden ``list`` method to implement the membership test (in and
+ not in).
+ The method considers that an entry is in the file if it finds an entry
+ that has the same msgid (the test is **case sensitive**) and the same
+ msgctxt (or none for both entries).
+
+ Argument:
+
+ ``entry``
+ an instance of :class:`~polib._BaseEntry`.
+ """
+ return self.find(entry.msgid, by='msgid', msgctxt=entry.msgctxt) \
+ is not None
+
+ def __eq__(self, other):
+ return str(self) == str(other)
+
+ def append(self, entry):
+ """
+ Overridden method to check for duplicates entries, if a user tries to
+ add an entry that is already in the file, the method will raise a
+ ``ValueError`` exception.
+
+ Argument:
+
+ ``entry``
+ an instance of :class:`~polib._BaseEntry`.
+ """
+ # check_for_duplicates may not be defined (yet) when unpickling.
+ # But if pickling, we never want to check for duplicates anyway.
+ if getattr(self, 'check_for_duplicates', False) and entry in self:
+ raise ValueError('Entry "%s" already exists' % entry.msgid)
+ super(_BaseFile, self).append(entry)
+
+ def insert(self, index, entry):
+ """
+ Overridden method to check for duplicates entries, if a user tries to
+ add an entry that is already in the file, the method will raise a
+ ``ValueError`` exception.
+
+ Arguments:
+
+ ``index``
+ index at which the entry should be inserted.
+
+ ``entry``
+ an instance of :class:`~polib._BaseEntry`.
+ """
+ if self.check_for_duplicates and entry in self:
+ raise ValueError('Entry "%s" already exists' % entry.msgid)
+ super(_BaseFile, self).insert(index, entry)
+
+ def metadata_as_entry(self):
+ """
+ Returns the file metadata as a :class:`~polib.POFile` instance.
+ """
+ e = POEntry(msgid='')
+ mdata = self.ordered_metadata()
+ if mdata:
+ strs = []
+ for name, value in mdata:
+ # Strip whitespace off each line in a multi-line entry
+ strs.append('%s: %s' % (name, value))
+ e.msgstr = '\n'.join(strs) + '\n'
+ if self.metadata_is_fuzzy:
+ e.flags.append('fuzzy')
+ return e
+
+ def save(self, fpath=None, repr_method='__unicode__'):
+ """
+ Saves the po file to ``fpath``.
+ If it is an existing file and no ``fpath`` is provided, then the
+ existing file is rewritten with the modified data.
+
+ Keyword arguments:
+
+ ``fpath``
+ string, full or relative path to the file.
+
+ ``repr_method``
+ string, the method to use for output.
+ """
+ if self.fpath is None and fpath is None:
+ raise IOError('You must provide a file path to save() method')
+ contents = getattr(self, repr_method)()
+ if fpath is None:
+ fpath = self.fpath
+ if repr_method == 'to_binary':
+ fhandle = open(fpath, 'wb')
+ else:
+ fhandle = io.open(fpath, 'w', encoding=self.encoding)
+ if not isinstance(contents, text_type):
+ contents = contents.decode(self.encoding)
+ fhandle.write(contents)
+ fhandle.close()
+ # set the file path if not set
+ if self.fpath is None and fpath:
+ self.fpath = fpath
+
+ def find(self, st, by='msgid', include_obsolete_entries=False,
+ msgctxt=False):
+ """
+ Find the entry which msgid (or property identified by the ``by``
+ argument) matches the string ``st``.
+
+ Keyword arguments:
+
+ ``st``
+ string, the string to search for.
+
+ ``by``
+ string, the property to use for comparison (default: ``msgid``).
+
+ ``include_obsolete_entries``
+ boolean, whether to also search in entries that are obsolete.
+
+ ``msgctxt``
+ string, allows specifying a specific message context for the
+ search.
+ """
+ if include_obsolete_entries:
+ entries = self[:]
+ else:
+ entries = [e for e in self if not e.obsolete]
+ for e in entries:
+ if getattr(e, by) == st:
+ if msgctxt is not False and e.msgctxt != msgctxt:
+ continue
+ return e
+ return None
+
+ def ordered_metadata(self):
+ """
+ Convenience method that returns an ordered version of the metadata
+ dictionary. The return value is list of tuples (metadata name,
+ metadata_value).
+ """
+ # copy the dict first
+ metadata = self.metadata.copy()
+ data_order = [
+ 'Project-Id-Version',
+ 'Report-Msgid-Bugs-To',
+ 'POT-Creation-Date',
+ 'PO-Revision-Date',
+ 'Last-Translator',
+ 'Language-Team',
+ 'Language',
+ 'MIME-Version',
+ 'Content-Type',
+ 'Content-Transfer-Encoding',
+ 'Plural-Forms'
+ ]
+ ordered_data = []
+ for data in data_order:
+ try:
+ value = metadata.pop(data)
+ ordered_data.append((data, value))
+ except KeyError:
+ pass
+ # the rest of the metadata will be alphabetically ordered since there
+ # are no specs for this AFAIK
+ for data in natural_sort(metadata.keys()):
+ value = metadata[data]
+ ordered_data.append((data, value))
+ return ordered_data
+
+ def to_binary(self):
+ """
+ Return the binary representation of the file.
+ """
+ offsets = []
+ entries = self.translated_entries()
+
+ # the keys are sorted in the .mo file
+ def cmp(_self, other):
+ # msgfmt compares entries with msgctxt if it exists
+ self_msgid = _self.msgctxt and _self.msgctxt or _self.msgid
+ other_msgid = other.msgctxt and other.msgctxt or other.msgid
+ if self_msgid > other_msgid:
+ return 1
+ elif self_msgid < other_msgid:
+ return -1
+ else:
+ return 0
+ # add metadata entry
+ entries.sort(key=lambda o: o.msgctxt or o.msgid)
+ mentry = self.metadata_as_entry()
+ #mentry.msgstr = mentry.msgstr.replace('\\n', '').lstrip()
+ entries = [mentry] + entries
+ entries_len = len(entries)
+ ids, strs = b(''), b('')
+ for e in entries:
+ # For each string, we need size and file offset. Each string is
+ # NUL terminated; the NUL does not count into the size.
+ msgid = b('')
+ if e.msgctxt:
+ # Contexts are stored by storing the concatenation of the
+ # context, a <EOT> byte, and the original string
+ msgid = self._encode(e.msgctxt + '\4')
+ if e.msgid_plural:
+ msgstr = []
+ for index in sorted(e.msgstr_plural.keys()):
+ msgstr.append(e.msgstr_plural[index])
+ msgid += self._encode(e.msgid + '\0' + e.msgid_plural)
+ msgstr = self._encode('\0'.join(msgstr))
+ else:
+ msgid += self._encode(e.msgid)
+ msgstr = self._encode(e.msgstr)
+ offsets.append((len(ids), len(msgid), len(strs), len(msgstr)))
+ ids += msgid + b('\0')
+ strs += msgstr + b('\0')
+
+ # The header is 7 32-bit unsigned integers.
+ keystart = 7 * 4 + 16 * entries_len
+ # and the values start after the keys
+ valuestart = keystart + len(ids)
+ koffsets = []
+ voffsets = []
+ # The string table first has the list of keys, then the list of values.
+ # Each entry has first the size of the string, then the file offset.
+ for o1, l1, o2, l2 in offsets:
+ koffsets += [l1, o1 + keystart]
+ voffsets += [l2, o2 + valuestart]
+ offsets = koffsets + voffsets
+
+ output = struct.pack(
+ "Iiiiiii",
+ # Magic number
+ MOFile.MAGIC,
+ # Version
+ 0,
+ # number of entries
+ entries_len,
+ # start of key index
+ 7 * 4,
+ # start of value index
+ 7 * 4 + entries_len * 8,
+ # size and offset of hash table, we don't use hash tables
+ 0, keystart
+
+ )
+ if sys.version_info >= (3, 2):
+ output += array.array("i", offsets).tobytes()
+ else:
+ output += array.array("i", offsets).tostring()
+ output += ids
+ output += strs
+ return output
+
+ def _encode(self, mixed):
+ """
+ Encodes the given ``mixed`` argument with the file encoding if and
+ only if it's a unicode string and returns the encoded string.
+ """
+ if isinstance(mixed, text_type):
+ mixed = mixed.encode(self.encoding)
+ return mixed
+# }}}
+# class POFile {{{
+
+
+class POFile(_BaseFile):
+ """
+ Po (or Pot) file reader/writer.
+ This class inherits the :class:`~polib._BaseFile` class and, by extension,
+ the python ``list`` type.
+ """
+
+ def __unicode__(self):
+ """
+ Returns the unicode representation of the po file.
+ """
+ ret, headers = '', self.header.split('\n')
+ for header in headers:
+ if not len(header):
+ ret += "#\n"
+ elif header[:1] in [',', ':']:
+ ret += '#%s\n' % header
+ else:
+ ret += '# %s\n' % header
+
+ if not isinstance(ret, text_type):
+ ret = ret.decode(self.encoding)
+
+ return ret + _BaseFile.__unicode__(self)
+
+ def save_as_mofile(self, fpath):
+ """
+ Saves the binary representation of the file to given ``fpath``.
+
+ Keyword argument:
+
+ ``fpath``
+ string, full or relative path to the mo file.
+ """
+ _BaseFile.save(self, fpath, 'to_binary')
+
+ def percent_translated(self):
+ """
+ Convenience method that returns the percentage of translated
+ messages.
+ """
+ total = len([e for e in self if not e.obsolete])
+ if total == 0:
+ return 100
+ translated = len(self.translated_entries())
+ return int(translated * 100 / float(total))
+
+ def translated_entries(self):
+ """
+ Convenience method that returns the list of translated entries.
+ """
+ return [e for e in self if e.translated()]
+
+ def untranslated_entries(self):
+ """
+ Convenience method that returns the list of untranslated entries.
+ """
+ return [e for e in self if not e.translated() and not e.obsolete
+ and not 'fuzzy' in e.flags]
+
+ def fuzzy_entries(self):
+ """
+ Convenience method that returns the list of fuzzy entries.
+ """
+ return [e for e in self if 'fuzzy' in e.flags]
+
+ def obsolete_entries(self):
+ """
+ Convenience method that returns the list of obsolete entries.
+ """
+ return [e for e in self if e.obsolete]
+
+ def merge(self, refpot):
+ """
+ Convenience method that merges the current pofile with the pot file
+ provided. It behaves exactly as the gettext msgmerge utility:
+
+ * comments of this file will be preserved, but extracted comments and
+ occurrences will be discarded;
+ * any translations or comments in the file will be discarded, however,
+ dot comments and file positions will be preserved;
+ * the fuzzy flags are preserved.
+
+ Keyword argument:
+
+ ``refpot``
+ object POFile, the reference catalog.
+ """
+ # Store entries in dict/set for faster access
+ self_entries = dict((entry.msgid, entry) for entry in self)
+ refpot_msgids = set(entry.msgid for entry in refpot)
+ # Merge entries that are in the refpot
+ for entry in refpot:
+ e = self_entries.get(entry.msgid)
+ if e is None:
+ e = POEntry()
+ self.append(e)
+ e.merge(entry)
+ # ok, now we must "obsolete" entries that are not in the refpot anymore
+ for entry in self:
+ if entry.msgid not in refpot_msgids:
+ entry.obsolete = True
+# }}}
+# class MOFile {{{
+
+
+class MOFile(_BaseFile):
+ """
+ Mo file reader/writer.
+ This class inherits the :class:`~polib._BaseFile` class and, by
+ extension, the python ``list`` type.
+ """
+ MAGIC = 0x950412de
+ MAGIC_SWAPPED = 0xde120495
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor, accepts all keywords arguments accepted by
+ :class:`~polib._BaseFile` class.
+ """
+ _BaseFile.__init__(self, *args, **kwargs)
+ self.magic_number = None
+ self.version = 0
+
+ def save_as_pofile(self, fpath):
+ """
+ Saves the mofile as a pofile to ``fpath``.
+
+ Keyword argument:
+
+ ``fpath``
+ string, full or relative path to the file.
+ """
+ _BaseFile.save(self, fpath)
+
+ def save(self, fpath=None):
+ """
+ Saves the mofile to ``fpath``.
+
+ Keyword argument:
+
+ ``fpath``
+ string, full or relative path to the file.
+ """
+ _BaseFile.save(self, fpath, 'to_binary')
+
+ def percent_translated(self):
+ """
+ Convenience method to keep the same interface with POFile instances.
+ """
+ return 100
+
+ def translated_entries(self):
+ """
+ Convenience method to keep the same interface with POFile instances.
+ """
+ return self
+
+ def untranslated_entries(self):
+ """
+ Convenience method to keep the same interface with POFile instances.
+ """
+ return []
+
+ def fuzzy_entries(self):
+ """
+ Convenience method to keep the same interface with POFile instances.
+ """
+ return []
+
+ def obsolete_entries(self):
+ """
+ Convenience method to keep the same interface with POFile instances.
+ """
+ return []
+# }}}
+# class _BaseEntry {{{
+
+
+class _BaseEntry(object):
+ """
+ Base class for :class:`~polib.POEntry` and :class:`~polib.MOEntry` classes.
+ This class should **not** be instantiated directly.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor, accepts the following keyword arguments:
+
+ ``msgid``
+ string, the entry msgid.
+
+ ``msgstr``
+ string, the entry msgstr.
+
+ ``msgid_plural``
+ string, the entry msgid_plural.
+
+ ``msgstr_plural``
+ list, the entry msgstr_plural lines.
+
+ ``msgctxt``
+ string, the entry context (msgctxt).
+
+ ``obsolete``
+ bool, whether the entry is "obsolete" or not.
+
+ ``encoding``
+ string, the encoding to use, defaults to ``default_encoding``
+ global variable (optional).
+ """
+ self.msgid = kwargs.get('msgid', '')
+ self.msgstr = kwargs.get('msgstr', '')
+ self.msgid_plural = kwargs.get('msgid_plural', '')
+ self.msgstr_plural = kwargs.get('msgstr_plural', {})
+ self.msgctxt = kwargs.get('msgctxt', None)
+ self.obsolete = kwargs.get('obsolete', False)
+ self.encoding = kwargs.get('encoding', default_encoding)
+
+ def __unicode__(self, wrapwidth=78):
+ """
+ Returns the unicode representation of the entry.
+ """
+ if self.obsolete:
+ delflag = '#~ '
+ else:
+ delflag = ''
+ ret = []
+ # write the msgctxt if any
+ if self.msgctxt is not None:
+ ret += self._str_field("msgctxt", delflag, "", self.msgctxt,
+ wrapwidth)
+ # write the msgid
+ ret += self._str_field("msgid", delflag, "", self.msgid, wrapwidth)
+ # write the msgid_plural if any
+ if self.msgid_plural:
+ ret += self._str_field("msgid_plural", delflag, "",
+ self.msgid_plural, wrapwidth)
+ if self.msgstr_plural:
+ # write the msgstr_plural if any
+ msgstrs = self.msgstr_plural
+ keys = list(msgstrs)
+ keys.sort()
+ for index in keys:
+ msgstr = msgstrs[index]
+ plural_index = '[%s]' % index
+ ret += self._str_field("msgstr", delflag, plural_index, msgstr,
+ wrapwidth)
+ else:
+ # otherwise write the msgstr
+ ret += self._str_field("msgstr", delflag, "", self.msgstr,
+ wrapwidth)
+ ret.append('')
+ usedirect = True
+ if not PY3 and type(ret[0] != unicode):
+ try:
+ usedirect = False
+ ret = u('\n').join(x.decode('utf-8') for x in ret)
+ except:
+ usedirect = True
+ if usedirect:
+ ret = u('\n').join(ret)
+ return ret
+
+ if PY3:
+ def __str__(self):
+ return self.__unicode__()
+ else:
+ def __str__(self):
+ """
+ Returns the string representation of the entry.
+ """
+ return unicode(self).encode(self.encoding)
+
+ def __eq__(self, other):
+ return str(self) == str(other)
+
+ def _str_field(self, fieldname, delflag, plural_index, field,
+ wrapwidth=78):
+ lines = field.splitlines(True)
+ if len(lines) > 1:
+ lines = [''] + lines # start with initial empty line
+ else:
+ escaped_field = escape(field)
+ specialchars_count = 0
+ for c in ['\\', '\n', '\r', '\t', '"']:
+ specialchars_count += field.count(c)
+ # comparison must take into account fieldname length + one space
+ # + 2 quotes (eg. msgid "<string>")
+ flength = len(fieldname) + 3
+ if plural_index:
+ flength += len(plural_index)
+ real_wrapwidth = wrapwidth - flength + specialchars_count
+ if wrapwidth > 0 and len(field) > real_wrapwidth:
+ # Wrap the line but take field name into account
+ lines = [''] + [unescape(item) for item in wrap(
+ escaped_field,
+ wrapwidth - 2, # 2 for quotes ""
+ drop_whitespace=False,
+ break_long_words=False
+ )]
+ else:
+ lines = [field]
+ if fieldname.startswith('previous_'):
+ # quick and dirty trick to get the real field name
+ fieldname = fieldname[9:]
+
+ ret = ['%s%s%s "%s"' % (delflag, fieldname, plural_index,
+ escape(lines.pop(0)))]
+ for line in lines:
+ ret.append('%s"%s"' % (delflag, escape(line)))
+ return ret
+# }}}
+# class POEntry {{{
+
+
+class POEntry(_BaseEntry):
+ """
+ Represents a po file entry.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor, accepts the following keyword arguments:
+
+ ``comment``
+ string, the entry comment.
+
+ ``tcomment``
+ string, the entry translator comment.
+
+ ``occurrences``
+ list, the entry occurrences.
+
+ ``flags``
+ list, the entry flags.
+
+ ``previous_msgctxt``
+ string, the entry previous context.
+
+ ``previous_msgid``
+ string, the entry previous msgid.
+
+ ``previous_msgid_plural``
+ string, the entry previous msgid_plural.
+
+ ``linenum``
+ integer, the line number of the entry
+ """
+ _BaseEntry.__init__(self, *args, **kwargs)
+ self.comment = kwargs.get('comment', '')
+ self.tcomment = kwargs.get('tcomment', '')
+ self.occurrences = kwargs.get('occurrences', [])
+ self.flags = kwargs.get('flags', [])
+ self.previous_msgctxt = kwargs.get('previous_msgctxt', None)
+ self.previous_msgid = kwargs.get('previous_msgid', None)
+ self.previous_msgid_plural = kwargs.get('previous_msgid_plural', None)
+ self.linenum = kwargs.get('linenum', None)
+
+ def __unicode__(self, wrapwidth=0):
+ """
+ Returns the unicode representation of the entry.
+ """
+ ret = []
+ # comments first, if any (with text wrapping as xgettext does)
+ if self.obsolete:
+ comments = [('tcomment', '# ')]
+ else:
+ comments = [('comment', '#. '), ('tcomment', '# ')]
+ for c in comments:
+ val = getattr(self, c[0])
+ if val:
+ for comment in val.split('\n'):
+ if wrapwidth > 0 and len(comment) + len(c[1]) > wrapwidth:
+ ret += wrap(
+ comment,
+ wrapwidth,
+ initial_indent=c[1],
+ subsequent_indent=c[1],
+ break_long_words=False
+ )
+ else:
+ ret.append('%s%s' % (c[1], comment))
+
+ # occurrences (with text wrapping as xgettext does)
+ if not self.obsolete and self.occurrences:
+ filelist = []
+ for fpath, lineno in self.occurrences:
+ if lineno:
+ filelist.append('%s:%s' % (fpath, lineno))
+ else:
+ filelist.append(fpath)
+ filestr = ' '.join(filelist)
+ if wrapwidth > 0 and len(filestr) + 3 > wrapwidth:
+ # textwrap split words that contain hyphen, this is not
+ # what we want for filenames, so the dirty hack is to
+ # temporally replace hyphens with a char that a file cannot
+ # contain, like "*"
+ ret += [l.replace('*', '-') for l in wrap(
+ filestr.replace('-', '*'),
+ wrapwidth,
+ initial_indent='#: ',
+ subsequent_indent='#: ',
+ break_long_words=False
+ )]
+ else:
+ ret.append('#: ' + filestr)
+
+ # flags (TODO: wrapping ?)
+ if self.flags:
+ ret.append('#, %s' % ', '.join(self.flags))
+
+ # previous context and previous msgid/msgid_plural
+ fields = ['previous_msgctxt', 'previous_msgid',
+ 'previous_msgid_plural']
+ if self.obsolete:
+ prefix = "#~| "
+ else:
+ prefix = "#| "
+ for f in fields:
+ val = getattr(self, f)
+ if val:
+ ret += self._str_field(f, prefix, "", val, wrapwidth)
+
+ ret.append(_BaseEntry.__unicode__(self, wrapwidth))
+ ret = u('\n').join(ret)
+ return ret
+
+ def __cmp__(self, other):
+ """
+ Called by comparison operations if rich comparison is not defined.
+ """
+
+ # First: Obsolete test
+ if self.obsolete != other.obsolete:
+ if self.obsolete:
+ return -1
+ else:
+ return 1
+ # Work on a copy to protect original
+ occ1 = sorted(self.occurrences[:])
+ occ2 = sorted(other.occurrences[:])
+ pos = 0
+ for entry1 in occ1:
+ try:
+ entry2 = occ2[pos]
+ except IndexError:
+ return 1
+ pos = pos + 1
+ if entry1[0] != entry2[0]:
+ if entry1[0] > entry2[0]:
+ return 1
+ else:
+ return -1
+ if entry1[1] != entry2[1]:
+ if entry1[1] > entry2[1]:
+ return 1
+ else:
+ return -1
+ # Compare msgid_plural if set
+ if self.msgid_plural:
+ if not other.msgid_plural:
+ return 1
+ for pos in self.msgid_plural:
+ if pos not in other.msgid_plural:
+ return 1
+ if self.msgid_plural[pos] > other.msgid_plural[pos]:
+ return 1
+ if self.msgid_plural[pos] < other.msgid_plural[pos]:
+ return -1
+ # Finally: Compare message ID
+ if self.msgid > other.msgid:
+ return 1
+ elif self.msgid < other.msgid:
+ return -1
+ return 0
+
+ def __gt__(self, other):
+ return self.__cmp__(other) > 0
+
+ def __lt__(self, other):
+ return self.__cmp__(other) < 0
+
+ def __ge__(self, other):
+ return self.__cmp__(other) >= 0
+
+ def __le__(self, other):
+ return self.__cmp__(other) <= 0
+
+ def __eq__(self, other):
+ return self.__cmp__(other) == 0
+
+ def __ne__(self, other):
+ return self.__cmp__(other) != 0
+
+ def translated(self):
+ """
+ Returns ``True`` if the entry has been translated or ``False``
+ otherwise.
+ """
+ if self.obsolete or 'fuzzy' in self.flags:
+ return False
+ if self.msgstr != '':
+ return True
+ if self.msgstr_plural:
+ for pos in self.msgstr_plural:
+ if self.msgstr_plural[pos] == '':
+ return False
+ return True
+ return False
+
+ def merge(self, other):
+ """
+ Merge the current entry with the given pot entry.
+ """
+ self.msgid = other.msgid
+ self.msgctxt = other.msgctxt
+ self.occurrences = other.occurrences
+ self.comment = other.comment
+ fuzzy = 'fuzzy' in self.flags
+ self.flags = other.flags[:] # clone flags
+ if fuzzy:
+ self.flags.append('fuzzy')
+ self.msgid_plural = other.msgid_plural
+ self.obsolete = other.obsolete
+ self.previous_msgctxt = other.previous_msgctxt
+ self.previous_msgid = other.previous_msgid
+ self.previous_msgid_plural = other.previous_msgid_plural
+ if other.msgstr_plural:
+ for pos in other.msgstr_plural:
+ try:
+ # keep existing translation at pos if any
+ self.msgstr_plural[pos]
+ except KeyError:
+ self.msgstr_plural[pos] = ''
+
+ def __hash__(self):
+ return hash((self.msgid, self.msgstr))
+# }}}
+# class MOEntry {{{
+
+
+class MOEntry(_BaseEntry):
+ """
+ Represents a mo file entry.
+ """
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor, accepts the following keyword arguments,
+ for consistency with :class:`~polib.POEntry`:
+
+ ``comment``
+ ``tcomment``
+ ``occurrences``
+ ``flags``
+ ``previous_msgctxt``
+ ``previous_msgid``
+ ``previous_msgid_plural``
+
+ Note: even though these keyword arguments are accepted,
+ they hold no real meaning in the context of MO files
+ and are simply ignored.
+ """
+ _BaseEntry.__init__(self, *args, **kwargs)
+ self.comment = ''
+ self.tcomment = ''
+ self.occurrences = []
+ self.flags = []
+ self.previous_msgctxt = None
+ self.previous_msgid = None
+ self.previous_msgid_plural = None
+
+ def __hash__(self):
+ return hash((self.msgid, self.msgstr))
+
+# }}}
+# class _POFileParser {{{
+
+
+class _POFileParser(object):
+ """
+ A finite state machine to parse efficiently and correctly po
+ file format.
+ """
+
+ def __init__(self, pofile, *args, **kwargs):
+ """
+ Constructor.
+
+ Keyword arguments:
+
+ ``pofile``
+ string, path to the po file or its content
+
+ ``encoding``
+ string, the encoding to use, defaults to ``default_encoding``
+ global variable (optional).
+
+ ``check_for_duplicates``
+ whether to check for duplicate entries when adding entries to the
+ file (optional, default: ``False``).
+ """
+ enc = kwargs.get('encoding', default_encoding)
+ if _is_file(pofile):
+ try:
+ self.fhandle = io.open(pofile, 'rt', encoding=enc)
+ except LookupError:
+ enc = default_encoding
+ self.fhandle = io.open(pofile, 'rt', encoding=enc)
+ else:
+ self.fhandle = pofile.splitlines()
+
+ klass = kwargs.get('klass')
+ if klass is None:
+ klass = POFile
+ self.instance = klass(
+ pofile=pofile,
+ encoding=enc,
+ check_for_duplicates=kwargs.get('check_for_duplicates', False)
+ )
+ self.transitions = {}
+ self.current_line = 0
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_state = 'st'
+ self.current_token = None
+ # two memo flags used in handlers
+ self.msgstr_index = 0
+ self.entry_obsolete = 0
+ # Configure the state machine, by adding transitions.
+ # Signification of symbols:
+ # * ST: Beginning of the file (start)
+ # * HE: Header
+ # * TC: a translation comment
+ # * GC: a generated comment
+ # * OC: a file/line occurrence
+ # * FL: a flags line
+ # * CT: a message context
+ # * PC: a previous msgctxt
+ # * PM: a previous msgid
+ # * PP: a previous msgid_plural
+ # * MI: a msgid
+ # * MP: a msgid plural
+ # * MS: a msgstr
+ # * MX: a msgstr plural
+ # * MC: a msgid or msgstr continuation line
+ all = ['st', 'he', 'gc', 'oc', 'fl', 'ct', 'pc', 'pm', 'pp', 'tc',
+ 'ms', 'mp', 'mx', 'mi']
+
+ self.add('tc', ['st', 'he'], 'he')
+ self.add('tc', ['gc', 'oc', 'fl', 'tc', 'pc', 'pm', 'pp', 'ms',
+ 'mp', 'mx', 'mi'], 'tc')
+ self.add('gc', all, 'gc')
+ self.add('oc', all, 'oc')
+ self.add('fl', all, 'fl')
+ self.add('pc', all, 'pc')
+ self.add('pm', all, 'pm')
+ self.add('pp', all, 'pp')
+ self.add('ct', ['st', 'he', 'gc', 'oc', 'fl', 'tc', 'pc', 'pm',
+ 'pp', 'ms', 'mx'], 'ct')
+ self.add('mi', ['st', 'he', 'gc', 'oc', 'fl', 'ct', 'tc', 'pc',
+ 'pm', 'pp', 'ms', 'mx'], 'mi')
+ self.add('mp', ['tc', 'gc', 'pc', 'pm', 'pp', 'mi'], 'mp')
+ self.add('ms', ['mi', 'mp', 'tc'], 'ms')
+ self.add('mx', ['mi', 'mx', 'mp', 'tc'], 'mx')
+ self.add('mc', ['ct', 'mi', 'mp', 'ms', 'mx', 'pm', 'pp', 'pc'], 'mc')
+
+ def parse(self):
+ """
+ Run the state machine, parse the file line by line and call process()
+ with the current matched symbol.
+ """
+
+ keywords = {
+ 'msgctxt': 'ct',
+ 'msgid': 'mi',
+ 'msgstr': 'ms',
+ 'msgid_plural': 'mp',
+ }
+ prev_keywords = {
+ 'msgid_plural': 'pp',
+ 'msgid': 'pm',
+ 'msgctxt': 'pc',
+ }
+ tokens = []
+ for line in self.fhandle:
+ self.current_line += 1
+ line = line.strip()
+ if line == '':
+ continue
+
+ tokens = line.split(None, 2)
+ nb_tokens = len(tokens)
+
+ if tokens[0] == '#~|':
+ continue
+
+ if tokens[0] == '#~' and nb_tokens > 1:
+ line = line[3:].strip()
+ tokens = tokens[1:]
+ nb_tokens -= 1
+ self.entry_obsolete = 1
+ else:
+ self.entry_obsolete = 0
+
+ # Take care of keywords like
+ # msgid, msgid_plural, msgctxt & msgstr.
+ if tokens[0] in keywords and nb_tokens > 1:
+ line = line[len(tokens[0]):].lstrip()
+ if re.search(r'([^\\]|^)"', line[1:-1]):
+ raise IOError('Syntax error in po file %s (line %s): '
+ 'unescaped double quote found' %
+ (self.instance.fpath, self.current_line))
+ self.current_token = line
+ self.process(keywords[tokens[0]])
+ continue
+
+ self.current_token = line
+
+ if tokens[0] == '#:':
+ if nb_tokens <= 1:
+ continue
+ # we are on an occurrences line
+ self.process('oc')
+
+ elif line[:1] == '"':
+ # we are on a continuation line
+ if re.search(r'([^\\]|^)"', line[1:-1]):
+ raise IOError('Syntax error in po file %s (line %s): '
+ 'unescaped double quote found' %
+ (self.instance.fpath, self.current_line))
+ self.process('mc')
+
+ elif line[:7] == 'msgstr[':
+ # we are on a msgstr plural
+ self.process('mx')
+
+ elif tokens[0] == '#,':
+ if nb_tokens <= 1:
+ continue
+ # we are on a flags line
+ self.process('fl')
+
+ elif tokens[0] == '#' or tokens[0].startswith('##'):
+ if line == '#':
+ line += ' '
+ # we are on a translator comment line
+ self.process('tc')
+
+ elif tokens[0] == '#.':
+ if nb_tokens <= 1:
+ continue
+ # we are on a generated comment line
+ self.process('gc')
+
+ elif tokens[0] == '#|':
+ if nb_tokens <= 1:
+ raise IOError('Syntax error in po file %s (line %s)' %
+ (self.instance.fpath, self.current_line))
+
+ # Remove the marker and any whitespace right after that.
+ line = line[2:].lstrip()
+ self.current_token = line
+
+ if tokens[1].startswith('"'):
+ # Continuation of previous metadata.
+ self.process('mc')
+ continue
+
+ if nb_tokens == 2:
+ # Invalid continuation line.
+ raise IOError('Syntax error in po file %s (line %s): '
+ 'invalid continuation line' %
+ (self.instance.fpath, self.current_line))
+
+ # we are on a "previous translation" comment line,
+ if tokens[1] not in prev_keywords:
+ # Unknown keyword in previous translation comment.
+ raise IOError('Syntax error in po file %s (line %s): '
+ 'unknown keyword %s' %
+ (self.instance.fpath, self.current_line,
+ tokens[1]))
+
+ # Remove the keyword and any whitespace
+ # between it and the starting quote.
+ line = line[len(tokens[1]):].lstrip()
+ self.current_token = line
+ self.process(prev_keywords[tokens[1]])
+
+ else:
+ raise IOError('Syntax error in po file %s (line %s)' %
+ (self.instance.fpath, self.current_line))
+
+ if self.current_entry and len(tokens) > 0 and \
+ not tokens[0].startswith('#'):
+ # since entries are added when another entry is found, we must add
+ # the last entry here (only if there are lines). Trailing comments
+ # are ignored
+ self.instance.append(self.current_entry)
+
+ # before returning the instance, check if there's metadata and if
+ # so extract it in a dict
+ metadataentry = self.instance.find('')
+ if metadataentry: # metadata found
+ # remove the entry
+ self.instance.remove(metadataentry)
+ self.instance.metadata_is_fuzzy = metadataentry.flags
+ key = None
+ for msg in metadataentry.msgstr.splitlines():
+ try:
+ key, val = msg.split(':', 1)
+ self.instance.metadata[key] = val.strip()
+ except (ValueError, KeyError):
+ if key is not None:
+ self.instance.metadata[key] += '\n' + msg.strip()
+ # close opened file
+ if not isinstance(self.fhandle, list): # must be file
+ self.fhandle.close()
+ return self.instance
+
+ def add(self, symbol, states, next_state):
+ """
+ Add a transition to the state machine.
+
+ Keywords arguments:
+
+ ``symbol``
+ string, the matched token (two chars symbol).
+
+ ``states``
+ list, a list of states (two chars symbols).
+
+ ``next_state``
+ the next state the fsm will have after the action.
+ """
+ for state in states:
+ action = getattr(self, 'handle_%s' % next_state)
+ self.transitions[(symbol, state)] = (action, next_state)
+
+ def process(self, symbol):
+ """
+ Process the transition corresponding to the current state and the
+ symbol provided.
+
+ Keywords arguments:
+
+ ``symbol``
+ string, the matched token (two chars symbol).
+
+ ``linenum``
+ integer, the current line number of the parsed file.
+ """
+ try:
+ (action, state) = self.transitions[(symbol, self.current_state)]
+ if action():
+ self.current_state = state
+ except Exception:
+ raise IOError('Syntax error in po file (line %s)' %
+ self.current_line)
+
+ # state handlers
+
+ def handle_he(self):
+ """Handle a header comment."""
+ if self.instance.header != '':
+ self.instance.header += '\n'
+ self.instance.header += self.current_token[2:]
+ return 1
+
+ def handle_tc(self):
+ """Handle a translator comment."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ if self.current_entry.tcomment != '':
+ self.current_entry.tcomment += '\n'
+ tcomment = self.current_token.lstrip('#')
+ if tcomment.startswith(' '):
+ tcomment = tcomment[1:]
+ self.current_entry.tcomment += tcomment
+ return True
+
+ def handle_gc(self):
+ """Handle a generated comment."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ if self.current_entry.comment != '':
+ self.current_entry.comment += '\n'
+ self.current_entry.comment += self.current_token[3:]
+ return True
+
+ def handle_oc(self):
+ """Handle a file:num occurrence."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ occurrences = self.current_token[3:].split()
+ for occurrence in occurrences:
+ if occurrence != '':
+ try:
+ fil, line = occurrence.rsplit(':', 1)
+ if not line.isdigit():
+ fil = fil + line
+ line = ''
+ self.current_entry.occurrences.append((fil, line))
+ except (ValueError, AttributeError):
+ self.current_entry.occurrences.append((occurrence, ''))
+ return True
+
+ def handle_fl(self):
+ """Handle a flags line."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_entry.flags += [c.strip() for c in
+ self.current_token[3:].split(',')]
+ return True
+
+ def handle_pp(self):
+ """Handle a previous msgid_plural line."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_entry.previous_msgid_plural = \
+ unescape(self.current_token[1:-1])
+ return True
+
+ def handle_pm(self):
+ """Handle a previous msgid line."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_entry.previous_msgid = \
+ unescape(self.current_token[1:-1])
+ return True
+
+ def handle_pc(self):
+ """Handle a previous msgctxt line."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_entry.previous_msgctxt = \
+ unescape(self.current_token[1:-1])
+ return True
+
+ def handle_ct(self):
+ """Handle a msgctxt."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_entry.msgctxt = unescape(self.current_token[1:-1])
+ return True
+
+ def handle_mi(self):
+ """Handle a msgid."""
+ if self.current_state in ['mc', 'ms', 'mx']:
+ self.instance.append(self.current_entry)
+ self.current_entry = POEntry(linenum=self.current_line)
+ self.current_entry.obsolete = self.entry_obsolete
+ self.current_entry.msgid = unescape(self.current_token[1:-1])
+ return True
+
+ def handle_mp(self):
+ """Handle a msgid plural."""
+ self.current_entry.msgid_plural = unescape(self.current_token[1:-1])
+ return True
+
+ def handle_ms(self):
+ """Handle a msgstr."""
+ self.current_entry.msgstr = unescape(self.current_token[1:-1])
+ return True
+
+ def handle_mx(self):
+ """Handle a msgstr plural."""
+ index = self.current_token[7]
+ value = self.current_token[self.current_token.find('"') + 1:-1]
+ self.current_entry.msgstr_plural[int(index)] = unescape(value)
+ self.msgstr_index = int(index)
+ return True
+
+ def handle_mc(self):
+ """Handle a msgid or msgstr continuation line."""
+ token = unescape(self.current_token[1:-1])
+ if self.current_state == 'ct':
+ self.current_entry.msgctxt += token
+ elif self.current_state == 'mi':
+ self.current_entry.msgid += token
+ elif self.current_state == 'mp':
+ self.current_entry.msgid_plural += token
+ elif self.current_state == 'ms':
+ self.current_entry.msgstr += token
+ elif self.current_state == 'mx':
+ self.current_entry.msgstr_plural[self.msgstr_index] += token
+ elif self.current_state == 'pp':
+ self.current_entry.previous_msgid_plural += token
+ elif self.current_state == 'pm':
+ self.current_entry.previous_msgid += token
+ elif self.current_state == 'pc':
+ self.current_entry.previous_msgctxt += token
+ # don't change the current state
+ return False
+# }}}
+# class _MOFileParser {{{
+
+
+class _MOFileParser(object):
+ """
+ A class to parse binary mo files.
+ """
+
+ def __init__(self, mofile, *args, **kwargs):
+ """
+ Constructor.
+
+ Keyword arguments:
+
+ ``mofile``
+ string, path to the mo file or its content
+
+ ``encoding``
+ string, the encoding to use, defaults to ``default_encoding``
+ global variable (optional).
+
+ ``check_for_duplicates``
+ whether to check for duplicate entries when adding entries to the
+ file (optional, default: ``False``).
+ """
+ self.fhandle = open(mofile, 'rb')
+
+ klass = kwargs.get('klass')
+ if klass is None:
+ klass = MOFile
+ self.instance = klass(
+ fpath=mofile,
+ encoding=kwargs.get('encoding', default_encoding),
+ check_for_duplicates=kwargs.get('check_for_duplicates', False)
+ )
+
+ def __del__(self):
+ """
+ Make sure the file is closed, this prevents warnings on unclosed file
+ when running tests with python >= 3.2.
+ """
+ if self.fhandle:
+ self.fhandle.close()
+
+ def parse(self):
+ """
+ Build the instance with the file handle provided in the
+ constructor.
+ """
+ # parse magic number
+ magic_number = self._readbinary('<I', 4)
+ if magic_number == MOFile.MAGIC:
+ ii = '<II'
+ elif magic_number == MOFile.MAGIC_SWAPPED:
+ ii = '>II'
+ else:
+ raise IOError('Invalid mo file, magic number is incorrect !')
+ self.instance.magic_number = magic_number
+ # parse the version number and the number of strings
+ version, numofstrings = self._readbinary(ii, 8)
+ # from MO file format specs: "A program seeing an unexpected major
+ # revision number should stop reading the MO file entirely"
+ if version not in (0, 1):
+ raise IOError('Invalid mo file, unexpected major revision number')
+ self.instance.version = version
+ # original strings and translation strings hash table offset
+ msgids_hash_offset, msgstrs_hash_offset = self._readbinary(ii, 8)
+ # move to msgid hash table and read length and offset of msgids
+ self.fhandle.seek(msgids_hash_offset)
+ msgids_index = []
+ for i in range(numofstrings):
+ msgids_index.append(self._readbinary(ii, 8))
+ # move to msgstr hash table and read length and offset of msgstrs
+ self.fhandle.seek(msgstrs_hash_offset)
+ msgstrs_index = []
+ for i in range(numofstrings):
+ msgstrs_index.append(self._readbinary(ii, 8))
+ # build entries
+ encoding = self.instance.encoding
+ for i in range(numofstrings):
+ self.fhandle.seek(msgids_index[i][1])
+ msgid = self.fhandle.read(msgids_index[i][0])
+
+ self.fhandle.seek(msgstrs_index[i][1])
+ msgstr = self.fhandle.read(msgstrs_index[i][0])
+ if i == 0 and not msgid: # metadata
+ raw_metadata, metadata = msgstr.split(b('\n')), {}
+ for line in raw_metadata:
+ tokens = line.split(b(':'), 1)
+ if tokens[0] != b(''):
+ try:
+ k = tokens[0].decode(encoding)
+ v = tokens[1].decode(encoding)
+ metadata[k] = v.strip()
+ except IndexError:
+ metadata[k] = u('')
+ self.instance.metadata = metadata
+ continue
+ # test if we have a plural entry
+ msgid_tokens = msgid.split(b('\0'))
+ if len(msgid_tokens) > 1:
+ entry = self._build_entry(
+ msgid=msgid_tokens[0],
+ msgid_plural=msgid_tokens[1],
+ msgstr_plural=dict((k, v) for k, v in
+ enumerate(msgstr.split(b('\0'))))
+ )
+ else:
+ entry = self._build_entry(msgid=msgid, msgstr=msgstr)
+ self.instance.append(entry)
+ # close opened file
+ self.fhandle.close()
+ return self.instance
+
+ def _build_entry(self, msgid, msgstr=None, msgid_plural=None,
+ msgstr_plural=None):
+ msgctxt_msgid = msgid.split(b('\x04'))
+ encoding = self.instance.encoding
+ if len(msgctxt_msgid) > 1:
+ kwargs = {
+ 'msgctxt': msgctxt_msgid[0].decode(encoding),
+ 'msgid': msgctxt_msgid[1].decode(encoding),
+ }
+ else:
+ kwargs = {'msgid': msgid.decode(encoding)}
+ if msgstr:
+ kwargs['msgstr'] = msgstr.decode(encoding)
+ if msgid_plural:
+ kwargs['msgid_plural'] = msgid_plural.decode(encoding)
+ if msgstr_plural:
+ for k in msgstr_plural:
+ msgstr_plural[k] = msgstr_plural[k].decode(encoding)
+ kwargs['msgstr_plural'] = msgstr_plural
+ return MOEntry(**kwargs)
+
+ def _readbinary(self, fmt, numbytes):
+ """
+ Private method that unpack n bytes of data using format <fmt>.
+ It returns a tuple or a mixed value if the tuple length is 1.
+ """
+ bytes = self.fhandle.read(numbytes)
+ tup = struct.unpack(fmt, bytes)
+ if len(tup) == 1:
+ return tup[0]
+ return tup
+# }}}
+# class TextWrapper {{{
+
+
+class TextWrapper(textwrap.TextWrapper):
+ """
+ Subclass of textwrap.TextWrapper that backport the
+ drop_whitespace option.
+ """
+ def __init__(self, *args, **kwargs):
+ drop_whitespace = kwargs.pop('drop_whitespace', True)
+ textwrap.TextWrapper.__init__(self, *args, **kwargs)
+ self.drop_whitespace = drop_whitespace
+
+ def _wrap_chunks(self, chunks):
+ """_wrap_chunks(chunks : [string]) -> [string]
+
+ Wrap a sequence of text chunks and return a list of lines of
+ length 'self.width' or less. (If 'break_long_words' is false,
+ some lines may be longer than this.) Chunks correspond roughly
+ to words and the whitespace between them: each chunk is
+ indivisible (modulo 'break_long_words'), but a line break can
+ come between any two chunks. Chunks should not have internal
+ whitespace; ie. a chunk is either all whitespace or a "word".
+ Whitespace chunks will be removed from the beginning and end of
+ lines, but apart from that whitespace is preserved.
+ """
+ lines = []
+ if self.width <= 0:
+ raise ValueError("invalid width %r (must be > 0)" % self.width)
+
+ # Arrange in reverse order so items can be efficiently popped
+ # from a stack of chucks.
+ chunks.reverse()
+
+ while chunks:
+
+ # Start the list of chunks that will make up the current line.
+ # cur_len is just the length of all the chunks in cur_line.
+ cur_line = []
+ cur_len = 0
+
+ # Figure out which static string will prefix this line.
+ if lines:
+ indent = self.subsequent_indent
+ else:
+ indent = self.initial_indent
+
+ # Maximum width for this line.
+ width = self.width - len(indent)
+
+ # First chunk on line is whitespace -- drop it, unless this
+ # is the very beginning of the text (ie. no lines started yet).
+ if self.drop_whitespace and chunks[-1].strip() == '' and lines:
+ del chunks[-1]
+
+ while chunks:
+ l = len(chunks[-1])
+
+ # Can at least squeeze this chunk onto the current line.
+ if cur_len + l <= width:
+ cur_line.append(chunks.pop())
+ cur_len += l
+
+ # Nope, this line is full.
+ else:
+ break
+
+ # The current line is full, and the next chunk is too big to
+ # fit on *any* line (not just this one).
+ if chunks and len(chunks[-1]) > width:
+ self._handle_long_word(chunks, cur_line, cur_len, width)
+
+ # If the last chunk on this line is all whitespace, drop it.
+ if self.drop_whitespace and cur_line and not cur_line[-1].strip():
+ del cur_line[-1]
+
+ # Convert current line back to a string and store it in list
+ # of all lines (return value).
+ if cur_line:
+ lines.append(indent + ''.join(cur_line))
+
+ return lines
+# }}}
+# function wrap() {{{
+
+
+def wrap(text, width=70, **kwargs):
+ """
+ Wrap a single paragraph of text, returning a list of wrapped lines.
+ """
+ if sys.version_info < (2, 6):
+ return TextWrapper(width=width, **kwargs).wrap(text)
+ return textwrap.wrap(text, width=width, **kwargs)
+
+# }}}
+
+def genKeyId(inkey):
+ crc = binascii.crc32(bytes(inkey, encoding="UTF-8")) & 0xffffffff
+ # Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs
+ symbols = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
+ outkey = ""
+ for keyind in range(0, 5):
+ outkey += symbols[(crc & 63) % len(symbols)];
+ crc >>= 6;
+ return outkey
diff --git a/solenv/bin/pre2par.pl b/solenv/bin/pre2par.pl
new file mode 100644
index 0000000000..28ca73312d
--- /dev/null
+++ b/solenv/bin/pre2par.pl
@@ -0,0 +1,62 @@
+#
+# 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 .
+#
+
+use lib ("$ENV{SRCDIR}/solenv/bin/modules");
+
+use Cwd;
+use pre2par::directory;
+use pre2par::files;
+use pre2par::globals;
+use pre2par::language;
+use pre2par::parameter;
+use pre2par::work;
+
+####################################
+# Main program
+####################################
+
+pre2par::parameter::getparameter();
+pre2par::parameter::control_parameter();
+
+pre2par::directory::check_directory($pre2par::globals::parfilename);
+
+my $prefile = pre2par::files::read_file($pre2par::globals::prefilename);
+
+pre2par::work::check_content($prefile, $pre2par::globals::prefilename);
+
+my $parfile = pre2par::work::convert($prefile);
+
+pre2par::work::formatter($parfile);
+
+my $langfilename = $pre2par::globals::langfilename;
+
+if ( -f $langfilename )
+{
+ my $langfile = pre2par::files::read_file($langfilename);
+ pre2par::language::localize($parfile, $langfile);
+}
+
+pre2par::files::save_file($pre2par::globals::parfilename, $parfile);
+
+# checking of par file was written correctly
+my $parfilecomp = pre2par::files::read_file($pre2par::globals::parfilename);
+pre2par::work::diff_content($parfile, $parfilecomp, $pre2par::globals::parfilename);
+
+####################################
+# End main program
+####################################
diff --git a/solenv/bin/run-configure b/solenv/bin/run-configure
new file mode 100755
index 0000000000..9758ff1d86
--- /dev/null
+++ b/solenv/bin/run-configure
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+# We could run emconfigure here, but LO's gbuild should have set up everything
+# correctly. If something breaks because of this, we likely have mre problems.
+if test "$OS" = "EMSCRIPTEN"; then
+ export EMMAKEN_JUST_CONFIGURE=1
+fi
+
+exec "$@"
diff --git a/solenv/bin/uiex b/solenv/bin/uiex
new file mode 100755
index 0000000000..b9344c4295
--- /dev/null
+++ b/solenv/bin/uiex
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+import polib
+import binascii
+import getopt
+import sys
+import os.path
+from subprocess import check_output
+
+try:
+ myopts, args = getopt.getopt(sys.argv[1:], "i:o:")
+except getopt.GetoptError as e:
+ print(" Syntax: uiex -i FileIn -o FileOut")
+ print(" FileIn: Source files (*.ui)")
+ print(" FileOut: Destination file (*.*)")
+ sys.exit(2)
+
+for o, a in myopts:
+ if o == '-i':
+ ifile = a
+ elif o == '-o':
+ ofile = a
+
+with open(ofile, "a") as output:
+ input = check_output(["xgettext", "--add-comments", "--no-wrap", ifile, "-o", "-"], encoding="UTF-8")
+ po = polib.pofile(input)
+ if len(po) != 0:
+ print("", file=output)
+ for entry in po:
+ # skip 'stock' entries like "cancel", "help", "ok", etc
+ # l10ntools/source/localize.cxx will insert one entry for each stock per .po
+ if entry.msgctxt == "stock":
+ continue
+ keyid = entry.msgctxt + '|' + entry.msgid
+ print('#. ' + polib.genKeyId(keyid), file=output)
+ for i, occurrence in enumerate(entry.occurrences):
+ entry.occurrences[i] = os.path.relpath(occurrence[0], os.environ['SRCDIR']), occurrence[1]
+ print(entry, file=output)
diff --git a/solenv/bin/uiimagelist.xsl b/solenv/bin/uiimagelist.xsl
new file mode 100644
index 0000000000..659d3b943a
--- /dev/null
+++ b/solenv/bin/uiimagelist.xsl
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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/.
+ *
+-->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="text"/>
+
+ <xsl:template match="/|node()">
+ <xsl:apply-templates/>
+ </xsl:template>
+
+ <xsl:template match="@*|text()|processing-instruction()|comment()"/>
+
+ <xsl:template match="property[@name='icon_name'] | property[@name='icon-name'] | property[@name='pixbuf']">
+ <xsl:variable name="inpath" select="normalize-space(.)"/>
+ <xsl:variable name="outpath">
+ <xsl:choose>
+ <xsl:when test="starts-with($inpath,'res/')">
+ <xsl:value-of select="concat('%GLOBALRES%/',substring-after($inpath,'res/'))"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat('%MODULE%/',$inpath)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:value-of select="$outpath"/>
+ <xsl:text>&#xA;</xsl:text>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/solenv/bin/version.py b/solenv/bin/version.py
new file mode 100755
index 0000000000..3193b3fa39
--- /dev/null
+++ b/solenv/bin/version.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+from optparse import OptionParser
+import os.path
+import re
+import sys
+
+M = {
+ 'juh': 'javaunohelper',
+ 'jurt': 'jurt',
+ 'officebean': 'bean',
+ 'ridl': 'ridljar',
+ 'unoil': 'unoil',
+ 'unoloader': 'ridljar',
+ 'libreoffice': 'ridljar',
+}
+
+parser = OptionParser()
+_, args = parser.parse_args()
+
+if not len(args):
+ parser.error('not enough arguments')
+elif len(args) > 1:
+ parser.error('too many arguments')
+
+DEST = r'\g<1>%s\g<3>' % args[0]
+
+
+def replace_in_file(filename, src_pattern):
+ try:
+ f = open(filename, "r")
+ s = f.read()
+ f.close()
+ s = re.sub(src_pattern, DEST, s)
+ f = open(filename, "w")
+ f.write(s)
+ f.close()
+ except IOError as err:
+ print('error updating %s: %s' % (filename, err), file=sys.stderr)
+
+src_pattern = re.compile(r'^(\s*<version>)([-.@\w]+)(</version>\s*)$',
+ re.MULTILINE)
+
+for a in ['juh', 'jurt', 'libreoffice', 'officebean', 'ridl', 'unoil', 'unoloader']:
+ replace_in_file(os.path.join(M[a], 'pom.%s.xml' % a), src_pattern)
+
+src_pattern = re.compile(r"^(LIBREOFFICE_VERSION = ')([-.@\w]+)(')$",
+ re.MULTILINE)
+replace_in_file('solenv/maven/VERSION', src_pattern)
diff --git a/solenv/bin/write_classpath.sh b/solenv/bin/write_classpath.sh
new file mode 100755
index 0000000000..4b9fd6bcbb
--- /dev/null
+++ b/solenv/bin/write_classpath.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+dest="$1"
+shift
+base='Class-Path: '
+
+while [ "${1}" != "" ]; do
+ p="$1"
+ shift
+ echo "$base $p" >> $dest
+ base=' '
+done
+
+#echo "added classpath"
+#cat $dest
+#echo "==="