in eng/tools/generate_api_report/src/main.rs [13:132]
fn main() -> Result<(), Box<dyn Error>> {
// Verify we're running from the repository root
if !Path::new("eng/tools/generate_api_report").exists() {
return Err(
"This tool must be run from the root of the azure-sdk-for-rust repository. Use: cargo run --manifest-path eng/tools/generate_api_report/Cargo.toml -- --package {package_name}".into(),
);
}
// Get the package name from command-line arguments
let args: Vec<String> = env::args().collect();
if args.len() != 3 || args[1] != "--package" {
eprintln!("Usage: {} --package <package_name>", args[0]);
std::process::exit(1);
}
let package_name = &args[2];
let path_str = format!("./target/doc/{}.json", package_name);
let path = Path::new(&path_str);
// Call cargo +nightly rustdoc to generate the JSON file
let channel = env!("TOOLCHAIN_CHANNEL");
let mut command = Command::new("cargo");
command
.arg(format!("+{channel}"))
.arg("rustdoc")
.arg("-Z")
.arg("unstable-options")
.arg("--output-format")
.arg("json")
.arg("--package")
.arg(package_name)
.arg("--all-features");
println!(
"Running: {} {}",
command.get_program().to_string_lossy(),
command
.get_args()
.collect::<Vec<&OsStr>>()
.join(OsStr::new(" "))
.to_string_lossy(),
);
let output = command.output()?;
if !output.status.success() {
eprintln!(
"Failed to generate JSON file: {}",
String::from_utf8_lossy(&output.stderr)
);
std::process::exit(1);
}
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
println!("Processing rustdoc output for package: {}", package_name);
let mut root: Crate = serde_json::from_str(&contents)?;
// Remove items
// 1. with item.inner.impl.is_synthetic set to true - [auto traits]
// 2. with item.inner.impl.blanket_impl is not null - [blanket impls]
root.index.retain(|_id, item| {
if let rustdoc_types::ItemEnum::Impl(impl_item) = &item.inner {
// Filter out auto traits
if impl_item.is_synthetic {
return false;
}
// Filter out blanket implementations
if impl_item.blanket_impl.is_some() {
return false;
}
}
true
});
// Clear unnecessary fields in the Crate structure
// 1. external_crates
// 2. span in all items
root.external_crates.clear();
for (_id, item) in root.index.iter_mut() {
// Reset span to default empty value
item.span = Default::default();
}
// Navigate to Cargo.toml and get the path for the package
let cargo_toml_path = Path::new("Cargo.toml");
let cargo_toml_content = std::fs::read_to_string(cargo_toml_path)?;
let cargo_toml: toml::Value = toml::from_str(&cargo_toml_content)?;
let package_path = cargo_toml
.get("workspace")
.and_then(|ws| ws.get("members"))
.and_then(|members| members.as_array())
.and_then(|members| {
members.iter().find_map(|member| {
if member.as_str()?.ends_with(package_name) {
Some(member.as_str()?.to_string())
} else {
None
}
})
})
.ok_or("Package path not found in Cargo.toml")?;
// Create the review/ folder under the obtained path if it doesn't exist
let review_folder_path = Path::new(&package_path).join("review");
if !review_folder_path.exists() {
std::fs::create_dir_all(&review_folder_path)?;
}
// Create the package_name.rust.json in the review/ folder
let output_path_str = review_folder_path.join(format!("{}.rust.json", package_name));
let output_path = Path::new(&output_path_str);
let mut output_file = File::create(output_path)?;
serde_json::to_writer(&mut output_file, &root)?;
println!("File has been generated at: {}", output_path_str.display());
Ok(())
}