Skip to content

Commit e13d16a

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 e13d16a

File tree

3 files changed

+134
-10
lines changed

3 files changed

+134
-10
lines changed

src/default.nix

Whitespace-only changes.

src/main.rs

Lines changed: 97 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,74 @@ 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.to_path_buf()));
447+
}
448+
}
449+
}
450+
451+
editor.set_helper(Some(Prompter::List(
452+
sourceroot_candidates
453+
.iter()
454+
.map(|sr| {
455+
PathBuf::from(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 src_dir = &PathBuf::from(choice
464+
.parse()
465+
.ok()
466+
.and_then(|i: usize| sourceroot_candidates.get(i))
467+
.unwrap_or_else(|| &sourceroot_candidates[0]));
468+
let source_root = if src_dir != &root_src_dir {
469+
Some(src_dir)
470+
} else {
471+
None
472+
};
473+
let source_root_expr = match source_root {
474+
Some(subdir) => format!(
475+
"\nsourceRoot = \"source/{}\";",
476+
subdir
477+
.strip_prefix(&root_src_dir)
478+
.expect("Failed to strip the root source directory prefix")
479+
.to_string_lossy()
480+
),
481+
None => "".to_string(),
482+
};
483+
398484
let has_cargo = src_dir.join("Cargo.toml").is_file();
399485
let cargo_lock = File::open(src_dir.join("Cargo.lock"));
400486
let has_cargo_lock = cargo_lock.is_ok();
@@ -405,6 +491,7 @@ async fn run() -> Result<()> {
405491
let has_pyproject = pyproject_toml.is_file();
406492
let has_setuptools = src_dir.join("setup.py").is_file();
407493

494+
let mut choices = Vec::new();
408495
let rust_vendors = if has_cargo {
409496
if cargo_lock.map_or(true, |file| {
410497
BufReader::new(file)
@@ -454,7 +541,6 @@ async fn run() -> Result<()> {
454541
}
455542

456543
choices.push(BuildType::MkDerivation { rust: None });
457-
458544
editor.set_helper(Some(Prompter::Build(choices)));
459545
let choice = editor.readline(&prompt("How should this package be built?"))?;
460546
let Some(Prompter::Build(choices)) = editor.helper_mut() else {
@@ -564,7 +650,7 @@ async fn run() -> Result<()> {
564650
pname = {pname:?};
565651
version = {version:?};
566652
567-
src = {src_expr};
653+
src = {src_expr};{source_root_expr}
568654
569655
vendorHash = {hash};
570656
@@ -653,7 +739,7 @@ async fn run() -> Result<()> {
653739
version = {version:?};
654740
format = "{format}";
655741
656-
src = {src_expr};
742+
src = {src_expr};{source_root_expr}
657743
658744
"#,
659745
if application {
@@ -712,7 +798,7 @@ async fn run() -> Result<()> {
712798
pname = {pname:?};
713799
version = {version:?};
714800
715-
src = {src_expr};
801+
src = {src_expr};{source_root_expr}
716802
717803
cargoHash = "{hash}";
718804
@@ -742,7 +828,7 @@ async fn run() -> Result<()> {
742828
pname = "{pname}";
743829
version = "{version}";
744830
745-
src = {src_expr};
831+
src = {src_expr};{source_root_expr}
746832
747833
cargoLock = "#,
748834
)?;
@@ -762,6 +848,7 @@ async fn run() -> Result<()> {
762848
version = {version:?};
763849
764850
src = {src_expr};
851+
{source_root_expr}
765852
766853
"#,
767854
)?;
@@ -791,7 +878,7 @@ async fn run() -> Result<()> {
791878
pname = {pname:?};
792879
version = {version:?};
793880
794-
src = {src_expr};
881+
src = {src_expr};{source_root_expr}
795882
796883
cargoDeps = rustPlatform.fetchCargoTarball {{
797884
inherit src;
@@ -824,7 +911,7 @@ async fn run() -> Result<()> {
824911
pname = "{pname}";
825912
version = "{version}";
826913
827-
src = {src_expr};
914+
src = {src_expr};{source_root_expr}
828915
829916
cargoDeps = rustPlatform.importCargoLock "#,
830917
)?;
@@ -908,7 +995,7 @@ async fn run() -> Result<()> {
908995
}
909996

910997
let mut desc = desc.trim_matches(|c: char| !c.is_alphanumeric()).to_owned();
911-
desc.get_mut(0 .. 1).map(str::make_ascii_uppercase);
998+
desc.get_mut(0..1).map(str::make_ascii_uppercase);
912999
write!(out, " ")?;
9131000
writedoc!(
9141001
out,
@@ -1022,5 +1109,5 @@ fn get_version(rev: &str) -> &str {
10221109
}
10231110

10241111
fn get_version_number(rev: &str) -> &str {
1025-
&rev[rev.find(char::is_numeric).unwrap_or_default() ..]
1112+
&rev[rev.find(char::is_numeric).unwrap_or_default()..]
10261113
}

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)