diff --git a/.travis.yml b/.travis.yml index b729efac2d..91fc368026 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ # Enable container based builds sudo: required language: python +dist: xenial services: - docker @@ -8,15 +9,19 @@ services: python: - "2.7" - "3.6" + - "3.7" -# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs -matrix: - include: - - python: 3.7 - dist: xenial - sudo: true +addons: + apt: + packages: + # Xenial images don't have jdk8 installed by default. + - openjdk-8-jdk before_install: + # Use the JDK8 that we installed + - JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64 + - PATH=$JAVA_HOME/bin:$PATH + - nvm install 8.10 - npm --version - node --version diff --git a/requirements/base.txt b/requirements/base.txt index 2ebb325b91..65bbf5ee59 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -12,5 +12,5 @@ dateparser~=0.7 python-dateutil~=2.6 pathlib2~=2.3.2; python_version<"3.4" requests==2.20.1 -aws_lambda_builders==0.0.5 +aws_lambda_builders==0.1.0 serverlessrepo==0.1.5 diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index ef94b13248..a8c3660eb3 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -30,9 +30,10 @@ \b Supported Runtimes ------------------ -1. Python2.7\n -2. Python3.6\n -3. Python3.7\n +1. Python 2.7, 3.6, 3.7 using PIP\n +4. Nodejs 8.10, 6.10 using NPM +4. Ruby 2.5 using Bundler +5. Java 8 using Gradle \b Examples -------- diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py index 38be88d8d4..0e0ac5a93a 100644 --- a/samcli/lib/build/app_builder.py +++ b/samcli/lib/build/app_builder.py @@ -144,13 +144,33 @@ def update_template(self, template_dict, original_template_path, built_artifacts return template_dict def _build_function(self, function_name, codeuri, runtime): - config = get_workflow_config(runtime) + """ + Given the function information, this method will build the Lambda function. Depending on the configuration + it will either build the function in process or by spinning up a Docker container. - # Create the arguments to pass to the builder + Parameters + ---------- + function_name : str + Name or LogicalId of the function + + codeuri : str + Path to where the code lives + + runtime : str + AWS Lambda function runtime + Returns + ------- + str + Path to the location where built artifacts are available + """ + + # Create the arguments to pass to the builder # Code is always relative to the given base directory. code_dir = str(pathlib.Path(self._base_dir, codeuri).resolve()) + config = get_workflow_config(runtime, code_dir, self._base_dir) + # artifacts directory will be created by the builder artifacts_dir = str(pathlib.Path(self._build_dir, function_name)) @@ -186,7 +206,8 @@ def _build_function_in_process(self, artifacts_dir, scratch_dir, manifest_path, - runtime=runtime) + runtime=runtime, + executable_search_paths=config.executable_search_paths) except LambdaBuilderError as ex: raise BuildError(str(ex)) @@ -212,7 +233,8 @@ def _build_function_on_container(self, # pylint: disable=too-many-locals runtime, log_level=log_level, optimizations=None, - options=None) + options=None, + executable_search_paths=config.executable_search_paths) try: try: diff --git a/samcli/lib/build/workflow_config.py b/samcli/lib/build/workflow_config.py index 54d49c77df..055b0c2240 100644 --- a/samcli/lib/build/workflow_config.py +++ b/samcli/lib/build/workflow_config.py @@ -2,60 +2,158 @@ Contains Builder Workflow Configs for different Runtimes """ +import os +import logging from collections import namedtuple -CONFIG = namedtuple('Capability', ["language", "dependency_manager", "application_framework", "manifest_name"]) +LOG = logging.getLogger(__name__) + + +CONFIG = namedtuple('Capability', ["language", "dependency_manager", "application_framework", "manifest_name", + "executable_search_paths"]) PYTHON_PIP_CONFIG = CONFIG( language="python", dependency_manager="pip", application_framework=None, - manifest_name="requirements.txt") + manifest_name="requirements.txt", + executable_search_paths=None) NODEJS_NPM_CONFIG = CONFIG( language="nodejs", dependency_manager="npm", application_framework=None, - manifest_name="package.json") + manifest_name="package.json", + executable_search_paths=None) RUBY_BUNDLER_CONFIG = CONFIG( language="ruby", dependency_manager="bundler", application_framework=None, - manifest_name="Gemfile") + manifest_name="Gemfile", + executable_search_paths=None) + +JAVA_GRADLE_CONFIG = CONFIG( + language="java", + dependency_manager="gradle", + application_framework=None, + manifest_name="build.gradle", + executable_search_paths=None) class UnsupportedRuntimeException(Exception): pass -def get_workflow_config(runtime): +def get_workflow_config(runtime, code_dir, project_dir): """ - Get a workflow config that corresponds to the runtime provided + Get a workflow config that corresponds to the runtime provided. This method examines contents of the project + and code directories to determine the most appropriate workflow for the given runtime. Currently the decision is + based on the presence of a supported manifest file. For runtimes that have more than one workflow, we choose a + workflow by examining ``code_dir`` followed by ``project_dir`` for presence of a supported manifest. Parameters ---------- runtime str The runtime of the config + code_dir str + Directory where Lambda function code is present + + project_dir str + Root of the Serverless application project. + Returns ------- namedtuple(Capability) namedtuple that represents the Builder Workflow Config """ - workflow_config_by_runtime = { - "python2.7": PYTHON_PIP_CONFIG, - "python3.6": PYTHON_PIP_CONFIG, - "python3.7": PYTHON_PIP_CONFIG, - "nodejs4.3": NODEJS_NPM_CONFIG, - "nodejs6.10": NODEJS_NPM_CONFIG, - "nodejs8.10": NODEJS_NPM_CONFIG, - "ruby2.5": RUBY_BUNDLER_CONFIG + selectors_by_runtime = { + "python2.7": BasicWorkflowSelector(PYTHON_PIP_CONFIG), + "python3.6": BasicWorkflowSelector(PYTHON_PIP_CONFIG), + "python3.7": BasicWorkflowSelector(PYTHON_PIP_CONFIG), + "nodejs4.3": BasicWorkflowSelector(NODEJS_NPM_CONFIG), + "nodejs6.10": BasicWorkflowSelector(NODEJS_NPM_CONFIG), + "nodejs8.10": BasicWorkflowSelector(NODEJS_NPM_CONFIG), + "ruby2.5": BasicWorkflowSelector(RUBY_BUNDLER_CONFIG), + + # When Maven builder exists, add to this list so we can automatically choose a builder based on the supported + # manifest + "java8": ManifestWorkflowSelector([ + # Gradle builder needs custom executable paths to find `gradlew` binary + JAVA_GRADLE_CONFIG._replace(executable_search_paths=[code_dir, project_dir]) + ]), } - try: - return workflow_config_by_runtime[runtime] - except KeyError: + if runtime not in selectors_by_runtime: raise UnsupportedRuntimeException("'{}' runtime is not supported".format(runtime)) + + selector = selectors_by_runtime[runtime] + + try: + config = selector.get_config(code_dir, project_dir) + return config + except ValueError as ex: + raise UnsupportedRuntimeException("Unable to find a supported build workflow for runtime '{}'. Reason: {}" + .format(runtime, str(ex))) + + +class BasicWorkflowSelector(object): + """ + Basic workflow selector that returns the first available configuration in the given list of configurations + """ + + def __init__(self, configs): + + if not isinstance(configs, list): + configs = [configs] + + self.configs = configs + + def get_config(self, code_dir, project_dir): + """ + Returns the first available configuration + """ + return self.configs[0] + + +class ManifestWorkflowSelector(BasicWorkflowSelector): + """ + Selects a workflow by examining the directories for presence of a supported manifest + """ + + def get_config(self, code_dir, project_dir): + """ + Finds a configuration by looking for a manifest in the given directories. + + Returns + ------- + samcli.lib.build.workflow_config.CONFIG + A supported configuration if one is found + + Raises + ------ + ValueError + If none of the supported manifests files are found + """ + + # Search for manifest first in code directory and then in the project directory. + # Search order is important here because we want to prefer the manifest present within the code directory over + # a manifest present in project directory. + search_dirs = [code_dir, project_dir] + LOG.debug("Looking for a supported build workflow in following directories: %s", search_dirs) + + for config in self.configs: + + if any([self._has_manifest(config, directory) for directory in search_dirs]): + return config + + raise ValueError("None of the supported manifests '{}' were found in the following paths '{}'".format( + [config.manifest_name for config in self.configs], + search_dirs)) + + @staticmethod + def _has_manifest(config, directory): + return os.path.exists(os.path.join(directory, config.manifest_name)) diff --git a/samcli/local/docker/lambda_build_container.py b/samcli/local/docker/lambda_build_container.py index 368c9b054d..9921123d9a 100644 --- a/samcli/local/docker/lambda_build_container.py +++ b/samcli/local/docker/lambda_build_container.py @@ -35,6 +35,7 @@ def __init__(self, # pylint: disable=too-many-locals runtime, optimizations=None, options=None, + executable_search_paths=None, log_level=None): abs_manifest_path = pathlib.Path(manifest_path).resolve() @@ -53,7 +54,8 @@ def __init__(self, # pylint: disable=too-many-locals manifest_file_name, runtime, optimizations, - options) + options, + executable_search_paths) image = LambdaBuildContainer._get_image(runtime) entry = LambdaBuildContainer._get_entrypoint(request_json) @@ -96,7 +98,8 @@ def _make_request(protocol_version, manifest_file_name, runtime, optimizations, - options): + options, + executable_search_paths): return json.dumps({ "jsonschema": "2.0", @@ -119,6 +122,7 @@ def _make_request(protocol_version, "runtime": runtime, "optimizations": optimizations, "options": options, + "executable_search_paths": executable_search_paths } }) diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py index 0882762087..a40569455a 100644 --- a/tests/integration/buildcmd/build_integ_base.py +++ b/tests/integration/buildcmd/build_integ_base.py @@ -1,6 +1,9 @@ import os import shutil import tempfile +import logging +import subprocess +import json from unittest import TestCase import docker @@ -10,10 +13,12 @@ except ImportError: from pathlib2 import Path - from samcli.yamlhelper import yaml_parse +LOG = logging.getLogger(__name__) + + class BuildIntegBase(TestCase): @classmethod @@ -93,3 +98,15 @@ def _verify_resource_property(self, template_path, logical_id, property, expecte with open(template_path, 'r') as fp: template_dict = yaml_parse(fp.read()) self.assertEquals(expected_value, template_dict["Resources"][logical_id]["Properties"][property]) + + def _verify_invoke_built_function(self, template_path, function_logical_id, overrides, expected_result): + LOG.info("Invoking built function '{}'", function_logical_id) + + cmdlist = [self.cmd, "local", "invoke", function_logical_id, "-t", str(template_path), "--no-event", + "--parameter-overrides", overrides] + + process = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) + process.wait() + + process_stdout = b"".join(process.stdout.readlines()).strip().decode('utf-8') + self.assertEquals(json.loads(process_stdout), expected_result) diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index 2416b302ec..b8b5183968 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -1,7 +1,6 @@ import sys import os import subprocess -import json import logging try: @@ -40,7 +39,7 @@ def test_with_default_requirements(self, runtime, use_container): self.skipTest("Current Python version '{}' does not match Lambda runtime version '{}'".format(py_version, runtime)) - overrides = {"Runtime": runtime, "CodeUri": "Python"} + overrides = {"Runtime": runtime, "CodeUri": "Python", "Handler": "main.handler"} cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) @@ -69,19 +68,6 @@ def test_with_default_requirements(self, runtime, use_container): expected) self.verify_docker_container_cleanedup(runtime) - def _verify_invoke_built_function(self, template_path, function_logical_id, overrides, expected_result): - LOG.info("Invoking built function '{}'", function_logical_id) - - cmdlist = [self.cmd, "local", "invoke", function_logical_id, "-t", str(template_path), "--no-event", - "--parameter-overrides", overrides] - - process = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) - process.wait() - - process_stdout = b"".join(process.stdout.readlines()).strip().decode('utf-8') - print(process_stdout) - self.assertEquals(json.loads(process_stdout), expected_result) - def _verify_built_artifact(self, build_dir, function_logical_id, expected_files): self.assertTrue(build_dir.exists(), "Build directory should be created") @@ -141,7 +127,7 @@ class TestBuildCommand_NodeFunctions(BuildIntegBase): ("nodejs8.10", "use_container") ]) def test_with_default_package_json(self, runtime, use_container): - overrides = {"Runtime": runtime, "CodeUri": "Node"} + overrides = {"Runtime": runtime, "CodeUri": "Node", "Handler": "ignored"} cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) @@ -200,7 +186,7 @@ class TestBuildCommand_RubyFunctions(BuildIntegBase): ("ruby2.5", "use_container") ]) def test_with_default_gemfile(self, runtime, use_container): - overrides = {"Runtime": runtime, "CodeUri": "Ruby"} + overrides = {"Runtime": runtime, "CodeUri": "Ruby", "Handler": "ignored"} cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) @@ -252,3 +238,71 @@ def _verify_built_artifact(self, build_dir, function_logical_id, expected_files, gem_path = ruby_bundled_path.joinpath(ruby_version[0], 'gems') self.assertTrue(any([True if self.EXPECTED_RUBY_GEM in gem else False for gem in os.listdir(str(gem_path))])) + + +class TestBuildCommand_JavaGradle(BuildIntegBase): + + EXPECTED_FILES_PROJECT_MANIFEST = {'aws', 'lib', "META-INF"} + EXPECTED_DEPENDENCIES = {'annotations-2.1.0.jar', "aws-lambda-java-core-1.1.0.jar"} + + FUNCTION_LOGICAL_ID = "Function" + USING_GRADLE_PATH = os.path.join("Java", "gradle") + USING_GRADLEW_PATH = os.path.join("Java", "gradlew") + + @parameterized.expand([ + ("java8", USING_GRADLE_PATH, False), + ("java8", USING_GRADLEW_PATH, False), + ("java8", USING_GRADLE_PATH, "use_container"), + ("java8", USING_GRADLEW_PATH, "use_container"), + ]) + def test_with_gradle(self, runtime, code_path, use_container): + overrides = {"Runtime": runtime, "CodeUri": code_path, "Handler": "aws.example.Hello::myHandler"} + cmdlist = self.get_command_list(use_container=use_container, + parameter_overrides=overrides) + + LOG.info("Running Command: {}".format(cmdlist)) + process = subprocess.Popen(cmdlist, cwd=self.working_dir) + process.wait() + + self._verify_built_artifact(self.default_build_dir, self.FUNCTION_LOGICAL_ID, + self.EXPECTED_FILES_PROJECT_MANIFEST, self.EXPECTED_DEPENDENCIES) + + self._verify_resource_property(str(self.built_template), + "OtherRelativePathResource", + "BodyS3Location", + os.path.relpath( + os.path.normpath(os.path.join(str(self.test_data_path), "SomeRelativePath")), + str(self.default_build_dir)) + ) + + expected = "Hello World" + self._verify_invoke_built_function(self.built_template, + self.FUNCTION_LOGICAL_ID, + self._make_parameter_override_arg(overrides), + expected) + + self.verify_docker_container_cleanedup(runtime) + + def _verify_built_artifact(self, build_dir, function_logical_id, expected_files, expected_modules): + + self.assertTrue(build_dir.exists(), "Build directory should be created") + + build_dir_files = os.listdir(str(build_dir)) + self.assertIn("template.yaml", build_dir_files) + self.assertIn(function_logical_id, build_dir_files) + + template_path = build_dir.joinpath("template.yaml") + resource_artifact_dir = build_dir.joinpath(function_logical_id) + + # Make sure the template has correct CodeUri for resource + self._verify_resource_property(str(template_path), + function_logical_id, + "CodeUri", + function_logical_id) + + all_artifacts = set(os.listdir(str(resource_artifact_dir))) + actual_files = all_artifacts.intersection(expected_files) + self.assertEquals(actual_files, expected_files) + + lib_dir_contents = set(os.listdir(str(resource_artifact_dir.joinpath("lib")))) + self.assertEquals(lib_dir_contents, expected_modules) diff --git a/tests/integration/testdata/buildcmd/Java/gradle/build.gradle b/tests/integration/testdata/buildcmd/Java/gradle/build.gradle new file mode 100644 index 0000000000..ee63ee0c43 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradle/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' + compile ( + 'com.amazonaws:aws-lambda-java-core:1.1.0' + ) +} diff --git a/tests/integration/testdata/buildcmd/Java/gradle/src/main/java/aws/example/Hello.java b/tests/integration/testdata/buildcmd/Java/gradle/src/main/java/aws/example/Hello.java new file mode 100644 index 0000000000..db02d37583 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradle/src/main/java/aws/example/Hello.java @@ -0,0 +1,13 @@ +package aws.example; + + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; + +public class Hello { + public String myHandler(Context context) { + LambdaLogger logger = context.getLogger(); + logger.log("Function Invoked\n"); + return "Hello World"; + } +} diff --git a/tests/integration/testdata/buildcmd/Java/gradlew/build.gradle b/tests/integration/testdata/buildcmd/Java/gradlew/build.gradle new file mode 100644 index 0000000000..ee63ee0c43 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradlew/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' + compile ( + 'com.amazonaws:aws-lambda-java-core:1.1.0' + ) +} diff --git a/tests/integration/testdata/buildcmd/Java/gradlew/gradle/wrapper/gradle-wrapper.jar b/tests/integration/testdata/buildcmd/Java/gradlew/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..87b738cbd0 Binary files /dev/null and b/tests/integration/testdata/buildcmd/Java/gradlew/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tests/integration/testdata/buildcmd/Java/gradlew/gradle/wrapper/gradle-wrapper.properties b/tests/integration/testdata/buildcmd/Java/gradlew/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..558870dad5 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradlew/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/tests/integration/testdata/buildcmd/Java/gradlew/gradlew b/tests/integration/testdata/buildcmd/Java/gradlew/gradlew new file mode 100755 index 0000000000..af6708ff22 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradlew/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/tests/integration/testdata/buildcmd/Java/gradlew/gradlew.bat b/tests/integration/testdata/buildcmd/Java/gradlew/gradlew.bat new file mode 100644 index 0000000000..0f8d5937c4 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradlew/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/tests/integration/testdata/buildcmd/Java/gradlew/src/main/java/aws/example/Hello.java b/tests/integration/testdata/buildcmd/Java/gradlew/src/main/java/aws/example/Hello.java new file mode 100644 index 0000000000..db02d37583 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Java/gradlew/src/main/java/aws/example/Hello.java @@ -0,0 +1,13 @@ +package aws.example; + + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; + +public class Hello { + public String myHandler(Context context) { + LambdaLogger logger = context.getLogger(); + logger.log("Function Invoked\n"); + return "Hello World"; + } +} diff --git a/tests/integration/testdata/buildcmd/template.yaml b/tests/integration/testdata/buildcmd/template.yaml index 9682db822b..fb85168ff4 100644 --- a/tests/integration/testdata/buildcmd/template.yaml +++ b/tests/integration/testdata/buildcmd/template.yaml @@ -6,13 +6,15 @@ Parameteres: Type: String CodeUri: Type: String + Handler: + Type: String Resources: Function: Type: AWS::Serverless::Function Properties: - Handler: main.handler + Handler: !Ref Handler Runtime: !Ref Runtime CodeUri: !Ref CodeUri Timeout: 600 diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 2eb23dbd1c..28bdb8298f 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -205,7 +205,8 @@ def test_must_use_lambda_builder(self, lambda_builder_mock): "artifacts_dir", "scratch_dir", "manifest_path", - runtime="runtime") + runtime="runtime", + executable_search_paths=config_mock.executable_search_paths) @patch("samcli.lib.build.app_builder.LambdaBuilder") def test_must_raise_on_error(self, lambda_builder_mock): @@ -273,7 +274,8 @@ def mock_wait_for_logs(stdout, stderr): "runtime", log_level=log_level, optimizations=None, - options=None) + options=None, + executable_search_paths=config.executable_search_paths) self.container_manager.run.assert_called_with(container_mock) self.builder._parse_builder_response.assert_called_once_with(stdout_data, container_mock.image) diff --git a/tests/unit/lib/build_module/test_workflow_config.py b/tests/unit/lib/build_module/test_workflow_config.py index 45c90f322f..0722ea8a45 100644 --- a/tests/unit/lib/build_module/test_workflow_config.py +++ b/tests/unit/lib/build_module/test_workflow_config.py @@ -1,22 +1,28 @@ from unittest import TestCase from parameterized import parameterized +from mock import patch from samcli.lib.build.workflow_config import get_workflow_config, UnsupportedRuntimeException class Test_get_workflow_config(TestCase): + def setUp(self): + self.code_dir = '' + self.project_dir = '' + @parameterized.expand([ ("python2.7", ), ("python3.6", ) ]) def test_must_work_for_python(self, runtime): - result = get_workflow_config(runtime) + result = get_workflow_config(runtime, self.code_dir, self.project_dir) self.assertEquals(result.language, "python") self.assertEquals(result.dependency_manager, "pip") self.assertEquals(result.application_framework, None) self.assertEquals(result.manifest_name, "requirements.txt") + self.assertIsNone(result.executable_search_paths) @parameterized.expand([ ("nodejs4.3", ), @@ -25,28 +31,61 @@ def test_must_work_for_python(self, runtime): ]) def test_must_work_for_nodejs(self, runtime): - result = get_workflow_config(runtime) + result = get_workflow_config(runtime, self.code_dir, self.project_dir) self.assertEquals(result.language, "nodejs") self.assertEquals(result.dependency_manager, "npm") self.assertEquals(result.application_framework, None) self.assertEquals(result.manifest_name, "package.json") + self.assertIsNone(result.executable_search_paths) @parameterized.expand([ ("ruby2.5", ) ]) def test_must_work_for_ruby(self, runtime): - result = get_workflow_config(runtime) + result = get_workflow_config(runtime, self.code_dir, self.project_dir) self.assertEquals(result.language, "ruby") self.assertEquals(result.dependency_manager, "bundler") self.assertEquals(result.application_framework, None) self.assertEquals(result.manifest_name, "Gemfile") + self.assertIsNone(result.executable_search_paths) + + @parameterized.expand([ + ("java8", "build.gradle") + ]) + @patch("samcli.lib.build.workflow_config.os") + def test_must_work_for_java(self, runtime, build_file, os_mock): + + os_mock.path.join.side_effect = lambda dirname, v: v + os_mock.path.exists.side_effect = lambda v: v == build_file + + result = get_workflow_config(runtime, self.code_dir, self.project_dir) + self.assertEquals(result.language, "java") + self.assertEquals(result.dependency_manager, "gradle") + self.assertEquals(result.application_framework, None) + self.assertEquals(result.manifest_name, "build.gradle") + self.assertEquals(result.executable_search_paths, [self.code_dir, self.project_dir]) + + @parameterized.expand([ + ("java8", "unknown.manifest") + ]) + @patch("samcli.lib.build.workflow_config.os") + def test_must_fail_when_manifest_not_found(self, runtime, build_file, os_mock): + + os_mock.path.join.side_effect = lambda dirname, v: v + os_mock.path.exists.side_effect = lambda v: v == build_file + + with self.assertRaises(UnsupportedRuntimeException) as ctx: + get_workflow_config(runtime, self.code_dir, self.project_dir) + + self.assertIn("Unable to find a supported build workflow for runtime '{}'.".format(runtime), + str(ctx.exception)) def test_must_raise_for_unsupported_runtimes(self): runtime = "foobar" with self.assertRaises(UnsupportedRuntimeException) as ctx: - get_workflow_config(runtime) + get_workflow_config(runtime, self.code_dir, self.project_dir) self.assertEquals(str(ctx.exception), "'foobar' runtime is not supported") diff --git a/tests/unit/local/docker/test_lambda_build_container.py b/tests/unit/local/docker/test_lambda_build_container.py index 6c993ab086..a64ec8de05 100644 --- a/tests/unit/local/docker/test_lambda_build_container.py +++ b/tests/unit/local/docker/test_lambda_build_container.py @@ -87,7 +87,8 @@ def test_must_make_request_object_string(self): "manifest_file_name", "runtime", "optimizations", - "options") + "options", + "executable_search_paths") self.maxDiff = None # Print whole json diff self.assertEqual(json.loads(result), { @@ -107,7 +108,8 @@ def test_must_make_request_object_string(self): "manifest_path": "manifest_dir/manifest_file_name", "runtime": "runtime", "optimizations": "optimizations", - "options": "options" + "options": "options", + "executable_search_paths": "executable_search_paths" } })