Skip to content

Commit 3d2a54e

Browse files
committed
feat: link crates referencing metric_doc macro at doc gen runtime
1 parent 534898e commit 3d2a54e

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

datafusion/core/build.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
use std::env;
19+
use std::fs;
20+
use std::io::{Read, Write};
21+
use std::path::Path;
22+
23+
fn main() {
24+
let out_dir = env::var("OUT_DIR").unwrap();
25+
let dest_path = Path::new(&out_dir).join("link_metrics.rs");
26+
let mut f = fs::File::create(&dest_path).unwrap();
27+
28+
// Identify crates that use #[metric_doc]
29+
// We scan sibling directories in `datafusion/`
30+
let core_crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
31+
let datafusion_dir = Path::new(&core_crate_dir).parent().unwrap();
32+
33+
let mut crates_with_metrics = Vec::new();
34+
35+
// Read Cargo.toml to check dependencies
36+
let cargo_toml_path = Path::new(&core_crate_dir).join("Cargo.toml");
37+
let mut cargo_toml_content = String::new();
38+
if let Ok(mut file) = fs::File::open(&cargo_toml_path) {
39+
file.read_to_string(&mut cargo_toml_content).unwrap();
40+
}
41+
42+
if let Ok(entries) = fs::read_dir(datafusion_dir) {
43+
for entry in entries.flatten() {
44+
let path = entry.path();
45+
if path.is_dir() {
46+
// Check if it's a crate (has Cargo.toml)
47+
if path.join("Cargo.toml").exists() {
48+
let dir_name = path.file_name().unwrap().to_str().unwrap();
49+
let crate_name = format!("datafusion-{}", dir_name);
50+
51+
// Skip self (core) and macros (definition only)
52+
if dir_name == "core" || dir_name == "macros" {
53+
continue;
54+
}
55+
56+
// Skip if not a dependency in Cargo.toml
57+
// This is a rough check
58+
if !cargo_toml_content.contains(&format!("{} =", crate_name))
59+
&& !cargo_toml_content.contains(&format!("\"{}\"", crate_name))
60+
{
61+
continue;
62+
}
63+
64+
if has_metric_doc(&path) {
65+
crates_with_metrics.push(crate_name);
66+
}
67+
}
68+
}
69+
}
70+
}
71+
72+
// Sort for deterministic output
73+
crates_with_metrics.sort();
74+
75+
writeln!(
76+
f,
77+
"/// Automatically generated by build.rs to link crates with metrics"
78+
)
79+
.unwrap();
80+
writeln!(f, "pub fn link_metrics() {{").unwrap();
81+
for krate in crates_with_metrics {
82+
let krate_snake = krate.replace("-", "_");
83+
84+
let is_optional = cargo_toml_content
85+
.lines()
86+
.any(|line| line.contains(&krate) && line.contains("optional = true"));
87+
88+
if is_optional {
89+
writeln!(f, " #[cfg(feature = \"{}\")]", krate_snake).unwrap();
90+
}
91+
92+
writeln!(f, " {{").unwrap();
93+
writeln!(f, " // Force link {}", krate).unwrap();
94+
writeln!(f, " #[allow(unused_imports)]").unwrap();
95+
writeln!(f, " use {} as _;", krate_snake).unwrap();
96+
writeln!(f, " }}").unwrap();
97+
}
98+
writeln!(f, "}}").unwrap();
99+
100+
println!("cargo:rerun-if-changed=build.rs");
101+
println!("cargo:rerun-if-changed=Cargo.toml");
102+
}
103+
104+
fn has_metric_doc(dir: &Path) -> bool {
105+
if let Ok(entries) = fs::read_dir(dir) {
106+
for entry in entries.flatten() {
107+
let path = entry.path();
108+
if path.is_dir() {
109+
// Skip target, .git, etc
110+
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
111+
if name == "target" || name.starts_with('.') {
112+
continue;
113+
}
114+
}
115+
if has_metric_doc(&path) {
116+
return true;
117+
}
118+
} else if let Some(ext) = path.extension() {
119+
if ext == "rs" {
120+
if let Ok(mut file) = fs::File::open(&path) {
121+
let mut content = String::new();
122+
if file.read_to_string(&mut content).is_ok() {
123+
if content.contains("#[metric_doc") {
124+
return true;
125+
}
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}
132+
false
133+
}

datafusion/core/src/bin/print_metric_docs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ use std::collections::HashSet;
2323
use datafusion_doc::metric_doc_sections::{
2424
ExecDoc, MetricDoc, MetricDocPosition, exec_docs, metric_docs,
2525
};
26-
use datafusion_execution as _; // Link metrics defined in execution crate.
27-
use datafusion_physical_plan as _; // Link metrics and execs defined in physical plan.
2826

2927
fn main() -> std::io::Result<()> {
28+
datafusion::doc::link_metrics();
29+
3030
let mut content = String::new();
3131
let mut metrics: Vec<&MetricDoc> = metric_docs().collect();
3232
metrics.sort_by(|a, b| a.name.cmp(b.name));

datafusion/core/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,11 @@ pub mod test;
900900
mod schema_equivalence;
901901
pub mod test_util;
902902

903+
/// Documentation generation helpers
904+
pub mod doc {
905+
include!(concat!(env!("OUT_DIR"), "/link_metrics.rs"));
906+
}
907+
903908
#[cfg(doctest)]
904909
doc_comment::doctest!("../../../README.md", readme_example_test);
905910

0 commit comments

Comments
 (0)