Build the feature jar with Bazel (#37)


diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..93df28b
--- /dev/null
+++ b/BUILD
@@ -0,0 +1 @@
+exports_files(["LICENSE.txt"])
diff --git a/WORKSPACE b/WORKSPACE
index fba3f3b..fd4aa93 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,9 +1,26 @@
 workspace(name = "build_bazel_e4b")
 
-load("//tools/build_defs:eclipse_plugin.bzl", "load_eclipse_deps")
+load("//tools/build_defs:eclipse.bzl", "load_eclipse_deps")
 
 load_eclipse_deps()
 
+new_http_archive(
+    name = "com_google_python_gflags",
+    url = "https://github.com/google/python-gflags/archive/python-gflags-2.0.zip",
+    strip_prefix = "python-gflags-python-gflags-2.0",
+    build_file_content = """
+py_library(
+    name = "gflags",
+    srcs = [
+        "gflags.py",
+        "gflags_validators.py",
+    ],
+    visibility = ["//visibility:public"],
+)
+""",
+    sha256 = "344990e63d49b9b7a829aec37d5981d558fea12879f673ee7d25d2a109eb30ce",
+)
+
 # TODO(dmarting): Use http_file and relies on a mirror instead of maven_jar
 maven_jar(
     name = "com_google_guava",
diff --git a/com.google.devtools.bazel.e4b.feature/BUILD b/com.google.devtools.bazel.e4b.feature/BUILD
new file mode 100644
index 0000000..70d2162
--- /dev/null
+++ b/com.google.devtools.bazel.e4b.feature/BUILD
@@ -0,0 +1,15 @@
+load("//tools/build_defs:eclipse.bzl", "eclipse_feature")
+
+eclipse_feature(
+    name = "com.google.devtools.bazel.e4b.feature",
+    copyright = "Copyright 2016 The Bazel Authors",
+    description = "Integrate Eclipse with the Bazel build system.",
+    label = "Eclipse 4 Bazel",
+    license = "//:LICENSE.txt",
+    license_url = "http://www.apache.org/licenses/LICENSE-2.0",
+    plugins = ["//com.google.devtools.bazel.e4b"],
+    provider = "The Bazel Authors",
+    sites = {"Bazel": "https://bazel.build"},
+    url = "https://github.com/bazelbuild/e4b",
+    version = "0.0.3.qualifier",
+)
diff --git a/com.google.devtools.bazel.e4b/BUILD b/com.google.devtools.bazel.e4b/BUILD
index 78ccfa5..c6413dd 100644
--- a/com.google.devtools.bazel.e4b/BUILD
+++ b/com.google.devtools.bazel.e4b/BUILD
@@ -1,13 +1,14 @@
-load("//tools/build_defs:eclipse_plugin.bzl", "eclipse_plugin")
+load("//tools/build_defs:eclipse.bzl", "eclipse_plugin")
 
 eclipse_plugin(
     name = "com.google.devtools.bazel.e4b",
     srcs = glob(["src/**/*.java"]),
+    activator = "com.google.devtools.bazel.e4b.Activator",
     bundle_name = "Eclipse 4 Bazel",
     resources = glob(["resources/**"]),
-    version = "0.0.3.qualifier",
     vendor = "The Bazel Authors",
-    activator = "com.google.devtools.bazel.e4b.Activator",
+    version = "0.0.3.qualifier",
+    visibility = ["//visibility:public"],
     deps = [
         "//com.google.devtools.bazel.e4b/src/com/google/devtools/bazel/e4b/command",
         "@com_google_guava//jar",
diff --git a/tools/build_defs/BUILD b/tools/build_defs/BUILD
index e69de29..07c8d68 100644
--- a/tools/build_defs/BUILD
+++ b/tools/build_defs/BUILD
@@ -0,0 +1,7 @@
+package(default_visibility = ["//visibility:public"])
+
+py_binary(
+    name = "feature_builder",
+    srcs = ["feature_builder.py"],
+    deps = ["@com_google_python_gflags//:gflags"],
+)
diff --git a/tools/build_defs/eclipse_plugin.bzl b/tools/build_defs/eclipse.bzl
similarity index 74%
rename from tools/build_defs/eclipse_plugin.bzl
rename to tools/build_defs/eclipse.bzl
index 0170f14..dee905f 100644
--- a/tools/build_defs/eclipse_plugin.bzl
+++ b/tools/build_defs/eclipse.bzl
@@ -97,7 +97,9 @@
               "deps",
               "classpath_resources",
               "deploy_manifest_lines",
+              "visibility",
               "main_class"]}
+  visibility = kwargs["visibility"] if "visibility" in kwargs else None
   # Generate the .api_description to put in the final jar
   native.genrule(
     name = name + ".api_description",
@@ -148,11 +150,64 @@
     outs = ["%s_%s.jar" % (name, version)],
     cmd = "cp $< $@",
     output_to_bindir = 1,
+    visibility = visibility,
   )
 
 
-# TODO(dmarting): implement eclipse_feature.
-# An eclipse feature is jar with only the feature.xml file in it.
+def _eclipse_feature_impl(ctx):
+  feature_xml = ctx.new_file(ctx.outputs.out, ctx.label.name + ".xml")
+  ctx.action(
+    outputs = [feature_xml],
+    inputs = [ctx.file.license],
+    executable = ctx.executable._builder,
+    arguments = [
+      "--output=" + feature_xml.path,
+      "--id=" + ctx.label.name,
+      "--label=" + ctx.attr.label,
+      "--version=" + ctx.attr.version,
+      "--provider=" + ctx.attr.provider,
+      "--url=" + ctx.attr.url,
+      "--description=" + ctx.attr.description,
+      "--copyright=" + ctx.attr.copyright,
+      "--license_url=" + ctx.attr.license_url,
+      "--license=" + ctx.file.license.path] + [
+        "--site=%s=%s" % (site, ctx.attr.sites[site])
+        for site in ctx.attr.sites] + [
+          "--plugin=" + p.basename for p in ctx.files.plugins])
+  ctx.action(
+      outputs = [ctx.outputs.out],
+      inputs = [feature_xml],
+      executable = ctx.executable._zipper,
+      arguments = ["c",
+                   ctx.outputs.out.path,
+                   "feature.xml=" + feature_xml.path],
+  )
+
+
+eclipse_feature = rule(
+   implementation=_eclipse_feature_impl,
+   attrs = {
+       "label": attr.string(mandatory=True),
+       "version": attr.string(mandatory=True),
+       "provider": attr.string(mandatory=True),
+       "description": attr.string(mandatory=True),
+       "url": attr.string(mandatory=True),
+       "copyright": attr.string(mandatory=True),
+       "license_url": attr.string(mandatory=True),
+       "license": attr.label(mandatory=True, allow_single_file=True),
+       "sites": attr.string_dict(),
+       # TODO(dmarting): restrict what can be passed to the plugins attribute.
+       "plugins": attr.label_list(),
+       "_zipper": attr.label(default=Label("@bazel_tools//tools/zip:zipper"),
+                             executable=True,
+                             cfg="host"),
+       "_builder": attr.label(default=Label("//tools/build_defs:feature_builder"),
+                              executable=True,
+                              cfg="host"),
+    },
+    outputs = {"out": "%{name}_%{version}.jar"})
+"""Create an eclipse feature jar."""
+
 
 # TODO(dmarting): implement eclipse_p2updatesite.
 # An p2 site is a site which has the following layout:
diff --git a/tools/build_defs/feature_builder.py b/tools/build_defs/feature_builder.py
new file mode 100644
index 0000000..0964ebc
--- /dev/null
+++ b/tools/build_defs/feature_builder.py
@@ -0,0 +1,111 @@
+# Copyright 2017 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.
+"""This tool build feature.xml files describing an Eclipse feature."""
+
+from xml.etree import ElementTree
+from xml.dom import minidom
+import gflags
+import sys
+
+gflags.DEFINE_string("output", None, "The output file, mandatory")
+gflags.MarkFlagAsRequired("output")
+
+gflags.DEFINE_string("id", None, "The feature ID, mandatory")
+gflags.MarkFlagAsRequired("id")
+
+gflags.DEFINE_string(
+  "label",
+  None,
+  "The feature label (i.e. short description), mandatory")
+gflags.MarkFlagAsRequired("label")
+
+gflags.DEFINE_string("version", None, "The feature version, mandatory")
+gflags.MarkFlagAsRequired("version")
+
+gflags.DEFINE_string(
+  "provider", None, "The provider (i.e. the vendor) of the feature, mandatory")
+gflags.MarkFlagAsRequired("provider")
+
+gflags.DEFINE_string(
+  "url", None, "A URL associated to the description, optional")
+
+gflags.DEFINE_string(
+  "description", None, "Description of the feature, mandatory")
+gflags.MarkFlagAsRequired("description")
+
+gflags.DEFINE_string(
+  "copyright", None, "Copyright line for the repository, mandatory")
+gflags.MarkFlagAsRequired("copyright")
+
+gflags.DEFINE_string(
+  "license_url", None, "URL pointing to the license, mandatory")
+gflags.MarkFlagAsRequired("license_url")
+
+gflags.DEFINE_string(
+  "license", None, "Text file of the license of the feature, mandatory")
+gflags.MarkFlagAsRequired("license")
+
+gflags.DEFINE_multistring(
+  "site", [], "Sites related to the plugin, in the form `label=url`")
+gflags.DEFINE_multistring(
+  "plugin", [], "List of plugins that this feature contains (filename).")
+
+FLAGS=gflags.FLAGS
+
+def _plugins(parent, plugins):
+  for plugin in plugins:
+    if plugin.endswith(".jar"):
+      id, version = plugin[:-4].split("_", 1)
+      p = ElementTree.SubElement(parent, "plugin")
+      p.set("id", id)
+      p.set("download-size", "0")
+      p.set("install-size", "0")
+      p.set("version", version)
+
+
+def _sites(parent, sites):
+  for site in sites:
+    label, url = site.split("=", 1)
+    p = ElementTree.SubElement(parent, "discovery")
+    p.set("label", label)
+    p.set("url", url)
+
+
+def main(unused_argv):
+  feature = ElementTree.Element("feature")
+  feature.set("id", FLAGS.id)
+  feature.set("label", FLAGS.label)
+  feature.set("version", FLAGS.version)
+  feature.set("provider-name", FLAGS.provider)
+  description = ElementTree.SubElement(feature, "description")
+  if FLAGS.url:
+    description.set("url", FLAGS.url)
+  description.text = FLAGS.description
+  copyright = ElementTree.SubElement(feature, "copyright")
+  copyright.text = FLAGS.copyright
+  license = ElementTree.SubElement(feature, "license")
+  license.set("url", FLAGS.license_url)
+  with open(FLAGS.license, "r") as f:
+    license.text = f.read()
+  _sites(ElementTree.SubElement(feature, "url"), FLAGS.site)
+  _plugins(feature, FLAGS.plugin)
+
+  # Pretty print the resulting tree
+  output = ElementTree.tostring(feature, 'utf-8')
+  reparsed = minidom.parseString(output)
+  with open(FLAGS.output, "w") as f:
+    f.write(reparsed.toprettyxml(indent="  "))
+
+if __name__ == "__main__":
+  main(FLAGS(sys.argv))