347 lines
15 KiB
Bash
Executable file
347 lines
15 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# The MIT License (MIT)
|
|
#
|
|
# Copyright (C) 2026 Scott Duensing
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to
|
|
# deal in the Software without restriction, including without limitation the
|
|
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
# sell copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
# IN THE SOFTWARE.
|
|
|
|
# installToolchain.sh -- Install the DOS cross toolchain and test tools into toolchains/
|
|
#
|
|
# Usage: ./installToolchain.sh [--force]
|
|
#
|
|
# Downloads and unpacks, into toolchains/ next to this script:
|
|
# toolchains/djgpp DJGPP cross compiler, GCC 12.2.0 (prebuilt
|
|
# linux64 release of andrewwutw/build-djgpp v3.4)
|
|
# toolchains/cwsdpmi CWSDPMI r7 (csdpmi7b.zip); src/common.mk uses
|
|
# toolchains/cwsdpmi/bin/bin/CWSDSTUB.EXE
|
|
# toolchains/86box 86Box v6.0 (build 9001) x86_64 Linux AppImage,
|
|
# extracted once into app/ (no FUSE needed), and
|
|
# the matching 86Box ROM set (tag v6.0) in roms/
|
|
# toolchains/freedos FreeDOS 1.3 master boot record code, FAT16 boot
|
|
# sector, KERNEL.SYS and COMMAND.COM (from the
|
|
# LiteUSB disk image) and CuteMouse 2.1b4
|
|
# CTMOUSE.EXE: the DOS the emulator test disk boots
|
|
# (src/test/dos)
|
|
# toolchains/dosbox DOSBox Staging v0.83.0 (the quick emulator)
|
|
# toolchains/cppcheck cppcheck 2.21, built from its source release
|
|
# (src/test/lint/staticGate.sh)
|
|
# toolchains/lrzsz lrzsz 0.12.20 (src/test/terminal/installLrzsz.sh,
|
|
# which pins it), the Terminal tests' ZMODEM peer
|
|
# toolchains/pktdrv NE2000.COM packet driver (src/test/net/
|
|
# installPktDrv.sh, which pins it) for the DOS
|
|
# network tests
|
|
#
|
|
# Every download is checked against a pinned SHA-256 before it is unpacked,
|
|
# so a changed or corrupted archive is refused instead of installed.
|
|
# Archives are cached in toolchains/cache and reused on later runs.
|
|
# An existing, working install is left alone unless --force is given.
|
|
# After installing 86Box the script runs it once with its isolated
|
|
# environment (src/test/dos/emuEnv.sh) and fails if anything appeared in
|
|
# the per-user folders of the home directory.
|
|
# toolchains/ is gitignored; run this after a fresh clone.
|
|
|
|
set -euo pipefail
|
|
|
|
DJGPP_URL="https://github.com/andrewwutw/build-djgpp/releases/download/v3.4/djgpp-linux64-gcc1220.tar.bz2"
|
|
DJGPP_SHA256="8464f17017d6ab1b2bb2df4ed82357b5bf692e6e2b7fee37e315638f3d505f00"
|
|
DJGPP_VERSION="12.2.0"
|
|
CWSDPMI_URL="https://sandmann.dotster.com/cwsdpmi/csdpmi7b.zip"
|
|
CWSDPMI_SHA256="deacda0488e1cdd7c4a9f32fab45662b34c0ed6b2d7d4d13bc07041b62004a8c"
|
|
BOX86_URL="https://github.com/86Box/86Box/releases/download/v6.0/86Box-Linux-x86_64-b9001.AppImage"
|
|
BOX86_FILE="86Box-Linux-x86_64-b9001.AppImage"
|
|
BOX86_SHA256="535d3c329f8c32b601103970c86d80aa80298f345452c3524ea2f433624727a4"
|
|
ROMS_URL="https://github.com/86Box/roms/archive/refs/tags/v6.0.tar.gz"
|
|
ROMS_FILE="86box-roms-v6.0.tar.gz"
|
|
ROMS_SHA256="38fccaf4a7b48e581ccdfee2dc3cc0545e7d3a778bc25611e902a117eec571e6"
|
|
FREEDOS_URL="https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.3/official/FD13-LiteUSB.zip"
|
|
FREEDOS_FILE="FD13-LiteUSB.zip"
|
|
FREEDOS_SHA256="64a934585087ccd91a18c55e20ee01f5f6762be712eeaa5f456be543778f9f7e"
|
|
FREEDOS_DISK_IMG="FD13LITE.img"
|
|
FREEDOS_PART_SECTOR=63 # first sector of the image's FAT16 partition
|
|
MBR_CODE_BYTES=446 # boot code before the partition table
|
|
CTMOUSE_URL="https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/repositories/1.3/base/ctmouse.zip"
|
|
CTMOUSE_FILE="ctmouse.zip"
|
|
CTMOUSE_SHA256="a891124cd5b13e8141778fcae718f3b2667b0a49727ce92b782ab11a8c4bb63a"
|
|
DOSBOX_URL="https://github.com/dosbox-staging/dosbox-staging/releases/download/v0.83.0/dosbox-staging-linux-x86_64-v0.83.0.tar.xz"
|
|
DOSBOX_FILE="dosbox-staging-linux-x86_64-v0.83.0.tar.xz"
|
|
DOSBOX_SHA256="d3a94f7f1c3e68a47ec88d61145506c7904452adb0c9c5928cb8cfe2331d6c5c"
|
|
CPPCHECK_URL="https://github.com/danmar/cppcheck/archive/refs/tags/2.21.0.tar.gz"
|
|
CPPCHECK_FILE="cppcheck-2.21.0.tar.gz"
|
|
CPPCHECK_SHA256="f028ff75ca5372738f3737c8b3e8611426a6526b6aea2ef01301ab0f5902f044"
|
|
CPPCHECK_VERSION="2.21.0"
|
|
|
|
# Seconds the isolation check lets 86Box run
|
|
BOX86_CHECK_SECONDS=8
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TOOLCHAINS="${ROOT}/toolchains"
|
|
CACHE="${TOOLCHAINS}/cache"
|
|
DJGPP_DIR="${TOOLCHAINS}/djgpp"
|
|
CWSDPMI_DIR="${TOOLCHAINS}/cwsdpmi"
|
|
DJGPP_GCC="${DJGPP_DIR}/bin/i586-pc-msdosdjgpp-gcc"
|
|
CWSDSTUB="${CWSDPMI_DIR}/bin/bin/CWSDSTUB.EXE"
|
|
|
|
# Emulator, FreeDOS and cppcheck locations and the isolated run environment
|
|
DVX_ROOT="${ROOT}"
|
|
# shellcheck source=src/test/dos/emuEnv.sh
|
|
. "${ROOT}/src/test/dos/emuEnv.sh"
|
|
|
|
# Each install records the SHA-256 of the archive(s) it came from here, so
|
|
# a changed pin reinstalls.
|
|
PIN_FILE=".installed"
|
|
|
|
FORCE=0
|
|
|
|
for arg in "$@"; do
|
|
case "${arg}" in
|
|
--force) FORCE=1 ;;
|
|
-h|--help)
|
|
sed -n '/^# Usage/,/^# toolchains\/ is gitignored/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "installToolchain: unknown option '${arg}' (try --help)" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$(uname -s)" != "Linux" ] || [ "$(uname -m)" != "x86_64" ]; then
|
|
echo "installToolchain: the pinned DJGPP build is for x86_64 Linux; this host is $(uname -s) $(uname -m)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for tool in curl sha256sum tar bzip2 xz unzip make g++ mcopy xvfb-run; do
|
|
if ! command -v "${tool}" >/dev/null 2>&1; then
|
|
echo "installToolchain: required tool '${tool}' is not installed" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
mkdir -p "${CACHE}"
|
|
|
|
|
|
# fetch URL FILE SHA256 -- download FILE into the cache (unless a cached copy
|
|
# already matches) and verify it.
|
|
fetch() {
|
|
local url="$1"
|
|
local file="${CACHE}/$2"
|
|
local sum="$3"
|
|
|
|
if [ -f "${file}" ] && echo "${sum} ${file}" | sha256sum -c --status; then
|
|
echo "installToolchain: using cached $2"
|
|
return 0
|
|
fi
|
|
|
|
echo "installToolchain: downloading $2"
|
|
curl -fL --retry 3 -o "${file}.part" "${url}"
|
|
mv "${file}.part" "${file}"
|
|
|
|
if ! echo "${sum} ${file}" | sha256sum -c --status; then
|
|
echo "installToolchain: checksum mismatch for $2 (expected ${sum}); refusing to install" >&2
|
|
rm -f "${file}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
|
|
# pinned DIR PIN -- true when DIR holds an install made from PIN.
|
|
pinned() {
|
|
[ -f "$1/${PIN_FILE}" ] && [ "$(cat "$1/${PIN_FILE}")" = "$2" ]
|
|
}
|
|
|
|
|
|
# DJGPP
|
|
if [ "${FORCE}" -eq 0 ] && [ -x "${DJGPP_GCC}" ] && [ "$("${DJGPP_GCC}" -dumpfullversion 2>/dev/null)" = "${DJGPP_VERSION}" ]; then
|
|
echo "installToolchain: DJGPP ${DJGPP_VERSION} already installed"
|
|
else
|
|
fetch "${DJGPP_URL}" "djgpp-linux64-gcc1220.tar.bz2" "${DJGPP_SHA256}"
|
|
rm -rf "${DJGPP_DIR}.new"
|
|
mkdir -p "${DJGPP_DIR}.new"
|
|
tar -xjf "${CACHE}/djgpp-linux64-gcc1220.tar.bz2" -C "${DJGPP_DIR}.new" --strip-components=1
|
|
rm -rf "${DJGPP_DIR}"
|
|
mv "${DJGPP_DIR}.new" "${DJGPP_DIR}"
|
|
echo "installToolchain: DJGPP installed in ${DJGPP_DIR}"
|
|
fi
|
|
|
|
# CWSDPMI
|
|
if [ "${FORCE}" -eq 0 ] && [ -f "${CWSDSTUB}" ]; then
|
|
echo "installToolchain: CWSDPMI already installed"
|
|
else
|
|
fetch "${CWSDPMI_URL}" "csdpmi7b.zip" "${CWSDPMI_SHA256}"
|
|
rm -rf "${CWSDPMI_DIR}.new"
|
|
mkdir -p "${CWSDPMI_DIR}.new/bin"
|
|
(cd "${CWSDPMI_DIR}.new/bin" && unzip -qo "${CACHE}/csdpmi7b.zip")
|
|
rm -rf "${CWSDPMI_DIR}"
|
|
mv "${CWSDPMI_DIR}.new" "${CWSDPMI_DIR}"
|
|
echo "installToolchain: CWSDPMI installed in ${CWSDPMI_DIR}"
|
|
fi
|
|
|
|
# 86Box AppImage, extracted once so it runs without FUSE
|
|
if [ "${FORCE}" -eq 0 ] && pinned "${BOX86_APP}" "${BOX86_SHA256}" && [ -x "${BOX86_BIN}" ]; then
|
|
echo "installToolchain: 86Box already installed"
|
|
else
|
|
fetch "${BOX86_URL}" "${BOX86_FILE}" "${BOX86_SHA256}"
|
|
chmod +x "${CACHE}/${BOX86_FILE}"
|
|
rm -rf "${BOX86_APP}.new"
|
|
mkdir -p "${BOX86_APP}.new"
|
|
(cd "${BOX86_APP}.new" && "${CACHE}/${BOX86_FILE}" --appimage-extract >/dev/null)
|
|
mv "${BOX86_APP}.new/squashfs-root" "${BOX86_APP}.tmp"
|
|
rm -rf "${BOX86_APP}.new" "${BOX86_APP}"
|
|
mv "${BOX86_APP}.tmp" "${BOX86_APP}"
|
|
echo "${BOX86_SHA256}" > "${BOX86_APP}/${PIN_FILE}"
|
|
echo "installToolchain: 86Box installed in ${BOX86_APP}"
|
|
fi
|
|
|
|
# 86Box ROM set
|
|
if [ "${FORCE}" -eq 0 ] && pinned "${BOX86_ROMS}" "${ROMS_SHA256}"; then
|
|
echo "installToolchain: 86Box ROM set already installed"
|
|
else
|
|
fetch "${ROMS_URL}" "${ROMS_FILE}" "${ROMS_SHA256}"
|
|
rm -rf "${BOX86_ROMS}.new"
|
|
mkdir -p "${BOX86_ROMS}.new"
|
|
tar -xzf "${CACHE}/${ROMS_FILE}" -C "${BOX86_ROMS}.new" --strip-components=1
|
|
rm -rf "${BOX86_ROMS}"
|
|
mv "${BOX86_ROMS}.new" "${BOX86_ROMS}"
|
|
echo "${ROMS_SHA256}" > "${BOX86_ROMS}/${PIN_FILE}"
|
|
echo "installToolchain: 86Box ROM set installed in ${BOX86_ROMS}"
|
|
fi
|
|
|
|
# FreeDOS boot files and CuteMouse
|
|
FREEDOS_PIN="${FREEDOS_SHA256} ${CTMOUSE_SHA256}"
|
|
|
|
if [ "${FORCE}" -eq 0 ] && pinned "${FREEDOS_DIR}" "${FREEDOS_PIN}"; then
|
|
echo "installToolchain: FreeDOS boot files already installed"
|
|
else
|
|
fetch "${FREEDOS_URL}" "${FREEDOS_FILE}" "${FREEDOS_SHA256}"
|
|
fetch "${CTMOUSE_URL}" "${CTMOUSE_FILE}" "${CTMOUSE_SHA256}"
|
|
rm -rf "${FREEDOS_DIR}.new"
|
|
mkdir -p "${FREEDOS_DIR}.new"
|
|
unzip -p "${CACHE}/${FREEDOS_FILE}" "${FREEDOS_DISK_IMG}" > "${FREEDOS_DIR}.new/disk.img"
|
|
dd if="${FREEDOS_DIR}.new/disk.img" of="${FREEDOS_DIR}.new/MBR.BIN" bs="${MBR_CODE_BYTES}" count=1 status=none
|
|
dd if="${FREEDOS_DIR}.new/disk.img" of="${FREEDOS_DIR}.new/BOOTSECT.BIN" bs=512 skip="${FREEDOS_PART_SECTOR}" count=1 status=none
|
|
MTOOLS_SKIP_CHECK=1 mcopy -n -i "${FREEDOS_DIR}.new/disk.img@@$((FREEDOS_PART_SECTOR * 512))" ::KERNEL.SYS ::COMMAND.COM "${FREEDOS_DIR}.new/"
|
|
unzip -p "${CACHE}/${CTMOUSE_FILE}" BIN/CTMOUSE.EXE > "${FREEDOS_DIR}.new/CTMOUSE.EXE"
|
|
rm -f "${FREEDOS_DIR}.new/disk.img"
|
|
rm -rf "${FREEDOS_DIR}"
|
|
mv "${FREEDOS_DIR}.new" "${FREEDOS_DIR}"
|
|
echo "${FREEDOS_PIN}" > "${FREEDOS_DIR}/${PIN_FILE}"
|
|
echo "installToolchain: FreeDOS boot files installed in ${FREEDOS_DIR}"
|
|
fi
|
|
|
|
# DOSBox Staging
|
|
if [ "${FORCE}" -eq 0 ] && pinned "${DOSBOX_DIR}" "${DOSBOX_SHA256}" && [ -x "${DOSBOX_BIN}" ]; then
|
|
echo "installToolchain: DOSBox Staging already installed"
|
|
else
|
|
fetch "${DOSBOX_URL}" "${DOSBOX_FILE}" "${DOSBOX_SHA256}"
|
|
rm -rf "${DOSBOX_DIR}.new"
|
|
mkdir -p "${DOSBOX_DIR}.new"
|
|
tar -xJf "${CACHE}/${DOSBOX_FILE}" -C "${DOSBOX_DIR}.new" --strip-components=1
|
|
rm -rf "${DOSBOX_DIR}"
|
|
mv "${DOSBOX_DIR}.new" "${DOSBOX_DIR}"
|
|
echo "${DOSBOX_SHA256}" > "${DOSBOX_DIR}/${PIN_FILE}"
|
|
echo "installToolchain: DOSBox Staging installed in ${DOSBOX_DIR}"
|
|
fi
|
|
|
|
# cppcheck, built from source (no system package, no sudo)
|
|
if [ "${FORCE}" -eq 0 ] && pinned "${CPPCHECK_DIR}" "${CPPCHECK_SHA256}" && [ -x "${CPPCHECK_BIN}" ]; then
|
|
echo "installToolchain: cppcheck ${CPPCHECK_VERSION} already installed"
|
|
else
|
|
fetch "${CPPCHECK_URL}" "${CPPCHECK_FILE}" "${CPPCHECK_SHA256}"
|
|
rm -rf "${CPPCHECK_DIR}.build"
|
|
mkdir -p "${CPPCHECK_DIR}.build"
|
|
tar -xzf "${CACHE}/${CPPCHECK_FILE}" -C "${CPPCHECK_DIR}.build" --strip-components=1
|
|
echo "installToolchain: building cppcheck ${CPPCHECK_VERSION} (a few minutes)"
|
|
make -C "${CPPCHECK_DIR}.build" -j"$(nproc)" CXXFLAGS="-O2" FILESDIR="${CPPCHECK_DIR}/share/cppcheck" >"${CPPCHECK_DIR}.build/build.log" 2>&1 || {
|
|
echo "installToolchain: cppcheck build failed; see ${CPPCHECK_DIR}.build/build.log" >&2
|
|
exit 1
|
|
}
|
|
# FILESDIR is compiled in, so install straight to its final place.
|
|
rm -rf "${CPPCHECK_DIR}"
|
|
make -C "${CPPCHECK_DIR}.build" CXXFLAGS="-O2" PREFIX="${CPPCHECK_DIR}" FILESDIR="${CPPCHECK_DIR}/share/cppcheck" install >>"${CPPCHECK_DIR}.build/build.log" 2>&1
|
|
rm -rf "${CPPCHECK_DIR}.build"
|
|
echo "${CPPCHECK_SHA256}" > "${CPPCHECK_DIR}/${PIN_FILE}"
|
|
echo "installToolchain: cppcheck installed in ${CPPCHECK_DIR}"
|
|
fi
|
|
|
|
# lrzsz (host ZMODEM peer for the Terminal tests), pinned in its own script
|
|
if [ "${FORCE}" -eq 1 ]; then
|
|
"${ROOT}/src/test/terminal/installLrzsz.sh" --force
|
|
else
|
|
"${ROOT}/src/test/terminal/installLrzsz.sh"
|
|
fi
|
|
|
|
# NE2000 packet driver for the emulator network tests, pinned in its own script
|
|
if [ "${FORCE}" -eq 1 ]; then
|
|
"${ROOT}/src/test/net/installPktDrv.sh" --force
|
|
else
|
|
"${ROOT}/src/test/net/installPktDrv.sh"
|
|
fi
|
|
|
|
# Verify what the build needs.
|
|
if [ "$("${DJGPP_GCC}" -dumpfullversion)" != "${DJGPP_VERSION}" ]; then
|
|
echo "installToolchain: ${DJGPP_GCC} is not GCC ${DJGPP_VERSION}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for f in "${DJGPP_DIR}/i586-pc-msdosdjgpp/bin/dxe3gen" "${DJGPP_DIR}/i586-pc-msdosdjgpp/bin/exe2coff" "${CWSDSTUB}" "${BOX86_BIN}" "${BOX86_ROMS}/machines" "${FREEDOS_DIR}/MBR.BIN" "${FREEDOS_DIR}/BOOTSECT.BIN" "${FREEDOS_DIR}/KERNEL.SYS" "${FREEDOS_DIR}/COMMAND.COM" "${FREEDOS_DIR}/CTMOUSE.EXE" "${DOSBOX_BIN}" "${CPPCHECK_BIN}" "${TOOLCHAINS}/lrzsz/bin/lsz" "${TOOLCHAINS}/pktdrv/NE2000.COM"; do
|
|
if [ ! -e "${f}" ]; then
|
|
echo "installToolchain: expected file missing after install: ${f}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
case "$("${CPPCHECK_BIN}" --version)" in
|
|
"Cppcheck ${CPPCHECK_VERSION}"|"Cppcheck ${CPPCHECK_VERSION%.0}") ;;
|
|
*)
|
|
echo "installToolchain: ${CPPCHECK_BIN} is not cppcheck ${CPPCHECK_VERSION}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Run 86Box once, isolated, and prove nothing reached the home directory.
|
|
checkDir="${BOX86_DIR}/check"
|
|
rm -rf "${checkDir}"
|
|
mkdir -p "${checkDir}/vm"
|
|
printf '[Machine]\nmachine = ibmpc\nmem_size = 256\n' > "${checkDir}/vm/86box.cfg"
|
|
touch "${checkDir}/stamp"
|
|
emuHomeProbe "${checkDir}/stamp" > "${checkDir}/before.txt"
|
|
emuIsolatedEnv "${BOX86_HOME}"
|
|
setsid env "${EMU_ENV[@]}" xvfb-run -a "${BOX86_BIN}" -P "${checkDir}/vm" -R "${BOX86_ROMS}" -O "${checkDir}/global.cfg" -L "${checkDir}/86box.log" -N >/dev/null 2>&1 &
|
|
checkPid=$!
|
|
sleep "${BOX86_CHECK_SECONDS}"
|
|
emuStopGroup "${checkPid}"
|
|
emuHomeProbe "${checkDir}/stamp" > "${checkDir}/after.txt"
|
|
|
|
if ! diff -u "${checkDir}/before.txt" "${checkDir}/after.txt"; then
|
|
echo "installToolchain: 86Box changed the home directory (above); refusing to use it" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 86Box rewrites the machine config when it starts.
|
|
if ! grep -q "^emu_build_num" "${checkDir}/vm/86box.cfg"; then
|
|
echo "installToolchain: the 86Box check run did not start" >&2
|
|
exit 1
|
|
fi
|
|
|
|
rm -rf "${checkDir}"
|
|
echo "installToolchain: 86Box ran isolated (nothing written outside ${DVX_ROOT})"
|
|
|
|
echo "installToolchain: toolchain ready (GCC ${DJGPP_VERSION}, CWSDPMI r7, 86Box 6.0, FreeDOS 1.3, DOSBox Staging 0.83.0, cppcheck ${CPPCHECK_VERSION})"
|