root/tools/make.py

Revision 1002, 11.3 kB (checked in by MaierMan, 4 months ago)

* require --updateURL for nightlies

Line 
1 #!/bin/env python
2 """
3 Small python make tool for XPIs using SVN.
4 (C) 2007 Nils Maier
5 Licensed under MPL1.1/GPL2.0/LGPL2.1
6 """
7
8 import os, re, sys
9 import tarfile
10
11 try:
12     import pysvn as svn
13 except:
14     print 'pysvn required: http://pysvn.tigris.org/'
15     sys.exit(1)
16
17 from httplib import HTTPConnection
18 from os import path
19 from shutil import rmtree
20 from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
21 from urlparse import urlparse
22 from xml.dom.minidom import parse as xml_open
23 from time import strftime
24
25 NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'
26 NS_EM  = 'http://www.mozilla.org/2004/em-rdf#'
27
28 BLOCK_START = "***** BEGIN LICENSE BLOCK *****"
29 BLOCK_END = "***** END LICENSE BLOCK *****"
30
31 block_replacement = "You may find the license in the LICENSE file"
32 svn_path = 'http://code.downthemall.net/repos/'
33 locales_url = 'http://www.babelzilla.org/components/com_wts/com_download.php?extension=1455&type=all'
34 locales_headers = {}
35 locales_file = 'locales.tar.gz'
36
37 xpi_file = 'downthemall.xpi'
38
39 class XPI:
40     rev = None
41    
42     def __init__(self, opts, repos, exportTo='export'):
43         self.client = svn.Client()
44         self.opts = opts
45         self.opts.branch = repos + opts.branch + "/"
46         self.opts.exportTo = exportTo
47        
48     def create(self, xpi_file):
49         global locales_url, locales_headers
50         yield "preparing"
51         self.prepare()
52         yield "checking out"
53         self.checkout()
54         if self.opts.locget:
55             yield "getting locales"
56             self.getlocales(locales_url, locales_headers, locales_file, self.opts.locget)
57         if self.opts.locint or self.opts.locget:
58             yield "integrating locales"
59             self.integratelocales(locales_file)
60         if self.opts.nightly:
61             yield "nightlifying"
62             self.nightlify()
63         if self.opts.extid:
64             yield "setting the extension id"
65             self.setId()
66         if self.opts.nocomments:
67             yield "stripping comments"
68             self.stripcomments()
69         if self.opts.release:
70             yield "jarring"
71             self.jar()               
72         yield "creating the xpi"
73         for x in self.createXPI(xpi_file, self.opts.rc):
74             yield x
75         yield "cleaning up"
76         self.cleanup()
77
78     def prepare(self):
79         if path.isdir(self.opts.exportTo):
80             rmtree(self.opts.exportTo)
81     def checkout(self):
82         self.client.export(self.opts.branch, self.opts.exportTo, True)
83
84     def getlocales(self, url, headers, filename, cookie):
85         if path.isfile(filename):
86             os.remove(filename)
87         if not headers:
88             headers = dict()
89         if cookie:
90             headers['Cookie'] = cookie
91
92         url = urlparse(url)
93         http = HTTPConnection(url.hostname, url.port)
94         http.request('GET', "%s?%s" % (url.path, url.query), None, headers)
95         r = http.getresponse()
96         f = open(filename, 'wb')
97         f.write(r.read())
98         f.close()
99         http.close()
100
101     def integratelocales(self, filename):
102         tar = tarfile.open(filename)
103         localedir = '%s/chrome/locale/' % self.opts.exportTo
104         tar.extractall(localedir)
105
106         man = open("export/chrome.manifest", 'ab')
107         man.write("# integrated locales:\n")
108        
109         for x in os.listdir(localedir):
110             if not re.match(r'\w+-\w+$', x):
111                 continue
112             if x == 'en-US':
113                 continue
114             man.write('locale\tdta\t%s\tchrome/locale/%s/\n' % (x, x))
115         man.close()
116
117     def getrevision(self):
118         if not self.rev:
119             self.rev = self.client.info2(self.opts.branch, recurse=False)[0][1].rev.number
120         return self.rev
121        
122
123     def nightlify(self):
124         'open'
125         rdf = xml_open('%s/install.rdf' % self.opts.exportTo)
126
127         'update the version'
128         node = rdf.getElementsByTagNameNS(NS_EM, 'version')[0].childNodes[0]
129         node.data = "%s.%s.%s" % (node.data, strftime("%Y%m%d"), self.getrevision())
130
131         node = rdf.getElementsByTagNameNS(NS_EM, 'name')[0].childNodes[0]
132         node.data = node.data + ' *nightly*'
133
134         'insert the updateURL node'
135         node = rdf.getElementsByTagNameNS(NS_EM, 'aboutURL')[0]
136         u = rdf.createElementNS(NS_EM, 'em:updateURL')
137         u.appendChild(rdf.createTextNode(self.opts.updateURL))
138         node.parentNode.insertBefore(u, node)
139
140         'insert the updateKey node'
141         node = rdf.getElementsByTagNameNS(NS_EM, 'aboutURL')[0]
142         u = rdf.createElementNS(NS_EM, 'em:updateKey')
143         u.appendChild(rdf.createTextNode(self.opts.nightly))
144         node.parentNode.insertBefore(u, node)
145
146         'prettify'
147         node.parentNode.insertBefore(rdf.createTextNode('\n\t\t'), node)
148
149         'save'
150         f = open('export/install.rdf', 'w')
151         rdf.writexml(f)
152
153         'cleanup'
154         rdf.unlink()
155         f.close()       
156
157     def setId(self):
158         rdf = xml_open('%s/install.rdf' % self.opts.exportTo)
159
160         'update the id'
161         node = rdf.getElementsByTagNameNS(NS_EM, 'id')[0].childNodes[0]
162         node.data = self.opts.extid
163
164         'save'
165         f = open('export/install.rdf', 'w')
166         rdf.writexml(f)
167
168         'cleanup'
169         rdf.unlink()
170         f.close()
171
172         for vi in ('modules/version.jsm', 'chrome/content/common/verinfo.js'):
173             vi = '%s/%s' % (self.opts.exportTo, vi)
174             if not os.path.exists(vi):
175                 continue
176             f = open(vi)
177             lines = f.readlines()
178             f.close()
179             f = open(vi, 'wb')
180             for l in lines:
181                 if re.search('const ID', l):
182                     l = "const ID = '%s';\n" % self.opts.extid
183                 elif re.search('const DTA_ID', l):
184                     l = "const DTA_ID = '%s';\n" % self.opts.extid
185                 f.write(l)
186             f.close()
187            
188     def jar(self):
189         f = open('%s/chrome.manifest' % self.opts.exportTo)
190         lines = f.readlines()
191         f.close()
192         f = open('%s/chrome.manifest' % self.opts.exportTo, 'wb')
193         for l in lines:
194             l = re.sub(r'(\s)chrome/', r'\1jar:chrome/chrome.jar!/', l);
195             f.write(l)
196         f.close()
197
198         p = self.opts.exportTo +  "/chrome"
199         dc = os.listdir(p)[:]
200         dirs = ()
201         for x in dc:
202             if x in ('icons'):
203                 continue
204             f = p + "/" + x
205             if path.isdir(f):
206                 dirs += f,
207         jar_file = ZipFile(p + "/chrome.jar", 'w', ZIP_STORED)
208         for d in dirs:
209             for x in self.getfilelist(d):
210                 jar_file.write(x, x[len(p) + 1:].encode('cp437'))
211             rmtree(d)
212         jar_file.close()
213
214
215     def getfilelist(self, p):
216         l = os.listdir(p)[:]
217         for x in l:
218             f = p + "/" + x
219             if path.isfile(f):
220                 yield unicode(f)
221             elif path.isdir(f):
222                 for i in self.getfilelist(f):
223                     yield i
224
225     def stripcomments(self):
226         global BLOCK_START, BLOCK_END, block_replacement
227         replacement = r'%s.+%s' % (re.escape(BLOCK_START), re.escape(BLOCK_END))
228         replacement = re.compile(replacement, re.S | re.M)
229         mask = re.compile(r'\.(xul|xml|dtd|jsm?)$', re.I)
230         for f in self.getfilelist(self.opts.exportTo):
231             if not mask.search(f):
232                 continue
233             p = open(f, 'rb')
234             c = p.read()
235             p.close()
236             c = replacement.sub(block_replacement, c)
237             p = open(f, 'wb')
238             p.write(c)
239             p.close()
240        
241     def createXPI(self, xpi_file, rc):
242         additional = ''
243
244         if self.opts.version:
245             rdf = xml_open('%s/install.rdf' % self.opts.exportTo)
246             additional += '-' + rdf.getElementsByTagNameNS(NS_EM, 'version')[0].childNodes[0].data
247             if rc > 0:
248                 additional += "rc%d" % rc
249             rdf.unlink()
250         if self.opts.revision:
251             additional += '-r%d' % self.getrevision()
252         output = xpi_file % additional
253         xpi_file = ZipFile(output, 'w', ZIP_DEFLATED)
254         for x in self.getfilelist(self.opts.exportTo):
255             xpi_file.write(x, x[len(self.opts.exportTo) + 1:].encode('cp437'))
256         xpi_file.close()
257         yield "written to: " + output
258
259     def cleanup(self):
260         if path.isdir(self.opts.exportTo):
261             rmtree(self.opts.exportTo)
262
263 def main():
264     global xpi_file, svn_path
265    
266     parser = OptionParser(usage="%prog [options] [branch]", version="$Id$")
267     parser.add_option(
268         '--locint',
269         dest='locint',
270         help='integrate locales',
271         action='store_true',
272         default=False
273         )
274     parser.add_option(
275         '--locget',
276         dest='locget',
277         help='download locales (provide your babelzilla auth cookie). Implies --locint',
278         action='store',
279         type='string',
280         default=''
281         )
282     parser.add_option(
283         '--addversion',
284         dest='version',
285         help='add version to filename',
286         action='store_true',
287         default=False
288         )
289     parser.add_option(
290         '--addrevision',
291         dest="revision",
292         help="adds the SVN revision to the filename",
293         action="store_true",
294         default=False
295         )
296     parser.add_option(
297         '--nightly',
298         dest='nightly',
299         help='is a nightly (provide update key)',
300         type="string",
301         )
302     parser.add_option(
303         '--updateURL',
304         dest='updateURL',
305         help='The update url of the nightly build',
306         type="string"
307         )
308     parser.add_option(
309         '--release',
310         dest='release',
311         help='Is a release',
312         action='store_true',
313         default=False
314         )
315     parser.add_option(
316         '--extid',
317         dest='extid',
318         help='Set the extension id',
319         type='string'
320         )   
321     parser.add_option(
322         '--nocomment',
323         dest='nocomments',
324         help='collapse those comment blocks',
325         action='store_true',
326         default=False
327         )
328     parser.add_option(
329         '--quiet',
330         dest="quiet",
331         help="don't display info messages",
332         action="store_true",
333         default=False
334         )
335     parser.add_option(
336         '--output',
337         dest='xpifile',
338         help='the resulting xpi file (might be changed by the modificators)',
339         type='string',
340         default=xpi_file
341         )
342     parser.add_option(
343         '--rc',
344         dest='rc',
345         help='the resulting xpi file (might be changed by the modificators)',
346         type='int',
347         default=0
348         )   
349     opts, args = parser.parse_args()
350     if len(args) > 1:
351         parser.error("provide exactly one branch!")
352     elif len(args) == 0:
353         args = ['trunk']
354
355     if opts.nightly and not opts.updateURL:
356         parser.error("when building nightlies you must provide an updateURL")
357
358     opts.branch, = args
359
360     xpi_file = "%s%%s%s" % path.splitext(opts.xpifile)
361
362     xpi = XPI(opts, svn_path)
363     for x in xpi.create(xpi_file):
364         if not opts.quiet:
365             print x
366
367 if __name__ == "__main__":
368     from optparse import OptionParser
369     main()
Note: See TracBrowser for help on using the browser.