mirror of
https://github.com/zotero/zotero.git
synced 2026-08-28 05:25:31 +00:00
After a full build, dir_build saves a manifest of build/ files to staging/.build-manifest. On subsequent runs, if all changed files are ones that build.sh copies into omni.ja unmodified (chrome/, components/, resource/, and test/ when tests are staged), zip just those files into the staged omni.ja instead of rebuilding, taking rebuilds from ~15 seconds to ~0.3 seconds on an M1 Mac. Files are prescreened by size and mtime so that only changed files need to be hashed. Zotero .ftl files are also updated at their localization/<locale>/ paths, and test files are also copied to the staged tests/ directory. Any other change triggers an automatic full rebuild: files transformed by build.sh (defaults/, chrome.manifest, version, translators/, styles/, mozilla .ftl files, CSL locales), removed files, changes to build inputs in app/ (detected via a size/mtime fingerprint, with xulrunner runtimes covered by the hash-* files written by fetch_xulrunner), or requesting tests or devtools that the staged build doesn't include. Other changes: - dir_build no longer takes -q and always skips omni.ja compression and optimization, which only matter for distribution builds made via build.sh. Use -f (dir_build or build_and_run) to force a full rebuild. - build_and_run now always rebuilds. -r is deprecated, and -n skips the rebuild and just launches the app. - build_and_run no longer passes -purgecaches. Startup caches are invalidated automatically when the BuildID changes, which now happens whenever omni.ja is modified (including via add_omni_file), so relaunching an unchanged build can use the startup cache. - build_and_run and runtests.sh invoke js-build directly instead of via 'npm run', which saves ~270ms of npm overhead per build. - The Word integration dylib is now ad-hoc-signed by dir_build, and only on full rebuilds, since incremental updates don't invalidate the existing signature. This also covers test builds, which were never signed before. - dir_build removes broken symlinks left in build/ when source files are deleted, which previously broke rsync in prepare_build.
322 lines
8.1 KiB
Bash
Executable file
322 lines
8.1 KiB
Bash
Executable file
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
#
|
|
# Incrementally update a staged build in staging/ by zipping changed files from
|
|
# the client build/ directory directly into app/omni.ja, based on a manifest
|
|
# saved by dir_build after the last full build.
|
|
#
|
|
# Only files that are copied into omni.ja unmodified by build.sh can be updated
|
|
# this way. If any other file changed (or a file was removed), a full rebuild
|
|
# is required.
|
|
#
|
|
# Exit codes:
|
|
# 0 -- staged build was updated (or nothing changed)
|
|
# 2 -- a full rebuild is required
|
|
#
|
|
# This is normally run automatically by dir_build, not directly.
|
|
#
|
|
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
APP_ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
ROOT_DIR="$(dirname "$APP_ROOT_DIR")"
|
|
. "$APP_ROOT_DIR/config.sh"
|
|
. "$SCRIPT_DIR/utils.sh"
|
|
|
|
function usage {
|
|
cat >&2 <<DONE
|
|
Usage: $0 -p PLATFORM [-a ARCH] [-t]
|
|
|
|
Options
|
|
-p PLATFORM platform of the staged build to update (m=Mac, w=Windows, l=Linux)
|
|
-a ARCH target architecture (Windows/Linux only)
|
|
-t devtools requested
|
|
DONE
|
|
exit 1
|
|
}
|
|
|
|
platform=""
|
|
arch=""
|
|
devtools=0
|
|
while getopts "p:a:t" opt; do
|
|
case $opt in
|
|
p)
|
|
platform="$OPTARG"
|
|
;;
|
|
a)
|
|
arch="$OPTARG"
|
|
;;
|
|
t)
|
|
devtools=1
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$platform" ]; then
|
|
usage
|
|
fi
|
|
|
|
BUILD_SRC_DIR="$ROOT_DIR/build"
|
|
MANIFEST_FILE="$STAGE_DIR/.build-manifest"
|
|
|
|
function fall_back {
|
|
echo "$1 -- performing full build" >&2
|
|
exit 2
|
|
}
|
|
|
|
if [ ! -d "$BUILD_SRC_DIR" ]; then
|
|
fall_back "No build/ directory"
|
|
fi
|
|
if [ ! -f "$MANIFEST_FILE" ]; then
|
|
fall_back "No manifest from previous build"
|
|
fi
|
|
if [[ "$(sed -n 's/^#format=//p' "$MANIFEST_FILE")" != "2" ]]; then
|
|
fall_back "Unsupported manifest format"
|
|
fi
|
|
|
|
# Make sure a staged build exists for the requested platform
|
|
case $platform in
|
|
m)
|
|
app_dir="$STAGE_DIR/Zotero.app/Contents/Resources"
|
|
;;
|
|
w)
|
|
app_dir="$STAGE_DIR/Zotero_$(get_canonical_arch w "$arch")"
|
|
;;
|
|
l)
|
|
app_dir="$STAGE_DIR/Zotero_linux-$(get_canonical_arch l "$arch")"
|
|
;;
|
|
*)
|
|
fall_back "Unknown platform '$platform'"
|
|
;;
|
|
esac
|
|
if [ ! -f "$app_dir/app/omni.ja" ]; then
|
|
fall_back "No staged build for requested platform"
|
|
fi
|
|
|
|
# If the previous build didn't include tests or devtools and they're requested
|
|
# now, files are missing from the staged build
|
|
manifest_tests=$(sed -n 's/^#include_tests=//p' "$MANIFEST_FILE")
|
|
manifest_tests=${manifest_tests:-0}
|
|
manifest_devtools=$(sed -n 's/^#devtools=//p' "$MANIFEST_FILE")
|
|
manifest_devtools=${manifest_devtools:-0}
|
|
if [[ -z "${ZOTERO_TEST:-}" ]] || [[ "${ZOTERO_TEST:-}" == "0" ]]; then
|
|
want_tests=0
|
|
else
|
|
want_tests=1
|
|
fi
|
|
if [[ $want_tests -eq 1 ]] && [[ $manifest_tests != "1" ]]; then
|
|
fall_back "Tests requested but not included in staged build"
|
|
fi
|
|
if [[ $devtools -eq 1 ]] && [[ $manifest_devtools != "1" ]]; then
|
|
fall_back "Devtools requested but not included in staged build"
|
|
fi
|
|
|
|
# If anything changed in app/ (build scripts, assets, bundled modules, Firefox
|
|
# runtime), a full rebuild is required
|
|
manifest_app_hash=$(sed -n 's/^#app_hash=//p' "$MANIFEST_FILE")
|
|
app_hash=$(generate_app_hash "$APP_ROOT_DIR")
|
|
if [[ -z "$manifest_app_hash" ]] || [[ "$app_hash" != "$manifest_app_hash" ]]; then
|
|
fall_back "Files in app/ changed"
|
|
fi
|
|
|
|
file_list=$(mktemp)
|
|
new_stats=$(mktemp)
|
|
carried=$(mktemp)
|
|
candidates=$(mktemp)
|
|
removed=$(mktemp)
|
|
cand_entries=$(mktemp)
|
|
trap 'rm -f "$file_list" "$new_stats" "$carried" "$candidates" "$removed" "$cand_entries"' EXIT
|
|
|
|
cd "$BUILD_SRC_DIR"
|
|
|
|
# Quickly find files that may have changed based on size/mtime, so that only
|
|
# those have to be hashed. Unmodified manifest entries are carried over,
|
|
# new/modified files become candidates, and files no longer present are
|
|
# considered removed.
|
|
build_manifest_file_list > "$file_list"
|
|
stat_file_list < "$file_list" > "$new_stats"
|
|
awk -F'\t' -v carried="$carried" -v candidates="$candidates" -v removed="$removed" '
|
|
NR==FNR {
|
|
if (/^#/) next
|
|
hash[$3] = $1
|
|
stat[$3] = $2
|
|
next
|
|
}
|
|
{
|
|
if (!($2 in stat)) {
|
|
print > candidates
|
|
}
|
|
else {
|
|
if ($1 == stat[$2]) {
|
|
print hash[$2] "\t" $0 > carried
|
|
}
|
|
else {
|
|
print > candidates
|
|
}
|
|
delete stat[$2]
|
|
}
|
|
}
|
|
END {
|
|
for (path in stat) print path > removed
|
|
}
|
|
' "$MANIFEST_FILE" "$new_stats"
|
|
|
|
# Hash the candidates and compare to the previous hashes to find real changes
|
|
# -- A = added, C = changed, R = removed
|
|
if [ -s "$candidates" ]; then
|
|
paste \
|
|
<(cut -f2 "$candidates" | sed 's,^,./,' | tr '\n' '\0' | hash_file_list | cut -f1) \
|
|
"$candidates" \
|
|
> "$cand_entries"
|
|
fi
|
|
changes=$(
|
|
if [ -s "$cand_entries" ]; then
|
|
awk -F'\t' '
|
|
NR==FNR {
|
|
if (/^#/) next
|
|
old[$3] = $1
|
|
next
|
|
}
|
|
{
|
|
if (!($3 in old)) print "A\t" $3
|
|
else if (old[$3] != $1) print "C\t" $3
|
|
}
|
|
' "$MANIFEST_FILE" "$cand_entries"
|
|
fi
|
|
if [ -s "$removed" ]; then
|
|
awk '{print "R\t" $0}' "$removed"
|
|
fi
|
|
)
|
|
|
|
# Make sure all changed files can be zipped directly into omni.ja, and skip
|
|
# ones that aren't included in the staged build
|
|
zip_files=()
|
|
ftl_files=()
|
|
test_copy_files=()
|
|
while IFS=$'\t' read -r status path; do
|
|
if [ -z "$status" ]; then
|
|
continue
|
|
fi
|
|
if [ "$status" == "R" ]; then
|
|
case "$path" in
|
|
test/*)
|
|
# Ignore removed test files if tests weren't included
|
|
if [[ $manifest_tests != "1" ]]; then
|
|
continue
|
|
fi
|
|
;;
|
|
esac
|
|
fall_back "$path was removed"
|
|
fi
|
|
case "$path" in
|
|
# Copied into localization/ subdirectories by build.sh
|
|
chrome/locale/*/zotero/mozilla/*)
|
|
fall_back "Can't update $path in place"
|
|
;;
|
|
# Pruned by prepare_build
|
|
chrome/content/zotero/locale/csl/*)
|
|
fall_back "Can't update $path in place"
|
|
;;
|
|
# Copied to localization/<locale>/ in addition to the chrome path
|
|
chrome/locale/*/zotero/*.ftl)
|
|
zip_files+=("$path")
|
|
ftl_files+=("$path")
|
|
;;
|
|
chrome/*|components/*|resource/*)
|
|
zip_files+=("$path")
|
|
;;
|
|
# Appended to the main chrome.manifest by build.sh
|
|
test/chrome.manifest)
|
|
fall_back "Can't update $path in place"
|
|
;;
|
|
test/*)
|
|
# If tests weren't included in the staged build, ignore changes
|
|
# to test files
|
|
if [[ $manifest_tests == "1" ]]; then
|
|
zip_files+=("$path")
|
|
case "$path" in
|
|
# Also copied to tests/ at the top level of the app
|
|
test/tests/*)
|
|
test_copy_files+=("$path")
|
|
;;
|
|
esac
|
|
fi
|
|
;;
|
|
*)
|
|
fall_back "Can't update $path in place"
|
|
;;
|
|
esac
|
|
done <<< "$changes"
|
|
|
|
# Find all staged omni.ja files to update (multiple can exist -- e.g., all
|
|
# Windows architectures)
|
|
omni_files=()
|
|
while IFS= read -r file; do
|
|
omni_files+=("$file")
|
|
done < <(find "$STAGE_DIR" -path '*/app/omni.ja')
|
|
|
|
if [ ${#zip_files[@]} -gt 0 ]; then
|
|
echo "Changed files:"
|
|
for path in "${zip_files[@]}"; do
|
|
echo " $path"
|
|
done
|
|
|
|
cd "$BUILD_SRC_DIR"
|
|
for omni_file in "${omni_files[@]}"; do
|
|
echo "Updating ${omni_file#"$STAGE_DIR"/}"
|
|
zip -qX "$omni_file" "${zip_files[@]}"
|
|
done
|
|
|
|
# Copy .ftl files to their localization/ paths
|
|
if [ ${#ftl_files[@]} -gt 0 ]; then
|
|
l10n_dir=$(mktemp -d)
|
|
for path in "${ftl_files[@]}"; do
|
|
locale=${path#chrome/locale/}
|
|
locale=${locale%%/*}
|
|
mkdir -p "$l10n_dir/localization/$locale"
|
|
cp -L "$path" "$l10n_dir/localization/$locale/$(basename "$path")"
|
|
done
|
|
pushd "$l10n_dir" > /dev/null
|
|
for omni_file in "${omni_files[@]}"; do
|
|
zip -qrX "$omni_file" localization
|
|
done
|
|
popd > /dev/null
|
|
rm -rf "$l10n_dir"
|
|
fi
|
|
|
|
# Copy test files to tests/ directories
|
|
if [ ${#test_copy_files[@]} -gt 0 ]; then
|
|
for path in "${test_copy_files[@]}"; do
|
|
for omni_file in "${omni_files[@]}"; do
|
|
dest="${omni_file%/app/omni.ja}/tests/${path#test/tests/}"
|
|
mkdir -p "$(dirname "$dest")"
|
|
cp -L "$path" "$dest"
|
|
done
|
|
done
|
|
fi
|
|
|
|
# Bump BuildID so that startup caches are invalidated even without
|
|
# -purgecaches
|
|
build_id=$(date +%Y%m%d%H%M%S)
|
|
for omni_file in "${omni_files[@]}"; do
|
|
perl -pi -e "s/^BuildID=.*/BuildID=$build_id/" "$(dirname "$omni_file")/application.ini"
|
|
done
|
|
|
|
echo "Updated staged build with ${#zip_files[@]} changed file(s)"
|
|
elif [ -n "$changes" ]; then
|
|
echo "No staged files changed"
|
|
else
|
|
echo "No changes since last build"
|
|
fi
|
|
|
|
{
|
|
echo "#format=2"
|
|
echo "#include_tests=$manifest_tests"
|
|
echo "#devtools=$manifest_devtools"
|
|
echo "#app_hash=$app_hash"
|
|
LC_ALL=C sort -t$'\t' -k3 "$carried" "$cand_entries"
|
|
} > "$MANIFEST_FILE.tmp"
|
|
mv "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
|