Skip to content
Draft
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
36 changes: 26 additions & 10 deletions .github/skills/build-plugin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ description: Build and install Python plugins using plugin_builder.py. Use when

Use `plugin_builder.py` from repo root. Requires venv activation first.

By default, plugins are installed in **editable mode** (`pip install -e`), so
Python source edits take effect without rebuilding. Pass `--wheel` to use the
legacy build-wheel-then-install flow.

## First-time setup

```bash
Expand All @@ -22,13 +26,13 @@ npm install
## Commands

```bash
# First install of a plugin (REQUIRED before using --reinstall)
# First install of a plugin (editable)
python tools/plugin_builder.py --install ui

# First install with JS assets (for plugins with JS components)
python tools/plugin_builder.py --js --install python-remote-file-source

# Reinstall a plugin (use when code changed but version not bumped)
# Reinstall a plugin (use when JS bundles or entry points need re-linking)
python tools/plugin_builder.py --reinstall ui

# Build multiple plugins
Expand All @@ -39,21 +43,33 @@ python tools/plugin_builder.py --js --reinstall ui

# Build, install, and start server
python tools/plugin_builder.py --install --server ui

# Legacy wheel-based flow (build wheels and install them)
python tools/plugin_builder.py --wheel --reinstall ui
```

## Common flags

- `--install` / `-i`: Install plugin (REQUIRED on first run)
- `--reinstall` / `-r`: Force reinstall (no version bump needed, only after --install)
- `--js` / `-j`: Also build JS assets (additive - Python build still happens)
- `--install` / `-i`: Install plugin (editable by default)
- `--reinstall` / `-r`: Force reinstall (`--force-reinstall --no-deps`); useful
to refresh JS bundles or re-link entry points
- `--js` / `-j`: Also build JS assets (additive - Python install still happens)
- `--server` / `-s`: Start deephaven-server after build
- `--docs` / `-d`: Build documentation
- `--server-arg` / `-sa`: Pass argument to deephaven server (e.g., `-sa --port=9999`)
- `--wheel`: Opt out of editable mode and use the legacy build-wheel + install path
- `--build` / `-b`: Build wheels (only meaningful with `--wheel`; ignored otherwise)

## Notes

- **Must use `--install` on first run** - `--reinstall` will fail if plugin not already installed
- Assumes Python `.venv` is already sourced (`source .venv/bin/activate`)
- Use `--reinstall` during development when version hasn't changed
- Running with no flags defaults to `--js --install` for all plugins
- First run may take longer to install dependencies
- Editable installs mean Python source changes take effect on the next server
restart without rerunning the builder.
- Use `--reinstall` when you've changed JS bundles, entry points, or package
metadata that needs re-linking.
- JS assets are still bundled into `src/deephaven/<pkg>/_js/` during install
(driven by each plugin's `setup.py`); rerun the install when JS changes.
- Assumes Python `.venv` is already sourced (`source .venv/bin/activate`).
- Running with no flags defaults to `--js --install` for all plugins.
- First run may take longer to install dependencies.
- `--wheel` is required if you specifically need a wheel artifact (e.g. to
inspect or distribute it); CI builds wheels independently of this script.
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pre-commit==3.3.3
build
pip
setuptools
# setuptools>=64 required for PEP 660 editable installs of namespace packages
# used by `pip install -e` in tools/plugin_builder.py
setuptools>=64
tox

# Plugin builder dependencies
Expand Down
72 changes: 67 additions & 5 deletions tools/plugin_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,39 @@ def install_with_all_extras(
run_command(f'find {wheels} | xargs -I {{}} {install} "{{}}[all]"')


def run_install_editable(
plugins: tuple[str],
reinstall: bool,
) -> None:
"""
Install plugins in editable mode (pip install -e) for plugins that have a setup.cfg.
Editable installs let Python source edits take effect without rebuilding wheels.

Args:
plugins: The plugins to install. If empty, all plugins with a setup.cfg are installed.
reinstall: Whether to reinstall the plugins.
If True, the --force-reinstall and --no-deps flags are added to pip install.
Useful when JS bundles or entry points need to be re-linked.

Returns:
None
"""
install = "pip install"
if reinstall:
install += " --force-reinstall --no-deps"
install += " -e"

for plugin in plugin_names(plugins):
plugin_path = f"{plugins_dir}/{plugin}"
if os.path.exists(f"{plugin_path}/setup.cfg"):
click.echo(f"Installing {plugin} (editable)")
run_command(f'{install} "{plugin_path}[all]"')
elif plugins:
# explicit plugin list: error on missing setup.cfg, matching wheel-mode behavior
click.echo(f"Error: setup.cfg not found in {plugin}")
os._exit(1)


def run_install(
plugins: tuple[str],
reinstall: bool,
Expand Down Expand Up @@ -372,6 +405,7 @@ def handle_args(
server_arg: tuple[str],
js: bool,
configure: str | None,
wheel: bool,
plugins: tuple[str],
stop_event: threading.Event,
) -> None:
Expand All @@ -388,6 +422,8 @@ def handle_args(
js: True to build the JS files for the plugins
configure: The configuration to use. 'min' will install the minimum requirements for development.
'full' will install some optional packages for development, such as sphinx and deephaven-server.
wheel: True to use the legacy wheel-based build/install path. When False (the default),
installs run as `pip install -e` so Python source edits do not require a rebuild.
plugins: Plugins to build and install
stop_event: The event to signal the function to stop
"""
Expand All @@ -400,6 +436,13 @@ def handle_args(
js = True
install = True

# in editable mode, --build is meaningless because no wheels are produced.
# warn once so the user is not surprised when nothing is built.
if build and not wheel:
click.echo(
"Note: --build is ignored in editable mode. Pass --wheel to build wheels."
)

# if this thread is signaled to stop, return after the current command
# instead of in the middle of a command, which could leave the environment in a bad state
if stop_event.is_set():
Expand All @@ -411,14 +454,17 @@ def handle_args(
if stop_event.is_set():
return

if build or install or reinstall:
if wheel and (build or install or reinstall):
run_build(plugins, len(plugins) > 0)

if stop_event.is_set():
return

if install or reinstall:
run_install(plugins, reinstall)
if wheel:
run_install(plugins, reinstall)
else:
run_install_editable(plugins, reinstall)

if stop_event.is_set():
return
Expand Down Expand Up @@ -460,21 +506,27 @@ def handle_args(
"By default, all plugins with the necessary file are used unless specified via the plugins arg.",
)
@click.option(
"--build", "-b", is_flag=True, help="Build all plugins that have a setup.cfg"
"--build",
"-b",
is_flag=True,
help="Build all plugins that have a setup.cfg. "
"Only meaningful with --wheel; ignored in the default editable-install mode.",
)
@click.option(
"--install",
"-i",
is_flag=True,
help="Install all plugins that have a setup.cfg. This is the default behavior if no flags are provided.",
help="Install all plugins that have a setup.cfg. This is the default behavior if no flags are provided. "
"Defaults to an editable install (`pip install -e`); pass --wheel to build and install wheels instead.",
)
@click.option(
"--reinstall",
"-r",
is_flag=True,
help="Reinstall all plugins that have a setup.cfg. "
"This adds the --force-reinstall and --no-deps flags to pip install. "
"Useful to reinstall a plugin that has already been installed and does not have a new version number.",
"Useful to reinstall a plugin that has already been installed and does not have a new version number, "
"or to re-link refreshed JS bundles / entry points in editable mode.",
)
@click.option(
"--docs",
Expand Down Expand Up @@ -525,6 +577,13 @@ def handle_args(
"This will rerun all other commands (except configure) when files are changed. "
"The top level directory of this project is watched.",
)
@click.option(
"--wheel",
is_flag=True,
help="Use the legacy wheel-based build/install path. "
"By default, plugins are installed with `pip install -e` (editable) so Python source edits "
"do not require a wheel rebuild. Pass --wheel to build wheels and install them instead.",
)
@click.argument("plugins", nargs=-1)
def builder(
build: bool,
Expand All @@ -537,6 +596,7 @@ def builder(
js: bool,
configure: str | None,
watch: bool,
wheel: bool,
plugins: tuple[str],
) -> None:
"""
Expand All @@ -554,6 +614,7 @@ def builder(
configure: The configuration to use. 'min' will install the minimum requirements for development.
'full' will install some optional packages for development, such as sphinx and deephaven-server.
watch: True to rerun the other commands when files are changed
wheel: True to use the legacy wheel-based build/install path instead of editable installs
plugins: Plugins to build and install
"""
# no matter what, only run the configure command once
Expand All @@ -575,6 +636,7 @@ def run_handle_args() -> None:
server_arg,
js,
configure,
wheel,
plugins,
stop_event,
)
Expand Down
Loading