|
1 from datetime import datetime |
|
2 import os |
|
3 from os import path |
|
4 import re |
|
5 import shutil |
|
6 import sys |
|
7 from urllib2 import urlopen |
|
8 |
|
9 from release.paths import makeCandidatesDir |
|
10 |
|
11 import logging |
|
12 log = 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. |
|
16 FINAL_RELEASE_REGEX = "^\d+\.\d+$" |
|
17 |
|
18 |
|
19 class ConfigError(Exception): |
|
20 pass |
|
21 |
|
22 |
|
23 def 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 |
|
40 def 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 |
|
58 def 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 |
|
71 def readReleaseConfig(configfile, required=[]): |
|
72 return readConfig(configfile, keys=['releaseConfig'], required=required) |
|
73 |
|
74 |
|
75 def 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 |
|
88 def 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 |
|
104 def isFinalRelease(version): |
|
105 return bool(re.match(FINAL_RELEASE_REGEX, version)) |
|
106 |
|
107 |
|
108 def getBaseTag(product, version): |
|
109 product = product.upper() |
|
110 version = version.replace('.', '_') |
|
111 return '%s_%s' % (product, version) |
|
112 |
|
113 |
|
114 def 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 |
|
121 def getRuntimeTag(tag): |
|
122 return "%s_RUNTIME" % tag |
|
123 |
|
124 |
|
125 def getReleaseTag(tag): |
|
126 return "%s_RELEASE" % tag |
|
127 |
|
128 |
|
129 def generateRelbranchName(version, prefix='GECKO'): |
|
130 return '%s%s_%s_RELBRANCH' % ( |
|
131 prefix, version.replace('.', ''), |
|
132 datetime.now().strftime('%Y%m%d%H')) |
|
133 |
|
134 |
|
135 def getReleaseName(product, version, buildNumber): |
|
136 return '%s-%s-build%s' % (product.title(), version, str(buildNumber)) |
|
137 |
|
138 |
|
139 def getRepoMatchingBranch(branch, sourceRepositories): |
|
140 for sr in sourceRepositories.values(): |
|
141 if branch in sr['path']: |
|
142 return sr |
|
143 return None |
|
144 |
|
145 |
|
146 def 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 |