-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Support for gradle builder for Java #1007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7863557
9ba04cf
1b10abd
96d4205
f0b0207
8657df1
a50688c
923b134
96fc50a
673901e
bc4f135
e8a67e4
805832f
3deeb91
ffc6963
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's much cleaner if you do Also more pythonic :)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really. try/catch blocks should be scoped very tightly to the code you expect to throw. So when you know two statemtents will throw for two different reasons, I would rather have them in separte try blocks or let them throw very specific exceptions - not KeyError & ValueError. That's why I made this way. |
||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to be extended in the future? what is the value of this class, if its just returns the first one?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For inheritance.. I didn't create an abstract base class because it didn't seem helpful. But in future we will create a new subclass that will look up Samrc to find the workflow config to use given a runtime. This can be useful for |
||
| """ | ||
|
|
||
| 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How this works and is resolved needs to be clearly documented (somewhere)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add to the help text for now |
||
| # 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]): | ||
sanathkr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One day you will use pathlib.. one day..
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when we move to py3. Pathlib in py2 is really confusing |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just making sure, the protocol version is passed in by importing the builders library right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. |
||
| 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 | ||
| } | ||
| }) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0.1.0 🕺