Skip to content
Open
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
116 changes: 116 additions & 0 deletions .github/workflows/build_installers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Build Installers

on:
workflow_dispatch:
inputs:
ref:
description: "Git ref to build from (tag or branch, e.g., v2026.2.18)"
required: false
type: string
channels:
description: "Conda channels (space-separated, e.g., 'conda-forge royerlab')"
required: false
default: "conda-forge"
type: string
pull_request:
branches: [main]
paths:
- "packaging/**"
- ".github/workflows/build_installers.yml"

jobs:
build:
name: Build (${{ matrix.platform }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux-64
- os: macos-14
platform: osx-arm64
- os: windows-latest
platform: win-64

steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.sha }}

- name: Set up Miniconda
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-version: latest
activate-environment: aydin-build-installer
environment-file: packaging/environments/build_installer.yml
auto-activate-base: false

- name: Generate icons
shell: bash -l {0}
run: python packaging/scripts/generate_icons.py

- name: Build installer (dry run on PR)
if: github.event_name == 'pull_request'
shell: bash -l {0}
run: |
python packaging/build_installer.py \
--output-dir _work \
--dry-run

- name: Build installer
if: github.event_name != 'pull_request'
shell: bash -l {0}
run: |
CHANNELS="${{ inputs.channels || 'conda-forge' }}"
python packaging/build_installer.py \
--output-dir _work \
--channels $CHANNELS

- name: Smoke test installer (Linux)
if: matrix.platform == 'linux-64' && github.event_name != 'pull_request'
shell: bash -l {0}
run: |
INSTALLER=$(ls _work/AydinStudio-*.sh 2>/dev/null | head -1)
if [ -n "$INSTALLER" ]; then
echo "Testing installer: $INSTALLER"
bash "$INSTALLER" -b -p /tmp/aydin-test
/tmp/aydin-test/bin/aydin --version
echo "Smoke test passed!"
fi

- name: Upload installer artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: installer-${{ matrix.platform }}
path: |
_work/AydinStudio-*
if-no-files-found: warn
retention-days: 30

attach-to-release:
name: Attach to GitHub Release
needs: build
if: github.event_name == 'workflow_dispatch' && startsWith(inputs.ref, 'v')
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with:
path: installers/
merge-multiple: true

- name: Upload installers to release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
TAG="${{ inputs.ref }}"
echo "Uploading installers to release $TAG"
ls -la installers/
gh release upload "$TAG" installers/AydinStudio-* --clobber \
--repo "${{ github.repository }}" || echo "Warning: upload failed (release may not exist yet)"
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,14 @@ demo_results/
.zgroup
*.png
!docs/source/resources/**/*.png
!packaging/icons/*.png
!packaging/icons/*.ico
!packaging/icons/*.icns

src/aydin/_version.py

# Temporary working directories
delme/

# Conda-constructor build artifacts
_work/
21 changes: 20 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help setup install install-dev test test-cov test-cov-check test-heavy test-gpu test-unstable test-gui check format format-check lint validate clean build publish publish-patch docs docs-screenshots docs-build docs-publish docker-build docker-build-cli docker-build-gpu docker-build-studio docker-run-studio docker-test docker-test-all
.PHONY: help setup install install-dev test test-cov test-cov-check test-heavy test-gpu test-unstable test-gui check format format-check lint validate clean build publish publish-patch docs docs-screenshots docs-build docs-publish docker-build docker-build-cli docker-build-gpu docker-build-studio docker-run-studio docker-test docker-test-all installer installer-icons installer-env installer-clean

help:
@echo "Available commands:"
Expand Down Expand Up @@ -31,6 +31,12 @@ help:
@echo " make docker-test - Build + smoke test CLI image"
@echo " make docker-test-all - Build + smoke test CLI + Studio images"
@echo ""
@echo "Conda Packaging:"
@echo " make installer - Build native installer for current platform"
@echo " make installer-icons - Generate .ico and .icns from source PNG"
@echo " make installer-env - Create conda env with build tools"
@echo " make installer-clean - Clean installer build artifacts"
@echo ""
@echo "Documentation:"
@echo " make docs - Build HTML docs (regenerates screenshots first)"
@echo " make docs-screenshots - Regenerate napari plugin screenshots only"
Expand Down Expand Up @@ -177,6 +183,19 @@ docker-test-all:
@command -v docker >/dev/null 2>&1 || { echo "Error: Docker is not installed."; exit 1; }
./docker/test-smoke.sh --all

# Conda packaging (native installers via conda-constructor)
installer: installer-icons
python packaging/build_installer.py --output-dir _work

installer-icons:
python packaging/scripts/generate_icons.py

installer-env:
conda env create -f packaging/environments/build_installer.yml --force

installer-clean:
rm -rf _work/

publish-patch: validate
@echo "Current version: $(CURRENT_VERSION)"
@command -v gh >/dev/null 2>&1 || { echo "Error: GitHub CLI (gh) is required. Install from https://cli.github.com"; exit 1; }
Expand Down
19 changes: 19 additions & 0 deletions packaging/LICENSE.rtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{\rtf1\ansi\ansicpg1252\deff0
{\fonttbl{\f0\fswiss Helvetica;}{\f1\fmodern Courier New;}}
{\colortbl;\red0\green0\blue0;}
\f0\fs24
\b BSD 3-Clause License\b0\par
\par
Copyright (c) 2022, Aydin\par
All rights reserved.\par
\par
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\par
\par
\bullet Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\par
\par
\bullet Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\par
\par
\bullet Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\par
\par
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\par
}
144 changes: 144 additions & 0 deletions packaging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Conda-Constructor Packaging

This directory contains everything needed to build native OS installers for **Aydin Studio** using [conda-constructor](https://github.com/conda/constructor).

| Platform | Format | Output |
|----------|--------|--------|
| Linux x86_64 | `.sh` | Shell script installer |
| macOS ARM64 | `.pkg` | Native macOS GUI installer |
| Windows x64 | `.exe` | NSIS-based GUI installer |

## Architecture

The installer creates a single conda environment containing Python, Aydin, and all dependencies. A desktop shortcut ("Aydin Studio") is created via [menuinst](https://github.com/conda/menuinst) v2.

```
packaging/
build_installer.py # Generates construct.yaml and invokes constructor
LICENSE.rtf # License for installer UI (macOS/Windows)
conda-recipe/
aydin/recipe.yaml # Conda recipe for the aydin package
aydin-menu/recipe.yaml # Conda recipe for the desktop shortcut package
menuinst/
aydin-studio.json # menuinst v2 JSON (cross-platform shortcuts)
icons/
aydin_icon.png # Linux icon (295x295)
aydin_icon.ico # Windows icon (multi-size)
aydin_icon.icns # macOS icon
scripts/
generate_icons.py # Converts source PNG to ICO/ICNS
post_install/
post_install.sh # Unix post-install script
post_install.bat # Windows post-install script
environments/
build_installer.yml # Conda env for building installers
```

## Prerequisites

1. **conda or mamba** installed (e.g., via [Miniforge](https://github.com/conda-forge/miniforge))
2. The `aydin` and `aydin-menu` packages must be available on a conda channel (conda-forge or a custom channel like `anaconda.org/royerlab`)

## Quick Start

```bash
# 1. Create the build environment (one-time setup)
make installer-env

# 2. Activate the environment
conda activate aydin-build-installer

# 3. Build the installer for your platform
make installer

# Output appears in _work/
ls _work/AydinStudio-*
```

## Dry Run

Generate `construct.yaml` without building:

```bash
python packaging/build_installer.py --dry-run
```

## Custom Channels

To include a custom channel (e.g., during bootstrap before conda-forge has the package):

```bash
python packaging/build_installer.py --channels conda-forge royerlab
```

## GPU Support

The installer ships **CPU-only PyTorch** to keep the download size reasonable. Users who need GPU acceleration can add CUDA support after installation:

```bash
# Activate the installed environment
conda activate /path/to/aydin

# Add CUDA support
conda install pytorch-cuda -c conda-forge
```

## Icon Generation

Icons are pre-committed to `packaging/icons/`. To regenerate from the source PNG:

```bash
# Requires Pillow: pip install Pillow
make installer-icons
```

On macOS, the script uses `iconutil` (from Xcode Command Line Tools) for best `.icns` quality. On other platforms, it falls back to Pillow's built-in `.icns` support.

## CI/CD

The GitHub Actions workflow (`.github/workflows/build_installers.yml`) builds installers for all 3 platforms.

- **On PRs** touching `packaging/`: runs a **dry-run** only (validates construct.yaml generation)
- **On `workflow_dispatch`**: builds real installers and uploads as artifacts
- **With a tag ref** (e.g., `v2026.2.18`): also attaches installers to the GitHub Release

### Manual Trigger

Go to Actions > "Build Installers" > Run workflow, and provide:
- **ref**: git tag or branch (e.g., `v2026.2.18`)
- **channels**: conda channels (default: `conda-forge`)

## Code Signing (Future)

Code signing is not yet enabled but the infrastructure is ready. When certificates are available:

### macOS

Set these GitHub Actions secrets:
- `CONSTRUCTOR_SIGNING_IDENTITY`: Developer ID Installer certificate name
- `CONSTRUCTOR_NOTARIZATION_IDENTITY`: Apple ID for notarization

The `build_installer.py` script reads these from environment variables.

### Windows

Set this secret:
- `CONSTRUCTOR_SIGNING_CERTIFICATE`: Path to PFX certificate file

Without code signing, installers work but users see OS security warnings (Gatekeeper on macOS, SmartScreen on Windows).

## Troubleshooting

### "constructor not found"

Activate the build environment: `conda activate aydin-build-installer`

### Solver conflicts

If conda's solver takes too long or fails:
1. Ensure `conda-libmamba-solver` is installed (included in `build_installer.yml`)
2. Try pinning fewer packages — `build_installer.py` lists explicit deps for solver reliability, but you can remove some if they cause conflicts

### Icon generation fails

Ensure Pillow is installed: `pip install Pillow`. For best `.icns` quality on macOS, install Xcode Command Line Tools: `xcode-select --install`.
Loading
Loading