1from datetime import datetime 2import os 3from os import path 4import re 5import shutil 6import sys 7from urllib2 import urlopen 8 9from release.paths import makeCandidatesDir 10 11import logging 12log = logging.getLogger(__name__) 13 14# If version has two parts with no trailing specifiers like "rc", we 15# consider it a "final" release for which we only create a _RELEASE tag. 16FINAL_RELEASE_REGEX = "^\d+\.\d+$" 17 18 19class ConfigError(Exception): 20 pass 21 22 23def getBuildID(platform, product, version, buildNumber, nightlyDir='nightly', 24 server='stage.mozilla.org'): 25 infoTxt = makeCandidatesDir(product, version, buildNumber, nightlyDir, 26 protocol='http', server=server) + \ 27 '%s_info.txt' % platform 28 try: 29 buildInfo = urlopen(infoTxt).read() 30 except: 31 log.error("Failed to retrieve %s" % infoTxt) 32 raise 33 34 for line in buildInfo.splitlines(): 35 key, value = line.rstrip().split('=', 1) 36 if key == 'buildID': 37 return value 38 39 40def findOldBuildIDs(product, version, buildNumber, platforms, 41 nightlyDir='nightly', server='stage.mozilla.org'): 42 ids = {} 43 if buildNumber <= 1: 44 return ids 45 for n in range(1, buildNumber): 46 for platform in platforms: 47 if platform not in ids: 48 ids[platform] = [] 49 try: 50 id = getBuildID(platform, product, version, n, nightlyDir, 51 server) 52 ids[platform].append(id) 53 except Exception, e: 54 log.error("Hit exception: %s" % e) 55 return ids 56 57 58def getReleaseConfigName(product, branch, version=None, staging=False): 59 # XXX: Horrible hack for bug 842741. Because Thunderbird release 60 # and esr both build out of esr17 repositories we'll bump the wrong 61 # config for release without this. 62 if product == 'thunderbird' and 'esr17' in branch and version and 'esr' not in version: 63 cfg = 'release-thunderbird-comm-release.py' 64 else: 65 cfg = 'release-%s-%s.py' % (product, branch) 66 if staging: 67 cfg = 'staging_%s' % cfg 68 return cfg 69 70 71def readReleaseConfig(configfile, required=[]): 72 return readConfig(configfile, keys=['releaseConfig'], required=required) 73 74 75def readBranchConfig(dir, localconfig, branch, required=[]): 76 shutil.copy(localconfig, path.join(dir, "localconfig.py")) 77 oldcwd = os.getcwd() 78 os.chdir(dir) 79 sys.path.append(".") 80 try: 81 return readConfig("config.py", keys=['BRANCHES', branch], 82 required=required) 83 finally: 84 os.chdir(oldcwd) 85 sys.path.remove(".") 86 87 88def readConfig(configfile, keys=[], required=[]): 89 c = {} 90 execfile(configfile, c) 91 for k in keys: 92 c = c[k] 93 items = c.keys() 94 err = False 95 for key in required: 96 if key not in items: 97 err = True 98 log.error("Required item `%s' missing from %s" % (key, c)) 99 if err: 100 raise ConfigError("Missing at least one item in config, see above") 101 return c 102 103 104def isFinalRelease(version): 105 return bool(re.match(FINAL_RELEASE_REGEX, version)) 106 107 108def getBaseTag(product, version): 109 product = product.upper() 110 version = version.replace('.', '_') 111 return '%s_%s' % (product, version) 112 113 114def getTags(baseTag, buildNumber, buildTag=True): 115 t = ['%s_RELEASE' % baseTag] 116 if buildTag: 117 t.append('%s_BUILD%d' % (baseTag, int(buildNumber))) 118 return t 119 120 121def getRuntimeTag(tag): 122 return "%s_RUNTIME" % tag 123 124 125def getReleaseTag(tag): 126 return "%s_RELEASE" % tag 127 128 129def generateRelbranchName(version, prefix='GECKO'): 130 return '%s%s_%s_RELBRANCH' % ( 131 prefix, version.replace('.', ''), 132 datetime.now().strftime('%Y%m%d%H')) 133 134 135def getReleaseName(product, version, buildNumber): 136 return '%s-%s-build%s' % (product.title(), version, str(buildNumber)) 137 138 139def getRepoMatchingBranch(branch, sourceRepositories): 140 for sr in sourceRepositories.values(): 141 if branch in sr['path']: 142 return sr 143 return None 144 145 146def fileInfo(filepath, product): 147 """Extract information about a release file. Returns a dictionary with the 148 following keys set: 149 'product', 'version', 'locale', 'platform', 'contents', 'format', 150 'pathstyle' 151 152 'contents' is one of 'complete', 'installer' 153 'format' is one of 'mar' or 'exe' 154 'pathstyle' is either 'short' or 'long', and refers to if files are all in 155 one directory, with the locale as part of the filename ('short' paths, 156 firefox 3.0 style filenames), or if the locale names are part of the 157 directory structure, but not the file name itself ('long' paths, 158 firefox 3.5+ style filenames) 159 """ 160 try: 161 # Mozilla 1.9.0 style (aka 'short') paths 162 # e.g. firefox-3.0.12.en-US.win32.complete.mar 163 filename = os.path.basename(filepath) 164 m = re.match("^(%s)-([0-9.]+)\.([-a-zA-Z]+)\.(win32)\.(complete|installer)\.(mar|exe)$" % product, filename) 165 if not m: 166 raise ValueError("Could not parse: %s" % filename) 167 return {'product': m.group(1), 168 'version': m.group(2), 169 'locale': m.group(3), 170 'platform': m.group(4), 171 'contents': m.group(5), 172 'format': m.group(6), 173 'pathstyle': 'short', 174 'leading_path': '', 175 } 176 except: 177 # Mozilla 1.9.1 and on style (aka 'long') paths 178 # e.g. update/win32/en-US/firefox-3.5.1.complete.mar 179 # win32/en-US/Firefox Setup 3.5.1.exe 180 ret = {'pathstyle': 'long'} 181 if filepath.endswith('.mar'): 182 ret['format'] = 'mar' 183 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) 184 if not m: 185 raise ValueError("Could not parse: %s" % filepath) 186 ret['platform'] = m.group(1) 187 ret['locale'] = m.group(2) 188 ret['product'] = m.group(3) 189 ret['version'] = m.group(4) 190 ret['contents'] = m.group(5) 191 ret['leading_path'] = '' 192 elif filepath.endswith('.exe'): 193 ret['format'] = 'exe' 194 ret['contents'] = 'installer' 195 # EUballot builds use a different enough style of path than others 196 # that we can't catch them in the same regexp 197 if filepath.find('win32-EUballot') != -1: 198 ret['platform'] = 'win32' 199 m = re.search("(win32-EUballot/)([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+\d+)?(?:\ \w+\ \d+)?)\.exe" % product, filepath) 200 if not m: 201 raise ValueError("Could not parse: %s" % filepath) 202 ret['leading_path'] = m.group(1) 203 ret['locale'] = m.group(2) 204 ret['product'] = m.group(3).lower() 205 ret['version'] = m.group(4) 206 else: 207 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) 208 if not m: 209 raise ValueError("Could not parse: %s" % filepath) 210 ret['leading_path'] = m.group(1) 211 ret['platform'] = m.group(2) 212 ret['locale'] = m.group(3) 213 ret['product'] = m.group(4).lower() 214 ret['version'] = m.group(5) 215 else: 216 raise ValueError("Unknown filetype for %s" % filepath) 217 218 return ret 219