Skip to content

feat: support sourceRoot selection #118

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added src/default.nix
Empty file.
107 changes: 97 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ struct Outputs {
out: String,
}

fn is_target_for_packaging(src_dir: &PathBuf) -> bool {
let has_cargo = src_dir.join("Cargo.toml").is_file();
let cargo_lock = File::open(src_dir.join("Cargo.lock"));
let has_cargo_lock = cargo_lock.is_ok();
let has_cmake = src_dir.join("CMakeLists.txt").is_file();
let has_go = src_dir.join("go.mod").is_file();
let has_meson = src_dir.join("meson.build").is_file();
let pyproject_toml = src_dir.join("pyproject.toml");
let has_pyproject = pyproject_toml.is_file();
let has_setuptools = src_dir.join("setup.py").is_file();

(has_cargo && has_cargo_lock)
|| has_cmake
|| has_go
|| has_meson
|| has_pyproject
|| has_setuptools
}

#[tokio::main]
async fn main() -> Result<()> {
run().await
Expand Down Expand Up @@ -394,7 +413,74 @@ async fn run() -> Result<()> {
PathBuf::from(&src)
};

let mut choices = Vec::new();
// Let's first find all subdirectories that are compatible with our supported recipes.
let root_src_dir = src_dir.clone();
let mut open_subdirs = vec![(0, src_dir)];
let mut sourceroot_candidates = vec![];
while !open_subdirs.is_empty() {
// If we call this, we have a non-empty vector.
let (depth, target_dir) = open_subdirs.pop().unwrap();
let subdirs = std::fs::read_dir(&target_dir).with_context(|| {
format!("failed to list subdirectories of {}", target_dir.display())
})?;

for maybe_subdir in subdirs {
let subdir = maybe_subdir.context(format!(
"unexpected io error while reading a subdirectory of {}",
target_dir.to_string_lossy()
))?;

let subdir_path = &subdir.path();

// We want only subdirectories.
if !subdir_path.is_dir() {
continue;
}

if is_target_for_packaging(subdir_path) {
sourceroot_candidates.push(subdir.file_name());
}

// Recurse into that subdir.
if depth < 3 {
open_subdirs.push((depth + 1, subdir_path.to_path_buf()));
}
}
}

editor.set_helper(Some(Prompter::List(
sourceroot_candidates
.iter()
.map(|sr| {
PathBuf::from(sr).strip_prefix(&root_src_dir)
.expect("Failed to strip the root source directory prefix")
.to_string_lossy()
.to_string()
})
.collect(),
)));
let choice = editor.readline(&prompt("Which subdirectory should we use?"))?;
let src_dir = &PathBuf::from(choice
.parse()
.ok()
.and_then(|i: usize| sourceroot_candidates.get(i))
.unwrap_or_else(|| &sourceroot_candidates[0]));
let source_root = if src_dir != &root_src_dir {
Some(src_dir)
} else {
None
};
let source_root_expr = match source_root {
Some(subdir) => format!(
"\nsourceRoot = \"source/{}\";",
subdir
.strip_prefix(&root_src_dir)
.expect("Failed to strip the root source directory prefix")
.to_string_lossy()
),
None => "".to_string(),
};

let has_cargo = src_dir.join("Cargo.toml").is_file();
let cargo_lock = File::open(src_dir.join("Cargo.lock"));
let has_cargo_lock = cargo_lock.is_ok();
Expand All @@ -405,6 +491,7 @@ async fn run() -> Result<()> {
let has_pyproject = pyproject_toml.is_file();
let has_setuptools = src_dir.join("setup.py").is_file();

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

choices.push(BuildType::MkDerivation { rust: None });

editor.set_helper(Some(Prompter::Build(choices)));
let choice = editor.readline(&prompt("How should this package be built?"))?;
let Some(Prompter::Build(choices)) = editor.helper_mut() else {
Expand Down Expand Up @@ -564,7 +650,7 @@ async fn run() -> Result<()> {
pname = {pname:?};
version = {version:?};

src = {src_expr};
src = {src_expr};{source_root_expr}

vendorHash = {hash};

Expand Down Expand Up @@ -653,7 +739,7 @@ async fn run() -> Result<()> {
version = {version:?};
format = "{format}";

src = {src_expr};
src = {src_expr};{source_root_expr}

"#,
if application {
Expand Down Expand Up @@ -712,7 +798,7 @@ async fn run() -> Result<()> {
pname = {pname:?};
version = {version:?};

src = {src_expr};
src = {src_expr};{source_root_expr}

cargoHash = "{hash}";

Expand Down Expand Up @@ -742,7 +828,7 @@ async fn run() -> Result<()> {
pname = "{pname}";
version = "{version}";

src = {src_expr};
src = {src_expr};{source_root_expr}

cargoLock = "#,
)?;
Expand All @@ -762,6 +848,7 @@ async fn run() -> Result<()> {
version = {version:?};

src = {src_expr};
{source_root_expr}

"#,
)?;
Expand Down Expand Up @@ -791,7 +878,7 @@ async fn run() -> Result<()> {
pname = {pname:?};
version = {version:?};

src = {src_expr};
src = {src_expr};{source_root_expr}

cargoDeps = rustPlatform.fetchCargoTarball {{
inherit src;
Expand Down Expand Up @@ -824,7 +911,7 @@ async fn run() -> Result<()> {
pname = "{pname}";
version = "{version}";

src = {src_expr};
src = {src_expr};{source_root_expr}

cargoDeps = rustPlatform.importCargoLock "#,
)?;
Expand Down Expand Up @@ -908,7 +995,7 @@ async fn run() -> Result<()> {
}

let mut desc = desc.trim_matches(|c: char| !c.is_alphanumeric()).to_owned();
desc.get_mut(0 .. 1).map(str::make_ascii_uppercase);
desc.get_mut(0..1).map(str::make_ascii_uppercase);
write!(out, " ")?;
writedoc!(
out,
Expand Down Expand Up @@ -1022,5 +1109,5 @@ fn get_version(rev: &str) -> &str {
}

fn get_version_number(rev: &str) -> &str {
&rev[rev.find(char::is_numeric).unwrap_or_default() ..]
&rev[rev.find(char::is_numeric).unwrap_or_default()..]
}
37 changes: 37 additions & 0 deletions src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
#[derive(Helper, Highlighter)]
pub enum Prompter {
Path(FilenameCompleter),
List(Vec<String>),
Revision(Revisions),
NonEmpty,
YesNo,
Expand Down Expand Up @@ -49,6 +50,17 @@ impl Completer for Prompter {
Prompter::Revision(revisions) => Ok((0, revisions.completions.clone())),
Prompter::NonEmpty => Ok((0, Vec::new())),
Prompter::YesNo => Ok((0, Vec::new())),
Prompter::List(choices) => Ok((
0,
choices
.iter()
.enumerate()
.map(|(i, choice)| Pair {
display: format!("{i} - {choice}"),
replacement: i.to_string(),
})
.collect(),
)),
Prompter::Build(choices) => Ok((
0,
choices
Expand Down Expand Up @@ -114,6 +126,16 @@ impl Hinter for Prompter {

Prompter::YesNo => None,

Prompter::List(choices) => Some(SimpleHint(if line.is_empty() {
format_args!(" ({})", choices[0])
.blue()
.italic()
.to_string()
} else if let Some(choice) = line.parse().ok().and_then(|i: usize| choices.get(i)) {
format_args!(" ({choice})").blue().italic().to_string()
} else {
" press <tab> to see options".yellow().italic().to_string()
})),
Prompter::Build(choices) => Some(SimpleHint(if line.is_empty() {
format_args!(" ({})", choices[0])
.blue()
Expand Down Expand Up @@ -157,6 +179,21 @@ impl Validator for Prompter {

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

Prompter::List(choices) => {
let input = ctx.input();
if input.is_empty() {
ValidationResult::Valid(Some(choices[0].to_string()))
} else if let Some(choice) = input
.parse::<usize>()
.ok()
.and_then(|choice| choices.get(choice))
{
ValidationResult::Valid(Some(format!(" - {choice}")))
} else {
ValidationResult::Invalid(None)
}
}

Prompter::Build(choices) => {
let input = ctx.input();
if input.is_empty() {
Expand Down