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
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,28 @@ jobs:
toolchain: "1.88.0"
- run: cargo check --all-targets

python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- uses: actions/setup-python@v5
with:
# the abi3 floor and the newest CPython
python-version: |
3.11
3.14
- run: cargo fmt --check --manifest-path python/Cargo.toml
- run: cargo clippy --locked --manifest-path python/Cargo.toml --all-targets -- -D warnings
- name: Build the wheel
run: python3.14 -m pip wheel --no-deps --wheel-dir dist ./python
- name: Test the installed wheel
run: |
python3.11 scripts/release/smoke-python.py dist --tests python/tests
python3.14 scripts/release/smoke-python.py dist --tests python/tests

synthetic-correctness:
runs-on: ubuntu-latest
steps:
Expand Down
44 changes: 42 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
commit=$(git rev-parse "refs/tags/$TAG^{commit}")
git show "$commit:Cargo.toml" > /tmp/release-Cargo.toml
python3 -c 'import os,tomllib; assert tomllib.load(open("/tmp/release-Cargo.toml","rb"))["workspace"]["package"]["version"] == os.environ["TAG"][1:]'
git show "$commit:python/Cargo.toml" > /tmp/release-python-Cargo.toml
python3 -c 'import os,tomllib; assert tomllib.load(open("/tmp/release-python-Cargo.toml","rb"))["package"]["version"] == os.environ["TAG"][1:]'
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "commit=$commit" >> "$GITHUB_OUTPUT"

Expand Down Expand Up @@ -104,6 +106,12 @@ jobs:
path: dist/*
if-no-files-found: error

wheels:
needs: resolve
uses: ./.github/workflows/wheels.yml
with:
source-commit: ${{ needs.resolve.outputs.commit }}

wasm:
needs: resolve
uses: ./.github/workflows/wasm.yml
Expand All @@ -112,7 +120,7 @@ jobs:
tag: ${{ needs.resolve.outputs.tag }}

publish:
needs: [resolve, native, wasm]
needs: [resolve, native, wasm, wheels]
runs-on: ubuntu-24.04
permissions:
contents: write
Expand All @@ -127,7 +135,7 @@ jobs:
run: |
for attempt in $(seq 1 90); do
gh api "repos/$GH_REPO/commits/$SOURCE_COMMIT/check-runs?filter=latest&per_page=100" > /tmp/source-checks.json
if python3 -c 'import json,sys; checks=json.load(open("/tmp/source-checks.json"))["check_runs"]; required=[next((c for c in checks if c["name"]==name),None) for name in ("test","diagnostics","msrv","synthetic-correctness")]; failed=any(c and c["status"]=="completed" and c["conclusion"]!="success" for c in required); passed=all(c and c["status"]=="completed" and c["conclusion"]=="success" for c in required); sys.exit(2 if failed else 0 if passed else 1)'; then
if python3 -c 'import json,sys; checks=json.load(open("/tmp/source-checks.json"))["check_runs"]; required=[next((c for c in checks if c["name"]==name),None) for name in ("test","diagnostics","msrv","synthetic-correctness","python")]; failed=any(c and c["status"]=="completed" and c["conclusion"]!="success" for c in required); passed=all(c and c["status"]=="completed" and c["conclusion"]=="success" for c in required); sys.exit(2 if failed else 0 if passed else 1)'; then
break
else
state=$?
Expand All @@ -138,8 +146,13 @@ jobs:
done
- uses: actions/download-artifact@v4
with:
pattern: native-*
path: dist
merge-multiple: true
- uses: actions/download-artifact@v4
with:
name: wasm
path: dist
- name: Hash and publish all verified packages
run: |
test "$(gh api "repos/$GH_REPO/commits/$TAG" --jq .sha)" = "$SOURCE_COMMIT"
Expand All @@ -152,3 +165,30 @@ jobs:
cd ../readback
sha256sum -c SHA256SUMS
echo "Published $TAG from $SOURCE_COMMIT; all seven downloaded packages match SHA256SUMS." >> "$GITHUB_STEP_SUMMARY"

pypi:
needs: [resolve, publish]
runs-on: ubuntu-24.04
environment:
name: pypi
url: https://pypi.org/project/ournotes-sim/
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: wheel-*
path: dist
merge-multiple: true
- uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Check the wheels
run: |
test "$(find dist -maxdepth 1 -name '*.whl' | wc -l)" -eq 5
python -m pip install --upgrade twine
python -m twine check --strict dist/*
# trusted publishing: PyPI accepts this workflow's OIDC token, no API token is stored
- uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
64 changes: 64 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Python wheels

on:
workflow_call:
inputs:
source-commit:
description: Source commit to build and verify
required: true
type: string
pull_request:
paths:
- python/**
- scripts/release/smoke-python.py
- .github/workflows/wheels.yml

permissions:
contents: read

jobs:
# one abi3 wheel (CPython 3.11 and later) per platform with the extension module ournotes_sim._native
wheel:
strategy:
fail-fast: false
matrix:
include:
- {runner: ubuntu-24.04, target: x86_64, manylinux: '2_28'}
- {runner: ubuntu-24.04-arm, target: aarch64, manylinux: '2_28'}
- {runner: windows-2025, target: x64}
- {runner: macos-15-intel, target: x86_64}
- {runner: macos-15, target: aarch64}
runs-on: ${{ matrix.runner }}
timeout-minutes: 40
defaults:
run:
shell: bash
env:
SOURCE_COMMIT: ${{ inputs.source-commit || github.sha }}
MACOSX_DEPLOYMENT_TARGET: '11.0'
steps:
- uses: actions/checkout@v7
with:
path: automation
- uses: actions/checkout@v7
with:
ref: ${{ inputs.source-commit || github.sha }}
path: source
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- uses: PyO3/maturin-action@v1
with:
working-directory: source/python
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux || 'auto' }}
# SOURCE_COMMIT is compiled into info()["commit"]
docker-options: -e SOURCE_COMMIT
args: --release --locked --out dist
- name: Test the installed wheel
run: python automation/scripts/release/smoke-python.py source/python/dist --tests source/python/tests --commit "$SOURCE_COMMIT"
- uses: actions/upload-artifact@v4
with:
name: wheel-${{ matrix.runner }}-${{ matrix.target }}
path: source/python/dist/*.whl
if-no-files-found: error
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
/work
/tools/search-harness/target
/tools/search-harness/__pycache__
/python/target
/python/dist
/python/**/__pycache__
/python/.pytest_cache
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]
members = ["crates/ournotes-sim", "crates/ournotes-search"]
# separate packages with their own lockfiles and release profiles
exclude = ["wasm/replay", "wasm/recommend", "tools/search-harness"]
exclude = ["wasm/replay", "wasm/recommend", "python", "tools/search-harness"]
resolver = "3"

[workspace.package]
Expand Down
22 changes: 17 additions & 5 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ The repository is a Cargo workspace of two crates; the search depends on the mod

Depend on `ournotes-sim` for computation or simulation alone (chart statistics, replay); depend on `ournotes-search`
to search, and take the model's types from `ournotes-sim`. `wasm/replay` is the replay WASM over `ournotes-sim`,
`wasm/recommend` the recommendation WASM over `ournotes-search`, and `tools/search-harness` measures the search; these
three are separate packages outside the workspace, each with its own `Cargo.lock`. Both crates have a
`wasm/recommend` the recommendation WASM over `ournotes-search`, `python` the Python package `ournotes-sim` (the
chart statistics of `ournotes-sim`, see [python/README.md](python/README.md)), and `tools/search-harness` measures the
search; these four are separate packages outside the workspace, each with its own `Cargo.lock`. Both crates have a
`search-diagnostics` feature; `ournotes-search`'s also enables `ournotes-sim`'s.

## Recommendation pipeline
Expand Down Expand Up @@ -168,6 +169,16 @@ for deck in &out.results {
}
```

From Python, the PyPI package `ournotes-sim` measures the chart statistics of a deck data document with the model of
the same release:

```python
import json
import ournotes_sim

stats = json.loads(ournotes_sim.chart_stats(open("deck-data.json", encoding="utf-8").read()))
```

## Command line

Use one entry point, `ournotes-deck`. JSON recommendation requests use its
Expand Down Expand Up @@ -336,16 +347,17 @@ fn statistics(data: &DeckData) -> Result<serde_json::Value, Error> {

`cargo test` at the repository root runs the unit and integration tests of both crates: it reads synthetic deck data
files and compares the search with exhaustive enumeration on small synthetic pools. The two WASM packages build with
`cargo build --manifest-path wasm/<replay|recommend>/Cargo.toml --target wasm32-unknown-unknown --release`. `OURNOTES_DECK_ORACLE_CASES`, `OURNOTES_DECK_ORACLE_SEED0`,
`cargo build --manifest-path wasm/<replay|recommend>/Cargo.toml --target wasm32-unknown-unknown --release`. The Python
package builds and installs with `pip install ./python[test]`; `pytest python/tests` tests it. `OURNOTES_DECK_ORACLE_CASES`, `OURNOTES_DECK_ORACLE_SEED0`,
`OURNOTES_DECK_ORACLE_MEMBERS` and `OURNOTES_DECK_ORACLE_SNAPS` enlarge that comparison. For the live score with snap
skills, `OURNOTES_DECK_SNAPS_CASES`, `OURNOTES_DECK_SNAPS_SEED0`, `OURNOTES_DECK_SNAPS_MEMBERS`,
`OURNOTES_DECK_SNAPS_SNAPS`, `OURNOTES_DECK_SNAPS_NOTES` and `OURNOTES_DECK_SNAPS_VARIANTS` do the same.

## Changelog

[CHANGELOG.md](CHANGELOG.md) lists the changes, generated from the commits (Conventional Commits) by
[git-cliff](https://git-cliff.org/). The commit that bumps the version regenerates it with
`git cliff --tag vX.Y.Z -o CHANGELOG.md`.
[git-cliff](https://git-cliff.org/). The commit that bumps the version (the workspace, `wasm/*` and `python`
manifests with their lockfiles) regenerates it with `git cliff --tag vX.Y.Z -o CHANGELOG.md`.

## Licence

Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ BanG Dream! Our Notes 的综合力、跳过分数与演出分数计算,以及

只需要计算或模拟(例如谱面统计、回放)时依赖 `ournotes-sim`;要搜索时依赖 `ournotes-search`,模型类型仍从 `ournotes-sim`
引用。`wasm/replay` 是基于 `ournotes-sim` 的回放 WASM,`wasm/recommend` 是基于 `ournotes-search` 的推荐 WASM,
`tools/search-harness` 是搜索的测量工具;这三个是 workspace 之外的独立 package,各有自己的 `Cargo.lock`。
`python` 是 Python 包 `ournotes-sim`(`ournotes-sim` 的谱面统计,见 [python/README.md](python/README.md)),
`tools/search-harness` 是搜索的测量工具;这四个是 workspace 之外的独立 package,各有自己的 `Cargo.lock`。
两个 crate 都有 `search-diagnostics` 特性;`ournotes-search` 的这个特性会同时打开 `ournotes-sim` 的。

## 推荐主链
Expand Down Expand Up @@ -148,6 +149,15 @@ for deck in &out.results {
}
```

在 Python 里,PyPI 包 `ournotes-sim` 用同一版本的模型计算 deck data 文件的谱面统计:

```python
import json
import ournotes_sim

stats = json.loads(ournotes_sim.chart_stats(open("deck-data.json", encoding="utf-8").read()))
```

## 命令行

统一入口为 `ournotes-deck`。JSON 推荐请求使用 `recommend` 子命令:
Expand Down Expand Up @@ -238,15 +248,16 @@ Just 率插值和 Great 比例缩放是统计摘要的近似使用方式,完

在仓库根目录运行 `cargo test`,即运行两个 crate 的单元测试与集成测试:读取合成的 deck data 文件,并在小规模合成卡池上
将搜索结果与穷举结果逐项比较。两个 WASM package 用 `cargo build --manifest-path wasm/<replay|recommend>/Cargo.toml
--target wasm32-unknown-unknown --release` 构建。
--target wasm32-unknown-unknown --release` 构建。Python 包用 `pip install ./python[test]` 构建并安装,
`pytest python/tests` 测试它。
可用 `OURNOTES_DECK_ORACLE_CASES`、`OURNOTES_DECK_ORACLE_SEED0`、`OURNOTES_DECK_ORACLE_MEMBERS`、
`OURNOTES_DECK_ORACLE_SNAPS` 扩大比较规模。含 snap 技能的演出分数可用 `OURNOTES_DECK_SNAPS_CASES`、
`OURNOTES_DECK_SNAPS_SEED0`、`OURNOTES_DECK_SNAPS_MEMBERS`、`OURNOTES_DECK_SNAPS_SNAPS`、`OURNOTES_DECK_SNAPS_NOTES`、
`OURNOTES_DECK_SNAPS_VARIANTS` 扩大比较规模。

## 版本记录

改动见 [CHANGELOG.md](CHANGELOG.md),由 [git-cliff](https://git-cliff.org/) 根据提交(Conventional Commits)生成。发版时,改版本号的那个提交同时重新生成它:`git cliff --tag vX.Y.Z -o CHANGELOG.md`。
改动见 [CHANGELOG.md](CHANGELOG.md),由 [git-cliff](https://git-cliff.org/) 根据提交(Conventional Commits)生成。发版时,改版本号的那个提交(workspace、`wasm/*` 与 `python` 的清单及其锁文件)同时重新生成它:`git cliff --tag vX.Y.Z -o CHANGELOG.md`。

## 许可证

Expand Down
12 changes: 12 additions & 0 deletions docs/release-artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ owned snapshot; `power`, `skip`, `live`, and `chart-stats` provide the task-spec
interfaces. Use `ournotes-deck --help` and `ournotes-deck recommend --help`.
Rust library consumers use the tagged Git dependencies described in the README.

The same release publishes the Python package `ournotes-sim` to PyPI: one abi3
wheel (CPython 3.11 and later) for each of Linux x64 and ARM64 (manylinux 2.28),
Windows x64, and macOS Intel and Apple Silicon (macOS 11 or later). Its
`info()["commit"]` is the release commit. The package is published as wheels
only, so every installation runs the model whose `sourceSha256` the release
reports.

Both WASM packages include JavaScript, TypeScript declarations, and the matching
`_bg.wasm` files. Keep each binding directory together. The web directory uses ES
modules; the Node directory uses CommonJS. Serve the web package over HTTP, with
Expand Down Expand Up @@ -50,6 +57,11 @@ Worker. The worker checks Power and Free recommendation results against native
references and replay output against the Node binding. These package checks do
not establish additional native-game coverage.

Each wheel is installed in a fresh environment on its own platform, where the
package tests run against it and its reported commit is checked against the tag.

Only after all builds and checks pass does the workflow upload the archives and
`SHA256SUMS`, download the published assets again, and verify every archive hash.
The PyPI job then checks the five wheels with `twine check` and publishes them
through PyPI trusted publishing.
The workflow can add artifacts to an already published source-only Release.
Loading
Loading