diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index 07d18c2cd..3a46caa95 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -5,6 +5,8 @@ use std::{ path::PathBuf, }; +use anyhow::Context; +use cargo_metadata::MetadataCommand; use clap::builder::{OsStringValueParser, PossibleValue, TypedValueParser}; use clap::Parser; use clap_complete::Shell; @@ -46,6 +48,41 @@ pub struct ProjectArgs { pub name: Option, } +impl ProjectArgs { + pub fn workspace_path(&self) -> anyhow::Result { + let path = MetadataCommand::new() + .current_dir(&self.working_directory) + .exec() + .context("failed to get cargo metadata")? + .workspace_root + .into(); + + Ok(path) + } + + pub fn project_name(&self) -> anyhow::Result { + let workspace_path = self.workspace_path()?; + + let meta = MetadataCommand::new() + .current_dir(&workspace_path) + .exec() + .unwrap(); + let package_name = if let Some(root_package) = meta.root_package() { + root_package.name.clone().parse()? + } else { + workspace_path + .file_name() + .context("failed to get project name from workspace path")? + .to_os_string() + .into_string() + .expect("workspace file name should be valid unicode") + .parse()? + }; + + Ok(package_name) + } +} + #[derive(Parser)] pub enum Command { /// deploy a shuttle service @@ -270,6 +307,8 @@ pub(crate) fn parse_init_path(path: OsString) -> Result { mod tests { use strum::IntoEnumIterator; + use crate::tests::path_from_workspace_root; + use super::*; fn init_args_factory(framework: &str) -> InitArgs { @@ -317,4 +356,45 @@ mod tests { assert_eq!(args.framework(), Some(framework)); } } + + #[test] + fn workspace_path() { + let project_args = ProjectArgs { + working_directory: path_from_workspace_root("examples/axum/hello-world/src"), + name: None, + }; + + assert_eq!( + project_args.workspace_path().unwrap(), + path_from_workspace_root("examples/axum/hello-world/") + ); + } + + #[test] + fn project_name() { + let project_args = ProjectArgs { + working_directory: path_from_workspace_root("examples/axum/hello-world/src"), + name: None, + }; + + assert_eq!( + project_args.project_name().unwrap().to_string(), + "hello-world" + ); + } + + #[test] + fn project_name_in_workspace() { + let project_args = ProjectArgs { + working_directory: path_from_workspace_root( + "examples/rocket/workspace/hello-world/src", + ), + name: None, + }; + + assert_eq!( + project_args.project_name().unwrap().to_string(), + "workspace" + ); + } } diff --git a/cargo-shuttle/src/config.rs b/cargo-shuttle/src/config.rs index 74488eebf..5225258de 100644 --- a/cargo-shuttle/src/config.rs +++ b/cargo-shuttle/src/config.rs @@ -3,7 +3,6 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use anyhow::{anyhow, Context, Result}; -use cargo_metadata::MetadataCommand; use serde::{Deserialize, Serialize}; use shuttle_common::project::ProjectName; use shuttle_common::{ApiKey, ApiUrl, API_URL_DEFAULT}; @@ -236,25 +235,6 @@ pub struct RequestContext { api_url: Option, } -fn find_crate_name>(working_directory: P) -> Result { - let meta = MetadataCommand::new() - .current_dir(working_directory.as_ref()) - .exec() - .unwrap(); - let package_name = meta - .root_package() - .ok_or_else(|| { - anyhow!( - "could not find a root package in `{}`", - working_directory.as_ref().display() - ) - })? - .name - .clone() - .parse()?; - Ok(package_name) -} - impl RequestContext { /// Create a [`RequestContext`], only loading in the global configuration details. pub fn load_global() -> Result { @@ -290,7 +270,7 @@ impl RequestContext { project_args: &ProjectArgs, ) -> Result> { let local_manager = - LocalConfigManager::new(&project_args.working_directory, "Shuttle.toml".to_string()); + LocalConfigManager::new(project_args.workspace_path()?, "Shuttle.toml".to_string()); let mut project = Config::new(local_manager); if !project.exists() { @@ -302,6 +282,11 @@ impl RequestContext { let config = project.as_mut().unwrap(); + // Project names are preferred in this order: + // 1. Name given on command line + // 2. Name from Shuttle.toml file + // 3. Name from Cargo.toml package if it's a crate + // 3. Name from the workspace directory if it's a workspace match (&project_args.name, &config.name) { // Command-line name parameter trumps everything (Some(name_from_args), _) => { @@ -315,7 +300,7 @@ impl RequestContext { // If name key is not in project config, then we infer from crate name (None, None) => { trace!("using crate name as project name"); - config.name = Some(find_crate_name(&project_args.working_directory)?); + config.name = Some(project_args.project_name()?); } }; Ok(project) @@ -432,6 +417,18 @@ mod tests { assert_eq!(unwrap_project_name(&local_config), "hello-world-axum-app"); } + #[test] + fn get_local_config_finds_name_from_workspace_dir() { + let project_args = ProjectArgs { + working_directory: path_from_workspace_root("examples/rocket/workspace/hello-world/"), + name: None, + }; + + let local_config = RequestContext::get_local_config(&project_args).unwrap(); + + assert_eq!(unwrap_project_name(&local_config), "workspace"); + } + #[test] fn setting_name_overrides_name_in_config() { let project_args = ProjectArgs { diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index c8cbe07c2..d51c647bb 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -231,21 +231,8 @@ impl Shuttle { Ok(()) } - fn find_root_directory(dir: &Path) -> Option { - dir.ancestors() - .find(|ancestor| ancestor.join("Cargo.toml").exists()) - .map(|path| path.to_path_buf()) - } - pub fn load_project(&mut self, project_args: &mut ProjectArgs) -> Result<()> { trace!("loading project arguments: {project_args:?}"); - let root_directory_path = Self::find_root_directory(&project_args.working_directory); - - if let Some(working_directory) = root_directory_path { - project_args.working_directory = working_directory; - } else { - return Err(anyhow!("Could not locate the root of a cargo project. Are you inside a cargo project? You can also use `--working-directory` to locate your cargo project.")); - } self.ctx.load_local(project_args) } @@ -1004,10 +991,12 @@ mod tests { use std::path::PathBuf; use std::str::FromStr; - fn path_from_workspace_root(path: &str) -> PathBuf { - PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) + pub fn path_from_workspace_root(path: &str) -> PathBuf { + let path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()) .join("..") - .join(path) + .join(path); + + dunce::canonicalize(path).unwrap() } fn get_archive_entries(mut project_args: ProjectArgs) -> Vec { @@ -1037,18 +1026,6 @@ mod tests { .collect() } - #[test] - fn find_root_directory_returns_proper_directory() { - let working_directory = path_from_workspace_root("examples/axum/hello-world/src"); - - let root_dir = Shuttle::find_root_directory(&working_directory).unwrap(); - - assert_eq!( - root_dir, - path_from_workspace_root("examples/axum/hello-world/") - ); - } - #[test] fn load_project_returns_proper_working_directory_in_project_args() { let mut project_args = ProjectArgs { @@ -1061,7 +1038,11 @@ mod tests { assert_eq!( project_args.working_directory, - path_from_workspace_root("examples/axum/hello-world/") + path_from_workspace_root("examples/axum/hello-world/src") + ); + assert_eq!( + project_args.workspace_path().unwrap(), + path_from_workspace_root("examples/axum/hello-world") ); } @@ -1105,7 +1086,21 @@ mod tests { fs::write(working_directory.join(".env"), "API_KEY = 'blabla'").unwrap(); fs::write(working_directory.join(".ignore"), ".env").unwrap(); - fs::write(working_directory.join("Cargo.toml"), "[package]").unwrap(); + fs::write( + working_directory.join("Cargo.toml"), + r#" +[package] +name = "secrets" +version = "0.1.0" +"#, + ) + .unwrap(); + fs::create_dir_all(working_directory.join("src")).unwrap(); + fs::write( + working_directory.join("src").join("main.rs"), + "fn main() {}", + ) + .unwrap(); let project_args = ProjectArgs { working_directory: working_directory.to_path_buf(), @@ -1115,7 +1110,10 @@ mod tests { let mut entries = get_archive_entries(project_args); entries.sort(); - assert_eq!(entries, vec![".ignore", "Cargo.toml"]); + assert_eq!( + entries, + vec![".ignore", "Cargo.lock", "Cargo.toml", "src/main.rs"] + ); } #[test] @@ -1125,7 +1123,21 @@ mod tests { fs::create_dir_all(working_directory.join("target")).unwrap(); fs::write(working_directory.join("target").join("binary"), "12345").unwrap(); - fs::write(working_directory.join("Cargo.toml"), "[package]").unwrap(); + fs::write( + working_directory.join("Cargo.toml"), + r#" +[package] +name = "exclude_target" +version = "0.1.0" +"#, + ) + .unwrap(); + fs::create_dir_all(working_directory.join("src")).unwrap(); + fs::write( + working_directory.join("src").join("main.rs"), + "fn main() {}", + ) + .unwrap(); let project_args = ProjectArgs { working_directory: working_directory.to_path_buf(), @@ -1135,6 +1147,6 @@ mod tests { let mut entries = get_archive_entries(project_args); entries.sort(); - assert_eq!(entries, vec!["Cargo.toml"]); + assert_eq!(entries, vec!["Cargo.lock", "Cargo.toml", "src/main.rs"]); } }