You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by ha...@apache.org on 2017/08/10 20:04:39 UTC

zookeeper git commit: ZOOKEEPER-2864: Add script to run a java api compatibility tool

Repository: zookeeper
Updated Branches:
  refs/heads/branch-3.5 23962f123 -> 82be7f63c


ZOOKEEPER-2864: Add script to run a java api compatibility tool

For example: `python check_compatibility.py branch-3.5..branch-3.4 `

Author: Abraham Fine <af...@apache.org>

Reviewers: Michael Han <ha...@apache.org>

Closes #329 from afine/ZOOKEEPER-2864


Project: http://git-wip-us.apache.org/repos/asf/zookeeper/repo
Commit: http://git-wip-us.apache.org/repos/asf/zookeeper/commit/82be7f63
Tree: http://git-wip-us.apache.org/repos/asf/zookeeper/tree/82be7f63
Diff: http://git-wip-us.apache.org/repos/asf/zookeeper/diff/82be7f63

Branch: refs/heads/branch-3.5
Commit: 82be7f63c69b8aeecf80106c5e637bd80b64657e
Parents: 23962f1
Author: Abraham Fine <af...@apache.org>
Authored: Thu Aug 10 13:04:35 2017 -0700
Committer: Michael Han <ha...@apache.org>
Committed: Thu Aug 10 13:04:35 2017 -0700

----------------------------------------------------------------------
 src/java/test/bin/check_compatibility.py | 204 ++++++++++++++++++++++++++
 1 file changed, 204 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zookeeper/blob/82be7f63/src/java/test/bin/check_compatibility.py
----------------------------------------------------------------------
diff --git a/src/java/test/bin/check_compatibility.py b/src/java/test/bin/check_compatibility.py
new file mode 100644
index 0000000..cad8195
--- /dev/null
+++ b/src/java/test/bin/check_compatibility.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+# Script which checks Java API compatibility between two revisions of the
+# Java client.
+#
+# Based on the compatibility checker from the HBase project, but ported to
+# Python for better readability.
+
+# Lifted from Kudu: https://github.com/apache/kudu/blob/master/build-support/check_compatibility.py
+
+import logging
+import optparse
+import os
+import shutil
+import subprocess
+import sys
+
+JAVA_ACC_GIT_URL = "https://github.com/lvc/japi-compliance-checker.git"
+
+# The annotations for what we consider our public API.
+PUBLIC_ANNOTATIONS = ["org.apache.yetus.audience.InterfaceAudience.LimitedPrivate",
+                      "org.apache.yetus.audience.InterfaceAudience.Public"]
+
+# Various relative paths
+PATH_TO_REPO_DIR = "../../../../"
+PATH_TO_BUILD_DIR = PATH_TO_REPO_DIR + "build/compat-check"
+PATH_TO_JACC_DIR = PATH_TO_REPO_DIR + "build/jacc"
+
+def check_output(*popenargs, **kwargs):
+    # r"""Run command with arguments and return its output as a byte string.
+    # Backported from Python 2.7 as it's implemented as pure python on stdlib.
+    # >>> check_output(['/usr/bin/python', '--version'])
+    # Python 2.6.2
+    # """
+    process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
+    output, unused_err = process.communicate()
+    retcode = process.poll()
+    if retcode:
+        cmd = kwargs.get("args")
+        if cmd is None:
+            cmd = popenargs[0]
+        error = subprocess.CalledProcessError(retcode, cmd)
+        error.output = output
+        raise error
+    return output
+
+def get_repo_dir():
+    """ Return the path to the top of the repo. """
+    dirname, _ = os.path.split(os.path.abspath(__file__))
+    return os.path.abspath(os.path.join(dirname, PATH_TO_REPO_DIR))
+
+
+def get_scratch_dir():
+    """ Return the path to the scratch dir that we build within. """
+    dirname, _ = os.path.split(os.path.abspath(__file__))
+    return os.path.abspath(os.path.join(dirname, PATH_TO_BUILD_DIR))
+
+
+def get_java_acc_dir():
+    dirname, _ = os.path.split(os.path.abspath(__file__))
+    return os.path.abspath(os.path.join(dirname, PATH_TO_JACC_DIR))
+
+
+def clean_scratch_dir(scratch_dir):
+    """ Clean up and re-create the scratch directory. """
+    if os.path.exists(scratch_dir):
+        logging.info("Removing scratch dir %s...", scratch_dir)
+        shutil.rmtree(scratch_dir)
+    logging.info("Creating empty scratch dir %s...", scratch_dir)
+    os.makedirs(scratch_dir)
+
+
+def checkout_tree(rev, path):
+    """ Check out the Java source tree for the given revision into the given path. """
+    logging.info("Checking out %s in %s", rev, path)
+    os.makedirs(path)
+    # Extract java source
+    subprocess.check_call(["bash", '-o', 'pipefail', "-c",
+                           ("git archive --format=tar %s | " +
+                            "tar -C \"%s\" -xf -") % (rev, path)],
+                          cwd=get_repo_dir())
+
+
+def get_git_hash(revname):
+    """ Convert 'revname' to its SHA-1 hash. """
+    return check_output(["git", "rev-parse", revname],
+                        cwd=get_repo_dir()).strip()
+
+
+def build_tree(path):
+    """ Run the Java build within 'path'. """
+    logging.info("Building in %s...", path)
+    subprocess.check_call(["ant", "jar"], cwd=path)
+
+
+def checkout_java_acc(force):
+    """
+    Check out the Java API Compliance Checker. If 'force' is true, will re-download even if the
+    directory exists.
+    """
+    acc_dir = get_java_acc_dir()
+    if os.path.exists(acc_dir):
+        logging.info("Java JAVA_ACC is already downloaded.")
+        if not force:
+            return
+        logging.info("Forcing re-download.")
+        shutil.rmtree(acc_dir)
+    logging.info("Checking out Java JAVA_ACC...")
+    subprocess.check_call(["git", "clone", "-b", "2.1", "--single-branch", "--depth=1", JAVA_ACC_GIT_URL, acc_dir])
+
+
+def find_client_jars(path):
+    """ Return a list of jars within 'path' to be checked for compatibility. """
+    return check_output(["find", path, "-name", "zookeeper*.jar"]).rstrip('\n')
+
+
+def run_java_acc(src_name, src, dst_name, dst):
+    """ Run the compliance checker to compare 'src' and 'dst'. """
+    src_jar = find_client_jars(src)
+    dst_jar = find_client_jars(dst)
+    logging.info("Will check compatibility between original jars:\n%s\n" +
+                 "and new jars:\n%s",
+                 src_jar, dst_jar)
+
+    annotations_path = os.path.join(get_scratch_dir(), "annotations.txt")
+    with file(annotations_path, "w") as f:
+        for ann in PUBLIC_ANNOTATIONS:
+            print >>f,  ann
+
+    java_acc_path = os.path.join(get_java_acc_dir(), "japi-compliance-checker.pl")
+
+    out_path = os.path.join(get_scratch_dir(), "report.html")
+    subprocess.check_call(["perl", java_acc_path,
+                           "-lib", "ZooKeeper",
+                           "-v1", src_name,
+                           "-v2", dst_name,
+                           "-d1", src_jar,
+                           "-d2", dst_jar,
+                           "-annotations-list", annotations_path,
+                           "-report-path", out_path])
+
+
+def main(argv):
+    logging.basicConfig(level=logging.INFO)
+    parser = optparse.OptionParser(
+        usage="usage: %prog SRC..[DST]")
+    parser.add_option("-f", "--force-download", dest="force_download_deps",
+                      help=("Download dependencies (i.e. Java JAVA_ACC) even if they are " +
+                            "already present"))
+    opts, args = parser.parse_args()
+
+    if len(args) != 1:
+        parser.error("no src/dst revision specified")
+        sys.exit(1)
+
+    src_rev, dst_rev = args[0].split("..", 1)
+    if dst_rev == "":
+        dst_rev = "HEAD"
+    src_rev = get_git_hash(src_rev)
+    dst_rev = get_git_hash(dst_rev)
+
+    logging.info("Source revision: %s", src_rev)
+    logging.info("Destination revision: %s", dst_rev)
+
+    # Download deps.
+    checkout_java_acc(opts.force_download_deps)
+
+    # Set up the build.
+    scratch_dir = get_scratch_dir()
+    clean_scratch_dir(scratch_dir)
+
+    # Check out the src and dst source trees.
+    src_dir = os.path.join(scratch_dir, "src")
+    dst_dir = os.path.join(scratch_dir, "dst")
+    checkout_tree(src_rev, src_dir)
+    checkout_tree(dst_rev, dst_dir)
+
+    # Run the build in each.
+    build_tree(src_dir)
+    build_tree(dst_dir)
+
+    run_java_acc(src_rev, src_dir + "/build",
+                 dst_rev, dst_dir + "/build")
+
+
+if __name__ == "__main__":
+    main(sys.argv)
\ No newline at end of file