| # Copyright 2015 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Java AppEngine support for Bazel. |
| |
| For now, it only support bundling a WebApp and running locally. |
| |
| To create a WebApp for Google AppEngine, add the rules: |
| appengine_war( |
| name = "MyWebApp", |
| # Jars to use for the classpath in the webapp. |
| jars = ["//java/com/google/examples/mywebapp:java"], |
| # data to put in the webapp, the directory structure of the data set |
| # will be maintained. |
| data = ["//java/com/google/examples/mywebapp:data"], |
| # Data's root path, it will be considered as the root of the data files. |
| # If unspecified, the path to the current package will be used. The path is |
| # relative to current package or, relative to the workspace root if starting |
| # with a leading slash. |
| data_path = "/java/com/google/examples/mywebapp", |
| ) |
| |
| |
| You can also make directly a single target for it with: |
| |
| java_war( |
| name = "MyWebApp", |
| srcs = glob(["**/*.java"]), |
| resources = ["..."], |
| data = ["..."], |
| data_path = "...", |
| ) |
| |
| Resources will be put in the classpath whereas data will be bundled at the root |
| of the war file. This is strictly equivalent to (it is actually a convenience |
| macros that translate to that): |
| |
| java_library( |
| name = "libMyWebApp", |
| srcs = glob(["**/*.java"]), |
| resources = ["..."], |
| ) |
| |
| appengine_war( |
| name = "MyWebApp", |
| jars = [":libMyWebApp"], |
| data = ["..."], |
| data_path = "...", |
| ) |
| |
| This rule will also create a .deploy target that will try to use the AppEngine |
| SDK to upload your application to AppEngine. It takes an optional argument: the |
| APP_ID. If not specified, it uses the default APP_ID provided in the application |
| web.xml. |
| """ |
| |
| jar_filetype = FileType([".jar"]) |
| |
| def _add_file(in_file, output, path = None): |
| output_path = output |
| input_path = in_file.path |
| if path and input_path.startswith(path): |
| output_path += input_path[len(path):] |
| return [ |
| "mkdir -p $(dirname %s)" % output_path, |
| "test -L %s || ln -s $(pwd)/%s %s\n" % (output_path, input_path, |
| output_path) |
| ] |
| |
| def _make_war(zipper, input_dir, output): |
| return [ |
| "(root=$(pwd);" + |
| ("cd %s &&" % input_dir) + |
| ("${root}/%s Cc ${root}/%s $(find .))" % (zipper.path, output.path)) |
| ] |
| |
| def _common_substring(str1, str2): |
| i = 0 |
| res = "" |
| for c in str1: |
| if str2[i] != c: |
| return res |
| res += c |
| i += 1 |
| return res |
| |
| def _short_path_dirname(path): |
| sp = path.short_path |
| return sp[0:len(sp)-len(path.basename)-1] |
| |
| def _war_impl(ctxt): |
| zipper = ctxt.file._zipper |
| |
| data_path = ctxt.attr.data_path |
| if not data_path: |
| data_path = _short_path_dirname(ctxt.outputs.war) |
| elif data_path[0] == "/": |
| data_path = data_path[1:] |
| else: # relative path |
| data_path = _short_path_dirname(ctxt.outputs.war) + "/" + data_path |
| |
| war = ctxt.outputs.war |
| build_output = war.path + ".build_output" |
| cmd = [ |
| "set -e;rm -rf " + build_output, |
| "mkdir -p " + build_output |
| ] |
| |
| inputs = ctxt.files.jars + [zipper] |
| cmd += ["mkdir -p %s" % build_output + "/WEB-INF/lib"] |
| for jar in ctxt.files.jars: |
| # Add the jar to WEB-INF/lib. |
| cmd += _add_file(jar, build_output + "/WEB-INF/lib") |
| # Add its runtime classpath to WEB-INF/lib |
| if hasattr(jar, "java"): |
| inputs += jar.java.transitive_runtime_deps |
| for run_jar in jar.java.transitive_runtime_deps: |
| cmd += _add_file(run_jar, build_output + "/WEB-INF/lib") |
| for jar in ctxt.files._appengine_deps: |
| cmd += _add_file(jar, build_output + "/WEB-INF/lib") |
| inputs += [jar] |
| |
| inputs += ctxt.files.data |
| for res in ctxt.files.data: |
| # Add the data file |
| cmd += _add_file(res, build_output, path = data_path) |
| |
| cmd += _make_war(zipper, build_output, war) |
| |
| ctxt.action( |
| inputs = inputs, |
| outputs = [war], |
| mnemonic="WAR", |
| command="\n".join(cmd), |
| use_default_shell_env=True) |
| |
| executable = ctxt.outputs.executable |
| appengine_sdk = None |
| for f in ctxt.files._appengine_sdk: |
| if not appengine_sdk: |
| appengine_sdk = f.path |
| elif not f.path.startswith(appengine_sdk): |
| appengine_sdk = _common_substring(appengine_sdk, f.path) |
| |
| classpath = [ |
| "${JAVA_RUNFILES}/%s" % jar.short_path |
| for jar in ctxt.files._appengine_jars |
| ] |
| substitutions = { |
| "%{zipper}": ctxt.file._zipper.short_path, |
| "%{war}": ctxt.outputs.war.short_path, |
| "%{java}": ctxt.file._java.short_path, |
| "%{appengine_sdk}": appengine_sdk, |
| "%{classpath}": (":".join(classpath)), |
| } |
| ctxt.template_action( |
| output = executable, |
| template = ctxt.file._runner_template, |
| substitutions = substitutions, |
| executable = True) |
| ctxt.template_action( |
| output = ctxt.outputs.deploy, |
| template = ctxt.file._deploy_template, |
| substitutions = substitutions, |
| executable = True) |
| |
| runfiles = ctxt.runfiles(files = [war, executable] |
| + ctxt.files._appengine_sdk |
| + ctxt.files._appengine_jars |
| + [ctxt.file._java, ctxt.file._zipper]) |
| return struct(runfiles = runfiles) |
| |
| appengine_war = rule( |
| _war_impl, |
| executable = True, |
| attrs = { |
| "_java": attr.label( |
| default=Label("//tools/jdk:java"), |
| single_file=True), |
| "_zipper": attr.label( |
| default=Label("//third_party/ijar:zipper"), |
| single_file=True), |
| "_runner_template": attr.label( |
| default=Label("//tools/build_rules/appengine:runner_template"), |
| single_file=True), |
| "_deploy_template": attr.label( |
| default=Label("//tools/build_rules/appengine:deploy_template"), |
| single_file=True), |
| "_appengine_sdk": attr.label( |
| default=Label("//external:appengine/java/sdk")), |
| "_appengine_jars": attr.label( |
| default=Label("//external:appengine/java/jars")), |
| "_appengine_deps": attr.label_list( |
| default=[ |
| Label("@appengine-java//:api"), |
| Label("@commons-lang//jar"), |
| Label("//third_party:apache_commons_collections"), |
| ] |
| ), |
| "jars": attr.label_list(allow_files=jar_filetype, mandatory=True), |
| "data": attr.label_list(allow_files=True), |
| "data_path": attr.string(), |
| }, |
| outputs = { |
| "war": "%{name}.war", |
| "deploy": "%{name}.deploy", |
| }) |
| |
| def java_war(name, data=[], data_path=None, **kwargs): |
| native.java_library(name = "lib%s" % name, **kwargs) |
| appengine_war(name = name, |
| jars = ["lib%s" % name], |
| data=data, |
| data_path=data_path) |