Bazel build of ONOS GUI package

Change-Id: Id5f9931c38f82afcdcefc49171dd79f2f5bdad14
diff --git a/WORKSPACE b/WORKSPACE
index 1a47662..c04c831 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,6 +1,19 @@
 load("//tools/build/bazel:generate_workspace.bzl", "generated_maven_jars")
 load("//tools/build/bazel:p4lang_workspace.bzl", "generate_p4lang")
+
 generated_maven_jars()
 generate_p4lang()
 
+git_repository(
+        name = "build_bazel_rules_nodejs",
+        remote = "https://github.com/bazelbuild/rules_nodejs.git",
+        tag = "0.10.0", # check for the latest tag when you install
+)
+
+load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
+node_repositories(package_json = ["//tools/gui:package.json"])
+
+load("//tools/build/bazel:nodejs_workspace.bzl", "packages_example_setup_workspace")
+packages_example_setup_workspace()
+
 ONOS_VERSION = '1.14.0-SNAPSHOT'
diff --git a/modules.bzl b/modules.bzl
index 483290d..cc53368 100644
--- a/modules.bzl
+++ b/modules.bzl
@@ -76,7 +76,7 @@
     "//providers/p4runtime/packet:onos-providers-p4runtime-packet",
     "//web/api:onos-rest",
     #"//web/gui2:onos-gui2",
-    #"//web/gui:onos-gui",
+    "//web/gui:onos-gui",
     #"//incubator/protobuf/models:onos-incubator-protobuf-models",
     #"//incubator/protobuf/services/nb:onos-incubator-protobuf-services-nb",
 ]
@@ -304,7 +304,7 @@
     "//tools/package/features:onos-incubator",
     "//tools/package/features:onos-cli",
     "//tools/package/features:onos-rest",
-    #    "//tools/package/features:onos-gui",
+    "//tools/package/features:onos-gui",
     #    "//tools/package/features:onos-gui2",
     #    "//tools/package/features:onos-security",
 ]
diff --git a/tools/build/bazel/nodejs_workspace.bzl b/tools/build/bazel/nodejs_workspace.bzl
new file mode 100644
index 0000000..614f238
--- /dev/null
+++ b/tools/build/bazel/nodejs_workspace.bzl
@@ -0,0 +1,11 @@
+load("@build_bazel_rules_nodejs//:defs.bzl", "yarn_install", "npm_install")
+
+def packages_example_setup_workspace():
+
+  npm_install(
+      name = "packages_install",
+      package_json = "//tools/gui:package.json",
+      package_lock_json = "@packages_example//:package-lock.json",
+      data = ["@packages_example//:postinstall.js"],
+  )
+
diff --git a/tools/build/bazel/osgi_features.bzl b/tools/build/bazel/osgi_features.bzl
index 3ae3ce6..d2c8a1e 100644
--- a/tools/build/bazel/osgi_features.bzl
+++ b/tools/build/bazel/osgi_features.bzl
@@ -39,9 +39,9 @@
     for dep in ctx.attr.included_bundles:
         coord = maven_coordinates(dep.label)
         xmlArgs += ["-b", coord]
-        if java_common.provider in dep:
-            inputs += [dep.files.to_list()[0]]
-            bundleArgs += [dep.files.to_list()[0].path, coord]
+
+        inputs += [dep.files.to_list()[0]]
+        bundleArgs += [dep.files.to_list()[0].path, coord]
 
     for f in ctx.attr.excluded_bundles:
         xmlArgs += ["-e", maven_coordinates(dep.label)]
diff --git a/tools/build/bazel/osgi_java_library.bzl b/tools/build/bazel/osgi_java_library.bzl
index 6283ec8..b75b367 100644
--- a/tools/build/bazel/osgi_java_library.bzl
+++ b/tools/build/bazel/osgi_java_library.bzl
@@ -35,11 +35,18 @@
         return native.glob([resources_root + "**"])
 
 def _webapp():
-    return native.glob(["src/main/webapp/**"])
+    return native.glob(["src/main/webapp/WEB-INF/web.xml"])
+
+def _include_resources_to_string(include_resources):
+    result = ""
+    for (path, filename) in include_resources.items():
+        result += (path + "=" + filename)
+    return result
 
 """
     Implementation of the rule to call bnd to make an OSGI jar file
 """
+
 def _bnd_impl(ctx):
     if (len(ctx.files.source) == 1):
         input_file = ctx.files.source[0]
@@ -61,7 +68,7 @@
     license = ""
     import_packages = ctx.attr.import_packages
     exportPackages = "*"
-    includeResources = ""
+    include_resources = ctx.attr.include_resources
     web_context = ctx.attr.web_context
     if web_context == None or web_context == "":
         web_context = "NONE"
@@ -109,7 +116,7 @@
         license,
         import_packages,
         exportPackages,
-        includeResources,
+        include_resources,
         web_context,
         web_xml_root_path,
         dynamicimportPackages,
@@ -144,6 +151,7 @@
         "import_packages": attr.string(),
         "web_context": attr.string(),
         "web_xml": attr.label_list(allow_files = True),
+        "include_resources": attr.string(),
         "_bnd_exe": attr.label(
             executable = True,
             cfg = "host",
@@ -161,6 +169,7 @@
 """
     Implementation of the rule to call swagger generator to create the registrator java class source
 """
+
 def _swagger_java_impl(ctx):
     api_title = ctx.attr.api_title
     api_version = ctx.attr.api_version
@@ -169,7 +178,7 @@
     web_context = ctx.attr.web_context
 
     output_java = ctx.outputs.swagger_java.path
-    output_dir = output_java [:output_java.find("generated-sources")]
+    output_dir = output_java[:output_java.find("generated-sources")]
 
     package_name = ctx.attr.package_name
 
@@ -210,6 +219,7 @@
 """
 Implementation of the rule to call swagger generator for swagger.json file
 """
+
 def _swagger_json_impl(ctx):
     api_title = ctx.attr.api_title
     api_version = ctx.attr.api_version
@@ -316,6 +326,7 @@
         import_packages: OSGI import list. Optional, comma separated list, defaults to "*"
         visibility: Visibility of the produced jar file to other BUILDs. Optional, defaults to private
 """
+
 def wrapped_osgi_jar(
         name,
         jar,
@@ -345,6 +356,7 @@
         exclude_tests: Tests that should not be run. Useful for excluding things like test files without any @Test methods.
                        Optional ist of targets, defaults to []
 """
+
 def osgi_jar_with_tests(
         name = None,
         deps = None,
@@ -353,6 +365,7 @@
         srcs = None,
         resources_root = None,
         resources = None,
+        include_resources = {},
         test_srcs = None,
         exclude_tests = None,
         test_resources = None,
@@ -389,7 +402,7 @@
 
     native_srcs = srcs
     native_resources = resources
-    if web_context != None and api_title != None and len(resources) != 0:
+    if web_context != None and api_title != "" and len(resources) != 0:
         # generate Swagger files if needed
         _swagger_java(
             name = name + "_swagger_java",
@@ -401,8 +414,8 @@
             web_context = web_context,
             api_package = api_package,
             swagger_java = ("src/main/resources/apidoc/generated-sources/" +
-                           api_package.replace(".", "/") +
-                           "/ApiDocRegistrator.java").replace("//", "/"),
+                            api_package.replace(".", "/") +
+                            "/ApiDocRegistrator.java").replace("//", "/"),
         )
         _swagger_json(
             name = name + "_swagger_json",
@@ -417,10 +430,10 @@
         )
         native_resources = []
         for r in resources:
-             if not "definitions" in r:
+            if not "definitions" in r:
                 native_resources.append(r)
-        native_srcs = srcs + [ name + "_swagger_java" ]
-        native_resources.append(name + "_swagger_json");
+        native_srcs = srcs + [name + "_swagger_java"]
+        native_resources.append(name + "_swagger_json")
 
     # compile the Java code
     native.java_library(name = name + "-native", srcs = native_srcs, resources = native_resources, deps = deps, visibility = visibility)
@@ -435,6 +448,7 @@
         import_packages = import_packages,
         web_context = web_context,
         web_xml = web_xml,
+        include_resources = _include_resources_to_string(include_resources),
     )
     if test_srcs != []:
         native.java_library(
@@ -477,6 +491,7 @@
         api_description: Swagger API description. Optional string, only used if the jar file provides a REST API and has swagger annotations
         api_package: Swagger API package name. Optional string, only used if the jar file provides a REST API and has swagger annotations
 """
+
 def osgi_jar(
         name = None,
         deps = None,
@@ -485,6 +500,7 @@
         srcs = None,
         resources_root = None,
         resources = None,
+        include_resources = {},
         visibility = ["//visibility:public"],
         version = ONOS_VERSION,
         web_context = None,
diff --git a/tools/build_rules/prelude_bazel b/tools/build_rules/prelude_bazel
index 842e116..3c71020 100644
--- a/tools/build_rules/prelude_bazel
+++ b/tools/build_rules/prelude_bazel
@@ -18,3 +18,6 @@
 load("//tools/build/bazel:onos_app.bzl", "onos_app")
 
 generated_java_libraries()
+
+
+
diff --git a/tools/gui/BUILD b/tools/gui/BUILD
new file mode 100644
index 0000000..49f18fc
--- /dev/null
+++ b/tools/gui/BUILD
@@ -0,0 +1,6 @@
+exports_files(
+    [
+        "package.json",
+        "gulpfile.babel.js",
+    ],
+)
diff --git a/tools/package/features/BUILD b/tools/package/features/BUILD
index a474981..f445a4e 100644
--- a/tools/package/features/BUILD
+++ b/tools/package/features/BUILD
@@ -151,16 +151,17 @@
     visibility = ["//visibility:public"],
 )
 
-#osgi_feature (
-#  name = "onos-gui",
-#  description = "ONOS GUI console components",
-#  required_features = ["onos-api", "onos-thirdparty-web"],
-#  included_bundles = [
-#    "//lib:jetty_websocket",
-#    "//utils/rest:onlab-rest",
-#    "//web/gui:onos-gui",
-#  ]
-#)
+osgi_feature (
+    name = "onos-gui",
+    description = "ONOS GUI console components",
+    required_features = ["onos-api", "onos-thirdparty-web"],
+    included_bundles = [
+      "//web/gui:onos-gui",
+      "@jetty_websocket//jar",
+      "//utils/rest:onlab-rest",
+    ],
+    visibility = ["//visibility:public"],
+)
 
 #osgi_feature (
 #  name = "onos-gui2",
diff --git a/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java b/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java
index c819ebd..b5669fe 100644
--- a/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java
+++ b/utils/osgiwrap/src/main/java/org/onlab/osgiwrap/OSGiWrapper.java
@@ -273,8 +273,13 @@
         Jar dot = analyzer.getJar();
 
         log("wab %s", wab);
-        analyzer.setBundleClasspath("WEB-INF/classes," +
-                                    analyzer.getProperty(analyzer.BUNDLE_CLASSPATH));
+
+        String specifiedClasspath = analyzer.getProperty(analyzer.BUNDLE_CLASSPATH);
+        String bundleClasspath = "WEB-INF/classes";
+        if (specifiedClasspath != null) {
+            bundleClasspath += "," + specifiedClasspath;
+        }
+        analyzer.setBundleClasspath(bundleClasspath);
 
         Set<String> paths = new HashSet<>(dot.getResources().keySet());
 
diff --git a/web/gui/BUCK b/web/gui/BUCK
index 3796e5b..df70a20 100644
--- a/web/gui/BUCK
+++ b/web/gui/BUCK
@@ -51,7 +51,7 @@
     name = 'onos-tools-gui',
     srcs = glob(['src/main/webapp/*.js', 'src/main/webapp/app/**/*.js'], excludes = ['src/main/webapp/dist/*.js']),
     bash = '$(location :onos-tools-gui-exe) '
-        + '$(location :node-release-' + NODE_VERSION + ') '
+        + ' $(location :node-release-' + NODE_VERSION + ') '
         + '$(location :node-bin-' + NODE_VERSION + ') > $OUT',
     out = 'onos-tools-gui.log',
     visibility = [ 'PUBLIC' ],
diff --git a/web/gui/BUILD b/web/gui/BUILD
new file mode 100644
index 0000000..f4c2c93
--- /dev/null
+++ b/web/gui/BUILD
@@ -0,0 +1,150 @@
+NODE_VERSION = "8.0.1"
+
+COMPILE_DEPS = CORE_DEPS + JACKSON + KRYO + [
+    "@javax_ws_rs_api//jar",
+    "@servlet_api//jar",
+    "@jetty_websocket//jar",
+    "@jetty_util//jar",
+    "@jersey_media_multipart//jar",
+    "@org_apache_karaf_shell_console//jar",
+    "@jersey_server//jar",
+    "//cli:onos-cli",
+    "//incubator/api:onos-incubator-api",
+    "//incubator/net:onos-incubator-net",
+    "//utils/rest:onlab-rest",
+    "//core/store/serializers:onos-core-serializers",
+]
+
+TEST_DEPS = TEST + [
+    "//core/api:onos-api-tests",
+    "//drivers/default:onos-drivers-default",
+]
+
+genrule(
+    name = "_onos-gui-npm-install",
+    srcs = [
+        "//tools/gui:package.json",
+        "@nodejs//:bin/npm",
+    ] + glob(
+        [
+            "src/main/webapp/*.js",
+            "src/main/webapp/app/**/*.js",
+        ],
+        exclude = ["src/main/webapp/dist/*.js"],
+    ),
+    outs = ["onos-gui-npm-install.tar"],
+    cmd = "(ROOT=`pwd` &&" +
+          " NPM=$$ROOT/$(location @nodejs//:bin/npm) &&" +
+          " cd tools/gui &&" +
+          " $$NPM install --loglevel=error --no-cache --cache=$(@D)/.npm --no-update-notifier &&" +
+          " tar cf $$ROOT/$@ package.json node_modules)",
+    local = True,
+    visibility = ["//visibility:public"],
+)
+
+genrule(
+    name = "_onos-gui-npm-build",
+    srcs = [
+        "//tools/gui:gulpfile.babel.js",
+        ":_onos-gui-npm-install",
+        "@nodejs//:bin/npm",
+        "//tools/gui:package.json",
+    ] + glob(["src/main/webapp/*"]),
+    outs = ["onos-gui-npm-build.tar"],
+    cmd = "(ROOT=`pwd` &&" +
+          " NPM=$$ROOT/$(location @nodejs//:bin/npm) &&" +
+          " cd tools/gui &&" +
+          " tar xf ../../$(location :_onos-gui-npm-install) &&" +
+          " $$NPM run build --no-update-notifier &&" +
+          "  cd ../../web/gui/src/main/webapp &&" +
+          " jar cf $$ROOT/$@ dist vendor data tests README.md _doc _dev app/fw app/*.css app/*.js app/*.txt)",
+    local = True,
+    visibility = ["//visibility:public"],
+)
+
+genrule(
+    name = "_onos-gui-npm",
+    srcs = [
+        ":_onos-gui-npm-build",
+    ],
+    outs = ["onos-gui-npm.log"],
+    cmd = "ROOT=`pwd` && cd web/gui/src/main/webapp && tar xf $$ROOT/$(location :_onos-gui-npm-build) >$$ROOT/$@",
+    local = True,
+    visibility = ["//visibility:public"],
+)
+
+osgi_jar_with_tests(
+    name = "_onos-gui-base-jar",
+    exclude_tests = [
+        "org.onosproject.ui.impl.AbstractUiImplTest",
+        "org.onosproject.ui.impl.topo.model.AbstractTopoModelTest",
+    ],
+    test_deps = TEST_DEPS,
+    web_context = "/onos/ui",
+    deps = COMPILE_DEPS,
+)
+
+filegroup(
+    name = "_root_level_files",
+    srcs =
+        [
+            ":src/main/webapp/bower.json",
+            ":src/main/webapp/bs-config.js",
+            ":src/main/webapp/dev_server.js",
+            ":src/main/webapp/package.json",
+        ],
+)
+
+filegroup(
+    name = "_web_inf_classes_files",
+    srcs =
+        [
+            ":src/main/webapp/error.html",
+            ":src/main/webapp/index.html",
+            ":src/main/webapp/login.html",
+            ":src/main/webapp/nav.html",
+            ":src/main/webapp/not-ready.html",
+            ":src/main/webapp/onos.js",
+        ],
+)
+
+filegroup(
+    name = "_raw_classes_files",
+    srcs = glob(["src/main/webapp/raw/**"]),
+)
+
+# app/view is packaged as a tar file because it has subdirectories that need to be preserved
+genrule(
+    name = "_app_view_tar",
+    srcs = glob(["src/main/webapp/app/view/**"]),
+    outs = ["app_view_tar.tar"],
+    cmd = " ROOT=`pwd` &&" +
+          " cd web/gui/src/main/webapp/app/view &&" +
+          " tar cf $$ROOT/$@ .",
+)
+
+genrule(
+    name = "onos-gui",
+    srcs = [
+        ":_onos-gui-npm-build",
+        ":_onos-gui-base-jar",
+        ":_root_level_files",
+        ":_web_inf_classes_files",
+        ":_raw_classes_files",
+        ":_app_view_tar",
+    ],
+    outs = ["onos-gui.jar"],
+    cmd = " ROOT=`pwd` &&" +
+          " mkdir -p gui/WEB-INF/classes &&" +
+          " cd gui &&" +
+          " tar xf $$ROOT/$(location :_onos-gui-npm-build) &&" +
+          " (cd WEB-INF/classes && mkdir -p app/view && cd app/view && tar xf $$ROOT/$(location :_app_view_tar)) &&" +
+          " for i in $(locations :_root_level_files); do cp $$ROOT/$$i .; done &&" +
+          " for i in $(locations :_web_inf_classes_files); do cp $$ROOT/$$i ./WEB-INF/classes/; done &&" +
+          " mkdir ./WEB-INF/classes/raw && for i in $(locations :_raw_classes_files); do cp $$ROOT/$$i ./WEB-INF/classes/raw/; done &&" +
+          " jar xf $$ROOT/$(location :_onos-gui-base-jar) &&" +
+          " jar cmf META-INF/MANIFEST.MF $$ROOT/$@ .",
+    local = True,
+    output_to_bindir = 1,
+    visibility = ["//visibility:public"],
+)