634 lines
32 KiB
Bash
634 lines
32 KiB
Bash
#!/bin/sh
|
||
# Makepad for macOS and Linux: gets Rust, compiles Makepad's loader (makepad run) from its source on
|
||
# this computer and starts it. Read it before you run it; it is this one file.
|
||
#
|
||
# curl -fsSL https://makepad.nl/runner/unix | sh
|
||
#
|
||
# 1. Rust 1.98.0 (rustc, cargo, rust-std) from static.rust-lang.org, fetched as 25 MB pieces
|
||
# over parallel connections, checked against the sizes and SHA-256 sums below.
|
||
# 2. At the same time, the loader's source: the release service's licence-free loader release,
|
||
# a ZIP whose size and SHA-256 the service names (checked before unpacking); and, with no
|
||
# arguments, the packs of the runner's git repositories (the service's latest `runner`
|
||
# release), which makepad run takes instead of downloading them itself.
|
||
# 3. The loader is compiled for this computer's processor (`cargo build --profile host --offline`:
|
||
# the ZIP holds every crate) the moment Rust and the source are in place, and started with no
|
||
# arguments: it runs the runner, which downloads the rest. With arguments, the loader runs with
|
||
# them instead. A new loader release replaces only the files that changed, so only their crates
|
||
# compile again.
|
||
#
|
||
# In its folder it creates rust/ (the toolchain), cache/ (downloads, the loader, its caches and
|
||
# logs), apps/ (what `makepad` and `makepad app <id>` run: the runner's and each app's launch
|
||
# record), runner/ (the runner's own state: licence, catalog, launch logs) and target/ (what makepad
|
||
# run builds for the apps: build scripts, unit graphs, code cache). The folder is this
|
||
# file's own when it holds nothing else (or is an installation already), else ~/makepad-runner
|
||
# (piped into sh, or saved among other downloads); $MAKEPAD_LOADER_ROOT overrides. Run it again at any time: nothing is
|
||
# downloaded or compiled twice. The system needs a C linker (Apple's command line tools; on
|
||
# Linux cc) and, on Linux, OpenSSL 3's development files; it names the packages when missing.
|
||
set -u
|
||
umask 022
|
||
|
||
rust_version=1.98.0
|
||
rust_date=2026-08-20
|
||
dist=https://static.rust-lang.org/dist/$rust_date
|
||
piece=25000000
|
||
service=${MAKEPAD_LOADER_SERVICE:-https://makepad.nl/api/loader}
|
||
service=${service%/}
|
||
|
||
# --- The screen ---------------------------------------------------------------------------
|
||
# The Builder's look: header, one row per step, a rule, a status line and a footer, in plain
|
||
# terminal colours (bold title, dim secondary text, orange for work in progress, green checks).
|
||
# Rows are rewritten in place on a terminal; anywhere else each change is one plain line.
|
||
if [ -t 1 ] && [ "${TERM:-dumb}" != dumb ]; then
|
||
tty=1
|
||
E=$(printf '\033')
|
||
B="$E[0;1m" D="$E[0;2m" C="$E[0;38;2;255;92;57m" G="$E[0;32m" Y="$E[0;33m" R="$E[0;31m" N="$E[0m"
|
||
else
|
||
tty= E= B= D= C= G= Y= R= N=
|
||
fi
|
||
rule='────────────────────────────────────────────────────────────────────────────'
|
||
framed=
|
||
logs=
|
||
jobs=
|
||
shown=
|
||
|
||
now() { date +%s; }
|
||
# stamp NAME appends "NAME HH:MM:SS.cc" to the timing log
|
||
stamp() {
|
||
[ -n "$logs" ] && [ -d "$logs" ] || return 0
|
||
t=$(date +%H:%M:%S.%N)
|
||
case "$t" in *N) t=${t%.N} ;; *) t=$(printf '%s' "$t" | cut -c1-11) ;; esac
|
||
printf '%s %s\n' "$1" "$t" >> "$logs/bootstrap.log"
|
||
}
|
||
# row N NAME TEXT [KEY]: TEXT after the name column on screen row N; off a terminal the line is
|
||
# printed when KEY (default TEXT) changed, so progress shows in steps, not every second
|
||
row() {
|
||
[ -n "$framed" ] || return 0
|
||
if [ -n "$tty" ]; then
|
||
printf '%s[%s;1H%s[2K %-20s%s' "$E" "$1" "$E" "$2" "$3"
|
||
else
|
||
eval "last=\${last_$1:-}"
|
||
[ "$last" = "${4:-$3}" ] && return 0
|
||
eval "last_$1=\${4:-\$3}"
|
||
printf ' %-20s%s\n' "$2" "$3"
|
||
fi
|
||
}
|
||
status() {
|
||
[ -n "$framed" ] || return 0
|
||
if [ -n "$tty" ]; then printf '%s[11;1H%s[2K %s' "$E" "$E" "$1"
|
||
elif [ "${last_status:-}" != "$1" ]; then last_status=$1; printf ' %s\n' "$1"; fi
|
||
}
|
||
footer() {
|
||
[ -n "$framed" ] && [ -n "$tty" ] || return 0
|
||
printf '%s[13;1H%s[2K %s%s%s' "$E" "$E" "$D" "$1" "$N"
|
||
}
|
||
frame() {
|
||
framed=1
|
||
[ -z "$tty" ] || printf '%s[H%s[2J' "$E" "$E"
|
||
printf '\n %sMakepad%s › Getting started\n %sShipped as source code, so your coding agent can customize everything.%s\n\n' "$B" "$N" "$D" "$N"
|
||
if [ -n "$need_rust" ]; then row 5 "Rust $rust_version" "${D}○ downloads first$N"; else row 5 "Rust $rust_version" "$G✓$N ready"; fi
|
||
if [ -n "$need_zip" ]; then row 6 "Loader source" "${D}○ downloads with Rust$N"; else row 6 "Loader source" "$G✓$N here"; fi
|
||
row 7 "Makepad's loader" "${D}○ compiles once Rust is here$N"
|
||
row 8 "Makepad" "$D○$N"
|
||
[ -z "$tty" ] || printf '%s[10;1H%s[2K %s%s%s' "$E" "$E" "$D" "$rule" "$N"
|
||
status "Setting up this folder once; nothing outside it changes."
|
||
footer "working · ctrl+c stops"
|
||
}
|
||
# below the frame, for whatever is printed after it
|
||
leave() { [ -z "$framed" ] || [ -z "$tty" ] || printf '%s[14;1H\n' "$E"; }
|
||
# bar DONE TOTAL sets BAR (24 cells)
|
||
bar() {
|
||
filled=0
|
||
[ "$2" -gt 0 ] && filled=$(($1 * 24 / $2))
|
||
[ "$filled" -le 24 ] || filled=24
|
||
done_bar= left_bar= i=1
|
||
while [ $i -le 24 ]; do
|
||
if [ $i -le "$filled" ]; then done_bar="$done_bar━"; else left_bar="$left_bar─"; fi
|
||
i=$((i + 1))
|
||
done
|
||
BAR="$C$done_bar$D$left_bar$N"
|
||
}
|
||
# the background jobs this script started (by PID), stopped when it fails or is stopped
|
||
stop_jobs() {
|
||
[ -n "$jobs" ] && [ -d "$jobs" ] || return 0
|
||
for job in fetch build run unpack-rustc unpack-rust-std unpack-cargo; do
|
||
[ "$job" = run ] && [ -n "$shown" ] && continue
|
||
[ -f "$jobs/$job.pid" ] && [ ! -f "$jobs/$job.done" ] && kill "$(cat "$jobs/$job.pid")" 2>/dev/null
|
||
done
|
||
return 0
|
||
}
|
||
# fail MESSAGE: one red line, exit 1
|
||
fail() {
|
||
stamp failed
|
||
stop_jobs
|
||
if [ -n "$framed" ] && [ -n "$tty" ]; then
|
||
printf '%s[11;1H%s[2K %s✗%s %s%s[13;1H%s[2K%s[14;1H\n' "$E" "$E" "$R" "$N" "$1" "$E" "$E" "$E"
|
||
else
|
||
printf '\n %s✗%s %s\n\n' "$R" "$N" "$1"
|
||
fi
|
||
exit 1
|
||
}
|
||
trap 'stamp stopped; stop_jobs; leave; exit 130' INT TERM
|
||
# job NAME COMMAND...: runs COMMAND in the background; its PID in jobs/NAME.pid, its exit code
|
||
# in jobs/NAME.done when it ends
|
||
job() {
|
||
name=$1
|
||
shift
|
||
rm -f "$jobs/$name.pid" "$jobs/$name.done"
|
||
(
|
||
"$@" &
|
||
echo $! > "$jobs/$name.pid"
|
||
wait $!
|
||
echo $? > "$jobs/$name.code"
|
||
mv -f "$jobs/$name.code" "$jobs/$name.done"
|
||
) &
|
||
while [ ! -f "$jobs/$name.pid" ]; do sleep 0.1 2>/dev/null || sleep 1; done
|
||
}
|
||
nap() { sleep 0.5 2>/dev/null || sleep 1; }
|
||
sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi; }
|
||
size_of() { wc -c < "$1" | tr -d ' '; }
|
||
# a path inside a double-quoted curl config value
|
||
cfgq() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; }
|
||
|
||
# --- Where, and what this computer has ------------------------------------------------------
|
||
case "$0" in
|
||
sh | -sh | */sh | bash | -bash | */bash | dash | */dash | zsh | */zsh) script_dir= ;;
|
||
*) script_dir=; [ -f "$0" ] && script_dir=$(cd -P -- "$(dirname -- "$0")" && pwd -P) ;;
|
||
esac
|
||
# The folder: this file's own when it is an installation already, or holds nothing else (a folder
|
||
# made for it); else ~/makepad-runner (piped into sh, or a download among other files), where the
|
||
# file copies itself.
|
||
if [ -n "$script_dir" ] && [ -z "${MAKEPAD_LOADER_ROOT:-}" ] && [ ! -d "$script_dir/cache" ] \
|
||
&& [ -n "$(ls -A "$script_dir" | grep -v -x -e "$(basename -- "$0")" -e .DS_Store)" ]; then
|
||
script_dir_other=1
|
||
fi
|
||
if [ -n "$script_dir" ] && [ -z "${script_dir_other:-}" ]; then default_root=$script_dir; else default_root=$HOME/makepad-runner; fi
|
||
root=${MAKEPAD_LOADER_ROOT:-$default_root}
|
||
mkdir -p "$root" 2>/dev/null && root=$(cd -P -- "$root" && pwd -P) || fail "Cannot create the folder $root"
|
||
rust=$root/rust
|
||
cache=$root/cache
|
||
downloads=$cache/downloads
|
||
logs_dir=$cache/logs
|
||
jobs=$cache/jobs
|
||
|
||
case "$(uname -s)/$(uname -m)" in
|
||
Darwin/arm64 | Darwin/aarch64) triple=aarch64-apple-darwin ;;
|
||
Darwin/x86_64) triple=x86_64-apple-darwin ;;
|
||
Linux/x86_64) triple=x86_64-unknown-linux-gnu ;;
|
||
Linux/aarch64 | Linux/arm64) triple=aarch64-unknown-linux-gnu ;;
|
||
*) fail "Makepad supports macOS, and glibc Linux on x86_64 and ARM64." ;;
|
||
esac
|
||
# "<part> <bytes> <sha256>" for rustc, rust-std and cargo of this platform
|
||
case "$triple" in
|
||
aarch64-apple-darwin) parts='rustc 83206125 7b39cbbb11995584741b5821511b8706ab8ecab3bc7e12b11fb1ab50cab9d079
|
||
rust-std 46977544 49dc82ff5b6fb033baa6e85635ed9cf07a27e5297a22314808ab4cf65a81335a
|
||
cargo 12993393 ae556d0783f4ab2a6fccd4674158df08cf36526b73de03b7fde0e10b2bfc3d2e' ;;
|
||
x86_64-apple-darwin) parts='rustc 102807421 a0cf817df78615a10f4d0d29dbaafd657027e1a3665be728d0f19e341c8671bb
|
||
rust-std 47585228 57a2c40ea919c06c4cd9ff4fc8b26e309c00c112538ea522227251e8ccc1f931
|
||
cargo 13345290 7fdd094e131f98864fea06c6e0059fcecaba7b0010d07b90023f42215b6f33e8' ;;
|
||
x86_64-unknown-linux-gnu) parts='rustc 134061173 18ed6559de1b8ea6b77474ea86992b9a507d3a3d134d9ee017d30cf3f406e3ee
|
||
rust-std 48777886 8aa6405356392ce50160d1b286e86091c5e14adae3061115699c84ed4394d546
|
||
cargo 15858529 18bf1598891b30dd5eb52a337d08a92b4456255ddbe4c1ab996ffb578077031c' ;;
|
||
aarch64-unknown-linux-gnu) parts='rustc 102526949 b6fdb37bc08e320bd92381e13d7311837f04a98423b8a5b678a5eb5e87c978ca
|
||
rust-std 48592329 a2eece726a579de1f554a2c3bcc62410226b0be55736f57c2b5430c1bc71b98f
|
||
cargo 15833225 e271c3c5d50336259a166e68b60ba792a64278ab117686183be3e51a0958d34b' ;;
|
||
esac
|
||
part_names='rustc rust-std cargo'
|
||
part_field() { printf '%s\n' "$parts" | awk -v p="$1" -v f="$2" '$1 == p { print $f }'; }
|
||
|
||
# https only, except a release service on this machine (tests, a local service)
|
||
case "$service" in
|
||
https://*) proto='=https' ;;
|
||
http://127.0.0.1:* | http://localhost:*) proto='=http,https' ;;
|
||
# MAKEPAD_ALLOW_HTTP=1 (tests only): a plain-http release service on another machine
|
||
http://*) [ "${MAKEPAD_ALLOW_HTTP:-}" = 1 ] && proto='=http,https' || fail "The release service must be an https URL ${D}· $service$N" ;;
|
||
*) fail "The release service must be an https URL ${D}· $service$N" ;;
|
||
esac
|
||
command -v curl >/dev/null 2>&1 || fail "Makepad needs curl ${D}· install it, then run this again$N"
|
||
command -v tar >/dev/null 2>&1 || fail "Makepad needs tar ${D}· install it, then run this again$N"
|
||
|
||
# The system's linker (and on Linux OpenSSL's development files): named, never installed here.
|
||
if [ "$(uname -s)" = Darwin ]; then
|
||
if ! xcode-select -p >/dev/null 2>&1 || ! /usr/bin/xcrun --find ld >/dev/null 2>&1; then
|
||
xcode-select --install >/dev/null 2>&1 || :
|
||
fail "Makepad needs Apple's command line tools ${D}· their installer opens now; run this again when it has finished$N"
|
||
fi
|
||
else
|
||
missing=
|
||
command -v cc >/dev/null 2>&1 || missing="a C linker (cc)"
|
||
if ! { [ -e /usr/lib/libssl.so ] || [ -e /usr/lib64/libssl.so ] || ls /usr/lib/*-linux-gnu/libssl.so >/dev/null 2>&1; }; then
|
||
missing="${missing:+$missing and }OpenSSL's development files (libssl.so)"
|
||
fi
|
||
if [ -n "$missing" ]; then
|
||
if command -v apt-get >/dev/null 2>&1; then cmd='sudo apt-get install build-essential libssl-dev'
|
||
elif command -v dnf >/dev/null 2>&1; then cmd='sudo dnf install gcc openssl-devel'
|
||
elif command -v pacman >/dev/null 2>&1; then cmd='sudo pacman -S --needed base-devel openssl'
|
||
elif command -v zypper >/dev/null 2>&1; then cmd='sudo zypper install gcc libopenssl-devel'
|
||
else cmd='a C compiler/linker and the OpenSSL 3 development package'; fi
|
||
fail "Makepad needs $missing ${D}· install it with: $cmd$N"
|
||
fi
|
||
fi
|
||
mkdir -p "$downloads" "$logs_dir" "$jobs" "$cache/loader" || fail "Cannot create $cache"
|
||
logs=$logs_dir
|
||
stamp start
|
||
export MAKEPAD_RUN_HOME="$root"
|
||
|
||
# --- The runner's packs, from the start (no arguments only) --------------------------------
|
||
# The release service's latest licence-free `runner` release names the runner's git
|
||
# repositories on the service's read-only git face; each commit's pack goes into
|
||
# $MAKEPAD_RUN_HOME/cache/incoming/<commit>.pack (renamed into place when complete), where makepad run
|
||
# takes it instead of downloading. A release fetched before is not downloaded again.
|
||
prefetch_pids=
|
||
runner_release=
|
||
repos=
|
||
# With a loader and a runner here already, the release service gets a few seconds: offline or
|
||
# unreachable, the folder's stores and loader start at once (makepad run runs the stored commits).
|
||
# The two questions to the service go out together.
|
||
meta="--retry 2 -m 60"
|
||
if [ -f "$cache/loader/src/.built" ] && [ -f "$cache/runner/release" ]; then meta="--connect-timeout 3 -m 6"; fi
|
||
mkdir -p "$cache"
|
||
: > "$cache/loader-latest.json"
|
||
( curl -fsS --proto "$proto" $meta "$service/public/app/relax/latest" > "$cache/loader-latest.json.part" 2>>"$logs/download.log" \
|
||
&& mv -f "$cache/loader-latest.json.part" "$cache/loader-latest.json" ) &
|
||
latest_pid=$!
|
||
if [ $# -eq 0 ]; then
|
||
mkdir -p "$cache/runner" "$cache/incoming"
|
||
release_json=$(curl -fsS --proto "$proto" $meta "$service/public/app/runner/latest" 2>>"$logs/download.log") || release_json=
|
||
runner_release=$(printf '%s' "$release_json" | sed -n 's/.*"release":"\([A-Za-z0-9_-]*\)".*/\1/p')
|
||
if [ -n "$runner_release" ]; then
|
||
# one "<name> <path> <commit>" line per repository of the release
|
||
repos=$(printf '%s\n' "$release_json" | tr '{}' '\n\n' | while IFS= read -r line; do
|
||
name=$(printf '%s' "$line" | sed -n 's/.*"name":"\([A-Za-z0-9_-]*\)".*/\1/p')
|
||
path=$(printf '%s' "$line" | sed -n 's/.*"path":"\([A-Za-z0-9_/-]*\)".*/\1/p')
|
||
commit=$(printf '%s' "$line" | sed -n 's/.*"commit":"\([0-9a-f]\{40\}\)".*/\1/p')
|
||
if [ -n "$name" ] && [ -n "$path" ]; then printf '%s %s %s\n' "$name" "$path" "$commit"; fi
|
||
done)
|
||
if [ "$(sed -n 1p "$cache/runner/release" 2>/dev/null)" != "$runner_release" ]; then
|
||
for entry in $(printf '%s\n' "$repos" | tr ' ' ':'); do
|
||
name=${entry%%:*}
|
||
commit=${entry##*:}
|
||
[ ${#commit} -eq 40 ] || continue
|
||
pack=$cache/incoming/$commit.pack
|
||
[ -f "$pack" ] && continue
|
||
( printf '0032want %s\n00000009done\n' "$commit" \
|
||
| curl -fsS --proto "$proto" --retry 2 -m 1800 -H 'Content-Type: application/x-git-upload-pack-request' \
|
||
--data-binary @- -o "$pack.part" "$service/public/git/runner/$name.git/git-upload-pack" \
|
||
&& mv -f "$pack.part" "$pack" ) >/dev/null 2>>"$logs/download.log" &
|
||
prefetch_pids="$prefetch_pids $!"
|
||
done
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# --- What there is to do --------------------------------------------------------------------
|
||
need_rust=
|
||
[ "$(cat "$rust/.toolchain-version" 2>/dev/null)" = "$rust_version $triple" ] || need_rust=1
|
||
[ -x "$rust/bin/cargo" ] && [ -x "$rust/bin/rustc" ] || need_rust=1
|
||
# The latest loader release; offline, the one compiled before.
|
||
wait "$latest_pid" 2>/dev/null
|
||
latest=$(cat "$cache/loader-latest.json" 2>/dev/null) || latest=
|
||
loader_version=$(printf '%s' "$latest" | sed -n 's/.*"release":"\([A-Za-z0-9_-]*\)".*/\1/p')
|
||
archive=$(printf '%s' "$latest" | sed -n 's/.*"archive":{"name":"\([A-Za-z0-9_.-]*\)","bytes":\([0-9]*\),"sha256":"\([0-9a-f]*\)"}.*/\1 \2 \3/p')
|
||
[ -n "$archive" ] || loader_version=
|
||
# One source folder for every version: an update replaces only the files that changed, so cargo
|
||
# recompiles only the crates they belong to (target/ stays inside it: makepad finds its std two
|
||
# levels above itself).
|
||
src=$cache/loader/src
|
||
[ -n "$loader_version" ] || loader_version=$(cat "$src/.version" 2>/dev/null)
|
||
[ -n "$loader_version" ] || fail "Could not reach the release service ${D}($service)$N and no loader is here yet. ${D}Check the connection, then run this again.$N"
|
||
makepad=$src/target/host/makepad
|
||
built="$loader_version $rust_version $triple"
|
||
need_build=
|
||
[ -x "$makepad" ] && [ "$(cat "$src/.built" 2>/dev/null)" = "$built" ] || need_build=1
|
||
need_zip=
|
||
[ -n "$need_build" ] && [ "$(cat "$src/.version" 2>/dev/null)" != "$loader_version" ] && need_zip=1
|
||
if [ -n "$need_zip" ]; then
|
||
[ -n "$archive" ] || fail "The release service did not answer; the loader's source is not here yet. ${D}Check the connection, then run this again.$N"
|
||
loader_name=$(printf '%s' "$archive" | cut -d' ' -f1)
|
||
loader_bytes=$(printf '%s' "$archive" | cut -d' ' -f2)
|
||
loader_sha256=$(printf '%s' "$archive" | cut -d' ' -f3)
|
||
zip=$downloads/$loader_name
|
||
fi
|
||
[ -z "$need_rust" ] && [ -z "$need_build" ] || frame
|
||
|
||
# --- 1 + 2. Rust's pieces and the loader's source, all at once ------------------------------
|
||
cfg=$downloads/fetch.curl
|
||
rm -f "$cfg"
|
||
rust_total=0
|
||
rust_base=0
|
||
# pieces PART SIZE adds the pieces of PART not yet here to the curl config
|
||
pieces() {
|
||
idx=0 from=0
|
||
while [ $from -lt "$2" ]; do
|
||
to=$((from + piece - 1))
|
||
[ $to -lt "$2" ] || to=$(($2 - 1))
|
||
file=$downloads/$1.tar.gz.$idx
|
||
have=0
|
||
[ -f "$file" ] && have=$(size_of "$file")
|
||
if [ "$have" != $((to - from + 1)) ]; then
|
||
[ ! -f "$cfg" ] || echo next >> "$cfg"
|
||
printf 'url = "%s"\noutput = "%s"\nrange = %s-%s\nproto = =https\nfail\nlocation\nretry = 3\n' \
|
||
"$dist/$1-$rust_version-$triple.tar.gz" "$(cfgq "$file")" $from $to >> "$cfg"
|
||
fi
|
||
idx=$((idx + 1))
|
||
from=$((to + 1))
|
||
done
|
||
}
|
||
if [ -n "$need_rust" ]; then
|
||
for part in $part_names; do
|
||
size=$(part_field $part 2)
|
||
rust_total=$((rust_total + size))
|
||
if [ -f "$downloads/$part.tar.gz" ] && [ "$(size_of "$downloads/$part.tar.gz")" = "$size" ]; then
|
||
rust_base=$((rust_base + size))
|
||
else
|
||
rm -f "$downloads/$part.tar.gz"
|
||
pieces $part "$size"
|
||
fi
|
||
done
|
||
fi
|
||
if [ -n "$need_zip" ]; then
|
||
rm -f "$zip.part"
|
||
[ ! -f "$cfg" ] || echo next >> "$cfg"
|
||
printf 'url = "%s"\noutput = "%s"\nproto = %s\nfail\nretry = 3\n' \
|
||
"$service/public/app/relax/$loader_version/$loader_name" "$(cfgq "$zip.part")" "$proto" >> "$cfg"
|
||
fi
|
||
# the summed size of the Rust pieces here
|
||
pieces_sum() { ls -ln "$downloads" | awk '$9 ~ /\.tar\.gz\.[0-9]+$/ { s += $5 } END { print s + 0 }'; }
|
||
if [ -f "$cfg" ]; then
|
||
status "Downloading Rust and Makepad's loader; once, into this folder."
|
||
job fetch curl -sS -Z --parallel-immediate --parallel-max 16 -K "$cfg" 2>"$logs/download.log"
|
||
prev_sum=0 prev_t=$(now) speed='0.0 MB/s'
|
||
while :; do
|
||
rsum=$((rust_base + $(pieces_sum)))
|
||
zsum=0
|
||
[ -z "$need_zip" ] || [ ! -f "$zip.part" ] || zsum=$(size_of "$zip.part")
|
||
sum=$((rsum + zsum))
|
||
t=$(now)
|
||
if [ $((t - prev_t)) -ge 1 ]; then
|
||
speed=$(awk -v b=$((sum - prev_sum)) -v s=$((t - prev_t)) 'BEGIN { printf "%.1f MB/s", b / s / 1048576 }')
|
||
prev_sum=$sum prev_t=$t
|
||
fi
|
||
if [ -n "$need_rust" ]; then
|
||
pct=$((rsum * 100 / rust_total))
|
||
[ $pct -le 100 ] || pct=100
|
||
bar $rsum $rust_total
|
||
row 5 "Rust $rust_version" "$C●$N $BAR $(printf '%3d' $pct)% $D· $((rsum / 1048576)) / $((rust_total / 1048576)) MB · $speed$N" \
|
||
"downloading · $((pct / 10 * 10))%"
|
||
fi
|
||
if [ -n "$need_zip" ]; then
|
||
if [ "$zsum" -ge "$loader_bytes" ]; then
|
||
row 6 "Loader source" "$G✓$N downloaded"
|
||
else
|
||
bar $zsum $loader_bytes
|
||
row 6 "Loader source" "$C●$N $BAR $D· $((zsum / 1024)) / $((loader_bytes / 1024)) KB$N" "downloading"
|
||
fi
|
||
fi
|
||
[ -f "$jobs/fetch.done" ] && break
|
||
nap
|
||
done
|
||
[ "$(cat "$jobs/fetch.done")" = 0 ] || fail "The download stopped. ${D}Run makepad.sh again to continue it · log: $logs/download.log$N"
|
||
rm -f "$cfg"
|
||
stamp downloaded
|
||
fi
|
||
|
||
# --- Rust: join the pieces, check, unpack the three parts side by side ----------------------
|
||
if [ -n "$need_rust" ]; then
|
||
for part in $part_names; do
|
||
archive_file=$downloads/$part.tar.gz
|
||
[ -f "$archive_file" ] && continue
|
||
idx=0
|
||
: > "$archive_file.joined" || fail "Could not join the pieces of $part."
|
||
while [ -f "$archive_file.$idx" ]; do
|
||
cat "$archive_file.$idx" >> "$archive_file.joined" || fail "Could not join the pieces of $part."
|
||
idx=$((idx + 1))
|
||
done
|
||
idx=0
|
||
while [ -f "$archive_file.$idx" ]; do rm -f "$archive_file.$idx"; idx=$((idx + 1)); done
|
||
mv -f "$archive_file.joined" "$archive_file"
|
||
done
|
||
row 5 "Rust $rust_version" "$C●$N checking $D· SHA-256 of each part$N"
|
||
for part in $part_names; do
|
||
if [ "$(sha256 "$downloads/$part.tar.gz")" != "$(part_field $part 3)" ]; then
|
||
rm -f "$downloads/$part.tar.gz"
|
||
fail "Checksum mismatch for $part.tar.gz ${D}· downloaded again on the next run$N"
|
||
fi
|
||
done
|
||
stage=$cache/rust.unpack
|
||
rm -rf "$stage"
|
||
mkdir -p "$stage" || fail "Cannot create $stage"
|
||
for part in $part_names; do
|
||
top=$part-$rust_version-$triple
|
||
inner=$part
|
||
[ $part != rust-std ] || inner=rust-std-$triple
|
||
mkdir "$stage/$part" || fail "Cannot create $stage/$part"
|
||
job unpack-$part tar -xzf "$downloads/$part.tar.gz" -C "$stage/$part" --strip-components=2 "$top/$inner" 2>"$logs/unpack-$part.log"
|
||
done
|
||
while :; do
|
||
unpacked=0
|
||
for part in $part_names; do [ ! -f "$jobs/unpack-$part.done" ] || unpacked=$((unpacked + 1)); done
|
||
[ $unpacked -lt 3 ] || break
|
||
row 5 "Rust $rust_version" "$C●$N unpacking $D· $unpacked of 3$N" "unpacking"
|
||
nap
|
||
done
|
||
ready=$stage/ready
|
||
mkdir "$ready" || fail "Cannot create $ready"
|
||
for part in $part_names; do
|
||
[ "$(cat "$jobs/unpack-$part.done")" = 0 ] || fail "Unpacking $part failed. ${D}· log: $logs/unpack-$part.log$N"
|
||
rm -f "$stage/$part/manifest.in"
|
||
# the parts share bin/, lib/ and share/: move each file into place
|
||
( cd "$stage/$part" && find . -type d -exec sh -c 'for d; do mkdir -p "$0/$d"; done' "$ready" {} + \
|
||
&& find . ! -type d -exec sh -c 'for f; do mv -f "$f" "$0/$f"; done' "$ready" {} + ) \
|
||
2>>"$logs/unpack-$part.log" || fail "Unpacking $part failed. ${D}· log: $logs/unpack-$part.log$N"
|
||
done
|
||
printf '%s %s' "$rust_version" "$triple" > "$ready/.toolchain-version"
|
||
rm -rf "$rust"
|
||
mv "$ready" "$rust" || fail "Cannot move Rust into $rust"
|
||
rm -rf "$stage"
|
||
for part in $part_names; do rm -f "$downloads/$part.tar.gz"; done
|
||
row 5 "Rust $rust_version" "$G✓$N ready $D· in rust/$N"
|
||
stamp rust
|
||
fi
|
||
|
||
# --- The loader's source: check and unpack --------------------------------------------------
|
||
if [ -n "$need_zip" ]; then
|
||
if [ "$(size_of "$zip.part" 2>/dev/null)" != "$loader_bytes" ]; then
|
||
rm -f "$zip.part"
|
||
fail "The loader's source has the wrong size ${D}· downloaded again on the next run$N"
|
||
fi
|
||
if [ "$(sha256 "$zip.part")" != "$loader_sha256" ]; then
|
||
rm -f "$zip.part"
|
||
fail "Checksum mismatch for $loader_name ${D}· downloaded again on the next run$N"
|
||
fi
|
||
unpack=$cache/loader/.unpack
|
||
rm -rf "$unpack"
|
||
mkdir -p "$unpack" || fail "Cannot create $unpack"
|
||
if command -v unzip >/dev/null 2>&1; then unzip -q "$zip.part" -d "$unpack"
|
||
elif command -v bsdtar >/dev/null 2>&1; then bsdtar -xf "$zip.part" -C "$unpack"
|
||
elif command -v python3 >/dev/null 2>&1; then python3 -m zipfile -e "$zip.part" "$unpack"
|
||
else fail "Makepad needs unzip ${D}· install it, then run this again$N"
|
||
fi >>"$logs/download.log" 2>&1 || fail "Unpacking the loader's source failed. ${D}Run this again · log: $logs/download.log$N"
|
||
new=$unpack/$loader_version
|
||
[ -f "$new/Cargo.toml" ] || fail "The loader's source has no $loader_version/Cargo.toml. ${D}Run this again later; the release service is being updated.$N"
|
||
if [ ! -f "$src/Cargo.toml" ]; then
|
||
rm -rf "$src"
|
||
mv "$new" "$src" || fail "Cannot move the loader's source into $src"
|
||
else
|
||
# an update: files that changed are replaced (with the time of now, so cargo sees them
|
||
# as changed), files the new version no longer has are removed, the rest stay as they are
|
||
row 7 "Makepad's loader" "$C●$N updating its source $D· $loader_version$N" "updating"
|
||
# one `diff -rq` names what differs (one process, not one per file)
|
||
LC_ALL=C diff -rq -x target -x .version -x .built "$new" "$src" > "$unpack/changes" 2>&1
|
||
[ $? -le 1 ] || fail "Cannot compare the loader's source with $src ${D}· $(head -1 "$unpack/changes")$N"
|
||
while IFS= read -r line; do
|
||
case "$line" in
|
||
"Files $new/"*" differ")
|
||
rel=${line#"Files $new/"}
|
||
rel=${rel%%" and $src/"*}
|
||
mv -f "$new/$rel" "$src/$rel" && touch "$src/$rel" || exit 1 ;;
|
||
"Only in $new"*)
|
||
dir=${line#"Only in $new"}
|
||
dir=${dir%%": "*}
|
||
rel=${dir#/}${dir:+/}${line#*": "}
|
||
rel=${rel#/}
|
||
mkdir -p "$(dirname "$src/$rel")" && mv -f "$new/$rel" "$src/$rel" || exit 1
|
||
find "$src/$rel" -exec touch {} + ;;
|
||
"Only in $src"*)
|
||
dir=${line#"Only in $src"}
|
||
dir=${dir%%": "*}
|
||
rel=${dir#/}${dir:+/}${line#*": "}
|
||
rm -rf "$src/${rel#/}" ;;
|
||
esac
|
||
done < "$unpack/changes" || fail "Cannot update the loader's source in $src"
|
||
fi
|
||
printf '%s\n' "$loader_version" > "$src/.version"
|
||
rm -rf "$unpack"
|
||
rm -f "$zip.part"
|
||
stamp source
|
||
fi
|
||
|
||
# --- 3. Compile the loader --------------------------------------------------------------------
|
||
# The compile sees no Rust settings from elsewhere on this computer and writes only into
|
||
# cache/. Cargo's own output goes to the log; the screen shows one progress line.
|
||
unset RUSTUP_TOOLCHAIN RUSTFLAGS CARGO_ENCODED_RUSTFLAGS CARGO_BUILD_TARGET CARGO_BUILD_RUSTFLAGS CARGO_TARGET_DIR RUSTC_WRAPPER RUSTDOC
|
||
export PATH="$rust/bin:$PATH" CARGO_HOME="$cache/cargo-home" RUSTC="$rust/bin/rustc" CARGO_INCREMENTAL=0
|
||
if [ -n "$need_build" ]; then
|
||
build_log=$logs/build.log
|
||
rm -f "$build_log"
|
||
# the crates to compile: the lock file's packages (the archive holds only the loader's crates)
|
||
crates=$(grep -c '^name = ' "$src/Cargo.lock" 2>/dev/null)
|
||
[ "${crates:-0}" -gt 0 ] || crates=1
|
||
# the archive's lock file (path crates only): nothing is resolved or fetched on this computer
|
||
locked=
|
||
[ ! -f "$src/Cargo.lock" ] || locked=--locked
|
||
# room for the build (about 2 GB of target/), said before it starts rather than as a cargo error
|
||
free_kb=$(df -Pk "$cache" | awk 'NR == 2 { print $4 }')
|
||
[ "${free_kb:-0}" -ge 3000000 ] || fail "Compiling Makepad's loader needs about 3 GB free on this disk. ${D}Free some space, then run this again.$N"
|
||
status "Compiling Makepad's loader; once per version."
|
||
build_t0=$(now)
|
||
# for this computer's processor ([profile.host]: thin LTO, 16 codegen units, target-cpu=native)
|
||
job build env RUSTFLAGS=-Ctarget-cpu=native sh -c 'cd "$1" && exec "$2" build --profile host --offline $3 -p makepad-run --bin makepad' \
|
||
build "$src" "$rust/bin/cargo" "$locked" >"$build_log" 2>&1
|
||
while [ ! -f "$jobs/build.done" ]; do
|
||
compiled=$(grep -c '^ *Compiling ' "$build_log" 2>/dev/null)
|
||
compiled=${compiled:-0}
|
||
[ "$compiled" -le $crates ] || compiled=$crates
|
||
bar "$compiled" $crates
|
||
row 7 "Makepad's loader" "$C●$N $BAR $D· $compiled of $crates crates · $(($(now) - build_t0)) s$N" "compiling"
|
||
nap
|
||
done
|
||
if [ "$(cat "$jobs/build.done")" != 0 ]; then
|
||
why=$(grep -m1 '^error' "$build_log" | cut -c1-90)
|
||
fail "Makepad's loader did not compile${why:+: $why}. ${D}Run this again; if it stops the same way, the log says why: $build_log$N"
|
||
fi
|
||
printf '%s\n' "$built" > "$src/.built"
|
||
row 7 "Makepad's loader" "$G✓$N compiled $D· $(($(now) - build_t0)) s$N"
|
||
stamp built
|
||
fi
|
||
|
||
# This script stays in the folder for the next start.
|
||
if [ -n "$script_dir" ] && [ "$script_dir" != "$root" ]; then
|
||
cp "$0" "$root/makepad.sh.next" 2>/dev/null && chmod 755 "$root/makepad.sh.next" && mv -f "$root/makepad.sh.next" "$root/makepad.sh" || :
|
||
fi
|
||
|
||
# --- 4. Start it --------------------------------------------------------------------------------
|
||
# makepad run runs cargo/rustc for build scripts and unit graphs (until it does that itself), so the
|
||
# toolchain stays on PATH for it and every app it starts. With no arguments `makepad` runs the
|
||
# runner: its launch record names the runner's repositories, the release service's latest
|
||
# licence-free `runner` release, which the service serves read-only over git
|
||
# ($service/public/git/runner/<repository>.git, one branch per release). The repository at
|
||
# `makepad` is the workspace, the others mount at their folders inside it. makepad run keeps its
|
||
# stores in $MAKEPAD_RUN_HOME/cache and, with --cached, starts the last one at once while it
|
||
# fetches the new release for the next start. Offline, the release recorded last.
|
||
export MAKEPAD_LOADER_ROOT="$root" MAKEPAD_LOADER_SERVICE="$service"
|
||
if [ $# -gt 0 ]; then
|
||
stamp start-loader
|
||
leave
|
||
exec "$makepad" "$@"
|
||
fi
|
||
if [ -n "$framed" ]; then
|
||
row 8 "Makepad" "$C●$N starting"
|
||
status "Makepad opens in its own window; closing it ends this one."
|
||
else
|
||
printf ' %sMakepad%s %s· starting · log: %s%s\n' "$B" "$N" "$D" "$logs/makepad.log" "$N"
|
||
fi
|
||
# the runner's packs downloaded alongside the build are complete before makepad run looks for them
|
||
[ -z "$prefetch_pids" ] || wait $prefetch_pids || :
|
||
if [ -n "$runner_release" ]; then
|
||
[ -n "$repos" ] || fail "The runner release names no repositories."
|
||
{ printf '%s\n' "$runner_release"; printf '%s\n' "$repos"; } > "$cache/runner/release.next" && mv -f "$cache/runner/release.next" "$cache/runner/release"
|
||
else
|
||
[ -f "$cache/runner/release" ] || fail "Could not reach the release service ${D}($service)$N and the runner was never fetched."
|
||
status "The release service did not answer; starting the runner fetched before."
|
||
runner_release=$(sed -n 1p "$cache/runner/release")
|
||
repos=$(sed -n '2,$p' "$cache/runner/release")
|
||
fi
|
||
git_base=$service/public/git/runner
|
||
# The runner's launch record ($MAKEPAD_RUN_HOME/apps/runner.launch, one `key<TAB>value` line each):
|
||
# what `makepad` with no arguments runs, from here and from anywhere else (makepad launch.rs).
|
||
launch_dir=$root/apps
|
||
mkdir -p "$launch_dir" || fail "Cannot create $launch_dir"
|
||
record=$launch_dir/runner.launch
|
||
printf 'title\tMakepad\narg\trun\narg\t--quiet\narg\t--cached\n' > "$record.part" || fail "Cannot write $record"
|
||
rm -f "$record.mounts"
|
||
root_repo=
|
||
for entry in $(printf '%s\n' "$repos" | tr ' ' ':'); do
|
||
name=${entry%%:*}
|
||
path=${entry#*:}
|
||
path=${path%%:*}
|
||
# the release's commit pinned: makepad run takes the pack downloaded above (or its store) as it is,
|
||
# without asking the service again
|
||
commit=${entry##*:}
|
||
pin=
|
||
[ ${#commit} -ne 40 ] || pin=@$commit
|
||
case "$path" in
|
||
makepad) root_repo="$git_base/$name.git#$runner_release$pin" ;;
|
||
makepad/*) printf 'arg\t--mount\narg\t%s\n' "${path#makepad/}=$git_base/$name.git#$runner_release$pin" >> "$record.mounts" ;;
|
||
*) rm -f "$record.mounts"; fail "The runner release has a repository outside its workspace: $path" ;;
|
||
esac
|
||
done
|
||
[ -n "$root_repo" ] || fail "The runner release has no repository at makepad."
|
||
printf 'arg\t%s\n' "$root_repo" >> "$record.part"
|
||
[ ! -f "$record.mounts" ] || { cat "$record.mounts" >> "$record.part"; rm -f "$record.mounts"; }
|
||
printf 'arg\tmakepad-runner\n' >> "$record.part"
|
||
mv -f "$record.part" "$record" || fail "Cannot write $record"
|
||
stamp start-loader
|
||
# The loader runs as a job; the runner reports its first frame (MAKEPAD_RUN_REPORT_TO), and the row
|
||
# turns to running then. Its own output goes to the log.
|
||
report=$logs/runner.report
|
||
: > "$report"
|
||
export MAKEPAD_RUN_REPORT_TO="$report"
|
||
job run "$makepad" >"$logs/makepad.log" 2>&1
|
||
while [ ! -f "$jobs/run.done" ]; do
|
||
if [ -z "$shown" ] && grep -q '^present' "$report" 2>/dev/null; then
|
||
shown=1
|
||
stamp runner-present
|
||
row 8 "Makepad" "$G✓$N running"
|
||
status "Makepad is open in its own window; closing it ends this one."
|
||
footer "ctrl+c leaves Makepad running"
|
||
fi
|
||
nap
|
||
done
|
||
code=$(cat "$jobs/run.done")
|
||
stamp "loader-exit-$code"
|
||
[ "$code" = 0 ] || fail "Makepad stopped with an error (exit $code). ${D}Run this again to start it; the log says why: $logs/makepad.log$N"
|
||
leave
|
||
exit 0
|