-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Support for descriptions on third-party subcommands in cargo --list
#10663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "cargo-subcommand-metadata" | ||
version = "0.1.0" | ||
edition = "2021" | ||
license = "MIT OR Apache-2.0" | ||
repository = "https://github.com/rust-lang/cargo" | ||
description = "Embed metadata into a Cargo subcommand, so that `cargo --list` can show a description of the subcommand" | ||
|
||
[target.'cfg(target_os = "linux")'.dependencies] | ||
memmap = { version = "0.7", optional = true } | ||
object = { version = "0.28", optional = true } | ||
|
||
[features] | ||
parse = ["memmap", "object"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#[cfg(feature = "parse")] | ||
pub mod parse; | ||
|
||
/// Cargo's name for the purpose of ELF notes. | ||
/// | ||
/// The `name` field of an ELF note is designated to hold the entry's "owner" or | ||
/// "originator". No formal mechanism exists for avoiding name conflicts. By | ||
/// convention, vendors use their own name such as "XYZ Computer Company". | ||
pub const ELF_NOTE_NAME: &str = "rust-lang/cargo"; | ||
|
||
/// Values used by Cargo as the `type` of its ELF notes. | ||
/// | ||
/// Each originator controls its own note types. Multiple interpretations of a | ||
/// single type value can exist. A program must recognize both the `name` and | ||
/// the `type` to understand a descriptor. | ||
#[repr(i32)] | ||
#[non_exhaustive] | ||
pub enum ElfNoteType { | ||
// DESCRIP | ||
Description = 0xDE5C819, | ||
} | ||
|
||
/// Embed a description into a compiled Cargo subcommand, to be shown by `cargo | ||
/// --list`. | ||
/// | ||
/// The following restrictions apply to a subcommand description: | ||
/// | ||
/// - String length can be at most 280 bytes in UTF-8, although much shorter is | ||
/// better. | ||
/// - Must not contain the characters `\n`, `\r`, or `\x1B` (ESC). | ||
/// | ||
/// Please consider running `cargo --list` and following the style of the | ||
/// existing descriptions of the built-in Cargo subcommands. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// // subcommand's main.rs | ||
/// | ||
/// cargo_subcommand_metadata::description! { | ||
/// "Draw a spiffy visualization of things" | ||
/// } | ||
/// | ||
/// fn main() { | ||
/// /* … */ | ||
/// } | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! description { | ||
($description:expr) => { | ||
const _: () = { | ||
const CARGO_SUBCOMMAND_DESCRIPTION: &str = $description; | ||
|
||
assert!( | ||
CARGO_SUBCOMMAND_DESCRIPTION.len() <= 280, | ||
"subcommand description too long, must be at most 280", | ||
); | ||
|
||
#[cfg(target_os = "linux")] | ||
const _: () = { | ||
#[repr(C)] | ||
struct ElfNote { | ||
namesz: u32, | ||
descsz: u32, | ||
ty: $crate::ElfNoteType, | ||
|
||
name: [u8; $crate::ELF_NOTE_NAME.len()], | ||
// At least 1 to nul-terminate the string as is convention | ||
// (though not required), plus zero padding to a multiple of 4 | ||
// bytes. | ||
name_padding: [$crate::private::Padding; | ||
1 + match ($crate::ELF_NOTE_NAME.len() + 1) % 4 { | ||
0 => 0, | ||
r => 4 - r, | ||
}], | ||
|
||
desc: [u8; CARGO_SUBCOMMAND_DESCRIPTION.len()], | ||
// Zero padding to a multiple of 4 bytes. | ||
desc_padding: [$crate::private::Padding; | ||
match CARGO_SUBCOMMAND_DESCRIPTION.len() % 4 { | ||
0 => 0, | ||
r => 4 - r, | ||
}], | ||
} | ||
|
||
#[used] | ||
#[link_section = ".note.cargo.subcommand"] | ||
static ELF_NOTE: ElfNote = ElfNote { | ||
namesz: $crate::ELF_NOTE_NAME.len() as u32 + 1, | ||
descsz: CARGO_SUBCOMMAND_DESCRIPTION.len() as u32, | ||
ty: $crate::ElfNoteType::Description, | ||
name: unsafe { *$crate::ELF_NOTE_NAME.as_ptr().cast() }, | ||
name_padding: $crate::private::padding(), | ||
desc: unsafe { *CARGO_SUBCOMMAND_DESCRIPTION.as_ptr().cast() }, | ||
desc_padding: $crate::private::padding(), | ||
}; | ||
}; | ||
}; | ||
}; | ||
} | ||
|
||
// Implementation details. Not public API. | ||
#[doc(hidden)] | ||
pub mod private { | ||
#[derive(Copy, Clone)] | ||
#[repr(u8)] | ||
pub enum Padding { | ||
Zero = 0, | ||
} | ||
|
||
pub const fn padding<const N: usize>() -> [Padding; N] { | ||
[Padding::Zero; N] | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
use std::path::Path; | ||
|
||
pub fn description(path: &Path) -> Option<String> { | ||
implementation::description(path) | ||
} | ||
|
||
#[cfg(target_os = "linux")] | ||
mod implementation { | ||
use memmap::Mmap; | ||
use object::endian::LittleEndian; | ||
use object::read::elf::{ElfFile64, FileHeader, SectionHeader}; | ||
use std::fs::File; | ||
use std::path::Path; | ||
use std::str; | ||
|
||
pub(super) fn description(path: &Path) -> Option<String> { | ||
let executable_file = File::open(path).ok()?; | ||
let data = &*unsafe { Mmap::map(&executable_file) }.ok()?; | ||
let elf = ElfFile64::<LittleEndian>::parse(data).ok()?; | ||
let endian = elf.endian(); | ||
let file_header = elf.raw_header(); | ||
let section_headers = file_header.section_headers(endian, data).ok()?; | ||
let string_table = file_header | ||
.section_strings(endian, data, section_headers) | ||
.ok()?; | ||
|
||
let mut description = None; | ||
for section_header in section_headers { | ||
if section_header.name(endian, string_table).ok() == Some(b".note.cargo.subcommand") { | ||
if let Ok(Some(mut notes)) = section_header.notes(endian, data) { | ||
while let Ok(Some(note)) = notes.next() { | ||
if note.name() == crate::ELF_NOTE_NAME.as_bytes() | ||
&& note.n_type(endian) == crate::ElfNoteType::Description as u32 | ||
{ | ||
if description.is_some() { | ||
return None; | ||
} | ||
description = Some(note.desc()); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
let description: &[u8] = description?; | ||
let description: &str = str::from_utf8(description).ok()?; | ||
if description.len() > 280 || description.contains(&['\n', '\r', '\x1B']) { | ||
return None; | ||
} | ||
|
||
Some(description.to_owned()) | ||
} | ||
} | ||
|
||
#[cfg(not(target_os = "linux"))] | ||
mod implementation { | ||
use std::path::Path; | ||
|
||
pub(super) fn description(_path: &Path) -> Option<String> { | ||
None | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: maybe
cargo_subcommand_metadata::description!();
with no string argument could embedenv!(CARGO_PKG_DESCRIPTION)
by default.