Skip to content

Commit 3918c06

Browse files
committed
feat: support sourceRoot selection
This is now possible to package from a mono-repository, check opentelemetry-python for a usecase.
1 parent 9042ecc commit 3918c06

File tree

3 files changed

+137
-10
lines changed

3 files changed

+137
-10
lines changed

src/default.nix

Whitespace-only changes.

src/main.rs

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@ struct Outputs {
7272
out: String,
7373
}
7474

75+
fn is_target_for_packaging(src_dir: PathBuf) -> bool {
76+
let has_cargo = src_dir.join("Cargo.toml").is_file();
77+
let cargo_lock = File::open(src_dir.join("Cargo.lock"));
78+
let has_cargo_lock = cargo_lock.is_ok();
79+
let has_cmake = src_dir.join("CMakeLists.txt").is_file();
80+
let has_go = src_dir.join("go.mod").is_file();
81+
let has_meson = src_dir.join("meson.build").is_file();
82+
let pyproject_toml = src_dir.join("pyproject.toml");
83+
let has_pyproject = pyproject_toml.is_file();
84+
let has_setuptools = src_dir.join("setup.py").is_file();
85+
86+
(has_cargo && has_cargo_lock)
87+
|| has_cmake
88+
|| has_go
89+
|| has_meson
90+
|| has_pyproject
91+
|| has_setuptools
92+
}
93+
7594
#[tokio::main]
7695
async fn main() -> Result<()> {
7796
run().await
@@ -394,7 +413,77 @@ async fn run() -> Result<()> {
394413
PathBuf::from(&src)
395414
};
396415

397-
let mut choices = Vec::new();
416+
// Let's first find all subdirectories that are compatible with our supported recipes.
417+
let root_src_dir = src_dir.clone();
418+
let mut open_subdirs = vec![(0, src_dir)];
419+
let mut sourceroot_candidates = vec![];
420+
while !open_subdirs.is_empty() {
421+
// If we call this, we have a non-empty vector.
422+
let (depth, target_dir) = open_subdirs.pop().unwrap();
423+
let subdirs = std::fs::read_dir(&target_dir).with_context(|| {
424+
format!("failed to list subdirectories of {}", target_dir.display())
425+
})?;
426+
427+
for maybe_subdir in subdirs {
428+
let subdir = maybe_subdir.context(format!(
429+
"unexpected io error while reading a subdirectory of {}",
430+
target_dir.to_string_lossy()
431+
))?;
432+
433+
let subdir_path = subdir.path();
434+
435+
// We want only subdirectories.
436+
if !subdir_path.is_dir() {
437+
continue;
438+
}
439+
440+
if is_target_for_packaging(subdir_path) {
441+
sourceroot_candidates.push(subdir.file_name());
442+
}
443+
444+
// Recurse into that subdir.
445+
if depth < 3 {
446+
open_subdirs.push((depth + 1, subdir_path));
447+
}
448+
}
449+
}
450+
451+
editor.set_helper(Some(Prompter::List(
452+
sourceroot_candidates
453+
.iter()
454+
.map(|sr| {
455+
sr.strip_prefix(&root_src_dir)
456+
.expect("Failed to strip the root source directory prefix")
457+
.to_string_lossy()
458+
.to_string()
459+
})
460+
.collect(),
461+
)));
462+
let choice = editor.readline(&prompt("Which subdirectory should we use?"))?;
463+
//let Some(Prompter::List(_choices)) = editor.helper_mut() else {
464+
// unreachable!();
465+
//};
466+
let src_dir = choice
467+
.parse()
468+
.ok()
469+
.and_then(|i: usize| sourceroot_candidates.get(i))
470+
.unwrap_or_else(|| &sourceroot_candidates[0]);
471+
let source_root = if src_dir != &root_src_dir {
472+
Some(src_dir)
473+
} else {
474+
None
475+
};
476+
let source_root_expr = match source_root {
477+
Some(subdir) => format!(
478+
"\nsourceRoot = \"source/{}\";",
479+
subdir
480+
.strip_prefix(&root_src_dir)
481+
.expect("Failed to strip the root source directory prefix")
482+
.to_string_lossy()
483+
),
484+
None => "".to_string(),
485+
};
486+
398487
let has_cargo = src_dir.join("Cargo.toml").is_file();
399488
let cargo_lock = File::open(src_dir.join("Cargo.lock"));
400489
let has_cargo_lock = cargo_lock.is_ok();
@@ -405,6 +494,7 @@ async fn run() -> Result<()> {
405494
let has_pyproject = pyproject_toml.is_file();
406495
let has_setuptools = src_dir.join("setup.py").is_file();
407496

497+
let mut choices = Vec::new();
408498
let rust_vendors = if has_cargo {
409499
if cargo_lock.map_or(true, |file| {
410500
BufReader::new(file)
@@ -454,7 +544,6 @@ async fn run() -> Result<()> {
454544
}
455545

456546
choices.push(BuildType::MkDerivation { rust: None });
457-
458547
editor.set_helper(Some(Prompter::Build(choices)));
459548
let choice = editor.readline(&prompt("How should this package be built?"))?;
460549
let Some(Prompter::Build(choices)) = editor.helper_mut() else {
@@ -564,7 +653,7 @@ async fn run() -> Result<()> {
564653
pname = {pname:?};
565654
version = {version:?};
566655
567-
src = {src_expr};
656+
src = {src_expr};{source_root_expr}
568657
569658
vendorHash = {hash};
570659
@@ -653,7 +742,7 @@ async fn run() -> Result<()> {
653742
version = {version:?};
654743
format = "{format}";
655744
656-
src = {src_expr};
745+
src = {src_expr};{source_root_expr}
657746
658747
"#,
659748
if application {
@@ -712,7 +801,7 @@ async fn run() -> Result<()> {
712801
pname = {pname:?};
713802
version = {version:?};
714803
715-
src = {src_expr};
804+
src = {src_expr};{source_root_expr}
716805
717806
cargoHash = "{hash}";
718807
@@ -742,7 +831,7 @@ async fn run() -> Result<()> {
742831
pname = "{pname}";
743832
version = "{version}";
744833
745-
src = {src_expr};
834+
src = {src_expr};{source_root_expr}
746835
747836
cargoLock = "#,
748837
)?;
@@ -762,6 +851,7 @@ async fn run() -> Result<()> {
762851
version = {version:?};
763852
764853
src = {src_expr};
854+
{source_root_expr}
765855
766856
"#,
767857
)?;
@@ -791,7 +881,7 @@ async fn run() -> Result<()> {
791881
pname = {pname:?};
792882
version = {version:?};
793883
794-
src = {src_expr};
884+
src = {src_expr};{source_root_expr}
795885
796886
cargoDeps = rustPlatform.fetchCargoTarball {{
797887
inherit src;
@@ -824,7 +914,7 @@ async fn run() -> Result<()> {
824914
pname = "{pname}";
825915
version = "{version}";
826916
827-
src = {src_expr};
917+
src = {src_expr};{source_root_expr}
828918
829919
cargoDeps = rustPlatform.importCargoLock "#,
830920
)?;
@@ -908,7 +998,7 @@ async fn run() -> Result<()> {
908998
}
909999

9101000
let mut desc = desc.trim_matches(|c: char| !c.is_alphanumeric()).to_owned();
911-
desc.get_mut(0 .. 1).map(str::make_ascii_uppercase);
1001+
desc.get_mut(0..1).map(str::make_ascii_uppercase);
9121002
write!(out, " ")?;
9131003
writedoc!(
9141004
out,
@@ -1022,5 +1112,5 @@ fn get_version(rev: &str) -> &str {
10221112
}
10231113

10241114
fn get_version_number(rev: &str) -> &str {
1025-
&rev[rev.find(char::is_numeric).unwrap_or_default() ..]
1115+
&rev[rev.find(char::is_numeric).unwrap_or_default()..]
10261116
}

src/prompt.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::{
1717
#[derive(Helper, Highlighter)]
1818
pub enum Prompter {
1919
Path(FilenameCompleter),
20+
List(Vec<String>),
2021
Revision(Revisions),
2122
NonEmpty,
2223
YesNo,
@@ -49,6 +50,17 @@ impl Completer for Prompter {
4950
Prompter::Revision(revisions) => Ok((0, revisions.completions.clone())),
5051
Prompter::NonEmpty => Ok((0, Vec::new())),
5152
Prompter::YesNo => Ok((0, Vec::new())),
53+
Prompter::List(choices) => Ok((
54+
0,
55+
choices
56+
.iter()
57+
.enumerate()
58+
.map(|(i, choice)| Pair {
59+
display: format!("{i} - {choice}"),
60+
replacement: i.to_string(),
61+
})
62+
.collect(),
63+
)),
5264
Prompter::Build(choices) => Ok((
5365
0,
5466
choices
@@ -114,6 +126,16 @@ impl Hinter for Prompter {
114126

115127
Prompter::YesNo => None,
116128

129+
Prompter::List(choices) => Some(SimpleHint(if line.is_empty() {
130+
format_args!(" ({})", choices[0])
131+
.blue()
132+
.italic()
133+
.to_string()
134+
} else if let Some(choice) = line.parse().ok().and_then(|i: usize| choices.get(i)) {
135+
format_args!(" ({choice})").blue().italic().to_string()
136+
} else {
137+
" press <tab> to see options".yellow().italic().to_string()
138+
})),
117139
Prompter::Build(choices) => Some(SimpleHint(if line.is_empty() {
118140
format_args!(" ({})", choices[0])
119141
.blue()
@@ -157,6 +179,21 @@ impl Validator for Prompter {
157179

158180
Prompter::YesNo => ValidationResult::Valid(None),
159181

182+
Prompter::List(choices) => {
183+
let input = ctx.input();
184+
if input.is_empty() {
185+
ValidationResult::Valid(Some(choices[0].to_string()))
186+
} else if let Some(choice) = input
187+
.parse::<usize>()
188+
.ok()
189+
.and_then(|choice| choices.get(choice))
190+
{
191+
ValidationResult::Valid(Some(format!(" - {choice}")))
192+
} else {
193+
ValidationResult::Invalid(None)
194+
}
195+
}
196+
160197
Prompter::Build(choices) => {
161198
let input = ctx.input();
162199
if input.is_empty() {

0 commit comments

Comments
 (0)