#!/bin/sh
################################################################################
# B U I L D   W I T H   L A T E S T   X O R G   A P T . S H
#
# Builds the latest version of the xserver from gitlab.freedesktop.org
#
# (c) Matt Burt 2025
#
# Usage ./build_with_latest_xorg_apt.sh [-r] [-x git.clone.args] <build_dir>
#
# Options:-
# -r  Use an autoresume file. This can be useful when
#     tracking down problems with the build script. It can also make
#     things very confusing!
# -x git.clone.args
#     Use the specified arguments to the git clone command which
#    fetches the xserver. This can be useful to get the xserver
#    source from another repository (e.g.
#    -x '-b incubate https://gitlab.freedesktop.org/metux/xserver.git')
#
# sudo is used to install build dependencies, plus a few package
# dependencies.
#
# Most dependencies are pulled down and installed as the build user.
################################################################################

# ------------------------------------------------------------------------------
# G L O B A L S
# ------------------------------------------------------------------------------
# Build tools
APT_PKG_LIST="git pip pkg-config cmake automake autoconf libtool"
APT_PKG_LIST="$APT_PKG_LIST python3-venv" ; # meson/ninja
# Other OS-provided dependencies
#
# We pull in most of Mesa rather than building it from scratch as
# Mesa dependencies (notably the rust compiler version) can be
# problematic
APT_PKG_LIST="$APT_PKG_LIST mesa-common-dev" ; # See above
APT_PKG_LIST="$APT_PKG_LIST libfreetype-dev" ; # Needed by libXfont
APT_PKG_LIST="$APT_PKG_LIST libudev-dev" ; # Needed by xserver
APT_PKG_LIST="$APT_PKG_LIST libssl-dev" ; # Needed by xserver
APT_PKG_LIST="$APT_PKG_LIST libepoxy-dev" ; # Needed by xserver

# We use 'pip' to get the latest meson and ninja build tools down. These
# are installed for the build user.
PIP_PKG_LIST="meson ninja"

# The modular packages supported by the xserver build tool which we need to
# build. This includes the xserver package
#
# This list of packages is sorted into dependency order before use.
MODULAR_PKG_LIST="util/macros"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST font/util"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST proto/xorgproto proto/xcbproto"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST lib/libxcvt lib/libxtrans lib/libXau"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST lib/libXdmcp lib/libxcb lib/libxcb-util"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST lib/libxcb-wm lib/libX11 lib/libfontenc"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST lib/libXfont lib/libxkbfile"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST lib/libxshmfence"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST mesa/drm"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST pixman/pixman"
MODULAR_PKG_LIST="$MODULAR_PKG_LIST xserver"

# Check the XDG_RUNTIME_DIR is specified, and use it for the autoresume file
if [ -z "$XDG_RUNTIME_DIR" ]; then
    XDG_RUNTIME_DIR="/run/user/$(id -u)"
    export XDG_RUNTIME_DIR
fi
AUTORESUME_FILE="$XDG_RUNTIME_DIR/xserver-autores.txt"

# Our source dir (set from the BUILD_DIR)
SOURCE_DIR=

# Dependencies within the SOURCE_DIR. These are evaluated when
# the SOURCE_DIR is known (which is why the '$' is escaped)
BUILDER="\$SOURCE_DIR/util/modular/build.sh" ; # xorg modular build script
                                               # (Installed below)
MODFILE="\$SOURCE_DIR/modfile.txt"           ; # Used by above
PYTHON_VENV="\$SOURCE_DIR/python"            ; # Used for meson/ninja

# The build directory (set later)
BUILD_DIR=

# Dependencies within the BUILD_DIR. These are evaluated when the
# BUILD_DIR is known
BUILD_TARGET="\$BUILD_DIR/lib/pkgconfig/xorg-server.pc"
                                              # What we are trying to make

# Command used to clone the xserver repository
XSERVER_CLONE_ARGS=

# ------------------------------------------------------------------------------
# C R E A T E   B U I L D   M O D F I L E
#
# Creates the modfile used by utils/modular/build.sh on stdout
# Parameters:-
# 1..) List of modules to add to the file
#
# Return : != 0 for error
# The modules are sorted into dependency order before being added.
# ------------------------------------------------------------------------------
create_build_modfile()
{
    tmp=$(mktemp -t "modfile-XXXXXXXX")
    $BUILDER -L >"$tmp"
    while read -r mod; do
        newlist=
        for m in "$@"; do
            if [ "$m" = "$mod" ]; then
                echo "$mod"
            else
                newlist="$newlist $m"
            fi
        done
        # shellcheck disable=SC2086
        set -- $newlist
        if [ $# -eq 0 ]; then
            break
        fi
    done <"$tmp"
    rm "$tmp"
    if [ $# -gt 0 ]; then
        echo "** The following modules are not supported by build.sh: $*" >&2
    fi
    return $#
}

# ------------------------------------------------------------------------------
# I N S T A L L   S O U R C E S
#
# Installs the sources used by the modular build utility
# Parameters:-
# 1..) List of modules to get source for
#
# Return : != 0 for error
# ------------------------------------------------------------------------------
install_sources()
{
    rv=0
    tmp=$(mktemp -t "modfile-XXXXXXXX")
    defer_xserver=

    # Create a modfile for all the components. Defer the xserver
    # if it's present, and we have a custom command
    for mod in "$@"; do
        if [ -n "$XSERVER_CLONE_ARGS" ] && [ "$mod" = xserver ]; then
	       defer_xserver=1
       elif ! [ -d "$mod" ]; then
            echo "$mod"
        fi
    done >"$tmp"

    if [ -s "$tmp" ]; then
        $BUILDER --modfile "$tmp" -a -m --clone "$BUILD_DIR"
        rv=$?
    fi
    rm -f "$tmp"

    if [ "$rv" -eq 0 ] && [ "$defer_xserver" ] && ! [ -d xserver ]; then
        echo "======================================================================"
        echo "Processing: xserver (git clone $XSERVER_CLONE_ARGS)"
        # shellcheck disable=SC2086
        git clone $XSERVER_CLONE_ARGS
	rv=$?
    fi

    # Most autoconf-enabled modules required an 'm4' directory. If
    # we fetch from git, these directories are only created if there
    # are not empty. If the directory isn't present, the module
    # builds can fail.
    #
    # Add m4 directories for autoconf-enabled modules
    for mod in "$@"; do
        if [ -f "$mod/configure.ac" ]; then
            mkdir -p "$mod/m4"
        fi
    done

    return $rv
}

# ------------------------------------------------------------------------------
# T I T L E
#
# Outputs a log title
# ------------------------------------------------------------------------------
title()
{
    echo
    echo '==============================================================================='
    echo "$(date +%T): $*"
    echo '==============================================================================='
}

# ------------------------------------------------------------------------------
# M A I N
# ------------------------------------------------------------------------------

# Check parameters
use_autoresume=
while [ $# -gt 1 ]; do
    case "$1" in
        -r) use_autoresume=1
            ;;
	-x) shift  ; # Next param is guaranteed to exist
            XSERVER_CLONE_ARGS="$1"
	    ;;
        *)  echo "** Unexpected argument '$1'" >&2
            exit 1
    esac
    shift
done

if [ $# != 1 ]; then
    echo "Usage : $0 [-r] [-x git.clone.args] <build-dir>" >&2
    exit 1
fi

if ! [ "$use_autoresume" ]; then
    rm -f "$AUTORESUME_FILE"
fi

# Set up the BUILD_DIR, after resolving it to an absolute path
mkdir -p "$1"
if ! cd "$1" || ! [ -w . ]; then
    echo "** Build directory $BUILD_DIR is invalid or unwriteable" >&2
    exit 1
fi
BUILD_DIR=$(pwd)
# Resolve BUILD_DIR dependencies
eval BUILD_TARGET="$BUILD_TARGET"

# Have we run this script before?
if [ -e "$BUILD_TARGET" ]; then
    echo "- Target $BUILD_TARGET exists. The script will not be run again" >&2
    exit 0
fi

# Debian has an extra pkgconfig directory to consider in
# lib/$(uname -p)-linux-gnu/, which meson from pip doesn't pick up on.
# Rather than messing with PKG_CONFIG_PATH, we'll use a soft-link to make
# this work, so the resulting output directory can be more easily managed
debian_extra_pkgconf="$BUILD_DIR/lib/$(uname -p)-linux-gnu/pkgconfig"
if ! [ -L "$debian_extra_pkgconf" ]; then
    mkdir -p "${debian_extra_pkgconf%/*}"
    ln -sf ../pkgconfig "$debian_extra_pkgconf"
fi

# Base the SOURCE_DIR on the BUILD_DIR, and work in it.
SOURCE_DIR="$BUILD_DIR.src"

mkdir -p "$SOURCE_DIR"
cd "$SOURCE_DIR" || exit $?

# Resolve SOURCE_DIR dependencies
eval BUILDER="$BUILDER"
eval MODFILE="$MODFILE"
eval PYTHON_VENV="$PYTHON_VENV"

# Install all dependencies. Before running 'sudo', make sure we need to
title "Installing dependencies"
# shellcheck disable=SC2086
if ! dpkg-query -W $APT_PKG_LIST >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    sudo apt-get install -y $APT_PKG_LIST || exit $?
fi
if ! [ -d "$PYTHON_VENV" ]; then
    python3 -m venv "$PYTHON_VENV" || exit $?
fi
PATH="$PYTHON_VENV/bin:$PATH"
# shellcheck disable=SC2086
pip3 install $PIP_PKG_LIST || exit $?

title "Installing modular build script"
if ! [ -x "$BUILDER" ]; then
    git clone https://gitlab.freedesktop.org/xorg/util/modular.git util/modular || exit $?
    if ! [ -x "$BUILDER" ]; then
        echo "** Failed to install modular build script" >&2
        exit 1
    fi
fi

title "Creating module file"
# shellcheck disable=SC2086
create_build_modfile $MODULAR_PKG_LIST >"$MODFILE" || exit $?

title "Installing sources"
# shellcheck disable=SC2086
install_sources $MODULAR_PKG_LIST || exit $?

# If there are meson builddir files without build.ninja files, delete them.
# This can happen if a previous build failed in the configure stage with
# missing dependencies.
title "Removing failed meson build directories"
find . -name builddir | \
while read -r dir; do
    if [ ! -e "$dir/build.ninja" ]; then
        rm -rf "$dir"
    fi
done

title "Building sources"
$BUILDER \
    --modfile "$MODFILE" \
    --autoresume "$AUTORESUME_FILE" \
    "$BUILD_DIR"
