Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/build_pip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
name: Build PIP packages

on:
release:
types: [created, edited]

workflow_run:
workflows: ["Make release"]
types: [completed]
workflow_dispatch:

jobs:
deploy_source:
runs-on: 'macos-latest'
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'

steps:
- uses: actions/checkout@v2
Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/make_installers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Build Installers

on:
workflow_run:
workflows: ["Build PIP packages"]
types: [completed]
workflow_dispatch:

permissions:
contents: write

jobs:
windows:
runs-on: windows-latest
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'

steps:
- uses: actions/checkout@v4

- name: Set up uv
uses: astral-sh/setup-uv@v5

- name: Build bundle
shell: cmd
run: installers\install-python-microscopy.bat bundle

- name: Get installed version
id: version
shell: pwsh
run: |
$v = & "bundle\venv\Scripts\python.exe" `
-c "import importlib.metadata; print(importlib.metadata.version('python-microscopy'))"
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append

- name: Build installer
shell: pwsh
run: |
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" `
/DAppSourceDir="$env:GITHUB_WORKSPACE\bundle" `
/DAppVersion="${{ steps.version.outputs.version }}" `
installers\windows\pyme_setup.iss

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: PYME-${{ steps.version.outputs.version }}-windows-installer
path: installers\windows\Output\PYME-*-setup.exe

- name: Attach to release
if: github.event_name == 'workflow_run'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
files: installers/windows/Output/PYME-*-setup.exe
24 changes: 24 additions & 0 deletions installers/INSTALLERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Single-click / non expert installers
This folder contains the code to create installers for windows and mac.


## Commonalities
- A script (or potentiall similar bash/powershell/bat scritps) to construct a self contained folder with python and PYME.
- the script should use uv to create an enviroment and install the current pip PYME package in it, relying on that package to determine any additional dependencies
- the script should accept a parameter for the output folder, defaulting to creating a `PYME` directory in the users home directory.
- download (curl) uv if not already installed.
- The script should idealy create executable top level aliases to PYMEAcquire, PYMEImage, PYMEVis and PYMEClusterOfOne (as defined in the pyproject.toml scripts section). The script should also create a alias activating venv for console use.
- Python version is defined by an easily editable TARGET_PYTHON, with an initial target of 3.13 - potentially in installer_defines.yaml or a an alternative readily (minimal external deps) cross-platform format. We ahould also put the list of entry points to expose in here (which are expected to be a subset of the entrypoints defined in pyproject.toml).
- this script acts as the default installer while we build the more native windows and mac options, and remains the installation script for linux.


## Windows
- Use InnoSetup to create an executable installer, from the bundle created by the common script
- Start menu group PYME, with shortcuts to the top level entry points as described above, and a `PYME Console` shortcut to create a shell with the venv active.
- File associations to be revisited after basics working.
- Ideally install for me only and all users options.
- This will eventually run in a github action on windows-latest (which should already have inno setup available).

## Mac
- Ultimate goal is an app package, derived from bundle, with appropriate entry points.
- Parked for now pending to a bit more research, but avoid things in script structure which will bite us on that later.
82 changes: 82 additions & 0 deletions installers/install-python-microscopy.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@echo off
setlocal EnableDelayedExpansion

:: ---- Config (mirrors installer_defines.sh — keep in sync) ----
set "TARGET_PYTHON=3.13"
set "PACKAGE_NAME=python-microscopy"
set "ENTRY_POINTS=PYMEAcquire PYMEImage PYMEVis PYMEClusterOfOne"
set "DEFAULT_DEST=%USERPROFILE%\PYME"

:: ---- Destination (first positional arg, else default) ----
if not "%~1"=="" (set "DEST=%~1") else (set "DEST=%DEFAULT_DEST%")
echo Installing PYME to: !DEST!
if not exist "!DEST!\" mkdir "!DEST!"

:: ---- Locate or download uv ----
:: Context A (CI): uv is in PATH via astral-sh/setup-uv action — the where check passes immediately.
:: Context B (end-user): uv.exe is downloaded to DEST\bin\ and used from there.
where uv >nul 2>&1
if not errorlevel 1 (
set "UV=uv"
) else (
call :download_uv
if errorlevel 1 exit /b 1
)

:: ---- Managed Python + virtual environment ----
echo Installing Python !TARGET_PYTHON!...
"!UV!" python install !TARGET_PYTHON!
if errorlevel 1 (echo ERROR: uv python install failed & exit /b 1)

echo Creating virtual environment...
"!UV!" venv --python !TARGET_PYTHON! "!DEST!\venv"
if errorlevel 1 (echo ERROR: venv creation failed & exit /b 1)

:: ---- Install PYME from pip ----
echo Installing !PACKAGE_NAME!...
"!UV!" pip install --python "!DEST!\venv\Scripts\python.exe" !PACKAGE_NAME!
if errorlevel 1 (echo ERROR: package installation failed & exit /b 1)

:: ---- Entry point .cmd wrappers ----
for %%e in (%ENTRY_POINTS%) do call :mk_wrapper "%%e"

:: ---- Activated-console helper ----
(
echo @echo off
echo start cmd.exe /k "%%~dp0venv\Scripts\activate.bat"
) > "!DEST!\pyme-console.cmd"

echo.
echo Installation complete.
echo Wrappers: %ENTRY_POINTS%
echo Add !DEST! to your PATH, or run the .cmd files directly.
echo Activated console: !DEST!\pyme-console.cmd
goto :eof


:: ----------------------------------------------------------------
:download_uv
:: Downloads uv.exe (x86-64) into DEST\bin\ via curl + PowerShell.
:: Requires Windows 10 1803+ (curl.exe and tar.exe built-in).
if not exist "!DEST!\bin\" mkdir "!DEST!\bin"
set "UV_ZIP=%TEMP%\uv_download.zip"
echo Downloading uv (x86-64)...
curl -fsSLo "%UV_ZIP%" "https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip"
if errorlevel 1 (echo ERROR: Failed to download uv & exit /b 1)
powershell -NoProfile -Command "Expand-Archive -LiteralPath '%UV_ZIP%' -DestinationPath '!DEST!\bin' -Force"
if errorlevel 1 (echo ERROR: Failed to extract uv & exit /b 1)
del "%UV_ZIP%"
set "UV=!DEST!\bin\uv.exe"
exit /b 0


:: ----------------------------------------------------------------
:mk_wrapper
:: Writes a thin .cmd shim that forwards all arguments to the venv entry point exe.
:: Uses %~dp0 so wrappers work even if the install folder is moved.
set "_EP=%~1"
(
echo @echo off
echo "%%~dp0venv\Scripts\!_EP!.exe" %%*
) > "!DEST!\!_EP!.cmd"
exit /b 0
74 changes: 74 additions & 0 deletions installers/install-python-microscopy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Linux/Mac installer for PYME. Also serves as the Mac installer pending a native .app.
# Usage: install-python-microscopy.sh [--dest <path>] (default: ~/PYME)
#
# Context A (CI/dev): uv is expected to be in PATH already.
# Context B (end-user): uv is bootstrapped via astral.sh if not found.
set -euo pipefail

# --- Configuration ---
TARGET_PYTHON=3.13
PACKAGE_NAME=python-microscopy
ENTRY_POINTS=(PYMEAcquire PYMEImage PYMEVis PYMEClusterOfOne)
DEFAULT_DEST="$HOME/PYME"

DEST="$DEFAULT_DEST"

while [[ $# -gt 0 ]]; do
case "$1" in
--dest) DEST="$2"; shift 2 ;;
--dest=*) DEST="${1#--dest=}"; shift ;;
-h|--help)
echo "Usage: $0 [--dest <path>]"
echo " Installs PYME into a self-contained directory using uv."
echo " Default: $DEFAULT_DEST"
exit 0 ;;
*) echo "Unknown argument: $1 (run '$0 --help' for usage)" >&2; exit 1 ;;
esac
done

DEST="${DEST/#\~/$HOME}"
echo "==> Installing PYME to: $DEST"
mkdir -p "$DEST"

# --- Ensure uv is available ---
if ! command -v uv &>/dev/null; then
echo "==> uv not found; downloading via astral.sh..."
curl -LsSf https://astral.sh/uv/install.sh | sh
# Add the two common install locations; one will contain the binary.
export PATH="$HOME/.local/bin:${CARGO_HOME:-$HOME/.cargo}/bin:$PATH"
if ! command -v uv &>/dev/null; then
echo "ERROR: uv installation failed or installed to an unexpected location." >&2
exit 1
fi
fi

# --- Managed Python ---
uv python install "$TARGET_PYTHON"

# --- Virtual environment ---
uv venv --python "$TARGET_PYTHON" "$DEST/venv"

# --- Install PYME from pip ---
echo "==> Installing $PACKAGE_NAME..."
uv pip install --python "$DEST/venv/bin/python" "$PACKAGE_NAME"

# --- Top-level entry point symlinks ---
for ep in "${ENTRY_POINTS[@]}"; do
ln -sf "$DEST/venv/bin/$ep" "$DEST/$ep"
done

# --- Activated-shell helper ---
cat > "$DEST/pyme-shell" <<'SHELL_SCRIPT'
#!/usr/bin/env bash
PYME_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$PYME_DIR/venv/bin/activate"
exec "${SHELL:-bash}"
SHELL_SCRIPT
chmod +x "$DEST/pyme-shell"

echo ""
echo "==> Done."
echo " Entry points: ${ENTRY_POINTS[*]}"
echo " Add to PATH: export PATH=\"$DEST:\$PATH\""
echo " Activated shell: $DEST/pyme-shell"
47 changes: 47 additions & 0 deletions installers/windows/pyme_setup.iss
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
; PYME Windows Installer — produced by InnoSetup on windows-latest.
;
; Typical CI invocation (after running install.bat to create the bundle):
; set VERSION=<output of: python -c "import importlib.metadata; print(importlib.metadata.version('python-microscopy'))">
; ISCC.exe /DAppSourceDir=C:\path\to\bundle /DAppVersion=%VERSION% pyme_setup.iss

#ifndef AppSourceDir
#define AppSourceDir "..\..\PYME_bundle"
#endif
#ifndef AppVersion
#define AppVersion "0.0.0"
#endif

#define AppName "PYME"
#define AppPublisher "Baddeley Lab, University of Auckland"
; Icons ship with the package inside the venv tree.
; pymeLogo.png has no .ico equivalent — pmanal.ico is used in its place.
#define IconsDir "{app}\venv\Lib\site-packages\PYME\resources\icons"

[Setup]
; AppId uniquely identifies this application for upgrades and uninstall — do not change.
AppId={{8F3A2E1D-6B4C-4D9F-A7E2-3C1B5F8A2D6E}
AppName={#AppName}
AppVersion={#AppVersion}
AppPublisher={#AppPublisher}
DefaultDirName={autopf}\{#AppName}
DefaultGroupName={#AppName}
; Per-user install by default; elevation dialog allows all-users install.
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputBaseFilename=PYME-{#AppVersion}-setup
Compression=lzma2
SolidCompression=yes
WizardStyle=modern
; Avoid prompting to close running apps — PYME processes are independent.
CloseApplications=no

[Files]
Source: "{#AppSourceDir}\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs

[Icons]
Name: "{group}\PYMEAcquire"; Filename: "{app}\PYMEAcquire.cmd"; IconFilename: "{#IconsDir}\pmacquire.ico"
Name: "{group}\PYMEImage"; Filename: "{app}\PYMEImage.cmd"; IconFilename: "{#IconsDir}\pmanal.ico"
Name: "{group}\PYMEVis"; Filename: "{app}\PYMEVis.cmd"; IconFilename: "{#IconsDir}\pmvis.ico"
Name: "{group}\PYMEClusterOfOne"; Filename: "{app}\PYMEClusterOfOne.cmd"; IconFilename: "{#IconsDir}\pmanal.ico"
Name: "{group}\PYME Console"; Filename: "{app}\pyme-console.cmd"; IconFilename: "{#IconsDir}\pmanal.ico"
Name: "{group}\Uninstall PYME"; Filename: "{uninstallexe}"
Loading