diff --git a/Documentation/BUCK b/Documentation/BUCK index 43549b4df0..51a0c0a4fa 100644 --- a/Documentation/BUCK +++ b/Documentation/BUCK @@ -5,8 +5,9 @@ include_defs('//tools/git.defs') DOC_DIR = 'Documentation' -JSUI = '//gerrit-gwtui:ui_module' -MAIN = '//gerrit-pgm:pgm' +JSUI_JAVA_DEPS = ['//gerrit-gwtui:ui_module'] +JSUI_NON_JAVA_DEPS = ['//polygerrit-ui:polygerrit_components'] +MAIN_JAVA_DEPS = ['//gerrit-pgm:pgm'] SRCS = glob(['*.txt'], excludes = ['licenses.txt']) @@ -34,7 +35,8 @@ genasciidoc( genlicenses( name = 'licenses.txt', opts = ['--asciidoc'], - java_deps = [MAIN, JSUI], + java_deps = JSUI_JAVA_DEPS + MAIN_JAVA_DEPS, + non_java_deps = JSUI_NON_JAVA_DEPS, out = 'licenses.txt', ) @@ -42,7 +44,8 @@ genlicenses( genlicenses( name = 'js_licenses.txt', opts = ['--partial'], - java_deps = [JSUI], + java_deps = JSUI_JAVA_DEPS, + non_java_deps = JSUI_NON_JAVA_DEPS, out = 'js_licenses.txt', ) diff --git a/Documentation/gen_licenses.py b/Documentation/gen_licenses.py index 25625f2099..59dfe6f756 100755 --- a/Documentation/gen_licenses.py +++ b/Documentation/gen_licenses.py @@ -52,6 +52,13 @@ def parse_graph(): obj = json.load(p.stdout) for target, attrs in obj.iteritems(): for dep in attrs['buck.direct_dependencies']: + + # bower is only used by the build process; skip it in those cases, even if + # --partial is not passed. + if ((target == '//tools/js:download_bower' + or target.startswith('//lib/js:') + and dep == '//lib/js:bower')): + continue if target in KNOWN_PROVIDED_DEPS: continue diff --git a/lib/BUCK b/lib/BUCK index a92f910117..4d06afa10d 100644 --- a/lib/BUCK +++ b/lib/BUCK @@ -1,14 +1,12 @@ include_defs('//lib/maven.defs') +define_license(name = 'antlr') define_license(name = 'Apache1.1') define_license(name = 'Apache2.0') -define_license(name = 'CC-BY3.0') -define_license(name = 'MPL1.1') -define_license(name = 'PublicDomain') -define_license(name = 'antlr') define_license(name = 'args4j') define_license(name = 'automaton') define_license(name = 'bouncycastle') +define_license(name = 'CC-BY3.0') define_license(name = 'clippy') define_license(name = 'codemirror') define_license(name = 'diffy') @@ -17,12 +15,18 @@ define_license(name = 'freebie_application_icon_set') define_license(name = 'h2') define_license(name = 'jgit') define_license(name = 'jsch') +define_license(name = 'MPL1.1') define_license(name = 'ow2') +define_license(name = 'page.js') +define_license(name = 'polymer') define_license(name = 'postgresql') define_license(name = 'prologcafe') +define_license(name = 'promise-polyfill') define_license(name = 'protobuf') +define_license(name = 'PublicDomain') define_license(name = 'slf4j') define_license(name = 'xz') + define_license(name = 'DO_NOT_DISTRIBUTE') maven_jar( diff --git a/lib/LICENSE-page.js b/lib/LICENSE-page.js new file mode 100644 index 0000000000..78152a9741 --- /dev/null +++ b/lib/LICENSE-page.js @@ -0,0 +1,20 @@ +(The MIT License) + +Copyright (c) 2012 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the 'Software'), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/LICENSE-polymer b/lib/LICENSE-polymer new file mode 100644 index 0000000000..322c5a866e --- /dev/null +++ b/lib/LICENSE-polymer @@ -0,0 +1,27 @@ +Copyright (c) 2014 The Polymer Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/LICENSE-promise-polyfill b/lib/LICENSE-promise-polyfill new file mode 100644 index 0000000000..6f7c012316 --- /dev/null +++ b/lib/LICENSE-promise-polyfill @@ -0,0 +1,20 @@ +Copyright (c) 2014 Taylor Hakes +Copyright (c) 2014 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/js.defs b/lib/js.defs new file mode 100644 index 0000000000..f3ebe584d9 --- /dev/null +++ b/lib/js.defs @@ -0,0 +1,79 @@ +# Copyright (C) 2015 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. + +def npm_binary( + name, + version, + sha1 = '', + visibility = ['PUBLIC']): + + dir = '%s-%s' % (name, version) + filename = '%s.tgz' % dir + dest = '%s@%s.npm_binary.tgz' % (name, version) + url = 'http://registry.npmjs.org/%s/-/%s' % (name, filename) + cmd = ['$(exe //tools:download_file)', '-o', '$OUT', '-u', url] + if sha1: + cmd.extend(['-v', sha1]) + genrule( + name = name, + cmd = ' '.join(cmd), + out = dest, + license = 'DO_NOT_DISTRIBUTE', + visibility = visibility, + ) + + +def run_npm_binary(target): + return '$(location //tools/js:run_npm_binary) $(location %s)' % target + + +def bower_component( + name, + package, + version, + license, + sha1 = '', + visibility = ['PUBLIC']): + genrule( + name = name, + cmd = ' '.join([ + '$(exe //tools/js:download_bower)', + '-b', '"%s"' % run_npm_binary('//lib/js:bower'), + '-n', name, + '-p', package, + '-v', version, + '-s', sha1, + '-o', '$OUT', + ]), + out = '%s-%s.zip' % (name, version), + license = license, + visibility = visibility, + ) + + +def bower_components( + name, + deps, + visibility = ['PUBLIC']): + bc = '$TMP/bower_components' + cmds = ['mkdir %s' % bc, 'cd %s' % bc] + for dep in deps: + cmds.append('unzip $(location %s)' % dep) + cmds.extend(['cd $TMP', 'zip -r $OUT bower_components']) + genrule( + name = name, + cmd = ' && '.join(cmds), + out = '%s.bower_components.zip' % name, + visibility = visibility, + ) diff --git a/lib/js/BUCK b/lib/js/BUCK new file mode 100644 index 0000000000..280f8473a6 --- /dev/null +++ b/lib/js/BUCK @@ -0,0 +1,115 @@ +include_defs('//lib/js.defs') + +# WHEN REVIEWING NEW NPM_BINARY RULES: +# +# You must check licenses in the transitive closure of dependencies to ensure +# they can be used by Gerrit. (npm binaries are not distributed with Gerrit +# releases, so we are less restrictive in our selection of licenses, but we +# still need to do a sanity check.) +# +# To do this: +# npm install -g license-checker +# mkdir /tmp/npmtmp +# cd /tmp/npmtmp +# npm install @ +# license-checker +# (Piping to grep -o 'licenses:.*' and/or sort -u may make the output saner.) + +npm_binary( + name = 'bower', + version = '1.6.5', + sha1 = '59d457122a161e42cc1625bbab8179c214b7ac11', +) + +# bower_components are listed without transitive dependencies; the whole +# flattened dependency tree needs to be included in +# //polygerrit-ui:polygerrit_components. +# +# To print a full dependency tree with specific versions (i.e. not semantic +# versions containing ~ or ^), use bower-dependency-tree: +# npm install -g bower-dependency-tree +# cd polygerrit-ui +# bower-dependency-tree --production +# +# Pull the name and package strings from the initial "Fetching" lines, and the +# actual versions from the dependency tree output. + +bower_component( + name = 'iron-a11y-keys', + package = 'PolymerElements/iron-a11y-keys', + version = '1.0.3', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'iron-a11y-keys-behavior', + package = 'PolymerElements/iron-a11y-keys-behavior', + version = '1.0.8', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'iron-ajax', + package = 'PolymerElements/iron-ajax', + version = '1.0.8', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'iron-input', + package = 'PolymerElements/iron-input', + version = '1.0.6', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'iron-meta', + package = 'PolymerElements/iron-meta', + version = '1.1.1', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'iron-validatable-behavior', + package = 'PolymerElements/iron-validatable-behavior', + version = '1.0.5', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'page', + package = 'visionmedia/page.js', + version = '1.6.4', + license = 'page.js', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'polymer', + package = 'Polymer/polymer', + version = '1.2.1', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'promise-polyfill', + package = 'polymerlabs/promise-polyfill', + version = '1.0.0', + license = 'promise-polyfill', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) + +bower_component( + name = 'webcomponentsjs', + package = 'webcomponentsjs', + version = '0.7.16', + license = 'polymer', + sha1 = 'da39a3ee5e6b4b0d3255bfef95601890afd80709', +) diff --git a/polygerrit-ui/BUCK b/polygerrit-ui/BUCK new file mode 100644 index 0000000000..9c02064548 --- /dev/null +++ b/polygerrit-ui/BUCK @@ -0,0 +1,17 @@ +include_defs('//lib/js.defs') + +bower_components( + name = 'polygerrit_components', + deps = [ + '//lib/js:iron-a11y-keys', + '//lib/js:iron-a11y-keys-behavior', + '//lib/js:iron-ajax', + '//lib/js:iron-input', + '//lib/js:iron-meta', + '//lib/js:iron-validatable-behavior', + '//lib/js:page', + '//lib/js:polymer', + '//lib/js:promise-polyfill', + '//lib/js:webcomponentsjs', + ], +) diff --git a/polygerrit-ui/bower.json b/polygerrit-ui/bower.json index 3b930f4de0..260abd8cc2 100644 --- a/polygerrit-ui/bower.json +++ b/polygerrit-ui/bower.json @@ -1,21 +1,18 @@ { "name": "polygerrit", "version": "0.0.0", - "authors": [ - "Andrew Bonventre " - ], - "description": "Gerrit UI in Polymer", + "description": "Gerrit UI in Polymer; DO NOT PUBLISH, used only for dependency management", "private": true, "dependencies": { "polymer": "Polymer/polymer#^1.1", "page": "visionmedia/page.js#~1.6", "iron-ajax": "PolymerElements/iron-ajax#~1.0", "iron-a11y-keys": "PolymerElements/iron-a11y-keys#~1.0", - "iron-input": "PolymerElements/iron-input#~1.0", - "iron-test-helpers": "PolymerElements/iron-test-helpers#~1.0" + "iron-input": "PolymerElements/iron-input#~1.0" }, "devDependencies": { "web-component-tester": "*", + "iron-test-helpers": "PolymerElements/iron-test-helpers#~1.0", "test-fixture": "PolymerElements/test-fixture#^1.0.0" } } diff --git a/tools/js/BUCK b/tools/js/BUCK new file mode 100644 index 0000000000..3c71704499 --- /dev/null +++ b/tools/js/BUCK @@ -0,0 +1,11 @@ +python_binary( + name = 'download_bower', + main = 'download_bower.py', + visibility = ['PUBLIC'], +) + +python_binary( + name = 'run_npm_binary', + main = 'run_npm_binary.py', + visibility = ['PUBLIC'], +) diff --git a/tools/js/download_bower.py b/tools/js/download_bower.py new file mode 100644 index 0000000000..e794cc0be4 --- /dev/null +++ b/tools/js/download_bower.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# Copyright (C) 2015 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. + +from __future__ import print_function + +import hashlib +import json +import optparse +import os +import shutil +import subprocess +import sys + +CACHE_DIR = os.path.expanduser(os.path.join( + '~', '.gerritcodereview', 'buck-cache', 'downloaded-artifacts')) + + +def hash_file(h, p): + with open(p, 'rb') as f: + while True: + b = f.read(8192) + if not b: + break + h.update(p) + + +def hash_dir(dir): + # It's hard to get zipfiles to hash deterministically. Instead, do a sorted + # walk and hash filenames and contents together. + h = hashlib.sha1() + + for root, dirs, files in os.walk(dir): + dirs.sort() + for f in sorted(files): + p = os.path.join(root, f) + h.update(p) + hash_file(h, p) + + return h.hexdigest() + + +def bower_cmd(bower, *args): + cmd = bower.split(' ') + cmd.extend(args) + return cmd + + +def bower_info(bower, name, package, version): + cmd = bower_cmd(bower, '-l=error', '-j', + 'info', '%s#%s' % (package, version)) + p = subprocess.Popen(cmd , stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + if p.returncode: + sys.stderr.write(err) + raise OSError('Command failed: %s' % cmd) + + try: + info = json.loads(out) + except ValueError: + raise ValueError('invalid JSON from %s:\n%s' % (cmd, out)) + info_name = info.get('name') + if info_name != name: + raise ValueError('expected package name %s, got: %s' % (name, info_name)) + return info + + +def ignore_deps(info): + # Tell bower to ignore dependencies so we just download this component. This + # is just an optimization, since we only pick out the component we need, but + # it's important when downloading sizable dependency trees. + # + # As of 1.6.5 I don't think ignoredDependencies can be specified on the + # command line with --config, so we have to create .bowerrc. + deps = info.get('dependencies') + if deps: + with open(os.path.join('.bowerrc'), 'w') as f: + json.dump({'ignoredDependencies': deps.keys()}, f) + + +def cache_entry(name, version, sha1): + c = os.path.join(CACHE_DIR, '%s-%s.zip' % (name, version)) + if sha1: + c += '-%s' % sha1 + return c + + +def main(args): + opts = optparse.OptionParser() + opts.add_option('-n', help='short name of component') + opts.add_option('-b', help='bower command') + opts.add_option('-p', help='full package name of component') + opts.add_option('-v', help='version number') + opts.add_option('-s', help='expected content sha1') + opts.add_option('-o', help='output file location') + opts, _ = opts.parse_args() + + outzip = os.path.join(os.getcwd(), opts.o) + # TODO(dborowitz): match download_file behavior of pulling any old file from + # the cache if there is no -s + # Also don't double-append sha1. + cached = cache_entry(opts.n, opts.v, opts.s) + if os.path.isfile(cached): + shutil.copyfile(cached, outzip) + return 0 + + info = bower_info(opts.b, opts.n, opts.p, opts.v) + ignore_deps(info) + subprocess.check_call( + bower_cmd(opts.b, '--quiet', 'install', '%s#%s' % (opts.p, opts.v))) + name = info['name'] + sha1 = hash_dir(name) + + if opts.s and sha1 != opts.s: + print(( + '%s#%s:\n' + 'expected %s\n' + 'received %s\n') % (opts.p, opts.v, opts.s, sha1), file=sys.stderr) + return 1 + + os.chdir('bower_components') + cmd = ['zip', '-q', '-r', outzip, opts.n] + subprocess.check_call(cmd) + shutil.copyfile(outzip, cache_entry(opts.n, opts.v, sha1)) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/tools/js/run_npm_binary.py b/tools/js/run_npm_binary.py new file mode 100644 index 0000000000..11f6b10def --- /dev/null +++ b/tools/js/run_npm_binary.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# Copyright (C) 2015 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. + +from __future__ import print_function + +import atexit +import hashlib +import os +import shutil +import sys +import tarfile +import tempfile + + +def hash_file(p): + d = hashlib.sha1() + with open(p, 'rb') as f: + while True: + b = f.read(8192) + if not b: + break + d.update(b) + return d.hexdigest() + + +def extract(path, outdir, bin): + if os.path.exists(os.path.join(outdir, bin)): + return # Another process finished extracting, ignore. + + # Use a temp directory adjacent to outdir so shutil.move can use the same + # device atomically. + tmpdir = tempfile.mkdtemp(dir=os.path.dirname(outdir)) + def cleanup(): + try: + shutil.rmtree(tmpdir) + except OSError: + pass # Too late now + atexit.register(cleanup) + + def extract_one(mem): + dest = os.path.join(outdir, mem.name) + tar.extract(mem, path=tmpdir) + try: + os.makedirs(os.path.dirname(dest)) + except OSError: + pass # Either exists, or will fail on the next line. + shutil.move(os.path.join(tmpdir, mem.name), dest) + + with tarfile.open(path, 'r:gz') as tar: + for mem in tar.getmembers(): + if mem.name != bin: + extract_one(mem) + # Extract bin last so other processes only short circuit when extraction is + # finished. + extract_one(tar.getmember(bin)) + + +def main(args): + path = args[0] + suffix = '.npm_binary.tgz' + tgz = os.path.basename(path) + parts = tgz[:-len(suffix)].split('@') + + if not tgz.endswith(suffix) or len(parts) != 2: + print('usage: %s ' % sys.argv[0], file=sys.stderr) + return 1 + + name, version = parts + outdir = '%s-%s' % (path[:-len(suffix)], hash_file(path)) + rel_bin = os.path.join('package', 'bin', name) + bin = os.path.join(outdir, rel_bin) + if not os.path.isfile(bin): + extract(path, outdir, rel_bin) + + os.execv(bin, args[1:]) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:]))