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
2 changes: 1 addition & 1 deletion .build-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ tools:
node: 20.12.2
python: 3.11
dependencies:
xcomm: 18f3e60e9eb9d38127e3ecdccfacf91cf016b19c
xcomm: c39db6652cd08d46df05d08f0bb5a033b83e4d2f
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ jobs:

- name: Build Python wheels
if: matrix.full_test
env:
# Match the planned xcomm 1.x release while it is still in development.
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM: 1.0.0.dev0
run: |
python3 -m venv /tmp/picker-wheel-builder
/tmp/picker-wheel-builder/bin/python -m pip install 'build>=1,<2'
Expand Down
16 changes: 15 additions & 1 deletion .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ jobs:
test "$(uname -m)" = "$ARCH"
test -x AppDir/usr/bin/picker
test -d AppDir/usr/share/picker/template
test -e AppDir/usr/lib/libxspcomm.so.1
test -e AppDir/usr/lib/libxspcomm.so.3
output="$(AppDir/usr/bin/picker --version)"
[[ "$output" == *"$VERSION"* ]]
mkdir -p release-assets
Expand Down Expand Up @@ -256,3 +256,17 @@ jobs:
name: picker-release-bundle
path: release-assets/*
if-no-files-found: error

dispatch-release:
needs: bundle
runs-on: ubuntu-24.04
permissions:
actions: write
steps:
- name: Start release for this Tagged CI run
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref master \
--raw-field "run_id=$GITHUB_RUN_ID"
27 changes: 6 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
name: Release Picker

on:
workflow_run:
workflows: [Tagged CI]
types: [completed]
workflow_dispatch:
inputs:
run_id:
description: "Successful Tagged CI run ID whose exact artifacts should be published"
description: "Tagged CI run ID to wait for and publish its exact artifacts"
required: true
type: string

Expand All @@ -17,36 +14,24 @@ permissions:

jobs:
publish:
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-24.04
permissions:
contents: write
actions: read
pull-requests: read
env:
SOURCE_RUN_ID: ${{ github.event.workflow_run.id || inputs.run_id }}
SOURCE_RUN_ID: ${{ inputs.run_id }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate source run
- name: Wait for successful Tagged CI completion
timeout-minutes: 65
run: |
set -euo pipefail
[[ "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]]
gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${SOURCE_RUN_ID}" > source-run.json
python - <<'PY'
import json
import os

with open("source-run.json") as source:
run = json.load(source)
assert run["repository"]["full_name"] == os.environ["GITHUB_REPOSITORY"]
assert run["path"].split("@", 1)[0].endswith("/.github/workflows/release-build.yml") or run["path"].split("@", 1)[0] == ".github/workflows/release-build.yml"
assert run["event"] in {"repository_dispatch", "workflow_dispatch"}
assert run["status"] == "completed" and run["conclusion"] == "success"
assert run["id"] == int(os.environ["SOURCE_RUN_ID"])
PY
python3 scripts/release/wait_for_source_run.py \
"$GITHUB_REPOSITORY" "$SOURCE_RUN_ID" --timeout 3600
- name: Download exact CI release bundle
uses: actions/download-artifact@v4
with:
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
#dependencies
if ("$ENV{NO_BUILD_XSPCOMM}" STREQUAL "")
add_subdirectory(dependence/xcomm)
# Cover pinned xcomm revisions as well as their SWIG consumers. Standalone
# xcomm wheels are built separately by the wheel target in the Makefile.
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(xspcomm PUBLIC -fno-strict-aliasing)
endif()
endif()

# Verilog/SystemVerilog frontend
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ clean:
rm -rf temp build dist output app_image_build AppDir picker_out* picker_e203_ifu_ift2icb

wheel:
cd dependence/xcomm && $(PYTHON) -m build --wheel
cd dependence/xcomm && CFLAGS="$(CFLAGS) -fno-strict-aliasing" $(PYTHON) -m build --wheel
NO_BUILD_XSPCOMM=1 $(PYTHON) -m build --wheel
mkdir -p dist && cp dependence/xcomm/dist/*.whl ./dist

Expand Down
201 changes: 71 additions & 130 deletions doc/maintainer-guidance.md
Original file line number Diff line number Diff line change
@@ -1,140 +1,81 @@
# Maintaining Picker releases
# Maintaining Picker

Picker and xspcomm have independent version tags. Release xspcomm first;
Picker's wheel and native packages use a compatible xspcomm release. This page
records the maintainer procedure.
## Versions and dependencies

## Versions and releases
Picker and xcomm have independent `vX.Y.Z` package tags. Picker derives its
package, CLI and CMake release versions from its own tag using setuptools-scm.
Release a compatible xcomm version before releasing Picker.

Version tags are independent in the two Git repositories. Use `vX.Y.Z` tags,
for example `v2.0.0` for Picker and `v0.1.0` for xcomm. The wheel version is
derived from the tag by setuptools-scm. Commits after a tag yield development
versions such as `2.0.1.dev3`; ordinary CI uploads these wheels as artifacts
and does not create a stable release. Older date/SHA AppImage tags do not
participate in Python package version calculation; CI no longer creates them.
| Setting | Source | Current requirement |
| --- | --- | --- |
| Development xcomm source | `.build-config.yml` | Exact tested commit; `XCOMM_SOURCE_REF` can override it |
| Python dependency | `pyproject.toml` | `xspcomm>=1.0.0.dev0,<2` |
| Development xcomm wheel | Development CI | `1.0.0.dev0` preview until xcomm 1.0 is released |
| Native ABI | `xspcomm.abi_version()` | 3; packages contain `libxspcomm.so.3` |
| Coverage descriptors | `XTriggerEngine.CoverageVersion()` | 5 |

Every PR targeting `master` must have exactly one of `release:patch`,
`release:minor`, or `release:major`. Other labels are allowed. The **Version
policy / release-label** check verifies this. In GitHub Settings → Rules →
Rulesets (or the `master` branch protection rule), add this check to the
required status checks; the workflow alone does not prevent merging. Create
the three labels in each repository. Also require the relevant CI build checks
and require an up-to-date PR before merging; otherwise tagging after merge does
not imply the merged source has passed CI. Do not use merge queue until the
required checks also support `merge_group`.
Package version, native ABI and descriptor protocol are separate identifiers.
When updating xcomm, align the source pin, Python range and preview version,
then rebuild native clients and generated DUT extensions against matching
headers and runtime. ABI checks cannot be replaced by a package version range.

After a labeled PR merges, the `master` push starts **Version policy**. It
finds the merged PR, computes the next version, pushes its tag and dispatches
**Tagged CI** with the tag and commit SHA. A direct push without an associated
merged PR does not create a tag. Every merged PR, including a
documentation-only PR, creates a version tag. The tag push itself does not
start CI when it was made using `GITHUB_TOKEN`; the explicit dispatch does.
Both version calculation and Tagged CI apply the same master-tag rule: the tag
must point to a commit on master's first-parent line that GitHub records as the
result of a merged PR targeting master. A higher-numbered tag on a feature
branch is ignored when calculating the next version, and manually requesting a
build for a tag outside this rule fails before any assets are built. Tag names
remain repository-wide, so a conflicting name elsewhere still blocks creation
of the next tag rather than being overwritten. Git itself does not record the
branch on which a tag was created. To prevent users from creating, moving or
deleting `v*` tags outside this workflow, protect that tag pattern with a
GitHub tag ruleset; the workflow checks the target commit, not the tagger's
historical branch.
The version-tag job serializes its runs, but GitHub does not guarantee that
rapid consecutive merges enter that queue in commit order. Until version
allocation is made order-aware, avoid merging another PR while a previous
Version policy run is still tagging; a later commit tagged first can cause the
older run to fail its ancestry check.
## Local build and validation

**Tagged CI** checks out that exact tag, tests the native builds and builds a
CPython 3.12 Linux x86-64 wheel, Linux x86-64 and aarch64 AppImages, and Linux
x86-64 and aarch64 native archives. It uploads one checked release bundle.
**Release Picker** runs only after Tagged CI succeeds, downloads that run's
bundle, checks its hashes and tag SHA, and publishes those exact files without
rebuilding. All five packages, `SHA256SUMS` and `RELEASE-MANIFEST.json` go into
one GitHub Release. The manifest records the Picker tag, commit, CI run ID and
xcomm version used. The archives contain the installed
`usr/bin/picker`, `usr/share/picker` templates and native libraries; they are
not bare executables. AppImage is the self-contained desktop distribution;
the archive is an installed tree for a compatible Linux system. The wheel is
for pip and depends on a separately published xspcomm wheel. Tagged CI selects
one compatible xspcomm version for all three package types.

**Release Picker** never changes the tag. If the tagged build fails, rerun
**Tagged CI** manually with the existing tag and a compatible xcomm version.
If publication fails, rerun **Release Picker** with the successful Tagged CI
run ID; it publishes the same artifact. **Version policy** has a manual bump
input for recovering from a missed merge event. GitHub release wheels are not
automatically uploaded to PyPI; publishing there needs separate credentials
and platform-wheel policy. Development CI still builds AppImages and wheels,
but it no longer creates a date/SHA AppImage Release. The old `master`-push
Docker image publish is not part of this pipeline; Docker PR changes still
receive a build check.

Development CI and Tagged CI use the same scripts under `scripts/build/` for
native toolchain setup, CMake build and install, AppImage packaging, and wheel
installation checks. A master PR runs full Development CI and the version-label
check. A non-master branch push also runs Development CI unless that branch
already has an open master PR; in that case the PR run owns the build. A merge
to master runs Version policy rather than a second development build. Tagged
CI uses the final tag and a published xcomm version; its checked artifacts are
the only inputs to Release Picker. PR and branch builds pin xcomm to the commit
in `.build-config.yml` (an explicit `XCOMM_SOURCE_REF` commit can override it)
and never become release assets. Tagged builds use the selected published xcomm
tag instead. The build refuses to switch an xcomm checkout with local changes.

The `xspcomm>=0.1.0.dev0,<0.2` requirement in `pyproject.toml` is a deliberate
compatibility range, not an automatically generated version. Change it only
after testing the corresponding xcomm ABI and baseline APIs. Release metadata,
the installed Picker command and CMake receive their version from the same tag.

## Building and installing locally

Install a compiler, CMake, SWIG >= 4.2, Python development headers and the
Picker source dependencies. Run `make init` explicitly before building Picker
if those dependencies are missing. The wheel target itself does not fetch or
switch source repositories and does not remove existing packages.
Use a compiler, CMake, SWIG >= 4.2, Python development headers and a JDK
for Java tests. Run `make init` when source dependencies are missing. Build
from a clean checkout with a fresh wheel output directory; `make wheel` does
not align an existing xcomm checkout to the dependency pin or install packages
into the interpreter.

```sh
python3 -m venv /tmp/picker-package-build
/tmp/picker-package-build/bin/python -m pip install build
make wheel PYTHON=/tmp/picker-package-build/bin/python

python3 -m venv /tmp/picker-package-test
/tmp/picker-package-test/bin/python -m pip install --no-index --find-links=dist picker
/tmp/picker-package-test/bin/python -m pip check
/tmp/picker-package-test/bin/picker --version
python3 -m venv /tmp/picker-build
/tmp/picker-build/bin/python -m pip install build
make unit_tests BUILD_XSPCOMM_SWIG=python,java
SETUPTOOLS_SCM_PRETEND_VERSION_FOR_XSPCOMM=1.0.0.dev0 \
make wheel PYTHON=/tmp/picker-build/bin/python
bash scripts/build/verify-wheel.sh
make -C test all
```

Use a fresh checkout or empty `dist` directory for release builds; pip selects
the highest matching version when several old wheels are present. `make
wheel_install` intentionally only prints an installation command and never
uninstalls packages from the current interpreter.

Picker contains a compiled executable and xspcomm contains a SWIG/CPython
extension plus a C++ shared library. A matching wheel avoids compilation on
the user's machine. Picker's current sdist omits its vendored slang/fmt/yaml-cpp
sources, so **do not publish it as a pip fallback**. If no Picker wheel matches,
build from a full source checkout after `make init`; this does require local
compilation and the build toolchain. Use `--only-binary=:all:` when an
installation must fail instead of attempting a source build.
The wheel release currently targets CPython 3.12 on Linux x86-64. Native
archives and AppImages target Linux x86-64 and aarch64; expand these matrices
before claiming other platforms are supported.

## Native compatibility

xcomm's package version and C++ ABI version are different. The latter is
`XSPCOMM_ABI_VERSION` in xcomm's `CMakeLists.txt`; it controls the
`libxspcomm.so.N` SONAME and is exposed as `xspcomm.abi_version()`. Bump it
when a native ABI change is incompatible, then rebuild xcomm, Picker and any
generated DUT extensions. A package patch release alone must not change the
SONAME. A Python wheel is also tied to its CPython ABI and platform tag;
the SWIG module is not declared `abi3`.

The wheel CI installs xcomm and Picker from built wheels in a clean virtual
environment, checks metadata against native versions, exercises signal and clock APIs,
and runs the installed Picker command. These checks do **not** yet prove that
an independently packaged DUT extension uses the same `libxspcomm` instance:
generated Python DUTs can still copy xcomm. Keep DUT wheel/linker relocation
tests as a separate release gate before publishing a general-purpose DUT wheel.
The preview override affects only xcomm. Picker keeps its Git-derived version.
CI's native build script refuses to switch an xcomm checkout with local changes.
Keep `-O3`, the native `-fno-strict-aliasing` flags and signal-value regressions
enabled. Wheel verification installs both packages into a fresh environment and
checks dependency consistency, native APIs, clock ownership and CLI exit codes.

## Release procedure

1. Publish the compatible xcomm release. Tagged CI selects a published stable
release satisfying `pyproject.toml`; it never uses a development preview.
2. Give the Picker master PR exactly one label: `release:patch`, `release:minor`
or `release:major`. Require the version-label and build checks in branch
protection, and test the PR against current master. Merge queues need
`merge_group` checks before they can be used.
3. After merge, **Version policy** creates the next tag and explicitly dispatches
**Tagged CI**. Direct master pushes without an associated merged PR do not
create a tag. Wait for tagging to finish before merging the next release PR.
4. Tagged CI builds and validates the exact tag: CPython 3.12 Linux x86-64 wheel,
plus x86-64/aarch64 AppImages and native archives, using one xcomm release.
5. After bundle upload, Tagged CI dispatches **Release Picker** with its run ID.
The publisher waits up to 60 minutes for successful completion, verifies the
bundle hashes and tag SHA, and publishes those exact artifacts without
rebuilding. GitHub Releases are not automatically uploaded to PyPI.

Only tags on master's first-parent line that identify merged PR results are
accepted by the tagging/build policy. Configure a `v*` tag ruleset to restrict
manual creation or replacement. Do not publish the current Picker sdist as a
pip fallback: it omits vendored build dependencies; use a full source checkout
when a matching wheel is unavailable.

## Recovery

- Build failed: rerun **Tagged CI** with the existing tag and a compatible
published xcomm version. Do not move the tag.
- Publication failed: rerun **Release Picker** with the successful Tagged CI
run ID; it downloads the same checked bundle.
- Merge did not create a tag: inspect **Version policy**, then use its manual
bump input if recovery is needed.

Generated DUTs may copy xcomm libraries. The package checks do not establish
that independently packaged DUT extensions load the same runtime instance;
verify ABI compatibility and relocation separately before publishing DUT wheels.
12 changes: 6 additions & 6 deletions example/MultiClock/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ def test_multi_clock():
clk1, clk2, clk3 = XClock(lambda x: 0), XClock(lambda x: 0), XClock(lambda x: 0)
clk4, clk5, clk6 = XClock(lambda x: 0), XClock(lambda x: 0), XClock(lambda x: 0)

clk1.Add(dut.xport.SelectPins(["reg1"])).Add(dut.clk1.xdata)
clk2.Add(dut.xport.SelectPins(["reg2"])).Add(dut.clk2.xdata)
clk3.Add(dut.xport.SelectPins(["reg3"])).Add(dut.clk3.xdata)
clk4.Add(dut.xport.SelectPins(["reg4"])).Add(dut.clk4.xdata)
clk5.Add(dut.xport.SelectPins(["reg5"])).Add(dut.clk5.xdata)
clk6.Add(dut.xport.SelectPins(["reg6"])).Add(dut.clk6.xdata)
clk1.Add(dut.xport.SelectPins(["reg1"])).Add(dut.clk1)
clk2.Add(dut.xport.SelectPins(["reg2"])).Add(dut.clk2)
clk3.Add(dut.xport.SelectPins(["reg3"])).Add(dut.clk3)
clk4.Add(dut.xport.SelectPins(["reg4"])).Add(dut.clk4)
clk5.Add(dut.xport.SelectPins(["reg5"])).Add(dut.clk5)
clk6.Add(dut.xport.SelectPins(["reg6"])).Add(dut.clk6)

main_clock.FreqDivWith(1, clk1)
main_clock.FreqDivWith(2, clk2)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "scikit_build_core.build"
name = "picker"
dynamic = ["version"]
dependencies = [
"xspcomm>=0.1.0.dev0,<0.2",
"xspcomm>=1.0.0.dev0,<2",
]

[[tool.dynamic-metadata]]
Expand Down
Loading
Loading