bazel: bower support

* npm_binary (download tarball packaged npm apps)

* bower_archive (download a zip file, to be put in WORKSPACE)

* bower_component (defining a bower library, with dependency )

* bower_component_bundle (zipping up libraries together)

* js_component (insert plain js file into bower component bundle)

* bower2bazel.py: run bower to find dependencies, generate a .bzl to
  define archives and define components

Tested:

 python tools/js/bower2bazel.py -w lib/js/bower_archives.bzl -b \
    lib/js/bower_components.bzl

 bazel build polygerrit-ui:components
 unzip -v bazel-bin/polygerrit-ui/components.zip > /tmp/baz

 buck build polygerrit-ui:polygerrit_components
 unzip -v buck-out/gen/polygerrit-ui/polygerrit_components/polygerrit_components.bower_components.zip > /tmp/buck

 diff /tmp/buck /tmp/baz

The diff corresponds to newer file versions pinned through bower2bazel.

Change-Id: I4f33914d4853bcf8afe78b4719d0e0e83b139031
diff --git a/tools/js/bowerutil.py b/tools/js/bowerutil.py
new file mode 100644
index 0000000..eb8893b
--- /dev/null
+++ b/tools/js/bowerutil.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# 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.
+
+import os
+from os import path
+
+
+def hash_bower_component(hash_obj, path):
+  """Hash the contents of a bower component directory.
+
+  This is a stable hash of a directory downloaded with `bower install`, minus
+  the .bower.json file, which is autogenerated each time by bower. Used in lieu
+  of hashing a zipfile of the contents, since zipfiles are difficult to hash in
+  a stable manner.
+
+  Args:
+    hash_obj: an open hash object, e.g. hashlib.sha1().
+    path: path to the directory to hash.
+
+  Returns:
+    The passed-in hash_obj.
+  """
+  if not os.path.isdir(path):
+    raise ValueError('Not a directory: %s' % path)
+
+  path = os.path.abspath(path)
+  for root, dirs, files in os.walk(path):
+    dirs.sort()
+    for f in sorted(files):
+      if f == '.bower.json':
+        continue
+      p = os.path.join(root, f)
+      hash_obj.update(p[len(path)+1:])
+      hash_obj.update(open(p).read())
+
+  return hash_obj