You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@yetus.apache.org by aw...@apache.org on 2018/11/11 22:19:19 UTC

[03/17] yetus git commit: YETUS-15. build environment

http://git-wip-us.apache.org/repos/asf/yetus/blob/6ebaa111/release-doc-maker/releasedocmaker.py
----------------------------------------------------------------------
diff --git a/release-doc-maker/releasedocmaker.py b/release-doc-maker/releasedocmaker.py
deleted file mode 100755
index 2b2ada4..0000000
--- a/release-doc-maker/releasedocmaker.py
+++ /dev/null
@@ -1,947 +0,0 @@
-#!/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.
-
-import sys
-from glob import glob
-from optparse import OptionParser
-from time import gmtime, strftime, sleep
-from distutils.version import LooseVersion
-import errno
-import os
-import re
-import shutil
-import urllib
-import urllib2
-import httplib
-import json
-sys.dont_write_bytecode = True
-# pylint: disable=wrong-import-position
-from utils import get_jira, to_unicode, sanitize_text, processrelnote, Outputs
-# pylint: enable=wrong-import-position
-
-try:
-    import dateutil.parser
-except ImportError:
-    print "This script requires python-dateutil module to be installed. " \
-          "You can install it using:\n\t pip install python-dateutil"
-    sys.exit(1)
-
-RELEASE_VERSION = {}
-
-JIRA_BASE_URL = "https://issues.apache.org/jira"
-SORTTYPE = 'resolutiondate'
-SORTORDER = 'older'
-NUM_RETRIES = 5
-
-# label to be used to mark an issue as Incompatible change.
-BACKWARD_INCOMPATIBLE_LABEL = 'backward-incompatible'
-
-ASF_LICENSE = '''
-<!---
-# 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.
--->
-'''
-
-def buildindex(title, asf_license):
-    """Write an index file for later conversion using mvn site"""
-    versions = glob("[0-9]*.[0-9]*")
-    versions.sort(key=LooseVersion, reverse=True)
-    with open("index.md", "w") as indexfile:
-        if asf_license is True:
-            indexfile.write(ASF_LICENSE)
-        for version in versions:
-            indexfile.write("* %s v%s\n" % (title, version))
-            for k in ("Changelog", "Release Notes"):
-                indexfile.write("    * [%s](%s/%s.%s.html)\n" %
-                                (k, version, k.upper().replace(" ", ""),
-                                 version))
-
-
-def buildreadme(title, asf_license):
-    """Write an index file for Github using README.md"""
-    versions = glob("[0-9]*.[0-9]*")
-    versions.sort(key=LooseVersion, reverse=True)
-    with open("README.md", "w") as indexfile:
-        if asf_license is True:
-            indexfile.write(ASF_LICENSE)
-        for version in versions:
-            indexfile.write("* %s v%s\n" % (title, version))
-            for k in ("Changelog", "Release Notes"):
-                indexfile.write("    * [%s](%s/%s.%s.md)\n" %
-                                (k, version, k.upper().replace(" ", ""),
-                                 version))
-
-
-class GetVersions(object):
-    """ List of version strings """
-
-    def __init__(self, versions, projects):
-        versions = versions
-        projects = projects
-        self.newversions = []
-        versions.sort(key=LooseVersion)
-        print "Looking for %s through %s" % (versions[0], versions[-1])
-        newversions = set()
-        for project in projects:
-            url = JIRA_BASE_URL + \
-              "/rest/api/2/project/%s/versions" % project.upper()
-            try:
-                resp = get_jira(url)
-            except (urllib2.HTTPError, urllib2.URLError, httplib.BadStatusLine):
-                sys.exit(1)
-
-            datum = json.loads(resp.read())
-            for data in datum:
-                newversions.add(data['name'])
-        newlist = list(newversions.copy())
-        newlist.append(versions[0])
-        newlist.append(versions[-1])
-        newlist.sort(key=LooseVersion)
-        start_index = newlist.index(versions[0])
-        end_index = len(newlist) - 1 - newlist[::-1].index(versions[-1])
-        for newversion in newlist[start_index + 1:end_index]:
-            if newversion in newversions:
-                print "Adding %s to the list" % newversion
-                self.newversions.append(newversion)
-
-    def getlist(self):
-        return self.newversions
-
-
-class Version(object):
-    """Represents a version number"""
-
-    def __init__(self, data):
-        self.mod = False
-        self.data = data
-        found = re.match(r'^((\d+)(\.\d+)*).*$', data)
-        if found:
-            self.parts = [int(p) for p in found.group(1).split('.')]
-        else:
-            self.parts = []
-        # backfill version with zeros if missing parts
-        self.parts.extend((0,) * (3 - len(self.parts)))
-
-    def __str__(self):
-        if self.mod:
-            return '.'.join([str(p) for p in self.parts])
-        return self.data
-
-    def __cmp__(self, other):
-        return cmp(self.parts, other.parts)
-
-
-class Jira(object):
-    """A single JIRA"""
-
-    def __init__(self, data, parent):
-        self.key = data['key']
-        self.fields = data['fields']
-        self.parent = parent
-        self.notes = None
-        self.incompat = None
-        self.reviewed = None
-        self.important = None
-
-    def get_id(self):
-        return to_unicode(self.key)
-
-    def get_description(self):
-        return to_unicode(self.fields['description'])
-
-    def get_release_note(self):
-        if self.notes is None:
-            field = self.parent.field_id_map['Release Note']
-            if field in self.fields:
-                self.notes = to_unicode(self.fields[field])
-            elif self.get_incompatible_change() or self.get_important():
-                self.notes = self.get_description()
-            else:
-                self.notes = ""
-        return self.notes
-
-    def get_priority(self):
-        ret = ""
-        pri = self.fields['priority']
-        if pri is not None:
-            ret = pri['name']
-        return to_unicode(ret)
-
-    def get_assignee(self):
-        ret = ""
-        mid = self.fields['assignee']
-        if mid is not None:
-            ret = mid['displayName']
-        return to_unicode(ret)
-
-    def get_components(self):
-        if self.fields['components']:
-            return ", ".join([comp['name'] for comp in self.fields['components']
-                             ])
-        return ""
-
-    def get_summary(self):
-        return self.fields['summary']
-
-    def get_type(self):
-        ret = ""
-        mid = self.fields['issuetype']
-        if mid is not None:
-            ret = mid['name']
-        return to_unicode(ret)
-
-    def get_reporter(self):
-        ret = ""
-        mid = self.fields['reporter']
-        if mid is not None:
-            ret = mid['displayName']
-        return to_unicode(ret)
-
-    def get_project(self):
-        ret = ""
-        mid = self.fields['project']
-        if mid is not None:
-            ret = mid['key']
-        return to_unicode(ret)
-
-    def __cmp__(self, other):
-        result = 0
-
-        if SORTTYPE == 'issueid':
-            # compare by issue name-number
-            selfsplit = self.get_id().split('-')
-            othersplit = other.get_id().split('-')
-            result = cmp(selfsplit[0], othersplit[0])
-            if result == 0:
-                result = cmp(int(selfsplit[1]), int(othersplit[1]))
-                # dec is supported for backward compatibility
-                if SORTORDER in ['dec', 'desc']:
-                        result *= -1
-
-        elif SORTTYPE == 'resolutiondate':
-            dts = dateutil.parser.parse(self.fields['resolutiondate'])
-            dto = dateutil.parser.parse(other.fields['resolutiondate'])
-            result = cmp(dts, dto)
-            if SORTORDER == 'newer':
-                    result *= -1
-
-        return result
-
-    def get_incompatible_change(self):
-        if self.incompat is None:
-            field = self.parent.field_id_map['Hadoop Flags']
-            self.reviewed = False
-            self.incompat = False
-            if field in self.fields:
-                if self.fields[field]:
-                    for flag in self.fields[field]:
-                        if flag['value'] == "Incompatible change":
-                            self.incompat = True
-                        if flag['value'] == "Reviewed":
-                            self.reviewed = True
-            else:
-                # Custom field 'Hadoop Flags' is not defined,
-                # search for 'backward-incompatible' label
-                field = self.parent.field_id_map['Labels']
-                if field in self.fields and self.fields[field]:
-                    if BACKWARD_INCOMPATIBLE_LABEL in self.fields[field]:
-                        self.incompat = True
-                        self.reviewed = True
-        return self.incompat
-
-    def get_important(self):
-        if self.important is None:
-            field = self.parent.field_id_map['Flags']
-            self.important = False
-            if field in self.fields:
-                if self.fields[field]:
-                    for flag in self.fields[field]:
-                        if flag['value'] == "Important":
-                            self.important = True
-        return self.important
-
-
-class JiraIter(object):
-    """An Iterator of JIRAs"""
-
-    @staticmethod
-    def collect_fields():
-        """send a query to JIRA and collect field-id map"""
-        try:
-            resp = get_jira(JIRA_BASE_URL + "/rest/api/2/field")
-            data = json.loads(resp.read())
-        except (urllib2.HTTPError, urllib2.URLError, httplib.BadStatusLine, ValueError):
-            sys.exit(1)
-        field_id_map = {}
-        for part in data:
-            field_id_map[part['name']] = part['id']
-        return field_id_map
-
-    @staticmethod
-    def query_jira(ver, projects, pos):
-        """send a query to JIRA and collect
-        a certain number of issue information"""
-        count = 100
-        pjs = "','".join(projects)
-        jql = "project in ('%s') and \
-               fixVersion in ('%s') and \
-               resolution = Fixed" % (pjs, ver)
-        params = urllib.urlencode({'jql': jql,
-                                   'startAt': pos,
-                                   'maxResults': count})
-        return JiraIter.load_jira(params, 0)
-
-    @staticmethod
-    def load_jira(params, fail_count):
-        """send query to JIRA and collect with retries"""
-        try:
-            resp = get_jira(JIRA_BASE_URL + "/rest/api/2/search?%s" % params)
-        except (urllib2.URLError, httplib.BadStatusLine) as err:
-            return JiraIter.retry_load(err, params, fail_count)
-
-        try:
-            data = json.loads(resp.read())
-        except httplib.IncompleteRead as err:
-            return JiraIter.retry_load(err, params, fail_count)
-        return data
-
-    @staticmethod
-    def retry_load(err, params, fail_count):
-        """Retry connection up to NUM_RETRIES times."""
-        print(err)
-        fail_count += 1
-        if fail_count <= NUM_RETRIES:
-            print "Connection failed %d times. Retrying." % (fail_count)
-            sleep(1)
-            return JiraIter.load_jira(params, fail_count)
-        else:
-            print "Connection failed %d times. Aborting." % (fail_count)
-            sys.exit(1)
-
-    @staticmethod
-    def collect_jiras(ver, projects):
-        """send queries to JIRA and collect all issues
-        that belongs to given version and projects"""
-        jiras = []
-        pos = 0
-        end = 1
-        while pos < end:
-            data = JiraIter.query_jira(ver, projects, pos)
-            if 'error_messages' in data:
-                print "JIRA returns error message: %s" % data['error_messages']
-                sys.exit(1)
-            pos = data['startAt'] + data['maxResults']
-            end = data['total']
-            jiras.extend(data['issues'])
-
-            if ver not in RELEASE_VERSION:
-                for issue in data['issues']:
-                    for fix_version in issue['fields']['fixVersions']:
-                        if 'releaseDate' in fix_version:
-                            RELEASE_VERSION[fix_version['name']] = fix_version[
-                                'releaseDate']
-        return jiras
-
-    def __init__(self, version, projects):
-        self.version = version
-        self.projects = projects
-        self.field_id_map = JiraIter.collect_fields()
-        ver = str(version).replace("-SNAPSHOT", "")
-        self.jiras = JiraIter.collect_jiras(ver, projects)
-        self.iter = self.jiras.__iter__()
-
-    def __iter__(self):
-        return self
-
-    def next(self):
-        data = self.iter.next()
-        j = Jira(data, self)
-        return j
-
-
-class Linter(object):
-    """Encapsulates lint-related functionality.
-    Maintains running lint statistics about JIRAs."""
-
-    _valid_filters = ["incompatible", "important", "version", "component",
-                      "assignee"]
-
-    def __init__(self, version, options):
-        self._warning_count = 0
-        self._error_count = 0
-        self._lint_message = ""
-        self._version = version
-
-        self._filters = dict(zip(self._valid_filters, [False] * len(
-            self._valid_filters)))
-
-        self.enabled = False
-        self._parse_options(options)
-
-    @staticmethod
-    def add_parser_options(parser):
-        """Add Linter options to passed optparse parser."""
-        filter_string = ", ".join("'" + f + "'" for f in Linter._valid_filters)
-        parser.add_option(
-            "-n",
-            "--lint",
-            dest="lint",
-            action="append",
-            type="string",
-            help="Specify lint filters. Valid filters are " + filter_string +
-            ". " + "'all' enables all lint filters. " +
-            "Multiple filters can be specified comma-delimited and " +
-            "filters can be negated, e.g. 'all,-component'.")
-
-    def _parse_options(self, options):
-        """Parse options from optparse."""
-
-        if options.lint is None or not options.lint:
-            return
-        self.enabled = True
-
-        # Valid filter specifications are
-        # self._valid_filters, negations, and "all"
-        valid_list = self._valid_filters
-        valid_list += ["-" + v for v in valid_list]
-        valid_list += ["all"]
-        valid = set(valid_list)
-
-        enabled = []
-        disabled = []
-
-        for o in options.lint:
-            for token in o.split(","):
-                if token not in valid:
-                    print "Unknown lint filter '%s', valid options are: %s" % \
-                            (token, ", ".join(v for v in sorted(valid)))
-                    sys.exit(1)
-                if token.startswith("-"):
-                    disabled.append(token[1:])
-                else:
-                    enabled.append(token)
-
-        for e in enabled:
-            if e == "all":
-                for f in self._valid_filters:
-                    self._filters[f] = True
-            else:
-                self._filters[e] = True
-        for d in disabled:
-            self._filters[d] = False
-
-    def had_errors(self):
-        """Returns True if a lint error was encountered, else False."""
-        return self._error_count > 0
-
-    def message(self):
-        """Return summary lint message suitable for printing to stdout."""
-        if not self.enabled:
-            return None
-        return self._lint_message + \
-               "\n=======================================" + \
-               "\n%s: Error:%d, Warning:%d \n" % \
-               (self._version, self._error_count, self._warning_count)
-
-    def _check_missing_component(self, jira):
-        """Return if JIRA has a 'missing component' lint error."""
-        if not self._filters["component"]:
-            return False
-
-        if jira.fields['components']:
-            return False
-        return True
-
-    def _check_missing_assignee(self, jira):
-        """Return if JIRA has a 'missing assignee' lint error."""
-        if not self._filters["assignee"]:
-            return False
-
-        if jira.fields['assignee'] is not None:
-            return False
-        return True
-
-    def _check_version_string(self, jira):
-        """Return if JIRA has a version string lint error."""
-        if not self._filters["version"]:
-            return False
-
-        field = jira.parent.field_id_map['Fix Version/s']
-        for ver in jira.fields[field]:
-            found = re.match(r'^((\d+)(\.\d+)*).*$|^(\w+\-\d+)$', ver['name'])
-            if not found:
-                return True
-        return False
-
-    def lint(self, jira):
-        """Run lint check on a JIRA."""
-        if not self.enabled:
-            return
-        if not jira.get_release_note():
-            if self._filters["incompatible"] and jira.get_incompatible_change():
-                self._warning_count += 1
-                self._lint_message += "\nWARNING: incompatible change %s lacks release notes." % \
-                                (sanitize_text(jira.get_id()))
-            if self._filters["important"] and jira.get_important():
-                self._warning_count += 1
-                self._lint_message += "\nWARNING: important issue %s lacks release notes." % \
-                                (sanitize_text(jira.get_id()))
-
-        if self._check_version_string(jira):
-            self._warning_count += 1
-            self._lint_message += "\nWARNING: Version string problem for %s " % jira.get_id(
-            )
-
-        if self._check_missing_component(jira) or self._check_missing_assignee(
-                jira):
-            self._error_count += 1
-            error_message = []
-            if self._check_missing_component(jira):
-                error_message.append("component")
-            if self._check_missing_assignee(jira):
-                error_message.append("assignee")
-            self._lint_message += "\nERROR: missing %s for %s " \
-                            % (" and ".join(error_message), jira.get_id())
-
-
-def parse_args():
-    """Parse command-line arguments with optparse."""
-    usage = "usage: %prog [OPTIONS] " + \
-            "--project PROJECT [--project PROJECT] " + \
-            "--version VERSION [--version VERSION2 ...]"
-    parser = OptionParser(
-        usage=usage,
-        epilog=
-        "Markdown-formatted CHANGELOG and RELEASENOTES files will be stored"
-        " in a directory named after the highest version provided.")
-    parser.add_option("--dirversions",
-                      dest="versiondirs",
-                      action="store_true",
-                      default=False,
-                      help="Put files in versioned directories")
-    parser.add_option("--fileversions",
-                      dest="versionfiles",
-                      action="store_true",
-                      default=False,
-                      help="Write files with embedded versions")
-    parser.add_option("-i",
-                      "--index",
-                      dest="index",
-                      action="store_true",
-                      default=False,
-                      help="build an index file")
-    parser.add_option("-l",
-                      "--license",
-                      dest="license",
-                      action="store_true",
-                      default=False,
-                      help="Add an ASF license")
-    parser.add_option("-p",
-                      "--project",
-                      dest="projects",
-                      action="append",
-                      type="string",
-                      help="projects in JIRA to include in releasenotes",
-                      metavar="PROJECT")
-    parser.add_option("-r",
-                      "--range",
-                      dest="range",
-                      action="store_true",
-                      default=False,
-                      help="Given versions are a range")
-    parser.add_option(
-        "--sortorder",
-        dest="sortorder",
-        metavar="TYPE",
-        default=SORTORDER,
-        # dec is supported for backward compatibility
-        choices=["asc", "dec", "desc", "newer", "older"],
-        help="Sorting order for sort type (default: %s)" % SORTORDER)
-    parser.add_option("--sorttype",
-                      dest="sorttype",
-                      metavar="TYPE",
-                      default=SORTTYPE,
-                      choices=["resolutiondate", "issueid"],
-                      help="Sorting type for issues (default: %s)" % SORTTYPE)
-    parser.add_option(
-        "-t",
-        "--projecttitle",
-        dest="title",
-        type="string",
-        help="Title to use for the project (default is Apache PROJECT)")
-    parser.add_option("-u",
-                      "--usetoday",
-                      dest="usetoday",
-                      action="store_true",
-                      default=False,
-                      help="use current date for unreleased versions")
-    parser.add_option("-v",
-                      "--version",
-                      dest="versions",
-                      action="append",
-                      type="string",
-                      help="versions in JIRA to include in releasenotes",
-                      metavar="VERSION")
-    parser.add_option(
-        "-V",
-        dest="release_version",
-        action="store_true",
-        default=False,
-        help="display version information for releasedocmaker and exit.")
-    parser.add_option("-O",
-                      "--outputdir",
-                      dest="output_directory",
-                      action="append",
-                      type="string",
-                      help="specify output directory to put release docs to.")
-    parser.add_option("-B",
-                      "--baseurl",
-                      dest="base_url",
-                      action="append",
-                      type="string",
-                      help="specify base URL of the JIRA instance.")
-    parser.add_option(
-        "--retries",
-        dest="retries",
-        action="append",
-        type="int",
-        help="Specify how many times to retry connection for each URL.")
-    parser.add_option(
-        "--skip-credits",
-        dest="skip_credits",
-        action="store_true",
-        default=False,
-        help="While creating release notes skip the 'reporter' and 'contributor' columns")
-    parser.add_option("-X",
-                      "--incompatiblelabel",
-                      dest="incompatible_label",
-                      default="backward-incompatible",
-                      type="string",
-                      help="Specify the label to indicate backward incompatibility.")
-
-    Linter.add_parser_options(parser)
-
-    if len(sys.argv) <= 1:
-        parser.print_help()
-        sys.exit(1)
-
-    (options, _) = parser.parse_args()
-
-    # Handle the version string right away and exit
-    if options.release_version:
-        with open(
-                os.path.join(
-                    os.path.dirname(__file__), "../VERSION"), 'r') as ver_file:
-            print ver_file.read()
-        sys.exit(0)
-
-    # Validate options
-    if not options.release_version:
-        if options.versions is None:
-            parser.error("At least one version needs to be supplied")
-        if options.projects is None:
-            parser.error("At least one project needs to be supplied")
-        if options.base_url is not None:
-            if len(options.base_url) > 1:
-                parser.error("Only one base URL should be given")
-            else:
-                options.base_url = options.base_url[0]
-        if options.output_directory is not None:
-            if len(options.output_directory) > 1:
-                parser.error("Only one output directory should be given")
-            else:
-                options.output_directory = options.output_directory[0]
-
-    if options.range or len(options.versions) > 1:
-      if not options.versiondirs and not options.versionfiles:
-        parser.error("Multiple versions require either --fileversions or --dirversions")
-
-    return options
-
-
-def main():
-    options = parse_args()
-
-    if options.output_directory is not None:
-        # Create the output directory if it does not exist.
-        try:
-            os.makedirs(options.output_directory)
-        except OSError as exc:
-            if exc.errno == errno.EEXIST and os.path.isdir(
-                    options.output_directory):
-                pass
-            else:
-                print "Unable to create output directory %s: %s" % \
-                        (options.output_directory, exc.message)
-                sys.exit(1)
-        os.chdir(options.output_directory)
-
-    if options.base_url is not None:
-        global JIRA_BASE_URL
-        JIRA_BASE_URL = options.base_url
-
-    if options.incompatible_label is not None:
-        global BACKWARD_INCOMPATIBLE_LABEL
-        BACKWARD_INCOMPATIBLE_LABEL = options.incompatible_label
-
-
-    projects = options.projects
-
-    if options.range is True:
-        versions = [Version(v)
-                    for v in GetVersions(options.versions, projects).getlist()]
-    else:
-        versions = [Version(v) for v in options.versions]
-    versions.sort()
-
-    global SORTTYPE
-    SORTTYPE = options.sorttype
-    global SORTORDER
-    SORTORDER = options.sortorder
-
-    if options.title is None:
-        title = projects[0]
-    else:
-        title = options.title
-
-    if options.retries is not None:
-        global NUM_RETRIES
-        NUM_RETRIES = options.retries[0]
-
-    haderrors = False
-
-    for version in versions:
-        vstr = str(version)
-        linter = Linter(vstr, options)
-        jlist = sorted(JiraIter(vstr, projects))
-        if not jlist:
-            print "There is no issue which has the specified version: %s" % version
-            continue
-
-        if vstr in RELEASE_VERSION:
-            reldate = RELEASE_VERSION[vstr]
-        elif options.usetoday:
-            reldate = strftime("%Y-%m-%d", gmtime())
-        else:
-            reldate = "Unreleased (as of %s)" % strftime("%Y-%m-%d", gmtime())
-
-        if not os.path.exists(vstr) and options.versiondirs:
-            os.mkdir(vstr)
-
-        if options.versionfiles and options.versiondirs:
-          reloutputs = Outputs("%(ver)s/RELEASENOTES.%(ver)s.md",
-                               "%(ver)s/RELEASENOTES.%(key)s.%(ver)s.md", [],
-                               {"ver": version,
-                                "date": reldate,
-                                "title": title})
-          choutputs = Outputs("%(ver)s/CHANGELOG.%(ver)s.md",
-                              "%(ver)s/CHANGELOG.%(key)s.%(ver)s.md", [],
-                              {"ver": version,
-                               "date": reldate,
-                               "title": title})
-        elif options.versiondirs:
-          reloutputs = Outputs("%(ver)s/RELEASENOTES.md",
-                               "%(ver)s/RELEASENOTES.%(key)s.md", [],
-                               {"ver": version,
-                                "date": reldate,
-                                "title": title})
-          choutputs = Outputs("%(ver)s/CHANGELOG.md",
-                              "%(ver)s/CHANGELOG.%(key)s.md", [],
-                              {"ver": version,
-                               "date": reldate,
-                               "title": title})
-        elif options.versionfiles:
-          reloutputs = Outputs("RELEASENOTES.%(ver)s.md",
-                               "RELEASENOTES.%(key)s.%(ver)s.md", [],
-                               {"ver": version,
-                                "date": reldate,
-                                "title": title})
-          choutputs = Outputs("CHANGELOG.%(ver)s.md",
-                              "CHANGELOG.%(key)s.%(ver)s.md", [],
-                              {"ver": version,
-                               "date": reldate,
-                               "title": title})
-        else:
-          reloutputs = Outputs("RELEASENOTES.md",
-                               "RELEASENOTES.%(key)s.md", [],
-                               {"ver": version,
-                                "date": reldate,
-                                "title": title})
-          choutputs = Outputs("CHANGELOG.md",
-                              "CHANGELOG.%(key)s.md", [],
-                              {"ver": version,
-                               "date": reldate,
-                               "title": title})
-
-        if options.license is True:
-            reloutputs.write_all(ASF_LICENSE)
-            choutputs.write_all(ASF_LICENSE)
-
-        relhead = '# %(title)s %(key)s %(ver)s Release Notes\n\n' \
-                  'These release notes cover new developer and user-facing ' \
-                  'incompatibilities, important issues, features, and major improvements.\n\n'
-        chhead = '# %(title)s Changelog\n\n' \
-                 '## Release %(ver)s - %(date)s\n'\
-                 '\n'
-
-        reloutputs.write_all(relhead)
-        choutputs.write_all(chhead)
-
-        incompatlist = []
-        importantlist = []
-        buglist = []
-        improvementlist = []
-        newfeaturelist = []
-        subtasklist = []
-        tasklist = []
-        testlist = []
-        otherlist = []
-
-        for jira in jlist:
-            if jira.get_incompatible_change():
-                incompatlist.append(jira)
-            elif jira.get_important():
-                importantlist.append(jira)
-            elif jira.get_type() == "Bug":
-                buglist.append(jira)
-            elif jira.get_type() == "Improvement":
-                improvementlist.append(jira)
-            elif jira.get_type() == "New Feature":
-                newfeaturelist.append(jira)
-            elif jira.get_type() == "Sub-task":
-                subtasklist.append(jira)
-            elif jira.get_type() == "Task":
-                tasklist.append(jira)
-            elif jira.get_type() == "Test":
-                testlist.append(jira)
-            else:
-                otherlist.append(jira)
-
-            line = '* [%s](' % (sanitize_text(jira.get_id())) + JIRA_BASE_URL + \
-                   '/browse/%s) | *%s* | **%s**\n' \
-                   % (sanitize_text(jira.get_id()),
-                      sanitize_text(jira.get_priority()), sanitize_text(jira.get_summary()))
-
-            if jira.get_release_note() or \
-               jira.get_incompatible_change() or jira.get_important():
-                reloutputs.write_key_raw(jira.get_project(), "\n---\n\n")
-                reloutputs.write_key_raw(jira.get_project(), line)
-                if not jira.get_release_note():
-                    line = '\n**WARNING: No release note provided for this change.**\n\n'
-                else:
-                    line = '\n%s\n\n' % (
-                        processrelnote(jira.get_release_note()))
-                reloutputs.write_key_raw(jira.get_project(), line)
-
-            linter.lint(jira)
-
-        if linter.enabled:
-            print linter.message()
-            if linter.had_errors():
-                haderrors = True
-                shutil.rmtree(vstr)
-                continue
-
-        reloutputs.write_all("\n\n")
-        reloutputs.close()
-
-        if options.skip_credits:
-            CHANGEHDR1 = "| JIRA | Summary | Priority | " + \
-                     "Component |\n"
-            CHANGEHDR2 = "|:---- |:---- | :--- |:---- |\n"
-        else:
-            CHANGEHDR1 = "| JIRA | Summary | Priority | " + \
-                         "Component | Reporter | Contributor |\n"
-            CHANGEHDR2 = "|:---- |:---- | :--- |:---- |:---- |:---- |\n"
-
-        if incompatlist:
-            choutputs.write_all("### INCOMPATIBLE CHANGES:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(incompatlist, options.skip_credits, JIRA_BASE_URL)
-
-        if importantlist:
-            choutputs.write_all("\n\n### IMPORTANT ISSUES:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(importantlist, options.skip_credits, JIRA_BASE_URL)
-
-        if newfeaturelist:
-            choutputs.write_all("\n\n### NEW FEATURES:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(newfeaturelist, options.skip_credits, JIRA_BASE_URL)
-
-        if improvementlist:
-            choutputs.write_all("\n\n### IMPROVEMENTS:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(improvementlist, options.skip_credits, JIRA_BASE_URL)
-
-        if buglist:
-            choutputs.write_all("\n\n### BUG FIXES:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(buglist, options.skip_credits, JIRA_BASE_URL)
-
-        if testlist:
-            choutputs.write_all("\n\n### TESTS:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(testlist, options.skip_credits, JIRA_BASE_URL)
-
-        if subtasklist:
-            choutputs.write_all("\n\n### SUB-TASKS:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(subtasklist, options.skip_credits, JIRA_BASE_URL)
-
-        if tasklist or otherlist:
-            choutputs.write_all("\n\n### OTHER:\n\n")
-            choutputs.write_all(CHANGEHDR1)
-            choutputs.write_all(CHANGEHDR2)
-            choutputs.write_list(otherlist, options.skip_credits, JIRA_BASE_URL)
-            choutputs.write_list(tasklist, options.skip_credits, JIRA_BASE_URL)
-
-        choutputs.write_all("\n\n")
-        choutputs.close()
-
-    if options.index:
-        buildindex(title, options.license)
-        buildreadme(title, options.license)
-
-    if haderrors is True:
-        sys.exit(1)
-
-
-if __name__ == "__main__":
-    main()

http://git-wip-us.apache.org/repos/asf/yetus/blob/6ebaa111/release-doc-maker/utils.py
----------------------------------------------------------------------
diff --git a/release-doc-maker/utils.py b/release-doc-maker/utils.py
deleted file mode 100644
index db957d3..0000000
--- a/release-doc-maker/utils.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/usr/bin/env python2
-#
-# 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.
-
-import base64
-import os
-import re
-import urllib2
-import sys
-import json
-import httplib
-sys.dont_write_bytecode = True
-
-NAME_PATTERN = re.compile(r' \([0-9]+\)')
-
-def clean(input_string):
-    return sanitize_markdown(re.sub(NAME_PATTERN, "", input_string))
-
-
-def get_jira(jira_url):
-    """ Provide standard method for fetching content from apache jira and
-        handling of potential errors. Returns urllib2 response or
-        raises one of several exceptions."""
-
-    username = os.environ.get('RDM_JIRA_USERNAME')
-    password = os.environ.get('RDM_JIRA_PASSWORD')
-
-    req = urllib2.Request(jira_url)
-    if username and password:
-        basicauth = base64.encodestring("%s:%s" % (username, password)).replace('\n', '')
-        req.add_header('Authorization', 'Basic %s' % basicauth)
-
-    try:
-        response = urllib2.urlopen(req)
-    except urllib2.HTTPError as http_err:
-        code = http_err.code
-        print "JIRA returns HTTP error %d: %s. Aborting." % \
-              (code, http_err.msg)
-        error_response = http_err.read()
-        try:
-            error_response = json.loads(error_response)
-            print "- Please ensure that specified authentication, projects,"\
-                  " fixVersions etc. are correct."
-            for message in error_response['errorMessages']:
-                print "-", message
-        except ValueError:
-            print "FATAL: Could not parse json response from server."
-        sys.exit(1)
-    except urllib2.URLError as url_err:
-        print "Error contacting JIRA: %s\n" % jira_url
-        print "Reason: %s" % url_err.reason
-        raise url_err
-    except httplib.BadStatusLine as err:
-        raise err
-    return response
-
-
-def format_components(input_string):
-    input_string = re.sub(NAME_PATTERN, '', input_string).replace("'", "")
-    if input_string != "":
-        ret = input_string
-    else:
-        # some markdown parsers don't like empty tables
-        ret = "."
-    return clean(ret)
-
-
-# Return the string encoded as UTF-8.
-#
-# This is necessary for handling markdown in Python.
-def encode_utf8(input_string):
-    return input_string.encode('utf-8')
-
-
-# Sanitize Markdown input so it can be handled by Python.
-#
-# The expectation is that the input is already valid Markdown,
-# so no additional escaping is required.
-def sanitize_markdown(input_string):
-    input_string = encode_utf8(input_string)
-    input_string = input_string.replace("\r", "")
-    input_string = input_string.rstrip()
-    return input_string
-
-
-# Sanitize arbitrary text so it can be embedded in MultiMarkdown output.
-#
-# Note that MultiMarkdown is not Markdown, and cannot be parsed as such.
-# For instance, when using pandoc, invoke it as `pandoc -f markdown_mmd`.
-#
-# Calls sanitize_markdown at the end as a final pass.
-def sanitize_text(input_string):
-    escapes = dict()
-    # See: https://daringfireball.net/projects/markdown/syntax#backslash
-    # We only escape a subset of special characters. We ignore characters
-    # that only have significance at the start of a line.
-    slash_escapes = "_<>*|"
-    slash_escapes += "`"
-    slash_escapes += "\\"
-    all_chars = set()
-    # Construct a set of escapes
-    for c in slash_escapes:
-        all_chars.add(c)
-    for c in all_chars:
-        escapes[c] = "\\" + c
-
-    # Build the output string character by character to prevent double escaping
-    output_string = ""
-    for c in input_string:
-        o = c
-        if c in escapes:
-            o = escapes[c]
-        output_string += o
-
-    return sanitize_markdown(output_string.rstrip())
-
-
-# if release notes have a special marker,
-# we'll treat them as already in markdown format
-def processrelnote(input_string):
-    relnote_pattern = re.compile('^\<\!\-\- ([a-z]+) \-\-\>')
-    fmt = relnote_pattern.match(input_string)
-    if fmt is None:
-        return sanitize_text(input_string)
-    return {
-        'markdown': sanitize_markdown(input_string),
-    }.get(fmt.group(1), sanitize_text(input_string))
-
-
-def to_unicode(obj):
-    if obj is None:
-        return ""
-    return unicode(obj)
-
-
-class Outputs(object):
-    """Several different files to output to at the same time"""
-
-    def __init__(self, base_file_name, file_name_pattern, keys, params=None):
-        if params is None:
-            params = {}
-        self.params = params
-        self.base = open(base_file_name % params, 'w')
-        self.others = {}
-        for key in keys:
-            both = dict(params)
-            both['key'] = key
-            self.others[key] = open(file_name_pattern % both, 'w')
-
-    def write_all(self, pattern):
-        both = dict(self.params)
-        both['key'] = ''
-        self.base.write(pattern % both)
-        for key in self.others:
-            both = dict(self.params)
-            both['key'] = key
-            self.others[key].write(pattern % both)
-
-    def write_key_raw(self, key, input_string):
-        self.base.write(input_string)
-        if key in self.others:
-            self.others[key].write(input_string)
-
-    def close(self):
-        self.base.close()
-        for value in self.others.values():
-            value.close()
-
-    def write_list(self, mylist, skip_credits, base_url):
-        """ Take a Jira object and write out the relevants parts in a multimarkdown table line"""
-        for jira in sorted(mylist):
-            if skip_credits:
-                line = '| [{id}]({base_url}/browse/{id}) | {summary} |  ' \
-                       '{priority} | {component} |\n'
-            else:
-                line = '| [{id}]({base_url}/browse/{id}) | {summary} |  ' \
-                       '{priority} | {component} | {reporter} | {assignee} |\n'
-            args = {'id': encode_utf8(jira.get_id()),
-                    'base_url': base_url,
-                    'summary': sanitize_text(jira.get_summary()),
-                    'priority': sanitize_text(jira.get_priority()),
-                    'component': format_components(jira.get_components()),
-                    'reporter': sanitize_text(jira.get_reporter()),
-                    'assignee': sanitize_text(jira.get_assignee())
-                   }
-            line = line.format(**args)
-            self.write_key_raw(jira.get_project(), line)

http://git-wip-us.apache.org/repos/asf/yetus/blob/6ebaa111/releasedocmaker/pom.xml
----------------------------------------------------------------------
diff --git a/releasedocmaker/pom.xml b/releasedocmaker/pom.xml
new file mode 100644
index 0000000..75d541d
--- /dev/null
+++ b/releasedocmaker/pom.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+                      http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.yetus</groupId>
+    <artifactId>yetus-project</artifactId>
+    <version>0.9.0-SNAPSHOT</version>
+    <relativePath>..</relativePath>
+  </parent>
+  <artifactId>releasedocmaker</artifactId>
+  <description>JIRA-based Changelog and Release Notes Generator</description>
+  <name>Apache Yetus - releasedocmaker</name>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.scijava</groupId>
+      <artifactId>jython-shaded</artifactId>
+      <version>${jython-shaded.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/python</directory>
+      </resource>
+    </resources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.yetus</groupId>
+            <artifactId>yetus-assemblies</artifactId>
+            <version>${project.version}</version>
+          </dependency>
+        </dependencies>
+        <executions>
+          <execution>
+            <id>pre-dist</id>
+            <phase>prepare-package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <finalName>dist/apache-yetus-${project.version}</finalName>
+              <appendAssemblyId>false</appendAssemblyId>
+              <attach>false</attach>
+              <descriptors>
+                <descriptor>src/main/assemblies/${project.artifactId}.xml</descriptor>
+              </descriptors>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>module-dist</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <appendAssemblyId>false</appendAssemblyId>
+              <attach>true</attach>
+              <descriptorRefs>
+                <descriptorRef>module-dist</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>make-jar-assembly</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+            <configuration>
+              <archive>
+                <manifest>
+                  <mainClass>org.apache.yetus.releasedocmaker.ReleaseDocMaker</mainClass>
+                </manifest>
+              </archive>
+              <descriptorRefs>
+                <descriptorRef>jar-with-dependencies</descriptorRef>
+              </descriptorRefs>
+            </configuration>
+          </execution>
+
+        </executions>
+      </plugin>
+
+      <plugin>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>net.sf.mavenjython</groupId>
+        <artifactId>jython-compile-maven-plugin</artifactId>
+        <version>${jython-compile-maven-plugin.version}</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>jython</goal>
+            </goals>
+            <configuration>
+              <libraries>
+                <param>--index-url=https://pypi.python.org/simple/</param>
+                <param>python-dateutil</param>
+              </libraries>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/yetus/blob/6ebaa111/releasedocmaker/src/main/assemblies/releasedocmaker.xml
----------------------------------------------------------------------
diff --git a/releasedocmaker/src/main/assemblies/releasedocmaker.xml b/releasedocmaker/src/main/assemblies/releasedocmaker.xml
new file mode 100644
index 0000000..af5eedc
--- /dev/null
+++ b/releasedocmaker/src/main/assemblies/releasedocmaker.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+  <id>releasedocmaker</id>
+  <formats>
+    <format>dir</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <directory>${basedir}/src/main/python</directory>
+      <outputDirectory>lib/releasedocmaker</outputDirectory>
+      <includes>
+        <include>**</include>
+      </includes>
+      <fileMode>0755</fileMode>
+    </fileSet>
+    <fileSet>
+      <directory>${basedir}/src/main/shell</directory>
+      <outputDirectory>bin</outputDirectory>
+      <includes>
+        <include>*</include>
+      </includes>
+      <fileMode>0755</fileMode>
+    </fileSet>
+  </fileSets>
+</assembly>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/yetus/blob/6ebaa111/releasedocmaker/src/main/java/org/apache/yetus/releasedocmaker/ReleaseDocMaker.java
----------------------------------------------------------------------
diff --git a/releasedocmaker/src/main/java/org/apache/yetus/releasedocmaker/ReleaseDocMaker.java b/releasedocmaker/src/main/java/org/apache/yetus/releasedocmaker/ReleaseDocMaker.java
new file mode 100644
index 0000000..5633180
--- /dev/null
+++ b/releasedocmaker/src/main/java/org/apache/yetus/releasedocmaker/ReleaseDocMaker.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+package org.apache.yetus.releasedocmaker;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.python.core.Py;
+import org.python.core.PyException;
+import org.python.core.PySystemState;
+import org.python.util.PythonInterpreter;
+
+public class ReleaseDocMaker {
+  public static void main(final String[] args) throws PyException {
+    List<String> list = new LinkedList<String>(Arrays.asList(args));
+    list.add(0,"releasedocmaker");
+    String[] newargs = list.toArray(new String[list.size()]);
+    PythonInterpreter.initialize(System.getProperties(), System.getProperties(), newargs);
+    PySystemState systemState = Py.getSystemState();
+    PythonInterpreter interpreter = new PythonInterpreter();
+    systemState.__setattr__("_jy_interpreter", Py.java2py(interpreter));
+    String command = "try:\n "
+                     + "  import releasedocmaker\n "
+                     + "  releasedocmaker.main()\n"
+                     + "except "
+                     + "  SystemExit: pass";
+    interpreter.exec(command);
+  }
+}

http://git-wip-us.apache.org/repos/asf/yetus/blob/6ebaa111/releasedocmaker/src/main/python/releasedocmaker.py
----------------------------------------------------------------------
diff --git a/releasedocmaker/src/main/python/releasedocmaker.py b/releasedocmaker/src/main/python/releasedocmaker.py
new file mode 100755
index 0000000..63c2e00
--- /dev/null
+++ b/releasedocmaker/src/main/python/releasedocmaker.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python2
+#
+# 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.
+
+import sys
+sys.dont_write_bytecode = True
+# pylint: disable=wrong-import-position,import-self
+import releasedocmaker
+#pylint: disable=no-member
+releasedocmaker.main()