Repository: aws/aws-sam-cli

Issue:
`sam build` for Gradle using gradlew does not work with --use-container
<!-- Make sure we don't have an existing Issue that reports the bug you are seeing (both open and closed). 
If you do find an existing Issue, re-open or add a comment to that Issue instead of creating a new one. -->

### Description

Briefly describe the bug you are facing.
When a Gradle project uses `gradlew` scripts, this file does not get picked up with building inside a container.

### Steps to reproduce

Provide steps to replicate.
This integration test sets everything up to repro this issue - https://github.com/awslabs/aws-sam-cli/blob/develop/tests/integration/buildcmd/test_build_cmd.py#L256


### Observed result

Please provide command output with `--debug` flag set.
`gradle` installation within the container is used instead of the `gradlew` script

### Expected result

Describe what you expected.
`gradlew` script is used to build the project 

### Additional environment details (Ex: Windows, Mac, Amazon Linux etc)

1. OS:
2. `sam --version`:

`Add --debug flag to command you are running`


Discussion:

Resolving patch:
diff --git a/samcli/local/docker/lambda_build_container.py b/samcli/local/docker/lambda_build_container.py
--- a/samcli/local/docker/lambda_build_container.py
+++ b/samcli/local/docker/lambda_build_container.py
@@ -46,6 +46,18 @@ def __init__(self,  # pylint: disable=too-many-locals
 
         container_dirs = LambdaBuildContainer._get_container_dirs(source_dir, manifest_dir)
 
+        # `executable_search_paths` are provided as a list of paths on the host file system that needs to passed to
+        # the builder. But these paths don't exist within the container. We use the following method to convert the
+        # host paths to container paths. But if a host path is NOT mounted within the container, we will simply ignore
+        # it. In essence, only when the path is already in the mounted path, can the path resolver within the
+        # container even find the executable.
+        executable_search_paths = LambdaBuildContainer._convert_to_container_dirs(
+            host_paths_to_convert=executable_search_paths,
+            host_to_container_path_mapping={
+                source_dir: container_dirs["source_dir"],
+                manifest_dir: container_dirs["manifest_dir"]
+            })
+
         request_json = self._make_request(protocol_version,
                                           language,
                                           dependency_manager,
@@ -163,6 +175,54 @@ def _get_container_dirs(source_dir, manifest_dir):
 
         return result
 
+    @staticmethod
+    def _convert_to_container_dirs(host_paths_to_convert, host_to_container_path_mapping):
+        """
+        Use this method to convert a list of host paths to a list of equivalent paths within the container
+        where the given host path is mounted. This is necessary when SAM CLI needs to pass path information to
+        the Lambda Builder running within the container.
+
+        If a host path is not mounted within the container, then this method simply passes the path to the result
+        without any changes.
+
+        Ex:
+            [ "/home/foo", "/home/bar", "/home/not/mounted"]  => ["/tmp/source", "/tmp/manifest", "/home/not/mounted"]
+
+        Parameters
+        ----------
+        host_paths_to_convert : list
+            List of paths in host that needs to be converted
+
+        host_to_container_path_mapping : dict
+            Mapping of paths in host to the equivalent paths within the container
+
+        Returns
+        -------
+        list
+            Equivalent paths within the container
+        """
+
+        if not host_paths_to_convert:
+            # Nothing to do
+            return host_paths_to_convert
+
+        # Make sure the key is absolute host path. Relative paths are tricky to work with because two different
+        # relative paths can point to the same directory ("../foo", "../../foo")
+        mapping = {str(pathlib.Path(p).resolve()): v for p, v in host_to_container_path_mapping.items()}
+
+        result = []
+        for original_path in host_paths_to_convert:
+            abspath = str(pathlib.Path(original_path).resolve())
+
+            if abspath in mapping:
+                result.append(mapping[abspath])
+            else:
+                result.append(original_path)
+                LOG.debug("Cannot convert host path '%s' to its equivalent path within the container. "
+                          "Host path is not mounted within the container", abspath)
+
+        return result
+
     @staticmethod
     def _get_image(runtime):
         return "{}:build-{}".format(LambdaBuildContainer._IMAGE_REPO_NAME, runtime)


Test patch:
diff --git a/tests/unit/local/docker/test_lambda_build_container.py b/tests/unit/local/docker/test_lambda_build_container.py
--- a/tests/unit/local/docker/test_lambda_build_container.py
+++ b/tests/unit/local/docker/test_lambda_build_container.py
@@ -3,6 +3,11 @@
 """
 
 import json
+try:
+    import pathlib
+except ImportError:
+    import pathlib2 as pathlib
+
 
 from unittest import TestCase
 from mock import patch
@@ -157,3 +162,44 @@ class TestLambdaBuildContainer_get_entrypoint(TestCase):
     def test_must_get_entrypoint(self):
         self.assertEquals(["lambda-builders", "requestjson"],
                           LambdaBuildContainer._get_entrypoint("requestjson"))
+
+
+class TestLambdaBuildContainer_convert_to_container_dirs(TestCase):
+
+    def test_must_work_on_abs_and_relative_paths(self):
+
+        input = [".", "../foo", "/some/abs/path"]
+        mapping = {
+            str(pathlib.Path(".").resolve()): "/first",
+            "../foo": "/second",
+            "/some/abs/path": "/third"
+        }
+
+        expected = ["/first", "/second", "/third"]
+        result = LambdaBuildContainer._convert_to_container_dirs(input, mapping)
+
+        self.assertEquals(result, expected)
+
+    def test_must_skip_unknown_paths(self):
+
+        input = ["/known/path", "/unknown/path"]
+        mapping = {
+            "/known/path": "/first"
+        }
+
+        expected = ["/first", "/unknown/path"]
+        result = LambdaBuildContainer._convert_to_container_dirs(input, mapping)
+
+        self.assertEquals(result, expected)
+
+    def test_must_skip_on_empty_input(self):
+
+        input = None
+        mapping = {
+            "/known/path": "/first"
+        }
+
+        expected = None
+        result = LambdaBuildContainer._convert_to_container_dirs(input, mapping)
+
+        self.assertEquals(result, expected)