Repository: cargo-lambda/cargo-lambda (language: python)

Issue:
build builds bootstrap/boostrap, deploy looks for rustLambda/bootstrap
I don't know if this is by design or not, but its certainly confusing - I dropped down to strace to figure out what was going on.


Discussion / hints:
can you provide more detail on what you're seeing?  do you have a minimal repro?
This is the repo that I encountered this behaviour in. https://github.com/rbtcollins/PythonVsRustAWSLambda/
I cannot find any reference to `boostrap` either in that repo, or in cargo-lambda. I'm not really sure this is a problem with this tool without more information.
Ah! so 

`cargo lambda build --release --arm64` builds `target/lambda/bootstrap/bootstrap`
`cargo lambda deploy --iam-role xxx` consumes `target/lambda/rustLambda/bootstrap`

I woiuld expect either both to honour the Cargo.toml name, or neither.


Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1830e543..acddbe1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@ exclude = [
     "tests/fixtures/multi-binary-package",
     "tests/fixtures/target-dir-set-in-project",
     "tests/fixtures/workspace-package",
+    "tests/fixtures/single-binary-different-name",
 ]
 
 [workspace.package]
diff --git a/crates/cargo-lambda-deploy/src/lib.rs b/crates/cargo-lambda-deploy/src/lib.rs
index 32075f4e..dde7568e 100644
--- a/crates/cargo-lambda-deploy/src/lib.rs
+++ b/crates/cargo-lambda-deploy/src/lib.rs
@@ -1,7 +1,7 @@
 use aws_smithy_types::retry::{RetryConfig, RetryMode};
 use cargo_lambda_build::{find_binary_archive, zip_binary, BinaryArchive};
 use cargo_lambda_interactive::progress::Progress;
-use cargo_lambda_metadata::cargo::root_package;
+use cargo_lambda_metadata::cargo::main_binary;
 use cargo_lambda_remote::{
     aws_sdk_lambda::model::{Architecture, Runtime},
     RemoteConfig,
@@ -216,7 +216,7 @@ impl Deploy {
             None => {
                 let name = match &self.name {
                     Some(name) => name.clone(),
-                    None => root_package(&self.manifest_path)?.name,
+                    None => main_binary(&self.manifest_path)?,
                 };
                 let binary_name = self.binary_name.as_deref().unwrap_or(&name);
 
diff --git a/crates/cargo-lambda-metadata/src/cargo.rs b/crates/cargo-lambda-metadata/src/cargo.rs
index 5e4f6add..cf5dd7ed 100644
--- a/crates/cargo-lambda-metadata/src/cargo.rs
+++ b/crates/cargo-lambda-metadata/src/cargo.rs
@@ -1,5 +1,5 @@
 use aws_sdk_lambda::model::Environment;
-use cargo_metadata::{Metadata as CargoMetadata, Package};
+use cargo_metadata::Metadata as CargoMetadata;
 use miette::{IntoDiagnostic, Result};
 use serde::Deserialize;
 use std::{
@@ -360,23 +360,22 @@ pub fn function_build_metadata(metadata: &CargoMetadata) -> Result<BuildConfig>
     Ok(config)
 }
 
-/// Load the main package in the project.
-/// It returns an error if the project includes from than one package.
+/// Load the main binary in the project.
+/// It returns an error if the project includes from than one binary.
 /// Use this function when the user didn't provide any funcion name
-/// assuming that there is only one package in the project
-pub fn root_package<P: AsRef<Path> + Debug>(manifest_path: P) -> Result<Package> {
-    let metadata = load_metadata(manifest_path)?;
-    if metadata.packages.len() > 1 {
-        Err(MetadataError::MultiplePackagesInProject)?;
-    } else if metadata.packages.is_empty() {
-        Err(MetadataError::MissingPackageInProject)?;
+/// assuming that there is only one binary in the project
+pub fn main_binary<P: AsRef<Path> + Debug>(manifest_path: P) -> Result<String> {
+    let targets = binary_targets(manifest_path)?;
+    if targets.len() > 1 {
+        Err(MetadataError::MultipleBinariesInProject)?;
+    } else if targets.is_empty() {
+        Err(MetadataError::MissingBinaryInProject)?;
     }
 
-    Ok(metadata
-        .packages
+    targets
         .into_iter()
         .next()
-        .expect("failed to extract the root package from the metadata"))
+        .ok_or_else(|| MetadataError::MissingBinaryInProject.into())
 }
 
 fn merge_deploy_config(base: &DeployConfig, package_deploy: &DeployConfig) -> DeployConfig {
@@ -623,4 +622,29 @@ mod tests {
         assert_eq!("BAR", vars["FOO"]);
         assert_eq!("QUX", vars["BAZ"]);
     }
+
+    #[test]
+    fn test_main_binary_with_package_name() {
+        let manifest_path = fixture("single-binary-package");
+        let name = main_binary(manifest_path).unwrap();
+        assert_eq!("basic-lambda", name);
+    }
+
+    #[test]
+    fn test_main_binary_with_binary_name() {
+        let manifest_path = fixture("single-binary-different-name");
+        let name = main_binary(manifest_path).unwrap();
+        assert_eq!("basic-lambda-binary", name);
+    }
+
+    #[test]
+    fn test_main_binary_multi_binaries() {
+        let manifest_path = fixture("multi-binary-package");
+        let err = main_binary(manifest_path).unwrap_err();
+        let err: MetadataError = err.downcast().unwrap();
+        assert_eq!(
+            "there are more than one binary in the project, you must specify a binary name",
+            err.to_string()
+        );
+    }
 }
diff --git a/crates/cargo-lambda-metadata/src/error.rs b/crates/cargo-lambda-metadata/src/error.rs
index 076e1b30..0c639f15 100644
--- a/crates/cargo-lambda-metadata/src/error.rs
+++ b/crates/cargo-lambda-metadata/src/error.rs
@@ -17,12 +17,12 @@ pub enum MetadataError {
     #[error("invalid tracing option `{0}`")]
     #[diagnostic()]
     InvalidTracing(String),
-    #[error("there are more than one package in the project, you must specify a function name")]
+    #[error("there are more than one binary in the project, you must specify a binary name")]
     #[diagnostic()]
-    MultiplePackagesInProject,
-    #[error("there are no packages in this project")]
+    MultipleBinariesInProject,
+    #[error("there are no binaries in this project")]
     #[diagnostic()]
-    MissingPackageInProject,
+    MissingBinaryInProject,
     #[error("invalid environment variable `{0}`")]
     #[diagnostic()]
     InvalidEnvVar(String),


Test patch:
diff --git a/tests/fixtures/single-binary-different-name/Cargo.toml b/tests/fixtures/single-binary-different-name/Cargo.toml
new file mode 100644
index 00000000..b55724d9
--- /dev/null
+++ b/tests/fixtures/single-binary-different-name/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "basic-lambda"
+version = "0.1.0"
+edition = "2021"
+
+[[bin]]
+name = "basic-lambda-binary"
+path = "src/main.rs"
+
+[dependencies]
+lambda_runtime = "0.5.1"
+serde = "1.0.136"
+tokio = { version = "1", features = ["macros"] }
+
+[package.metadata.lambda.env]
+FOO = "BAR"
+
+[package.metadata.lambda.deploy]
+memory = 512
+timeout = 60
+env_file = ".env.production"
+layers = [
+    "arn:aws:lambda:us-east-1:xxxxxxxx:layers:layer1",
+    "arn:aws:lambda:us-east-1:xxxxxxxx:layers:layer2",
+]
+tracing = "active"
+role = "arn:aws:lambda:us-east-1:xxxxxxxx:iam:role1"
+env = { "VAR1" = "VAL1" }
+tags = { "organization" = "aws", "team" = "lambda" }
+
+[package.metadata.lambda.build.compiler]
+type = "cargo"
+subcommand = ["brazil", "build"]
diff --git a/tests/fixtures/single-binary-different-name/src/main.rs b/tests/fixtures/single-binary-different-name/src/main.rs
new file mode 100644
index 00000000..074f05b3
--- /dev/null
+++ b/tests/fixtures/single-binary-different-name/src/main.rs
@@ -0,0 +1,48 @@
+use lambda_runtime::{run, service_fn, Error, LambdaEvent};
+use serde::{Deserialize, Serialize};
+
+/// This is a made-up example. Requests come into the runtime as unicode
+/// strings in json format, which can map to any structure that implements `serde::Deserialize`
+/// The runtime pays no attention to the contents of the request payload.
+#[derive(Deserialize)]
+struct Request {
+    command: String,
+}
+
+/// This is a made-up example of what a response structure may look like.
+/// There is no restriction on what it can be. The runtime requires responses
+/// to be serialized into json. The runtime pays no attention
+/// to the contents of the response payload.
+#[derive(Serialize)]
+struct Response {
+    req_id: String,
+    msg: String,
+}
+
+/// This is the main body for the function.
+/// Write your code inside it.
+/// There are some code example in the following URLs:
+/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/lambda-runtime/examples
+/// - https://github.com/aws-samples/serverless-rust-demo/
+async fn function_handler(event: LambdaEvent<Request>) -> Result<Response, Error> {
+    // Extract some useful info from the request
+    let command = event.payload.command;
+
+    // Prepare the response
+    let resp = Response {
+        req_id: event.context.request_id,
+        msg: format!(
+            "Command {}. Env FOO={}",
+            command,
+            std::env::var("FOO").unwrap()
+        ),
+    };
+
+    // Return `Response` (it will be serialized to JSON automatically by the runtime)
+    Ok(resp)
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    run(service_fn(function_handler)).await
+}
diff --git a/tests/fixtures/single-binary-package/Cargo.toml b/tests/fixtures/single-binary-package/Cargo.toml
index bb5f00fc..7598acdb 100644
--- a/tests/fixtures/single-binary-package/Cargo.toml
+++ b/tests/fixtures/single-binary-package/Cargo.toml
@@ -3,10 +3,6 @@ name = "basic-lambda"
 version = "0.1.0"
 edition = "2021"
 
-[[bin]]
-name = "basic-lambda"
-src = "src/main.rs"
-
 [dependencies]
 lambda_runtime = "0.5.1"
 serde = "1.0.136"