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))
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
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.")
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