Initial revision

Change-Id: I51b57261d8076c36ca3b85ec002129bdc1eccf9a
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/java7.bucklet b/java7.bucklet
new file mode 100644
index 0000000..488fba4
--- /dev/null
+++ b/java7.bucklet
@@ -0,0 +1,52 @@
+# see https://github.com/facebook/buck/pull/67
+original_java_library = java_library
+def java_library(
+    name,
+    srcs=[],
+    resources=[],
+    source='7',
+    target='7',
+    proguard_config=None,
+    deps=[],
+    exported_deps=[],
+    visibility=[],
+    ):
+  original_java_library(
+    name=name,
+    srcs=srcs,
+    resources=resources,
+    source=source,
+    target=target,
+    proguard_config=proguard_config,
+    deps=deps,
+    exported_deps=exported_deps,
+    visibility=visibility,
+  )
+
+original_java_test = java_test
+def java_test(
+    name,
+    srcs=[],
+    labels=[],
+    resources=[],
+    source='7',
+    target='7',
+    vm_args=[],
+    source_under_test=[],
+    contacts=[],
+    deps=[],
+    visibility=[],
+    ):
+  original_java_test(
+    name=name,
+    srcs=srcs,
+    labels=labels,
+    resources=resources,
+    source=source,
+    target=target,
+    vm_args=vm_args,
+    source_under_test=source_under_test,
+    contacts=contacts,
+    deps=deps,
+    visibility=visibility,
+  )
diff --git a/java8.bucklet b/java8.bucklet
new file mode 100644
index 0000000..1fb22aa
--- /dev/null
+++ b/java8.bucklet
@@ -0,0 +1,52 @@
+# see https://github.com/facebook/buck/pull/67
+original_java_library = java_library
+def java_library(
+    name,
+    srcs=[],
+    resources=[],
+    source='8',
+    target='8',
+    proguard_config=None,
+    deps=[],
+    exported_deps=[],
+    visibility=[],
+    ):
+  original_java_library(
+    name=name,
+    srcs=srcs,
+    resources=resources,
+    source=source,
+    target=target,
+    proguard_config=proguard_config,
+    deps=deps,
+    exported_deps=exported_deps,
+    visibility=visibility,
+  )
+
+original_java_test = java_test
+def java_test(
+    name,
+    srcs=[],
+    labels=[],
+    resources=[],
+    source='8',
+    target='8',
+    vm_args=[],
+    source_under_test=[],
+    contacts=[],
+    deps=[],
+    visibility=[],
+    ):
+  original_java_test(
+    name=name,
+    srcs=srcs,
+    labels=labels,
+    resources=resources,
+    source=source,
+    target=target,
+    vm_args=vm_args,
+    source_under_test=source_under_test,
+    contacts=contacts,
+    deps=deps,
+    visibility=visibility,
+  )
diff --git a/java_doc.bucklet b/java_doc.bucklet
new file mode 100644
index 0000000..8df3f85
--- /dev/null
+++ b/java_doc.bucklet
@@ -0,0 +1,32 @@
+def java_doc(
+    name,
+    title,
+    pkg,
+    paths,
+    srcs = [],
+    deps = [],
+    visibility = []
+  ):
+  genrule(
+    name = name,
+    cmd = ' '.join([
+      'javadoc',
+      '-quiet',
+      '-protected',
+      '-encoding UTF-8',
+      '-charset UTF-8',
+      '-notimestamp',
+      '-windowtitle "' + title + '"',
+      '-link http://docs.oracle.com/javase/7/docs/api',
+      '-subpackages ' + pkg,
+      '-sourcepath ',
+      ':'.join([n for n in paths]),
+      ' -classpath ',
+      ':'.join(['$(location %s)' % n for n in deps]),
+      '-d $TMP',
+    ]) + ';jar cf $OUT -C $TMP .',
+    srcs = srcs,
+    deps = deps,
+    out = name + '.jar',
+    visibility = visibility,
+)
diff --git a/java_library2.bucklet b/java_library2.bucklet
new file mode 100644
index 0000000..8fe78e8
--- /dev/null
+++ b/java_library2.bucklet
@@ -0,0 +1,37 @@
+# Compiles a Java library with additional compile-time dependencies
+# that do not show up as transitive dependencies to java_library()
+# or java_binary() rule that depends on this library.
+# work around for: https://github.com/facebook/buck/issues/63
+def java_library2(
+    name,
+    srcs = [],
+    resources = [],
+    deps = [],
+    compile_deps = [],
+    visibility = []):
+  c = name + '__compile'
+  t = name + '__link'
+  j = 'lib__%s__output/%s.jar' % (c, c)
+  o = 'lib__%s__output/%s.jar' % (name, name)
+  java_library(
+    name = c,
+    srcs = srcs,
+    resources = resources,
+    deps = deps + compile_deps,
+    visibility = visibility,
+  )
+  # Break the dependency chain by passing the newly built
+  # JAR to consumers through a prebuilt_jar().
+  genrule(
+    name = t,
+    cmd = 'mkdir -p $(dirname $OUT);ln -s $SRCS $OUT',
+    srcs = [genfile(j)],
+    deps = [':' + c],
+    out = o,
+  )
+  prebuilt_jar(
+    name = name,
+    binary_jar = genfile(o),
+    deps = deps + [':' + t],
+    visibility = visibility,
+  )
diff --git a/java_sources.bucklet b/java_sources.bucklet
new file mode 100644
index 0000000..0b3974e
--- /dev/null
+++ b/java_sources.bucklet
@@ -0,0 +1,10 @@
+def java_sources(
+    name,
+    srcs,
+    visibility = []
+  ):
+  java_library(
+    name = name,
+    resources = srcs,
+    visibility = visibility,
+  )
diff --git a/local_jar.bucklet b/local_jar.bucklet
new file mode 100644
index 0000000..8131daa
--- /dev/null
+++ b/local_jar.bucklet
@@ -0,0 +1,50 @@
+#
+# If a dependent library is undergoing active development it must be
+# recompiled and the change must be reflected in the Buck build process. For
+# example testing Gerrit against changed JGit snapshot version. After building
+# JGit library, the artifacts are created in local Maven build directory.
+#
+# To shorten that workflow and take the installation of the artifacts to the
+# local Maven repository and fetching it again from there out of the picture,
+# `local_jar()` method is used:
+#
+# local_jar(
+#   name = 'jgit',
+#   jar = '/home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT.jar',
+#   src = '/home/<user>/projects/jgit/org.eclipse.jgit/target/org.eclipse.jgit-3.3.0-SNAPSHOT-sources.jar',
+#   deps = [':ewah']
+# )
+
+def local_jar(
+    name,
+    jar,
+    src = None,
+    deps = [],
+    visibility = ['PUBLIC']):
+  binjar = name + '.jar'
+  srcjar = name + '-src.jar'
+  genrule(
+    name = name + '__local_bin',
+    cmd = 'ln -s %s $OUT' % jar,
+    out = binjar)
+  if src:
+    genrule(
+      name = name + '__local_src',
+      cmd = 'ln -s %s $OUT' % src,
+      out = srcjar)
+    prebuilt_jar(
+      name = name + '_src',
+      deps = [':' + name + '__local_src'],
+      binary_jar = genfile(srcjar),
+      visibility = visibility,
+    )
+  else:
+    srcjar = None
+
+  prebuilt_jar(
+    name = name,
+    deps = deps + [':' + name + '__local_bin'],
+    binary_jar = genfile(binjar),
+    source_jar = genfile(srcjar) if srcjar else None,
+    visibility = visibility,
+  )
diff --git a/maven_jar.bucklet b/maven_jar.bucklet
new file mode 100644
index 0000000..19a06f9
--- /dev/null
+++ b/maven_jar.bucklet
@@ -0,0 +1,105 @@
+#
+# Fetch artifacts from Maven repository
+# see: https://github.com/facebook/buck/issues/64
+#
+
+GERRIT = 'GERRIT:'
+GERRIT_API = 'GERRIT_API:'
+ECLIPSE = 'ECLIPSE:'
+MAVEN_CENTRAL = 'MAVEN_CENTRAL:'
+MAVEN_LOCAL = 'MAVEN_LOCAL:'
+
+def maven_jar(
+    name,
+    id,
+    exclude = [],
+    exclude_java_sources = False,
+    unsign = False,
+    deps = [],
+    exported_deps = [],
+    sha1 = '', bin_sha1 = '', src_sha1 = '',
+    repository = MAVEN_CENTRAL,
+    attach_source = True,
+    visibility = ['PUBLIC']):
+  from os import path
+
+  parts = id.split(':')
+  if len(parts) != 3:
+    raise NameError('expected id="groupId:artifactId:version"')
+  group, artifact, version = parts
+
+  jar = path.join(name, artifact.lower() + '-' + version)
+  url = '/'.join([
+    repository,
+    group.replace('.', '/'), artifact, version,
+    artifact + '-' + version])
+
+  binjar = jar + '.jar'
+  binurl = url + '.jar'
+
+  srcjar = jar + '-src.jar'
+  srcurl = url + '-sources.jar'
+
+  cmd = ['$(exe //bucklets/tools:download_file)', '-o', '$OUT', '-u', binurl]
+  if sha1:
+    cmd.extend(['-v', sha1])
+  elif bin_sha1:
+    cmd.extend(['-v', bin_sha1])
+  for x in exclude:
+    cmd.extend(['-x', x])
+  if exclude_java_sources:
+    cmd.append('--exclude_java_sources')
+  if unsign:
+    cmd.append('--unsign')
+
+  genrule(
+    name = name + '__download_bin',
+    cmd = ' '.join(cmd),
+    deps = ['//bucklets/tools:download_file'],
+    out = binjar,
+  )
+
+  if src_sha1 or attach_source:
+    cmd = ['$(exe //bucklets/tools:download_file)', '-o', '$OUT', '-u', srcurl]
+    if src_sha1:
+      cmd.extend(['-v', src_sha1])
+    genrule(
+      name = name + '__download_src',
+      cmd = ' '.join(cmd),
+      deps = ['//bucklets/tools:download_file'],
+      out = srcjar,
+    )
+    prebuilt_jar(
+      name = name + '_src',
+      binary_jar = genfile(srcjar),
+      deps = [':' + name + '__download_src'],
+      visibility = visibility,
+    )
+  else:
+    srcjar = None
+    genrule(
+      name = name + '__download_src',
+      cmd = ':>$OUT',
+      out = '__' + name + '__no_src',
+    )
+
+  if exported_deps:
+    prebuilt_jar(
+      name = name + '__jar',
+      deps = deps + [':' + name + '__download_bin'],
+      binary_jar = genfile(binjar),
+      source_jar = genfile(srcjar) if srcjar else None,
+    )
+    java_library(
+      name = name,
+      exported_deps = exported_deps + [':' + name + '__jar'],
+      visibility = visibility,
+    )
+  else:
+    prebuilt_jar(
+      name = name,
+      deps = deps + [':' + name + '__download_bin'],
+      binary_jar = genfile(binjar),
+      source_jar = genfile(srcjar) if srcjar else None,
+      visibility = visibility,
+    )
diff --git a/maven_package.bucklet b/maven_package.bucklet
new file mode 100644
index 0000000..0e865b6
--- /dev/null
+++ b/maven_package.bucklet
@@ -0,0 +1,46 @@
+# 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.
+
+def maven_package(
+    version,
+    repository = None,
+    url = None,
+    jar = {},
+    src = {},
+    doc = {}):
+  cmd = ['$(exe //bucklets/tools:mvn)', '-v', version, '-o', '$OUT']
+  dep = []
+
+  for type,d in [('jar', jar), ('java-source', src), ('javadoc', doc)]:
+    for a,t in d.iteritems():
+      cmd.append('-s %s:%s:$(location %s)' % (a,type,t))
+      dep.append(t)
+
+  genrule(
+    name = 'install',
+    cmd = ' '.join(cmd + ['-a', 'install']),
+    deps = dep + ['//bucklets/tools:mvn'],
+    out = 'install.info',
+  )
+
+  if repository and url:
+    genrule(
+      name = 'deploy',
+      cmd = ' '.join(cmd + [
+        '-a', 'deploy',
+        '--repository', repository,
+        '--url', url]),
+      deps = dep + ['//bucklets/tools:mvn'],
+      out = 'deploy.info',
+    )
diff --git a/tools/BUCK b/tools/BUCK
new file mode 100644
index 0000000..4bdfe5a
--- /dev/null
+++ b/tools/BUCK
@@ -0,0 +1,46 @@
+python_binary(
+  name = 'download_file',
+  main = 'download_file.py',
+  deps = [':util'],
+  visibility = ['PUBLIC'],
+)
+
+python_binary(
+  name = 'pack_war',
+  main = 'pack_war.py',
+  deps = [':util'],
+  visibility = ['PUBLIC'],
+)
+
+python_binary(
+  name = 'mvn',
+  main = 'mvn.py',
+  deps = [':util'],
+  visibility = ['PUBLIC']
+)
+
+python_library(
+  name = 'util',
+  srcs = [
+    'util.py',
+    '__init__.py'
+  ],
+  visibility = ['PUBLIC'],
+)
+
+def shquote(s):
+  return s.replace("'", "'\\''")
+
+def os_path():
+  from os import environ
+  return environ.get('PATH')
+
+genrule(
+  name = 'buck.properties',
+  cmd = 'echo buck=`which buck`>$OUT;' +
+    ("echo PATH=\''%s'\' >>$OUT;" % shquote(os_path())),
+  deps = [],
+  out = 'buck.properties',
+  visibility = ['PUBLIC'],
+)
+
diff --git a/tools/__init__.py b/tools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/__init__.py
diff --git a/tools/download_all.py b/tools/download_all.py
new file mode 100755
index 0000000..a70cbda
--- /dev/null
+++ b/tools/download_all.py
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+# 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.
+
+from optparse import OptionParser
+import re
+from subprocess import check_call, CalledProcessError, Popen, PIPE
+
+MAIN = ['//:classpath']
+PAT = re.compile(r'"(//.*?)" -> "//bucklets/tools:download_file"')
+
+opts = OptionParser()
+opts.add_option('--src', action='store_true')
+args, _ = opts.parse_args()
+
+targets = set()
+
+p = Popen(['buck', 'audit', 'classpath', '--dot'] + MAIN, stdout = PIPE)
+for line in p.stdout:
+  m = PAT.search(line)
+  if m:
+    n = m.group(1)
+    if args.src and n.endswith('__download_bin'):
+      n = n[:-4] + '_src'
+    targets.add(n)
+r = p.wait()
+if r != 0:
+  exit(r)
+
+try:
+  check_call(['buck', 'build'] + sorted(targets))
+except CalledProcessError as err:
+  exit(1)
diff --git a/tools/download_file.py b/tools/download_file.py
new file mode 100755
index 0000000..3e6fca9
--- /dev/null
+++ b/tools/download_file.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+
+from hashlib import sha1
+from optparse import OptionParser
+from os import link, makedirs, path, remove
+import shutil
+from subprocess import check_call, CalledProcessError
+from sys import stderr
+from util import resolve_url
+from zipfile import ZipFile, BadZipfile, LargeZipFile
+
+GERRIT_HOME = path.expanduser('~/.gerritcodereview')
+CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache')
+LOCAL_PROPERTIES = 'local.properties'
+
+def hashfile(p):
+  d = sha1()
+  with open(p, 'rb') as f:
+    while True:
+      b = f.read(8192)
+      if not b:
+        break
+      d.update(b)
+  return d.hexdigest()
+
+def safe_mkdirs(d):
+  if path.isdir(d):
+    return
+  try:
+    makedirs(d)
+  except OSError as err:
+    if not path.isdir(d):
+      raise err
+
+def download_properties(root_dir):
+  """ Get the download properties.
+
+  First tries to find the properties file in the given root directory,
+  and if not found there, tries in the Gerrit settings folder in the
+  user's home directory.
+
+  Returns a set of download properties, which may be empty.
+
+  """
+  p = {}
+  local_prop = path.join(root_dir, LOCAL_PROPERTIES)
+  if not path.isfile(local_prop):
+    local_prop = path.join(GERRIT_HOME, LOCAL_PROPERTIES)
+  if path.isfile(local_prop):
+    try:
+      with open(local_prop) as fd:
+        for line in fd:
+          if line.startswith('download.'):
+            d = [e.strip() for e in line.split('=', 1)]
+            name, url = d[0], d[1]
+            p[name[len('download.'):]] = url
+    except OSError:
+      pass
+  return p
+
+def cache_entry(args):
+  if args.v:
+    h = args.v
+  else:
+    h = sha1(args.u).hexdigest()
+  name = '%s-%s' % (path.basename(args.o), h)
+  return path.join(CACHE_DIR, name)
+
+opts = OptionParser()
+opts.add_option('-o', help='local output file')
+opts.add_option('-u', help='URL to download')
+opts.add_option('-v', help='expected content SHA-1')
+opts.add_option('-x', action='append', help='file to delete from ZIP')
+opts.add_option('--exclude_java_sources', action='store_true')
+opts.add_option('--unsign', action='store_true')
+args, _ = opts.parse_args()
+
+root_dir = args.o
+while root_dir:
+  root_dir, n = path.split(root_dir)
+  if n == 'buck-out':
+    break
+
+redirects = download_properties(root_dir)
+cache_ent = cache_entry(args)
+src_url = resolve_url(args.u, redirects)
+
+if not path.exists(cache_ent):
+  try:
+    safe_mkdirs(path.dirname(cache_ent))
+  except OSError as err:
+    print('error creating directory %s: %s' %
+          (path.dirname(cache_ent), err), file=stderr)
+    exit(1)
+
+  print('Download %s' % src_url, file=stderr)
+  try:
+    check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url])
+  except OSError as err:
+    print('could not invoke curl: %s\nis curl installed?' % err, file=stderr)
+    exit(1)
+  except CalledProcessError as err:
+    print('error using curl: %s' % err, file=stderr)
+    exit(1)
+
+if args.v:
+  have = hashfile(cache_ent)
+  if args.v != have:
+    print((
+      '%s:\n' +
+      'expected %s\n' +
+      'received %s\n') % (src_url, args.v, have), file=stderr)
+    try:
+      remove(cache_ent)
+    except OSError as err:
+      if path.exists(cache_ent):
+        print('error removing %s: %s' % (cache_ent, err), file=stderr)
+    exit(1)
+
+exclude = []
+if args.x:
+  exclude += args.x
+if args.exclude_java_sources:
+  try:
+    zf = ZipFile(cache_ent, 'r')
+    try:
+      for n in zf.namelist():
+        if n.endswith('.java'):
+          exclude.append(n)
+    finally:
+      zf.close()
+  except (BadZipfile, LargeZipFile) as err:
+    print('error opening %s: %s'  % (cache_ent, err), file=stderr)
+    exit(1)
+
+if args.unsign:
+  try:
+    zf = ZipFile(cache_ent, 'r')
+    try:
+      for n in zf.namelist():
+        if (n.endswith('.RSA')
+            or n.endswith('.SF')
+            or n.endswith('.LIST')):
+          exclude.append(n)
+    finally:
+      zf.close()
+  except (BadZipfile, LargeZipFile) as err:
+    print('error opening %s: %s'  % (cache_ent, err), file=stderr)
+    exit(1)
+
+safe_mkdirs(path.dirname(args.o))
+if exclude:
+  try:
+    shutil.copyfile(cache_ent, args.o)
+  except (shutil.Error, IOError) as err:
+    print('error copying to %s: %s' % (args.o, err), file=stderr)
+    exit(1)
+  try:
+    check_call(['zip', '-d', args.o] + exclude)
+  except CalledProcessError as err:
+    print('error removing files from zip: %s' % err, file=stderr)
+    exit(1)
+else:
+  try:
+    link(cache_ent, args.o)
+  except OSError as err:
+    try:
+      shutil.copyfile(cache_ent, args.o)
+    except (shutil.Error, IOError) as err:
+      print('error copying to %s: %s' % (args.o, err), file=stderr)
+      exit(1)
diff --git a/tools/eclipse.py b/tools/eclipse.py
new file mode 100755
index 0000000..f5b46f8
--- /dev/null
+++ b/tools/eclipse.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+# 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.
+#
+# TODO(sop): Remove hack after Buck supports Eclipse
+
+from __future__ import print_function
+from optparse import OptionParser
+from os import path
+from subprocess import Popen, PIPE, CalledProcessError, check_call
+from xml.dom import minidom
+import re
+import sys
+
+MAIN = ['//:classpath']
+JRE = '/'.join([
+  'org.eclipse.jdt.launching.JRE_CONTAINER',
+  'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType',
+  'JavaSE-1.7',
+])
+
+ROOT = path.abspath(__file__)
+for _ in range(0, 3):
+  ROOT = path.dirname(ROOT)
+
+opts = OptionParser()
+opts.add_option('--src', action='store_true')
+opts.add_option('-n', '--name')
+args, _ = opts.parse_args()
+
+def gen_project():
+  p = path.join(ROOT, '.project')
+  name = args.name if args.name else path.basename(ROOT)
+  with open(p, 'w') as fd:
+    print("""\
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+  <name>%s</name>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>\
+""" % (name,), file=fd)
+
+def gen_classpath():
+  def query_classpath(targets):
+    deps = []
+    p = Popen(['buck', 'audit', 'classpath'] + targets, stdout=PIPE)
+    for line in p.stdout:
+      deps.append(line.strip())
+    s = p.wait()
+    if s != 0:
+      exit(s)
+    return deps
+
+  def make_classpath():
+    impl = minidom.getDOMImplementation()
+    return impl.createDocument(None, 'classpath', None)
+
+  def classpathentry(kind, path, src=None, out=None):
+    e = doc.createElement('classpathentry')
+    e.setAttribute('kind', kind)
+    e.setAttribute('path', path)
+    if src:
+      e.setAttribute('sourcepath', src)
+    if out:
+      e.setAttribute('output', out)
+    doc.documentElement.appendChild(e)
+
+  doc = make_classpath()
+  src = set()
+  lib = set()
+
+  java_library = re.compile(r'[^/]+/gen/(.*)/lib__[^/]+__output/[^/]+[.]jar$')
+  for p in query_classpath(MAIN):
+    m = java_library.match(p)
+    if m:
+      src.add(m.group(1))
+    else:
+      lib.add(p)
+
+  for s in sorted(src):
+    out = None
+
+    if s.startswith('lib/'):
+      out = 'buck-out/eclipse/lib'
+    elif s.startswith('plugins/'):
+      out = 'buck-out/eclipse/' + s
+
+    p = path.join(s, 'java')
+    if path.exists(p):
+      classpathentry('src', p, out=out)
+      continue
+
+    for env in ['main', 'test']:
+      o = None
+      if out:
+        o = out + '/' + env
+      elif env == 'test':
+        o = 'buck-out/eclipse/test'
+
+      for srctype in ['java', 'resources']:
+        p = path.join(s, 'src', env, srctype)
+        if path.exists(p):
+          classpathentry('src', p, out=o)
+
+  for libs in [lib]:
+    for j in sorted(libs):
+      s = None
+      if j.endswith('.jar'):
+        s = j[:-4] + '-src.jar'
+        if not path.exists(s):
+          s = None
+      classpathentry('lib', j, s)
+
+  classpathentry('con', JRE)
+  classpathentry('output', 'buck-out/eclipse/classes')
+
+  p = path.join(ROOT, '.classpath')
+  with open(p, 'w') as fd:
+    doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')
+
+try:
+  if args.src:
+    try:
+      check_call([path.join(ROOT, 'bucklets/tools', 'download_all.py'), '--src'])
+    except CalledProcessError as err:
+      exit(1)
+
+  gen_project()
+  gen_classpath()
+
+  try:
+    targets = ['//bucklets/tools:buck.properties'] + MAIN
+    check_call(['buck', 'build'] + targets)
+  except CalledProcessError as err:
+    exit(1)
+except KeyboardInterrupt:
+  print('Interrupted by user', file=sys.stderr)
+  exit(1)
diff --git a/tools/mvn.py b/tools/mvn.py
new file mode 100644
index 0000000..0518439
--- /dev/null
+++ b/tools/mvn.py
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+from optparse import OptionParser
+from os import path
+
+from sys import stderr
+from util import check_output
+
+def mvn(action):
+  return ['mvn', '--file', path.join(ROOT, 'fake_pom_%s.xml' % action)]
+
+def mvn(action):
+  return ['mvn', '--file', path.join(ROOT, 'fake_pom_%s.xml' % action)]
+
+opts = OptionParser()
+opts.add_option('--repository', help='maven repository id')
+opts.add_option('--url', help='maven repository url')
+opts.add_option('-o')
+opts.add_option('-a', help='action (valid actions are: install,deploy)')
+opts.add_option('-v', help='gerrit version')
+opts.add_option('-s', action='append', help='triplet of artifactId:type:path')
+
+args, ctx = opts.parse_args()
+if not args.v:
+  print('version is empty', file=stderr)
+  exit(1)
+
+common = [
+  '-DgroupId=com.google.gerrit',
+  '-Dversion=%s' % args.v,
+]
+
+ROOT = path.abspath(__file__)
+for _ in range(0, 3):
+  ROOT = path.dirname(ROOT)
+
+if 'install' == args.a:
+  cmd = mvn(args.a) + ['install:install-file'] + common
+elif 'deploy' == args.a:
+  cmd = mvn(args.a) + [
+    'deploy:deploy-file',
+    '-DrepositoryId=%s' % args.repository,
+    '-Durl=%s' % args.url,
+  ] + common
+else:
+  print("unknown action -a %s" % args.a, file=stderr)
+  exit(1)
+
+for spec in args.s:
+  artifact, packaging_type, src = spec.split(':')
+  try:
+    check_output(cmd + [
+      '-DartifactId=%s' % artifact,
+      '-Dpackaging=%s' % packaging_type,
+      '-Dfile=%s' % src,
+    ])
+  except Exception as e:
+    print('%s command failed: %s' % (args.a, e), file=stderr)
+    exit(1)
+
+with open(args.o, 'w') as fd:
+  if args.repository:
+    print('Repository: %s' % args.repository, file=fd)
+  if args.url:
+    print('URL: %s' % args.url, file=fd)
+  print('Version: %s' % args.v, file=fd)
diff --git a/tools/pack_war.py b/tools/pack_war.py
new file mode 100755
index 0000000..6c71d81
--- /dev/null
+++ b/tools/pack_war.py
@@ -0,0 +1,55 @@
+#!/usr/bin/python
+# 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.
+
+from __future__ import print_function
+from optparse import OptionParser
+from os import makedirs, path, symlink
+from subprocess import check_call
+import sys
+from util import check_output
+
+opts = OptionParser()
+opts.add_option('-o', help='path to write WAR to')
+opts.add_option('--lib', action='append', help='target for WEB-INF/lib')
+opts.add_option('--pgmlib', action='append', help='target for WEB-INF/pgm-lib')
+opts.add_option('--tmp', help='temporary directory')
+args, ctx = opts.parse_args()
+
+war = args.tmp
+root = war[:war.index('buck-out')]
+jars = set()
+
+def link_jars(libs, directory):
+  makedirs(directory)
+  cp = check_output(['buck', 'audit', 'classpath'] + libs)
+  for j in cp.strip().splitlines():
+    if j not in jars:
+      jars.add(j)
+      n = path.basename(j)
+      if j.startswith('buck-out/gen/gerrit-'):
+        n = j.split('/')[2] + '-' + n
+      symlink(path.join(root, j), path.join(directory, n))
+
+if args.lib:
+  link_jars(args.lib, path.join(war, 'WEB-INF', 'lib'))
+if args.pgmlib:
+  link_jars(args.pgmlib, path.join(war, 'WEB-INF', 'pgm-lib'))
+try:
+  for s in ctx:
+    check_call(['unzip', '-q', '-d', war, s])
+  check_call(['zip', '-9qr', args.o, '.'], cwd = war)
+except KeyboardInterrupt:
+  print('Interrupted by user', file=sys.stderr)
+  exit(1)
diff --git a/tools/util.py b/tools/util.py
new file mode 100644
index 0000000..9115ac7
--- /dev/null
+++ b/tools/util.py
@@ -0,0 +1,58 @@
+# 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.
+
+from os import path
+
+try:
+  from subprocess import check_output
+except ImportError:
+  from subprocess import Popen, PIPE
+  def check_output(*cmd):
+    return Popen(*cmd, stdout=PIPE).communicate()[0]
+
+REPO_ROOTS = {
+  'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
+  'GERRIT_API': 'https://gerrit-api.commondatastorage.googleapis.com/release',
+  'ECLIPSE': 'https://repo.eclipse.org/content/groups/releases',
+  'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2',
+  'MAVEN_LOCAL': 'file://' + path.expanduser('~/.m2/repository'),
+}
+
+def resolve_url(url, redirects):
+  """ Resolve URL of a Maven artifact.
+
+  prefix:path is passed as URL. prefix identifies known or custom
+  repositories that can be rewritten in redirects set, passed as
+  second arguments.
+
+  A special case is supported, when prefix neither exists in
+  REPO_ROOTS, no in redirects set: the url is returned as is.
+  This enables plugins to pass custom maven_repository URL as is
+  directly to maven_jar().
+
+  Returns a resolved path for Maven artifact.
+  """
+  s = url.find(':')
+  if s < 0:
+    return url
+  scheme, rest = url[:s], url[s+1:]
+  if scheme in redirects:
+    root = redirects[scheme]
+  elif scheme in REPO_ROOTS:
+    root = REPO_ROOTS[scheme]
+  else:
+    return url
+  root = root.rstrip('/')
+  rest = rest.lstrip('/')
+  return '/'.join([root, rest])
diff --git a/war.bucklet b/war.bucklet
new file mode 100644
index 0000000..2987e05
--- /dev/null
+++ b/war.bucklet
@@ -0,0 +1,36 @@
+def war(
+    name,
+    libs = [],
+    pgmlibs = [],
+    context = [],
+    visibility = [],
+    ):
+  cmd = ['$(exe //bucklets/tools:pack_war)', '-o', '$OUT', '--tmp', '$TMP']
+  for l in libs:
+    cmd.extend(['--lib', l])
+  for l in pgmlibs:
+    cmd.extend(['--pgmlib', l])
+
+  src = []
+  dep = []
+  if context:
+    root = get_base_path()
+    if root:
+      root = '/'.join(['..' for _ in root.split('/')]) + '/'
+    for r in context:
+      dep.append(r[:r.rindex('.')])
+      if r.startswith('//'):
+        r = root + r[2:]
+      r = r.replace(':', '/')
+      src.append(genfile(r))
+  if src:
+    cmd.append('$SRCS')
+
+  genrule(
+    name = name,
+    cmd = ' '.join(cmd),
+    srcs = src,
+    deps = libs + pgmlibs + dep + ['//bucklets/tools:pack_war'],
+    out = name + '.war',
+    visibility = visibility,
+  )