diff --git a/.appveyor.yml b/.appveyor.yml index 4b252170f..f526a3c1d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,6 +4,7 @@ image: Visual Studio 2017 environment: GOPATH: c:\gopath GOVERSION: 1.11 + GRADLE_OPTS: -Dorg.gradle.daemon=false matrix: @@ -36,6 +37,9 @@ install: - "go version" - "go env" +# setup Gradle +- "choco install gradle" + test_script: - "%PYTHON%\\python.exe -m pytest --cov aws_lambda_builders --cov-report term-missing tests/unit tests/functional" - "%PYTHON%\\python.exe -m pytest tests/integration" diff --git a/aws_lambda_builders/workflows/__init__.py b/aws_lambda_builders/workflows/__init__.py index 24b82710a..1a3ed60ec 100644 --- a/aws_lambda_builders/workflows/__init__.py +++ b/aws_lambda_builders/workflows/__init__.py @@ -7,3 +7,4 @@ import aws_lambda_builders.workflows.ruby_bundler import aws_lambda_builders.workflows.go_dep import aws_lambda_builders.workflows.go_modules +import aws_lambda_builders.workflows.java_gradle diff --git a/aws_lambda_builders/workflows/java_gradle/DESIGN.md b/aws_lambda_builders/workflows/java_gradle/DESIGN.md new file mode 100644 index 000000000..f20c986f3 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/DESIGN.md @@ -0,0 +1,195 @@ +# Java - Gradle Lambda Builder + +## Scope + +This package enables the creation of a Lambda deployment package for Java +projects managed using the Gradle build tool. + +For Java projects, the most popular way to create a distribution package for +Java based Lambdas is to create an "uber" or "fat" JAR. This is a single JAR +file that contains both the customers' classes and resources, as well as all the +classes and resources extracted from their dependency JAR's. However, this can +cause files that have the same path in two different JAR's to collide within the +uber JAR. + +Another solution is to create a distribution ZIP containing the customer's +classes and resources and include their dependency JARs under a `lib` directory. +This keeps the customers' classes and resources separate from their +dependencies' to avoid any file collisions. However, this incurs some overhead +as the ZIP must be unpacked before the code can run. + +To avoid the problem of colliding files, we will choose the second option and +create distribution ZIP. + +## Challenges + +Java bytecode can only run on the same or newer version of the JVM for which +it was compiled for. For example Java 8 bytecode can run a JVM that is at +least version 8, but bytecode targetting Java 9 cannot run on a Java 8 VM. +This is further complicated by the fact that a newer JDK can generate code to +be run on an older VM if configured using the `targetCompatibility` and +`sourceCompatibility` properties of the Java plugin. Therefore, it is not +sufficient to check the version of the local JDK, nor is it possible to check +the value set for `targetCompatibility` or `sourceCompatibility` since it can +be local to the compile/build task. At best, we can check if the local +version of the JDK is newer than Java 8 and emit a warning that the built +artifact may not run in Lambda. + +Gradle projects are configured using `build.gradle` build scripts. These are +executable files authored in either Groovy or since 5.0, Kotlin, and using the +Gradle DSL. This presents a similar problem to `setup.py` in the Python world in +that arbitrary logic can be executed during build time that could affect both +how the customer's artifact is built, and which dependencies are chosen. + +An interesting challenge is dealing with single build and multi build projects. +Consider the following different projects structures: + +**Project A** +``` +ProjectA +├── build.gradle +├── gradlew +├── src +└── template.yaml +``` + +**Project B** +``` +ProjectB +├── common +│   └── build.gradle +├── lambda1 +│   └── build.gradle +├── lambda2 +│   └── build.gradle +├── build.gradle +├── gradlew +├── settings.gradle +└── template.yaml +``` + +Here `ProjectA` is a a single lambda function, and `ProjectB` is a multi-build +project where sub directories `lambda1` and `lambda2` are each a lambda +function. In addition, suppose that `ProjectB/lambda1` has a dependency on its +sibling project `ProjectB/common`. + +Building Project A is relatively simple since we just need to issue `gradlew +build` and place the built ZIP within the artifact directory. + +Building `ProjectB/lambda1` is very similar from the point of view of the +workflow since it still issues the same command (`gradlew build`), but it +requires that Gradle is able to find its way back up to the parent `ProjectB` so +that it can also build `ProjectB/common` which can be a challenge when mounting +within a container. + +## Implementation + +### Build Workflow + +We leverage Gradle to do all the heavy lifting for executing the +`build.gradle` script which will resolve and download the dependencies and +build the project. To create the distribution ZIP, we use the help of a +Gradle init script to insert a post-build action to do this. + +#### Step 1: Copy custom init file to temporary location + +There is no standard task in Gradle to create a distribution ZIP (or uber JAR). +We add this functionality through the use of a Gradle init script. The script +will be responsible for adding a post-build action that creates the distribution +ZIP. + +It will do something similar to: + +```sh +cp /path/to/lambda-build-init.gradle /$SCRATCH_DIR/ +``` + +where the contents of `lambda-build-init.gradle` contains the code for defining +the post-build action: + +```gradle +gradle.project.afterProject { p -> + // Set the give project's buildDir to one under SCRATCH_DIR +} + +// Include the project classes and resources in the root, and the dependencies +// under lib +gradle.taskGraph.afterTask { t -> + if (t.name != 'build') { + return + } + + // Step 1: Find the directory under scratch_dir where the artifact for + // t.project is located + // Step 2: Open ZIP file in $buildDir/distributions/lambda_build + // Step 3: Copy project class files and resources to ZIP root + // Step 3: Copy libs in configurations.runtimeClasspath into 'lib' + // subdirectory in ZIP +} +``` + +#### Step 2: Resolve Gradle executable to use + +[The recommended +way](https://docs.gradle.org/current/userguide/gradle_wrapper.html) way to +author and distribute a Gradle project is to include a `gradlew` or Gradle +Wrapper file within the root of the project. This essentially locks in the +version of Gradle for the project and uses an executable that is independent of +any local installations. This helps ensure that builds are always consistent +over different environments. + +The `gradlew` script, if it is included, will be located at the root of the +project. We will rely on the invoker of the workflow to supply the path to the +`gradlew` script. + +We give precedence to this `gradlew` file, and if isn't found, we use the +`gradle` executable found on the `PATH` using the [path resolver][path resolver]. + +#### Step 3: Check Java version and emit warning + +Check whether the local JDK version is <= Java 8, and if it is not, emit a +warning that the built artifact may not run in Lambda unless a) the project is +properly configured (i.e. using `targetCompatibility`) or b) the project is +built within a Lambda-compatibile environment like `lambci`. + +We use the Gradle executable from Step 2 for this to ensure that we check the +actual JVM version Gradle is using in case it has been configured to use a +different one than can be found on the PATH. + +#### Step 4: Build and package + +```sh +$GRADLE_EXECUTABLE --project-cache-dir $SCRATCH_DIR/gradle-cache \ + -Dsoftware.amazon.aws.lambdabuilders.scratch-dir=$SCRATCH_DIR \ + --init-script $SCRATCH_DIR/lambda-build-init.gradle build +``` + +Since by default, Gradle stores its build-related metadata in a `.gradle` +directory under the source directory, we specify an alternative directory under +`scratch_dir` to avoid writing anything under `source_dir`. This is simply a +`gradle-cache` directory under `scratch_dir`. + +Next, we also pass the location of the `scratch_dir` as a Java system +property so that it's availabe to our init script. This allows it to correctly +map the build directory for each sub-project within `scratch_dir`. Again, this +ensures that we are not writing anything under the source directory. + +One important detail here is that the init script may create *multiple* +subdirectories under `scratch_dir`, one for each project involved in building +the lambda located at `source_dir`. Going back to the `ProjectB` example, if +we're building `lambda1`, this also has the effect of building `common` because +it's a declared dependency in its `build.gradle`. So, within `scratch_dir` will +be a sub directory for each project that gets built as a result of building +`source_dir`; in this case there will be one for each of `lambda1` and `common`. +The init file uses some way of mapping the source root of each project involved +to a unique directory under `scratch_dir`, like a hashing function. + +#### Step 5: Copy to artifact directory + +The workflow implementation is aware of the mapping scheme used to map a +`source_dir` to the correct directory under `scratch_dir` (described in step 4), +so it knows where to find the built Lambda artifact when copying it to +`artifacts_dir`. They will be located in +`$SCRATCH_DIR//build/distributions/lambda-build`. + +[path resolver]: https://github.com/awslabs/aws-lambda-builders/pull/55 diff --git a/aws_lambda_builders/workflows/java_gradle/__init__.py b/aws_lambda_builders/workflows/java_gradle/__init__.py new file mode 100644 index 000000000..7b18eed99 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/__init__.py @@ -0,0 +1,5 @@ +""" +Builds Java Lambda functions using the Gradle build tool +""" + +from .workflow import JavaGradleWorkflow diff --git a/aws_lambda_builders/workflows/java_gradle/actions.py b/aws_lambda_builders/workflows/java_gradle/actions.py new file mode 100644 index 000000000..3108c8aff --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/actions.py @@ -0,0 +1,84 @@ +""" +Actions for the Java Gradle Workflow +""" + +import os +from aws_lambda_builders.actions import ActionFailedError, BaseAction, Purpose +from .gradle import GradleExecutionError + + +class JavaGradleBuildAction(BaseAction): + NAME = "GradleBuild" + DESCRIPTION = "Building the project using Gradle" + PURPOSE = Purpose.COMPILE_SOURCE + + INIT_SCRIPT = 'lambda-build-init.gradle' + SCRATCH_DIR_PROPERTY = 'software.amazon.aws.lambdabuilders.scratch-dir' + GRADLE_CACHE_DIR_NAME = 'gradle-cache' + + def __init__(self, + source_dir, + build_file, + subprocess_gradle, + scratch_dir, + os_utils): + self.source_dir = source_dir + self.build_file = build_file + self.scratch_dir = scratch_dir + self.subprocess_gradle = subprocess_gradle + self.os_utils = os_utils + self.cache_dir = os.path.join(self.scratch_dir, self.GRADLE_CACHE_DIR_NAME) + + def execute(self): + init_script_file = self._copy_init_script() + self._build_project(init_script_file) + + @property + def gradle_cache_dir(self): + return self.cache_dir + + def _copy_init_script(self): + try: + src = os.path.join(os.path.dirname(__file__), 'resources', self.INIT_SCRIPT) + dst = os.path.join(self.scratch_dir, self.INIT_SCRIPT) + return self.os_utils.copy(src, dst) + except Exception as ex: + raise ActionFailedError(str(ex)) + + def _build_project(self, init_script_file): + try: + if not self.os_utils.exists(self.scratch_dir): + self.os_utils.makedirs(self.scratch_dir) + self.subprocess_gradle.build(self.source_dir, self.build_file, self.gradle_cache_dir, + init_script_file, + {self.SCRATCH_DIR_PROPERTY: os.path.abspath(self.scratch_dir)}) + except GradleExecutionError as ex: + raise ActionFailedError(str(ex)) + + +class JavaGradleCopyArtifactsAction(BaseAction): + NAME = "CopyArtifacts" + DESCRIPTION = "Copying the built artifacts" + PURPOSE = Purpose.COPY_SOURCE + + def __init__(self, + source_dir, + artifacts_dir, + build_dir, + os_utils): + self.source_dir = source_dir + self.artifacts_dir = artifacts_dir + self.build_dir = build_dir + self.os_utils = os_utils + + def execute(self): + self._copy_artifacts() + + def _copy_artifacts(self): + lambda_build_output = os.path.join(self.build_dir, 'build', 'distributions', 'lambda-build') + try: + if not self.os_utils.exists(self.artifacts_dir): + self.os_utils.makedirs(self.artifacts_dir) + self.os_utils.copytree(lambda_build_output, self.artifacts_dir) + except Exception as ex: + raise ActionFailedError(str(ex)) diff --git a/aws_lambda_builders/workflows/java_gradle/gradle.py b/aws_lambda_builders/workflows/java_gradle/gradle.py new file mode 100644 index 000000000..e1222ba99 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/gradle.py @@ -0,0 +1,53 @@ +""" +Wrapper around calls to Gradle through a subprocess. +""" + +import logging +import subprocess + +LOG = logging.getLogger(__name__) + + +class GradleExecutionError(Exception): + MESSAGE = "Gradle Failed: {message}" + + def __init__(self, **kwargs): + Exception.__init__(self, self.MESSAGE.format(**kwargs)) + + +class BuildFileNotFoundError(GradleExecutionError): + def __init__(self, build_file_path): + super(BuildFileNotFoundError, self).__init__( + message='Gradle build file not found: %s' % build_file_path) + + +class SubprocessGradle(object): + + def __init__(self, gradle_binary, os_utils=None): + if gradle_binary is None: + raise ValueError("Must provide Gradle BinaryPath") + self.gradle_binary = gradle_binary + if os_utils is None: + raise ValueError("Must provide OSUtils") + self.os_utils = os_utils + + def build(self, source_dir, build_file, cache_dir=None, init_script_path=None, properties=None): + if not self.os_utils.exists(build_file): + raise BuildFileNotFoundError(build_file) + + args = ['build', '--build-file', build_file] + if cache_dir is not None: + args.extend(['--project-cache-dir', cache_dir]) + if properties is not None: + args.extend(['-D%s=%s' % (n, v) for n, v in properties.items()]) + if init_script_path is not None: + args.extend(['--init-script', init_script_path]) + ret_code, _, stderr = self._run(args, source_dir) + if ret_code != 0: + raise GradleExecutionError(message=stderr.decode('utf8').strip()) + + def _run(self, args, cwd=None): + p = self.os_utils.popen([self.gradle_binary.binary_path] + args, cwd=cwd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + return p.returncode, stdout, stderr diff --git a/aws_lambda_builders/workflows/java_gradle/gradle_resolver.py b/aws_lambda_builders/workflows/java_gradle/gradle_resolver.py new file mode 100644 index 000000000..7827d5083 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/gradle_resolver.py @@ -0,0 +1,31 @@ +""" +Gradle executable resolution +""" + +from .utils import OSUtils + + +class GradleResolver(object): + + def __init__(self, executable_search_paths=None, os_utils=None): + self.binary = 'gradle' + self.executables = [self.binary] + self.executable_search_paths = executable_search_paths + self.os_utils = os_utils if os_utils else OSUtils() + + @property + def exec_paths(self): + # Prefer gradlew/gradlew.bat + paths = self.os_utils.which(self.wrapper_name, executable_search_paths=self.executable_search_paths) + if not paths: + # fallback to the gradle binary + paths = self.os_utils.which('gradle', executable_search_paths=self.executable_search_paths) + + if not paths: + raise ValueError("No Gradle executable found!") + + return paths + + @property + def wrapper_name(self): + return 'gradlew.bat' if self.os_utils.is_windows() else 'gradlew' diff --git a/aws_lambda_builders/workflows/java_gradle/gradle_validator.py b/aws_lambda_builders/workflows/java_gradle/gradle_validator.py new file mode 100644 index 000000000..3350baa84 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/gradle_validator.py @@ -0,0 +1,64 @@ +""" +Gradle Binary Validation +""" + +import logging +import re + +from .utils import OSUtils + +LOG = logging.getLogger(__name__) + + +class GradleValidator(object): + VERSION_STRING_WARNING = "%s failed to return a version string using the '-v' option. The workflow is unable to " \ + "check that the version of the JVM used is compatible with AWS Lambda." + + MAJOR_VERSION_WARNING = "%s is using a JVM with major version %s which is newer than 8 that is supported by AWS " \ + "Lambda. The compiled function code may not run in AWS Lambda unless the project has " \ + "been configured to be compatible with Java 8 using 'targetCompatibility' in Gradle." + + def __init__(self, os_utils=None, log=None): + self.language = 'java' + self._valid_binary_path = None + self.os_utils = OSUtils() if not os_utils else os_utils + self.log = LOG if not log else log + + def validate(self, gradle_path): + jvm_mv = self._get_major_version(gradle_path) + + if jvm_mv: + if int(jvm_mv) > 8: + self.log.warning(self.MAJOR_VERSION_WARNING, gradle_path, jvm_mv) + else: + self.log.warning(self.VERSION_STRING_WARNING, gradle_path) + + self._valid_binary_path = gradle_path + return self._valid_binary_path + + @property + def validated_binary_path(self): + return self._valid_binary_path + + def _get_major_version(self, gradle_path): + vs = self._get_jvm_string(gradle_path) + if vs: + m = re.search(r'JVM:\s+(\d.*)', vs) + version = m.group(1).split('.') + # For Java 8 or earlier, version strings begin with 1.{Major Version} + if version[0] == '1': + return version[1] + # Starting with Java 9, the major version is first + return version[0] + + def _get_jvm_string(self, gradle_path): + p = self.os_utils.popen([gradle_path, '-version'], stdout=self.os_utils.pipe, stderr=self.os_utils.pipe) + stdout, _ = p.communicate() + + if p.returncode != 0: + return None + + for l in stdout.splitlines(): + l_dec = l.decode() + if l_dec.startswith('JVM'): + return l_dec diff --git a/aws_lambda_builders/workflows/java_gradle/resources/lambda-build-init.gradle b/aws_lambda_builders/workflows/java_gradle/resources/lambda-build-init.gradle new file mode 100644 index 000000000..d98d72635 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/resources/lambda-build-init.gradle @@ -0,0 +1,105 @@ +import groovy.io.FileType +import java.nio.file.FileAlreadyExistsException +import java.nio.file.Files +import java.nio.file.FileVisitOption +import java.nio.file.FileVisitResult +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.SimpleFileVisitor +import java.security.MessageDigest +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream +import java.util.zip.ZipEntry + +gradle.ext.SCRATCH_DIR_PROPERTY = 'software.amazon.aws.lambdabuilders.scratch-dir' + +// Called after the project has been evaluated +gradle.afterProject({p -> + def buildDir = buildDirForProject(p) + p.buildDir = buildDir +}) + +gradle.taskGraph.afterTask{ t -> + if (t.name != 'build') { + return; + } + + def artifactJars = t.project.configurations.archives.artifacts.files.files + + def runtimeCpJars = t.project.configurations.runtimeClasspath.files + + def artifactDir = createArtifactDir(t.project) + + copyToArtifactDir(artifactDir, artifactJars, runtimeCpJars) +} + +def buildDirForProject(p) { + def scratchDir = System.properties[SCRATCH_DIR_PROPERTY] + if (scratchDir == null) { + throw new RuntimeException("Missing '${SCRATCH_DIR_PROPERTY}' value") + } + return Paths.get(scratchDir, scratchDirForProject(p), 'build') +} + +def scratchDirForProject(p) { + def sha1 = MessageDigest.getInstance('SHA-1') + return sha1.digest(p.projectDir.toString().getBytes('UTF-8')).encodeHex().toString() +} + +def assertExpectedBuildDir(p) { + def expected = buildDirForProject(p) + if (!expected.equals(p.buildDir.toPath())) { + throw new RuntimeException("Project buildDir was changed by the project build script! Expected $expected but found ${p.buildDir}") + } +} + +def createArtifactDir(project) { + def distsDir = project.buildDir.toPath().resolve(project.distsDirName).resolve('lambda-build') + return makeDirs(distsDir) +} + +def copyToArtifactDir(artifactDir, artifactJars, classPathJars) { + artifactJars.each { + it.withInputStream({ jis -> + def zipIs = new ZipInputStream(jis) + for (def e = zipIs.getNextEntry(); e != null; e = zipIs.getNextEntry()) { + def entryPath = artifactDir.resolve(e.name) + if (e.isDirectory()) { + makeDirs(entryPath) + } else { + copyToFile(zipIs, entryPath) + } + zipIs.closeEntry() + } + zipIs.close() + }) + } + + def libDir = artifactDir.resolve('lib') + makeDirs(libDir) + classPathJars.each { + def jarPath = libDir.resolve(it.name) + it.withInputStream({ jIs -> + copyToFile(jIs, jarPath) + }) + } +} + +def makeDirs(p) { + try { + Files.createDirectories(p) + } catch (FileAlreadyExistsException e) { + // ignored + } + return p +} + +def copyToFile(zipIs, path) { + path.withOutputStream({ fos -> + byte[] buff = new byte[4096] + int bytesRead + while ((bytesRead = zipIs.read(buff)) != -1) { + fos.write(buff, 0, bytesRead) + } + }) +} diff --git a/aws_lambda_builders/workflows/java_gradle/utils.py b/aws_lambda_builders/workflows/java_gradle/utils.py new file mode 100644 index 000000000..2e178a320 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/utils.py @@ -0,0 +1,54 @@ +""" +Commonly used utilities +""" + +import os +import platform +import shutil +import subprocess +from aws_lambda_builders.utils import which + + +class OSUtils(object): + """ + Convenience wrapper around common system functions + """ + + def popen(self, command, stdout=None, stderr=None, env=None, cwd=None): + p = subprocess.Popen(command, stdout=stdout, stderr=stderr, env=env, cwd=cwd) + return p + + def is_windows(self): + return platform.system().lower() == 'windows' + + def copy(self, src, dst): + shutil.copy2(src, dst) + return dst + + def listdir(self, d): + return os.listdir(d) + + def exists(self, p): + return os.path.exists(p) + + def which(self, executable, executable_search_paths=None): + return which(executable, executable_search_paths=executable_search_paths) + + def copytree(self, source, destination): + if not os.path.exists(destination): + self.makedirs(destination) + names = self.listdir(source) + for name in names: + new_source = os.path.join(source, name) + new_destination = os.path.join(destination, name) + if os.path.isdir(new_source): + self.copytree(new_source, new_destination) + else: + self.copy(new_source, new_destination) + + def makedirs(self, d): + return os.makedirs(d) + + @property + def pipe(self): + return subprocess.PIPE diff --git a/aws_lambda_builders/workflows/java_gradle/workflow.py b/aws_lambda_builders/workflows/java_gradle/workflow.py new file mode 100644 index 000000000..cb34b14f9 --- /dev/null +++ b/aws_lambda_builders/workflows/java_gradle/workflow.py @@ -0,0 +1,75 @@ +""" +Java Gradle Workflow +""" +import hashlib +import os +from aws_lambda_builders.workflow import BaseWorkflow, Capability +from .actions import JavaGradleBuildAction, JavaGradleCopyArtifactsAction +from .gradle import SubprocessGradle +from .utils import OSUtils +from .gradle_resolver import GradleResolver +from .gradle_validator import GradleValidator + + +class JavaGradleWorkflow(BaseWorkflow): + """ + A Lambda builder workflow that knows how to build Java projects using Gradle. + """ + NAME = "JavaGradleWorkflow" + + CAPABILITY = Capability(language="java", + dependency_manager="gradle", + application_framework=None) + + INIT_FILE = "lambda-build-init.gradle" + + def __init__(self, + source_dir, + artifacts_dir, + scratch_dir, + manifest_path, + **kwargs): + super(JavaGradleWorkflow, self).__init__(source_dir, + artifacts_dir, + scratch_dir, + manifest_path, + **kwargs) + + self.os_utils = OSUtils() + self.build_dir = None + subprocess_gradle = SubprocessGradle(gradle_binary=self.binaries['gradle'], os_utils=self.os_utils) + + self.actions = [ + JavaGradleBuildAction(source_dir, + manifest_path, + subprocess_gradle, + scratch_dir, + self.os_utils), + JavaGradleCopyArtifactsAction(source_dir, + artifacts_dir, + self.build_output_dir, + self.os_utils) + ] + + def get_resolvers(self): + return [GradleResolver(executable_search_paths=self.executable_search_paths)] + + def get_validators(self): + return [GradleValidator(self.os_utils)] + + @property + def build_output_dir(self): + if self.build_dir is None: + self.build_dir = os.path.join(self.scratch_dir, self._compute_scratch_subdir()) + return self.build_dir + + def _compute_scratch_subdir(self): + """ + Compute where the init script will instruct Gradle to place the built artifacts for the lambda within + `scratch_dir`; i.e. the that it will set for 'project.buildDir`. + + :return: The path of the buildDir used for building the lambda. + """ + sha1 = hashlib.sha1() + sha1.update(os.path.abspath(self.source_dir).encode('utf8')) + return sha1.hexdigest() diff --git a/tests/functional/workflows/java_gradle/__init__.py b/tests/functional/workflows/java_gradle/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/functional/workflows/java_gradle/test_java_utils.py b/tests/functional/workflows/java_gradle/test_java_utils.py new file mode 100644 index 000000000..a071dbab7 --- /dev/null +++ b/tests/functional/workflows/java_gradle/test_java_utils.py @@ -0,0 +1,57 @@ +import os +import sys +import tempfile + +from unittest import TestCase + +from aws_lambda_builders.workflows.java_gradle import utils + + +class TestOSUtils(TestCase): + + def setUp(self): + self.src = tempfile.mkdtemp() + self.dst = tempfile.mkdtemp() + self.os_utils = utils.OSUtils() + + def test_popen_runs_a_process_and_returns_outcome(self): + cwd_py = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata', 'cwd.py') + p = self.os_utils.popen([sys.executable, cwd_py], + stdout=self.os_utils.pipe, + stderr=self.os_utils.pipe) + out, err = p.communicate() + self.assertEqual(p.returncode, 0) + self.assertEqual(out.decode('utf8').strip(), os.getcwd()) + + def test_popen_can_accept_cwd(self): + testdata_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'testdata') + p = self.os_utils.popen([sys.executable, 'cwd.py'], + stdout=self.os_utils.pipe, + stderr=self.os_utils.pipe, + cwd=testdata_dir) + out, err = p.communicate() + self.assertEqual(p.returncode, 0) + self.assertEqual(out.decode('utf8').strip(), os.path.abspath(testdata_dir)) + + def test_listdir(self): + names = ['a', 'b', 'c'] + for n in names: + self.new_file(self.src, n) + self.assertEquals(set(names), set(self.os_utils.listdir(self.src))) + + def test_copy(self): + f = self.new_file(self.src, 'a') + expected = os.path.join(self.dst, 'a') + copy_ret = self.os_utils.copy(f, expected) + self.assertEquals(expected, copy_ret) + self.assertTrue('a' in os.listdir(self.dst)) + + def test_exists(self): + self.new_file(self.src, 'foo') + self.assertTrue(self.os_utils.exists(os.path.join(self.src, 'foo'))) + + def new_file(self, d, name): + p = os.path.join(d, name) + with open(p, 'w') as f: + f.close() + return p diff --git a/tests/integration/workflows/java_gradle/test_java_gradle.py b/tests/integration/workflows/java_gradle/test_java_gradle.py new file mode 100644 index 000000000..d9fa06bb2 --- /dev/null +++ b/tests/integration/workflows/java_gradle/test_java_gradle.py @@ -0,0 +1,119 @@ +import os +import shutil +import tempfile + +from zipfile import ZipFile +from unittest import TestCase + +from aws_lambda_builders.builder import LambdaBuilder +from aws_lambda_builders.exceptions import WorkflowFailedError + + +class TestJavaGradle(TestCase): + SINGLE_BUILD_TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "testdata", "single-build") + MULTI_BUILD_TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "testdata", "multi-build") + + def setUp(self): + self.artifacts_dir = tempfile.mkdtemp() + self.scratch_dir = tempfile.mkdtemp() + self.builder = LambdaBuilder(language='java', dependency_manager='gradle', application_framework=None) + self.runtime = 'java' + + def tearDown(self): + shutil.rmtree(self.artifacts_dir) + shutil.rmtree(self.scratch_dir) + + def test_build_single_build_with_deps(self): + source_dir = os.path.join(self.SINGLE_BUILD_TEST_DATA_DIR, 'with-deps') + manifest_path = os.path.join(source_dir, 'build.gradle') + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, manifest_path, runtime=self.runtime) + expected_files = [p('aws', 'lambdabuilders', 'Main.class'), p('lib', 'annotations-2.1.0.jar')] + + self.assert_artifact_contains_files(expected_files) + + def test_build_single_build_with_resources(self): + source_dir = os.path.join(self.SINGLE_BUILD_TEST_DATA_DIR, 'with-resources') + manifest_path = os.path.join(source_dir, 'build.gradle') + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, manifest_path, runtime=self.runtime) + expected_files = [p('aws', 'lambdabuilders', 'Main.class'), p('some_data.txt'), + p('lib', 'annotations-2.1.0.jar')] + + self.assert_artifact_contains_files(expected_files) + + def test_build_single_build_with_test_deps_test_jars_not_included(self): + source_dir = os.path.join(self.SINGLE_BUILD_TEST_DATA_DIR, 'with-test-deps') + manifest_path = os.path.join(source_dir, 'build.gradle') + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, manifest_path, runtime=self.runtime) + expected_files = [p('aws', 'lambdabuilders', 'Main.class'), p('lib', 'annotations-2.1.0.jar')] + + self.assert_artifact_contains_files(expected_files) + self.assert_artifact_not_contains_file(p('lib', 's3-2.1.0.jar')) + + def test_build_single_build_with_deps_gradlew(self): + source_dir = os.path.join(self.SINGLE_BUILD_TEST_DATA_DIR, 'with-deps-gradlew') + manifest_path = os.path.join(source_dir, 'build.gradle') + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, manifest_path, runtime=self.runtime, + executable_search_paths=[source_dir]) + expected_files = [p('aws', 'lambdabuilders', 'Main.class'), p('lib', 'annotations-2.1.0.jar')] + + self.assert_artifact_contains_files(expected_files) + + def test_build_multi_build_with_deps_lambda1(self): + parent_dir = os.path.join(self.MULTI_BUILD_TEST_DATA_DIR, 'with-deps') + manifest_path = os.path.join(parent_dir, 'lambda1', 'build.gradle') + + lambda1_source = os.path.join(parent_dir, 'lambda1') + self.builder.build(lambda1_source, self.artifacts_dir, self.scratch_dir, manifest_path, + runtime=self.runtime) + + lambda1_expected_files = [p('aws', 'lambdabuilders', 'Lambda1_Main.class'), p('lib', 'annotations-2.1.0.jar')] + self.assert_artifact_contains_files(lambda1_expected_files) + + def test_build_multi_build_with_deps_lambda2(self): + parent_dir = os.path.join(self.MULTI_BUILD_TEST_DATA_DIR, 'with-deps') + manifest_path = os.path.join(parent_dir, 'lambda2', 'build.gradle') + + lambda2_source = os.path.join(parent_dir, 'lambda2') + self.builder.build(lambda2_source, self.artifacts_dir, self.scratch_dir, manifest_path, + runtime=self.runtime) + + lambda2_expected_files = [p('aws', 'lambdabuilders', 'Lambda2_Main.class'), p('lib', 'annotations-2.1.0.jar')] + self.assert_artifact_contains_files(lambda2_expected_files) + + def test_build_multi_build_with_deps_inter_module(self): + parent_dir = os.path.join(self.MULTI_BUILD_TEST_DATA_DIR, 'with-deps-inter-module') + manifest_path = os.path.join(parent_dir, 'lambda1', 'build.gradle') + + lambda1_source = os.path.join(parent_dir, 'lambda1') + self.builder.build(lambda1_source, self.artifacts_dir, self.scratch_dir, manifest_path, + runtime=self.runtime) + + lambda1_expected_files = [p('aws', 'lambdabuilders', 'Lambda1_Main.class'), p('lib', 'annotations-2.1.0.jar'), + p('lib', 'common.jar')] + self.assert_artifact_contains_files(lambda1_expected_files) + + def test_build_single_build_with_deps_broken(self): + source_dir = os.path.join(self.SINGLE_BUILD_TEST_DATA_DIR, 'with-deps-broken') + manifest_path = os.path.join(source_dir, 'build.gradle') + with self.assertRaises(WorkflowFailedError) as raised: + self.builder.build(source_dir, self.artifacts_dir, self.scratch_dir, manifest_path, runtime=self.runtime) + self.assertTrue(raised.exception.args[0].startswith('JavaGradleWorkflow:GradleBuild - Gradle Failed')) + + def assert_artifact_contains_files(self, files): + for f in files: + self.assert_artifact_contains_file(f) + + def assert_artifact_contains_file(self, p): + self.assertTrue(os.path.exists(os.path.join(self.artifacts_dir, p))) + + def assert_artifact_not_contains_file(self, p): + self.assertFalse(os.path.exists(os.path.join(self.artifacts_dir, p))) + + def assert_zip_contains(self, zip_path, files): + with ZipFile(zip_path) as z: + zip_names = set(z.namelist()) + self.assertTrue(set(files).issubset(zip_names)) + + +def p(path, *comps): + return os.path.join(path, *comps) diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/build.gradle new file mode 100644 index 000000000..5c41c8421 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/build.gradle @@ -0,0 +1,5 @@ +allprojects { + repositories { + mavenCentral() + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/common/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/common/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/common/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/common/src/main/java/aws/lambdabuilders/Foo.java b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/common/src/main/java/aws/lambdabuilders/Foo.java new file mode 100644 index 000000000..542be0095 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/common/src/main/java/aws/lambdabuilders/Foo.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Foo { + public void sayHello() { + System.out.println("Hello world!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda1/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda1/build.gradle new file mode 100644 index 000000000..f3648f8cc --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda1/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' + implementation project(':common') +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda1/src/main/java/aws/lambdabuilders/Lambda1_Main.java b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda1/src/main/java/aws/lambdabuilders/Lambda1_Main.java new file mode 100644 index 000000000..fca189e70 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda1/src/main/java/aws/lambdabuilders/Lambda1_Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Lambda1_Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda2/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda2/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda2/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda2/src/main/java/aws/lambdabuilders/Lambda2_Main.java b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda2/src/main/java/aws/lambdabuilders/Lambda2_Main.java new file mode 100644 index 000000000..5f7fa59ef --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/lambda2/src/main/java/aws/lambdabuilders/Lambda2_Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Lambda2_Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/settings.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/settings.gradle new file mode 100644 index 000000000..965186cb4 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps-inter-module/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'multi-build' +include 'lambda1', 'lambda2', 'common' diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/build.gradle new file mode 100644 index 000000000..5c41c8421 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/build.gradle @@ -0,0 +1,5 @@ +allprojects { + repositories { + mavenCentral() + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda1/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda1/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda1/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda1/src/main/java/aws/lambdabuilders/Lambda1_Main.java b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda1/src/main/java/aws/lambdabuilders/Lambda1_Main.java new file mode 100644 index 000000000..fca189e70 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda1/src/main/java/aws/lambdabuilders/Lambda1_Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Lambda1_Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda2/build.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda2/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda2/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda2/src/main/java/aws/lambdabuilders/Lambda2_Main.java b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda2/src/main/java/aws/lambdabuilders/Lambda2_Main.java new file mode 100644 index 000000000..5f7fa59ef --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/lambda2/src/main/java/aws/lambdabuilders/Lambda2_Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Lambda2_Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/settings.gradle b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/settings.gradle new file mode 100644 index 000000000..6c882858a --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/multi-build/with-deps/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'multi-build' +include 'lambda1', 'lambda2' diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-broken/build.gradle b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-broken/build.gradle new file mode 100644 index 000000000..71d034524 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-broken/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:does-not-exist:1.2.3' +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-broken/src/main/java/aws/lambdabuilders/Main.java b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-broken/src/main/java/aws/lambdabuilders/Main.java new file mode 100644 index 000000000..22c7a289d --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-broken/src/main/java/aws/lambdabuilders/Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/build.gradle b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradle/wrapper/gradle-wrapper.jar b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..87b738cbd Binary files /dev/null and b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradle/wrapper/gradle-wrapper.jar differ diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradle/wrapper/gradle-wrapper.properties b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..558870dad --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-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/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradlew b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradlew new file mode 100755 index 000000000..af6708ff2 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-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/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradlew.bat b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/gradlew.bat new file mode 100644 index 000000000..0f8d5937c --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-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/workflows/java_gradle/testdata/single-build/with-deps-gradlew/src/main/java/aws/lambdabuilders/Main.java b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/src/main/java/aws/lambdabuilders/Main.java new file mode 100644 index 000000000..22c7a289d --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps-gradlew/src/main/java/aws/lambdabuilders/Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps/build.gradle b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-deps/src/main/java/aws/lambdabuilders/Main.java b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps/src/main/java/aws/lambdabuilders/Main.java new file mode 100644 index 000000000..22c7a289d --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-deps/src/main/java/aws/lambdabuilders/Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/build.gradle b/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/build.gradle new file mode 100644 index 000000000..e4af9df15 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/src/main/java/aws/lambdabuilders/Main.java b/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/src/main/java/aws/lambdabuilders/Main.java new file mode 100644 index 000000000..22c7a289d --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/src/main/java/aws/lambdabuilders/Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/src/main/resources/some_data.txt b/tests/integration/workflows/java_gradle/testdata/single-build/with-resources/src/main/resources/some_data.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-test-deps/build.gradle b/tests/integration/workflows/java_gradle/testdata/single-build/with-test-deps/build.gradle new file mode 100644 index 000000000..a413ecf82 --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-test-deps/build.gradle @@ -0,0 +1,12 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'software.amazon.awssdk:annotations:2.1.0' + testImplementation 'software.amazon.awssdk:s3:2.1.0' +} diff --git a/tests/integration/workflows/java_gradle/testdata/single-build/with-test-deps/src/main/java/aws/lambdabuilders/Main.java b/tests/integration/workflows/java_gradle/testdata/single-build/with-test-deps/src/main/java/aws/lambdabuilders/Main.java new file mode 100644 index 000000000..22c7a289d --- /dev/null +++ b/tests/integration/workflows/java_gradle/testdata/single-build/with-test-deps/src/main/java/aws/lambdabuilders/Main.java @@ -0,0 +1,7 @@ +package aws.lambdabuilders; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello AWS Lambda Builders!"); + } +} diff --git a/tests/unit/workflows/java_gradle/__init__.py b/tests/unit/workflows/java_gradle/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/workflows/java_gradle/test_actions.py b/tests/unit/workflows/java_gradle/test_actions.py new file mode 100644 index 000000000..f69a97075 --- /dev/null +++ b/tests/unit/workflows/java_gradle/test_actions.py @@ -0,0 +1,103 @@ +from unittest import TestCase +from mock import patch +import os + +from aws_lambda_builders.actions import ActionFailedError +from aws_lambda_builders.workflows.java_gradle.actions import JavaGradleBuildAction, JavaGradleCopyArtifactsAction, \ + GradleExecutionError + + +class TestJavaGradleBuildAction(TestCase): + + @patch("aws_lambda_builders.workflows.java_gradle.utils.OSUtils") + @patch("aws_lambda_builders.workflows.java_gradle.gradle.SubprocessGradle") + def setUp(self, MockSubprocessGradle, MockOSUtils): + self.subprocess_gradle = MockSubprocessGradle.return_value + self.os_utils = MockOSUtils.return_value + self.os_utils.copy.side_effect = lambda src, dst: dst + self.source_dir = os.path.join('source_dir') + self.manifest_path = os.path.join(self.source_dir, 'manifest') + self.artifacts_dir = os.path.join('artifacts_dir') + self.scratch_dir = os.path.join('scratch_dir') + + def test_calls_gradle_build(self): + action = JavaGradleBuildAction(self.source_dir, + self.manifest_path, + self.subprocess_gradle, + self.scratch_dir, + self.os_utils) + action.execute() + self.subprocess_gradle.build.assert_called_with(self.source_dir, + self.manifest_path, + os.path.join(self.scratch_dir, + JavaGradleBuildAction.GRADLE_CACHE_DIR_NAME), + os.path.join(self.scratch_dir, + JavaGradleBuildAction.INIT_SCRIPT), + {JavaGradleBuildAction.SCRATCH_DIR_PROPERTY: os.path.abspath( + self.scratch_dir)}) + + def test_error_in_init_file_copy_raises_action_error(self): + self.os_utils.copy.side_effect = Exception("Copy failed!") + action = JavaGradleBuildAction(self.source_dir, + self.manifest_path, + self.subprocess_gradle, + self.scratch_dir, + self.os_utils) + with self.assertRaises(ActionFailedError) as raised: + action.execute() + self.assertEquals(raised.exception.args[0], "Copy failed!") + + def test_error_building_project_raises_action_error(self): + self.subprocess_gradle.build.side_effect = GradleExecutionError(message='Build failed!') + action = JavaGradleBuildAction(self.source_dir, + self.manifest_path, + self.subprocess_gradle, + self.scratch_dir, + self.os_utils) + with self.assertRaises(ActionFailedError) as raised: + action.execute() + self.assertEquals(raised.exception.args[0], 'Gradle Failed: Build failed!') + + def test_computes_correct_cache_dir(self): + action = JavaGradleBuildAction(self.source_dir, + self.manifest_path, + self.subprocess_gradle, + self.scratch_dir, + self.os_utils) + self.assertEquals(action.gradle_cache_dir, + os.path.join(self.scratch_dir, JavaGradleBuildAction.GRADLE_CACHE_DIR_NAME)) + + +class TestJavaGradleCopyArtifactsAction(TestCase): + + @patch("aws_lambda_builders.workflows.java_gradle.utils.OSUtils") + def setUp(self, MockOSUtils): + self.os_utils = MockOSUtils.return_value + self.os_utils.copy.side_effect = lambda src, dst: dst + self.source_dir = "source_dir" + self.artifacts_dir = "artifacts_dir" + self.scratch_dir = "scratch_dir" + self.build_dir = os.path.join(self.scratch_dir, 'build1') + + def test_copies_artifacts(self): + self.os_utils.copytree.side_effect = lambda src, dst: None + self.os_utils.copy.side_effect = lambda src, dst: None + + action = JavaGradleCopyArtifactsAction(self.source_dir, + self.artifacts_dir, + self.build_dir, + self.os_utils) + action.execute() + + self.os_utils.copytree.assert_called_with( + os.path.join(self.build_dir, 'build', 'distributions', 'lambda-build'), self.artifacts_dir) + + def test_error_in_artifact_copy_raises_action_error(self): + self.os_utils.copytree.side_effect = Exception("scandir failed!") + action = JavaGradleCopyArtifactsAction(self.source_dir, + self.artifacts_dir, + self.build_dir, + self.os_utils) + with self.assertRaises(ActionFailedError) as raised: + action.execute() + self.assertEquals(raised.exception.args[0], "scandir failed!") diff --git a/tests/unit/workflows/java_gradle/test_gradle.py b/tests/unit/workflows/java_gradle/test_gradle.py new file mode 100644 index 000000000..b4376cafc --- /dev/null +++ b/tests/unit/workflows/java_gradle/test_gradle.py @@ -0,0 +1,93 @@ +import subprocess + +from unittest import TestCase +from mock import patch + +from aws_lambda_builders.binary_path import BinaryPath +from aws_lambda_builders.workflows.java_gradle.gradle import SubprocessGradle, GradleExecutionError, \ + BuildFileNotFoundError + + +class FakePopen: + def __init__(self, out=b'out', err=b'err', retcode=0): + self.out = out + self.err = err + self.returncode = retcode + + def communicate(self): + return self.out, self.err + + def wait(self): + pass + + +class TestSubprocessGradle(TestCase): + + @patch("aws_lambda_builders.workflows.java_gradle.utils.OSUtils") + def setUp(self, MockOSUtils): + self.os_utils = MockOSUtils.return_value + self.os_utils.exists.side_effect = lambda d: True + self.popen = FakePopen() + self.os_utils.popen.side_effect = [self.popen] + self.gradle_path = '/path/to/gradle' + self.gradle_binary = BinaryPath(None, None, 'gradle', binary_path=self.gradle_path) + self.source_dir = '/foo/bar/baz' + self.manifest_path = '/foo/bar/baz/build.gradle' + self.init_script = '/path/to/init' + + def test_no_os_utils_build_init_throws(self): + with self.assertRaises(ValueError) as err_assert: + SubprocessGradle(gradle_binary=self.gradle_binary) + self.assertEquals(err_assert.exception.args[0], 'Must provide OSUtils') + + def test_no_gradle_exec_init_throws(self): + with self.assertRaises(ValueError) as err_assert: + SubprocessGradle(None) + self.assertEquals(err_assert.exception.args[0], 'Must provide Gradle BinaryPath') + + def test_no_build_file_throws(self): + self.os_utils.exists.side_effect = lambda d: False + gradle = SubprocessGradle(gradle_binary=self.gradle_binary, os_utils=self.os_utils) + with self.assertRaises(BuildFileNotFoundError) as raised: + gradle.build(self.source_dir, self.manifest_path) + self.assertEquals(raised.exception.args[0], + 'Gradle Failed: Gradle build file not found: %s' % self.manifest_path) + + def test_build_no_init_script(self): + gradle = SubprocessGradle(gradle_binary=self.gradle_binary, os_utils=self.os_utils) + gradle.build(self.source_dir, self.manifest_path) + self.os_utils.popen.assert_called_with([self.gradle_path, 'build', '--build-file', self.manifest_path], + cwd=self.source_dir, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + + def test_gradlew_path_is_dummy_uses_gradle_binary(self): + gradle = SubprocessGradle(gradle_binary=self.gradle_binary, os_utils=self.os_utils) + gradle.build(self.source_dir, self.manifest_path) + self.os_utils.popen.assert_called_with([self.gradle_path, 'build', '--build-file', self.manifest_path], + cwd=self.source_dir, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + + def test_build_with_init_script(self): + gradle = SubprocessGradle(gradle_binary=self.gradle_binary, os_utils=self.os_utils) + gradle.build(self.source_dir, self.manifest_path, init_script_path=self.init_script) + self.os_utils.popen.assert_called_with( + [self.gradle_path, 'build', '--build-file', self.manifest_path, '--init-script', self.init_script], + cwd=self.source_dir, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + + def test_raises_exception_if_retcode_not_0(self): + self.popen = FakePopen(retcode=1, err=b'Some Error Message') + self.os_utils.popen.side_effect = [self.popen] + gradle = SubprocessGradle(gradle_binary=self.gradle_binary, os_utils=self.os_utils) + with self.assertRaises(GradleExecutionError) as err: + gradle.build(self.source_dir, self.manifest_path) + self.assertEquals(err.exception.args[0], 'Gradle Failed: Some Error Message') + + def test_includes_build_properties_in_command(self): + gradle = SubprocessGradle(gradle_binary=self.gradle_binary, os_utils=self.os_utils) + gradle.build(self.source_dir, self.manifest_path, init_script_path=self.init_script, properties={'foo': 'bar'}) + self.os_utils.popen.assert_called_with( + [self.gradle_path, 'build', '--build-file', self.manifest_path, '-Dfoo=bar', '--init-script', + self.init_script], + cwd=self.source_dir, stderr=subprocess.PIPE, stdout=subprocess.PIPE) diff --git a/tests/unit/workflows/java_gradle/test_gradle_validator.py b/tests/unit/workflows/java_gradle/test_gradle_validator.py new file mode 100644 index 000000000..cdadf52fa --- /dev/null +++ b/tests/unit/workflows/java_gradle/test_gradle_validator.py @@ -0,0 +1,75 @@ +from unittest import TestCase + +from mock import patch, Mock +from parameterized import parameterized +from aws_lambda_builders.workflows.java_gradle.gradle_validator import GradleValidator + + +class FakePopen(object): + def __init__(self, stdout=None, stderr=None, returncode=0): + self._stdout = stdout + self._stderr = stderr + self._returncode = returncode + + def communicate(self): + return self._stdout, self._stderr + + @property + def returncode(self): + return self._returncode + + +class TestGradleBinaryValidator(TestCase): + + @patch("aws_lambda_builders.workflows.java_gradle.utils.OSUtils") + def setUp(self, MockOSUtils): + self.mock_os_utils = MockOSUtils.return_value + self.mock_log = Mock() + self.gradle_path = '/path/to/gradle' + + @parameterized.expand([ + '1.7.0', + '1.8.9', + '11.0.0' + ]) + def test_accepts_any_jvm_mv(self, version): + version_string = ('JVM: %s' % version).encode() + self.mock_os_utils.popen.side_effect = [FakePopen(stdout=version_string)] + validator = GradleValidator(os_utils=self.mock_os_utils) + self.assertTrue(validator.validate(gradle_path=self.gradle_path)) + self.assertEqual(validator.validated_binary_path, self.gradle_path) + + def test_emits_warning_when_jvm_mv_greater_than_8(self): + version_string = 'JVM: 9.0.0'.encode() + self.mock_os_utils.popen.side_effect = [FakePopen(stdout=version_string)] + validator = GradleValidator(os_utils=self.mock_os_utils, log=self.mock_log) + self.assertTrue(validator.validate(gradle_path=self.gradle_path)) + self.assertEqual(validator.validated_binary_path, self.gradle_path) + self.mock_log.warning.assert_called_with(GradleValidator.MAJOR_VERSION_WARNING, self.gradle_path, '9') + + @parameterized.expand([ + '1.6.0', + '1.7.0', + '1.8.9' + ]) + def test_does_not_emit_warning_when_jvm_mv_8_or_less(self, version): + version_string = ('JVM: %s' % version).encode() + self.mock_os_utils.popen.side_effect = [FakePopen(stdout=version_string)] + validator = GradleValidator(os_utils=self.mock_os_utils, log=self.mock_log) + self.assertTrue(validator.validate(gradle_path=self.gradle_path)) + self.assertEqual(validator.validated_binary_path, self.gradle_path) + self.mock_log.warning.assert_not_called() + + def test_emits_warning_when_gradle_excutable_fails(self): + version_string = 'JVM: 9.0.0'.encode() + self.mock_os_utils.popen.side_effect = [FakePopen(stdout=version_string, returncode=1)] + validator = GradleValidator(os_utils=self.mock_os_utils, log=self.mock_log) + validator.validate(gradle_path=self.gradle_path) + self.mock_log.warning.assert_called_with(GradleValidator.VERSION_STRING_WARNING, self.gradle_path) + + def test_emits_warning_when_version_string_not_found(self): + version_string = 'The Java Version: 9.0.0'.encode() + self.mock_os_utils.popen.side_effect = [FakePopen(stdout=version_string, returncode=0)] + validator = GradleValidator(os_utils=self.mock_os_utils, log=self.mock_log) + validator.validate(gradle_path=self.gradle_path) + self.mock_log.warning.assert_called_with(GradleValidator.VERSION_STRING_WARNING, self.gradle_path) diff --git a/tests/unit/workflows/java_gradle/test_gradlew_resolver.py b/tests/unit/workflows/java_gradle/test_gradlew_resolver.py new file mode 100644 index 000000000..10b784c82 --- /dev/null +++ b/tests/unit/workflows/java_gradle/test_gradlew_resolver.py @@ -0,0 +1,44 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized +from aws_lambda_builders.workflows.java_gradle.gradle_resolver import GradleResolver + + +class TestGradleResolver(TestCase): + + @patch("aws_lambda_builders.workflows.java_gradle.utils.OSUtils") + def setUp(self, MockOSUtils): + self.mock_os_utils = MockOSUtils.return_value + self.mock_os_utils.is_windows.side_effect = [False] + + def test_gradlew_exists_returns_gradlew(self): + gradlew_path = '/path/to/gradlew' + self.mock_os_utils.which.side_effect = lambda executable, executable_search_paths: [gradlew_path] + + resolver = GradleResolver(os_utils=self.mock_os_utils) + self.assertEquals(resolver.exec_paths, [gradlew_path]) + + def test_gradlew_not_exists_returns_gradle_on_path(self): + gradle_path = '/path/to/gradle' + self.mock_os_utils.which.side_effect = lambda executable, executable_search_paths: \ + [] if executable == 'gradlew' else [gradle_path] + + resolver = GradleResolver(os_utils=self.mock_os_utils) + self.assertEquals(resolver.exec_paths, [gradle_path]) + + def test_throws_value_error_if_no_exec_found(self): + self.mock_os_utils.which.side_effect = lambda executable, executable_search_paths: [] + resolver = GradleResolver(os_utils=self.mock_os_utils) + with self.assertRaises(ValueError) as raised: + resolver.exec_paths() + self.assertEquals(raised.exception.args[0], 'No Gradle executable found!') + + @parameterized.expand([ + [True, 'gradlew.bat'], + [False, 'gradlew'] + ]) + def test_uses_correct_gradlew_name(self, is_windows, expected_wrapper_name): + self.mock_os_utils.is_windows.side_effect = [is_windows] + resolver = GradleResolver(os_utils=self.mock_os_utils) + self.assertEquals(resolver.wrapper_name, expected_wrapper_name) diff --git a/tests/unit/workflows/java_gradle/test_workflow.py b/tests/unit/workflows/java_gradle/test_workflow.py new file mode 100644 index 000000000..1a17da9d5 --- /dev/null +++ b/tests/unit/workflows/java_gradle/test_workflow.py @@ -0,0 +1,50 @@ +from unittest import TestCase + +import hashlib +import os +from aws_lambda_builders.workflows.java_gradle.workflow import JavaGradleWorkflow +from aws_lambda_builders.workflows.java_gradle.actions import JavaGradleBuildAction, JavaGradleCopyArtifactsAction +from aws_lambda_builders.workflows.java_gradle.gradle_resolver import GradleResolver +from aws_lambda_builders.workflows.java_gradle.gradle_validator import GradleValidator + + +class TestJavaGradleWorkflow(TestCase): + """ + the workflow requires an external utility (gradle) to run, so it is extensively tested in integration tests. + this is just a quick wiring test to provide fast feedback if things are badly broken + """ + + def test_workflow_sets_up_gradle_actions(self): + workflow = JavaGradleWorkflow("source", "artifacts", "scratch_dir", "manifest") + + self.assertEqual(len(workflow.actions), 2) + + self.assertIsInstance(workflow.actions[0], JavaGradleBuildAction) + + self.assertIsInstance(workflow.actions[1], JavaGradleCopyArtifactsAction) + + def test_workflow_sets_up_resolvers(self): + workflow = JavaGradleWorkflow("source", "artifacts", "scratch_dir", "manifest") + + resolvers = workflow.get_resolvers() + self.assertEqual(len(resolvers), 1) + + self.assertIsInstance(resolvers[0], GradleResolver) + + def test_workflow_sets_up_validators(self): + workflow = JavaGradleWorkflow("source", "artifacts", "scratch_dir", "manifest") + + validators = workflow.get_validators() + self.assertEqual(len(validators), 1) + + self.assertIsInstance(validators[0], GradleValidator) + + def test_computes_correct_build_dir(self): + workflow = JavaGradleWorkflow("source", "artifacts", "scratch_dir", "manifest") + + sha1 = hashlib.sha1() + sha1.update(os.path.abspath(workflow.source_dir).encode('utf8')) + + expected_build_dir = os.path.join(workflow.scratch_dir, sha1.hexdigest()) + + self.assertEqual(expected_build_dir, workflow.build_output_dir)