Updating buck to build OSGi JARs

Includes:
  OSGiWrapper to wrap Buck JARs
    - cfgdef file support
    - WAR file generation support
  Adding checkstyle support

Change-Id: Ia25c41f945980e4b94ad5a8bd161328fa5f79c27
diff --git a/.buckconfig b/.buckconfig
index 3270c10..2dc3441 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -5,3 +5,6 @@
   source_level = 8
   target_level = 8
 
+[download]
+  maven_repo = https://repo1.maven.org/maven2
+  in_build = true
diff --git a/buck-tools/default.defs b/buck-tools/default.defs
index 86b7149..5ed0fea 100644
--- a/buck-tools/default.defs
+++ b/buck-tools/default.defs
@@ -1,4 +1,5 @@
 include_defs('//bucklets/maven_jar.bucklet')
+include_defs('//bucklets/onos.bucklet')
 
 BASE_DEPS = [
     '//lib:junit',
diff --git a/bucklets/maven_jar.bucklet b/bucklets/maven_jar.bucklet
index 16a3f91..845ac9b 100644
--- a/bucklets/maven_jar.bucklet
+++ b/bucklets/maven_jar.bucklet
@@ -119,6 +119,7 @@
     prebuilt_jar(
       name = '%s_src' % name,
       binary_jar = ':%s__download_src' % name,
+      maven_coords = id,
       deps = license,
       visibility = visibility,
     )
@@ -136,6 +137,7 @@
       deps = deps + license,
       binary_jar = ':%s__download_bin' % name,
       source_jar = ':%s__download_src' % name if srcjar else None,
+      maven_coords = id,
     )
     java_library(
       name = name,
@@ -149,6 +151,7 @@
       binary_jar = ':%s__download_bin' % name,
       source_jar = ':%s__download_src' % name if srcjar else None,
       visibility = visibility,
+      maven_coords = id,
     )
 
 
diff --git a/bucklets/onos.bucklet b/bucklets/onos.bucklet
new file mode 100644
index 0000000..8bb6811
--- /dev/null
+++ b/bucklets/onos.bucklet
@@ -0,0 +1,107 @@
+
+DEBUG_ARG='JAVA_TOOL_OPTIONS="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=5005,suspend=y"'
+
+def osgi_jar(
+    name,
+    srcs,
+    group_id = 'org.onosproject',
+    version = '1.6.0-SNAPSHOT',
+    deps = [],
+    visibility = ['PUBLIC'],
+    license = 'NONE',
+    description = '',
+    debug = False,
+    web_context = 'NONE',
+    **kwargs
+    ):
+
+  bare_jar_name = name + '-jar'
+  osgi_jar_name = name + '-osgi'
+  mvn_coords = group_id + ':' + name + ':' + version
+
+  java_library(
+      name = bare_jar_name,
+      srcs = srcs,
+      deps = deps,
+      visibility = [], #intentially, not visible
+      **kwargs
+  )
+
+  cp = ':'.join(['$(classpath %s)' % c for c in deps])
+
+  args = ( '$(location :%s)' % bare_jar_name, #input jar
+           '$OUT',                            #output jar
+           cp,                                #classpath
+           name,                              #bundle name
+           group_id,                          #group id
+           version,                           #version
+           license,                           #license url
+           web_context,                       #web context (REST API only)
+           description,                       #description
+          )
+
+  #TODO stage_jar is a horrendous hack
+  stage_jar = 'pushd $SRCDIR; mkdir bin; cd bin; jar xf $(location :%s); ls; popd; ' % bare_jar_name
+  wrap_jar = '$(exe //utils/osgiwrap:osgi-jar) ' + ' '.join(args)
+  bash = stage_jar + wrap_jar
+  if debug:
+    bash = stage_jar + DEBUG_ARG + ' ' + wrap_jar
+    print bash
+  # TODO this is a hack to add checkstyle as dependency before generating jar
+  bash = 'ls $(location :' + name + '-checkstyle) > /dev/null; ' + bash
+
+  genrule(
+    name = osgi_jar_name,
+    bash = bash,
+    out = name + '.jar',
+    visibility = [], #intentially, not visible
+  )
+
+  # TODO we really should shade the jar with maven flavor
+  prebuilt_jar(
+    name = name,
+    maven_coords = mvn_coords,
+    binary_jar = ':' + osgi_jar_name,
+    visibility = visibility,
+  )
+
+
+
+  ### Checkstyle
+  chk_cmd = ' '.join(( 'java -jar $(location //lib:checkstyle)',
+                       '-o $OUT',
+                       '-c $(location //tools/build/conf:checkstyle-xml)',
+                       ' '.join(srcs) ))
+  error_cmd = '(touch $OUT; cat $OUT | grep "^\[ERROR\]"; exit 1)'
+  cmd = ' || '.join((chk_cmd, error_cmd))
+  genrule(
+    name = name + '-checkstyle',
+    bash = cmd,
+    srcs = srcs,
+    out = 'checkstyle.log',
+  )
+
+  ### .m2 Install
+  mvn_cmd = ' '.join(('mvn install:install-file',
+                      '-Dfile=$(location :%s)' % name,
+                      '-DgroupId=%s' % group_id,
+                      '-DartifactId=%s' % name,
+                      '-Dversion=%s' % version,
+                      '-Dpackaging=jar'
+                      ))
+  genrule(
+    name = name + '-install',
+    bash = mvn_cmd + ' > $OUT',
+    out = 'install.log',
+    visibility = visibility,
+  )
+
+def onos_app(
+    name,
+    **kwargs):
+
+    osgi_jar(
+      name = name,
+      **kwargs
+    )
+
diff --git a/lib/BUCK b/lib/BUCK
index 76174c4..0826537 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -781,3 +781,62 @@
   attach_source = False,
   license = 'Apache2.0',
 )
+
+# ------ needed for OSGi Wrapper ------------------
+#TODO should these live in osgiwrap or in lib
+#FIXME replace with release version
+maven_jar(
+  name = 'org.apache.felix.scr.bnd',
+  id = 'org.onosproject:org.apache.felix.scr.bnd:1.4.1-SNAPSHOT',
+  repository = 'https://oss.sonatype.org/content/repositories/snapshots',
+  full_url = 'https://oss.sonatype.org/content/repositories/snapshots/org/onosproject/org.apache.felix.scr.bnd/1.4.1-SNAPSHOT/org.apache.felix.scr.bnd-1.4.1-20160328.235003-2',
+  attach_source = False,
+  license = 'Apache2.0',
+)
+#TODO update this to org.apache.felix when changes are merged upstream
+# prebuilt_jar(
+#   name = 'felix-bnd',
+#   binary_jar =  ':org.apache.felix.scr.bnd-jar',
+# )
+# remote_file(
+#   name = 'org.apache.felix.scr.bnd-jar',
+#   out = 'org.apache.felix.scr.bnd-jar-1.4.1-SNAPSHOT.jar',
+#   url = 'mvn:https://oss.sonatype.org/content/repositories/snapshots:org.onosproject:org.apache.felix.scr.bnd:jar:1.4.1-SNAPSHOT',
+#   sha1 = '89b5161d60dfe4138046f13c789f17a6b89e823d',
+# )
+
+prebuilt_jar(
+  name = 'bndlib',
+  binary_jar = ':biz.aQute.bnd-biz.aQute.bndlib-jar',
+  visibility = [ 'PUBLIC' ] #:onlab-osgiwrap and :osgi-jar
+#  source_jar
+#  maven_coords
+)
+
+remote_file(
+  name = 'biz.aQute.bnd-biz.aQute.bndlib-jar',
+  out = 'biz.aQute.bnd-biz.aQute.bndlib-jar-3.1.0.jar',
+  url = 'mvn:biz.aQute.bnd:biz.aQute.bndlib:jar:3.1.0',
+  sha1 = '8e45564ca80bf089276a35f916e8702e7b798cbb',
+)
+
+prebuilt_jar(
+  name = 'checkstyle',
+  binary_jar = ':checkstyle-jar',
+  visibility = [ 'PUBLIC' ]
+)
+
+# TODO upgrade to newer version of checkstyle
+# remote_file(
+#   name = 'checkstyle-jar',
+#   out = 'checkstyle-6.17-all.jar',
+#   url = 'http://onlab.vicci.org/onos/third-party/checkstyle-6.17-all.jar',
+#   sha1 = '11a02d7b0374f8a82fbd76361a69756faa6aefa0'
+# )
+
+remote_file(
+  name = 'checkstyle-jar',
+  out = 'checkstyle-6.11.2-all.jar',
+  url = 'http://onlab.vicci.org/onos/third-party/checkstyle-6.11.2-all.jar',
+  sha1 = 'f504187b1743e73ffe72c2eede0ff57d45536b7d'
+)
diff --git a/tools/build/conf/BUCK b/tools/build/conf/BUCK
new file mode 100644
index 0000000..71f32e6
--- /dev/null
+++ b/tools/build/conf/BUCK
@@ -0,0 +1,20 @@
+checkstyle_source = 'src/main/resources/onos/checkstyle.xml'
+suppression_source = 'src/main/resources/onos/suppressions.xml'
+
+xml = ('<module name="SuppressionFilter">'
+       '<property name="file" value="$(location :suppressions.xml)"/>'
+       '</module>' )
+cmd = "sed 's#<module name=\"Checker\">#<module name=\"Checker\">%s#' %s > $OUT" % ( xml, checkstyle_source )
+
+genrule(
+  name = 'checkstyle-xml',
+  srcs = [ checkstyle_source ],
+  out = 'checkstyle.xml',
+  bash = cmd,
+  visibility = [ 'PUBLIC' ]
+)
+
+export_file(
+  name = 'suppressions.xml',
+  src = suppression_source,
+)
diff --git a/utils/osgiwrap/BUCK b/utils/osgiwrap/BUCK
new file mode 100644
index 0000000..078777d
--- /dev/null
+++ b/utils/osgiwrap/BUCK
@@ -0,0 +1,26 @@
+SRC = 'src/main/java/org/onlab/**/'
+TEST = 'src/test/java/org/onlab/**/'
+CURRENT_NAME = 'onlab-osgiwrap'
+CURRENT_TARGET = ':' + CURRENT_NAME
+
+COMPILE_DEPS = [
+    '//lib:guava',
+    '//lib:bndlib',
+    '//lib:org.apache.felix.scr.bnd'
+]
+
+
+java_library(
+    name = CURRENT_NAME,
+    #maven_coords = 'org.onosproject:' + CURRENT_NAME + ':' + '1.2.3',
+    srcs = glob([SRC + '/*.java']),
+    deps = COMPILE_DEPS,
+    visibility = ['PUBLIC'],
+)
+
+java_binary(
+    name = 'osgi-jar',
+    deps = COMPILE_DEPS + [ ':' + CURRENT_NAME ],
+    main_class = 'org.onlab.OSGiWrapper',
+    visibility = [ 'PUBLIC' ]
+)
diff --git a/utils/osgiwrap/pom.xml b/utils/osgiwrap/pom.xml
new file mode 100644
index 0000000..5aa1e12
--- /dev/null
+++ b/utils/osgiwrap/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>onlab-utils</artifactId>
+        <groupId>org.onosproject</groupId>
+        <version>1.6.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>osgiwrap</artifactId>
+    <version>0.9-SNAPSHOT</version>
+
+    <dependencies>
+        <!-- TODO update this to org.apache.felix when changes are merged upstream -->
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>org.apache.felix.scr.bnd</artifactId>
+            <version>1.4.1-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>biz.aQute.bnd</groupId>
+            <artifactId>biz.aQute.bndlib</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>19.0</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java b/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java
new file mode 100644
index 0000000..3425bde
--- /dev/null
+++ b/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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 org.onlab.osgiwrap;
+
+import aQute.bnd.osgi.Analyzer;
+import aQute.bnd.osgi.Jar;
+import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.jar.Manifest;
+
+/**
+ * BND-based wrapper to convert Buck JARs to OSGi-compatible JARs.
+ */
+public class OSGiWrapper {
+
+    private String inputJar;
+    private String outputJar;
+    private List<String> classpath;
+
+    private String bundleName;
+    private String groupId;
+    private String bundleSymbolicName;
+    private String bundleVersion;
+
+    private String bundleDescription;
+    private String bundleLicense;
+
+    private String webContext;
+
+    public static void main(String[] args) {
+
+        if (args.length < 7) {
+            System.err.println("Not enough args");
+            System.exit(1);
+        }
+
+        String jar = args[0];
+        String output = args[1];
+        String cp = args[2];
+        String name = args[3];
+        String group = args[4];
+        String version = args[5];
+        String license = args[6];
+        String webContext = args[7];
+        String desc = Joiner.on(' ').join(Arrays.copyOfRange(args, 8, args.length));
+
+        OSGiWrapper wrapper = new OSGiWrapper(jar, output, cp,
+                                              name, group,
+                                              version, license,
+                                              webContext, desc);
+        wrapper.log(wrapper + "\n");
+        if (!wrapper.execute()) {
+            System.err.println("ERROR");
+            System.exit(2);
+        }
+    }
+
+
+    public OSGiWrapper(String inputJar,
+                       String outputJar,
+                       String classpath,
+                       String bundleName,
+                       String groupId,
+                       String bundleVersion,
+                       String bundleLicense,
+                       String webContext,
+                       String bundleDescription) {
+        this.inputJar = inputJar;
+        this.classpath = Lists.newArrayList(classpath.split(":"));
+        if (!this.classpath.contains(inputJar)) {
+            this.classpath.add(0, inputJar);
+        }
+        this.outputJar = outputJar;
+
+        this.bundleName = bundleName;
+        this.groupId = groupId;
+        this.bundleSymbolicName = String.format("%s.%s", groupId, bundleName);
+
+        this.bundleVersion = bundleVersion;
+        this.bundleLicense = bundleLicense;
+        this.bundleDescription = bundleDescription;
+
+        this.webContext = webContext;
+    }
+
+    private void setProperties(Analyzer analyzer) {
+        analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName);
+        analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
+        analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion.replace('-', '.'));
+
+        analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, bundleDescription);
+        analyzer.setProperty(Analyzer.BUNDLE_LICENSE, bundleLicense);
+
+        //TODO consider using stricter version policy
+        //analyzer.setProperty("-provider-policy", "${range;[===,==+)}");
+        //analyzer.setProperty("-consumer-policy", "${range;[===,==+)}");
+
+        // There are no good defaults so make sure you set the Import-Package
+        analyzer.setProperty(Analyzer.IMPORT_PACKAGE, "*,org.glassfish.jersey.servlet");
+
+        // TODO include version in export, but not in import
+        analyzer.setProperty(Analyzer.EXPORT_PACKAGE, "*");
+
+        // TODO we may need INCLUDE_RESOURCE, or that might be done by Buck
+        //analyzer.setProperty(analyzer.INCLUDE_RESOURCE, ...)
+        if (isWab()) {
+            analyzer.setProperty(Analyzer.WAB, "src/main/webapp/");
+            analyzer.setProperty("Web-ContextPath", webContext);
+        }
+    }
+
+    public boolean execute() {
+        Analyzer analyzer = new Analyzer();
+        try {
+
+            Jar jar = new Jar(new File(inputJar));  // where our data is
+            analyzer.setJar(jar);                   // give bnd the contents
+
+            // You can provide additional class path entries to allow
+            // bnd to pickup export version from the packageinfo file,
+            // Version annotation, or their manifests.
+            analyzer.addClasspath(classpath);
+
+            setProperties(analyzer);
+
+//            analyzer.setProperty("DESTDIR");
+//            analyzer.setBase();
+
+            // ------------- let's begin... -------------------------
+
+            // Analyze the target JAR first
+            analyzer.analyze();
+
+            // Scan the JAR for Felix SCR annotations and generate XML files
+            Map<String, String> properties = Maps.newHashMap();
+            SCRDescriptorBndPlugin scrDescriptorBndPlugin = new SCRDescriptorBndPlugin();
+            scrDescriptorBndPlugin.setProperties(properties);
+            scrDescriptorBndPlugin.setReporter(analyzer);
+            scrDescriptorBndPlugin.analyzeJar(analyzer);
+
+            // Repack the JAR as a WAR
+            doWabStaging(analyzer);
+
+            // Calculate the manifest
+            Manifest manifest = analyzer.calcManifest();
+            //OutputStream s = new FileOutputStream("/tmp/foo2.txt");
+            //manifest.write(s);
+            //s.close();
+
+            if (analyzer.isOk()) {
+                analyzer.getJar().setManifest(manifest);
+                analyzer.save(new File(outputJar), true);
+                log("Saved!\n");
+            } else {
+                warn("%s\n", analyzer.getErrors());
+                return false;
+            }
+
+            analyzer.close();
+
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private boolean isWab() {
+        return !Objects.equals(webContext, "NONE");
+    }
+
+    private void doWabStaging(Analyzer analyzer) throws Exception {
+        if (!isWab()) {
+            return;
+        }
+        String wab = analyzer.getProperty(analyzer.WAB);
+        Jar dot = analyzer.getJar();
+
+        log("wab %s", wab);
+        analyzer.setBundleClasspath("WEB-INF/classes," +
+                                    analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
+
+        Set<String> paths = new HashSet<String>(dot.getResources().keySet());
+
+        for (String path : paths) {
+            if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+                log("wab: moving: %s", path);
+                dot.rename(path, "WEB-INF/classes/" + path);
+            }
+        }
+    }
+
+    private void log(String format, Object... objects) {
+        //System.err.printf(format, objects);
+    }
+
+    private void warn(String format, Object... objects) {
+        System.err.printf(format, objects);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("inputJar", inputJar)
+                .add("outputJar", outputJar)
+                .add("classpath", classpath)
+                .add("bundleName", bundleName)
+                .add("groupId", groupId)
+                .add("bundleSymbolicName", bundleSymbolicName)
+                .add("bundleVersion", bundleVersion)
+                .add("bundleDescription", bundleDescription)
+                .add("bundleLicense", bundleLicense)
+                .toString();
+
+    }
+}
diff --git a/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/package-info.java b/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/package-info.java
new file mode 100644
index 0000000..6071357
--- /dev/null
+++ b/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * OSGi-wrapper for raw JARs.
+ */
+package org.onlab.osgiwrap;
diff --git a/utils/pom.xml b/utils/pom.xml
index a571965..6595ffd 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -41,6 +41,7 @@
         <module>stc</module>
         <module>jdvue</module>
         <module>jnc</module>
+        <module>osgiwrap</module>
     </modules>
 
     <dependencies>