michael@0: from datetime import datetime michael@0: import os michael@0: from os import path michael@0: import re michael@0: import shutil michael@0: import sys michael@0: from urllib2 import urlopen michael@0: michael@0: from release.paths import makeCandidatesDir michael@0: michael@0: import logging michael@0: log = logging.getLogger(__name__) michael@0: michael@0: # If version has two parts with no trailing specifiers like "rc", we michael@0: # consider it a "final" release for which we only create a _RELEASE tag. michael@0: FINAL_RELEASE_REGEX = "^\d+\.\d+$" michael@0: michael@0: michael@0: class ConfigError(Exception): michael@0: pass michael@0: michael@0: michael@0: def getBuildID(platform, product, version, buildNumber, nightlyDir='nightly', michael@0: server='stage.mozilla.org'): michael@0: infoTxt = makeCandidatesDir(product, version, buildNumber, nightlyDir, michael@0: protocol='http', server=server) + \ michael@0: '%s_info.txt' % platform michael@0: try: michael@0: buildInfo = urlopen(infoTxt).read() michael@0: except: michael@0: log.error("Failed to retrieve %s" % infoTxt) michael@0: raise michael@0: michael@0: for line in buildInfo.splitlines(): michael@0: key, value = line.rstrip().split('=', 1) michael@0: if key == 'buildID': michael@0: return value michael@0: michael@0: michael@0: def findOldBuildIDs(product, version, buildNumber, platforms, michael@0: nightlyDir='nightly', server='stage.mozilla.org'): michael@0: ids = {} michael@0: if buildNumber <= 1: michael@0: return ids michael@0: for n in range(1, buildNumber): michael@0: for platform in platforms: michael@0: if platform not in ids: michael@0: ids[platform] = [] michael@0: try: michael@0: id = getBuildID(platform, product, version, n, nightlyDir, michael@0: server) michael@0: ids[platform].append(id) michael@0: except Exception, e: michael@0: log.error("Hit exception: %s" % e) michael@0: return ids michael@0: michael@0: michael@0: def getReleaseConfigName(product, branch, version=None, staging=False): michael@0: # XXX: Horrible hack for bug 842741. Because Thunderbird release michael@0: # and esr both build out of esr17 repositories we'll bump the wrong michael@0: # config for release without this. michael@0: if product == 'thunderbird' and 'esr17' in branch and version and 'esr' not in version: michael@0: cfg = 'release-thunderbird-comm-release.py' michael@0: else: michael@0: cfg = 'release-%s-%s.py' % (product, branch) michael@0: if staging: michael@0: cfg = 'staging_%s' % cfg michael@0: return cfg michael@0: michael@0: michael@0: def readReleaseConfig(configfile, required=[]): michael@0: return readConfig(configfile, keys=['releaseConfig'], required=required) michael@0: michael@0: michael@0: def readBranchConfig(dir, localconfig, branch, required=[]): michael@0: shutil.copy(localconfig, path.join(dir, "localconfig.py")) michael@0: oldcwd = os.getcwd() michael@0: os.chdir(dir) michael@0: sys.path.append(".") michael@0: try: michael@0: return readConfig("config.py", keys=['BRANCHES', branch], michael@0: required=required) michael@0: finally: michael@0: os.chdir(oldcwd) michael@0: sys.path.remove(".") michael@0: michael@0: michael@0: def readConfig(configfile, keys=[], required=[]): michael@0: c = {} michael@0: execfile(configfile, c) michael@0: for k in keys: michael@0: c = c[k] michael@0: items = c.keys() michael@0: err = False michael@0: for key in required: michael@0: if key not in items: michael@0: err = True michael@0: log.error("Required item `%s' missing from %s" % (key, c)) michael@0: if err: michael@0: raise ConfigError("Missing at least one item in config, see above") michael@0: return c michael@0: michael@0: michael@0: def isFinalRelease(version): michael@0: return bool(re.match(FINAL_RELEASE_REGEX, version)) michael@0: michael@0: michael@0: def getBaseTag(product, version): michael@0: product = product.upper() michael@0: version = version.replace('.', '_') michael@0: return '%s_%s' % (product, version) michael@0: michael@0: michael@0: def getTags(baseTag, buildNumber, buildTag=True): michael@0: t = ['%s_RELEASE' % baseTag] michael@0: if buildTag: michael@0: t.append('%s_BUILD%d' % (baseTag, int(buildNumber))) michael@0: return t michael@0: michael@0: michael@0: def getRuntimeTag(tag): michael@0: return "%s_RUNTIME" % tag michael@0: michael@0: michael@0: def getReleaseTag(tag): michael@0: return "%s_RELEASE" % tag michael@0: michael@0: michael@0: def generateRelbranchName(version, prefix='GECKO'): michael@0: return '%s%s_%s_RELBRANCH' % ( michael@0: prefix, version.replace('.', ''), michael@0: datetime.now().strftime('%Y%m%d%H')) michael@0: michael@0: michael@0: def getReleaseName(product, version, buildNumber): michael@0: return '%s-%s-build%s' % (product.title(), version, str(buildNumber)) michael@0: michael@0: michael@0: def getRepoMatchingBranch(branch, sourceRepositories): michael@0: for sr in sourceRepositories.values(): michael@0: if branch in sr['path']: michael@0: return sr michael@0: return None michael@0: michael@0: michael@0: def fileInfo(filepath, product): michael@0: """Extract information about a release file. Returns a dictionary with the michael@0: following keys set: michael@0: 'product', 'version', 'locale', 'platform', 'contents', 'format', michael@0: 'pathstyle' michael@0: michael@0: 'contents' is one of 'complete', 'installer' michael@0: 'format' is one of 'mar' or 'exe' michael@0: 'pathstyle' is either 'short' or 'long', and refers to if files are all in michael@0: one directory, with the locale as part of the filename ('short' paths, michael@0: firefox 3.0 style filenames), or if the locale names are part of the michael@0: directory structure, but not the file name itself ('long' paths, michael@0: firefox 3.5+ style filenames) michael@0: """ michael@0: try: michael@0: # Mozilla 1.9.0 style (aka 'short') paths michael@0: # e.g. firefox-3.0.12.en-US.win32.complete.mar michael@0: filename = os.path.basename(filepath) michael@0: m = re.match("^(%s)-([0-9.]+)\.([-a-zA-Z]+)\.(win32)\.(complete|installer)\.(mar|exe)$" % product, filename) michael@0: if not m: michael@0: raise ValueError("Could not parse: %s" % filename) michael@0: return {'product': m.group(1), michael@0: 'version': m.group(2), michael@0: 'locale': m.group(3), michael@0: 'platform': m.group(4), michael@0: 'contents': m.group(5), michael@0: 'format': m.group(6), michael@0: 'pathstyle': 'short', michael@0: 'leading_path': '', michael@0: } michael@0: except: michael@0: # Mozilla 1.9.1 and on style (aka 'long') paths michael@0: # e.g. update/win32/en-US/firefox-3.5.1.complete.mar michael@0: # win32/en-US/Firefox Setup 3.5.1.exe michael@0: ret = {'pathstyle': 'long'} michael@0: if filepath.endswith('.mar'): michael@0: ret['format'] = 'mar' michael@0: m = re.search("update/(win32|linux-i686|linux-x86_64|mac|mac64)/([-a-zA-Z]+)/(%s)-(\d+\.\d+(?:\.\d+)?(?:\w+(?:\d+)?)?)\.(complete)\.mar" % product, filepath) michael@0: if not m: michael@0: raise ValueError("Could not parse: %s" % filepath) michael@0: ret['platform'] = m.group(1) michael@0: ret['locale'] = m.group(2) michael@0: ret['product'] = m.group(3) michael@0: ret['version'] = m.group(4) michael@0: ret['contents'] = m.group(5) michael@0: ret['leading_path'] = '' michael@0: elif filepath.endswith('.exe'): michael@0: ret['format'] = 'exe' michael@0: ret['contents'] = 'installer' michael@0: # EUballot builds use a different enough style of path than others michael@0: # that we can't catch them in the same regexp michael@0: if filepath.find('win32-EUballot') != -1: michael@0: ret['platform'] = 'win32' michael@0: m = re.search("(win32-EUballot/)([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+\d+)?(?:\ \w+\ \d+)?)\.exe" % product, filepath) michael@0: if not m: michael@0: raise ValueError("Could not parse: %s" % filepath) michael@0: ret['leading_path'] = m.group(1) michael@0: ret['locale'] = m.group(2) michael@0: ret['product'] = m.group(3).lower() michael@0: ret['version'] = m.group(4) michael@0: else: michael@0: m = re.search("(partner-repacks/[-a-zA-Z0-9_]+/|)(win32|mac|linux-i686)/([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+(?:\d+)?)?(?:\ \w+\ \d+)?)\.exe" % product, filepath) michael@0: if not m: michael@0: raise ValueError("Could not parse: %s" % filepath) michael@0: ret['leading_path'] = m.group(1) michael@0: ret['platform'] = m.group(2) michael@0: ret['locale'] = m.group(3) michael@0: ret['product'] = m.group(4).lower() michael@0: ret['version'] = m.group(5) michael@0: else: michael@0: raise ValueError("Unknown filetype for %s" % filepath) michael@0: michael@0: return ret