Example #1
0
def validate_version(base_version, vcs_info):
    branch = vcs_info.branch

    brnorm = utils.normalize_branch_name(branch)
    btypestr = utils.get_branch_type(branch)

    try:
        btype = BRANCH_TYPES[btypestr]
    except KeyError:
        allowed_branches = ", ".join(x for x in BRANCH_TYPES.keys())
        raise ValueError("Malformed branch name '%s', cannot classify as one "
                         "of %s" % (btypestr, allowed_branches))

    if btype.versioned:
        try:
            bverstr = brnorm.split("-")[1]
        except IndexError:
            # No version
            raise ValueError("Branch name '%s' should contain version" %
                             branch)

        # Check that version is well-formed
        if not re.match(VERSION_RE, bverstr):
            raise ValueError("Malformed version '%s' in branch name '%s'" %
                             (bverstr, branch))

    m = re.match(btype.allowed_version_re, base_version)
    if not m or (btype.versioned and m.groupdict()["bverstr"] != bverstr):
        raise ValueError("Base version '%s' unsuitable for branch name '%s'" %
                         (base_version, branch))
Example #2
0
def get_build_mode():
    """Determine the build mode"""
    # Get it from environment if exists
    mode = os.environ.get("DEVFLOW_BUILD_MODE", None)
    if mode is None:
        branch = get_branch_type(get_vcs_info().branch)
        try:
            br_type = BRANCH_TYPES[get_branch_type(branch)]
        except KeyError:
            allowed_branches = ", ".join(x for x in BRANCH_TYPES.keys())
            raise ValueError("Malformed branch name '%s', cannot classify as"
                             " one of %s" % (branch, allowed_branches))
        mode = "snapshot" if br_type.builds_snapshot else "release"
    return mode
Example #3
0
def main():
    from devflow.version import __version__  # pylint: disable=E0611,F0401
    parser = OptionParser(usage="usage: %prog [options] mode",
                          version="devflow %s" % __version__,
                          add_help_option=False)
    parser.add_option("-h", "--help",
                      action="store_true",
                      default=False,
                      help="show this help message")
    parser.add_option("-k", "--keep-repo",
                      action="store_true",
                      dest="keep_repo",
                      default=False,
                      help="Do not delete the cloned repository")
    parser.add_option("-b", "--build-dir",
                      dest="build_dir",
                      default=None,
                      help="Directory to store created pacakges")
    parser.add_option("-r", "--repo-dir",
                      dest="repo_dir",
                      default=None,
                      help="Directory to clone repository")
    parser.add_option("-d", "--dirty",
                      dest="force_dirty",
                      default=False,
                      action="store_true",
                      help="Do not check if working directory is dirty")
    parser.add_option("-c", "--config-file",
                      dest="config_file",
                      help="Override default configuration file")
    parser.add_option("--no-sign",
                      dest="sign",
                      action="store_false",
                      default=True,
                      help="Do not sign the packages")
    parser.add_option("--key-id",
                      dest="keyid",
                      help="Use this keyid for gpg signing")
    parser.add_option("--dist",
                      dest="dist",
                      default=None,
                      help="Force distribution in Debian changelog")
    parser.add_option("-S", "--source-only",
                      dest="source_only",
                      default=False,
                      action="store_true",
                      help="Specifies a source-only build, no binary packages"
                           " need to be made.")
    parser.add_option("--debian-branch",
                      dest="debian_branch",
                      default=None,
                      help="Use this debian branch, instead of"
                           "auto-discovering the debian branch to use")
    parser.add_option("--push-back",
                      dest="push_back",
                      default=False,
                      action="store_true",
                      help="Automatically push branches and tags to repo.")
    parser.add_option("--color",
                      dest="color_output",
                      default="auto",
                      help="Enable/disable colored output. Default mode is" +
                           " auto, available options are yes/no")

    (options, args) = parser.parse_args()

    if options.color_output == "yes":
        use_colors = True
    elif options.color_output == "no":
        use_colors = False
    else:
        if sys.stdout.isatty():
            use_colors = True
        else:
            use_colors = False

    red = lambda x: x
    green = lambda x: x

    if use_colors:
        try:
            import colors
            red = colors.red
            green = colors.green
        except AttributeError:
            pass

    print_red = lambda x: sys.stdout.write(red(x) + "\n")
    print_green = lambda x: sys.stdout.write(green(x) + "\n")

    if options.help:
        print_help(parser.get_prog_name())
        parser.print_help()
        return

    # Get build mode
    try:
        mode = args[0]
    except IndexError:
        mode = utils.get_build_mode()
    if mode not in AVAILABLE_MODES:
        raise ValueError(red("Invalid argument! Mode must be one: %s"
                         % ", ".join(AVAILABLE_MODES)))

    # Load the repository
    original_repo = utils.get_repository()

    # Check that repository is clean
    toplevel = original_repo.working_dir
    if original_repo.is_dirty() and not options.force_dirty:
        raise RuntimeError(red("Repository %s is dirty." % toplevel))

    # Get packages from configuration file
    config = utils.get_config(options.config_file)
    packages = config['packages'].keys()
    print_green("Will build the following packages:\n" + "\n".join(packages))

    # Get current branch name and type and check if it is a valid one
    branch = original_repo.head.reference.name
    branch = utils.undebianize(branch)
    branch_type_str = utils.get_branch_type(branch)

    if branch_type_str not in BRANCH_TYPES.keys():
        allowed_branches = ", ".join(BRANCH_TYPES.keys())
        raise ValueError("Malformed branch name '%s', cannot classify as"
                         " one of %s" % (branch, allowed_branches))

    # Fix needed environment variables
    v = utils.get_vcs_info()
    os.environ["DEVFLOW_BUILD_MODE"] = mode
    os.environ["DEBFULLNAME"] = v.name
    os.environ["DEBEMAIL"] = v.email

    # Check that base version file and branch are correct
    versioning.get_python_version()

    # Get the debian branch
    if options.debian_branch:
        debian_branch = options.debian_branch
    else:
        debian_branch = utils.get_debian_branch(branch)
    origin_debian = "origin/" + debian_branch

    # Clone the repo
    repo_dir = options.repo_dir or create_temp_directory("df-repo")
    repo_dir = os.path.abspath(repo_dir)
    repo = original_repo.clone(repo_dir, branch=branch)
    print_green("Cloned repository to '%s'." % repo_dir)

    build_dir = options.build_dir or create_temp_directory("df-build")
    build_dir = os.path.abspath(build_dir)
    print_green("Build directory: '%s'" % build_dir)

    # Create the debian branch
    repo.git.branch(debian_branch, origin_debian)
    print_green("Created branch '%s' to track '%s'" % (debian_branch,
                origin_debian))

    # Go to debian branch
    repo.git.checkout(debian_branch)
    print_green("Changed to branch '%s'" % debian_branch)

    # Merge with starting branch
    repo.git.merge(branch)
    print_green("Merged branch '%s' into '%s'" % (branch, debian_branch))

    # Compute python and debian version
    cd(repo_dir)
    python_version = versioning.get_python_version()
    debian_version = versioning.\
        debian_version_from_python_version(python_version)
    print_green("The new debian version will be: '%s'" % debian_version)

    # Update the version files
    versioning.update_version()

    if not options.sign:
        sign_tag_opt = None
    elif options.keyid:
        sign_tag_opt = "-u=%s" % options.keyid
    elif mode == "release":
        sign_tag_opt = "-s"
    else:
        sign_tag_opt = None

    # Tag branch with python version
    branch_tag = python_version
    tag_message = "%s version %s" % (mode.capitalize(), python_version)
    try:
        repo.git.tag(branch_tag, branch, sign_tag_opt, "-m %s" % tag_message)
    except GitCommandError:
        # Tag may already exist, if only the debian branch has changed
        pass
    upstream_tag = "upstream/" + branch_tag
    repo.git.tag(upstream_tag, branch)

    # Update changelog
    dch = git_dch("--debian-branch=%s" % debian_branch,
                  "--git-author",
                  "--ignore-regex=\".*\"",
                  "--multimaint-merge",
                  "--since=HEAD",
                  "--new-version=%s" % debian_version)
    print_green("Successfully ran '%s'" % " ".join(dch.cmd))

    if options.dist is not None:
        distribution = options.dist
    elif mode == "release":
        distribution = utils.get_distribution_codename()
    else:
        distribution = "unstable"

    f = open("debian/changelog", 'r+')
    lines = f.readlines()
    lines[0] = lines[0].replace("UNRELEASED", distribution)
    lines[2] = lines[2].replace("UNRELEASED", "%s build" % mode)
    f.seek(0)
    f.writelines(lines)
    f.close()

    if mode == "release":
        call("vim debian/changelog")

    # Add changelog to INDEX
    repo.git.add("debian/changelog")
    # Commit Changes
    repo.git.commit("-s", "debian/changelog",
                    m="Bump version to %s" % debian_version)
    # Tag debian branch
    debian_branch_tag = "debian/" + utils.version_to_tag(debian_version)
    tag_message = "%s version %s" % (mode.capitalize(), debian_version)
    if mode == "release":
        repo.git.tag(debian_branch_tag, sign_tag_opt, "-m %s" % tag_message)

    # Create debian packages
    cd(repo_dir)
    version_files = []
    for _, pkg_info in config['packages'].items():
        if pkg_info.get("version_file"):
            version_files.extend(pkg_info.as_list('version_file'))

    # Add version.py files to repo
    repo.git.add("-f", *version_files)

    # Export version info to debuilg environment
    os.environ["DEB_DEVFLOW_DEBIAN_VERSION"] = debian_version
    os.environ["DEB_DEVFLOW_VERSION"] = python_version
    build_cmd = "git-buildpackage --git-export-dir=%s"\
                " --git-upstream-branch=%s --git-debian-branch=%s"\
                " --git-export=INDEX --git-ignore-new -sa"\
                " --source-option=--auto-commit"\
                " --git-upstream-tag=%s"\
                % (build_dir, branch, debian_branch, upstream_tag)
    if options.source_only:
        build_cmd += " -S"
    if not options.sign:
        build_cmd += " -uc -us"
    elif options.keyid:
        build_cmd += " -k\"'%s'\"" % options.keyid
    call(build_cmd)

    # Remove cloned repo
    if mode != 'release' and not options.keep_repo:
        print_green("Removing cloned repo '%s'." % repo_dir)
        rm("-r", repo_dir)

    # Print final info
    info = (("Version", debian_version),
            ("Upstream branch", branch),
            ("Upstream tag", branch_tag),
            ("Debian branch", debian_branch),
            ("Debian tag", debian_branch_tag),
            ("Repository directory", repo_dir),
            ("Packages directory", build_dir))
    print_green("\n".join(["%s: %s" % (name, val) for name, val in info]))

    # Print help message
    if mode == "release":
        origin = original_repo.remote().url
        repo.create_remote("original_origin", origin)
        print_green("Created remote 'original_origin' for the repository '%s'"
                    % origin)

        print_green("To update repositories '%s' and '%s' go to '%s' and run:"
                    % (toplevel, origin, repo_dir))
        for remote in ['origin', 'original_origin']:
            objects = [debian_branch, branch_tag, debian_branch_tag]
            print_green("git push %s %s" % (remote, " ".join(objects)))
        if options.push_back:
            objects = [debian_branch, branch_tag, debian_branch_tag]
            repo.git.push("origin", *objects)
            print_green("Automatically updated origin repo.")
Example #4
0
def python_version(base_version, vcs_info, mode):
    """Generate a Python distribution version following devtools conventions.

    This helper generates a Python distribution version from a repository
    commit, following devtools conventions. The input data are:
        * base_version: a base version number, presumably stored in text file
          inside the repository, e.g., /version
        * vcs_info: vcs information: current branch name and revision no
        * mode: "snapshot", or "release"

    This helper assumes a git branching model following:
    http://nvie.com/posts/a-successful-git-branching-model/

    with 'master', 'develop', 'release-X', 'hotfix-X' and 'feature-X' branches.

    General rules:
    a) any repository commit can get as a Python version
    b) a version is generated either in 'release' or in 'snapshot' mode
    c) the choice of mode depends on the branch, see following table.

    A python version is of the form A_NNN,
    where A: X.Y.Z{,next,rcW} and NNN: a revision number for the commit,
    as returned by vcs_info().

    For every combination of branch and mode, releases are numbered as follows:

    BRANCH:  /  MODE: snapshot        release
    --------          ------------------------------
    feature           0.14next_150    N/A
    develop           0.14next_151    N/A
    release           0.14rc2_249     0.14rc2
    master            N/A             0.14
    hotfix            0.14.1rc6_121   0.14.1rc6
                      N/A             0.14.1

    The suffix 'next' in a version name is used to denote the upcoming version,
    the one being under development in the develop and release branches.
    Version '0.14next' is the version following 0.14, and only lives on the
    develop and feature branches.

    The suffix 'rc' is used to denote release candidates. 'rc' versions live
    only in release and hotfix branches.

    Suffixes 'next' and 'rc' have been chosen to ensure proper ordering
    according to setuptools rules:

        http://www.python.org/dev/peps/pep-0386/#setuptools

    Every branch uses a value for A so that all releases are ordered based
    on the branch they came from, so:

    So
        0.13next < 0.14rcW < 0.14 < 0.14next < 0.14.1

    and

    >>> V("0.14next") > V("0.14")
    True
    >>> V("0.14next") > V("0.14rc7")
    True
    >>> V("0.14next") > V("0.14.1")
    False
    >>> V("0.14rc6") > V("0.14")
    False
    >>> V("0.14.2rc6") > V("0.14.1")
    True

    The value for _NNN is chosen based of the revision number of the specific
    commit. It is used to ensure ascending ordering of consecutive releases
    from the same branch. Every version of the form A_NNN comes *before*
    than A: All snapshots are ordered so they come before the corresponding
    release.

    So
        0.14next_* < 0.14
        0.14.1_* < 0.14.1
        etc

    and

    >>> V("0.14next_150") < V("0.14next")
    True
    >>> V("0.14.1next_150") < V("0.14.1next")
    True
    >>> V("0.14.1_149") < V("0.14.1")
    True
    >>> V("0.14.1_149") < V("0.14.1_150")
    True

    Combining both of the above, we get
       0.13next_* < 0.13next < 0.14rcW_* < 0.14rcW < 0.14_* < 0.14
       < 0.14next_* < 0.14next < 0.14.1_* < 0.14.1

    and

    >>> V("0.13next_102") < V("0.13next")
    True
    >>> V("0.13next") < V("0.14rc5_120")
    True
    >>> V("0.14rc3_120") < V("0.14rc3")
    True
    >>> V("0.14rc3") < V("0.14_1")
    True
    >>> V("0.14_120") < V("0.14")
    True
    >>> V("0.14") < V("0.14next_20")
    True
    >>> V("0.14next_20") < V("0.14next")
    True

    Note: one of the tests above fails because of constraints in the way
    setuptools parses version numbers. It does not affect us because the
    specific version format that triggers the problem is not contained in the
    table showing allowed branch / mode combinations, above.


    """

    branch = vcs_info.branch

    brnorm = utils.normalize_branch_name(branch)
    btypestr = utils.get_branch_type(branch)

    try:
        btype = BRANCH_TYPES[btypestr]
    except KeyError:
        allowed_branches = ", ".join(x for x in BRANCH_TYPES.keys())
        raise ValueError("Malformed branch name '%s', cannot classify as one " "of %s" % (btypestr, allowed_branches))

    if btype.versioned:
        try:
            bverstr = brnorm.split("-")[1]
        except IndexError:
            # No version
            raise ValueError("Branch name '%s' should contain version" % branch)

        # Check that version is well-formed
        if not re.match(VERSION_RE, bverstr):
            raise ValueError("Malformed version '%s' in branch name '%s'" % (bverstr, branch))

    m = re.match(btype.allowed_version_re, base_version)
    if not m or (btype.versioned and m.groupdict()["bverstr"] != bverstr):
        raise ValueError("Base version '%s' unsuitable for branch name '%s'" % (base_version, branch))

    if mode not in ["snapshot", "release"]:
        raise ValueError("Specified mode '%s' should be one of 'snapshot' or " "'release'" % mode)
    snap = mode == "snapshot"

    if (snap and not btype.builds_snapshot) or (not snap and not btype.builds_release):  # nopep8
        raise ValueError("Invalid mode '%s' in branch type '%s'" % (mode, btypestr))

    if snap:
        v = "%s_%d_%s" % (base_version, vcs_info.revno, vcs_info.revid)
    else:
        v = base_version
    return v