| 51 |
51 |
|
/// The version line itself stays first and unchanged. The image build asserts on
|
| 52 |
52 |
|
/// `alloy --version | grep -q '^alloy '` to prove it did not ship the stub main
|
| 53 |
53 |
|
/// from the Containerfile's cache split, and that assertion reads the first line.
|
|
54 |
+ |
/// `build/rpm/build.sh` reads the same line's second field to check the packaged
|
|
55 |
+ |
/// binary against the spec, so nothing may be inserted above it.
|
| 54 |
56 |
|
///
|
| 55 |
57 |
|
/// Starts with the version and not the name: clap prints the binary name ahead
|
| 56 |
|
- |
/// of this string, so naming it here gets `alloy alloy 0.0.0`.
|
| 57 |
|
- |
const NOTICE: &str = concat!(
|
| 58 |
|
- |
env!("CARGO_PKG_VERSION"),
|
| 59 |
|
- |
"\n",
|
| 60 |
|
- |
"Copyright (c) 2026 Make Creative, LLC\n",
|
| 61 |
|
- |
"License MIT: <https://opensource.org/licenses/MIT>\n",
|
| 62 |
|
- |
"This is free software: you are free to change and redistribute it.\n",
|
| 63 |
|
- |
"There is NO WARRANTY, to the extent permitted by law."
|
| 64 |
|
- |
);
|
|
58 |
+ |
/// of this string, so naming it here gets `alloy alloy 0.1.0`.
|
|
59 |
+ |
///
|
|
60 |
+ |
/// # The image line
|
|
61 |
+ |
///
|
|
62 |
+ |
/// Two versions exist and they are allowed to disagree. This crate's version is
|
|
63 |
+ |
/// the console's; the image carries its own `VERSION_ID`, and the hotfix channel
|
|
64 |
+ |
/// is precisely the mechanism that puts a newer console on an older image. Max
|
|
65 |
+ |
/// chose 2026-08-14 that one command reports both, so a bug report quoting
|
|
66 |
+ |
/// `alloy --version` distinguishes a layered machine from a rebuilt one without
|
|
67 |
+ |
/// anyone having to know to ask a second question.
|
|
68 |
+ |
///
|
|
69 |
+ |
/// Absent off an Alloy machine, where there is no image to have a version. See
|
|
70 |
+ |
/// [`image::version`].
|
|
71 |
+ |
///
|
|
72 |
+ |
/// Built at runtime rather than `concat!`ed, which is why this is a function:
|
|
73 |
+ |
/// the image version is read from a file. Interned so the `&'static str` clap
|
|
74 |
+ |
/// wants outlives the call.
|
|
75 |
+ |
fn notice() -> &'static str {
|
|
76 |
+ |
static NOTICE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
|
77 |
+ |
NOTICE.get_or_init(|| notice_text(env!("CARGO_PKG_VERSION"), image::version().as_deref()))
|
|
78 |
+ |
}
|
|
79 |
+ |
|
|
80 |
+ |
/// The assembly, split from the read so both shapes can be tested: the machine
|
|
81 |
+ |
/// that has an image and the dev host that does not.
|
|
82 |
+ |
fn notice_text(console: &str, image: Option<&str>) -> String {
|
|
83 |
+ |
let mut notice = String::from(console);
|
|
84 |
+ |
if let Some(image) = image {
|
|
85 |
+ |
notice.push_str("\nimage ");
|
|
86 |
+ |
notice.push_str(image);
|
|
87 |
+ |
}
|
|
88 |
+ |
notice.push_str(concat!(
|
|
89 |
+ |
"\n",
|
|
90 |
+ |
"Copyright (c) 2026 Make Creative, LLC\n",
|
|
91 |
+ |
"License MIT: <https://opensource.org/licenses/MIT>\n",
|
|
92 |
+ |
"This is free software: you are free to change and redistribute it.\n",
|
|
93 |
+ |
"There is NO WARRANTY, to the extent permitted by law."
|
|
94 |
+ |
));
|
|
95 |
+ |
notice
|
|
96 |
+ |
}
|
| 65 |
97 |
|
|
| 66 |
98 |
|
#[derive(Parser)]
|
| 67 |
|
- |
#[command(
|
| 68 |
|
- |
name = "alloy",
|
| 69 |
|
- |
about = "Alloy Console",
|
| 70 |
|
- |
version,
|
| 71 |
|
- |
long_version = NOTICE
|
| 72 |
|
- |
)]
|
|
99 |
+ |
// `long_version` is set on the built command in `main` instead of here: it
|
|
100 |
+ |
// reads a file now, and the derive attribute takes a const.
|
|
101 |
+ |
#[command(name = "alloy", about = "Alloy Console", version)]
|
| 73 |
102 |
|
struct Cli {
|
| 74 |
103 |
|
/// Theme id to render in (default: Akari, matched to the terminal background)
|
| 75 |
104 |
|
#[arg(long, global = true)]
|
| 268 |
297 |
|
// Hidden from `--help`, still resolved. clap would accept the verb either
|
| 269 |
298 |
|
// way, and a command that silently works while being undocumented is how
|
| 270 |
299 |
|
// documentation becomes wrong; the match arms below say why instead.
|
| 271 |
|
- |
let command = profile::hide_unavailable(<Cli as clap::CommandFactory>::command(), profile);
|
|
300 |
+ |
let command = profile::hide_unavailable(<Cli as clap::CommandFactory>::command(), profile)
|
|
301 |
+ |
.long_version(notice());
|
| 272 |
302 |
|
let cli = Cli::from_arg_matches(&command.get_matches())?;
|
| 273 |
303 |
|
|
| 274 |
304 |
|
// Refused as well as hidden, and through the same table, so the two
|
| 462 |
492 |
|
Command::Status { .. } => Ok(()),
|
| 463 |
493 |
|
}
|
| 464 |
494 |
|
}
|
|
495 |
+ |
|
|
496 |
+ |
#[cfg(test)]
|
|
497 |
+ |
mod tests {
|
|
498 |
+ |
use super::*;
|
|
499 |
+ |
|
|
500 |
+ |
/// The invariant two build steps depend on and neither can state here:
|
|
501 |
+ |
/// `Containerfile` asserts `alloy --version | grep -q '^alloy '` to prove
|
|
502 |
+ |
/// it did not ship the stub main, and `build/rpm/build.sh` reads the first
|
|
503 |
+ |
/// line's second field to check the packaged binary against the spec. Both
|
|
504 |
+ |
/// break silently if anything is inserted above the version.
|
|
505 |
+ |
#[test]
|
|
506 |
+ |
fn the_version_stays_alone_on_the_first_line() {
|
|
507 |
+ |
let notice = notice_text("0.1.0", Some("0.0"));
|
|
508 |
+ |
assert_eq!(notice.lines().next(), Some("0.1.0"));
|
|
509 |
+ |
}
|
|
510 |
+ |
|
|
511 |
+ |
/// A bug report quoting `alloy --version` has to distinguish a layered
|
|
512 |
+ |
/// machine from a rebuilt one, which is the whole reason both are printed.
|
|
513 |
+ |
#[test]
|
|
514 |
+ |
fn a_machine_with_an_image_reports_both_versions() {
|
|
515 |
+ |
let notice = notice_text("0.1.0", Some("0.0"));
|
|
516 |
+ |
let mut lines = notice.lines();
|
|
517 |
+ |
assert_eq!(lines.next(), Some("0.1.0"));
|
|
518 |
+ |
assert_eq!(lines.next(), Some("image 0.0"));
|
|
519 |
+ |
}
|
|
520 |
+ |
|
|
521 |
+ |
/// A dev host has no image, so the line is omitted rather than filled in
|
|
522 |
+ |
/// with a placeholder that would read as a real answer.
|
|
523 |
+ |
#[test]
|
|
524 |
+ |
fn a_dev_host_reports_the_console_and_says_nothing_about_an_image() {
|
|
525 |
+ |
let notice = notice_text("0.1.0", None);
|
|
526 |
+ |
assert_eq!(notice.lines().next(), Some("0.1.0"));
|
|
527 |
+ |
assert!(!notice.contains("image"), "{notice}");
|
|
528 |
+ |
}
|
|
529 |
+ |
|
|
530 |
+ |
/// The notice MIT asks to travel with the binary, in both shapes.
|
|
531 |
+ |
#[test]
|
|
532 |
+ |
fn the_copyright_notice_survives_either_shape() {
|
|
533 |
+ |
for image in [Some("0.0"), None] {
|
|
534 |
+ |
let notice = notice_text("0.1.0", image);
|
|
535 |
+ |
assert!(notice.contains("Make Creative, LLC"), "{notice}");
|
|
536 |
+ |
assert!(notice.contains("License MIT"), "{notice}");
|
|
537 |
+ |
}
|
|
538 |
+ |
}
|
|
539 |
+ |
}
|