1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
//! Active toolchain capture for the §3.3 envelope's
//! `results.lihaaf.toolchain` field (§3.4 of
//! `docs/compatibility-plan.md`).
//!
//! ## What this module owns
//!
//! - [`capture_active_toolchain`] — the public entry that the compat
//! driver calls with the `--compat-root` path. Invokes `rustup show
//! active-toolchain` with cwd set to the pilot fork so resolution
//! honors the fork's `rust-toolchain.toml`, returns the trimmed first
//! line, or falls back to the rustc release line on rustup absence /
//! subprocess error.
//! - [`parse_active_toolchain`] — `pub(crate)` byte-level parser that
//! isolates the "first line, trim at first ASCII space" rule from the
//! subprocess plumbing so the rule is unit-testable without spawning.
//! - [`capture_with_program`] — `pub(crate)` variant of the public entry
//! that takes the program name as a parameter. The public entry calls
//! it with `"rustup"`; the integration tests call it with a path that
//! does not exist to exercise the fallback path without mutating
//! `PATH`.
//!
//! ## Why subprocess `rustup`, not `cargo --version` or manifest parsing
//!
//! §3.4 of the compatibility plan names `rustup show active-toolchain`
//! specifically. Parsing
//! `rust-toolchain.toml` directly would re-implement rustup's resolution
//! rules (the `channel` / `path` / `components` keys, the upward search,
//! the `RUSTUP_TOOLCHAIN` override) and drift from rustup's behavior.
//! `cargo --version` reports the lihaaf-build toolchain, not the active
//! one. The subprocess is cheap (rustup reads one manifest file and
//! prints two short lines) and stays correct as rustup evolves.
//!
//! ## Why the rustc release-line fallback
//!
//! The §3.3 envelope schema requires a non-empty `toolchain` field.
//! Adopters who run lihaaf
//! without rustup installed (e.g. distro rustc, custom CI images) must
//! still produce a parseable envelope. The rustc release line is what
//! the v0.1 freshness machinery already captures (see
//! `crate::toolchain::Toolchain::release_line`), so the fallback adds
//! zero new subprocess shape.
use Path;
use Command;
use crateError;
/// Capture the active toolchain via `rustup show active-toolchain`.
///
/// Returns the trimmed first line of rustup's stdout with any trailing
/// ` (default)` suffix dropped — e.g. `"stable-x86_64-unknown-linux-gnu
/// (default)"` becomes `"stable-x86_64-unknown-linux-gnu"`. On rustup
/// absence (spawn error) or a non-zero exit, falls back to the rustc
/// release line via `crate::toolchain::capture` so the §3.3 envelope's
/// `results.lihaaf.toolchain` field is always non-empty.
///
/// ## cwd contract
///
/// `compat_root` is the pilot fork's checkout root (the
/// `--compat-root` value). The subprocess inherits this as its cwd so
/// `rustup show` resolves against the fork's `rust-toolchain.toml`
/// rather than the caller's cwd. Without this, running `cargo lihaaf
/// --compat --compat-root /other/crate` from an unrelated directory
/// would record the CALLER's pinned toolchain, not the fork's.
///
/// ## Errors
///
/// Returns the propagated [`Error`] from `crate::toolchain::capture`
/// only when BOTH rustup subprocess and rustc subprocess fail — the
/// caller's environment has neither toolchain manager available, which
/// is fatal at the v0.1 freshness layer regardless of compat mode.
/// Internal variant of [`capture_active_toolchain`] that takes the
/// program name as a parameter. The public entry calls this with
/// `"rustup"`; integration tests call it with a path that does not
/// exist to exercise the fallback path without mutating `PATH`.
///
/// `compat_root` is forwarded to the subprocess as its working
/// directory — the same cwd contract as the public entry. The fallback
/// `crate::toolchain::capture` reads no manifest and is unaffected by
/// cwd.
/// Extract the active-toolchain identifier from rustup's stdout.
///
/// Returns the first non-empty line trimmed of leading/trailing ASCII
/// whitespace and truncated at the first ASCII space — the trim
/// drops the `(default)` annotation rustup appends when the active
/// toolchain is the user's `rustup default`. Returns `None` if every
/// line is empty after trimming (a defensive guard against future
/// rustup output formats).