From adf31979504bd12170194b83986518f3ef74a739 Mon Sep 17 00:00:00 2001 From: Joshua Blake Smith Date: Fri, 26 Jun 2026 12:53:43 -0600 Subject: [PATCH] docs: add developer build workflow --- .env.local.example | 14 +++++++ .gitignore | 12 ++++++ README.md | 33 +++++++++++++++ requirements-build.txt | 2 + requirements.txt | 6 +++ scripts/_common.sh | 81 +++++++++++++++++++++++++++++++++++++ scripts/build-macos.sh | 42 +++++++++++++++++++ scripts/run-source.sh | 9 +++++ scripts/setup-dev.sh | 21 ++++++++++ scripts/smoke-test-macos.sh | 38 +++++++++++++++++ 10 files changed, 258 insertions(+) create mode 100644 .env.local.example create mode 100644 requirements-build.txt create mode 100644 requirements.txt create mode 100644 scripts/_common.sh create mode 100755 scripts/build-macos.sh create mode 100755 scripts/run-source.sh create mode 100755 scripts/setup-dev.sh create mode 100755 scripts/smoke-test-macos.sh diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..ca199d5 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,14 @@ +# Copy this file to .env.local before running the scripts. + +# scripts +MARMAT_APP_NAME=MaRMAT +MARMAT_PYTHON=python3 +MARMAT_VENV_DIR=.venv +MARMAT_SRC_SUBDIR=src +MARMAT_RUNTIME_REQUIREMENTS=requirements.txt +MARMAT_BUILD_REQUIREMENTS=requirements-build.txt +MARMAT_BUILD_DIR=build-local +MARMAT_RELEASE_DIR=release +MARMAT_ZIP_NAME=MaRMAT-local-macOS.zip +MARMAT_QT_PLATFORM=offscreen +MARMAT_SMOKE_TIMEOUT_SECONDS=5 diff --git a/.gitignore b/.gitignore index c4166a9..f16c3f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ __pycache__/ +.venv/ +.env.local +build-local/ +release/ MaRMAT.spec build/MaRMAT/Analysis-00.toc build/MaRMAT/base_library.zip @@ -31,3 +35,11 @@ build/MaRMAT-min/localpycs/pyimod03_ctypes.pyc build/MaRMAT-min/localpycs/pyimod04_pywin32.pyc build/MaRMAT-min/localpycs/struct.pyc dist/MaRMAT-min.exe + +# Local developer notes +SETUP_NOTES.md +local-docs/ +local-notes/ +private-docs/ +notes-local/ +*.local.md diff --git a/README.md b/README.md index e0f6355..585eec4 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ The Marriott Reparative Metadata Assessment Tool (MaRMAT) is an open-source appl 3.2 [Windows Users](#32-windows-users) + 3.3 [Developer Builds](#33-developer-builds) + 4. [Tips](#4-tips) 5. [Credits and Acknowledgments](#5-credits-and-acknowledgments) @@ -129,6 +131,37 @@ Option 3: 4. If the necesssary python libraries haven't been installed yet, double-click to `install-dependencies.bat` file. 5. Open MaRMAT by double-clicking the `run-marmat.bat` file. +### 3.3 Developer Builds + +Developers who want to run or package MaRMAT from source can use the scripts in `scripts/`. + +First, create local script settings and install the development dependencies: + +```bash +cp .env.local.example .env.local +./scripts/setup-dev.sh +``` + +To run MaRMAT directly from source: + +```bash +./scripts/run-source.sh +``` + +To build a local macOS app bundle and release zip: + +```bash +./scripts/build-macos.sh +``` + +The macOS build output is written to `build-local/dist/MaRMAT.app`, and the zip archive is written to `release/MaRMAT-local-macOS.zip`. To smoke test the packaged app: + +```bash +./scripts/smoke-test-macos.sh +``` + +These scripts are intended for local developer builds. Public release distribution may still require project-specific assets, signing, notarization, and platform-specific packaging checks. + ## 4. Tips - Ensure metadata files can be in a TSV or CSV format. - Ensure the lexicon files are in a CSV format. diff --git a/requirements-build.txt b/requirements-build.txt new file mode 100644 index 0000000..5fd6177 --- /dev/null +++ b/requirements-build.txt @@ -0,0 +1,2 @@ +-r requirements.txt +pyinstaller diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0916b5a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +pandas +PyQt6 +matplotlib +wordcloud +beautifulsoup4 +lxml diff --git a/scripts/_common.sh b/scripts/_common.sh new file mode 100644 index 0000000..f07e475 --- /dev/null +++ b/scripts/_common.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -euo pipefail + +MARMAT_PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MARMAT_ENV_FILE="$MARMAT_PROJECT_ROOT/.env.local" + +if [ ! -f "$MARMAT_ENV_FILE" ]; then + echo "Missing .env.local. Copy .env.local.example to .env.local before running this script." >&2 + exit 1 +fi + +set -a +source "$MARMAT_ENV_FILE" +set +a + +require_env() { + for name in "$@"; do + if [ "${!name+x}" != "x" ]; then + echo "Missing required setting: $name" >&2 + exit 1 + fi + + if [ -z "${!name}" ]; then + echo "Empty required setting: $name" >&2 + exit 1 + fi + done +} + +project_path() { + case "$1" in + /*) + printf '%s\n' "$1" + ;; + *) + printf '%s/%s\n' "$MARMAT_PROJECT_ROOT" "$1" + ;; + esac +} + +require_file() { + for path in "$@"; do + if [ ! -f "$path" ]; then + echo "Missing required file: $path" >&2 + exit 1 + fi + done +} + +require_dir() { + for path in "$@"; do + if [ ! -d "$path" ]; then + echo "Missing required directory: $path" >&2 + exit 1 + fi + done +} + +require_env \ + MARMAT_APP_NAME \ + MARMAT_PYTHON \ + MARMAT_VENV_DIR \ + MARMAT_SRC_SUBDIR \ + MARMAT_RUNTIME_REQUIREMENTS \ + MARMAT_BUILD_REQUIREMENTS \ + MARMAT_BUILD_DIR \ + MARMAT_RELEASE_DIR \ + MARMAT_ZIP_NAME \ + MARMAT_QT_PLATFORM \ + MARMAT_SMOKE_TIMEOUT_SECONDS + +MARMAT_VENV_PATH="$(project_path "$MARMAT_VENV_DIR")" +MARMAT_SRC_PATH="$(project_path "$MARMAT_SRC_SUBDIR")" +MARMAT_RUNTIME_REQUIREMENTS_PATH="$(project_path "$MARMAT_RUNTIME_REQUIREMENTS")" +MARMAT_BUILD_REQUIREMENTS_PATH="$(project_path "$MARMAT_BUILD_REQUIREMENTS")" +MARMAT_BUILD_PATH="$(project_path "$MARMAT_BUILD_DIR")" +MARMAT_RELEASE_PATH="$(project_path "$MARMAT_RELEASE_DIR")" +MARMAT_PYTHON_BIN="$MARMAT_VENV_PATH/bin/python" +MARMAT_PYINSTALLER_BIN="$MARMAT_VENV_PATH/bin/pyinstaller" +MARMAT_APP_BUNDLE_PATH="$MARMAT_BUILD_PATH/dist/$MARMAT_APP_NAME.app" +MARMAT_RELEASE_ZIP_PATH="$MARMAT_RELEASE_PATH/$MARMAT_ZIP_NAME" diff --git a/scripts/build-macos.sh b/scripts/build-macos.sh new file mode 100755 index 0000000..e9e5d5a --- /dev/null +++ b/scripts/build-macos.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +if [ "$(uname -s)" != "Darwin" ]; then + echo "This script builds the macOS app bundle and must run on macOS." >&2 + exit 1 +fi + +require_file "$MARMAT_PYTHON_BIN" "$MARMAT_PYINSTALLER_BIN" "$MARMAT_SRC_PATH/main.py" +require_dir "$MARMAT_SRC_PATH/data" + +rm -rf "$MARMAT_BUILD_PATH" +mkdir -p "$MARMAT_BUILD_PATH" "$MARMAT_RELEASE_PATH" + +"$MARMAT_PYTHON_BIN" -m compileall \ + "$MARMAT_SRC_PATH/main.py" \ + "$MARMAT_SRC_PATH/controllers" \ + "$MARMAT_SRC_PATH/models" \ + "$MARMAT_SRC_PATH/views" + +cd "$MARMAT_SRC_PATH" +"$MARMAT_PYINSTALLER_BIN" \ + --noconfirm \ + --clean \ + --onedir \ + --windowed \ + --name "$MARMAT_APP_NAME" \ + --distpath "$MARMAT_BUILD_PATH/dist" \ + --workpath "$MARMAT_BUILD_PATH/work" \ + --specpath "$MARMAT_BUILD_PATH/spec" \ + --add-data "$MARMAT_SRC_PATH/data:data" \ + main.py + +require_dir "$MARMAT_APP_BUNDLE_PATH" + +rm -f "$MARMAT_RELEASE_ZIP_PATH" +ditto -c -k --sequesterRsrc --keepParent "$MARMAT_APP_BUNDLE_PATH" "$MARMAT_RELEASE_ZIP_PATH" + +echo "Built app bundle: $MARMAT_APP_BUNDLE_PATH" +echo "Built release zip: $MARMAT_RELEASE_ZIP_PATH" diff --git a/scripts/run-source.sh b/scripts/run-source.sh new file mode 100755 index 0000000..edd9ba3 --- /dev/null +++ b/scripts/run-source.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +require_file "$MARMAT_PYTHON_BIN" "$MARMAT_SRC_PATH/main.py" + +cd "$MARMAT_SRC_PATH" +exec "$MARMAT_PYTHON_BIN" main.py diff --git a/scripts/setup-dev.sh b/scripts/setup-dev.sh new file mode 100755 index 0000000..61c3f42 --- /dev/null +++ b/scripts/setup-dev.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +require_dir "$MARMAT_SRC_PATH" +require_file "$MARMAT_BUILD_REQUIREMENTS_PATH" "$MARMAT_SRC_PATH/main.py" + +if [ ! -d "$MARMAT_VENV_PATH" ]; then + "$MARMAT_PYTHON" -m venv "$MARMAT_VENV_PATH" +fi + +"$MARMAT_PYTHON_BIN" -m pip install --upgrade pip +"$MARMAT_PYTHON_BIN" -m pip install -r "$MARMAT_BUILD_REQUIREMENTS_PATH" +"$MARMAT_PYTHON_BIN" -m compileall \ + "$MARMAT_SRC_PATH/main.py" \ + "$MARMAT_SRC_PATH/controllers" \ + "$MARMAT_SRC_PATH/models" \ + "$MARMAT_SRC_PATH/views" + +echo "MaRMAT developer environment is ready." diff --git a/scripts/smoke-test-macos.sh b/scripts/smoke-test-macos.sh new file mode 100755 index 0000000..7f40201 --- /dev/null +++ b/scripts/smoke-test-macos.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_common.sh" + +require_file "$MARMAT_APP_BUNDLE_PATH/Contents/MacOS/$MARMAT_APP_NAME" + +QT_QPA_PLATFORM="$MARMAT_QT_PLATFORM" "$MARMAT_PYTHON_BIN" - "$MARMAT_APP_BUNDLE_PATH/Contents/MacOS/$MARMAT_APP_NAME" "$MARMAT_SMOKE_TIMEOUT_SECONDS" <<'PY' +import os +import subprocess +import sys + +app_path = sys.argv[1] +timeout_seconds = int(sys.argv[2]) + +try: + result = subprocess.run( + [app_path], + env=os.environ.copy(), + text=True, + capture_output=True, + timeout=timeout_seconds, + ) +except subprocess.TimeoutExpired: + print(f"Packaged app started and stayed running for {timeout_seconds} seconds.") + raise SystemExit(0) + +if result.returncode == 0: + print("Packaged app exited cleanly before the smoke-test timeout.") + raise SystemExit(0) + +if result.stdout: + print(result.stdout) +if result.stderr: + print(result.stderr, file=sys.stderr) +print(f"Packaged app smoke test failed with exit code {result.returncode}.", file=sys.stderr) +raise SystemExit(result.returncode) +PY