#!/bin/sh

#===============================================================
# Filename : scripts/sinner
# Purpose  : Builds Linux-to-Windows cross-compiler toolchains.
# Authors  : Zach van Rijn <me@zv.io>
# License  : MIT
# Revision : 20190531
#===============================================================

#---------------------------------------------------------------
# README
#
# overview
# --------
#
# This script builds "musl-cross-make" cross-compiler toolchains
# that are hosted on Linux and target Windows. For example, your
# MIPS router can use up-to-date versions of GCC to build code
# (C, C++, Fortran) for your Windows XP machine. The libc used
# is called 'musl': https://www.musl-libc.org/faq.html
#
# Others have tried and failed to deliver what you're able to do
# with this tiny script. Additions to this list are welcome:
#
# What does this look like? Modern GCC that does:
#
#   * Linux on ARM, Motorola 68000, MIPS, OpenRISC, PowerPC,
#              RISC-V, S/390, SuperH, x86-based, more?
#
#         --> Windows XP to 10, and possibly older.
#
# The goal, of course, is to achieve parity with musl.cc's Linux
# offerings. This is the script to build the binaries located at
# https://more.musl.cc/YOUR-HOST-ARCHITECTURE/*-w64-mingw32.zip
#
#
# build platforms
# ---------------
#
# You need Linux to build these, and an architecture that runs a
# toolchain from musl.cc.
#
#
# requirements
# ------------
#
# Consider running this script inside of an isolated environment
# such as a container or virtual machine. While not required, we
# do not recommend running any foreign scripts or binaries in an
# important environment. You'll need the following packages:
#
#   * cmake
#   * curl
#   * git
#   * make
#   * patch (GNU)
#   * tar
#   * xz
#   * zip
#
# e.g., apk add cmake curl git make patch rsync tar xz zip
#
#
# other notes
# -----------
#
#   * A prior version of this script ('prepare') relies on Linux
#     'binfmt_misc' and QEMU to emulate certain build byproducts
#     and requires root privileges to install (but not use) that
#     setup. This version cross-compiles the toolchains purely.
#
#   * This script uses the 'mingw-cross-make' flavor maintained
#     at https://git.zv.io/toolchains/mingw-cross-make to build
#     MinGW toolchains. Issues should be addressed there.
#
#
# usage
# -----
#
#   $ ./scripts/sinner [TRIPLE ...]
#
#   $ ./scripts/sinner i686-w64-mingw32 x86_64-w64-mingw32

#---------------------------------------------------------------
# Configuration.

## Component Versions
#
# These options are self explanatory, but *must* correspond to a
# supported version within the "musl-cross-make" repository. One
# other factor to consider is that kernel headers and musl libc
# will be harvested from a "donor" toolchain; these versions do
# not necessarily correspond to what is built here (yet, TODO).
#
GCC_VER=9.1.0
BINUTILS_VER=2.32
MUSL_VER=git-a60b9e06861e56c0810bae0249b421e1758d281a
GMP_VER=6.1.2
MPC_VER=1.1.0
MPFR_VER=4.0.2
MINGW_VER=git-3e6c10aeba81c589a7b2ed1e7daee4d4d75b646e

## Directories
#
# By default, all toolchains that can be built, are built. This
# requires a significant amount of disk space. Please ensure you
# have at least 50GB of free disk space in these directories:
#
base="${HOME}/sinner_src"       # base source directory
huge="${HOME}/sinner_bld"       # base  build directory
logs="${HOME}/sinner_log"       # suite build log directory
zips="${HOME}/sinner_bin"       # completed toolchains go here

## Toolchain Mirror
#
# If you have access to an x86_64 Linux machine, or one that has
# an x86_64 QEMU user-mode emulator registered in 'binfmt_misc',
# you will always be using the latest available software.
#
musl=https://more.musl.cc       # more.musl.cc or mirror

## Toolchain Naming Conventions
#
# The musl.cc toolchains follow a simple naming convention: all
# cross compilers are suffixed with '-cross', native '-native'.
# If you're using a different mirror or convention, set it here.
#
csuf=-cross                     # cross  suffix
nsuf=-native                    # native suffix

## Toolchain Tuples
#
# Values can be found at 'https://more.musl.cc/' where the $user
# variable corresponds to your build platform, and $host to your
# intended Linux development environment. Note that unless the
# website says otherwise, only the 'x86_64-linux-musl' toolchain
# directory is up-to-date. If you can't find what you're looking
# for you must build a MinGW-w64 suitable toolchain from source.
#
user=x86_64-linux-musl          # platform that builds suite

## Build Environment
#
# This variable is extended during the toolchain download step.
#
kale="${base}/${user}${csuf}/bin";

## Repositories
#
# The build infrastructure used is called "musl-cross-make" and
# is upstream https://github.com/richfelker/musl-cross-make, but
# this version is incompatible with the current script. It's out
# of date, too, so please leave the default unless you fork it.
#
name=musl-cross-make
repo=https://git.zv.io/toolchains/${name}
brch=musl-git                   # branch name (no assumptions!)

sinn=mingw-cross-make
sinr=https://git.zv.io/toolchains/${sinn}
sinb=mingw

## Suite Targets
#
# Now that RISC-V patches are (unofficially) merged into musl, a
# single text file with target tuples (which comprise the suite)
# may now be used. Modify this with e.g. a pastebin link if you
# wish to use a different list (this one is self-updating).
#
list=${repo}/raw/${brch}/scripts/triples.txt
filt=tuples.txt                 # filename of saved tuples list

## Suite Configuration
#
# To facilitate users' needs in customizing the toolchain suite,
# a configuration file is embedded below. These settings *must*
# be supported by the repository specified above.
#
# Note: items that are prefixed/suffixed with double underscores
# are automatically populated later. Do not modify them here!
#
conf=$(cat <<'EOF'
STAT = -static --static
FLAG = -g0 -O2 -fno-align-functions -fno-align-jumps -fno-align-loops -fno-align-labels

COMMON_CONFIG += CC="$(HOST)-gcc ${STAT}" CXX="$(HOST)-g++ ${STAT}" FC="$(HOST)-gfortran -${STAT}"
COMMON_CONFIG += CFLAGS="${FLAG}" CXXFLAGS="${FLAG}" FFLAGS="${FLAG}" LDFLAGS="-s ${STAT}"
COMMON_CONFIG += --disable-nls --disable-bootstrap --build=__USER__ --host=__HOST__ --target=__TARG__

GCC_CONFIG += --enable-threads=__THREADS__
OVERRIDE = --enable-libquadmath --enable-libquadmath-support

GCC_VER      = __GCC_VER__
BINUTILS_VER = __BINUTILS_VER__
MUSL_VER     = __MUSL_VER__
GMP_VER      = __GMP_VER__
MPC_VER      = __MPC_VER__
MPFR_VER     = __MPFR_VER__
MINGW_VER    = __MINGW_VER__
LINUX_VER    =
EOF
);

## Thread Configuration
#
# By popular demand, both Win32 and POSIX thread models will are
# supported. Edit this variable if you don't wish to build both.
#
tmod="posix"              # default: "win32 posix"

#---------------------------------------------------------------
# Subroutines.

# Download preliminary toolchains.
#
get_tool ()
{
    mkdir -p "${base}";

    # build
    if [ ! -d "${base}/${user}${csuf}" ]; then
        curl ${musl}/${user}/${user}${csuf}.tgz                \
            | tar 2>/dev/null -C "${base}" -xzf -;
    fi
}

# Clone a suitable "musl-cross-make" repository.
#
get_repo ()
{
    [ -d "${base}" ] || exit 1;
    [ ! -d "${base}/${sinn}" ] || return;
    git clone ${sinr} "${base}/${sinn}";
}

# Overwrite any existing configuration (config.mak) template.
#
get_conf ()
{
    [ -d "${base}/${sinn}" ] || exit 1;
    printf > "${base}/${sinn}/config.mak" "%s\n"               \
        "${conf}";
}

# Fetch an up-to-date list of possible target tuples. Allow the
# user to edit this list, if the line is uncommented, before DL.
#
get_list ()
{
    [ ! -f "${base}/${filt}" ] || return;
    curl -o "${base}/${filt}" ${list};
    sed -i "${base}/${filt}" -e '/mingw/d';
    nano "${base}/${filt}";
}

# Download all necessary target toolchains.
#
get_targ ()
{
    # targets (if different from build)
    cat "${base}/${filt}" | grep -v "#" | while read k; do
        if [ ! -d "${base}/${k}${csuf}" ]; then
            curl ${musl}/${user}/${k}${csuf}.tgz               \
                | tar 2>/dev/null -C "${base}" -xzf -;
        fi
    done;
}

# Build the compiler suite. Note: the '-ik' in 'make' is used to
# ignore an error:
#
#     The directory that should contain system headers does not
#     exist: //mingw/include
#
# We use 'make clean' to ensure that the updated configuration
# takes full effect (relevant to thread model settings).
#
run_make ()
{
    cat "${base}/${filt}" | grep -v "#" | while read k; do
    for tget in ${@}; do        # command-line argument list
    for t in ${tmod}; do        # thread model list

        mkdir -p "${huge}/${k}";
        mkdir -p "${logs}/${k}";

        get_conf; sed -i "${base}/${sinn}/config.mak"          \
            -e "s@__USER__@${user}@"                           \
            -e "s@__HOST__@${k}@"                              \
            -e "s@__TARG__@${tget}@"                           \
                                                               \
            -e "s@__GCC_VER__@${GCC_VER}@"                     \
            -e "s@__BINUTILS_VER__@${BINUTILS_VER}@"           \
            -e "s@__MUSL_VER__@${MUSL_VER}@"                   \
            -e "s@__GMP_VER__@${GMP_VER}@"                     \
            -e "s@__MPC_VER__@${MPC_VER}@"                     \
            -e "s@__MPFR_VER__@${MPFR_VER}@"                   \
            -e "s@__MINGW_VER__@${MINGW_VER}@"                 \
                                                               \
            -e "s@__THREADS__@${t}@";

        if [ ! -d "${huge}/${k}/${tget}${csuf}-${t}" ]; then
            PATH="${kale}:${base}/${k}${csuf}/bin:${base}/${tget}${csuf}/bin:${PATH}"     \
                                                               \
            CC="${k}-gcc"                                      \
            CXX="${k}-g++"                                     \
            RANLIB="${k}-ranlib"                               \
                                                               \
            CC_FOR_BUILD="${user}-gcc"                         \
            CXX_FOR_BUILD="${user}-g++"                        \
            RANLIB_FOR_BUILD="${user}-ranlib"                  \
                                                               \
            make -ik -C "${base}/${sinn}" -O clean install     \
                HOST=${k}                                      \
                TARGET=${tget}                                 \
                OUTPUT="${huge}/${k}/${tget}${csuf}-${t}"      \
                2>&1 | tee                          \
                    "${logs}/${k}/${tget}${csuf}-${t}.log";
        fi

    done;                       # thread model list
    done;                       # command-line argument list
    done;                       # source list (hosts)
}

# Pack the toolchains into tgz files. They're ready for distro.
#
run_pack ()
{
    cat "${base}/${filt}" | grep -v "#" | while read k; do
    for tget in ${@}; do        # command-line argument list
    for t in ${tmod}; do        # thread model list

        mkdir -p "${zips}/${k}";

        if [ ! -e "${zips}/${k}/${tget}${csuf}-${t}.tgz" ]; then
        (
            cd "${huge}/${k}";
            tar -pczf "${zips}/${k}/${tget}${csuf}-${t}.tgz"   \
                ${tget}${csuf}-${t};
        )
        fi

    done;                       # thread model list
    done;                       # command-line argument list
    done;                       # source list (hosts)
}

#---------------------------------------------------------------
# Driver.

get_tool;                       # Download initial toolchains.
get_repo;                       # Clone "musl-cross-make" repo.
get_conf;                       # Write toolchain configuration.
get_list;                       # Generate list of targets.
get_targ;                       # Fetch "donor" toolchains.

run_make ${@};                  # Build specified toolchains.
run_pack ${@};                  # Pack output for distribution.