Skip to content

Commit 4537732

Browse files
authored
feat(install): one git install command, with the studio behind a feature
`cargo install --git https://github.com/LeadcodeDev/rustmotion` did not work at all before this: cargo refused it with "multiple packages with binaries found: rustmotion, rustmotion-studio". That check is not about the workspace — cargo searches the whole repository for any Cargo.toml and demands that exactly one package declare a `[[bin]]`. Verified, not assumed: neither `default-members` nor `required-features` changes the count. So one package has to own both binaries, and it has to be `rustmotion`, because `cargo install rustmotion` from crates.io must keep delivering a command. The studio was a package that depended on `rustmotion` for `loader` and `encode`, so making it a dependency of `rustmotion` was a cycle — which cargo refuses even for an optional dependency. It becomes a module instead: `crates/rustmotion/src/studio/`. With no second package there is no cycle, and because `lib.rs` already declares `extern crate self as rustmotion`, all of the studio's `rustmotion::loader::…` paths stay valid verbatim. The only code change is requalifying its 82 internal `crate::` paths to `crate::studio::`. The alternative — extracting `loader`, `encode`, `include`, `assets` and the `engine` extensions into a third crate — would have moved 14 900 lines and added two crates to the published set to reach the same command. cargo install --git <url> # CLI cargo install --git <url> --features studio # CLI + studio `studio` is out of the default build on purpose: it pulls gpui and a native GUI toolchain, which do not build everywhere the CLI builds, a headless server being the obvious case. Its seven dependencies are optional and the second `[[bin]]` carries `required-features = ["studio"]`. Two consequences worth naming. CI: `--workspace` used to compile the studio because it was a member. Behind a non-default feature it would not, so clippy and the tests would have stayed green over 11 600 lines nobody checks any more. Both jobs now pass `--features rustmotion/studio`. Test count is unchanged at 1701 with the feature, 1510 without. `serde_json/preserve_order` moves into the `studio` feature rather than the default: the studio rewrites the user's scenario files and must not reorder their keys. `validate --fix` would want the same thing, but turning it on by default would change the key order of every file it rewrites, which is a separate decision.
1 parent 9e1828d commit 4537732

51 files changed

Lines changed: 246 additions & 167 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yaml‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
steps:
2525
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
2626
- name: Install system dependencies
27-
# xkbcommon-x11/wayland/xcb-cursor: required to LINK rustmotion-studio (gpui).
27+
# xkbcommon-x11/wayland/xcb-cursor: required to LINK the studio (gpui).
2828
# clippy passes without them — it never links — so the failure only shows in tests.
2929
# asound: required by cpal, which rodio pulls in for preview audio
3030
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libxkbcommon-x11-dev libwayland-dev libxcb-cursor-dev libasound2-dev
@@ -33,21 +33,28 @@ jobs:
3333
components: clippy
3434
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
3535
- name: Clippy
36-
run: cargo clippy --workspace --all-targets -- -D warnings
36+
# `--features rustmotion/studio` n'est pas décoratif : le studio est
37+
# derrière un feature non-défaut depuis qu'il est un module de
38+
# `rustmotion`, et `--workspace` seul ne compilerait plus une seule de
39+
# ses 11 600 lignes. Sans ce drapeau, clippy reste vert sur du code que
40+
# personne ne vérifie plus.
41+
run: cargo clippy --workspace --all-targets --features rustmotion/studio -- -D warnings
3742

3843
test:
3944
runs-on: ubuntu-latest
4045
steps:
4146
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
4247
- name: Install system dependencies
43-
# xkbcommon-x11/wayland/xcb-cursor: required to LINK rustmotion-studio (gpui).
48+
# xkbcommon-x11/wayland/xcb-cursor: required to LINK the studio (gpui).
4449
# clippy passes without them — it never links — so the failure only shows in tests.
4550
# asound: required by cpal, which rodio pulls in for preview audio
4651
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libxkbcommon-x11-dev libwayland-dev libxcb-cursor-dev libasound2-dev
4752
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable channel, 2026-09-22
4853
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
4954
- name: Run tests
50-
run: cargo test --workspace
55+
# Même raison qu'au job clippy : sans le feature, les tests du studio
56+
# (`tests/studio_audit_ws_g.rs`) ne sont même pas compilés.
57+
run: cargo test --workspace --features rustmotion/studio
5158

5259
audit:
5360
runs-on: ubuntu-latest
@@ -73,7 +80,7 @@ jobs:
7380
# Via rayon-core <- exr <- image, reaches rustmotion-core/-components. Fix: >=0.9.20.
7481
# RUSTSEC-2026-0195, RUSTSEC-2026-0194 — quick-xml 0.38.4 / 0.39.4, DoS + quadratic runtime.
7582
# 0.38.4 via syntect reaches the published crates; 0.39.4 via dioxus-desktop/rfd is
76-
# rustmotion-studio-only (Linux/Wayland file dialogs). Fix: >=0.41.0.
83+
# studio-only (Linux/Wayland file dialogs). Fix: >=0.41.0.
7784
# RUSTSEC-2026-0285 — rustls 0.23.37, TLS 1.3 handshake level-boundary bug.
7885
# Via ureq, used by rustmotion/rustmotion-core for Google Fonts + Iconify fetches. Fix: >=0.23.45.
7986
# RUSTSEC-2026-0104, RUSTSEC-2026-0098, RUSTSEC-2026-0099, RUSTSEC-2026-0049 — rustls-webpki

‎.github/workflows/publish.yaml‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ jobs:
1313

1414
- name: Install system dependencies
1515
# Doit rester identique à ci.yaml : l'étape « Run tests » ci-dessous lance
16-
# `cargo test --workspace`, qui compile ET LIE rustmotion-studio. Sans ces
17-
# paquets l'édition de liens échoue et la release s'arrête avant toute
18-
# publication.
16+
# `cargo test --workspace --features rustmotion/studio`, qui compile ET
17+
# LIE le studio. Sans ces paquets l'édition de liens échoue et la
18+
# release s'arrête avant toute publication.
1919
# xkbcommon-x11/wayland/xcb-cursor : exigés par gpui, à l'édition de liens
2020
# seulement. clippy passe sans eux puisqu'il ne lie pas — la panne
2121
# n'apparaît donc qu'au job de test. C'est ce qui a cassé le premier CI du
@@ -65,6 +65,10 @@ jobs:
6565
# pouvait donc jamais reussir, et la release 0.6.0 s'est arretee la — apres
6666
# avoir publie rustmotion-core, definitivement.
6767
#
68-
# rustmotion-studio porte `publish = false` : cargo la saute tout seul.
68+
# Le studio n'est plus un paquet : c'est un module de `rustmotion` derrière
69+
# le feature `studio`, donc il part avec elle et il n'y a plus rien à
70+
# sauter. Ses dépendances (gpui-kit, gpui-component, rfd, rodio, palette)
71+
# sont optionnelles mais doivent rester résolvables depuis le registre,
72+
# sans quoi la publication de `rustmotion` échoue.
6973
- name: Publish all crates
7074
run: cargo publish --workspace --token ${{ secrets.CARGO_REGISTRY_TOKEN }}

‎Cargo.lock‎

Lines changed: 7 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ members = [
44
"crates/rustmotion-core",
55
"crates/rustmotion-components",
66
"crates/rustmotion",
7-
"crates/rustmotion-studio",
87
"crates/rustmotion-html",
98
]
109

11-
# Les cinq crates avancent ensemble. La version se change ici, et nulle part
10+
# Les quatre crates avancent ensemble. La version se change ici, et nulle part
1211
# ailleurs : elle était auparavant répétée dix fois — une par manifeste, plus une
1312
# par dépendance interne — et une seule oubliée fait échouer la publication après
1413
# que les précédentes soient parties, ce qui ne se rattrape pas.
@@ -34,6 +33,7 @@ debug-assertions = false
3433
overflow-checks = false
3534

3635
# The image crate's encoders are generic and monomorphize into the calling
37-
# crate, so the studio itself must be optimized for fast preview encoding.
38-
[profile.dev.package.rustmotion-studio]
36+
# crate, so the studio itself must be optimized for fast preview encoding. The
37+
# studio is a module of `rustmotion` now, so this covers the whole crate.
38+
[profile.dev.package.rustmotion]
3939
opt-level = 3

‎README.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ MIT-licensed: no licence key, no telemetry, no per-render billing. See [Non-goal
1414
cargo install rustmotion
1515
```
1616

17+
From this repository, without waiting for a release:
18+
19+
```bash
20+
cargo install --git https://github.com/LeadcodeDev/rustmotion
21+
```
22+
23+
Either command installs the `rustmotion` CLI. Add the `studio` feature to get
24+
the live preview window (`rustmotion-studio`) alongside it:
25+
26+
```bash
27+
cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio
28+
```
29+
30+
The studio is not in the default build because it pulls gpui and a native GUI
31+
toolchain, which do not build everywhere the CLI builds — a headless server
32+
being the obvious case.
33+
1734
**Requirements:** Rust toolchain + C++ compiler (for openh264). **Recommended:** `ffmpeg` CLI for 10-bit H.264 and H.265/VP9/ProRes/WebM/GIF output.
1835

1936
### Shell Completions
@@ -2155,11 +2172,12 @@ crates/
21552172
│ └── *.rs # one file per component (Painter implementation)
21562173
└── rustmotion/src/
21572174
├── cli/ # the `rustmotion` binary (clap subcommands)
2175+
├── studio/ # the `rustmotion-studio` binary (feature `studio`)
21582176
├── encode/ # video/audio encoders and muxing
21592177
└── loader.rs # JSON/HTML → ResolvedScenario
21602178
```
21612179

2162-
The `rustmotion` crate is where the binary lives — a crate with only a `[lib]` target installs nothing executable via `cargo install`.
2180+
The `rustmotion` crate is where both binaries live — a crate with only a `[lib]` target installs nothing executable via `cargo install`, and `cargo install --git <url>` refuses a repository in which more than one package declares a `[[bin]]`. That second constraint is why the studio is a module of this crate (`src/studio/`, behind the `studio` feature) rather than a package of its own.
21632181

21642182
## License
21652183

‎crates/rustmotion-components/src/lib.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// never lints here regardless of this attribute. This single crate-root
2020
// allow silences only that internal noise; it does not extend to any other
2121
// crate, so a hand-written construction in `rustmotion-html`,
22-
// `rustmotion-studio`, or this crate's own `tests/` integration suite (each
22+
// the studio, or this crate's own `tests/` integration suite (each
2323
// a separate compilation unit) still warns. Verified empirically before
2424
// relying on it: see the phase-B report for issue #333.
2525
#![allow(deprecated)]

‎crates/rustmotion-core/tests/audit_ws_k.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const KNOWN_INERT_FIELDS: &[(&str, &str)] = &[
128128
"target",
129129
"Not one of workstream K's 9 named findings — surfaced by this guard test itself, with \
130130
a caveat this test can't resolve on its own: Annotation.target is written by \
131-
rustmotion-studio (crates/rustmotion-studio/src/editor/annotations.rs:94) as raw JSON \
131+
the studio (crates/rustmotion/src/studio/editor/annotations.rs:94) as raw JSON \
132132
(a `\"target\": {...}` object literal, not a `.target` field access — this grep-based \
133133
check only matches Rust member access), so it may be consumed by the `apply-annotations` \
134134
Claude Code skill reading the scenario file's raw JSON rather than by any Rust code path. \

‎crates/rustmotion-studio/Cargo.toml‎

Lines changed: 0 additions & 28 deletions
This file was deleted.

‎crates/rustmotion/CLAUDE.md‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,20 +201,38 @@ crates/
201201
└── rustmotion/src/
202202
├── cli/ # Le binaire `rustmotion` (clap + sous-commandes)
203203
│ └── commands/ # validate, render, schema, info
204+
├── studio/ # Le binaire `rustmotion-studio` (feature `studio`)
204205
├── encode/ # Encodeurs vidéo/audio, mux
205206
└── loader.rs # Chargement JSON/HTML → ResolvedScenario
206207
```
207208

208-
> Le binaire vit dans la crate publiée `rustmotion` : une crate qui n'a qu'une
209-
> `[lib]` n'installe rien d'exécutable via `cargo install`. Il n'y a pas de
210-
> sous-commande `studio` — `rustmotion-studio` dépend de `rustmotion`, donc la
211-
> dépendance inverse serait un cycle. Le studio s'ouvre par son propre binaire.
212-
213-
### `rustmotion-studio` : aucun commentaire
209+
> Les deux binaires vivent dans la crate publiée `rustmotion` : une crate qui
210+
> n'a qu'une `[lib]` n'installe rien d'exécutable via `cargo install`, et
211+
> `cargo install --git <url>` **refuse** un dépôt où plus d'un paquet déclare un
212+
> `[[bin]]` (« multiple packages with binaries found » — ni `default-members` ni
213+
> `required-features` ne changent ce décompte). C'est pour ça que le studio est
214+
> un module de cette crate et non un paquet à part : en paquet, il dépendait de
215+
> `loader`/`encode`, donc en faire une dépendance de `rustmotion` était un cycle,
216+
> que cargo refuse même optionnel.
217+
>
218+
> ```bash
219+
> cargo install --git https://github.com/LeadcodeDev/rustmotion # CLI
220+
> cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio # CLI + studio
221+
> ```
222+
>
223+
> `studio` est hors du build par défaut : il tire gpui et une toolchain GUI, qui
224+
> ne compilent pas partout où le CLI compile. Il n'y a pas de **sous-commande**
225+
> `studio` non plus — elle devrait disparaître du `--help` selon le feature.
226+
> `--workspace` seul ne compile plus le studio : CI passe
227+
> `--features rustmotion/studio` à clippy et aux tests, sans quoi 11 600 lignes
228+
> cessent d'être vérifiées en restant vertes.
229+
230+
### `src/studio/` : aucun commentaire
214231
215232
Le code du studio ne porte **aucun commentaire** — ni `//`, ni `///`, ni `//!`.
216-
La règle ne vaut que pour cette crate : les quatre autres sont publiées, et
217-
vider leurs doc comments viderait leurs pages docs.rs.
233+
La règle suivait la crate ; elle suit maintenant le dossier
234+
`crates/rustmotion/src/studio/`, la fusion du paquet n'ayant rien changé à son
235+
bien-fondé.
218236
219237
Quand un commentaire semble nécessaire, c'est le signal qu'il faut **renommer
220238
la liaison ou extraire une fonction nommée** : l'explication va dans un

‎crates/rustmotion/Cargo.toml‎

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,30 @@ path = "src/lib.rs"
1212

1313
# Le binaire vit ici, et pas dans une crate à part, parce que c'est la seule
1414
# façon que `cargo install rustmotion` livre une commande : une crate qui n'a
15-
# qu'une `[lib]` installe une bibliothèque et rien d'exécutable. La
16-
# sous-commande `studio` ne peut pas y être : `rustmotion-studio` dépend de
17-
# `rustmotion`, donc la dépendance inverse — même optionnelle — est un cycle
18-
# que cargo refuse. Le studio garde son propre binaire `rustmotion-studio`.
15+
# qu'une `[lib]` installe une bibliothèque et rien d'exécutable.
16+
#
17+
# Le studio a rejoint cette crate (`src/studio/`) pour la même raison, poussée
18+
# d'un cran : `cargo install --git <url>` sans nom de paquet refuse un dépôt où
19+
# plus d'un paquet déclare un `[[bin]]` (« multiple packages with binaries
20+
# found »), et ni `default-members` ni `required-features` ne changent ce
21+
# décompte — vérifié, pas supposé. Un seul paquet peut donc porter des
22+
# binaires. Il était un paquet à part, qui dépendait de `rustmotion` pour
23+
# `loader` et `encode` : en faire une dépendance de `rustmotion` aurait été un
24+
# cycle, que cargo refuse même optionnel. Devenu un module, il n'y a plus deux
25+
# paquets, donc plus de cycle, et ses chemins `rustmotion::…` restent valides
26+
# tels quels grâce au `extern crate self as rustmotion` de `lib.rs`.
1927
[[bin]]
2028
name = "rustmotion"
2129
path = "src/bin/main.rs"
2230

31+
# Derrière `required-features`, hors du build par défaut : gpui et sa toolchain
32+
# GUI ne doivent pas entrer dans un `cargo install rustmotion` fait sur une
33+
# machine sans affichage, où elles peuvent ne pas compiler du tout.
34+
[[bin]]
35+
name = "rustmotion-studio"
36+
path = "src/bin/studio.rs"
37+
required-features = ["studio"]
38+
2339
[dependencies]
2440
rustmotion-core.workspace = true
2541
rustmotion-components = { workspace = true, features = ["lottie-native"] }
@@ -50,7 +66,40 @@ tiny-skia = "0.11"
5066
ureq = "3"
5167
rustfft = "6"
5268

69+
# Dépendances du studio (`src/studio/`), toutes optionnelles : le feature
70+
# `studio` les active, le build par défaut ne les voit pas.
71+
gpui-kit = { version = "=0.6.6", optional = true }
72+
gpui-component = { version = "0.6.6", optional = true }
73+
smallvec = { version = "1", optional = true }
74+
tokio = { version = "1", features = ["time"], optional = true }
75+
rfd = { version = "0.17", optional = true }
76+
rodio = { version = "0.22", default-features = false, features = [
77+
"playback",
78+
], optional = true }
79+
palette = { version = "0.7.6", default-features = false, features = [
80+
"std",
81+
], optional = true }
82+
5383
[features]
84+
## Compile et installe aussi le binaire `rustmotion-studio` :
85+
## `cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio`.
86+
## Hors du build par défaut parce qu'il tire gpui et sa toolchain GUI, qui ne
87+
## compilent pas partout où le CLI compile.
88+
##
89+
## `serde_json/preserve_order` en fait partie parce que le studio réécrit les
90+
## fichiers de scénario de l'utilisateur et ne doit pas en réordonner les clés.
91+
## C'est aussi ce que voudrait `validate --fix`, mais l'activer par défaut
92+
## changerait l'ordre de ses réécritures : à décider ailleurs qu'ici.
93+
studio = [
94+
"dep:gpui-kit",
95+
"dep:gpui-component",
96+
"dep:smallvec",
97+
"dep:tokio",
98+
"dep:rfd",
99+
"dep:rodio",
100+
"dep:palette",
101+
"serde_json/preserve_order",
102+
]
54103
# Opt-in: integration tests that shell out to a real ffmpeg binary.
55104
ffmpeg_integration = []
56105
## Re-expose native Lottie decoding (default-on). Activating this feature here

0 commit comments

Comments
 (0)