From b02a9e0d1a222fe1b393c51bc951db394634acfa Mon Sep 17 00:00:00 2001 From: Rob Figueiredo Date: Mon, 25 Nov 2019 15:37:18 -0500 Subject: [PATCH] plugin_modules: add closure_templates_plugin rule Pass classpath and option to Soy compiler in closure_js_template_library. Add unit test to confirm it works with a custom function. Rename plugin_modules to plugins to match Google's internal rule. Fixes #197 --- closure/defs.bzl | 2 + .../templates/closure_js_template_library.bzl | 26 ++++++-- .../templates/closure_templates_plugin.bzl | 42 +++++++++++++ closure/templates/test/BUILD | 32 +++++++++- closure/templates/test/ExampleModule.java | 61 +++++++++++++++++++ closure/templates/test/plugin.soy | 25 ++++++++ 6 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 closure/templates/closure_templates_plugin.bzl create mode 100644 closure/templates/test/ExampleModule.java create mode 100644 closure/templates/test/plugin.soy diff --git a/closure/defs.bzl b/closure/defs.bzl index f87354f9af..037e1fcd07 100644 --- a/closure/defs.bzl +++ b/closure/defs.bzl @@ -24,6 +24,7 @@ load("//closure/stylesheets:closure_css_binary.bzl", _closure_css_binary = "clos load("//closure/stylesheets:closure_css_library.bzl", _closure_css_library = "closure_css_library") load("//closure/templates:closure_java_template_library.bzl", _closure_java_template_library = "closure_java_template_library") load("//closure/templates:closure_js_template_library.bzl", _closure_js_template_library = "closure_js_template_library") +load("//closure/templates:closure_templates_plugin.bzl", _closure_templates_plugin = "closure_templates_plugin") load("//closure/testing:closure_js_test.bzl", _closure_js_test = "closure_js_test") load("//closure/testing:phantomjs_test.bzl", _phantomjs_test = "phantomjs_test") load("//closure:filegroup_external.bzl", _filegroup_external = "filegroup_external") @@ -44,6 +45,7 @@ closure_css_binary = _closure_css_binary closure_css_library = _closure_css_library closure_java_template_library = _closure_java_template_library closure_js_template_library = _closure_js_template_library +closure_templates_plugin = _closure_templates_plugin closure_js_test = _closure_js_test phantomjs_test = _phantomjs_test filegroup_external = _filegroup_external diff --git a/closure/templates/closure_js_template_library.bzl b/closure/templates/closure_js_template_library.bzl index 0645e31112..4c95c1c77e 100644 --- a/closure/templates/closure_js_template_library.bzl +++ b/closure/templates/closure_js_template_library.bzl @@ -18,6 +18,7 @@ load("//closure/compiler:closure_js_aspect.bzl", "closure_js_aspect") load("//closure/compiler:closure_js_library.bzl", "closure_js_library") load("//closure/private:defs.bzl", "SOY_FILE_TYPE", "unfurl") +load("//closure/templates:closure_templates_plugin.bzl", "SoyPluginInfo") _SOYTOJSSRCCOMPILER = "@com_google_template_soy//:SoyToJsSrcCompiler" @@ -30,8 +31,11 @@ def _impl(ctx): args += ["--shouldGenerateGoogMsgDefs"] if ctx.attr.bidi_global_dir: args += ["--bidiGlobalDir=%s" % ctx.attr.bidi_global_dir] - if ctx.attr.plugin_modules: - args += ["--pluginModules=%s" % ",".join(ctx.attr.plugin_modules)] + if ctx.attr.plugins: + args += ["--pluginModules=%s" % ",".join([ + m[SoyPluginInfo].generator.module + for m in ctx.attr.plugins + ])] for arg in ctx.attr.defs: if not arg.startswith("--") or (" " in arg and "=" not in arg): fail("Please use --flag=value syntax for defs") @@ -47,6 +51,16 @@ def _impl(ctx): for f in dep.closure_js_library.descriptors.to_list(): args += ["--protoFileDescriptors=%s" % f.path] inputs.append(f) + + plugin_transitive_deps = depset( + transitive = [m[SoyPluginInfo].generator.runtime.transitive_runtime_deps for m in ctx.attr.plugins], + ).to_list() + inputs.extend(plugin_transitive_deps) + plugin_classpath = [dep.path for dep in plugin_transitive_deps] + if len(plugin_classpath) > 0: + args.insert(0, "--main_advice_classpath=" + + ctx.configuration.host_path_separator.join(plugin_classpath)) + ctx.actions.run( inputs = inputs, outputs = ctx.outputs.outputs, @@ -69,7 +83,9 @@ _closure_js_template_library = rule( ), "outputs": attr.output_list(), "globals": attr.label(allow_single_file = True), - "plugin_modules": attr.label_list(), + "plugins": attr.label_list( + providers = [SoyPluginInfo], + ), "should_generate_soy_msg_defs": attr.bool(), "bidi_global_dir": attr.int(default = 1, values = [1, -1]), "soy_msgs_are_external": attr.bool(), @@ -85,7 +101,7 @@ def closure_js_template_library( suppress = [], testonly = None, globals = None, - plugin_modules = None, + plugins = None, should_generate_soy_msg_defs = None, bidi_global_dir = None, soy_msgs_are_external = None, @@ -101,7 +117,7 @@ def closure_js_template_library( testonly = testonly, visibility = ["//visibility:private"], globals = globals, - plugin_modules = plugin_modules, + plugins = plugins, should_generate_soy_msg_defs = should_generate_soy_msg_defs, bidi_global_dir = bidi_global_dir, soy_msgs_are_external = soy_msgs_are_external, diff --git a/closure/templates/closure_templates_plugin.bzl b/closure/templates/closure_templates_plugin.bzl new file mode 100644 index 0000000000..db2a2953cb --- /dev/null +++ b/closure/templates/closure_templates_plugin.bzl @@ -0,0 +1,42 @@ +# Copyright 2019 The Closure Rules 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. + +SoyPluginInfo = provider( + doc = "provides the module and java_library implementing this plugin", +) + +def _impl(ctx): + return [SoyPluginInfo( + generator = struct( + module = ctx.attr.module, + runtime = java_common.merge([dep[JavaInfo] for dep in ctx.attr.deps]), + ), + )] + +closure_templates_plugin = rule( + implementation = _impl, + doc = "a closure templates plugin providing user-defined functions", + attrs = { + "module": attr.string( + doc = "fully-qualified class name of extension module", + mandatory = True, + ), + "deps": attr.label_list( + doc = "java_library rules providing the specified class name", + providers = [JavaInfo], + mandatory = True, + ), + }, + provides = [SoyPluginInfo], +) diff --git a/closure/templates/test/BUILD b/closure/templates/test/BUILD index a775de87f1..9a0876e60b 100644 --- a/closure/templates/test/BUILD +++ b/closure/templates/test/BUILD @@ -16,7 +16,7 @@ package(default_testonly = True) licenses(["notice"]) # Apache 2.0 -load("//closure:defs.bzl", "closure_java_template_library", "closure_js_library", "closure_js_proto_library", "closure_js_template_library", "closure_js_test") +load("//closure:defs.bzl", "closure_java_template_library", "closure_js_library", "closure_js_proto_library", "closure_js_template_library", "closure_js_test", "closure_templates_plugin") load("//closure/private:file_test.bzl", "file_test") closure_js_proto_library( @@ -58,6 +58,36 @@ closure_js_template_library( ], ) +closure_js_template_library( + name = "plugin_soy", + srcs = ["plugin.soy"], + plugins = [":plugin_ExampleModule"], +) + +java_library( + name = "ExampleModule", + srcs = ["ExampleModule.java"], + deps = [ + "@com_google_guava//jar", + "@com_google_inject_extensions_guice_multibindings//jar", + "@com_google_inject_guice//jar", + "@com_google_template_soy//jar", + ], +) + +closure_templates_plugin( + name = "plugin_ExampleModule", + module = "test.ExampleModule", + deps = [":ExampleModule"], +) + +file_test( + name = "plugin_test_toLower", + size = "small", + file = ":plugin.soy.js", + regexp = ".toLowerCase()", +) + file_test( name = "localized_using_defs_test", size = "small", diff --git a/closure/templates/test/ExampleModule.java b/closure/templates/test/ExampleModule.java new file mode 100644 index 0000000000..c4a2d0c3fc --- /dev/null +++ b/closure/templates/test/ExampleModule.java @@ -0,0 +1,61 @@ +// Copyright 2019 The Closure Rules 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. +package test; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import com.google.template.soy.data.SoyValue; +import com.google.template.soy.data.restricted.StringData; +import com.google.template.soy.exprtree.Operator; +import com.google.template.soy.jssrc.restricted.JsExpr; +import com.google.template.soy.jssrc.restricted.SoyJsSrcFunction; +import com.google.template.soy.shared.restricted.SoyFunction; +import java.util.List; +import java.util.Set; + +/** + * An example module providing a custom soy function and js source implementation. + * See {@link https://github.com/google/closure-templates/blob/master/documentation/dev/plugins.md} + */ +public class ExampleModule extends AbstractModule { + /** {@inheritDoc} */ + @Override + protected void configure() { + bindFunctions(Multibinder.newSetBinder(binder(), SoyFunction.class)); + } + + private void bindFunctions(Multibinder fns) { + fns.addBinding().to(ToLowerFunction.class); + } + + public static class ToLowerFunction implements SoyJsSrcFunction { + @Override + public String getName() { + return "toLower"; + } + + @Override + public Set getValidArgsSizes() { + return ImmutableSet.of(1); + } + + @Override + public JsExpr computeForJsSrc(List args) { + JsExpr arg = args.get(0); + String exprText = "(" + arg.getText() + ").toLowerCase()"; + return new JsExpr(exprText, Operator.NOT_EQUAL.getPrecedence()); + } + } +} diff --git a/closure/templates/test/plugin.soy b/closure/templates/test/plugin.soy new file mode 100644 index 0000000000..1c06ce3adf --- /dev/null +++ b/closure/templates/test/plugin.soy @@ -0,0 +1,25 @@ +// Copyright 2019 The Closure Rules 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. + +{namespace io.bazel.rules.closure.soy.plugin} + + +/** + * Greets a person, loudly. + */ +{template .greetLoudly} + {@param name: string} +

+ {toLower('Hello')} {$name}! +{/template}