#!/bin/bash set -euo pipefail # SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" ARCHS=( "x86_64" "aarch64" ) HOSTARCH=$(uname -m) SOURCE_DIR="" BUILD_DIR="" OUTPUT_DIR="" EXECUTABLE_NAME="" # set flags from command line RELEASE=0 MULTIBUILD=0 function help() { echo "Usage: $0 [options]" echo "Options:" echo " version Show version information and exit" echo " " echo " Build the project in " echo " -r, --release Build release version" echo " -m, --multi Build for multiple architectures" } function version() { echo "magicbuild 0.1" } # ---------------------------------------------------------------------------------------------------------- # CHECK PACKAGES # ---------------------------------------------------------------------------------------------------------- function check_packages() { # Define packages based on distribution case $OS in "Ubuntu"|"Debian GNU/Linux") # Common packages for both Ubuntu and Debian PACKAGES="cmake make g++ build-essential upx musl-tools wget tar ccache ninja-build mold" ;; *) print_error "Unsupported distribution: $OS" exit 1 ;; esac # Function to check if a package is installed is_package_installed() { dpkg -l "$1" 2>/dev/null | grep -q "^ii" } # Install missing packages print_status "Checking required packages..." for pkg in $PACKAGES; do if ! is_package_installed "$pkg"; then echo "Missing package: $pkg" exit 1 fi echo "Package $pkg is installed" done } # ---------------------------------------------------------------------------------------------------------- # MUSL CROSS COMPILERS # ---------------------------------------------------------------------------------------------------------- function installmusl() { echo "Installing musl toolchain" # Set install directory if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then USER_HOME=$(eval echo "~${SUDO_USER}") else USER_HOME="$HOME" fi INSTALL_DIR="$USER_HOME/.musl-cross" mkdir -p "$INSTALL_DIR" TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT function install_musl_cross() { local TOOLCHAIN="$1" local MUSL_CC_URL="https://musl.cc" if [ ! -d "$INSTALL_DIR/$TOOLCHAIN" ]; then echo "Downloading $TOOLCHAIN musl cross toolchain..." wget -nc -O "$TMPDIR/$TOOLCHAIN.tgz" "$MUSL_CC_URL/$TOOLCHAIN.tgz" tar -C "$INSTALL_DIR" -xvf "$TMPDIR/$TOOLCHAIN.tgz" fi } function check_path() { if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then local BASHRC="$USER_HOME/.bashrc" local TOOLCHAIN="$1" local MUSL_PATH="$INSTALL_DIR/$TOOLCHAIN/bin" if ! echo "$PATH" | grep -q "$MUSL_PATH"; then echo "Adding $MUSL_PATH to PATH in $BASHRC" PATH_LINE="export PATH=\"$MUSL_PATH:\$PATH\"" if ! grep -Fxq "$PATH_LINE" "$BASHRC"; then echo "" >> "$BASHRC" echo "# Add musl cross compilers to PATH for dropshell" >> "$BASHRC" echo "$PATH_LINE" >> "$BASHRC" echo "Added musl cross compilers to $BASHRC" echo "You should run 'source ~/.bashrc' to update your PATH" else echo "You should run 'source ~/.bashrc' to update your PATH" fi fi fi } TOOLCHAIN_LIST=( "aarch64-linux-musl-cross" "x86_64-linux-musl-cross" "x86_64-linux-musl-native" ) for TOOLCHAIN in "${TOOLCHAIN_LIST[@]}"; do install_musl_cross "$TOOLCHAIN" check_path "$TOOLCHAIN" done # Clean up rm -rf "$TMPDIR" } # ---------------------------------------------------------------------------------------------------------- # BUILD # ---------------------------------------------------------------------------------------------------------- CMAKE_FILE="CMakeLists.txt" function get_executable_name() { EXECUTABLE_NAME="" local var_value="" echo "Getting executable name from ${SOURCE_DIR}/${CMAKE_FILE}" while IFS= read -r line; do # Look for set(PROJECT_EXE_NAME ipdemo) if [[ "$line" =~ set\(PROJECT_EXE_NAME[[:space:]]+([a-zA-Z0-9_-]+)\) ]]; then var_value=$(echo "$line" | sed -E 's/.*set\(PROJECT_EXE_NAME[[:space:]]+([a-zA-Z0-9_-]+)\).*/\1/') fi # Look for add_executable(${PROJECT_EXE_NAME} if [[ "$line" =~ add_executable\([[:space:]]*\$\{PROJECT_EXE_NAME\}[[:space:]] ]]; then echo "Found executable name: $var_value" EXECUTABLE_NAME="$var_value" fi done < "${SOURCE_DIR}/${CMAKE_FILE}" if [[ -z "$EXECUTABLE_NAME" ]]; then echo "Executable name not found." exit 1 else echo "Executable name: $EXECUTABLE_NAME" fi } function build_arch() { local ARCH="$1" local PREVDIR="$PWD" local JOBS; JOBS=$(nproc) # Set JOBS to the number of available CPU cores cd "${SOURCE_DIR}" || exit 1 local ARCH_BUILD_DIR=${BUILD_DIR}/${ARCH} mkdir -p "${ARCH_BUILD_DIR}" echo "Building for ${ARCH} in ${ARCH_BUILD_DIR}, from ${SOURCE_DIR}" if [ ! -f "${HOME}/.musl-cross/${ARCH}-linux-musl-cross/bin/${ARCH}-linux-musl-g++" ]; then installmusl fi CMAKE_BUILD_TYPE="Release" if [ "$RELEASE" -eq 0 ]; then CMAKE_BUILD_TYPE="Debug" fi CC="${HOME}/.musl-cross/${ARCH}-linux-musl-cross/bin/${ARCH}-linux-musl-gcc" CXX="${HOME}/.musl-cross/${ARCH}-linux-musl-cross/bin/${ARCH}-linux-musl-g++" cmake -B "${ARCH_BUILD_DIR}" -G Ninja \ -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_LINKER=mold \ -DCMAKE_C_COMPILER="${CC}" \ -DCMAKE_CXX_COMPILER="${CXX}" cd "${ARCH_BUILD_DIR}" || exit 1 ninja -j"${JOBS}" if [ "$RELEASE" -eq 1 ]; then upx "${ARCH_BUILD_DIR}/${EXECUTABLE_NAME}" fi if [ ! -d "${OUTPUT_DIR}" ]; then mkdir -p "${OUTPUT_DIR}" fi cp "${ARCH_BUILD_DIR}/${EXECUTABLE_NAME}" "${OUTPUT_DIR}/${EXECUTABLE_NAME}.${ARCH}" cd "${PREVDIR}" || exit 1 } function build() { if [ -z "$1" ]; then help echo " " echo "Error: DIR is required" exit 1 fi echo "Building project in directory $1" if [ ! -d "$1" ]; then echo "Directory $1 does not exist" exit 1 fi # remove trailing slash from SOURCE_DIR SOURCE_DIR="$1" SOURCE_DIR=$(echo "$SOURCE_DIR" | sed 's:/*$::') # make SOURCE_DIR absolute SOURCE_DIR=$(realpath "$SOURCE_DIR") BUILD_DIR="${SOURCE_DIR}/build" OUTPUT_DIR="${SOURCE_DIR}/output" if [ ! -f "${SOURCE_DIR}/${CMAKE_FILE}" ]; then echo "${CMAKE_FILE} not found in ${SOURCE_DIR}" exit 1 fi get_executable_name # if have the -m option, then build for multiple architectures if [ $MULTIBUILD -eq 1 ]; then for arch in "${ARCHS[@]}"; do build_arch "$arch" done else build_arch "$HOSTARCH" fi } # ---------------------------------------------------------------------------------------------------------- # MAIN # ---------------------------------------------------------------------------------------------------------- function main() { while getopts "rm" opt; do case $opt in r) RELEASE=1 ;; m) MULTIBUILD=1 ;; *) help exit 1 ;; esac done # Shift the processed options out of the argument list shift $((OPTIND-1)) # get command argument, now that the flags are set CMD="$1" if [ -z "$CMD" ]; then help exit 1 fi case "$CMD" in version) version ;; *) build "$1" ;; esac } main "$@"