Skip to content

Commit d438aaf

Browse files
committed
Switch to hand-written man pages with auto option sync
See the updates to `Justfile` for how to use this. Closes: #1428 Assisted-By: Claude Code (opus + sonnet) Signed-off-by: Colin Walters <[email protected]>
1 parent 011c737 commit d438aaf

Some content is hidden

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

50 files changed

+2051
-1420
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
- name: Run tests
4040
run: cargo test -- --nocapture --quiet
4141
- name: Manpage generation
42-
run: mkdir -p target/man && cargo run --features=docgen -- man --directory target/man
42+
run: cargo xtask update-generated
4343
- name: Clippy (gate on correctness and suspicous)
4444
run: make validate-rust
4545
fedora-container-tests:

.github/workflows/scheduled-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jobs:
8585
env:
8686
INPUT_VERSION: ${{ github.event.inputs.version }}
8787
run: |
88-
dnf -y install pandoc
88+
dnf -y install go-md2man
8989
cargo install cargo-edit
9090
9191
# If version is provided via workflow dispatch, validate and use it

Cargo.lock

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

Justfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,14 @@ run-container-external-tests:
1616

1717
unittest *ARGS:
1818
podman build --jobs=4 --target units -t localhost/bootc-units --build-arg=unitargs={{ARGS}} .
19+
20+
# Update all generated files (man pages and JSON schemas)
21+
#
22+
# This is the unified command that:
23+
# - Auto-discovers new CLI commands and creates man page templates
24+
# - Syncs CLI options from Rust code to existing man page templates
25+
# - Updates JSON schema files
26+
#
27+
# Use this after adding, removing, or modifying CLI options or schemas.
28+
update-generated:
29+
cargo run -p xtask update-generated

Makefile

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,28 @@ TAR_REPRODUCIBLE = tar --mtime="@${SOURCE_DATE_EPOCH}" --sort=name --owner=0 --g
1010
# (Note we should also make installation of the units conditional on the rhsm feature)
1111
CARGO_FEATURES ?= $(shell . /usr/lib/os-release; if echo "$$ID_LIKE" |grep -qF rhel; then echo rhsm; fi)
1212

13-
all:
13+
all: bin manpages
14+
15+
bin:
1416
cargo build --release --features "$(CARGO_FEATURES)"
1517

18+
# Generate man pages from markdown sources
19+
MAN5_SOURCES := $(wildcard docs/src/man/*.5.md)
20+
MAN8_SOURCES := $(wildcard docs/src/man/*.8.md)
21+
TARGETMAN := target/man
22+
MAN5_TARGETS := $(patsubst docs/src/man/%.5.md,$(TARGETMAN)/%.5,$(MAN5_SOURCES))
23+
MAN8_TARGETS := $(patsubst docs/src/man/%.8.md,$(TARGETMAN)/%.8,$(MAN8_SOURCES))
24+
25+
$(TARGETMAN)/%.5: docs/src/man/%.5.md
26+
@mkdir -p $(TARGETMAN)
27+
go-md2man -in $< -out $@
28+
29+
$(TARGETMAN)/%.8: docs/src/man/%.8.md
30+
@mkdir -p $(TARGETMAN)
31+
go-md2man -in $< -out $@
32+
33+
manpages: $(MAN5_TARGETS) $(MAN8_TARGETS)
34+
1635
STORAGE_RELATIVE_PATH ?= $(shell realpath -m -s --relative-to="$(prefix)/lib/bootc/storage" /sysroot/ostree/bootc/storage)
1736
install:
1837
install -D -m 0755 -t $(DESTDIR)$(prefix)/bin target/release/bootc
@@ -22,15 +41,12 @@ install:
2241
ln -s "$(STORAGE_RELATIVE_PATH)" "$(DESTDIR)$(prefix)/lib/bootc/storage"
2342
install -D -m 0755 crates/cli/bootc-generator-stub $(DESTDIR)$(prefix)/lib/systemd/system-generators/bootc-systemd-generator
2443
install -d $(DESTDIR)$(prefix)/lib/bootc/install
25-
# Support installing pre-generated man pages shipped in source tarball, to avoid
26-
# a dependency on pandoc downstream. But in local builds these end up in target/man,
27-
# so we honor that too.
28-
for d in man target/man; do \
29-
if test -d $$d; then \
30-
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man5 $$d/*.5; \
31-
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man8 $$d/*.8; \
32-
fi; \
33-
done
44+
if [ -n "$(MAN5_TARGETS)" ]; then \
45+
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man5 $(MAN5_TARGETS); \
46+
fi
47+
if [ -n "$(MAN8_TARGETS)" ]; then \
48+
install -D -m 0644 -t $(DESTDIR)$(prefix)/share/man/man8 $(MAN8_TARGETS); \
49+
fi
3450
install -D -m 0644 -t $(DESTDIR)/$(prefix)/lib/systemd/system systemd/*.service systemd/*.timer systemd/*.path systemd/*.target
3551
install -d -m 0755 $(DESTDIR)/$(prefix)/lib/systemd/system/multi-user.target.wants
3652
ln -s ../bootc-status-updated.path $(DESTDIR)/$(prefix)/lib/systemd/system/multi-user.target.wants/bootc-status-updated.path

contrib/packaging/bootc.spec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ BuildRequires: libzstd-devel
3636
BuildRequires: make
3737
BuildRequires: ostree-devel
3838
BuildRequires: openssl-devel
39+
BuildRequires: go-md2man
3940
%if 0%{?rhel}
4041
BuildRequires: rust-toolset
4142
%else
@@ -107,6 +108,8 @@ export SYSTEM_REINSTALL_BOOTC_INSTALL_PODMAN_PATH=%{system_reinstall_bootc_insta
107108
%cargo_build %cargo_args
108109
%endif
109110

111+
make manpages
112+
110113
%cargo_vendor_manifest
111114
# https://pagure.io/fedora-rust/rust-packaging/issue/33
112115
sed -i -e '/https:\/\//d' cargo-vendor.txt

crates/lib/src/cli.rs

Lines changed: 26 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,9 @@ use crate::utils::sigpolicy_from_opt;
3939
/// Shared progress options
4040
#[derive(Debug, Parser, PartialEq, Eq)]
4141
pub(crate) struct ProgressOptions {
42-
/// File descriptor number which must refer to an open pipe (anonymous or named).
42+
/// File descriptor number which must refer to an open pipe.
4343
///
44-
/// Interactive progress will be written to this file descriptor as "JSON lines"
45-
/// format, where each value is separated by a newline.
44+
/// Progress is written as JSON lines to this file descriptor.
4645
#[clap(long, hide = true)]
4746
pub(crate) progress_fd: Option<RawProgressFd>,
4847
}
@@ -69,23 +68,19 @@ pub(crate) struct UpgradeOpts {
6968

7069
/// Check if an update is available without applying it.
7170
///
72-
/// This only downloads an updated manifest and image configuration (i.e. typically kilobyte-sized metadata)
73-
/// as opposed to the image layers.
71+
/// This only downloads updated metadata, not the full image layers.
7472
#[clap(long, conflicts_with = "apply")]
7573
pub(crate) check: bool,
7674

7775
/// Restart or reboot into the new target image.
7876
///
79-
/// Currently, this option always reboots. In the future this command
80-
/// will detect the case where no kernel changes are queued, and perform
81-
/// a userspace-only restart.
77+
/// Currently, this always reboots. Future versions may support userspace-only restart.
8278
#[clap(long, conflicts_with = "check")]
8379
pub(crate) apply: bool,
8480

8581
/// Configure soft reboot behavior.
8682
///
87-
/// 'required' will fail if soft reboot is not available.
88-
/// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
83+
/// 'required' fails if soft reboot unavailable, 'auto' falls back to regular reboot.
8984
#[clap(long = "soft-reboot", conflicts_with = "check")]
9085
pub(crate) soft_reboot: Option<SoftRebootMode>,
9186

@@ -102,16 +97,13 @@ pub(crate) struct SwitchOpts {
10297

10398
/// Restart or reboot into the new target image.
10499
///
105-
/// Currently, this option always reboots. In the future this command
106-
/// will detect the case where no kernel changes are queued, and perform
107-
/// a userspace-only restart.
100+
/// Currently, this always reboots. Future versions may support userspace-only restart.
108101
#[clap(long)]
109102
pub(crate) apply: bool,
110103

111104
/// Configure soft reboot behavior.
112105
///
113-
/// 'required' will fail if soft reboot is not available.
114-
/// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
106+
/// 'required' fails if soft reboot unavailable, 'auto' falls back to regular reboot.
115107
#[clap(long = "soft-reboot")]
116108
pub(crate) soft_reboot: Option<SoftRebootMode>,
117109

@@ -161,8 +153,7 @@ pub(crate) struct RollbackOpts {
161153

162154
/// Configure soft reboot behavior.
163155
///
164-
/// 'required' will fail if soft reboot is not available.
165-
/// 'auto' will use soft reboot if available, otherwise fall back to regular reboot.
156+
/// 'required' fails if soft reboot unavailable, 'auto' falls back to regular reboot.
166157
#[clap(long = "soft-reboot")]
167158
pub(crate) soft_reboot: Option<SoftRebootMode>,
168159
}
@@ -279,14 +270,6 @@ pub(crate) enum InstallOpts {
279270
PrintConfiguration,
280271
}
281272

282-
/// Options for man page generation
283-
#[derive(Debug, Parser, PartialEq, Eq)]
284-
pub(crate) struct ManOpts {
285-
#[clap(long)]
286-
/// Output to this directory
287-
pub(crate) directory: Utf8PathBuf,
288-
}
289-
290273
/// Subcommands which can be executed as part of a container build.
291274
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
292275
pub(crate) enum ContainerOpts {
@@ -540,6 +523,9 @@ pub(crate) enum InternalsOpts {
540523
#[clap(long)]
541524
merge: bool,
542525
},
526+
#[cfg(feature = "docgen")]
527+
/// Dump CLI structure as JSON for documentation generation
528+
DumpCliJson,
543529
}
544530

545531
#[derive(Debug, clap::Subcommand, PartialEq, Eq)]
@@ -625,70 +611,26 @@ pub(crate) enum Opt {
625611
///
626612
/// Only changes to the `spec` section are honored.
627613
Edit(EditOpts),
628-
/// Display status
629-
///
630-
/// If standard output is a terminal, this will output a description of the bootc system state.
631-
/// If standard output is not a terminal, output a YAML-formatted object using a schema
632-
/// intended to match a Kubernetes resource that describes the state of the booted system.
633-
///
634-
/// ## Parsing output via programs
635-
///
636-
/// Either the default YAML format or `--format=json` can be used. Do not attempt to
637-
/// explicitly parse the output of `--format=humanreadable` as it will very likely
638-
/// change over time.
614+
/// Display status.
639615
///
640-
/// ## Programmatically detecting whether the system is deployed via bootc
641-
///
642-
/// Invoke e.g. `bootc status --json`, and check if `status.booted` is not `null`.
616+
/// Shows bootc system state. Outputs YAML by default, human-readable if terminal detected.
643617
Status(StatusOpts),
644-
/// Adds a transient writable overlayfs on `/usr` that will be discarded on reboot.
645-
///
646-
/// ## Use cases
647-
///
648-
/// A common pattern is wanting to use tracing/debugging tools, such as `strace`
649-
/// that may not be in the base image. A system package manager such as `apt` or
650-
/// `dnf` can apply changes into this transient overlay that will be discarded on
651-
/// reboot.
652-
///
653-
/// ## /etc and /var
654-
///
655-
/// However, this command has no effect on `/etc` and `/var` - changes written
656-
/// there will persist. It is common for package installations to modify these
657-
/// directories.
658-
///
659-
/// ## Unmounting
660-
///
661-
/// Almost always, a system process will hold a reference to the open mount point.
662-
/// You can however invoke `umount -l /usr` to perform a "lazy unmount".
618+
/// Add a transient writable overlayfs on `/usr`.
663619
///
620+
/// Allows temporary package installation that will be discarded on reboot.
664621
#[clap(alias = "usroverlay")]
665622
UsrOverlay,
666623
/// Install the running container to a target.
667624
///
668-
/// ## Understanding installations
669-
///
670-
/// OCI containers are effectively layers of tarballs with JSON for metadata; they
671-
/// cannot be booted directly. The `bootc install` flow is a highly opinionated
672-
/// method to take the contents of the container image and install it to a target
673-
/// block device (or an existing filesystem) in such a way that it can be booted.
674-
///
675-
/// For example, a Linux partition table and filesystem is used, and the bootloader and kernel
676-
/// embedded in the container image are also prepared.
677-
///
678-
/// A bootc installed container currently uses OSTree as a backend, and this sets
679-
/// it up such that a subsequent `bootc upgrade` can perform in-place updates.
680-
///
681-
/// An installation is not simply a copy of the container filesystem, but includes
682-
/// other setup and metadata.
625+
/// Takes a container image and installs it to disk in a bootable format.
683626
#[clap(subcommand)]
684627
Install(InstallOpts),
685628
/// Operations which can be executed as part of a container build.
686629
#[clap(subcommand)]
687630
Container(ContainerOpts),
688-
/// Operations on container images
631+
/// Operations on container images.
689632
///
690-
/// Stability: This interface is not declared stable and may change or be removed
691-
/// at any point in the future.
633+
/// Stability: This interface may change in the future.
692634
#[clap(subcommand, hide = true)]
693635
Image(ImageOpts),
694636
/// Execute the given command in the host mount namespace
@@ -704,9 +646,6 @@ pub(crate) enum Opt {
704646
#[clap(subcommand)]
705647
#[clap(hide = true)]
706648
Internals(InternalsOpts),
707-
#[clap(hide(true))]
708-
#[cfg(feature = "docgen")]
709-
Man(ManOpts),
710649
}
711650

712651
/// Ensure we've entered a mount namespace, so that we can remount
@@ -1499,6 +1438,14 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
14991438
}
15001439
#[cfg(feature = "rhsm")]
15011440
InternalsOpts::PublishRhsmFacts => crate::rhsm::publish_facts(&root).await,
1441+
#[cfg(feature = "docgen")]
1442+
InternalsOpts::DumpCliJson => {
1443+
use clap::CommandFactory;
1444+
let cmd = Opt::command();
1445+
let json = crate::cli_json::dump_cli_json(&cmd)?;
1446+
println!("{}", json);
1447+
Ok(())
1448+
}
15021449
InternalsOpts::DirDiff {
15031450
pristine_etc,
15041451
current_etc,
@@ -1522,8 +1469,6 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
15221469
Ok(())
15231470
}
15241471
},
1525-
#[cfg(feature = "docgen")]
1526-
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
15271472
Opt::State(opts) => match opts {
15281473
StateOpts::WipeOstree => {
15291474
let sysroot = ostree::Sysroot::new_default();

0 commit comments

Comments
 (0)