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
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
self
quickstart
configuration
wheel_sources
usage
release_notes
contributing
Expand Down
14 changes: 14 additions & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ To view all the instances that are produced use the ``list`` command:
The ``black`` and ``mypy`` instances will be run with Python 3.9 and the
``pytest`` instance will be run in Python 3.8 and 3.9.


Using Pre-built Wheels
----------------------

By default, riot installs your project in editable mode (``pip install -e .``).
If you want to test with pre-built wheels instead, use the ``--wheel-path`` option:

.. code-block:: bash

$ pip wheel --no-deps -w dist/ .
$ riot --wheel-path dist/ run test

See :doc:`wheel_sources` for more details on using pre-built wheels.

176 changes: 176 additions & 0 deletions docs/wheel_sources.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
Using Pre-built Wheels
======================

By default, riot installs your project in editable mode (``pip install -e .``) when
creating virtual environments. However, you can configure riot to install from
pre-built wheels instead. This is useful for:

- **CI/CD pipelines**: Using pre-built wheels from a previous build step
- **Testing distributions**: Verifying that your built wheels work correctly
- **Faster environment creation**: Avoiding repeated package builds
- **Reproducibility**: Testing with exact wheel artifacts


Specifying a Wheel Path
------------------------

There are two ways to specify a wheel path:


Command-line Option
~~~~~~~~~~~~~~~~~~~

Use the global ``--wheel-path`` option before any subcommand:

.. code-block:: bash

# With a local directory containing wheels
riot --wheel-path /path/to/wheels run test

# With a remote URL (e.g., index.html)
riot --wheel-path https://example.com/wheels/ generate

# Works with all commands
riot --wheel-path /tmp/wheels shell mypy


Environment Variable
~~~~~~~~~~~~~~~~~~~~

Set the ``RIOT_WHEEL_PATH`` environment variable:

.. code-block:: bash

export RIOT_WHEEL_PATH=/path/to/wheels
riot run test

This is particularly useful in CI/CD environments where you want to configure
wheel paths without modifying commands.


Package Name Resolution
-----------------------

When using wheel paths, riot needs to know the package name to install. It
determines this automatically by:

1. **Checking the ``RIOT_PACKAGE_NAME`` environment variable** (highest priority)
2. **Parsing ``pyproject.toml``**: Reads the ``[project]`` table's ``name`` field

For projects using ``pyproject.toml`` with a ``[project]`` section, no additional
configuration is needed:

.. code-block:: toml

[project]
name = "my-package"
version = "1.0.0"

For projects not using ``pyproject.toml`` or with custom naming, set the
``RIOT_PACKAGE_NAME`` environment variable:

.. code-block:: bash

export RIOT_PACKAGE_NAME=my-package
export RIOT_WHEEL_PATH=/tmp/wheels
riot run test


How It Works
------------

When a wheel path is specified:

1. **Download**: riot downloads the wheel using ``pip download --no-index --find-links``
to ensure only wheels from the specified source are used (not PyPI)
2. **Install**: The downloaded wheel is installed into the virtual environment
3. **No Fallback**: If the wheel is not found, riot fails with a clear error message
(no fallback to editable install)

This ensures reproducibility and prevents accidental use of incorrect package versions.


Example: CI/CD Workflow
-----------------------

A typical CI/CD workflow using wheel paths:

.. code-block:: bash

# Step 1: Build wheels
pip wheel --no-deps -w dist/ .

# Step 2: Run tests with built wheels
riot --wheel-path dist/ run test

# Step 3: Verify wheels work in clean environments
riot --wheel-path dist/ generate --recreate-venvs


Example: Testing with Remote Wheels
------------------------------------

Test against wheels published to a remote location:

.. code-block:: bash

# Test against wheels on an S3 bucket or web server
riot --wheel-path https://artifacts.example.com/wheels/v1.2.3/ run test

The wheel path can be any location supported by pip's ``--find-links`` option,
including:

- Local directories (``/path/to/wheels``)
- File URLs (``file:///path/to/wheels``)
- HTTP/HTTPS URLs with index.html (``https://example.com/wheels/``)


Compatibility with Existing Options
------------------------------------

Wheel sources work with all existing riot options:

.. code-block:: bash

# Recreate environments with wheels
riot --wheel-path /tmp/wheels run --recreate-venvs test

# Skip base install (wheels already installed)
riot --wheel-path /tmp/wheels run --skip-base-install test

# Generate base environments with wheels
riot --wheel-path /tmp/wheels generate


Troubleshooting
---------------

**Wheel not found error**:

If you see an error like "Wheel download failed", verify:

- The wheel file exists in the specified location
- The package name matches (check ``RIOT_PACKAGE_NAME`` or ``pyproject.toml``)
- For URLs, the index.html or directory listing is accessible

**Package name cannot be determined**:

If you see "Could not determine package name", either:

- Add a ``[project]`` section with ``name`` field to ``pyproject.toml``
- Set the ``RIOT_PACKAGE_NAME`` environment variable


Environment Variables Reference
--------------------------------

.. list-table::
:header-rows: 1
:widths: 30 70

* - Variable
- Description
* - ``RIOT_WHEEL_PATH``
- Path or URL to wheel files. When set, installs from wheels instead of editable mode.
* - ``RIOT_PACKAGE_NAME``
- Package name to use when installing from wheels. Overrides automatic detection from ``pyproject.toml``.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
features:
- |
Add support for installing from pre-built wheels instead of editable mode via the
``--wheel-path`` global option and ``RIOT_WHEEL_PATH`` environment variable.
See https://riot.readthedocs.io/en/latest/wheel_sources.html for details.
17 changes: 16 additions & 1 deletion riot/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,17 @@ def convert(self, value, param, ctx):
default=False,
help="Pipe mode. Makes riot emit plain output.",
)
@click.option(
"--wheel-path",
"wheel_path",
type=str,
default=None,
envvar="RIOT_WHEEL_PATH",
help="Path or URL to wheel files. When set, installs from wheels instead of editable mode.",
)
@click.version_option(__version__)
@click.pass_context
def main(ctx, riotfile, log_level, pipe_mode):
def main(ctx, riotfile, log_level, pipe_mode, wheel_path):
if pipe_mode:
if log_level:
logging.basicConfig(level=log_level)
Expand All @@ -94,6 +102,7 @@ def main(ctx, riotfile, log_level, pipe_mode):

ctx.ensure_object(dict)
ctx.obj["pipe"] = pipe_mode
ctx.obj["wheel_path"] = wheel_path

# Check if file exists first (before checking for subcommand)
import os
Expand Down Expand Up @@ -168,11 +177,13 @@ def list_venvs(ctx, pythons, pattern, venv_pattern, interpreters, hash_only):
@PATTERN_ARG
@click.pass_context
def generate(ctx, recreate_venvs, skip_base_install, pythons, pattern):
wheel_path = ctx.obj.get("wheel_path")
ctx.obj["session"].generate_base_venvs(
pattern=re.compile(pattern),
recreate=recreate_venvs,
skip_deps=skip_base_install,
pythons=pythons,
wheel_path=wheel_path,
)


Expand Down Expand Up @@ -202,6 +213,7 @@ def run(
venv_pattern,
recompile_reqs,
):
wheel_path = ctx.obj.get("wheel_path")
ctx.obj["session"].run(
pattern=re.compile(pattern),
venv_pattern=re.compile(venv_pattern),
Expand All @@ -213,6 +225,7 @@ def run(
skip_missing=skip_missing,
exit_first=exit_first,
recompile_reqs=recompile_reqs,
wheel_path=wheel_path,
)


Expand All @@ -221,9 +234,11 @@ def run(
@click.option("--pass-env", "pass_env", is_flag=True, default=False)
@click.pass_context
def shell(ctx, ident, pass_env):
wheel_path = ctx.obj.get("wheel_path")
ctx.obj["session"].shell(
ident=ident,
pass_env=pass_env,
wheel_path=wheel_path,
)


Expand Down
Loading
Loading