diff --git a/cmake/cpack/appimage.cmake b/cmake/cpack/appimage.cmake index 1627104f..fbdb7265 100644 --- a/cmake/cpack/appimage.cmake +++ b/cmake/cpack/appimage.cmake @@ -9,7 +9,7 @@ elseif(CPACK_MCPI_ARCH STREQUAL "amd64") endif() set(RUNTIME "${CPACK_TOPLEVEL_DIRECTORY}/runtime") file(DOWNLOAD - "https://github.com/AppImage/AppImageKit/releases/download/continuous/runtime-${RUNTIME_ARCH}" + "https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-${RUNTIME_ARCH}" "${RUNTIME}" STATUS DOWNLOAD_STATUS ) @@ -34,10 +34,11 @@ execute_process( COMMAND "${CMAKE_COMMAND}" "-E" "env" "ARCH=${APPIMAGE_ARCH}" + "VERSION=${CPACK_MCPI_VERSION}" "appimagetool" "--updateinformation" "zsync|${CPACK_MCPI_REPO}/releases/download/latest/${CPACK_PACKAGE_FILE_NAME_ZSYNC}${CPACK_MCPI_APPIMAGE_ZSYNC_EXT}" "--runtime-file" "${RUNTIME}" - "--comp" "xz" + "--comp" "zstd" "${CPACK_TEMPORARY_DIRECTORY}" "${CPACK_PACKAGE_FILE_NAME}${CPACK_MCPI_APPIMAGE_EXT}" WORKING_DIRECTORY "${CPACK_PACKAGE_DIRECTORY}" diff --git a/cmake/cpack/packaging.cmake b/cmake/cpack/packaging.cmake index 8fbb5bab..1a6af99d 100644 --- a/cmake/cpack/packaging.cmake +++ b/cmake/cpack/packaging.cmake @@ -21,6 +21,7 @@ if(MCPI_IS_APPIMAGE_BUILD) macro(pass_to_cpack var) set("CPACK_MCPI_${var}" "${MCPI_${var}}") endmacro() + pass_to_cpack(VERSION) pass_to_cpack(ARCH) pass_to_cpack(REPO) pass_to_cpack(APPIMAGE_EXT) diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 92bfb4cf..ef4ff81c 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -27,7 +27,7 @@ The AppImage requires Debian Bullseye or higher. This is equivalent to Ubuntu 20 It also requires some additional packages. To install them, run: ```sh -sudo apt install -y libfuse2 libopenal1 libglib2.0-0 +sudo apt install -y libopenal1 libglib2.0-0 ``` diff --git a/scripts/build.mjs b/scripts/build.mjs index 6f76649f..87a9696f 100755 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -2,113 +2,28 @@ 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); -} +import { info, err, run } from './lib/util.mjs'; +import { parseOptions, Enum, Architectures } from './lib/options.mjs'; // Enums -function Enum(values) { - for (const value of values) { - this[value] = { - prettyName: 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([ +const PackageTypes = new Enum([ 'None', 'AppImage', 'Flatpak' -])); -const Architectures = wrap(new Enum([ - 'AMD64', - 'ARM64', - 'ARMHF', - 'Host' -])); -const Configurations = wrap(new Enum([ - 'Release', - 'Debug' -])); +]); -// 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 -function parseArg(arg, from, type) { - // Check Argument - if (arg === undefined) { - err('Expecting ' + type); - } - // Read Argument - const value = from.get(arg); - if (value === null) { - err(`Invalid ${type}: ${arg}`); - } - // Return - return value; -} -function readArg(...args) { - return parseArg(process.argv[argIndex++], ...args); -} -// Type Of Packaging -const packageType = readArg(PackageTypes, 'Package Type'); -// Build Architecture -const architecture = readArg(Architectures, 'Architecture'); -// Flatpak Builds Work Best Without Custom Toolchains -if (packageType === PackageTypes.Flatpak && architecture !== Architectures.Host) { - err('Flatpak Builds Do Not Support Custom Toolchains'); -} - -// CMake Build Options -const options = new Map(); - -// Other Arguments -let clean = false; -let install = false; -let config = Configurations.Release; -for (; argIndex < process.argv.length; argIndex++) { - const arg = process.argv[argIndex]; - const cmakeArgPrefix = '-D'; - if (arg.startsWith(cmakeArgPrefix)) { +// CMake Options +const cmakeArgPrefix = '-D'; +const cmakeArgSeparator = '='; +const cmakeOptions = new Map(); +function parseCMakeOption(arg) { + if (arg === null) { + // Usage Text + return `${cmakeArgPrefix}var${cmakeArgSeparator}value...`; + } else if (arg.startsWith(cmakeArgPrefix)) { // Pass Build Option To CMake let parsedArg = arg.substring(cmakeArgPrefix.length); - const parts = parsedArg.split('='); + const parts = parsedArg.split(cmakeArgSeparator); if (parts.length !== 2) { err('Unable To Parse Build Option: ' + arg); } @@ -117,50 +32,68 @@ for (; argIndex < process.argv.length; argIndex++) { if (!/^[a-zA-Z_]+$/.test(name) || name.length === 0) { err('Invalid Build Option Name: ' + name); } - options.set(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 if (arg === '--config') { - // Set Configuration - config = parseArg(process.argv[++argIndex], Configurations, 'Configuration'); + cmakeOptions.set(name, value); + return true; } else { - // Invalid - err('Invalid Argument: ' + arg); + // Unknown Option + return false; } } -// Update Folders -function updateDir(dir) { - if (packageType !== PackageTypes.None) { - dir = path.join(dir, packageType.name); - } - return path.join(dir, architecture.name); +// Options +const options = parseOptions([ + ['packageType', PackageTypes], + ['architecture', Architectures] +], [ + 'clean', + 'install', + 'debug' +], parseCMakeOption); + +// Check Options +if (options.packageType === PackageTypes.Flatpak && options.architecture !== Architectures.Host) { + err('Flatpak Builds Do Not Support Custom Toolchains'); } -build = updateDir(build); -let cleanOut = false; -// AppImages Are Placed Directly In ./out -if (packageType !== PackageTypes.AppImage) { - cleanOut = true; - out = updateDir(out); +if (options.packageType === PackageTypes.AppImage && options.install) { + err('AppImages Cannot Be Installed'); } -// Configure Build Options -function toCmakeBool(val) { - return val ? 'ON' : 'OFF'; +// 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'); + +// Update Build Directory +function specializeDir(dir) { + // Use Unique Folder For Build Type + return path.join(dir, options.packageType.name, options.architecture.name); } -options.set('MCPI_IS_APPIMAGE_BUILD', toCmakeBool(packageType === PackageTypes.AppImage)); -options.set('MCPI_IS_FLATPAK_BUILD', toCmakeBool(packageType === PackageTypes.Flatpak)); -if (architecture !== Architectures.Host) { - options.set('CMAKE_TOOLCHAIN_FILE', path.join(root, 'cmake', 'toolchain', architecture.name + '-toolchain.cmake')); +build = specializeDir(build); +// Update Output Directory +const useOutRoot = options.packageType === PackageTypes.AppImage; +if (!useOutRoot) { + out = specializeDir(out); +} +// Print Directories +function printDir(name, dir) { + info(name + ' Directory: ' + dir); +} +printDir('Build', build); +printDir('Output', out); + +// Configure CMake Options +function setupPackageTypeOption(type) { + cmakeOptions.set(`MCPI_IS_${type.name.toUpperCase()}_BUILD`, options.packageType === type ? 'ON' : 'OFF'); +} +setupPackageTypeOption(PackageTypes.AppImage); +setupPackageTypeOption(PackageTypes.Flatpak); +const toolchainOption = 'CMAKE_TOOLCHAIN_FILE'; +if (options.architecture !== Architectures.Host) { + cmakeOptions.set(toolchainOption, path.join(root, 'cmake', 'toolchain', options.architecture.name + '-toolchain.cmake')); } else { - options.delete('CMAKE_TOOLCHAIN_FILE'); + cmakeOptions.delete(toolchainOption); } // Make Build Directory @@ -170,39 +103,31 @@ function createDir(dir, clean) { } fs.mkdirSync(dir, {recursive: true}); } -createDir(build, clean); -if (!install) { - createDir(out, cleanOut); +createDir(build, options.clean); +if (!options.install) { + createDir(out, !useOutRoot); } // 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 configure = ['cmake', '-GNinja Multi-Config']; -options.forEach((value, key, map) => { - configure.push(`-D${key}=${value}`); +cmakeOptions.forEach((value, key) => { + configure.push(cmakeArgPrefix + key + cmakeArgSeparator + value); }); -configure.push(root); +configure.push('-S', root, '-B', build); run(configure); // Build -const configArg = ['--config', config.prettyName]; -run(['cmake', '--build', '.', ...configArg/*, '-v'/*, '--', '-n', '-j1', '-j1'*/]); +const configArg = ['--config', options.debug ? 'Debug' : 'Release']; +run(['cmake', '--build', build, ...configArg]); // Package -if (packageType !== PackageTypes.AppImage) { - if (!install) { +if (options.packageType !== PackageTypes.AppImage) { + if (!options.install) { process.env.DESTDIR = out; } - run(['cmake', '--install', '.', ...configArg]); + run(['cmake', '--install', build, ...configArg]); } else { - run(['cmake', '--build', '.', '--target', 'package', ...configArg]); + run(['cmake', '--build', build, '--target', 'package', ...configArg]); // Copy Generated Files const files = fs.readdirSync(build); for (const file of files) { diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 8c23e87a..9490c5e4 100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -61,19 +61,19 @@ run_build() { "libxext-dev:$1" \ `# QEMU Dependencies` \ "libglib2.0-dev:$1" \ - `# AppStream Verification` \ - appstream + `# AppImage` \ + appstream \ + zsync # Install appimagetool sudo rm -rf /opt/squashfs-root /opt/appimagetool /usr/local/bin/appimagetool case "$(dpkg --print-architecture)" in 'armhf') APPIMAGE_ARCH='armhf';; 'arm64') APPIMAGE_ARCH='aarch64';; - 'i386') APPIMAGE_ARCH='i686';; 'amd64') APPIMAGE_ARCH='x86_64';; esac sudo mkdir -p /opt - sudo wget -O /opt/appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${APPIMAGE_ARCH}.AppImage" + sudo wget -O /opt/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-${APPIMAGE_ARCH}.AppImage" sudo chmod +x /opt/appimagetool # Workaround AppImage Issues With Docker sudo ./scripts/fix-appimage-for-docker.sh /opt/appimagetool @@ -83,7 +83,8 @@ run_build() { sudo rm -f ./appimagetool # Link sudo mv ./squashfs-root ./appimagetool - sudo ln -s /opt/appimagetool/AppRun /usr/local/bin/appimagetool + printf '#!/bin/sh\nexec /opt/appimagetool/AppRun "$@"\n' | sudo tee /usr/local/bin/appimagetool > /dev/null + sudo chmod +x /usr/local/bin/appimagetool } # Test Dependencies diff --git a/scripts/lib/options.mjs b/scripts/lib/options.mjs new file mode 100644 index 00000000..1fcd1180 --- /dev/null +++ b/scripts/lib/options.mjs @@ -0,0 +1,91 @@ +import { fail } from './util.mjs'; + +// Enums +export function Enum(values) { + this.values = []; + for (const value of values) { + const obj = { + prettyName: value, + name: value.toLowerCase() + }; + this[value] = obj; + this.values.push(obj); + } +} +Enum.prototype.get = function (name) { + if (name) { + for (const value of this.values) { + if (value.name === name.toLowerCase()) { + return value; + } + } + } + return null; +}; + +// Supported Architectures +export const Architectures = new Enum([ + 'AMD64', + 'ARM64', + 'ARMHF', + 'Host' +]); + +// Parse +function formatFlag(name) { + return '--' + name; +} +function formatOptionalArg(arg) { + return '[' + arg + '] ';; +} +export function parseOptions(positionalArgs, flags, customHandler) { + // Usage Text + let usage = 'USAGE: '; + for (const arg of positionalArgs) { + const option = arg[1]; + const arr = []; + for (const value of option.values) { + arr.push(value.name); + } + usage += '<' + arr.join('|') + '> '; + } + for (const flag of flags) { + usage += formatOptionalArg(formatFlag(flag)); + } + usage += formatOptionalArg(customHandler(null)); + usage = usage.trim(); + + // Copy Arguments + const args = process.argv.slice(2); // Skip First Two Arguments + + // Read Positional Arguments + const out = {}; + for (const arg of positionalArgs) { + let value = args.shift(); + value = arg[1].get(value); + if (value === null) { + fail(usage); + } + out[arg[0]] = value; + } + + // Read Flags + for (const flag of flags) { + const name = formatFlag(flag); + const isSet = args.includes(name); + if (isSet) { + args.splice(args.indexOf(name), 1); + } + out[flag] = isSet; + } + + // Unknown Arguments + for (const arg of args) { + if (!customHandler(arg)) { + fail(usage); + } + } + + // Return + return out; +} \ No newline at end of file diff --git a/scripts/lib/util.mjs b/scripts/lib/util.mjs new file mode 100644 index 00000000..d2c75196 --- /dev/null +++ b/scripts/lib/util.mjs @@ -0,0 +1,24 @@ +import * as child_process from 'node:child_process'; + +// Logging +const EXIT_FAILURE = 1; +export function fail(message) { + console.error(message); + process.exit(EXIT_FAILURE); +} +export function err(message) { + fail('ERROR: ' + message); +} +export function info(message) { + console.log('INFO: ' + message); +} + +// Sub-Process +export function run(command) { + try { + info('Running: ' + command.join(' ')); + child_process.execFileSync(command[0], command.slice(1), {stdio: 'inherit'}); + } catch (e) { + err(e); + } +} \ No newline at end of file