Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 110 additions & 31 deletions build-scripts/package-msi
Original file line number Diff line number Diff line change
@@ -1,37 +1,46 @@
#!/bin/sh -x
#!/bin/sh
#
# package-msi: Build Windows MSI installer package for CFEngine using WiX Toolset
# This script creates a Windows installer (.msi) file for CFEngine Nova
# It handles both x86 (32-bit) and x64 (64-bit) architectures

# Source common build functions and environment configuration
. "$(dirname "$0")"/functions
. detect-environment
. compile-options
. version

# Path where WiX toolset binaries will be installed
WIXPATH="$HOME/wix"

# First see if wix tools are installed, if not, do so
# Check if WiX tools are installed, install them if not present
log_debug "Checking for WiX tools at $WIXPATH"
if [ -f "$WIXPATH/candle.exe" ] && [ -f "$WIXPATH/light.exe" ]; then
echo "Wix Tools are installed at $WIXPATH"
log_debug "WiX tools found - candle.exe and light.exe are present"
else
log_debug "WiX tools not found - starting installation"
(
# Fetch some prerequisites
# Download prerequisites from build artifacts cache server
cd /tmp || exit
echo '
get /export/images/windows/wix310-binaries.zip
get /export/images/windows/wine-folder.tar.xz
' | sftp -b - jenkins_sftp_cache@build-artifacts-cache.cloud.cfengine.com

# check checksums
# Verify downloaded files
sha256sum -c - <<EOF || exit 42
493145b3fac22bdf8c55142a9f96ef8136d56b38d78a2322f13f1ba11f9cf2f8 wix310-binaries.zip
3510fd8c4ecb4a9c479dfe43849183c666f9e41b019fc7135dc8735d0032d16e wine-folder.tar.xz
EOF
# Install Wix tools
# Install WiX Toolset binaries
mkdir -p "$WIXPATH"
cd "$WIXPATH" || exit
unzip /tmp/wix310-binaries.zip
chown "$USER":"$USER" -R "$WIXPATH"

# Extract pre-installed Wine .NET tree
# This file was generated by using a fresh Wine installation and running "winetricks dotnet45".
# Extract pre-configured Wine environment with .NET Framework
# This archive contains a Wine prefix with dotnet45 already installed
# (created by running "winetricks dotnet45" on a fresh Wine installation)
cd "$HOME" || exit
tar -xJf /tmp/wine-folder.tar.xz
chown "$USER":"$USER" -R "$HOME"/.wine
Expand All @@ -41,52 +50,77 @@ EOF
)
fi

# Wine can handle these tools under the following conditions:
# Wine configuration requirements for running WiX tools:
# * You must use Wine 32-bit (wine:i386)
# * The host must have run "winetricks dotnet45" and clicked through all the
# installations.
#
# This was tested with Wix tools 3.10.
# This was tested with WiX tools 3.10.
# Define WiX tool commands - candle.exe compiles .wxs files, light.exe links them
#
# TODO: ENT-13249: candle.exe and light.exe have been replaced with the single WiX build command.
CANDLE="wine $WIXPATH/candle.exe"
LIGHT="wine $WIXPATH/light.exe"

# Determine build directory name based on Jenkins job or version/arch
log_debug "Determining build directory name (JOB_NAME=$JOB_NAME, VERSION=$VERSION, ARCH=$ARCH)"
if [ -z "$JOB_NAME" ]; then
DIRNAME=build-$VERSION-$ARCH
log_debug "No JOB_NAME set, using DIRNAME=$DIRNAME"
else
# Extract first part of Jenkins job name (before the slash)
DIRNAME=$(echo "${JOB_NAME}" | sed 's/\(.*\)\/.*/\1/g')
log_debug "Using Jenkins job name, DIRNAME=$DIRNAME"
fi

# Set up packaging directory paths
PKGD=$BASEDIR/packaging/cfengine-nova/pkg
P=$PKGD/$DIRNAME

# pre() - Prepare the packaging directory structure and copy all required files
# This function sets up the directory layout for the MSI package build
pre() {
log_debug "Preparing packaging directory structure"

# Clean up any existing packaging directory and create fresh structure
log_debug "Removing existing packaging directory: $PKGD"
rm -rf "$PKGD"
mkdir -p "$P"/bin
mkdir -p "$P"/ssl

# Copy CFEngine binaries and SSL files from build prefix
log_debug "Copying binaries from $BUILDPREFIX/bin to $P/bin"
cp -a "$BUILDPREFIX"/bin/* "$P"/bin
log_debug "Copying SSL files from $BUILDPREFIX/ssl to $P/ssl"
cp -a "$BUILDPREFIX"/ssl/* "$P"/ssl
log_debug "Copying dist binaries from $BASEDIR/cfengine/dist$BUILDPREFIX/bin"
cp -a "$BASEDIR"/cfengine/dist"$BUILDPREFIX"/bin/* "$P"/bin

# TODO make not hard-coded paths for debian/ubuntu installations of mingw
# I couldn't find an easy way to dynamically find the path to this dll so hard-coded
# it is. :(
# Copy MinGW runtime DLL and architecture-specific event DLL
# TODO: make not hard-coded paths for debian/ubuntu installations of mingw
# These paths are currently hard-coded as there's no easy way to dynamically find them
log_debug "Copying MinGW runtime DLLs for architecture: $ARCH"
case "$ARCH" in
x86)
# 32-bit: Copy i686 MinGW pthread DLL and 32-bit event DLL
cp -a /usr/i686-w64-mingw32/lib/libwinpthread-1.dll "$P"/bin/libwinpthread-1.dll
cp -a "$BASEDIR"/enterprise/libcfenterprise/cf.events.i686.dll "$P"/bin/cf.events.dll
;;
x64)
# 64-bit: Copy x86_64 MinGW pthread DLL and 64-bit event DLL
cp -a /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll "$P"/bin/libwinpthread-1.dll
cp -a "$BASEDIR"/enterprise/libcfenterprise/cf.events.x86_64.dll "$P"/bin/cf.events.dll
;;
*)
echo "Unknown architecture: $ARCH"
log_error "Unknown architecture: $ARCH"
exit 1
;;
esac

# Copy WiX source file for MSI generation
cp "$BASEDIR"/buildscripts/packaging/cfengine-nova/cfengine-nova.wxs "$P"

# Handle OpenSSL library naming differences between architectures
# OpenSSL libs have different names on x32 and x64 platforms:
# on 32-bit platforms: libcrypto_1_1.dll and libssl_1_1.dll
# on 64-bit platforms: libcrypto_1_1_x64.dll and libssl_1_1_x64.dll
Expand All @@ -97,46 +131,72 @@ pre() {
fi
}

# candle() - Compile WiX source file (.wxs) into WiX object file (.wixobj)
# Parameters:
# $1 - REVISION: The version number for the MSI package
# Defines preprocessor variables for the WiX compilation:
# CfSourceDir: Current directory containing files to package
# CfVersion: Version number for the package
# CfArch: Architecture (x86 or x64)
candle() {
REVISION="$1"
log_debug "Running candle with REVISION=$REVISION, ARCH=$ARCH"
$CANDLE -dCfSourceDir=. -dCfVersion="$REVISION" -dCfArch="$ARCH" cfengine-nova.wxs
}

# light() - Link WiX object file into final MSI package
# Uses WiX linker to create the MSI from compiled object file
# Options:
# -sval: Suppress validation (speeds up build)
# -sice:ICE20: Suppress ICE20 validation (makes sure you have the necessary dialogs defined to handle things like showing a friendly message when the user cancels the install)
# -ext WixUtilExtension: Include WiX utility extension for additional functionality
light() {
log_debug "Running light to link WiX object into MSI"
$LIGHT -sval -sice:ICE20 -ext WixUtilExtension cfengine-nova.wixobj
}

# package() - Main packaging function that creates the MSI installer
package() {
log_debug "Creating MSI installer"
cd "$P"

# Determine the package revision number based on build configuration
log_debug "Determining package revision (BUILD_TYPE=$BUILD_TYPE, VERSION=$VERSION)"
if [ -z "$EXPLICIT_VERSION" ]; then
# First make sure VERSION does not contain a 4th dot, since on windows we'll
# First make sure VERSION does not contain a 4th dot, since on Windows we'll
# add one later (plus they can only be numeric); so convert
# 3.10.0a.abcdef to 3.10.0a.
VERSION=$(echo "$VERSION" | sed -e 's/\([^.]*\.[^.]*\.[^.]*\).*/\1/')

case $BUILD_TYPE in
RELEASE)
# For release builds, use VERSION.RELEASE or VERSION.1
if [ -z "$EXPLICIT_RELEASE" ]; then
REVISION="$VERSION.1"
else
REVISION="$VERSION.$EXPLICIT_RELEASE"
fi
;;
DEBUG | CODE_COVERAGE)
# For debug/coverage builds, use main version with build number
# Strip anything after ~ (e.g., 3.10.0~build1 becomes 3.10.0)
MAIN_VERSION=${VERSION%\~*}
REVISION=$MAIN_VERSION.$BUILD_NUMBER
;;
esac
else
# Use explicitly provided version with release number
if [ -z "$EXPLICIT_RELEASE" ]; then
REVISION="$EXPLICIT_VERSION.1"
else
REVISION="$EXPLICIT_VERSION.$EXPLICIT_RELEASE"
fi
fi

# convert any alphabets in revison to int as no alphabets are allowed in windows package version
# Convert alphabetic characters to numeric
# MSI version numbers must be purely numeric
# Example: 3.10.0a becomes 3.10.097 (where 'a' = ASCII 97)
log_debug "Transforming $REVISION to only numeric characters by replacing alphabet characters to their ASCII value"
while true; do
alphabet=$(echo "$REVISION" | sed -e 's/[^a-zA-Z]*\([a-zA-Z]*\).*/\1/')
if [ -n "$alphabet" ]; then
Expand All @@ -147,24 +207,28 @@ package() {
fi
done

# ensure all revision components are 4 digits max (MSI allows each part to be up to 65534, but we limit it to 9999)
# Ensure each version component is max 4 digits
# MSI allows up to 65534 per component, but we limit to 9999 for simplicity
REVISION=$(echo "$REVISION" | tr '.' '\n' | sed 's/\(....\).*/\1/' | tr '\n' '.' | sed 's/\.$//')

# ensure revision has exactly 4 components - add .0.0.0 at the end and cut unneded parts
# MSI requires exactly 4 version components (Major.Minor.Build.Revision)
# Pad with zeros if needed, then truncate to exactly 4 components
REVISION=$(echo "$REVISION".0.0.0 | cut -d '.' -f 1-4)

# Wix tools have a ridiculously short maximum file length of 128 characters.
# This is easily exceeded when Jenkins workspace paths are involved. Luckily
# we can shortcut the paths by using Windows drive letters to point to a
# directory closer to the source files. Wine will automatically use the
# shortest one.
# Work around WiX's 128-character path length limitation
# Long Jenkins workspace paths often exceed this limit
# Solution: Create a Wine drive letter symlink to current directory
# This provides a shorter path like D:\ instead of /home/user/very/long/path

# Find a free drive letter. Z: is usually / in Wine, but apart from that it's
# more likely to find free letters at the bottom end, so count backwards.
# Find an available drive letter for Wine mapping
# Start from Y: and work backwards (Z: is typically mapped to root /)
log_debug "Finding available Wine drive letter for path limitation workaround"
WORKSPACE_DRIVE=
for letter in y x w v u t s r q p o n m l k j i h g f e d; do
if [ ! -e "$HOME"/.wine/dosdevices/$letter: ]; then
WORKSPACE_DRIVE=$letter:
log_debug "Using Wine drive letter $WORKSPACE_DRIVE mapped to $PWD"
# Create symlink from Wine drive letter to current directory
ln -s "$PWD" "$HOME"/.wine/dosdevices/$WORKSPACE_DRIVE
break
fi
Expand All @@ -175,43 +239,58 @@ package() {
exit 2
fi

# Run WiX compilation and linking steps
log_debug "Starting WiX compilation and linking with final REVISION=$REVISION"
ret=0
candle "$REVISION" && light || ret=$?

# Make sure the drive letter from above is cleaned up in case this build slave
# is reused.
# Clean up Wine drive letter mapping to prevent conflicts in future builds
rm -f "$HOME"/.wine/dosdevices/$WORKSPACE_DRIVE

return $ret
}

# post() - Post-processing function to finalize the MSI package
# Moves the generated MSI to the output directory and renames it appropriately
post() {
log_debug "Finalizing MSI package"
# Create output directory and move the MSI file there
mkdir -p "$BASEDIR"/cfengine-nova
mv "$P"/cfengine-nova.msi "$BASEDIR"/cfengine-nova
cd "$BASEDIR"/cfengine-nova

# For some reason candle is giving file no permissions at all.
# Fix file permissions - WiX/Wine sometimes creates files with no permissions
chmod 644 cfengine-nova.msi

# Determine package name based on build type
case $BUILD_TYPE in
RELEASE)
# Release builds: cfengine-nova-VERSION-RELEASE
PKGNAME="cfengine-nova-$VERSION-${EXPLICIT_RELEASE:-1}"
;;
DEBUG | CODE_COVERAGE)
# Debug/coverage builds: cfengine-nova-VERSION-BUILD_NUMBER
PKGNAME="cfengine-nova-$VERSION-$BUILD_NUMBER"
;;
esac

# Rename MSI file with architecture suffix
log_debug "Renaming MSI file for architecture $ARCH with PKGNAME=$PKGNAME"
case $ARCH in
x86)
# 32-bit: Add i686 suffix
mv cfengine-nova.msi "$PKGNAME-i686.msi"
;;
x64)
# 64-bit: Add x86_64 suffix
mv cfengine-nova.msi "$PKGNAME-x86_64.msi"
;;
esac
}

pre
package
post
# Main execution flow:
log_debug "--- Starting MSI package build process ---"
pre # Prepare packaging directory and copy files
package # Build the MSI using WiX tools
post # Finalize and rename the MSI package
log_debug "--- MSI package build completed ----"
Loading