Karam Assany's Personal Blog

Getting Lazarus to develop Windows applications on Void Linux (musl)

Published at:

This is a guide to prepare a development environment on Void Linux to develop native GUI applications for Windows using Free Pascal and the Lazarus IDE.

The process will have the following characteristics:

Note that Void Linux does not include Lazarus in their repository, so we have to build it from source.

Moreover, Void Linux includes FPC 3.2.0, which is not the latest version (and is unable to compile the latest version of Lazarus), so we have to build FPC from source as well.

This means that the process requires a bit of patience.

Questions

Q: Why Free Pascal and not Delphi?
A: Because Delphi is proprietary.

Q: Why don’t you just use Lazarus on Windows (the target platform)?
A: Because Windows is proprietary and garbage.

Q: Why Pascal and not a popular programming language?
A: Because I love Pascal.

Q: Why Void and not a popular distro that includes Lazarus?
A: Because I love Void.

Q: Why build from source instead of using the official builds?
A: Because they’re not compatible with musl.

Q: Why use musl in the first place instead of glibc?
A: Because I love challenges.

Q: Why Qt5 and not the default GTK?
A: Because GTK2 is outdated and the GTK3 LCL interface is not stable yet.

Q: Why Qt5 and not Qt6 (the latest)?
A: Because the Qt5 LCL interface is more stable.

Targets

What we’re going to build:

Assumptions

This guide assumes you have native and cross-compiler GCC toolchains:

sudo xbps-install base-devel cross-i686-w64-mingw32 cross-x86_64-w64-mingw32

Note: FPC depends on GNU tools for assembling and linking, so you have to keep these packages (or at least a subset of them) as long as you’re using FPC to build Windows applications.

Note: Wine and GDB

To run Windows applications in the development environment, we’re going to use use Wine:

sudo xbps-install wine

To debug Windows binaries on Linux, we’re going to use gdbserver.exe inside Wine, and use the native gdb to connect to it.

sudo xbps-install gdb

To get gdbserver.exe, you can save your time and download pre-built mingw-w64 toolchain packages for Windows.

I chose w64devkit because it’s simple and portable. But you’re free to choose another provider (or build gdbserver.exe yourself).

# We're gonna need two versions, one for 32-bit binaries and one for 64-bit
# binaries
sudo wget https://github.com/skeeto/w64devkit/releases/download/v2.4.0/w64devkit-x64-2.4.0.7z.exe
sudo wget https://github.com/skeeto/w64devkit/releases/download/v2.4.0/w64devkit-x86-2.4.0.7z.exe

# Run and install somewhere suitable
wine w64devkit-x64-2.4.0.7z.exe
wine w64devkit-x86-2.4.0.7z.exe

Note: Qt5

Void provides an outdated libQt5Pas package. I tried to compile Lazarus against it and I failed.

This is why we’re going to compile our own. We need the Qt5 toolchain:

sudo xbps-install qt5-x11extras-devel
# This will pull important dependencies like `qt5-devel` and `qt5-qmake`

Note: While some pulled dependencies will only be used when building libQt5Pas, other dependencies will be runtime dependencies (needed by Lazarus at runtime). Be careful when cleaning build dependencies.

Getting FPC 3.2.2

In order to build FPC, we need FPC. As per the wiki, an FPC release is only guaranteed to be compiled successfully using its preceding release.

Void includes FPC 3.2.0, which is two releases older than the current version. This means we have to compile FPC 3.2.1 using Void’s FPC 3.2.0, and then compile FPC 3.2.2 using our FPC 3.2.1.

However, and by looking at the void-package build recipe for FPC, it seems that FPC won’t normally compile under musl without some modifications in its source code. (I already tried and fpmake will fail to link or something.)

This means that we need to patch FPC 3.2.1 and FPC 3.2.2, which is not an easy task (because these patches are not official).

Luckily, Alpine Linux (also uses musl) have FPC 3.2.2 in their testing repository. We are going to use it to build our own FPC (because their FPC build does not support GDB).

Getting Alpine’s FPC

# Make a new directory for Alpine's FPC
mkdir alpine_fpc
cd alpine_fpc

# Download Alpine's FPC package
wget https://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/fpc-3.2.2-r4.apk

# Extract it
tar -xf fpc-3.2.2-r4.apk

# (Optional) Delete the package
rm fpc-3.2.2-r4.apk

Now we have two directories, etc and usr.

# Temporarily prepend Alpine's FPC to the PATH
export PATH="$PWD/usr/bin:$PATH"

Alpine’s FPC now resides in usr/bin/fpc but it won’t work because it can’t find ppcx64. The reason is that the Alpine build script creates an absolute symlink for ppcx64 that assumes the package is installed in / (which is fair). We have to fix the link:

ln -sf ../lib/fpc/3.2.2/ppcx64 usr/bin

Now we have a working FPC.

Preparing the FPC build

# Get out of alpine_fpc
cd ..

# Download the FPC source code from the official mirror
wget https://downloads.sourceforge.net/sourceforge/freepascal/fpcbuild-3.2.2.tar.gz

# Extract it
tar -xf fpcbuild-3.2.2.tar.gz

# (Optional) Delete the tarball
rm fpcbuild-3.2.2.tar.gz

Now we have an fpcbuild-3.2.2 directory, containing the source code of FPC.

cd fpcbuild-3.2.2

Looking at Alpine’s build recipe at git.alpinelinux.org, there are two modifications that must be done before building FPC on musl systems.

# Use correct linker path for produced binaries
sed -i \
    -e "s,/lib64/ld-linux-x86-64\.so\..,/lib/ld-musl-x86_64.so.1," \
    -e "s,/lib/ld-linux\.so\..,/lib/ld-musl-i386.so.1," \
    -e "s,/lib/ld-linux-aarch64\.so\..,/lib/ld-musl-aarch64.so.1," \
    -e "s,/lib/ld-linux-armhf\.so\..,/lib/ld-musl-armhf.so.1," \
    -e "s,/lib64/ld64\.so\..,/lib/ld-musl-powerpc64le.so.1," \
    fpcsrc/compiler/systems/t_linux.pas

# Strip out any unsupported instructions
find fpcsrc/rtl/linux -type f -print0 | xargs -0 sed -i '/libc_csu/d'

Building and installing FPC

As per the recipe (with some modifications):

# Build
cd fpcsrc/compiler
fpcmake -Tall
cd ../..
make build

# Assign and prepare the installation prefix
P=~/.fpc  # or whatever you want
mkdir -p $P

# Install
make install PREFIX=$P/usr

# Create a symlink for ppcx64
ln -s ../lib/fpc/3.2.2/ppcx64 $P/usr/bin

# Generate default configuration
$P/usr/lib/fpc/3.2.2/samplecfg $P/usr/lib/fpc/3.2.2 $P/usr/lib/fpc/etc

# Move the source directory to the prefix (will be needed)
mv fpcsrc $P/source

# Get out of fpcbuild-3.2.2
cd ..

# (Optional) Delete fpcbuild-3.2.2 as we no longer need it
rm -r fpcbuild-3.2.2

# (Optional) Delete alpine_fpc as we no longer need it
rm -r alpine_fpc

Now permanently prepend $P/usr/bin in your user’s $PATH.

The guide will assume the PATH is properly prepended from here on.

Getting Lazarus 4.4.0

Now that we have a working FPC, compiling Lazarus is such a trivial task.

# Assign and create a permanent directory for Lazarus
L=~/.lazarus
mkdir $L
cd $L

# Download the Lazarus source code from the official mirror
wget https://downloads.sourceforge.net/project/lazarus/Lazarus%20Zip%20_%20GZip/Lazarus%204.4/lazarus-4.4-0.tar.gz

# Extract it
tar -xf lazarus-4.4.0.tar.gz

# (Optional) Delete the tarball
rm lazarus-4.4.0.tar.gz

Now we have a lazarus directory inside $L.

Building qt5pas

cd lazarus/lcl/interfaces/qt5/cbindings

# Generate the Makefile
qmake

# Build
make -j$(nproc)
# This is going to take a while (C++ compilers suck, I know)

After make finishes, we’ll have libQt5Pas.so.1.2.16 and symlinks to it. As we won’t touch the system directories, we’re going to copy them to somewhere reachable by FPC at compile time.

# Create directory for shared libraries
mkdir -p $P/usr/lib/fpc/3.2.2/lib/x86_64-linux

# Copy the library and its symlinks
cp libQt5Pas.so* $P/usr/lib/fpc/3.2.2/lib/x86_64-linux

# (Optional) Clean the source tree
make distclean

Now we have to append $P/usr/lib/fpc/3.2.2/lib/x86_64-linux to the user’s LD_LIBRARY_PATH so Lazarus can find the library at runtime.

The guide will assume the LD_LIBRARY_PATH is already appended from here on.

Building Lazarus

cd $L/lazarus

# Build Lazarus
make bigide LCL_PLATFORM=qt5

# Run for the first time
./startlazarus

Running make clean is not recommended, as rebuilding Lazarus is a common thing (unless you’re never going to use non-default Lazarus packages).

Now you can either add $L/lazarus to your user’s PATH or symlink startlazarus to somewhere in your user’s PATH.

Getting the cross-compilers

# Go to the FPC source tree
cd $P/source

# Clean the source tree
make clean

# Build and then install the cross-compiler for "Windows x64"
make all OS_TARGET=win64 CPU_TARGET=x86_64
make crossinstall OS_TARGET=win64 CPU_TARGET=x86_64 INSTALL_PREFIX=$P/usr

# Do the same thing for "Windows x86"
make clean
make all OS_TARGET=win32 CPU_TARGET=i386
make crossinstall OS_TARGET=win32 CPU_TARGET=i386 INSTALL_PREFIX=$P/usr

# (Optional) clean the source free for the last time
make distclean

# Create a symlinks for ppcrossx64 and ppcross386
ln -s ../lib/fpc/3.2.2/ppcrossx64 $P/usr/bin
ln -s ../lib/fpc/3.2.2/ppcross386 $P/usr/bin

Keeping the $P/source directory is recommended as Lazarus needs it for code inspection purposes.

Getting the debugger to work

This is by-far the hardest part. Lazarus is not really well-equipped for a cross-debugging environment. All we currently have is an experiment feature-incomplete debugger backend: GNU remote debugger (gdbserver).

You can change the current debugger backend inside Lazarus in: Tools -> Options -> Debugger -> Debugger backend.

You’ll see that there is only two backends available:

As per the Lazarus forums, you have to manually edit $L/lazarus/ide/debugmanager.pas, adding gdbmidebugger to the uses statement in the interface section of the unit. Insertion order was not specified so I simply appended.

Rebuild Lazarus from inside Lazarus in: Tools -> Configure “Build Lazarus” …. Change the Profile to build value to Optimized IDE then click Save Settings. Now click Tools -> Build Lazarus with Profile: Optimized IDE.

After Lazarus finishes the build, it will restart itself. Now go to the debugger backend settings and you’ll see that our much-needed backend is there.

Now, to target Windows, go to Project -> Project Options … -> Compiler options -> Config and target and set Target OS to Win32 or Win64.

If you build and run the project, you’ll get a debugger error. That’s because Lazarus does not (currently) manage the gdbserver. You have to run it yourself:

.../w64devkit/bin/gdbserver :2345 $YOUR_EXE

You have to match the architecture of the target with that of the debugger.

Now try to rerun the project, debugging should work. That said, there are many essential features missing; like the ability to pause/stop execution.

What now?

It appear the DX of remote debugging in Lazarus sucks so bad; but there is always some space for improvement. I’m no expert in gdbserver, but it seems it supports the following paradigm:

Unfortunately, the current implementation of the gdbserver debugging backend does not support all of that. You have to start the gdbserver manually after building but before running the project. All of that to get a feature-incomplete debugger (as opposed to normal GDB-powered debugger or the internal debugger).

Compromise

If you’re like me and you’re not invested nor experienced enough to contribute a better GDB remote debugger backend for Lazarus, you can go with the obvious solution: Build, run, debug and test for Linux, release for Windows.

The RTL, the FCL and the LCL are great libraries to write cross-platform software. As long as you do not introduce platform-specific code, you’re good to go. And you can always give a final, comprehensive user testing within Wine (or ReactOS or MS Windows).