Skip to content
Draft
Show file tree
Hide file tree
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
8 changes: 6 additions & 2 deletions build-in-container-inner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,18 @@ fi
run_step "04-configure" "$BASEDIR/buildscripts/build-scripts/configure"
run_step "05-compile" "$BASEDIR/buildscripts/build-scripts/compile"
run_step "06-package" "$BASEDIR/buildscripts/build-scripts/package"
run_step "07-masterfiles-tarballs" build_masterfiles_tarballs
# Masterfiles tarballs are platform-independent and irrelevant to a Windows MSI
# cross build, which only emits the .msi. Skip them when cross-compiling.
if [ -z "$CROSS_TARGET" ]; then
run_step "07-masterfiles-tarballs" build_masterfiles_tarballs
fi

# === Copy output packages ===
# Packages are created under $BASEDIR/<project>/ by dpkg-buildpackage / rpmbuild.
# Exclude deps-packaging to avoid copying dependency packages.
find "$BASEDIR" -maxdepth 4 \
-path "$BASEDIR/buildscripts/deps-packaging" -prune -o \
\( -name '*.deb' -o -name '*.rpm' -o -name '*.pkg.tar.gz' \) -print \
\( -name '*.deb' -o -name '*.rpm' -o -name '*.msi' -o -name '*.pkg.tar.gz' \) -print \
-exec cp {} /output/ \;

echo ""
Expand Down
15 changes: 15 additions & 0 deletions build-in-container.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ def run_container(args, image_tag, source_dir, script_dir):
if args.version:
cmd.extend(["-e", f"EXPLICIT_VERSION={args.version}"])

# Cross-compilation target (e.g. x64-mingw for Windows MSI builds). The
# build-scripts derive OS/PACKAGING/ARCH from CROSS_TARGET; an env-set value
# takes precedence over Jenkins label detection.
cross_target = get_config()[args.platform].get("cross_target")
if cross_target:
cmd.extend(["-e", f"CROSS_TARGET={cross_target}"])

cmd.append(image_tag)

if args.shell:
Expand Down Expand Up @@ -417,6 +424,13 @@ def parse_args():
if not args.build_type:
parser.error("missing required argument --build-type")

# Cross-compiled Windows (mingw) builds are always Enterprise agent builds.
if get_config()[args.platform].get("cross_target"):
if args.project != "nova":
parser.error(f"--platform {args.platform} requires --project nova")
if args.role != "agent":
parser.error(f"--platform {args.platform} requires --role agent")

return args


Expand Down Expand Up @@ -481,6 +495,7 @@ def main():
packages = (
list(output_dir.glob("*.deb"))
+ list(output_dir.glob("*.rpm"))
+ list(output_dir.glob("*.msi"))
+ list(output_dir.glob("*.tar.gz"))
)
if packages:
Expand Down
145 changes: 41 additions & 104 deletions build-scripts/package-msi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/sh
#
# package-msi: Build Windows MSI installer package for CFEngine using WiX Toolset
# package-msi: Build Windows MSI installer package for CFEngine
# This script creates a Windows installer (.msi) file for CFEngine Nova
# It handles both x86 (32-bit) and x64 (64-bit) architectures

Expand All @@ -10,57 +10,10 @@
. compile-options
. version

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

# 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
log_debug "WiX tools found - candle.exe and light.exe are present"
else
log_debug "WiX tools not found - starting installation"
(
# 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

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

# 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

# Prevent .exe files from auto launching with wine (breaks cross compile checks).
sudo update-binfmts --package wine --remove wine /usr/bin/wine
)
fi

# 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.
# 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"
# MSI packaging uses wixl (GNOME msitools), which builds Windows MSIs natively
# on Linux - no Windows and no Wine. The build image (container/Dockerfile.mingw)
# ships wixl. See packaging/cfengine-nova/cfengine-nova.wxs for the wixl-specific
# authoring notes.

# 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)"
Expand Down Expand Up @@ -134,30 +87,44 @@ pre() {
if [ "$ARCH" = "x86" ]; then
sed -i '/lib\(crypto\|ssl\)/s/[_-]x64//g' "$P"/cfengine-nova.wxs
fi

# Current mingw-w64 binutils emit a non-allocated .comment section (the GCC
# version string) with an out-of-range virtual address (0xc0000000). The
# Windows PE loader validates section addresses against SizeOfImage and
# rejects such binaries with ERROR_BAD_EXE_FORMAT ("not a valid application
# for this OS platform"), so strip it from every packaged binary. See
# ENT-13868.
case "$ARCH" in
x86) mingw_objcopy=i686-w64-mingw32-objcopy ;;
x64) mingw_objcopy=x86_64-w64-mingw32-objcopy ;;
esac
for f in "$P"/bin/*.exe "$P"/bin/*.dll; do
[ -f "$f" ] && "$mingw_objcopy" --remove-section=.comment "$f"
done
}

# candle() - Compile WiX source file (.wxs) into WiX object file (.wixobj)
# wixl_build() - Build the MSI from the WiX source using wixl (msitools).
# 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() {
# wixl preprocessor variables (-D name=value):
# CfSourceDir: Current directory containing files to package (".")
# CfVersion: Version number for the package
# CfArch: Architecture (x86 or x64) - consumed by the .wxs for e.g.
# OpenSSL DLL naming.
# "-a" sets the target architecture.
wixl_build() {
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
case "$ARCH" in
x86 | x64) wixl_arch="$ARCH" ;;
*)
log_error "Unknown architecture: $ARCH"
exit 1
;;
esac
log_debug "Running wixl with REVISION=$REVISION, ARCH=$ARCH"
wixl -a "$wixl_arch" \
-D CfSourceDir=. -D CfVersion="$REVISION" -D CfArch="$ARCH" \
-o cfengine-nova.msi cfengine-nova.wxs
}

# package() - Main packaging function that creates the MSI installer
Expand Down Expand Up @@ -220,39 +187,9 @@ package() {
# Pad with zeros if needed, then truncate to exactly 4 components
REVISION=$(echo "$REVISION".0.0.0 | cut -d '.' -f 1-4)

# 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 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
done

if [ -z "$WORKSPACE_DRIVE" ]; then
echo "Unable to find a free drive letter for Wine path."
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=$?

# Clean up Wine drive letter mapping to prevent conflicts in future builds
rm -f "$HOME"/.wine/dosdevices/$WORKSPACE_DRIVE

return $ret
# Build the MSI natively with wixl.
log_debug "Starting wixl build with final REVISION=$REVISION"
wixl_build "$REVISION"
}

# post() - Post-processing function to finalize the MSI package
Expand Down
5 changes: 5 additions & 0 deletions ci/cfengine-build-host-setup.cf
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ bundle agent cfengine_build_host_setup
mingw_build_host::
"mingw-w64";

# build-scripts/package-msi builds the MSI natively with wixl; msiinfo
# (msitools) is used to inspect it. No Wine, no i386. See ENT-13868.
"wixl";
"msitools";

"binfmt-support"
comment => "update-binfmts command needed for build-scripts/package-msi script";

Expand Down
11 changes: 11 additions & 0 deletions ci/fix-buildhost.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,14 @@ if [ -f /etc/os-release ]; then
fi
fi
fi

# MinGW hosts build the MSI with wixl (build-scripts/package-msi) and inspect it
# with msiinfo (msitools). Installed by the build-host-setup policy at image
# time; install here too so not-yet-reimaged mingw hosts get them without a
# reimage. See ENT-13868.
if [ -f /etc/cfengine-mingw-build-host.flag ]; then
if ! command -v wixl >/dev/null 2>&1 || ! command -v msiinfo >/dev/null 2>&1; then
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y wixl msitools
fi
fi
55 changes: 55 additions & 0 deletions container/Dockerfile.mingw
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
ARG BASE_IMAGE=ubuntu:24.04
FROM ${BASE_IMAGE}

ENV DEBIAN_FRONTEND=noninteractive

# Build tools extracted from ci/cfengine-build-host-setup.cf (debian|ubuntu section).
# MinGW builds are always Enterprise *agent* builds, so the Node.js/PHP/Composer
# hub tooling from Dockerfile.debian is intentionally omitted here.
RUN apt-get update && apt-get install -y \
autoconf automake binutils bison build-essential curl debhelper \
dpkg-dev expat fakeroot flex gdb git libexpat1-dev \
libmodule-load-conditional-perl libpam0g-dev libtool \
libncurses6 libncurses-dev \
pkg-config psmisc python3-pip rsync sudo systemd-coredump unzip wget \
&& rm -rf /var/lib/apt/lists/*

# MinGW-w64 cross toolchain. Provides x86_64-w64-mingw32-{gcc,g++,...} and the
# Windows runtime DLLs (e.g. /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll)
# that package-msi copies into the MSI.
RUN apt-get update && apt-get install -y \
mingw-w64 \
&& rm -rf /var/lib/apt/lists/*

# MSI tooling: wixl (GNOME msitools) builds Windows MSIs natively on Linux - no
# Windows and no Wine, so no i386 architecture is needed. The wixl binary is in
# the separate "wixl" package; "msitools" provides msiinfo/msibuild for
# inspecting the resulting .msi.
RUN apt-get update && apt-get install -y \
wixl msitools \
&& rm -rf /var/lib/apt/lists/*

# Remove system dev libraries that conflict with bundled deps
# (from cfengine-build-host-setup.cf lines 53-59)
RUN apt-get purge -y \
libattr1-dev libssl-dev libpcre2-dev libacl1-dev \
libyaml-dev libxml2-dev librsync-dev 2>/dev/null || true

# Rust toolchain and protobuf compiler for building the cargo-based leech2
# dependency. On mingw hosts leech2 is cross-compiled for Windows, so we also
# install the x86_64-pc-windows-gnu std (linux-install-rust.sh takes the target
# triple as an argument).
COPY --from=ci linux-install-protobuf.sh linux-install-rust.sh /tmp/
RUN /tmp/linux-install-protobuf.sh
RUN /tmp/linux-install-rust.sh x86_64-pc-windows-gnu

# Create build user with passwordless sudo (needed by install-dependencies, package, etc.)
RUN useradd -m -s /bin/bash builder \
&& echo "builder ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/builder

USER builder
WORKDIR /home/builder

# Pre-create the build directory so that when a named volume is mounted here,
# Docker initializes it with the correct ownership (builder:builder).
RUN mkdir -p /home/builder/build
Loading
Loading