diff options
| -rw-r--r-- | docs/changelog.md | 2 | ||||
| -rwxr-xr-x | scripts/buildrelease.py | 26 | ||||
| -rwxr-xr-x | scripts/updateversion.py | 173 | ||||
| -rwxr-xr-x | scripts/uploadrelease.py | 158 |
4 files changed, 266 insertions, 93 deletions
diff --git a/docs/changelog.md b/docs/changelog.md index 037fca3a..f0e1a72f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,7 +6,7 @@ Older changelogs: [v2.x](changelog_v2.x.md). -## 5.21 - ??-???-2015 +## 5.21.0 - ??-???-2015 ## 5.20.1 - 06-Dec-2015 diff --git a/scripts/buildrelease.py b/scripts/buildrelease.py index 99e9be85..9b126bce 100755 --- a/scripts/buildrelease.py +++ b/scripts/buildrelease.py @@ -58,7 +58,8 @@ def usage(): Options: -h | --help Show this usage message - -b | --branch <branch> Use specified branch (defaults to %s) + -b | --branch <branch> Use specified branch (defaults to '%s' for '.0' + releases, or 'hotfix/<version>' 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 @@ -87,9 +88,9 @@ def set_version_and_tag(version): subprocess.call("git checkout %s" % release_branch, shell=True) if not debug_mode: - # Make sure we're up-to-date + # Make sure we're up-to-date, ignore untracked files ret = subprocess.check_output( - "git status --branch --porcelain", + "git status --branch --porcelain --untracked-files=no", shell=True ) if not re.search(release_branch + "$", ret): @@ -141,12 +142,15 @@ def main(): version = updateversion.version_check(args[0]) release_path = args[1] - global release_prefix - release_prefix += version.split(".")[0] + # Default release branch + if updateversion.version_is_patch(version): + release_branch = 'hotfix/' + version # ------------------------------------------------------------------------- # Start the build # + global release_prefix + print "Building ADOdb release %s into '%s'\n" % ( version, release_path @@ -198,8 +202,9 @@ def main(): set_version_and_tag(version) # Copy files to release dir - release_tmp_dir = path.join(release_path, release_prefix) - print "Copying files to '%s'" % release_path + release_files = release_prefix + version.split(".")[0] + release_tmp_dir = path.join(release_path, release_files) + print "Copying release files to '%s'" % release_tmp_dir retry = True while True: try: @@ -224,17 +229,18 @@ def main(): # Create tarballs print "Creating release tarballs..." - release_name = release_prefix + version.split(".")[1] + release_name = release_prefix + '-' + version + print release_prefix, version, release_name os.chdir(release_path) print "- tar" subprocess.call( - "tar -czf %s.tar.gz %s" % (release_name, release_prefix), + "tar -czf %s.tar.gz %s" % (release_name, release_files), shell=True ) print "- zip" subprocess.call( - "zip -rq %s.zip %s" % (release_name, release_prefix), + "zip -rq %s.zip %s" % (release_name, release_files), shell=True ) diff --git a/scripts/updateversion.py b/scripts/updateversion.py index b95e6f94..0c39fd53 100755 --- a/scripts/updateversion.py +++ b/scripts/updateversion.py @@ -15,8 +15,9 @@ import sys # ADOdb version validation regex +# These are used by sed - they are not PCRE ! _version_dev = "dev" -_version_regex = "[Vv]?[0-9]\.[0-9]+(%s|[a-z]|\.[0-9])?" % _version_dev +_version_regex = "[Vv]?([0-9]\.[0-9]+)(\.([0-9]+))?(-?%s)?" % _version_dev _release_date_regex = "[0-9?]+-.*-[0-9]+" _changelog_file = "docs/changelog.md" @@ -44,24 +45,59 @@ def usage(): #end usage() +def version_is_dev(version): + ''' Returns true if version is a development release + ''' + return version.endswith(_version_dev) + + +def version_is_patch(version): + ''' Returns true if version is a patch release (i.e. X.Y.Z with Z > 0) + ''' + return not version.endswith('.0') + + +def version_parse(version): + ''' Breakdown the version into groups (Z and -dev are optional) + 1:(X.Y), 2:(.Z), 3:(Z), 4:(-dev) + ''' + return re.match(r'^%s$' % _version_regex, version) + + def version_check(version): ''' Checks that the given version is valid, exits with error if not. - Returns the version without the "v" prefix + Returns the SemVer-normalized version without the "v" prefix + - add '.0' if missing patch bit + - add '-' before dev release suffix if needed ''' - if not re.search("^%s$" % _version_regex, version): + vparse = version_parse(version) + if not vparse: usage() print "ERROR: invalid version ! \n" sys.exit(1) - return version.lstrip("Vv") + vnorm = vparse.group(1) + + # Add .patch version component + if vparse.group(2): + vnorm += vparse.group(2) + else: + # None was specified, assume a .0 release + vnorm += '.0' + # Normalize version number + if version_is_dev(version): + vnorm += '-' + _version_dev -def release_date(version): + return vnorm + + +def get_release_date(version): ''' Returns the release date in DD-MMM-YYYY format For development releases, DD-MMM will be ??-??? ''' # Development release - if version.endswith(_version_dev): + if version_is_dev(version): date_format = "??-???-%Y" else: date_format = "%d-%b-%Y" @@ -75,11 +111,11 @@ def sed_script(version): ''' # Version number and release date - script = r"s/%s\s+(-?)\s+%s/v%s \2 %s/" % ( + script = r"s/{}\s+(-?)\s+{}/v{} \5 {}/".format( _version_regex, _release_date_regex, version, - release_date(version) + get_release_date(version) ) return script @@ -135,7 +171,7 @@ def tag_create(version): "git tag --sign --message '%s' %s" % ( "ADOdb version %s released %s" % ( version, - release_date(version) + get_release_date(version) ), tag_name(version) ), @@ -144,61 +180,120 @@ def tag_create(version): return result == 0 +def section_exists(filename, version, print_message=True): + ''' Checks given file for existing section with specified version + ''' + script = True + for i, line in enumerate(open(filename)): + if re.search(r'^## ' + version, line): + if print_message: + print " Existing section for v%s found," % version, + return True + return False + + +def version_get_previous(version): + ''' Returns the previous version number + Don't decrease major versions (raises exception) + ''' + vprev = version.split('.') + item = len(vprev) - 1 + + while item > 0: + val = int(vprev[item]) + if val > 0: + vprev[item] = str(val - 1) + break + else: + item -= 1 + + if item == 0: + raise ValueError('Refusing to decrease major version number') + + return '.'.join(vprev) + + def update_changelog(version): ''' Updates the release date in the Change Log ''' print "Updating Changelog" - # Development release - # Insert a new section for next release before the most recent one - if version.endswith(_version_dev): - version_release = version[:-len(_version_dev)] + vparse = version_parse(version) + + # Version number without '-dev' suffix + version_release = vparse.group(1) + vparse.group(2) + version_previous = version_get_previous(version_release) - version_previous = version_release.split(".") - version_previous[1] = str(int(version_previous[1]) - 1) - version_previous = ".".join(version_previous) + if not section_exists(_changelog_file, version_previous, False): + raise ValueError( + "ERROR: previous version %s does not exist in changelog" % + version_previous + ) + # Check if version already exists in changelog + version_exists = section_exists(_changelog_file, version_release) + if (not version_exists + and not version_is_patch(version) + and not version_is_dev(version)): + version += '-' + _version_dev + + release_date = get_release_date(version) + + # Development release + # Insert a new section for next release before the most recent one + if version_is_dev(version): # Check changelog file for existing section - script = True - for i, line in enumerate(open(_changelog_file)): - if re.search(r'^## ' + version_release, line): - print " Found existing section for v%s," % version_release, - print "nothing to do" - return + if version_exists: + print "nothing to do" + return # No existing section found, insert new one - print " Inserting new section for v%s" % version_release - script = "1,/^##/s/^##.*$/## %s - %s\\n\\n\\0/" % ( + if version_is_patch(version_release): + print " Inserting new section for hotfix release v%s" % version + else: + print " Inserting new section for v%s" % version_release + # Adjust previous version number (remove patch component) + version_previous = version_parse(version_previous).group(1) + script = "1,/^## {0}/s/^## {0}.*$/## {1} - {2}\\n\\n\\0/".format( + version_previous, version_release, - release_date(version) + release_date ) - # Stable release (X.Y.0 or X.Y) - # Replace the occurence of markdown level 2 header matching version + # Stable release (X.Y.0) + # Replace the 1st occurence of markdown level 2 header matching version # and release date patterns - elif version.endswith(".0") or re.match('[Vv]?[0-9]\.[0-9]+$', version): + elif not version_is_patch(version): print " Updating release date for v%s" % version - script = "1,/^##/s/^(## )%s - %s.*$/\\1%s - %s/" % ( - _version_regex, + script = r"s/^(## ){0}(\.0)? - {1}.*$/\1{2} - {3}/".format( + vparse.group(1), _release_date_regex, version, - release_date(version) + release_date ) - # Hotfix release (X.Y.[0-9] or X.Y[a-z]) + # Hotfix release (X.Y.[0-9]) # Insert a new section for the hotfix release before the most recent # section for version X.Y and display a warning message else: - version_parent = re.match('[Vv]?([0-9]\.[0-9]+)', version).group(1) - print " Inserting new section for hotfix release v%s" % version + if version_exists: + print 'updating release date' + script = "s/^## {0}.*$/## {1} - {2}/".format( + version.replace('.', '\.'), + version, + release_date + ) + else: + print " Inserting new section for hotfix release v%s" % version + script = "1,/^## {0}/s/^## {0}.*$/## {1} - {2}\\n\\n\\0/".format( + version_previous, + version, + release_date + ) + print " WARNING: review '%s' to ensure added section is correct" % ( _changelog_file ) - script = "1,/^## {0}/s/^## {0}.*$/## {1} - {2}\\n\\n\\0/".format( - version_parent, - version, - release_date(version) - ) subprocess.call( "sed -r -i '%s' %s " % ( diff --git a/scripts/uploadrelease.py b/scripts/uploadrelease.py index 7fb4a08e..04e13ed9 100755 --- a/scripts/uploadrelease.py +++ b/scripts/uploadrelease.py @@ -3,23 +3,24 @@ ADOdb release upload script ''' +from distutils.version import LooseVersion import getopt import glob import os from os import path +import re import subprocess import sys # Directories and files to exclude from release tarballs -sf_files = "frs.sourceforge.net:/home/frs/project/adodb" \ - "/adodb-php5-only/adodb-{ver}-for-php5/" +sf_files = "frs.sourceforge.net:/home/frs/project/adodb/" sf_doc = "web.sourceforge.net:/home/project-web/adodb/htdocs/" rsync_cmd = "rsync -vP --rsh ssh {opt} {src} {usr}@{dst}" # Command-line options -options = "hfd" -long_options = ["help", "files", "doc"] +options = "hfdn" +long_options = ["help", "files", "doc", "dry-run"] def usage(): @@ -37,13 +38,75 @@ def usage(): -h | --help Show this usage message -f | --files Upload release files only -d | --doc Upload documentation only + -n | --dry-run Do not upload the files ''' % ( path.basename(__file__) ) #end usage() -def main(): +def call_rsync(usr, opt, src, dst): + ''' Calls rsync to upload files with given parameters + usr = ssh username + opt = options + src = source directory + dst = target directory + ''' + global dry_run + + command = rsync_cmd.format(usr=usr, opt=opt, src=src, dst=dst) + + if dry_run: + print command + else: + subprocess.call(command, shell=True) + + +def get_release_version(): + ''' Get the version number from the zip file to upload + ''' + try: + zipfile = glob.glob('adodb-*.zip')[0] + except IndexError: + print "ERROR: release zip file not found in '%s'" % release_path + sys.exit(1) + + try: + version = re.search( + "^adodb-([\d]+\.[\d]+\.[\d]+)\.zip$", + zipfile + ).group(1) + except AttributeError: + print "ERROR: unable to extract version number from '%s'" % zipfile + print " Only 3 groups of digits separated by periods are allowed" + sys.exit(1) + + return version + + +def sourceforge_target_dir(version): + ''' Returns the sourceforge target directory + Base directory as defined in sf_files global variable, plus + - if version >= 5.21: adodb-X.Y + - for older versions: adodb-XYZ-for-php5 + ''' + # Keep only X.Y (discard patch number) + short_version = version.rsplit('.', 1)[0] + + directory = 'adodb-php5-only/' + if LooseVersion(version) >= LooseVersion('5.21'): + directory += "adodb-" + short_version + else: + directory += "adodb-{}-for-php5".format(short_version.replace('.', '')) + + return directory + + +def process_command_line(): + ''' Retrieve command-line options and set global variables accordingly + ''' + global upload_files, upload_doc, dry_run, username, release_path + # Get command-line options try: opts, args = getopt.gnu_getopt(sys.argv[1:], options, long_options) @@ -57,8 +120,10 @@ def main(): print "ERROR: please specify the Sourceforge user and release_path" sys.exit(1) + # Default values for flags upload_files = True upload_doc = True + dry_run = False for opt, val in opts: if opt in ("-h", "--help"): @@ -71,57 +136,64 @@ def main(): elif opt in ("-d", "--doc"): upload_files = False + elif opt in ("-n", "--dry-run"): + dry_run = True + # Mandatory parameters username = args[0] + # Change to release directory, current if not specified try: release_path = args[1] os.chdir(release_path) except IndexError: release_path = os.getcwd() - # Upload release files - if upload_files: - # Get the version number from the zip file to upload - try: - zipfile = glob.glob('*.zip')[0] - except IndexError: - print "ERROR: release zip file not found in '%s'" % release_path - sys.exit(1) - version = zipfile[5:8] - # Start upload process - print "ADOdb release upload script" +def upload_release_files(): + ''' Upload release files from source directory to SourceForge + ''' + version = get_release_version() + target = sf_files + sourceforge_target_dir(version) + + print + print "Uploading release files..." + print " Source:", release_path + print " Target: " + target + print + call_rsync( + username, + "--exclude=docs", + path.join(release_path, "*"), + target + ) + - target = sf_files.format(ver=version) - print - print "Uploading release files..." - print " Target: " + target - print - subprocess.call( - rsync_cmd.format( - usr=username, - opt="--exclude=docs", - src=path.join(release_path, "*"), - dst=target - ), - shell=True - ) +def upload_documentation(): + ''' Upload documentation to Sourceforge web site + ''' + print + print "Uploading documentation..." + print + call_rsync( + username, + "", + path.join(release_path, "docs", "*"), + sf_doc + ) + + +def main(): + process_command_line() + + # Start upload process + print "ADOdb release upload script" + + if upload_files: + upload_release_files() - # Upload documentation if upload_doc: - print - print "Uploading documentation..." - print - subprocess.call( - rsync_cmd.format( - usr=username, - opt="", - src=path.join(release_path, "docs", "*"), - dst=sf_doc - ), - shell=True - ) + upload_documentation() #end main() |
