1
0
Fork 0

Refactor the build and list-packages scripts

Until now the build and package scripts have supported
several architectures, compilers, build types, package-managers,
and bit-depth targets.

The code might be maintainable if left as-such, however we have plans
to continue expanding the number of architectures (ARM, PPC, ... ),
operating systems (Android, BSDs, ...), and build variations
amung those.

The scripts (regardless of language) would only grow in complexity
as more variations are added. Thus, we needed a solution that can
scale without adding complexity.

To achieve this, the scripts were refactored as follows:
 - all "data" was moved out of code into configuration files
 - A back-end "Automator" engine was written to parse the
   data based on generic variables fed to it by a front-end
   script
 - build.sh and list-packages.sh were re-authored as thin front-end
   scripts that drive the automator
 - Their CLI's were retained so there has been very little change
   needed to the CI invocation lines.  The only changes have been to
   clarify the existing arguments improved based on feedback, ie:
   --build-type release, --build-type debug instead of fast, small
This commit is contained in:
krcroft 2019-11-23 19:54:19 -08:00 committed by Patryk Obara
parent fd74aa0720
commit 0f11ab8ecb
39 changed files with 478 additions and 697 deletions

10
.gitignore vendored
View file

@ -53,6 +53,16 @@ stamp-h1
# Other
compile
build.log
clean.log
make.log
.previous_build
.current_build
# Visual Studio
.vs
# Common editor temp/backup files
*~
*.swp
*.tmp

View file

@ -0,0 +1,8 @@
ld="ld"
TYPES+=("msan" "usan")
cflags_msan=("${cflags_debug[@]}" "-fsanitize-recover=all" "-fsanitize=memory" "-fno-omit-frame-pointer")
cflags_usan=("${cflags_debug[@]}" "-fsanitize-recover=all" "-fsanitize=undefined")
MODIFIERS+=("lto")
cflags_lto=("-flto=thin")

View file

@ -0,0 +1,16 @@
# Tool overrides
cc="clang${postfix}"
cxx="clang++${postfix}"
# Flag additions
TYPES+=("debug" "profile")
cflags+=("-fcolor-diagnostics")
cflags_release=("${cflags[@]}" "-Os")
cflags_debug=("${cflags[@]}" "-g" "-Og" "-fno-omit-frame-pointer")
cflags_profile=("${cflags_debug[@]}" "-fprofile-instr-generate" "-fcoverage-mapping")
# Modifier additions
MODIFIERS=("fdo")
ldflags_fdo=("-fprofile-instr-generate")
cflags_fdo=("-fprofile-sample-use=${FDO_FILE:-}")

View file

@ -0,0 +1,13 @@
# Tool additions
ar="llvm-ar${postfix}"
ld="llvm-link${postfix}"
ranlib="llvm-ranlib${postfix}"
# Build additions
TYPES+=("msan" "usan")
cflags_msan=("${cflags_debug[@]}" "-fsanitize-recover=all" "-fsanitize=memory" "-fno-omit-frame-pointer")
cflags_usan=("${cflags_debug[@]}" "-fsanitize-recover=all" "-fsanitize=undefined")
# Modifier additions
MODIFIERS+=("lto")
cflags_lto=("-O2" "-flto=thin")

View file

@ -0,0 +1,5 @@
# Flag additions
ar="llvm-ar${postfix}"
ld="llvm-link${postfix}"
ranlib="llvm-ranlib${postfix}"
ldflags+=("-static-libgcc" "-static-libstdc++")

View file

View file

@ -0,0 +1,14 @@
# Tools and flags for all compilers
VARIABLES=("ar" "cc" "cxx" "ld" "ranlib" "cflags" "ldflags" "libs")
ar=""
cc=""
cxx=""
ld=""
ranlib=""
cflags=("-Wall" "-pipe")
ldflags=("")
libs=("")
# Builds for all compilers
TYPES=("release")
cflags_release=("${cflags[@]}")

View file

View file

@ -0,0 +1,15 @@
# Tool overrides
ar="ar"
ranlib="ranlib"
# Build additions
TYPES+=("asan" "uasan" "usan" "tsan")
cflags_asan=("${cflags_debug[@]}" "-fsanitize=address")
cflags_uasan=("${cflags_debug[@]}" "-fsanitize=address,undefined" "-fsanitize-recover=signed-integer-overflow")
cflags_usan=("${cflags_debug[@]}" "-fsanitize=undefined" "-fsanitize-recover=signed-integer-overflow")
cflags_tsan=("${cflags_debug[@]}" "-fsanitize=thread")
# Modifier additions
MODIFIERS+=("lto")
cflags_lto=("-flto")
ldflags_lto=("${cflags[@]}" "-flto")

View file

@ -0,0 +1,17 @@
# Tool overrides
ar="gcc-ar${postfix}"
cc="gcc${postfix}"
cxx="g++${postfix}"
ld="gcc${postfix}"
ranlib="gcc-ranlib${postfix}"
# Flag additions
TYPES+=("debug" "profile")
cflags+=("-fstack-protector" "-fdiagnostics-color=always")
cflags_release=("${cflags[@]}" "-Ofast" "-ffunction-sections" "-fdata-sections")
cflags_debug=("${cflags[@]}" "-g" "-Og" "-fno-omit-frame-pointer")
cflags_profile=("${cflags_debug[@]}" "-pg")
# Modifier additions
MODIFIERS=("fdo")
cflags_fdo=("-fauto-profile=${FDO_FILE:-}")

View file

@ -0,0 +1,14 @@
# Tool additions
ldflags+=("-Wl,--as-needed")
# Build additions
TYPES+=("asan" "uasan" "usan" "tsan")
cflags_asan=("${cflags_debug[@]}" "-fsanitize=address")
cflags_uasan=("${cflags_debug[@]}" "-fsanitize=address,undefined" "-fsanitize-recover=signed-integer-overflow")
cflags_usan=("${cflags_debug[@]}" "-fsanitize=undefined" "-fsanitize-recover=signed-integer-overflow")
cflags_tsan=("${cflags_debug[@]}" "-fsanitize=thread")
# Modifier additions
MODIFIERS+=("lto")
cflags_lto=("-flto")
ldflags_lto=("${cflags[@]}" "-flto=$(( $(nproc) + 2 ))")

View file

@ -0,0 +1,3 @@
# Flag additions
ldflags+=("-Wl,--as-needed" "-static-libgcc" "-static-libstdc++")

View file

@ -0,0 +1,3 @@
# Modifier additions universal for gcc and clang, but specific to x86_64:
MODIFIERS+=("native")
cflags_native=("-march=native")

View file

@ -0,0 +1,5 @@
# Tool additions and overrides for Darwin, regardless of compiler
ar="ar"
ranlib="ranlib"
function make_binary() { make -j$(sysctl -n hw.physicalcpu) 2>&1 | tee build.log; }
dependencies=("otool" "-L" "${executable}")

View file

@ -0,0 +1,57 @@
# Steps in-common to all OSes, plus some helper variables and functions.
STEPS=("pre_build" "clean" "autogen" "configure" "make_binary" "strip_binary" "dependencies" "post_build")
executable="src/dosbox"
dependencies=("ldd" "${executable}")
function pre_build() {
cd ../..
underline "Environment"
echo "[$("${CC}" --version | head -1)]"
for v in "${VARIABLES[@]}"; do
vupper=$(upper "${v}")
echo "${vupper}=\"$(eval echo \$"${vupper}")\""
done
underline "Launching"
}
function stamp() {
echo "${compiler}-${selected_type}-${modifiers[*]}"
}
function autogen() {
if [[ ! -f configure || configure.ac -nt configure ]]; then
./autogen.sh
fi
}
function configure() {
if [[ ! -f Makefile || ! -f .previous_build || configure -nt Makefile ]]; then
export CXXFLAGS="${CFLAGS}"
./configure --enable-core-inline
fi
}
function strip_binary() {
if [[ "${selected_type}" == "release" ]]; then
strip "${executable}"
fi
}
function post_build() {
underline "Binary" "-"
ls -1lh "${executable}"
stamp > .previous_build
}
function clean() {
stamp > .current_build
if [[ -f Makefile ]]; then
if [[ ! -f .previous_build ]] || ! diff -q .previous_build .current_build &> /dev/null; then
rm -f .previous_build
echo "cleaning, this is a different build type"
make clean &> clean.log || cat clean.log
fi
fi
}

View file

@ -0,0 +1,2 @@
function make_binary() { make -j$(nproc) 2>&1 | tee build.log; }

View file

@ -0,0 +1,3 @@
function make_binary() { make -j$(nproc) 2>&1 | tee build.log; }
executable="src/dosbox.exe"

120
scripts/automator/main.sh Normal file
View file

@ -0,0 +1,120 @@
#!/bin/bash
# Copyright (c) 2019 Kevin R Croft <krcroft@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later
# Automator is a generic automation tool that helps separate data
# from logical code. It uses one or more "variables" files placed
# in a single sub-directory, that are sourced in logical succession,
# such as variables defined in earler files are either added-to or
# overridden in subsequent files.
#
# For a thorough example, see scripts/build.sh and its data files
# in scripts/automator/build.
#
# TODO: This script manages variables using eval meta-programming.
# Although the syntax is quite readable under bash 4.x,
# we have limitted our syntax to the uglier and heavier bash 3.x
# because Apple is still only shipping bash 3.x, even on their
# latest operating system (Nov-2019).
#
# Future work might involve switching this to python and using YAML
# syntax in the variables files. Python's dictionaries can be
# merged or have their values overridden, and key names can be readily
# manipulated.
#
set -euo pipefail
function underline() {
echo ""
echo "$1"
echo "${1//?/${2:--}}"
}
function lower() {
echo "${1}" | tr '[:upper:]' '[:lower:]'
}
function upper() {
echo "${1}" | tr '[:lower:]' '[:upper:]'
}
function arg_error() {
local PURPLE='\033[0;34m'
local BLACK='\033[0;30m'
local ON_WHITE='\033[47m'
local NO_COLOR='\033[0m'
>&2 echo -e "Please specify the ${BLACK}${ON_WHITE}${1}${NO_COLOR} argument with one of: ${PURPLE}${2}${NO_COLOR}"
exit 1
}
function import() { # arguments: category instance
category=$(lower "${1}")
instance=$(lower "${2}")
if [[ -z "$instance" ]]; then
instances=( "$(find "${data_dir}" -name "${category}-*" -a ! -name '*defaults' | sed 's/.*-//' | xargs)" )
arg_error "--${category}" "${instances[*]}"
else for instance in defaults "${instance}"; do
varfile="${data_dir}/${category}-${instance}"
# shellcheck disable=SC1090,SC1091
if [[ -f "$varfile" ]]; then source "$varfile"
else >&2 echo "${varfile} does not exist, skipping"; fi; done
fi
}
function construct_environment() {
# underline "Environment"
for base_var in "${VARIABLES[@]}"; do
# First check if we have a build-specific variable
# shellcheck disable=SC2154
if [[ -n "$(eval 'echo ${'"${base_var}_${selected_type}"'[*]:-}')" ]]; then
var_name="${base_var}_${selected_type}"
# Otherwise fallback to the default variable
else var_name="${base_var}"; fi
# Aggregate any modifiers, which are stand-alone arguments
mod_values=""
# shellcheck disable=SC2154
for mod_upper in "${modifiers[@]}"; do
mod=$(lower "${mod_upper}")
# Mods can only add options to existing VARs, so we check for those
if [[ -n "$(eval 'echo ${'"${base_var}_${mod}"'[*]:-}')" ]]; then
var_with_mod="${base_var}_${mod}"
# shellcheck disable=SC2034
mod_values=$(eval 'echo ${mod_values} ${'"${var_with_mod}"'[*]}')
fi
done
# Expand our variable array (or scalar) and combine it with our aggregated
# modification string. Echo takes care of spacing out our variables without
# explicit padding, because it either drops empty variables or adds spaces
# between them if they exist.
# shellcheck disable=SC2034
combined=$(eval 'echo ${'"${var_name}"'[*]} ${mod_values}')
env_var=$(upper "${base_var}")
eval 'export '"${env_var}"'="${combined}"'
# echo "${env_var}=\"$(eval echo \$"${env_var}")\""
done
}
function perform_steps() {
# underline "Launching"
for step in "${STEPS[@]}"; do
if type -t "$step" | grep -q function; then "$step"
else
# eval 'echo ${'"${step}"'[*]}'
eval '${'"${step}"'[*]}'
fi
done
}
function main() {
cd "$(dirname "$0")/automator"
if [[ -z "${data_dir:-}" ]]; then data_dir="$(basename "$0" '.sh')"; fi
parse_args "$@"
construct_environment
perform_steps
}
main "$@"

View file

@ -0,0 +1,3 @@
# Brew does not supply "clang", so exclude it here
compiler=""

View file

@ -0,0 +1,2 @@
compiler=(clang${postfix})

View file

@ -0,0 +1,2 @@
# macports doesn't supply Clang, so knock it out here
compiler=""

View file

@ -0,0 +1,4 @@
# Under MSYS2, only the 'default' version is available, so we don't postfix the package
compiler="mingw-w64-${pkg_type}-${selected_type}"

View file

@ -0,0 +1,4 @@
# vcpkg doesn't provide clang or gcc, because it assumes you're using MS VisualStudio.
# So we knock out the compiler.
compiler=""

View file

@ -0,0 +1,2 @@
compiler=(g++${postfix})

View file

@ -0,0 +1 @@
compiler=(gcc${postfix})

View file

@ -0,0 +1,3 @@
# Under MSYS2 only the 'default' version of gcc is available, so we don't postfix it with the version
compiler="mingw-w64-${pkg_type}-${selected_type}"

View file

@ -0,0 +1,4 @@
# vcpkg doesn't provide clang or gcc, because it assumes you're using MS VisualStudio.
# So we knock out the compiler.
compiler=""

View file

@ -0,0 +1,2 @@
# Package repo: https://packages.ubuntu.com/
packages+=(xvfb libtool build-essential libsdl1.2-dev libsdl-net1.2-dev libopusfile-dev)

View file

@ -0,0 +1,3 @@
# Package repo: https://formulae.brew.sh/
delim="@"
packages+=(coreutils autogen autoconf automake pkg-config libpng sdl sdl_net opusfile)

View file

@ -0,0 +1,11 @@
VARIABLES=(packages bits delim compiler)
TYPES=(gcc clang)
packages=(autoconf-archive zstd)
delim="-"
compiler=""
STEPS=(print)
function print() {
echo "${PACKAGES[*]}" "${COMPILER}"
}

View file

@ -0,0 +1,2 @@
# Package repo: https://apps.fedoraproject.org/packages/
packages+=(xvfb libtool SDL SDL_net-devel opusfile-devel)

View file

@ -0,0 +1,3 @@
# Package repo: https://www.macports.org/ports.php?by=name
delim=""
packages+=(coreutils autogen autoconf automake pkgconfig libpng libsdl libsdl_net opusfile)

View file

@ -0,0 +1,7 @@
# Package repo: https://packages.msys2.org/base
# MSYS2 only supports the current latest releases of Clang and GCC, so we disable version customization
packages+=(autogen autoconf base-devel automake-wrapper binutils)
pkg_type=$([[ "${bits}" == "64" ]] && echo "x86_64" || echo "i686")
for pkg in pkg-config libtool libpng zlib SDL SDL_net opusfile; do
packages+=("mingw-w64-${pkg_type}-${pkg}")
done

View file

@ -0,0 +1,4 @@
# Package repo: https://www.archlinux.org/packages/
# Arch offers 32-bit versions of SDL (but not others)
packages+=(xvfb libtool sdl_net opusfile)
[[ "${bits}" == "32" ]] && packages+=(lib32-sdl) || packages+=(sdl)

View file

@ -0,0 +1,2 @@
# Package repo: https://repology.org/projects/?inrepo=vcpkg
packages+=(libpng sdl1 sdl1-net opusfile)

View file

@ -0,0 +1,10 @@
# Package repo: https://pkgs.org/
# openSUSE offers 32-bit versions of SDL and SDL_net (but not others)
packages+=(devel_basis xvfb libtool opusfile)
if [[ "${bits}" == "32" ]]; then
packages+=(libSDL-devel-32bit libSDL_net-devel-32bit)
else
packages+=(SDL SDL_net)
fi

View file

@ -57,7 +57,7 @@ Use of both scripts is described below.
`./scripts/list-build-dependencies.sh -p msys2 | xargs pacman -S --noconfirm`
1. Launch the build script with default settings:
`./scripts/build.sh --bin-path /mingw64/bin`
`./scripts/build/run.sh --bin-path /mingw64/bin`
## MacOS Installation and Usage
@ -131,10 +131,15 @@ options to the **list-build-dependencies.sh** and **build.sh** scripts:
After building, your `dosbox` or `dosbox.exe` binary will reside inside `./dosbox-staging/src/`.
Build flags you might be interested in:
* `--release debug`, to build a binary containing debug symbols (instead of **fast** or **small**)
* `--lto`, perform optimizations across the entire object space instead of per-file (Only available on Mac and Linux)
The above flags are othogonal and thus can be mixed-and-matched as desired.
* `--release debug`, to build a binary containing debug symbols
* You can run the resulting binary in the GNU debugger: `gdb /path/to/dosbox`, followed by `start mygame.bat`
* `--release profile`, to generate performance statistics
* Instructions are provided after the build completes, which describe how to generate and process the profiling data
* `--release <sanitizer-type>`, to build a binary that performs dynamic code-analysis at runtime (Linux and macOS)
* see `./scripts/build.sh --help` for a list of sanitizer-types that are available
* Run your binary like normal and it will generate output describing problematic behavior
* Some sanitizers accept runtime options via an environment variables, such as `ASAN_OPTIONS`, described here: https://github.com/google/sanitizers/wiki/AddressSanitizerFlags
If you want to run multiple back-to-back builds from the same directory with different settings then
add the `--clean` flag to ensure previous objects and binaries are removed.

View file

@ -1,518 +1,61 @@
#!/bin/bash
##
#
# Copyright (c) 2019 Kevin R. Croft
# SPDX-License-Identifier: GPL-2.0-or-later
#
# This script builds DOSBox within supported environments including
# MacOS, Ubuntu Linux, and MSYS2 using specified compilers and release types.
#
# For automation without prompts, Windows should have User Account Control (UAC)
# disabled, which matches the configuration of GitHub'Windows VMs, described here:
# https://help.github.com/en/articles/virtual-environments-for-github-actions
#
# See the usage block below for details or run it with the -h or --help arguments.
#
# In general, this script adheres to Google's shell scripting style guide
# (https://google.github.io/styleguide/shell.xml), however some deviations (such as
# tab indents instead of two-spaces) are used to fit with DOSBox's in-practice"
# coding style.
#
# Copyright (c) 2019 Kevin R Croft <krcroft@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later
# This script builds the software for the given build-type (release,
# debug, ... ) and compiler (gcc or clang).
#
# If run without arguments, the script asks for required arguments one
# by one, which includes the above two (--compiler and --build-type).
#
# Optional arguments include the version of compiler and additional
# build modifiers such as link-time-optimizations (--modifier lto),
# feedback-directed-optimizations (--modifier fdo), and taking advantage
# of the building-machine's full instructions sets (--modifier native).
# All modifiers are available simulatenously.
#
# Usage examples:
# $ ./build.sh # asks for a compiler
# $ ./build.sh --compiler gcc # asks for a build-type
# $ ./build.sh --compiler clang --build-type debug # builds!
# $ ./build.sh -c gcc -t release -m lto -m native # builds!
#
# This script makes use of the automator package, see automator/main.sh.
#
set -euo pipefail
readonly INVOCATION="${0}"
readonly IFS=$'\n\t'
function usage() {
if [ -n "${1}" ]; then
errcho "${1}"
fi
local script
script=$(basename "${INVOCATION}")
echo "Usage: ${script} [-b 32|64] [-c gcc|clang] [-f linux|macos|msys2] [-d] [-l]"
echo " [-p /custom/bin] [-u #] [-r fast|small|debug] [-s /your/src] [-t #]"
echo ""
echo " FLAG Description Default"
echo " ----------------------- ----------------------------------------------------- -------"
echo " -b, --bit-depth Build a 64 or 32 bit binary [$(print_var "${BITS}")]"
echo " -c, --compiler Choose either gcc or clang [$(print_var "${COMPILER}")]"
echo " -f, --force-system Force the system to be linux, macos, or msys2 [$(print_var "${SYSTEM}")]"
echo " -d, --fdo Use Feedback-Directed Optimization (FDO) data [$(print_var "${FDO}")]"
echo " -l, --lto Perform Link-Time-Optimizations (LTO) [$(print_var "${LTO}")]"
echo " -p, --bin-path Prepend PATH with the one provided to find executables [$(print_var "${BIN_PATH}")]"
echo " -u, --compiler-version Customize the compiler postfix (ie: 9 -> gcc-9) [$(print_var "${COMPILER_VERSION}")]"
echo " -r, --release Build a fast, small, or debug release [$(print_var "${RELEASE}")]"
echo " -s, --src-path Enter a different source directory before building [$(print_var "${SRC_PATH}")]"
echo " -t, --threads Override the number of threads with which to compile [$(print_var "${THREADS}")]"
echo " -v, --version Print the version of this script [$(print_var "${SCRIPT_VERSION}")]"
echo " -x, --clean Clean old objects prior to building [$(print_var "${CLEAN}")]"
echo " -h, --help Print this usage text"
echo ""
echo "Example: ${script} -b 32 --compiler clang -u 8 --bin-path /mingw64/bin -r small --lto"
echo ""
echo "Note: the last value will take precendent if duplicate flags are provided."
exit 1
}
function parse_args() {
set_defaults
# Gather the parameters that define this build
postfix=""
modifiers=("")
while [[ "${#}" -gt 0 ]]; do case ${1} in
-b|--bit-depth) BITS="${2}"; shift;shift;;
-c|--compiler) COMPILER="${2}"; shift;shift;;
-d|--fdo) FDO="true"; shift;;
-f|--force-system) SYSTEM="${2}"; shift;shift;;
-l|--lto) LTO="true"; shift;;
-p|--bin-path) BIN_PATH="${2}"; shift;shift;;
-u|--compiler-version) COMPILER_VERSION="${2}";shift;shift;;
-r|--release) RELEASE="${2}"; shift;shift;;
-s|--src-path) SRC_PATH="${2}"; shift;shift;;
-t|--threads) THREADS="${2}"; shift;shift;;
-v|--version) print_version; shift;;
-x|--clean) CLEAN="true"; shift;;
-h|--help) usage "Show usage"; shift;;
*) usage "Unknown parameter: ${1}"; shift;shift;;
-c|--compiler) compiler="${2}"; shift 2;;
-v|--version-postfix) postfix="-${2}"; shift 2;;
-t|--build-type) selected_type="${2}"; shift 2;;
-m|--modifier) modifiers+=("${2}"); shift 2;;
-p|--bin-path) PATH="${2}:${PATH}"; shift 2;;
*) >&2 echo "Unknown parameter: ${1}"; exit 1; shift 2;;
esac; done
# Import our settings and report missing values
machine="$(uname -m | sed 's/-.*//')"; import machine "${machine}"
os="$(uname -s | sed 's/-.*//')"; import os "${os}"
import compiler "${compiler:-}"
import "${compiler:-}" "${os}_${machine}"
if [[ -z "${selected_type:-}" ]]; then arg_error "--build-type" "${TYPES[*]}"; fi
# Create a pretty modifier string that we can add to our build-type
printf -v mod_string '+%s' "${modifiers[@]:1}"
if [[ "${mod_string}" == "+" ]]; then mod_string=""; fi
# Print a summary of our build configuration
underline "Compiling a ${selected_type}${mod_string} build using "`
`"${compiler}${postfix} on ${os}-${machine}" "="
# Ensure our selected_type is lower-case before proceeding
selected_type=$(lower "${selected_type}")
}
function set_defaults() {
# variables that are directly set via user arguments
BITS="64"
CLEAN="false"
COMPILER="gcc"
COMPILER_VERSION="unset"
FDO="false"
LTO="false"
BIN_PATH="unset"
RELEASE="fast"
SRC_PATH="unset"
SYSTEM="auto"
THREADS="auto"
# derived variables with initial values
EXECUTABLE="unset"
VERSION_POSTFIX=""
MACHINE="unset"
CONFIGURE_OPTIONS=("--enable-core-inline")
CFLAGS_ARRAY=("-Wall" "-pipe")
LDFLAGS_ARRAY=("")
LIBS_ARRAY=("")
CALL_CACHE=("")
# read-only strings
readonly SCRIPT_VERSION="1.0"
readonly REPO_URL="https://github.com/dreamer/dosbox-staging"
# environment variables passed onto the build
export CC="CC_is_not_set"
export CXX="CXX_is_not_set"
export LD="LD_is_not_set"
export AR="AR_is_not_set"
export RANLIB="RANLIB_is_not_set"
export CFLAGS=""
export CXXFLAGS=""
export LDFLAGS=""
export LIBS=""
export GCC_COLORS="error=01;31:warning=01;35:note=01;36:range1=32:range2=34:locus=01:\
quote=01:fixit-insert=32:fixit-delete=31:diff-filename=01:\
diff-hunk=32:diff-delete=31:diff-insert=32:type-diff=01;32"
}
function errcho() {
local CLEAR='\033[0m'
local RED='\033[0;91m'
>&2 echo ""
>&2 echo -e " ${RED}👉 ${*}${CLEAR}" "\\n"
}
function bug() {
local CLEAR='\033[0m'
local YELLOW='\033[0;33m'
>&2 echo -e " ${YELLOW}Please report the following at ${REPO_URL}${CLEAR}"
errcho "${@}"
exit 1
}
function error() {
errcho "${@}"
exit 1
}
function exists() {
command -v "${1}" &> /dev/null
}
function print_var() {
if [[ -z "${1}" ]]; then
echo "unset"
else
echo "${1}"
fi
}
##
# Uses
# ----
# Alows function to indicate which other functions they depend on.
# For example: "uses system" indicates a function needs the system
# to be defined prior to running.
# This uses function acts like a call cache, ensuring each used function
# is only actually called once. This allows all functions to thoroughly
# employ the 'uses' mechanism without the performance-hit of repeatedly
# executing the same function. Likely, functions that are 'used' are
# atomic in that they will only be called once per script invocation.
#
function uses() {
# assert
if [[ "${#}" != 1 ]]; then
bug "The 'uses' function was called without an argument"
fi
# only operate on functions in our call-scope, otherwise fail hard
func="${1}"
if [[ "$(type -t "${func}")" != "function" ]]; then
bug "The 'uses' function was passed ${func}, which isn't a function"
fi
# Check the call cache to see if the function has already been called
local found_in_previous="false"
for previous_func in "${CALL_CACHE[@]}"; do
if [[ "${previous_func}" == "${func}" ]]; then
found_in_previous="true"
break
fi
done
# if it hasn't been called then record it and run it
if [[ "${found_in_previous}" == "false" ]]; then
CALL_CACHE+=("${func}")
"${func}"
fi
}
function print_version() {
echo "${SCRIPT_VERSION}"
exit 0
}
function system() {
if [[ "${MACHINE}" == "unset" ]]; then MACHINE="$(uname -m)"; fi
if [[ "${SYSTEM}" == "auto" ]]; then SYSTEM="$(uname -s)"; fi
case "$SYSTEM" in
Darwin|macos) SYSTEM="macos" ;;
MSYS*|msys2) SYSTEM="msys2" ;;
Linux|linux) SYSTEM="linux" ;;
*) error "Your system, $SYSTEM, is not currently supported" ;;
esac
}
function bits() {
if [[ "${BITS}" != 64 && "${BITS}" != 32 ]]; then
usage "A bit-depth of ${BITS} is not allowed; choose 64 or 32"
fi
}
function compiler_type() {
if [[ "${COMPILER}" != "gcc" && "${COMPILER}" != "clang" ]]; then
usage "The choice of compiler (${COMPILER}) is not valid; choose gcc or clang"
fi
}
function tools_and_flags() {
uses compiler_type
uses compiler_version
uses system
# GCC universal
if [[ "${COMPILER}" == "gcc" ]]; then
CC="gcc${VERSION_POSTFIX}"
LD="gcc${VERSION_POSTFIX}"
CXX="g++${VERSION_POSTFIX}"
CFLAGS_ARRAY+=("-fstack-protector" "-fdiagnostics-color=always")
# Prioritize versioned lib-tools over generics
AR="gcc-ar${VERSION_POSTFIX}"
RANLIB="gcc-ranlib${VERSION_POSTFIX}"
if ! exists "${AR}"; then AR="ar"; fi
if ! exists "${RANLIB}"; then RANLIB="ranlib"; fi
# CLANG universal
elif [[ "${COMPILER}" == "clang" ]]; then
CC="clang${VERSION_POSTFIX}"
CXX="clang++${VERSION_POSTFIX}"
CFLAGS_ARRAY+=("-fcolor-diagnostics")
# CLANG on Linux and MSYS2
if [[ "${SYSTEM}" == "linux" || "${SYSTEM}" == "msys2" ]]; then
LD="llvm-link${VERSION_POSTFIX}"
AR="llvm-ar${VERSION_POSTFIX}"
RANLIB="llvm-ranlib${VERSION_POSTFIX}"
# CLANG on MacOS
elif [[ "${SYSTEM}" == "macos" ]]; then LD="ld"; fi
fi
# macOS universal
if [[ "${SYSTEM}" == "macos" ]]; then
AR="ar"
RANLIB="ranlib"
fi
}
function src_path() {
if [[ "${SRC_PATH}" == "unset" ]]; then
SRC_PATH="$(cd "$(dirname "${INVOCATION}")" && cd .. && pwd -P)"
elif [[ ! -d "${SRC_PATH}" ]]; then
usage "The requested source directory (${SRC_PATH}) does not exist, is not a directory, or is not accessible"
fi
cd "${SRC_PATH}"
}
function bin_path() {
uses system
uses compiler_type
# By default, if we're on macOS and using GCC then always include /usr/local/bin, because
# that's where brew installs all the binaries. If the user adds their own --bin-path,
# that will be prefixed ahead of /usr/local/bin and take precedent.
if [[ "${SYSTEM}" == "macos" && "${COMPILER}" == "gcc" ]]; then
PATH="/usr/local/bin:${PATH}"
fi
if [[ "${BIN_PATH}" != "unset" ]]; then
if [[ ! -d "${BIN_PATH}" ]]; then
usage "The requested PATH (${BIN_PATH}) does not exist, is not a directory, or is not accessible"
else
PATH="${BIN_PATH}:${PATH}"
fi
fi
}
function check_build_tools() {
for tool in "${CC}" "${CXX}" "${LD}" "${AR}" "${RANLIB}"; do
if ! exists "${tool}"; then
error "${tool} was not found in your PATH or is not executable. If it's in a custom path, use --bin-path"
fi
done
}
function compiler_version() {
if [[ "${COMPILER_VERSION}" != "unset" ]]; then
VERSION_POSTFIX="-${COMPILER_VERSION}"
fi
}
function release_flags() {
uses compiler_type
if [[ "${RELEASE}" == "fast" ]]; then CFLAGS_ARRAY+=("-Ofast")
elif [[ "${RELEASE}" == "small" ]]; then
CFLAGS_ARRAY+=("-Os")
if [[ "${COMPILER}" == "gcc" ]]; then
CFLAGS_ARRAY+=("-ffunction-sections" "-fdata-sections")
# ld on MacOS doesn't understand --as-needed, so exclude it
uses system
if [[ "${SYSTEM}" != "macos" ]]; then LDFLAGS_ARRAY+=("-Wl,--as-needed"); fi
fi
elif [[ "${RELEASE}" == "debug" ]]; then CFLAGS_ARRAY+=("-g" "-O1")
else usage "The release type of ${RELEASE} is not allowed. Choose fast, small, or debug"
fi
}
function threads() {
if [[ "${THREADS}" == "auto" ]]; then
if exists nproc; then THREADS="$(nproc)"
else THREADS="$(sysctl -n hw.physicalcpu || echo 4)"; fi
fi
# make presents a descriptive error message in the scenario where the user overrides
# THREADS with an illegal value: the '-j' option requires a positive integer argument.
}
function fdo_flags() {
if [[ "${FDO}" != "true" ]]; then
return
fi
uses compiler_type
uses src_path
local fdo_file="${SRC_PATH}/scripts/profile-data/${COMPILER}.profile"
if [[ ! -f "${fdo_file}" ]]; then
error "The Feedback-Directed Optimization file provided (${fdo_file}) does not exist or could not be accessed"
fi
if [[ "${COMPILER}" == "gcc" ]]; then
# Don't let GCC 6.x and under use both FDO and LTO
uses compiler_version
if [[ ( "${COMPILER_VERSION}" == "unset"
&& "$(2>&1 gcc -v | grep -Po '(?<=version )[^.]+')" -lt "7"
|| "${COMPILER_VERSION}" -lt "7" )
&& "${LTO}" == "true" ]]; then
error "GCC versions 6 and under cannot handle FDO and LTO simultaneously; please change one or more these."
fi
CFLAGS_ARRAY+=("-fauto-profile=${fdo_file}")
elif [[ "${COMPILER}" == "clang" ]]; then
CFLAGS_ARRAY+=("-fprofile-sample-use=${fdo_file}")
fi
}
function lto_flags() {
if [[ "${LTO}" != "true" ]]; then
return
fi
uses system
# Only allow LTO on Linux and MacOS; it currently fails under Windows
if [[ "${SYSTEM}" == "msys2" ]]; then
usage "LTO currently does not link or build on Windows with GCC or Clang"
fi
uses compiler_type
if [[ "${COMPILER}" == "gcc" ]]; then
CFLAGS_ARRAY+=("-flto")
# The linker performs the code optimizations across all objects, and therefore
# needs the same flags as the compiler would normally get
LDFLAGS_ARRAY+=("${CFLAGS_ARRAY[@]}")
# GCC on MacOS fails to parse the thread flag, so we only provide it under Linux
# (error: unsupported argument 4 to option flto=)
if [[ "${SYSTEM}" == "linux" ]]; then
uses threads
LDFLAGS_ARRAY+=("-flto=$THREADS")
fi
elif [[ "${COMPILER}" == "clang" ]]; then
CFLAGS_ARRAY+=("-flto=thin")
# Clang LTO on Linux is incompatible with -Os so replace them with -O2
# (ld: error: Optimization level must be between 0 and 3)
if [[ "${SYSTEM}" == "linux" ]]; then
CFLAGS_ARRAY=("${CFLAGS_ARRAY[@]/-Os/-O2}")
fi # clang-linux-lto-small exclusion
fi # gcc & clang
}
function configure_options() {
uses system
if [[ "${MACHINE}" != *"86"* ]]; then
CONFIGURE_OPTIONS+=("--disable-dynamic-x86" "--disable-fpu-x86" "--disable-fpu-x64")
fi
}
function do_autogen() {
uses src_path
if [[ ! -f autogen.sh ]]; then
error "autogen.sh doesn't exist in our current directory $PWD. If your DOSBox source is somewhere else set it with --src-path"
fi
# Only autogen if needed ..
if [[ ! -f configure ]]; then
uses bin_path
./autogen.sh
fi
}
function do_configure() {
uses bin_path
uses src_path
uses tools_and_flags
uses release_flags
uses fdo_flags
uses lto_flags
uses check_build_tools
uses configure_options
# Convert our arrays into space-delimited strings
LIBS=$( printf "%s " "${LIBS_ARRAY[@]}")
CFLAGS=$( printf "%s " "${CFLAGS_ARRAY[@]}")
CXXFLAGS=$(printf "%s " "${CFLAGS_ARRAY[@]}")
LDFLAGS=$( printf "%s " "${LDFLAGS_ARRAY[@]}")
local lto_string=""
local fdo_string=""
if [[ "${LTO}" == "true" ]]; then lto_string="-LTO"; fi
if [[ "${FDO}" == "true" ]]; then fdo_string="-FDO"; fi
echo ""
echo "Launching with:"
echo ""
echo " CC = ${CC}"
echo " CXX = ${CXX}"
echo " LD = ${LD}"
echo " AR = ${AR}"
echo " RANLIB = ${RANLIB}"
echo " CFLAGS = ${CFLAGS}"
echo " CXXFLAGS = ${CXXFLAGS}"
echo " LIBS = ${LIBS}"
echo " LDFLAGS = ${LDFLAGS}"
echo " THREADS = ${THREADS}"
echo ""
echo "Build type: ${SYSTEM}-${MACHINE}-${BITS}bit-${COMPILER}-${RELEASE}${lto_string}${fdo_string}"
echo ""
"${CC}" --version
sleep 5
if [[ ! -f configure ]]; then
error "configure script doesn't exist in $PWD. If the source is somewhere else, set it with --src-path"
elif ! ./configure "${CONFIGURE_OPTIONS[@]}"; then
>&2 cat "config.log"
error "configure failed, see config.log output above"
fi
}
function executable() {
uses src_path
EXECUTABLE="src/"
if [[ "${SYSTEM}" == "msys2" ]]; then EXECUTABLE+="dosbox.exe"
else EXECUTABLE+="dosbox"
fi
if [[ ! -f "${EXECUTABLE}" ]]; then
error "${EXECUTABLE} does not exist or hasn't been created yet"
fi
}
function build() {
uses src_path
uses bin_path
uses threads
if [[ "${CLEAN}" == "true" && -f "Makefile" ]]; then
make clean 2>&1 | tee -a build.log
fi
do_autogen
do_configure
make -j "${THREADS}" 2>&1 | tee -a build.log
}
function strip_binary() {
if [[ "${RELEASE}" == "debug" ]]; then
echo "[skipping strip] Debug symbols will be left in the binary because it's a debug release"
else
uses bin_path
uses executable
strip "${EXECUTABLE}"
fi
}
function show_binary() {
uses bin_path
uses system
uses executable
if [[ "$SYSTEM" == "macos" ]]; then otool -L "${EXECUTABLE}"
else ldd "${EXECUTABLE}"; fi
ls -1lh "${EXECUTABLE}"
}
function main() {
parse_args "$@"
build
strip_binary
show_binary
}
main "$@"
# shellcheck source=scripts/automator/main.sh
source "$(dirname "$0")/automator/main.sh"

View file

@ -1,196 +1,55 @@
#!/bin/bash
##
# Copyright (c) 2019 Kevin R. Croft
# SPDX-License-Identifier: GPL-2.0-or-later
#
# This script lists development packages and DOSBox dependencies needed to build DOSBox.
# This package names provided are tailor based on the provided package manager,
# choice of compiler, and optionally its bit-depth.
#
# See the usage arguments below for details or run it with the -h or --help.
#
# In general, this script adheres to Google's shell scripting style guide
# (https://google.github.io/styleguide/shell.xml), however some deviations (such as
# tab indents instead of two-spaces) are used to fit DOSBox's in-practice coding style.
#
# Copyright (c) 2019 Kevin R Croft <krcroft@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later
# This script prints package dependencies for a given package manmager,
# compiler (gcc or clang), and bit-depth (32 or 64).
#
# If run without arguments, the script asks for required arguments one
# by one, which includes the package manager (--manager) and --compiler.
#
# Optional arguments include the bit-depth, defaults to 64-bit;
# and the compiler version, which defaults to the package manager's
# default version.
#
# Usage examples:
# $ ./list-build-dependencies.sh # asks for --manager
# $ ./list-build-dependencies.sh --manager apt # asks for --compiler
# $ ./list-build-dependencies.sh --manager apt --compiler gcc -v 9
# $ ./list-build-dependencies.sh --manager zypper --compiler clang
# $ ./list-build-dependencies.sh --manager msy2 --compiler gcc -b 32
#
# This script makes use of the automator package, see automator/main.sh.
#
set -euo pipefail
IFS=$'\n\t'
function usage() {
if [ -n "${1}" ]; then
errcho "${1}"
fi
local script
script=$(basename "${0}")
echo "Usage: ${script} -p apt|xcode|brew|macports|msys2|vcpkg [-b 32|64] [-c gcc|clang] [-o FILE] [-u #]"
echo ""
echo " FLAG Description Default"
echo " ----------------------- --------------------------------------------------- -------"
echo " -b, --bit-depth Choose either a 64 or 32 bit compiler [$(print_var "${BITS}")]"
echo " -c, --compiler Choose either gcc or clang [$(print_var "${COMPILER}")]"
echo " -u, --compiler-version <#> Customize the compiler version (if available) [$(print_var "${COMPILER_VERSION}")]"
echo " -v, --version Print the version of this script [$(print_var "${SCRIPT_VERSION}")]"
echo " -h, --help Print this usage text"
echo " -p, --package-manager Choose one of the following:"
echo " - apt (Linux: Debian, Ubuntu, Raspbian)"
echo " - dnf (Linux: Fedora, RedHat, CentOS)"
echo " - pacman (Linux: Arch, Manjaro)"
echo " - zypper (Linux: SuSE, OpenSUSE)"
echo " - xcode (OS X: Apple-supported)"
echo " - brew (OS X: Homebrew community-supported)"
echo " - macports (OS X: MacPorts community-supported)"
echo " - msys2 (Windows: Cygwin-based, community-support)"
echo " - vcpkg (Windows: Visual Studio builds)"
echo " ----------------------- --------------------------------------------------- -------"
echo ""
echo "Example: ${script} --package-manager apt --compiler clang --compiler-version 8"
echo ""
echo "Note: the last value will take precendent if duplicate flags are provided."
exit 1
}
function defaults() {
BITS="64"
COMPILER="gcc"
COMPILER_VERSION=""
PACKAGE_MANAGER="unset"
readonly SCRIPT_VERSION="0.2"
}
# parse params
function parse_args() {
defaults
# Gather the parameters that define this build
bits="64"
modifiers=("")
# shellcheck disable=SC2034
while [[ "${#}" -gt 0 ]]; do case ${1} in
-b|--bit-depth) BITS="${2}"; shift;shift;;
-c|--compiler) COMPILER="${2}"; shift;shift;;
-p|--package-manager) PACKAGE_MANAGER="${2}"; shift;shift;;
-u|--compiler-version) COMPILER_VERSION="${2}"; shift;shift;;
-v|--version) print_version; shift;;
-h|--help) usage "Show usage"; shift;;
*) usage "Unknown parameter: ${1}"; shift;shift;;
-m|--manager) manager="${2}"; shift 2;;
-c|--compiler) selected_type=$(lower "${2}"); shift 2;;
-b|--bit-depth) bits="${2}"; shift 2;;
-v|--compiler-version) version="${2}"; shift 2;;
-a|--addon) modifiers+=("${2}"); shift 2;;
*) >&2 echo "Unknown parameter: ${1}"; exit 1; shift 2;;
esac; done
# Check mandatory arguments
if [[ "${PACKAGE_MANAGER}" == "unset" ]]; then
usage "A choice of package manager must be provided; use '-p <choice>' or '--package-manager <choice>'"
fi
# Import our settings and report missing values
import manager "${manager:-}"
if [[ -z "${selected_type:-}" ]]; then arg_error "--compiler" "${TYPES[*]}"; fi
# If a version was provided then construct a postfix string given the manager's
# potential unique delimeter (ie: gcc@9 for brew, gcc-9 for apt).
# shellcheck disable=SC2034,SC2154
postfix=$([[ -n "${version:-}" ]] && echo "${delim}${version}" || echo "")
import "${selected_type:-}" "${manager:-}"
}
# shellcheck disable=SC2034
data_dir="packages"
function errcho() {
local CLEAR='\033[0m'
local RED='\033[0;91m'
>&2 echo ""
>&2 echo -e " ${RED}👉 ${*}${CLEAR}" "\\n"
}
function print_var() {
if [[ -z "${1}" ]]; then
echo "unset"
else
echo "${1}"
fi
}
function list_packages() {
VERSION_DELIM=""
case "$1" in
apt)
# Apt separates GCC into the gcc and g++ pacakges, the latter which depends on the prior.
# Therefore, we provide g++ in-place of gcc.
VERSION_DELIM="-"
if [[ "${COMPILER}" == "gcc" ]]; then
COMPILER="g++"
fi
PACKAGES=(libtool build-essential autoconf-archive libsdl1.2-dev libsdl-net1.2-dev libopusfile-dev)
;;
dnf)
VERSION_DELIM="-"
PACKAGES=(libtool autoconf-archive SDL SDL_net-devel opusfile-devel)
;;
pacman)
# Arch offers 32-bit versions of SDL (but not others)
PACKAGES=(libtool autoconf-archive sdl_net opusfile)
if [[ "${BITS}" == 32 ]]; then
PACKAGES+=(lib32-sdl)
else
PACKAGES+=(sdl)
fi
;;
zypper)
# OpenSUSE offers 32-bit versions of SDL and SDL_net (but not others)
PACKAGES=(devel_basis libtool autoconf-archive opusfile)
if [[ "${BITS}" == 32 ]]; then
PACKAGES+=(libSDL-devel-32bit libSDL_net-devel-32bit)
else
PACKAGES+=(SDL SDL_net)
fi
;;
xcode)
# If the user doesn't want to use Homebrew or MacPorts, then they are limited to
# Apple's Clang plus the provided command line development tools provided by Xcode.
COMPILER=""
echo "Execute the following:"
echo " xcode-select --install # to install command line development tools"
echo " sudo xcodebuild -license # to accept Apple's license agreement"
echo ""
echo "Now download, build, and install the following manually to avoid using Homebrew or MacPorts:"
echo " - coreutils autogen autoconf automake pkg-config libpng sdl sdl_net opusfile"
;;
brew)
# If the user wants Clang, we knock it out because it's provided provided out-of-the-box
VERSION_DELIM="@"
if [[ "${COMPILER}" == "clang" ]]; then
COMPILER=""
fi
PACKAGES=(coreutils autogen autoconf autoconf-archive automake pkg-config libpng sdl sdl_net opusfile)
;;
macports)
PACKAGES=(coreutils autogen automake autoconf autoconf-archive pkgconfig libpng libsdl libsdl_net opusfile)
;;
msys2)
# MSYS2 only supports the current latest releases of Clang and GCC, so we disable version customization
COMPILER_VERSION=""
local pkg_type
pkg_type="x86_64"
if [[ "${BITS}" == 32 ]]; then
pkg_type="i686"
fi
PACKAGES=(autogen autoconf autoconf-archive base-devel automake-wrapper)
for pkg in pkg-config libtool libpng zlib SDL SDL_net opusfile; do
PACKAGES+=("mingw-w64-${pkg_type}-${pkg}")
done
COMPILER="mingw-w64-${pkg_type}-${COMPILER}"
;;
vcpkg)
# VCPKG doesn't provide Clang or GCC, so we knock out the compiler and just give packages
COMPILER=""
PACKAGES=(libpng sdl1 sdl1-net opusfile)
;;
*)
usage "Unknown package manager ${1}"
;;
esac
if [[ -n "${COMPILER_VERSION}" && -n "${COMPILER}" ]]; then
COMPILER+="${VERSION_DELIM}${COMPILER_VERSION}"
fi
echo "${COMPILER} ${PACKAGES[*]}"
}
function main() {
parse_args "$@"
list_packages "${PACKAGE_MANAGER}"
}
main "$@"
# shellcheck source=scripts/automator/main.sh
source "$(dirname "$0")/automator/main.sh"