[wpkg-users] Dealing with localized apps

Rainer Meier r.meier at wpkg.org
Tue Jul 15 20:13:08 CEST 2008


Hi Nico,

Nico de Haer wrote:
> Hi all,
> 
> It seems that there is a shortcomming in wpkg when you are faced with
> a package that depends on an application that comes in different
> languages. For example:
> 
> Package: firefox-plugin-something, depends (obviously) on firefox,
> but: I have two packages for that: firefox3-en and firefox3-nl (dutch
> version)....

Here I use another approach - no need to create multiple Firefox 
packages when there is only one application (Firefox) but with different 
languages. But more about it later.


> The fastest 'fix' to me seems to be a concept that the packaging
> system of Debian uses called virtual packages (like 'httpd' for
> webservers). Any package that is a webserver (apache, light-httpd)
> have a tag that reads: 'provides: httpd' and other packages that need
> a webserver simply depend on 'httpd' instead of 'apache'. The same can
> be done for wpkg by adding an xml tag called 'provides' - It would
> look something like this:
> 
> <package id="firefox3-nl" provides="firefox3" name="Mozilla Firefox 3
> (Dutch version)" revision="3000" reboot="false" priority="10">
> <package id="firefox3-en" provides="firefox3" name="Mozilla Firefox 3
> (English version)" revision="3000" reboot="false" priority="10">
> <package id="firefox-plugin-something" depends="firefox3" name="Cool
> plugin for firefox 3" revision="100" reboot="false" priority="10">

I know about such "virtual" packages. Not only Debian is using such an 
approach. However it has some drawbacks too. Especially for WPKG which 
has to select packages from the package database without any possibility 
to ask the user.
For example it might happen that a package to be installed depends on a 
virtual software "xy". Multiple packages providing "xy" are available 
within the package database. So which one should be installed? If none 
of them can be selected automatically, then the dependency cannot be 
fulfilled.

The best approach would probably be something as follows:
- check package database for exact match of dependency
- if found, then install it
- if not found then check current profile for any assigned package
   providing "xy"
- if found, then install this package first
- if multiple packages are providing "xy" then just install one of
   them before applying the package which depends on it
- if no such package is assigned to the profile log an error (dependency
   not fulfilled) and skip the package which depends on it.

This is slightly different than the current dependency implementation 
which will simply install the package it depends on no matter if it is 
within the profile or not.


> An other (but more complex) approach would be to have one package with
> the id 'firefox3' but add more test-logic to install different
> localized versions of firefox pending on the target-system. A kind of
> case construct would work:
> 
> select case locale
>    case nl
>       <install cmd="%SOFTWARE%\Internet\firefox-nl\Firefox Setup 3.0.exe -ms" />
>    case de
>       <install cmd="%SOFTWARE%\Internet\firefox-nl\Firefox Setup 3.0.exe -ms" />
>    case *
>       <install cmd="%SOFTWARE%\Internet\firefox-en\Firefox Setup 3.0.exe -ms" />
> end select

Well I already introduced the concept of LCID to translation messages. I 
also plan to extend WPKG to allow LCID specification for each variable. 
So variables are only set if the current system LCID matches one of the 
listed ones within the variable definition.

The same could be applied to commands (install, upgrade, remove) as 
well. If no LCID is specified this command applies to all languages, if 
an LCID is specified it will be executed only if the system LCID matches 
the one specified.

Hmm, I am not sure yet if this is a good idea, I will think about it. 
Especially it could increase complexity of package definition and 
testing having multiple commands specified and not all of them are 
executed due to non-matching LCID.


> On the other hand, it could be that this is already solved and that I
> missed it while reading the wiki and documentation ;)

I am not sure if something like this documented. But as promised above I 
use a different method to work with multiple languages: Auto detection.

I usually put a cmd script to the program setup folder called 
"unattended.cmd" [1]. This script takes care of calling the correct 
commands to install the application silently. This provides me some 
advantages:
- WPKG commands always just need to call this unattended.cmd
- no special parameters to be coded to the package definition
- The unattended.cmd script can be as complicated as required by the 
application (for example detect running instances, kill them, copy 
files, rename files, run setup, clean up...)

For this purpose I wrote some generic scripts to help me. For example I 
use the attached "unattended.cmd" script for Firefox and many other 
applications.

It will call unattended-de.cmd or unattended-en.cmd depending on the 
system language. Of course it could be extended to much more languages.

The script is generic and does not need to be touched for different 
applications.

Then of course you need unattended-en.cmd which has to install English 
version of the application.

In case of Firefox I could use the following simple script:
unattended-en.cmd
-----------------------------------------------------------------
@echo off

set BINARY=Firefox Setup 3.0 en.exe
set INSTALL_LOC=%~dp0

echo Installing Mozilla FireFox

start /wait "Firefox" "%INSTALL_LOC%%BINARY%" /S
EXIT_CODE=%ERRORLEVEL%

-----------------------------------------------------------------

The changes for "Firefox german" (unattended-de.cmd) just differ in one 
single line, the BINARY variable definition.

The advantage of this approach is that I just need one single "firefox" 
WPKG package which decides by itself which version should be applied to 
the system (depending on system language).

By the way, if you look closely at the "unattended.cmd" script attached 
you will notice that if the variable LANG is already defined before 
unattended.cmd is involved (for example set on <host> node of the 
package deifinition) it will not be re-evaluated. This also allows to 
select a different software language for some hosts (different to the 
host OS language) without changing any script).


And now... another special "trick" I use quite often. You might have 
noticed that most applications are based on 4 basic installers:
- Microsoft Windows Installer (MSI Package)
- Inno-Setup
- InstallShield
- Nullsoft Scriptable Installation System (NSIS)

So I wrote a small Script which is able to work with all installers (and 
is also able to select a different installer for 32- or 64-bit operating 
systems. The script (install.cmd [2]) is attached as well.

You can call it as follows:
install.cmd <type> <32-bit-installer> <64-bit-installer>
             [installer-location [custom-options]]


Then my "unattended-[lng].cmd" just needs to call this installer script 
to select the right installer for the current operating system. This is 
demonstrated by the attached unattended-en.cmd [3] which uses an MSI 
installer.


Huh, sounds quite complicated for beginners but let me summarize it:
- WPKG always calls %SOFTWARE%\appXY\unattended.cmd
- unattended.cmd evaluates the language:
   EN: call unattended-en.cmd (within same directory)
   DE: call unattended-de.cmd (within same directory)
- unattended-en.cmd calls install.cmd with correct parameters
   - install.cmd detects 64/32-bit OS and runs proper installer


Well, the language selection "issue" could be solved by extending WPKG 
(as proposed above). I will think about this. Up to now I was perfectly 
happy with my automatic-detection-scripts as I wrote them once without 
the need to touch them any more. The OS type (32/64-bit) selection would 
still require either a script or another enhancement where I don't know 
exactly what will be the best way to solve it yet.

So I will make a note for possible enhancement to introduce "virtual" 
packages or language-specific install commands. Until it is available 
feel free to use my scripts.


> PS: Did I mention that the idea behind WPKG is excellent and that I
> like it a lot?

I already guessed so because you wrote to the mailing list. Users who 
don't like WPKG usually just go away ;-)

br,
Rainer


[1] unattended.cmd
----------------------------------------------------------------------
@echo off

REM Runs .\unattended-[lang].cmd according to system language.
REM Runs .\unattended-uninstall-[lang].cmd according to system language.

set INSTALL_LOC=%~dp0
set INSTALL_PREFIX=unattended
set UNINSTALL_PREFIX=unattended-uninstall
set LANG_SUFFIX=-en
set SCRIPT_SUFFIX=.cmd
set EXIT_CODE=0
set KEY="hklm\system\controlset001\control\nls\language"

REM Detect language
:select
if "%LANG%" == "en" goto en
if "%LANG%" == "enu" goto en
if "%LANG%" == "de" goto de
if "%LANG%" == "deu" goto de
goto detect

:detect
for /f "Skip=1 Tokens=3*" %%i in ('reg QUERY %KEY% /v Installlanguage') 
do set language=%%i

if "%language%" == "0407" (
   set LANG=de
   goto select
)
if "%language%" == "0409" (
   set LANG=en
   goto select
)
REM if "%ProgramFiles%" == "C:\Programme" (
REM   set LANG=de
REM   goto select
REM )
REM if "%ProgramFiles%" == "C:\Programme (x86)" (
REM   set LANG=de
REM   goto select
REM )
set LANG=en
goto select

:de
set LANG_SUFFIX=-de
goto execute

:en
set LANG_SUFFIX=-en
goto execute

:execute
if "%~n0" == "%UNINSTALL_PREFIX%" goto executeUninstall
goto executeInstall

:executeInstall
call "%INSTALL_LOC%%INSTALL_PREFIX%%LANG_SUFFIX%%SCRIPT_SUFFIX%"
set EXIT_CODE=%ERRORLEVEL%
goto end

:executeUninstall
call "%INSTALL_LOC%%UNINSTALL_PREFIX%%LANG_SUFFIX%%SCRIPT_SUFFIX%"
set EXIT_CODE=%ERRORLEVEL%
goto end

:end
exit /B %EXIT_CODE%


----------------------------------------------------------------------


[2] install.cmd
----------------------------------------------------------------------
@echo off

REM Usage:
REM install.cmd <type> <32-bit-installer> <64-bit-installer> 
[installer-location [custom-options]]
REM where type is one of
REM     msiinstall     Install the given MSI package
REM     msiuninstall   Uninstall the given MSI package
REM     innosetup      Inno setup
REM     installshield  Install shield
REM     nsis           Nullsoft install system (NSIS)
REM     custom         Custom installer - options required in this case
REM 32-bit-installer   Full file name (including extension) of 32-bit 
installer
REM 64-bit-installer   Full file name (including extension) of 64-bit 
installer
REM installer-location Path where the installers are stored, if empty 
assumes directory where install.cmd is
REM custom-options     Replace the default installer options with the 
ones given

REM Global variables
set INSTALL_CMD=
set EXIT_CODE=0

REM Get command type
set TYPE=%~1

REM Get 32-bit installer name
set CMD32=%~2

REM Get 64-bit installer name
set CMD64=%~3

REM get file path
set INSTALLER_PATH=%~dp0
if not "%~4" == "" (
set INSTALLER_PATH=%~4
)

set OPTIONS=
if not "%~5" == "" (
set OPTIONS=%~5
)


REM Detect which system is used
if not "%ProgramFiles(x86)%" == "" goto 64bit
goto 32bit


REM 
##########################################################################
REM 64-bit system detected
REM 
##########################################################################
:64bit
REM Determine 64-bit installer to be used
echo 64-bit system detected.
REM set INSTALLER64=
if not "%CMD64%" == "" (
set INSTALLER64=%CMD64%
) else (
REM Use 32-bit installer if available, no 64-bit installer available.
if not "%CMD32%" == "" (
echo Using 32-bit installer, no 64-bit installer specified.
set INSTALLER64=%CMD32%
) else (
echo Neither 64-bit nor 32-bit installer specified. Exiting.
goto usage
)
)

REM Check if installer is valid
if exist "%INSTALLER_PATH%%INSTALLER64%" (
set INSTALL_CMD=%INSTALLER_PATH%%INSTALLER64%
) else (
echo Installer "%INSTALLER_PATH%%INSTALLER64%" cannot be found! Exiting.
exit /B 97
)
goto installerselection


REM 
##########################################################################
REM 32-bit system detected
REM 
##########################################################################
:32bit
REM Determine 32-bit installer to be used
echo 32-bit system detected.
set INSTALLER32=
if not "%CMD32%" == "" (
set INSTALLER32=%CMD32%
) else (
echo No 32-bit installer specified. Exiting.
exit /B 96
)


REM Check if installer is valid
if exist "%INSTALLER_PATH%%INSTALLER32%" (
set INSTALL_CMD=%INSTALLER_PATH%%INSTALLER32%
) else (
echo Installer "%INSTALLER_PATH%%INSTALLER32%" cannot be found! Exiting.
exit /B 95
)
goto installerselection



REM 
##########################################################################
REM select installer system
REM 
##########################################################################
:installerselection
if /i "%TYPE%" == "msiinstall"    goto msiinstaller
if /i "%TYPE%" == "msiuninstall"  goto msiuninstaller
if /i "%TYPE%" == "innosetup"     goto innoinstaller
if /i "%TYPE%" == "installshield" goto installshieldinstaller
if /i "%TYPE%" == "nsis"          goto nsisinstaller
if /i "%TYPE%" == "custom"        goto custominstaller
goto usage



:msiinstaller
echo Installing "%INSTALL_CMD%"
if "%OPTIONS%" == "" (
set OPTIONS=/qn /norestart
)
start /wait "Software installation" msiexec /i "%INSTALL_CMD%" %OPTIONS%
set EXIT_CODE=%ERRORLEVEL%
goto end


:msiuninstaller
echo Uninstalling "%INSTALL_CMD%"
if "%OPTIONS%" == "" (
set OPTIONS=/qn /norestart
)
start /wait "Software uninstallation" msiexec /x "%INSTALL_CMD%" %OPTIONS%
set EXIT_CODE=%ERRORLEVEL%
goto end


:innoinstaller
echo Installing "%INSTALL_CMD%"
REM if "%OPTIONS%" == "" (
REM set OPTIONS=/verysilent /norestart /sp-
REM )
start /wait "Software installation" "%INSTALL_CMD%" /verysilent 
/norestart /sp- %OPTIONS%
set EXIT_CODE=%ERRORLEVEL%
goto end


:installshieldinstaller
echo Installing "%INSTALL_CMD%"
start /wait "Software installation" "%INSTALL_CMD%" /s %OPTIONS%
set EXIT_CODE=%ERRORLEVEL%
goto end


:nsisinstaller
echo Installing "%INSTALL_CMD%"
start /wait "Software installation" "%INSTALL_CMD%" /S %OPTIONS%
set EXIT_CODE=%ERRORLEVEL%
goto end

:custominstaller
if "%OPTIONS%" == "" goto usage
echo Installing "%INSTALL_CMD%"
start /wait "Software installation" "%INSTALL_CMD%" %OPTIONS%
set EXIT_CODE=%ERRORLEEL%
goto end

:usage
echo Usage:
echo "%~nx0 <type> <32-bit-installer> <64-bit-installer> 
[installer-location [custom-options]]"
echo where type is one of
echo     msiinstall        Install the given MSI package
echo     msiuninstall      Uninstall the given MSI package
echo     innosetup         Inno setup
echo     installshield     Install shield
echo     nsis              Nullsoft install system (NSIS)
echo     custom            Custom installer - options required in this case
echo 32-bit-installer      Full file name (including extension) of 
32-bit installer
echo 64-bit-installer      Full file name (including extension) of 
64-bit installer
echo installer-location    Path where the installers are stored
echo custom-options        Replace the default installer options with 
the ones given
exit /B 99

:end
exit /B %EXIT_CODE%

----------------------------------------------------------------------


[3] unattended-en.cmd
----------------------------------------------------------------------
@echo off

set CMD32=TortoiseSVN-1.4.7.11792-win32-svn-1.4.6.msi
set CMD64=TortoiseSVN-1.4.7.11792-x64-svn-1.4.6.msi
set INSTALLER=install.cmd
set INSTALLER_LOC=%~dp0

echo Installing TortoiseSVN

call "%INSTALLER_LOC%%INSTALLER%" msiinstall "%CMD32%" "%CMD64%"
----------------------------------------------------------------------



More information about the wpkg-users mailing list