| # Copyright 2015 Google Inc. 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 = "...", |
| ) |
| |
| """ |
| |
| 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 c ${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 += 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 |
| ] |
| ctxt.file_action( |
| output = executable, |
| content = "\n".join([ |
| "#!/bin/bash", |
| "# autogenerated - do not edit.", |
| "case \"$0\" in", |
| "/*) self=\"$0\" ;;", |
| "*) self=\"$PWD/$0\";;", |
| "esac", |
| "", |
| "if [[ -z \"$JAVA_RUNFILES\" ]]; then", |
| " if [[ -e \"${self}.runfiles\" ]]; then", |
| " export JAVA_RUNFILES=\"${self}.runfiles\"", |
| " fi", |
| " if [[ -n \"$JAVA_RUNFILES\" ]]; then", |
| " export TEST_SRCDIR=${TEST_SRCDIR:-$JAVA_RUNFILES}", |
| " fi", |
| "fi", |
| "", |
| "root_path=$(pwd)", |
| "tmp_dir=$(mktemp -d ${TMPDIR:-/tmp}/war.XXXXXXXX)", |
| "trap \"cd ${root_path}; rm -rf ${tmp_dir}\" EXIT", |
| "cd ${tmp_dir}", |
| "${JAVA_RUNFILES}/%s x ${JAVA_RUNFILES}/%s" % ( |
| ctxt.file._zipper.short_path, ctxt.outputs.war.short_path), |
| "jvm_bin=${JAVA_RUNFILES}/%s" % (ctxt.file._java.short_path), |
| "if [[ ! -x ${jvm_bin} ]]; then", |
| " jvm_bin=$(which java)", |
| "fi", |
| "APP_ENGINE_ROOT=${JAVA_RUNFILES}/%s" % appengine_sdk, |
| "main_class=\"com.google.appengine.tools.development.DevAppServerMain\"", |
| "classpath=\"%s\"" % (":".join(classpath)), |
| "${jvm_bin} -Dappengine.sdk.root=${APP_ENGINE_ROOT} " |
| + "-cp \"${classpath}\" ${main_class} \"$@\" ${tmp_dir}", |
| "", |
| ]), |
| 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), |
| "_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", |
| }) |
| |
| 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) |