Skip to content

Commit 7941f85

Browse files
committed
Enable running some commands from subdirectories
Signed-off-by: itowlson <[email protected]>
1 parent 1e3d971 commit 7941f85

File tree

6 files changed

+104
-24
lines changed

6 files changed

+104
-24
lines changed

crates/common/src/paths.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ use crate::ui::quoted_path;
88
/// The name given to the default manifest file.
99
pub const DEFAULT_MANIFEST_FILE: &str = "spin.toml";
1010

11+
/// Attempts to find a manifest. If a path is provided, that path is resolved
12+
/// using `resolve_manifest_file_path`; otherwise, a directory search is carried out
13+
/// using `search_upwards_for_manifest`. If a manifest is found, a boolean is
14+
/// also returned indicating if it was the default: this can be used to
15+
/// notify the user that a non-default manifest is being used.
16+
pub fn find_manifest_file_path(provided_path: Option<impl AsRef<Path>>) -> Result<(PathBuf, bool)> {
17+
match provided_path {
18+
Some(provided_path) => resolve_manifest_file_path(provided_path).map(|p| (p, true)),
19+
None => search_upwards_for_manifest()
20+
.ok_or_else(|| anyhow!("\"{}\" not found", DEFAULT_MANIFEST_FILE)),
21+
}
22+
}
23+
1124
/// Resolves a manifest path provided by a user, which may be a file or
1225
/// directory, to a path to a manifest file.
1326
pub fn resolve_manifest_file_path(provided_path: impl AsRef<Path>) -> Result<PathBuf> {
@@ -36,6 +49,30 @@ pub fn resolve_manifest_file_path(provided_path: impl AsRef<Path>) -> Result<Pat
3649
}
3750
}
3851

52+
/// Starting from the current directory, searches upward through
53+
/// the directory tree for a manifest (that is, a file with the default
54+
/// manifest name `spin.toml`). If found, the path to the manifest
55+
/// is returned, with a boolean flag indicating if the found path was
56+
/// the default (i.e. `spin.toml` in the current directory).
57+
/// If no matching file is found, the function returns None.
58+
pub fn search_upwards_for_manifest() -> Option<(PathBuf, bool)> {
59+
let mut inferred_dir = std::env::current_dir().unwrap();
60+
let mut is_default = true;
61+
62+
loop {
63+
let candidate = inferred_dir.join(DEFAULT_MANIFEST_FILE);
64+
65+
if candidate.is_file() {
66+
return Some((candidate, is_default));
67+
}
68+
69+
is_default = false;
70+
let parent = inferred_dir.parent()?;
71+
72+
inferred_dir = parent.to_owned();
73+
}
74+
}
75+
3976
/// Resolves the parent directory of a path, returning an error if the path
4077
/// has no parent. A path with a single component will return ".".
4178
pub fn parent_dir(path: impl AsRef<Path>) -> Result<PathBuf> {

src/commands/build.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use std::{ffi::OsString, path::PathBuf};
22

33
use anyhow::Result;
44
use clap::Parser;
5+
use spin_common::ui::quoted_path;
56

6-
use crate::opts::{APP_MANIFEST_FILE_OPT, BUILD_UP_OPT, DEFAULT_MANIFEST_FILE};
7+
use crate::opts::{APP_MANIFEST_FILE_OPT, BUILD_UP_OPT};
78

89
use super::up::UpCommand;
910

@@ -19,9 +20,8 @@ pub struct BuildCommand {
1920
short = 'f',
2021
long = "from",
2122
alias = "file",
22-
default_value = DEFAULT_MANIFEST_FILE
2323
)]
24-
pub app_source: PathBuf,
24+
pub app_source: Option<PathBuf>,
2525

2626
/// Component ID to build. This can be specified multiple times. The default is all components.
2727
#[clap(short = 'c', long, multiple = true)]
@@ -37,7 +37,16 @@ pub struct BuildCommand {
3737

3838
impl BuildCommand {
3939
pub async fn run(self) -> Result<()> {
40-
let manifest_file = spin_common::paths::resolve_manifest_file_path(&self.app_source)?;
40+
let (manifest_file, is_default) =
41+
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
42+
if !is_default {
43+
terminal::einfo!(
44+
"Using 'spin.toml' from parent directory:",
45+
"{}",
46+
quoted_path(&manifest_file)
47+
);
48+
}
49+
4150
spin_build::build(&manifest_file, &self.component_id).await?;
4251

4352
if self.up {

src/commands/doctor.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use clap::Parser;
55
use dialoguer::{console::Emoji, Confirm, Select};
66
use spin_doctor::{Diagnosis, DryRunNotSupported, PatientDiagnosis};
77

8-
use crate::opts::{APP_MANIFEST_FILE_OPT, DEFAULT_MANIFEST_FILE};
8+
use crate::opts::APP_MANIFEST_FILE_OPT;
99

1010
#[derive(Parser, Debug)]
1111
#[clap(about = "Detect and fix problems with Spin applications")]
@@ -18,21 +18,29 @@ pub struct DoctorCommand {
1818
short = 'f',
1919
long = "from",
2020
alias = "file",
21-
default_value = DEFAULT_MANIFEST_FILE
2221
)]
23-
pub app_source: PathBuf,
22+
pub app_source: Option<PathBuf>,
2423
}
2524

2625
impl DoctorCommand {
2726
pub async fn run(self) -> Result<()> {
28-
let manifest_file = spin_common::paths::resolve_manifest_file_path(&self.app_source)?;
27+
let (manifest_file, is_default) =
28+
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
2929

3030
println!("{icon}The Spin Doctor is in.", icon = Emoji("📟 ", ""));
31-
println!(
32-
"{icon}Checking {}...",
33-
manifest_file.display(),
34-
icon = Emoji("🩺 ", "")
35-
);
31+
if is_default {
32+
println!(
33+
"{icon}Checking {}...",
34+
manifest_file.display(),
35+
icon = Emoji("🩺 ", "")
36+
);
37+
} else {
38+
println!(
39+
"{icon}Checking file from parent directory {}...",
40+
manifest_file.display(),
41+
icon = Emoji("🩺 ", "")
42+
);
43+
}
3644

3745
let mut checkup = spin_doctor::Checkup::new(manifest_file)?;
3846
let mut has_problems = false;

src/commands/registry.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::opts::*;
22
use anyhow::{Context, Result};
33
use clap::{Parser, Subcommand};
44
use indicatif::{ProgressBar, ProgressStyle};
5-
use spin_common::arg_parser::parse_kv;
5+
use spin_common::{arg_parser::parse_kv, ui::quoted_path};
66
use spin_oci::{client::InferPredefinedAnnotations, Client};
77
use std::{io::Read, path::PathBuf, time::Duration};
88

@@ -37,9 +37,8 @@ pub struct Push {
3737
short = 'f',
3838
long = "from",
3939
alias = "file",
40-
default_value = DEFAULT_MANIFEST_FILE
4140
)]
42-
pub app_source: PathBuf,
41+
pub app_source: Option<PathBuf>,
4342

4443
/// Ignore server certificate errors
4544
#[clap(
@@ -71,7 +70,16 @@ pub struct Push {
7170

7271
impl Push {
7372
pub async fn run(self) -> Result<()> {
74-
let app_file = spin_common::paths::resolve_manifest_file_path(&self.app_source)?;
73+
let (app_file, is_default) =
74+
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
75+
if !is_default {
76+
terminal::einfo!(
77+
"Using 'spin.toml' from parent directory:",
78+
"{}",
79+
quoted_path(&app_file)
80+
);
81+
}
82+
7583
if self.build {
7684
spin_build::build(&app_file, &[]).await?;
7785
}

src/commands/up.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,19 @@ impl UpCommand {
382382
);
383383
AppSource::Unresolvable(msg)
384384
} else {
385-
AppSource::None
385+
match spin_common::paths::search_upwards_for_manifest() {
386+
Some((manifest_path, is_default)) => {
387+
if !is_default {
388+
terminal::einfo!(
389+
"Using 'spin.toml' from parent directory:",
390+
"{}",
391+
quoted_path(&manifest_path)
392+
);
393+
}
394+
AppSource::File(manifest_path)
395+
}
396+
None => AppSource::None,
397+
}
386398
}
387399
}
388400

src/commands/watch.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ use anyhow::{Context, Result};
88
use clap::Parser;
99
use itertools::Itertools;
1010
use path_absolutize::Absolutize;
11-
use spin_common::paths::parent_dir;
11+
use spin_common::{paths::parent_dir, ui::quoted_path};
1212
use uuid::Uuid;
1313
use watchexec::Watchexec;
1414

1515
use crate::opts::{
16-
APP_MANIFEST_FILE_OPT, DEFAULT_MANIFEST_FILE, WATCH_CLEAR_OPT, WATCH_DEBOUNCE_OPT,
17-
WATCH_SKIP_BUILD_OPT,
16+
APP_MANIFEST_FILE_OPT, WATCH_CLEAR_OPT, WATCH_DEBOUNCE_OPT, WATCH_SKIP_BUILD_OPT,
1817
};
1918

2019
mod buildifier;
@@ -41,9 +40,8 @@ pub struct WatchCommand {
4140
short = 'f',
4241
long = "from",
4342
alias = "file",
44-
default_value = DEFAULT_MANIFEST_FILE
4543
)]
46-
pub app_source: PathBuf,
44+
pub app_source: Option<PathBuf>,
4745

4846
/// Clear the screen before each run.
4947
#[clap(
@@ -93,7 +91,15 @@ impl WatchCommand {
9391
// has just done so. Subsequent asset changes _do_ clear the screen.
9492

9593
let spin_bin = std::env::current_exe()?;
96-
let manifest_file = spin_common::paths::resolve_manifest_file_path(&self.app_source)?;
94+
let (manifest_file, is_default) =
95+
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
96+
if !is_default {
97+
terminal::einfo!(
98+
"Using 'spin.toml' from parent directory:",
99+
"{}",
100+
quoted_path(&manifest_file)
101+
);
102+
}
97103
let manifest_file = manifest_file.absolutize()?.to_path_buf(); // or watchexec misses files in subdirectories
98104
let manifest_dir = parent_dir(&manifest_file)?;
99105

0 commit comments

Comments
 (0)