214 lines
8.9 KiB
Bash
Executable File
214 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
die() {
|
|
echo "appimage build: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
cat >&2 <<'EOF'
|
|
usage: scripts/build-appimage.sh --version VERSION --revision COMMIT
|
|
[--occt-root DIR] [--tools-dir DIR] [--out-dir DIR]
|
|
EOF
|
|
exit 2
|
|
}
|
|
|
|
invocation_dir=$PWD
|
|
invocation_dir=$(CDPATH= cd -- "$invocation_dir" && pwd -P)
|
|
repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
|
|
version=
|
|
revision=
|
|
occt_root=$repo/target/vendored-occt
|
|
tools_dir=$repo/target/appimage-tools
|
|
out_dir=$repo/target/appimage
|
|
while (( $# )); do
|
|
case "$1" in
|
|
--version) (( $# >= 2 )) || usage; version=$2; shift 2 ;;
|
|
--revision) (( $# >= 2 )) || usage; revision=$2; shift 2 ;;
|
|
--occt-root) (( $# >= 2 )) || usage; occt_root=$2; shift 2 ;;
|
|
--tools-dir) (( $# >= 2 )) || usage; tools_dir=$2; shift 2 ;;
|
|
--out-dir) (( $# >= 2 )) || usage; out_dir=$2; shift 2 ;;
|
|
--help|-h) usage ;;
|
|
*) usage ;;
|
|
esac
|
|
done
|
|
|
|
resolve_from_invocation() {
|
|
case "$1" in
|
|
/*) printf '%s\n' "$1" ;;
|
|
*) printf '%s/%s\n' "$invocation_dir" "$1" ;;
|
|
esac
|
|
}
|
|
|
|
occt_root=$(resolve_from_invocation "$occt_root")
|
|
tools_dir=$(resolve_from_invocation "$tools_dir")
|
|
out_dir=$(resolve_from_invocation "$out_dir")
|
|
|
|
[[ "$version" =~ ^[A-Za-z0-9][A-Za-z0-9._+-]*$ ]] || die "--version is missing or invalid"
|
|
[[ "$revision" =~ ^[0-9a-f]{40}$ ]] || die "--revision must be a lowercase 40-character commit"
|
|
head_revision=$(git -C "$repo" rev-parse HEAD)
|
|
[[ "$revision" == "$head_revision" ]] || die "--revision must equal the exact source HEAD"
|
|
[[ -z $(git -C "$repo" status --porcelain --untracked-files=all) ]] || die "source worktree must be clean"
|
|
|
|
source_date_epoch=${SOURCE_DATE_EPOCH:-$(git -C "$repo" show -s --format=%ct "$revision")}
|
|
[[ "$source_date_epoch" =~ ^[1-9][0-9]*$ ]] || die "SOURCE_DATE_EPOCH must be a positive integer"
|
|
export SOURCE_DATE_EPOCH=$source_date_epoch
|
|
export CARGO_INCREMENTAL=0
|
|
export RUSTFLAGS="--remap-path-prefix=$repo=/usr/src/vernier"
|
|
|
|
for command in cargo find install ldd patchelf readelf sha256sum sort touch; do
|
|
command -v "$command" >/dev/null || die "$command is required"
|
|
done
|
|
[[ -d "$occt_root" ]] || die "prepared OCCT tree is missing: $occt_root"
|
|
occt_root=$(CDPATH= cd -- "$occt_root" && pwd -P)
|
|
[[ -f "$occt_root/share/vernier/complete" ]] || die "prepared OCCT tree is incomplete: $occt_root"
|
|
[[ $(<"$occt_root/share/vernier/complete") == vernier.occt-static.v1 ]] || die "prepared OCCT completion marker is invalid"
|
|
[[ $(<"$occt_root/share/vernier/occt-source-revision.txt") == a016080bf6738d6aeae020badee4e888ad1540a5 ]] || die "prepared OCCT source revision is invalid"
|
|
for resource in \
|
|
SHMessage/SHAPE.us \
|
|
XSMessage/XSTEP.us \
|
|
XSTEPResource/STEP \
|
|
XSTEPResource/IGES \
|
|
StdResource/Standard \
|
|
StdResource/XCAF; do
|
|
[[ -s "$occt_root/resources/$resource" ]] || die "prepared OCCT resource is missing: $resource"
|
|
done
|
|
for license in LICENSE_LGPL_21.txt OCCT_LGPL_EXCEPTION.txt; do
|
|
[[ -s "$occt_root/licenses/$license" ]] || die "prepared OCCT license is missing: $license"
|
|
done
|
|
"$repo/scripts/fetch-appimage-tools.sh" --offline "$tools_dir" >/dev/null
|
|
tools_dir=$(CDPATH= cd -- "$tools_dir" && pwd -P)
|
|
appimagetool=$tools_dir/appimagetool-x86_64.AppImage
|
|
runtime=$tools_dir/runtime-x86_64
|
|
[[ -x "$appimagetool" ]] || die "verified appimagetool is not executable"
|
|
[[ -f "$runtime" ]] || die "verified type2 runtime is missing"
|
|
|
|
cd "$repo"
|
|
VERNIER_OCCT_STATIC_ROOT=$occt_root cargo build --locked --release \
|
|
-p vernier-app --bin vernier-app --bin vernier-worker
|
|
|
|
cargo_target=${CARGO_TARGET_DIR:-$repo/target}
|
|
[[ "$cargo_target" == /* ]] || cargo_target=$repo/$cargo_target
|
|
app=$cargo_target/release/vernier-app
|
|
worker=$cargo_target/release/vernier-worker
|
|
[[ -x "$app" && -x "$worker" ]] || die "release build did not produce both shipped binaries"
|
|
"$repo/scripts/check-static-occt.sh" "$app" "$worker"
|
|
|
|
mkdir -p -- "$out_dir"
|
|
out_dir=$(CDPATH= cd -- "$out_dir" && pwd -P)
|
|
stage=$(mktemp -d "$out_dir/.AppDir.XXXXXXXX")
|
|
artifact_tmp=
|
|
checksum_tmp=
|
|
cleanup() {
|
|
rm -rf -- "$stage"
|
|
[[ -z "$artifact_tmp" ]] || rm -f -- "$artifact_tmp"
|
|
[[ -z "$checksum_tmp" ]] || rm -f -- "$checksum_tmp"
|
|
}
|
|
trap cleanup EXIT
|
|
appdir=$stage/VernierCAD.AppDir
|
|
mkdir -p \
|
|
"$appdir/usr/bin" \
|
|
"$appdir/usr/lib" \
|
|
"$appdir/usr/share/vernier/occt" \
|
|
"$appdir/usr/share/vernier/fonts" \
|
|
"$appdir/usr/share/licenses/vernier/occt" \
|
|
"$appdir/usr/share/icons/hicolor/scalable/apps"
|
|
install -m 0755 packaging/appimage/AppRun "$appdir/AppRun"
|
|
install -m 0644 packaging/appimage/vernier.desktop "$appdir/vernier.desktop"
|
|
install -m 0644 packaging/appimage/vernier.svg "$appdir/vernier.svg"
|
|
install -m 0644 packaging/appimage/vernier.svg "$appdir/usr/share/icons/hicolor/scalable/apps/vernier.svg"
|
|
ln -s vernier.svg "$appdir/.DirIcon"
|
|
install -m 0755 "$app" "$appdir/usr/bin/vernier-app"
|
|
install -m 0755 "$worker" "$appdir/usr/bin/vernier-worker"
|
|
install -m 0644 LICENSE "$appdir/usr/share/licenses/vernier/LICENSE"
|
|
cp -a "$occt_root/resources" "$appdir/usr/share/vernier/occt/resources"
|
|
cp -a "$occt_root/licenses/." "$appdir/usr/share/licenses/vernier/occt/"
|
|
find crates/vernier-ui/assets/fonts -maxdepth 1 -type f \
|
|
\( -name '*.ttf' -o -name 'LICENSE-fonts.txt' \) -exec cp -p -- {} "$appdir/usr/share/vernier/fonts/" \;
|
|
|
|
is_base_runtime() {
|
|
local name=${1##*/}
|
|
case "$name" in
|
|
linux-vdso.so.*|ld-linux-x86-64.so.*|libc.so.*|libdl.so.*|libm.so.*|libpthread.so.*|libresolv.so.*|librt.so.*|libutil.so.*|libanl.so.*|libnss_*.so.*)
|
|
return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
declare -A scanned=()
|
|
declare -A bundled_sources=()
|
|
queue=("$app" "$worker")
|
|
for (( index=0; index < ${#queue[@]}; index++ )); do
|
|
binary=${queue[$index]}
|
|
binary=$(readlink -f "$binary")
|
|
[[ -z ${scanned[$binary]:-} ]] || continue
|
|
scanned[$binary]=1
|
|
set +e
|
|
ldd_output=$(ldd "$binary" 2>&1)
|
|
ldd_status=$?
|
|
set -e
|
|
(( ldd_status == 0 )) || die "ldd failed while resolving $binary: $ldd_output"
|
|
[[ "$ldd_output" != *'not found'* ]] || die "unresolved runtime dependency for $binary"
|
|
while IFS=$'\t' read -r soname resolved; do
|
|
[[ -n "$soname" && -n "$resolved" ]] || continue
|
|
[[ "$soname" != libTK* ]] || die "dynamic OCCT dependency is forbidden: $soname"
|
|
is_base_runtime "$soname" && continue
|
|
resolved=$(readlink -f "$resolved")
|
|
[[ -f "$resolved" ]] || die "resolved library is not a file: $resolved"
|
|
if [[ -n ${bundled_sources[$soname]:-} && ${bundled_sources[$soname]} != "$resolved" ]]; then
|
|
die "two runtime libraries share soname $soname"
|
|
fi
|
|
if [[ -z ${bundled_sources[$soname]:-} ]]; then
|
|
bundled_sources[$soname]=$resolved
|
|
install -m 0644 "$resolved" "$appdir/usr/lib/$soname"
|
|
queue+=("$resolved")
|
|
fi
|
|
done < <(awk '
|
|
$2 == "=>" && $3 ~ /^\// { print $1 "\t" $3 }
|
|
$1 ~ /^\// { n=$1; sub(/^.*\//, "", n); print n "\t" $1 }
|
|
' <<<"$ldd_output")
|
|
done
|
|
|
|
patchelf --set-rpath '$ORIGIN/../lib' "$appdir/usr/bin/vernier-app"
|
|
patchelf --set-rpath '$ORIGIN/../lib' "$appdir/usr/bin/vernier-worker"
|
|
while IFS= read -r -d '' library; do
|
|
patchelf --set-rpath '$ORIGIN' "$library"
|
|
done < <(find "$appdir/usr/lib" -maxdepth 1 -type f -print0)
|
|
|
|
manifest=$appdir/usr/share/vernier/release-manifest.tsv
|
|
manifest_input=$stage/release-manifest.unsorted
|
|
{
|
|
printf 'format\tvernier.appimage.v1\n'
|
|
printf 'occt_source_revision\ta016080bf6738d6aeae020badee4e888ad1540a5\n'
|
|
printf 'revision\t%s\n' "$revision"
|
|
printf 'source_date_epoch\t%s\n' "$source_date_epoch"
|
|
printf 'tool_appimagetool_sha256\ted4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0\n'
|
|
printf 'tool_runtime_sha256\t1cc49bcf1e2ccd593c379adb17c9f85a36d619088296504de95b1d06215aebbf\n'
|
|
printf 'version\t%s\n' "$version"
|
|
while IFS= read -r -d '' file; do
|
|
relative=${file#"$appdir/"}
|
|
[[ "$relative" != *$'\t'* && "$relative" != *$'\n'* ]] || die "AppDir path is not manifest-safe: $relative"
|
|
printf 'file\t%s\t%s\n' "$(sha256sum "$file" | awk '{print $1}')" "$relative"
|
|
done < <(find "$appdir" -type f ! -path "$manifest" -print0)
|
|
} >"$manifest_input"
|
|
LC_ALL=C sort "$manifest_input" >"$manifest"
|
|
|
|
timestamp="@$source_date_epoch"
|
|
find "$appdir" -exec touch -h -d "$timestamp" {} +
|
|
|
|
artifact=$out_dir/VernierCAD-$version-x86_64.AppImage
|
|
artifact_tmp=$(mktemp "$out_dir/.VernierCAD-$version-x86_64.AppImage.XXXXXXXX")
|
|
rm -f -- "$artifact_tmp"
|
|
ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 SOURCE_DATE_EPOCH=$source_date_epoch \
|
|
"$appimagetool" --runtime-file "$runtime" "$appdir" "$artifact_tmp"
|
|
chmod 0755 "$artifact_tmp"
|
|
mv -f -- "$artifact_tmp" "$artifact"
|
|
artifact_tmp=
|
|
|
|
checksum_tmp=$(mktemp "$out_dir/.VernierCAD-$version-x86_64.AppImage.sha256.XXXXXXXX")
|
|
printf '%s %s\n' "$(sha256sum "$artifact" | awk '{print $1}')" "$(basename "$artifact")" >"$checksum_tmp"
|
|
mv -f -- "$checksum_tmp" "$artifact.sha256"
|
|
checksum_tmp=
|
|
echo "built $artifact"
|