From 41fcc942fa7822669af311578f481ab9a627bec1 Mon Sep 17 00:00:00 2001 From: TheBrokenRail Date: Sat, 3 Feb 2024 21:07:53 -0500 Subject: [PATCH] JS-Based Build Script --- .gitea/workflows/build.yml | 4 +- CMakeLists.txt | 4 +- cmake/options/extra-options.cmake | 13 ++ cmake/toolchain/base-toolchain.cmake | 11 +- dependencies/CMakeLists.txt | 4 +- docs/BUILDING.md | 11 +- launcher/src/bootstrap.c | 10 +- libreborn/include/libreborn/config.h.in | 1 + mods/src/chat/ui.cpp | 2 +- mods/src/game-mode/ui.cpp | 4 +- scripts/build.mjs | 196 ++++++++++++++++++++++++ scripts/build.sh | 28 ---- scripts/package.sh | 20 --- scripts/setup.sh | 38 ----- scripts/test.sh | 7 +- 15 files changed, 231 insertions(+), 122 deletions(-) create mode 100755 scripts/build.mjs delete mode 100755 scripts/build.sh delete mode 100755 scripts/package.sh delete mode 100755 scripts/setup.sh diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 5af1cab32f..731a93da13 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -34,12 +34,12 @@ jobs: run: ./scripts/install-dependencies.sh ${{ matrix.arch }} # Build - name: Build - run: ./scripts/package.sh ${{ matrix.mode }} ${{ matrix.arch }} + run: ./scripts/build.mjs appimage ${{ matrix.mode }} ${{ matrix.arch }} - name: Upload Artifacts uses: actions/upload-artifact@v3 with: name: ${{ matrix.mode }}-${{ matrix.arch }} - path: ./out/*.AppImage* + path: ./out/**/*.AppImage* # Test Project test: strategy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 350e8ecf81..8df5635a28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ endif() include(cmake/options/core-options.cmake) # Build Mode -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release") +if(NOT DEFINED CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) endif() # Start Project diff --git a/cmake/options/extra-options.cmake b/cmake/options/extra-options.cmake index 17b2c6231b..896aec809e 100644 --- a/cmake/options/extra-options.cmake +++ b/cmake/options/extra-options.cmake @@ -2,6 +2,9 @@ mcpi_option(OPEN_SOURCE_ONLY "Only Install Open-Source Code (Will Result In Broken Install)" BOOL FALSE) mcpi_option(IS_APPIMAGE_BUILD "AppImage Build" BOOL FALSE) mcpi_option(IS_FLATPAK_BUILD "Flatpak Build" BOOL FALSE) +if(MCPI_IS_APPIMAGE_BUILD AND MCPI_IS_FLATPAK_BUILD) + message(FATAL_ERROR "Invalid Build Configuration") +endif() # Server/Headless Builds mcpi_option(SERVER_MODE "Server Mode" BOOL FALSE) @@ -61,3 +64,13 @@ mcpi_option(APP_TITLE "App Title" STRING "${DEFAULT_APP_TITLE}") # Skin Server mcpi_option(SKIN_SERVER "Skin Server" STRING "https://raw.githubusercontent.com/MCPI-Revival/Skins/data") + +# QEMU +if(BUILD_NATIVE_COMPONENTS) + include(CheckSymbolExists) + check_symbol_exists("__ARM_ARCH" "" MCPI_IS_ARM32_OR_ARM64_TARGETING) + set(MCPI_USE_QEMU TRUE) + if(MCPI_IS_ARM32_OR_ARM64_TARGETING) + set(MCPI_USE_QEMU FALSE) + endif() +endif() diff --git a/cmake/toolchain/base-toolchain.cmake b/cmake/toolchain/base-toolchain.cmake index dd8eba16b4..5060ca292a 100644 --- a/cmake/toolchain/base-toolchain.cmake +++ b/cmake/toolchain/base-toolchain.cmake @@ -9,6 +9,7 @@ macro(setup_toolchain target) add_target_variant(unknown) add_target_variant(none) add_target_variant(pc) + # Find Compiler macro(find_compiler output name) set(possible_names "") @@ -26,13 +27,11 @@ macro(setup_toolchain target) endmacro() find_compiler(CMAKE_C_COMPILER "gcc") find_compiler(CMAKE_CXX_COMPILER "g++") + # Extra set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # Custom Search Paths - if(NOT DEFINED ENV{MCPI_TOOLCHAIN_USE_DEFAULT_SEARCH_PATHS}) - # Find Root - set(CMAKE_FIND_ROOT_PATH "/usr/${target}" "/usr/lib/${target}" "/usr") - # pkg-config - set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/${target}/pkgconfig:/usr/${target}/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig") - endif() + set(CMAKE_FIND_ROOT_PATH "/usr/${target}" "/usr/lib/${target}" "/usr") + # pkg-config + set(ENV{PKG_CONFIG_LIBDIR} "/usr/lib/${target}/pkgconfig:/usr/${target}/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig") endmacro() diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index f4cc313fde..c613304121 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -13,11 +13,11 @@ if(BUILD_NATIVE_COMPONENTS AND NOT MCPI_SERVER_MODE) add_subdirectory(zenity) endif() # LIEF -if(BUILD_NATIVE_COMPONENTS OR (BUILD_ARM_COMPONENTS AND NOT MCPI_SERVER_MODE AND NOT MCPI_USE_MEDIA_LAYER_PROXY)) +if(BUILD_NATIVE_COMPONENTS OR (BUILD_MEDIA_LAYER_CORE AND NOT MCPI_HEADLESS_MODE)) add_subdirectory(LIEF) endif() # QEMU -if(BUILD_NATIVE_COMPONENTS AND NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "arm*" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")) +if(BUILD_NATIVE_COMPONENTS AND MCPI_USE_QEMU) add_subdirectory(qemu) endif() # GLFW diff --git a/docs/BUILDING.md b/docs/BUILDING.md index b1cf79b8e8..61654b4d0b 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -9,14 +9,5 @@ ## Instructions ```sh -./scripts/build.sh +./scripts/build.mjs [--clean] [--install] [-Dvar=value...] ``` - -### Custom CMake Arguments -```sh -./scripts/setup.sh -./scripts/build.sh -``` - -### Environment Variables -* `MCPI_TOOLCHAIN_USE_DEFAULT_SEARCH_PATHS`: Use Default CMake Search Paths Rather Than Guessing diff --git a/launcher/src/bootstrap.c b/launcher/src/bootstrap.c index e5e9ee418d..000289e33f 100644 --- a/launcher/src/bootstrap.c +++ b/launcher/src/bootstrap.c @@ -10,10 +10,6 @@ #define MCPI_BINARY "minecraft-pi" #define QEMU_BINARY "qemu-arm" -#ifndef __ARM_ARCH -#define USE_QEMU -#endif - #define REQUIRED_PAGE_SIZE 4096 #define _STR(x) #x #define STR(x) _STR(x) @@ -163,7 +159,7 @@ void pre_bootstrap(int argc, char *argv[]) { sigaction(SIGTERM, &act_sigterm, NULL); // Check Page Size (Not Needed When Using QEMU) -#ifndef USE_QEMU +#ifndef MCPI_USE_QEMU long page_size = sysconf(_SC_PAGESIZE); if (page_size != REQUIRED_PAGE_SIZE) { ERR("Invalid page size! A page size of %ld bytes is required, but the system size is %ld bytes.", (long) REQUIRED_PAGE_SIZE, page_size); @@ -338,7 +334,7 @@ void bootstrap(int argc, char *argv[]) { new_args[argv_start] = new_mcpi_exe_path; // Non-ARM Systems Need QEMU -#ifdef USE_QEMU +#ifdef MCPI_USE_QEMU argv_start--; new_args[argv_start] = QEMU_BINARY; // Use 4k Page Size @@ -349,7 +345,7 @@ void bootstrap(int argc, char *argv[]) { setup_exec_environment(1); // Pass LD_* Variables Through QEMU -#ifdef USE_QEMU +#ifdef MCPI_USE_QEMU char *qemu_set_env = NULL; #define pass_variable_through_qemu(name) string_append(&qemu_set_env, "%s%s=%s", qemu_set_env == NULL ? "" : ",", name, getenv(name)); for_each_special_environmental_variable(pass_variable_through_qemu); diff --git a/libreborn/include/libreborn/config.h.in b/libreborn/include/libreborn/config.h.in index a31eb1734c..3d0c5bc4b4 100644 --- a/libreborn/include/libreborn/config.h.in +++ b/libreborn/include/libreborn/config.h.in @@ -12,3 +12,4 @@ #cmakedefine MCPI_VARIANT_NAME "@MCPI_VARIANT_NAME@" #cmakedefine MCPI_SDK_DIR "@MCPI_SDK_DIR@" #cmakedefine MCPI_SKIN_SERVER "@MCPI_SKIN_SERVER@" +#cmakedefine MCPI_USE_QEMU diff --git a/mods/src/chat/ui.cpp b/mods/src/chat/ui.cpp index cb92195e40..4aa18c10bb 100644 --- a/mods/src/chat/ui.cpp +++ b/mods/src/chat/ui.cpp @@ -28,7 +28,7 @@ CUSTOM_VTABLE(chat_screen, Screen) { self->super.m_textInputs->push_back(self->chat); self->chat->init(super->font); self->chat->setFocused(true); - // Determien Max Length + // Determine Max Length std::string prefix = _chat_get_prefix(Strings_default_username); int max_length = MAX_CHAT_MESSAGE_LENGTH - prefix.length(); self->chat->setMaxLength(max_length); diff --git a/mods/src/game-mode/ui.cpp b/mods/src/game-mode/ui.cpp index 14b27d5399..9dd5932bcc 100644 --- a/mods/src/game-mode/ui.cpp +++ b/mods/src/game-mode/ui.cpp @@ -1,8 +1,9 @@ // Config Needs To Load First #include +#include "game-mode-internal.h" // Game Mode UI Code Is Useless In Headless Mode -#ifndef MCPI_SERVER_MODE +#ifndef MCPI_HEADLESS_MODE #include #include @@ -11,7 +12,6 @@ #include #include -#include "game-mode-internal.h" // Strings #define GAME_MODE_STR(mode) ("Game Mode: " mode) diff --git a/scripts/build.mjs b/scripts/build.mjs new file mode 100755 index 0000000000..d1bc92c626 --- /dev/null +++ b/scripts/build.mjs @@ -0,0 +1,196 @@ +#!/usr/bin/env node +import * as path from 'node:path'; +import * as url from 'node:url'; +import * as fs from 'node:fs'; +import * as child_process from 'node:child_process'; + +// Logging +const EXIT_FAILURE = 1; +function fail(message) { + console.error(message); + process.exit(EXIT_FAILURE); +} +function err(message) { + fail('ERROR: ' + message); +} +function info(message) { + console.log('INFO: ' + message); +} + +// Enums +function Enum(values) { + for (const value of values) { + this[value] = {name: value.toLowerCase()}; + } +} +Enum.prototype.get = function (name) { + for (const value in this) { + if (value.toLowerCase() === name.toLowerCase()) { + return this[value]; + } + } + return null; +}; +function wrap(obj) { + return new Proxy(obj, { + get(target, property) { + if (property in target) { + return target[property]; + } else { + err('Undefined Value: ' + property); + } + } + }); +} +const PackageTypes = wrap(new Enum([ + 'None', + 'AppImage', + 'Flatpak' +])); +const Variants = wrap(new Enum([ + 'Client', + 'Server' +])); +const Architectures = wrap(new Enum([ + 'AMD64', + 'ARM64', + 'ARMHF', + 'Host' +])); + +// Folders +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const root = path.join(__dirname, '..'); +let build = path.join(root, 'build'); +let out = path.join(root, 'out'); + +// Positional Arguments +let argIndex = 2; // Skip First Two Arguments +const POSITIONAL_ARGUMENT_COUNT = 3; +function readArg(from, type) { + // Check Argument Count + if (argIndex >= process.argv.length) { + err('Expecting ' + type); + } + // Read Argument + const arg = process.argv[argIndex++]; + const value = from.get(arg); + if (value === null) { + err(`Invalid ${type}: ${arg}`); + } + // Return + return value; +} +// Type Of Packaging +const packageType = readArg(PackageTypes, 'Package Type'); +// Build Variant +const variant = readArg(Variants, 'Variant'); +// Build Architecture +const architecture = readArg(Architectures, 'Architecture'); +// Flatpak Builds Work Best Without Custom Toolchains +if (packageType === PackageTypes.Flatpak && architecture !== Architecture.Host) { + err('Flatpak Builds Do Not Support Custom Toolchains'); +} + +// CMake Build Options +const options = {}; + +// Other Arguments +let clean = false; +let install = false; +for (; argIndex < process.argv.length; argIndex++) { + const arg = process.argv[argIndex]; + if (arg.startsWith('-D')) { + // Pass Build Option To CMake + let parsedArg = arg.substring(2); + const split = parsedArg.indexOf('='); + if (split === -1) { + err('Unable To Parse Build Option: ' + arg); + } + const name = parsedArg.substring(0, split); + const value = parsedArg.substring(split + 1); + if (!/^[a-zA-Z_]+$/.test(name) || name.length === 0) { + err('Invalid Build Option Name: ' + name); + } + options[name] = value; + } else if (arg === '--clean') { + // Remove Existing Build Directory + clean = true; + } else if (arg === '--install') { + // Install To System Instead Of Output Directory + if (packageType === PackageTypes.AppImage) { + err('AppImages Cannot Be Installed'); + } + install = true; + } else { + err('Invalid Argument: ' + arg); + } +} + +// Update Folders +function updateDir(dir) { + if (packageType !== PackageTypes.None) { + dir = path.join(dir, packageType.name); + } + return path.join(dir, variant.name, architecture.name); +} +build = updateDir(build); +out = updateDir(out); + +// Configure Build Options +function toCmakeBool(val) { + return val ? 'ON' : 'OFF'; +} +options['MCPI_SERVER_MODE'] = toCmakeBool(variant === Variants.Server); +options['MCPI_IS_APPIMAGE_BUILD'] = toCmakeBool(packageType === PackageTypes.AppImage); +options['MCPI_IS_FLATPAK_BUILD'] = toCmakeBool(packageType === PackageTypes.Flatpak); +if (architecture !== Architectures.Host) { + options['CMAKE_TOOLCHAIN_FILE'] = path.join(root, 'cmake', 'toolchain', architecture.name + '-toolchain.cmake'); +} else { + delete options['CMAKE_TOOLCHAIN_FILE']; +} +if (packageType === PackageTypes.AppImage) { + options['CPACK_PACKAGE_DIRECTORY'] = out; +} + +// Make Build Directory +function createDir(dir, clean) { + if (clean) { + fs.rmSync(dir, {recursive: true, force: true}); + } + fs.mkdirSync(dir, {recursive: true}); +} +createDir(build, clean); +if (!install) { + createDir(out, true); +} + +// Run CMake +function run(command) { + try { + info('Running: ' + command.join(' ')); + child_process.execFileSync(command[0], command.slice(1), {cwd: build, stdio: 'inherit'}); + } catch (e) { + err(e); + } +} +const cmake = ['cmake', '-GNinja']; +for (const option in options) { + cmake.push(`-D${option}=${options[option]}`); +} +cmake.push(root); +run(cmake); + +// Build +run(['cmake', '--build', '.']); + +// Package +if (packageType !== PackageTypes.AppImage) { + if (!install) { + process.env.DESTDIR = out; + } + run(['cmake', '--install', '.']); +} else { + run(['cmake', '--build', '.', '--target', 'package']); +} diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index ea3e8a2437..0000000000 --- a/scripts/build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh - -set -e - -# Variables -MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" - -# Run CMake If Needed -if [ ! -f "build/${MODE}-${ARCH}/build.ninja" ]; then - ./scripts/setup.sh "${MODE}" "${ARCH}" -fi -# Use Build Dir -cd "build/${MODE}-${ARCH}" - -# Create Prefix -if [ -z "${DESTDIR+x}" ]; then - export DESTDIR="$(cd ../../; pwd)/out/${MODE}-${ARCH}" - rm -rf "${DESTDIR}" - mkdir -p "${DESTDIR}" -fi - -# Build -cmake --build . -cmake --install . - -# Exit -cd ../../ diff --git a/scripts/package.sh b/scripts/package.sh deleted file mode 100755 index e00c99a7fe..0000000000 --- a/scripts/package.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -set -e - -# Prepare -NAME='minecraft-pi-reborn' -MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" - -# Build -./scripts/setup.sh "${MODE}" "${ARCH}" -DMCPI_IS_APPIMAGE_BUILD=ON -./scripts/build.sh "${MODE}" "${ARCH}" - -# Package -cd "build/${MODE}-${ARCH}" -rm -f *.AppImage* -cmake --build . --target package - -# Copy Output -cp *.AppImage* ../../out diff --git a/scripts/setup.sh b/scripts/setup.sh deleted file mode 100755 index 3bb312e406..0000000000 --- a/scripts/setup.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -set -e - -# Variables -MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(echo "$2" | tr '[:upper:]' '[:lower:]')" -shift 2 - -# Verify Mode -if [ "${MODE}" != "client" ] && [ "${MODE}" != "server" ]; then - echo "Invalid Mode: ${MODE}" > /dev/stderr - exit 1 -fi - -# Find Toolchain -toolchain_file="$(pwd)/cmake/toolchain/${ARCH}-toolchain.cmake" -if [ ! -f "${toolchain_file}" ]; then - echo "Invalid Architecture: ${ARCH}" > /dev/stderr - exit 1 -fi - -# Create Build Dir -rm -rf "build/${MODE}-${ARCH}" -mkdir -p "build/${MODE}-${ARCH}" -cd "build/${MODE}-${ARCH}" - -# Server Build -server_mode='OFF' -if [ "${MODE}" = "server" ]; then - server_mode='ON' -fi - -# Build Components -cmake -GNinja -DCMAKE_TOOLCHAIN_FILE="${toolchain_file}" -DMCPI_SERVER_MODE="${server_mode}" "$@" ../../ - -# Exit -cd ../../ diff --git a/scripts/test.sh b/scripts/test.sh index 009388f5e8..fc113843bf 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -4,14 +4,13 @@ set -e # Variables MODE="$(echo "$1" | tr '[:upper:]' '[:lower:]')" -ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH)" +ARCH='host' # Build -./scripts/setup.sh "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON -./scripts/build.sh "${MODE}" "${ARCH}" +./scripts/build.mjs none "${MODE}" "${ARCH}" -DMCPI_HEADLESS_MODE=ON # Add To PATH -export PATH="$(pwd)/out/${MODE}-${ARCH}/usr/bin:${PATH}" +export PATH="$(pwd)/out/${MODE}/${ARCH}/usr/bin:${PATH}" # Make Test Directory rm -rf build/test