#!/usr/bin/env bash # # JoeyLib installer: everything needed to build (and, with the emulator # components, run and test) JoeyLib programs, pinned and checksummed. # Host packages come from the Linux distribution (apt or dnf); everything # else is downloaded or built under toolchains/ and nowhere else, so that # folder can be deleted at any time and re-created by running this again. # # Usage: # ./install.sh [options] [targets...] # # Commands: # list every component, its groups and whether it is current # install TARGETS... install (or update) components and groups # deps TARGETS... show the host packages TARGETS need (--install: install them) # doctor check the host and smoke-test every installed component # import PATH... take private files (Kickstart, ...) from files, folders # or .zip archives, matched by checksum # (installer/files.sha1) # bundle FILE.tar.gz pack the private files present here, for the owner # to host (JOEY_BUNDLE_URL in installer/bundle.conf) # help this text # # Targets are component names (see `list`) or groups: # toolchain:PORT emulator:PORT PORT toolchains emulators all # where PORT is iigs, amiga, atarist, dos, x68000 or dvx. `all`, `toolchains` # and `emulators` leave out optional components (staxiData, and dvx, which # copies from a DVX tree you have); name those explicitly. # # Options: # -y, --yes install missing host packages without asking # --install with `deps`: install the missing host packages # --force reinstall even when a component is current # --jobs N parallel build jobs (default: every core) # --keep-build keep the scratch build trees in cache/build # --llvm816-dir PATH use an existing llvm816 checkout for the IIgs # compiler (remembered; "--llvm816-dir clone" undoes it) # --dvx-dir PATH the built DVX tree the dvx component copies from # (remembered; default: dvxgui beside this repository) # # Examples: # ./install.sh install toolchains every cross compiler # ./install.sh install emulator:dos what a tester needs for DOS # ./install.sh install amiga Amiga compiler + emulator # ./install.sh deps all --install just the host packages # # make reads toolchains/config.mk by itself. For interactive shells, # `source toolchains/env.sh` sets the same variables. set -euo pipefail # shellcheck source=installer/lib/common.sh source "$(dirname "${BASH_SOURCE[0]}")/installer/lib/common.sh" # shellcheck source=installer/lib/distro.sh source "$JOEY_INSTALLER/lib/distro.sh" # shellcheck source=installer/lib/config.sh source "$JOEY_INSTALLER/lib/config.sh" # shellcheck source=installer/lib/bundle.sh source "$JOEY_INSTALLER/lib/bundle.sh" JOEY_PORTS="iigs amiga atarist dos x68000 dvx" depsInstall=0 usage() { sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//' } # groupMembers GROUP -- print the components in GROUP. groupMembers() { local group="$1" local id local groups for id in $(componentIds); do groups=" $(compQuery "$id" compGroups) " case "$group" in all) if [[ "$groups" != *" optional "* ]]; then printf '%s\n' "$id" fi ;; toolchains) if [[ "$groups" == *" toolchain:"* && "$groups" != *" optional "* ]]; then printf '%s\n' "$id" fi ;; emulators) if [[ "$groups" == *" emulator:"* && "$groups" != *" optional "* ]]; then printf '%s\n' "$id" fi ;; iigs|amiga|atarist|dos|x68000|dvx) if [[ "$groups" == *" toolchain:$group "* || "$groups" == *" emulator:$group "* ]]; then printf '%s\n' "$id" fi ;; *) if [[ "$groups" == *" $group "* ]]; then printf '%s\n' "$id" fi ;; esac done } # resolveTargets TARGET... -- print component ids, each after the components # it requires. resolveTargets() { local target local id local members declare -A seen=() local ordered=() addWithRequires() { local req [ -z "${seen[$1]:-}" ] || return 0 seen[$1]=1 for req in $(compQuery "$1" compRequires); do addWithRequires "$req" done ordered+=("$1") } [ $# -gt 0 ] || die "no targets given (try: toolchains, emulators, all, or a port name)" for target in "$@"; do if [ -f "$JOEY_COMPONENTS/$target.sh" ]; then addWithRequires "$target" continue fi members="$(groupMembers "$target")" [ -n "$members" ] || die "unknown target: $target (see: ./install.sh list)" for id in $members; do addWithRequires "$id" done done printf '%s\n' "${ordered[@]}" } # hostPackagesFor ID... -- the distribution packages the components need. hostPackagesFor() { local id local logical=() for id in "$@"; do # shellcheck disable=SC2207 logical+=($(compQuery "$id" compHostDeps)) done [ ${#logical[@]} -gt 0 ] || return 0 distroPackageNames $(printf '%s\n' "${logical[@]}" | sort -u) | sort -u } cmdDeps() { local ids local pkgs local missing # Command substitution (not < <(...)) so a failure -- an unknown target, # an unsupported distribution -- stops the script. ids="$(resolveTargets "$@")" pkgs="$(hostPackagesFor $ids)" missing="$(distroMissing $pkgs)" info "host packages for $*: $(printf '%s ' $pkgs)" if [ -z "$missing" ]; then ok "all present" return 0 fi warn "missing: $(printf '%s ' $missing)" if [ "$depsInstall" = "1" ]; then distroInstall $missing else info "install them with: ./install.sh deps $* --install" return 1 fi } cmdInstall() { local ids local pkgs local missing local id local installed=0 local idsText idsText="$(resolveTargets "$@")" mapfile -t ids <<< "$idsText" pkgs="$(hostPackagesFor "${ids[@]}")" missing="$(distroMissing $pkgs)" if [ -n "$missing" ]; then if [ "$JOEY_ASSUME_YES" = "1" ]; then distroInstall $missing else die "missing host packages: $(printf '%s ' $missing) Install them with: ./install.sh deps $* --install or re-run with --yes to install them as part of this command." fi fi # A failed component does not stop the others (a tester without the # private bundle still gets every other emulator); anything that # requires a failed component is skipped. All of it is reported at the # end, and the exit status is non-zero. local failed=() local req local blocked for id in "${ids[@]}"; do blocked="" for req in $(compQuery "$id" compRequires); do if [[ " ${failed[*]:-} " == *" $req "* ]]; then blocked="$req" fi done if [ -n "$blocked" ]; then warn "$id skipped: it needs $blocked, which failed" failed+=("$id") continue fi if [ "$JOEY_FORCE" != "1" ] && compIsCurrent "$id"; then ok "$id is current" continue fi header "$id: $(compQuery "$id" compDescription)" stampClear "$id" if ! compRun "$id" compInstall; then warn "$id: install failed" failed+=("$id") continue fi if ! compRun "$id" compInstalled; then warn "$id: install finished but the result is missing" failed+=("$id") continue fi if ! compRun "$id" compVerify >/dev/null; then warn "$id: installed, but its smoke test failed" failed+=("$id") continue fi stampWrite "$id" "$(compQuery "$id" compPin)" ok "$id installed" installed=$((installed + 1)) done writeConfig if [ ${#failed[@]} -gt 0 ]; then die "$(( ${#ids[@]} - ${#failed[@]} )) of ${#ids[@]} components current; FAILED: ${failed[*]}" fi ok "${#ids[@]} components current ($installed installed now)" info "make picks the tools up by itself; for a shell: source toolchains/env.sh" } cmdImport() { local installedText local id [ $# -gt 0 ] || die "import needs at least one file, folder or .zip archive" installedText="$(importPaths "$@")" if [ -z "$installedText" ]; then warn "nothing matched: none of those files is in installer/files.sha1" return 1 fi printf '%s\n' "$installedText" | sort -u | while read -r dest; do ok "imported $dest" done # Mark the private components whose file is now in place as installed. for id in $(componentIds); do if [ -n "$(compQuery "$id" compPrivateFile)" ] && compRun "$id" compInstalled; then stampWrite "$id" "$(compQuery "$id" compPin)" fi done writeConfig } cmdList() { local id local status local stamp printf '%-13s %-9s %-46s %s\n' "COMPONENT" "STATUS" "GROUPS" "DESCRIPTION" for id in $(componentIds); do stamp="$(stampRead "$id")" if [ -z "$stamp" ] || ! compRun "$id" compInstalled >/dev/null 2>&1; then status="missing" elif [ "$stamp" = "$(compQuery "$id" compPin)" ]; then status="current" else status="outdated" fi printf '%-13s %-9s %-46s %s\n' "$id" "$status" "$(compQuery "$id" compGroups)" "$(compQuery "$id" compDescription)" done printf '\ngroups: toolchains emulators all %s, and toolchain:PORT / emulator:PORT\n' "$JOEY_PORTS" } # portGroups ID -- ID's toolchain:PORT and emulator:PORT groups. portGroups() { local group for group in $(compQuery "$1" compGroups); do case "$group" in toolchain:*|emulator:*) printf '%s\n' "$group" ;; esac done } # wantedGroups -- the port groups the user chose: those holding an installed # component that belongs to that group alone. Shared members (headless, # libxmp, mame, cadius) do not count, or installing one port would make # every port that shares them look half-installed. wantedGroups() { local id local groups for id in $(componentIds); do [ -n "$(stampRead "$id")" ] || continue groups="$(portGroups "$id")" [ -n "$groups" ] && [ "$groups" = "${groups%%$'\n'*}" ] && printf '%s\n' "$groups" done return 0 } # firstGroupIn ID " GROUP... " -- print ID's first port group in the list. firstGroupIn() { local group for group in $(portGroups "$1"); do if [[ "$2" == *" $group "* ]]; then printf '%s\n' "$group" return 0 fi done } cmdDoctor() { local id local failures=0 local family local wanted local group header "host" family="$(distroFamily)" if [ "$family" = "unknown" ]; then warn "unrecognised distribution: host packages must be installed by hand" failures=$((failures + 1)) else ok "package manager: $family" fi if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then warn "bash ${BASH_VERSION} is older than 4" failures=$((failures + 1)) fi header "components" wanted=" $(printf '%s ' $(wantedGroups))" for id in $(componentIds); do if [ -z "$(stampRead "$id")" ]; then group="$(firstGroupIn "$id" "$wanted")" if [ -n "$group" ]; then warn "$id: not installed, but the rest of $group is (run: ./install.sh install $id)" failures=$((failures + 1)) fi elif ! compRun "$id" compInstalled >/dev/null 2>&1; then if [ -n "$(compQuery "$id" compPrivateFile)" ]; then warn "$id: $(compQuery "$id" compPrivateFile) is gone; supply it with ./install.sh import PATH (or JOEY_BUNDLE_URL)" else warn "$id: its files are gone (run: ./install.sh install $id)" fi failures=$((failures + 1)) elif ! compIsCurrent "$id"; then warn "$id: out of date, its pin changed (run: ./install.sh install $id)" failures=$((failures + 1)) elif compRun "$id" compVerify >/dev/null; then ok "$id" else warn "$id: smoke test failed" failures=$((failures + 1)) fi done writeConfig if [ "$failures" -gt 0 ]; then die "$failures problem(s) found" fi ok "everything installed checks out" } # ---- argument parsing ----------------------------------------------------- command="" targets=() while [ $# -gt 0 ]; do case "$1" in -y|--yes) JOEY_ASSUME_YES=1 ;; --install) depsInstall=1 ;; --force) JOEY_FORCE=1 ;; --keep-build) JOEY_KEEP_BUILD=1 ;; --jobs) [ $# -ge 2 ] || die "--jobs needs a number" JOEY_JOBS="$2" shift ;; --llvm816-dir) [ $# -ge 2 ] || die "--llvm816-dir needs a path (or 'clone')" mkdir -p "$JOEY_STATE" if [ "$2" = "clone" ]; then rm -f "$JOEY_STATE/llvm816Dir" else [ -f "$2/setup.sh" ] || die "$2 does not look like an llvm816 checkout (no setup.sh)" (cd "$2" && pwd) > "$JOEY_STATE/llvm816Dir" fi shift ;; --dvx-dir) [ $# -ge 2 ] || die "--dvx-dir needs a path" [ -f "$2/bin/dvx.exe" ] || die "$2 does not look like a built DVX tree (no bin/dvx.exe)" mkdir -p "$JOEY_STATE" (cd "$2" && pwd) > "$JOEY_STATE/dvxDir" shift ;; -h|--help|help) usage exit 0 ;; -*) die "unknown option: $1 (see: ./install.sh help)" ;; *) if [ -z "$command" ]; then command="$1" else targets+=("$1") fi ;; esac shift done case "$command" in install) cmdInstall "${targets[@]}" ;; deps) cmdDeps "${targets[@]}" ;; import) cmdImport "${targets[@]}" ;; bundle) [ ${#targets[@]} -eq 1 ] || die "bundle needs one output file name"; bundleCreate "${targets[0]}" ;; list) cmdList ;; doctor) cmdDoctor ;; "") usage; exit 2 ;; *) die "unknown command: $command (see: ./install.sh help)" ;; esac