#!/usr/bin/python3 -u """ ADOdb release build script - Create release tag if it does not exist - Copy release files to target directory - Generate zip/tar balls """ import errno import getopt import re import os from os import path import shutil import subprocess import sys import tempfile import updateversion # ADOdb Repository reference origin_repo = "https://github.com/ADOdb/ADOdb.git" release_branch = "master" release_prefix = "adodb" # Directories and files to exclude from release tarballs # For consistency, this should match the list in .gitattributes exclude_list = (".git*", ".mailmap", "replicate", "scripts", "tests", "cute_icons_for_site", ".idea", ) # Command-line options options = "hb:dfk" long_options = ["help", "branch=", "debug", "fresh", "keep"] # Global flags debug_mode = False fresh_clone = False cleanup = True def usage(): """ Print script's command-line arguments help. """ print('''Usage: {} [options] version release_path Parameters: version ADOdb version to bundle (e.g. v5.19) release_path Where to save the release tarballs Options: -h | --help Show this usage message -b | --branch Use specified branch (defaults to '{}' for '.0' releases, or 'hotfix/' for patches) -d | --debug Debug mode (ignores upstream: no fetch, allows build even if local branch is not in sync) -f | --fresh Create a fresh clone of the repository -k | --keep Keep build directories after completion (useful for debugging) '''.format( path.basename(__file__), release_branch )) # end usage() def set_version_and_tag(version): global release_branch, debug_mode, fresh_clone, cleanup # Delete existing tag to force creation in debug mode if debug_mode: try: updateversion.tag_delete(version) except subprocess.CalledProcessError: pass # Checkout release branch subprocess.call("git checkout {}".format(release_branch), shell=True) if not debug_mode: # Make sure we're up-to-date, ignore untracked files ret = subprocess.check_output( "git status --branch --porcelain --untracked-files=no", text=True, shell=True ) if not re.search(release_branch + "$", ret): print("\nERROR: branch must be aligned with upstream") sys.exit(4) # Update the code, create commit and tag updateversion.version_set(version) # Make sure we don't delete the modified repo if fresh_clone: cleanup = False def main(): global release_branch, debug_mode, fresh_clone, cleanup # Get command-line options try: opts, args = getopt.gnu_getopt(sys.argv[1:], options, long_options) except getopt.GetoptError as err: print(str(err)) usage() sys.exit(2) if len(args) < 2: usage() print("ERROR: please specify the version and release_path") sys.exit(1) for opt, val in opts: if opt in ("-h", "--help"): usage() sys.exit(0) elif opt in ("-b", "--branch"): release_branch = val elif opt in ("-d", "--debug"): debug_mode = True elif opt in ("-f", "--fresh"): fresh_clone = True elif opt in ("-k", "--keep"): cleanup = False # Mandatory parameters version = updateversion.version_check(args[0]) release_path = args[1] # Default release branch if updateversion.version_is_patch(version): release_branch = 'hotfix/' + version.rsplit('.', 1)[0] # ------------------------------------------------------------------------- # Start the build # global release_prefix print("Building ADOdb release {} into '{}'\n".format( version, release_path )) if debug_mode: print("DEBUG MODE: ignoring upstream repository status") if fresh_clone: # Create a new repo clone print("Cloning a new repository") repo_path = tempfile.mkdtemp(prefix=release_prefix + "-", suffix=".git") subprocess.call( "git clone {} {}".format(origin_repo, repo_path), shell=True ) os.chdir(repo_path) else: # Git repo's root directory repo_path = updateversion.git_root() os.chdir(repo_path) # Check for any uncommitted changes try: subprocess.check_output( "git diff --exit-code && " "git diff --cached --exit-code", shell=True ) except subprocess.CalledProcessError: print("ERROR: there are uncommitted changes in the repository") sys.exit(3) # Update the repository if not debug_mode: print("Updating repository in '{}'".format(os.getcwd())) try: subprocess.check_output("git fetch", shell=True) except subprocess.CalledProcessError: print("ERROR: unable to fetch\n") sys.exit(3) # Check existence of Tag for version in repo, create if not found try: updateversion.tag_check(version) if debug_mode: set_version_and_tag(version) except subprocess.CalledProcessError: set_version_and_tag(version) # Copy files to release dir release_files = release_prefix + version.split(".")[0] release_tmp_dir = path.join(release_path, release_files) print("Copying release files to '{}'".format(release_tmp_dir)) retry = True while True: try: shutil.copytree( repo_path, release_tmp_dir, ignore=shutil.ignore_patterns(*exclude_list) ) break except OSError as err: # First try and file exists, try to delete dir if retry and err.errno == errno.EEXIST: print("WARNING: Directory '{}' exists, delete it and retry" .format(release_tmp_dir)) shutil.rmtree(release_tmp_dir) retry = False continue else: # We already tried to delete or some other error occurred raise # Create tarballs print("Creating release tarballs...") release_name = release_prefix + '-' + version os.chdir(release_path) print("- tar") subprocess.call( "tar -czf {}.tar.gz {}".format(release_name, release_files), shell=True ) print("- zip") subprocess.call( "zip -rq {}.zip {}".format(release_name, release_files), shell=True ) if cleanup: print("Deleting working directories") shutil.rmtree(release_tmp_dir) if fresh_clone: shutil.rmtree(repo_path) else: print("\nThe following working directories were kept:") if fresh_clone: print("- '{}' (repo clone)".format(repo_path)) print("- '{}' (release temp dir)".format(release_tmp_dir)) print("Delete them manually when they are no longer needed.") # Done print("\nADOdb release {} build complete, files saved in '{}'.".format( version, release_path )) print("Don't forget to generate a README file with the changelog") # end main() if __name__ == "__main__": main()