Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: packagerChrome.py

Issue 9051052: Changes to Chrome build process (Closed)
Patch Set: Created Dec. 19, 2012, 4:27 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « manifest.json.tmpl ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # coding: utf-8 1 # coding: utf-8
2 2
3 # This file is part of the Adblock Plus build tools, 3 # This file is part of the Adblock Plus build tools,
4 # Copyright (C) 2006-2012 Eyeo GmbH 4 # Copyright (C) 2006-2012 Eyeo GmbH
5 # 5 #
6 # Adblock Plus is free software: you can redistribute it and/or modify 6 # Adblock Plus is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License version 3 as 7 # it under the terms of the GNU General Public License version 3 as
8 # published by the Free Software Foundation. 8 # published by the Free Software Foundation.
9 # 9 #
10 # Adblock Plus is distributed in the hope that it will be useful, 10 # Adblock Plus is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details. 13 # GNU General Public License for more details.
14 # 14 #
15 # You should have received a copy of the GNU General Public License 15 # You should have received a copy of the GNU General Public License
16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 16 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
17 17
18 import sys, os, subprocess, re, json, codecs, struct 18 import sys, os, subprocess, re, json, codecs, struct, jinja2, buildtools
19 from ConfigParser import SafeConfigParser 19 from ConfigParser import SafeConfigParser
20 from StringIO import StringIO 20 from StringIO import StringIO
21 from zipfile import ZipFile, ZIP_DEFLATED 21 from zipfile import ZipFile, ZIP_DEFLATED
22 22
23 defaultLocale = 'en_US' 23 defaultLocale = 'en_US'
24 24
25 def getDefaultFileName(baseDir, metadata, version, ext): 25 def getDefaultFileName(baseDir, metadata, ext):
26 return os.path.join(baseDir, '%s-%s.%s' % (metadata.get('general', 'basename') , version, ext)) 26 return os.path.join(baseDir, '%s-%s.%s' % (metadata.get('general', 'basename') , metadata.get('general', 'version'), ext))
27 27
28 def getMetadataPath(baseDir): 28 def getMetadataPath(baseDir):
29 return os.path.join(baseDir, 'metadata') 29 return os.path.join(baseDir, 'metadata')
30 30
31 def getBuildNum(baseDir): 31 def getBuildNum(baseDir):
32 try: 32 try:
33 (result, dummy) = subprocess.Popen(['hg', 'id', '-n'], stdout=subprocess.PIP E).communicate() 33 (result, dummy) = subprocess.Popen(['hg', 'id', '-n'], stdout=subprocess.PIP E).communicate()
34 return re.sub(r'\D', '', result) 34 return re.sub(r'\D', '', result)
35 except Exception: 35 except Exception:
36 return '0' 36 return '0'
37 37
38 def getIgnoredFiles(params):
39 return ['store.description']
40
38 def readMetadata(baseDir): 41 def readMetadata(baseDir):
39 metadata = SafeConfigParser() 42 metadata = SafeConfigParser()
40 metadata.optionxform = str 43 metadata.optionxform = str
41 file = codecs.open(getMetadataPath(baseDir), 'rb', encoding='utf-8') 44 file = codecs.open(getMetadataPath(baseDir), 'rb', encoding='utf-8')
42 metadata.readfp(file) 45 metadata.readfp(file)
43 file.close() 46 file.close()
44 return metadata 47 return metadata
45 48
46 def readVersion(baseDir): 49 def getPackageFiles(params):
47 file = open(os.path.join(baseDir, 'manifest.json')) 50 baseDir = params['baseDir']
48 data = json.load(file) 51 for file in ('_locales', 'icons', 'jquery-ui', 'lib', 'skin', 'ui'):
49 file.close() 52 yield os.path.join(baseDir, file)
50 return data['version'] 53 if params['devenv']:
54 yield os.path.join(baseDir, 'qunit')
55 for file in os.listdir(baseDir):
56 if file.endswith('.js') or file.endswith('.html') or file.endswith('.xml'):
57 yield os.path.join(baseDir, file)
51 58
52 def setUpdateURL(updateName, zip, dir, fileName, fileData): 59 def createManifest(params):
53 if fileName == 'manifest.json': 60 env = jinja2.Environment(loader=jinja2.FileSystemLoader(buildtools.__path__[0] ))
54 data = json.loads(fileData) 61 env.filters.update({'json': json.dumps})
55 data['update_url'] = 'https://adblockplus.org/devbuilds/%s/updates.xml' % up dateName 62 template = env.get_template('manifest.json.tmpl')
56 return json.dumps(data, sort_keys=True, indent=2) 63 templateData = dict(params)
57 return fileData
58 64
59 def setExperimentalSettings(zip, dir, fileName, fileData): 65 baseDir = templateData['baseDir']
60 if fileName == 'manifest.json': 66 metadata = templateData['metadata']
61 data = json.loads(fileData)
62 data['permissions'] += ['experimental']
63 data['name'] += ' experimental build'
64 return json.dumps(data, sort_keys=True, indent=2)
65 return fileData
66 67
67 def addBuildNumber(revision, zip, dir, fileName, fileData): 68 if metadata.has_option('general', 'pageAction'):
68 if fileName == 'manifest.json': 69 icon, popup = re.split(r'\s+', metadata.get('general', 'pageAction'), 1)
69 if len(revision) > 0: 70 templateData['pageAction'] = {'icon': icon, 'popup': popup}
70 data = json.loads(fileData)
71 while data['version'].count('.') < 2:
72 data['version'] += '.0'
73 data['version'] += '.' + revision
74 return json.dumps(data, sort_keys=True, indent=2)
75 return fileData
76 71
77 def mergeContentScripts(zip, dir, fileName, fileData): 72 if metadata.has_option('general', 'icons'):
78 if fileName == 'manifest.json': 73 icons = {}
79 data = json.loads(fileData) 74 iconsDir = baseDir
80 if 'content_scripts' in data: 75 for dir in metadata.get('general', 'icons').split('/')[0:-1]:
81 scriptIndex = 1 76 iconsDir = os.path.join(iconsDir, dir)
82 for contentScript in data['content_scripts']:
83 if 'js' in contentScript:
84 scriptData = ''
85 for scriptFile in contentScript['js']:
86 parts = [dir] + scriptFile.split('/')
87 scriptPath = os.path.join(*parts)
88 handle = open(scriptPath, 'rb')
89 scriptData += handle.read()
90 handle.close()
91 contentScript['js'] = ['contentScript' + str(scriptIndex) + '.js']
92 zip.writestr('contentScript' + str(scriptIndex) + '.js', scriptData)
93 scriptIndex += 1
94 return json.dumps(data, sort_keys=True, indent=2)
95 return fileData
96 77
97 def addToZip(zip, filters, dir, baseName): 78 prefix, suffix = metadata.get('general', 'icons').split('/')[-1].split('?', 1)
98 for file in os.listdir(dir): 79 for file in os.listdir(iconsDir):
99 filelc = file.lower() 80 path = os.path.join(iconsDir, file)
100 if (file.startswith('.') or 81 if os.path.isfile(path) and file.startswith(prefix) and file.endswith(suff ix):
101 file == 'buildtools' or file == 'qunit' or file == 'metadata' or 82 size = file[len(prefix):-len(suffix)]
102 file == 'store.description' or 83 if not re.search(r'\D', size):
103 filelc.endswith('.py') or filelc.endswith('.pyc') or 84 icons[size] = os.path.relpath(path, baseDir).replace('\\', '/')
104 filelc.endswith('.crx') or filelc.endswith('.zip') or
105 filelc.endswith('.sh') or filelc.endswith('.bat') or
106 filelc.endswith('.txt')):
107 # skip special files, scripts, existing archives
108 continue
109 if file.startswith('include.'):
110 # skip includes, they will be added by other means
111 continue
112 85
113 filePath = os.path.join(dir, file) 86 templateData['icons'] = icons
114 if os.path.isdir(filePath):
115 addToZip(zip, filters, filePath, baseName + file + '/')
116 else:
117 handle = open(filePath, 'rb')
118 fileData = handle.read()
119 handle.close()
120 87
121 for filter in filters: 88 if metadata.has_option('general', 'permissions'):
122 fileData = filter(zip, dir, baseName + file, fileData) 89 templateData['permissions'] = re.split(r'\s+', metadata.get('general', 'perm issions'))
123 zip.writestr(baseName + file, fileData) 90 if params['experimentalAPI']:
91 templateData['permissions'].append('experimental')
124 92
125 def packDirectory(dir, filters): 93 if metadata.has_option('general', 'backgroundScripts'):
94 templateData['backgroundScripts'] = re.split(r'\s+', metadata.get('general', 'backgroundScripts'))
95
96 if metadata.has_option('general', 'webAccessible'):
97 templateData['webAccessible'] = re.split(r'\s+', metadata.get('general', 'we bAccessible'))
98
99 if metadata.has_section('contentScripts'):
100 contentScripts = []
101 for run_at, scripts in metadata.items('contentScripts'):
102 contentScripts.append({
103 'matches': ['http://*/*', 'https://*/*'],
104 'js': re.split(r'\s+', scripts),
105 'run_at': run_at,
106 'all_frames': True,
107 })
108 templateData['contentScripts'] = contentScripts
109
110 manifest = template.render(templateData)
111
112 # Normalize JSON structure
113 licenseComment = re.compile(r'/\*.*?\*/', re.S)
114 data = json.loads(re.sub(licenseComment, '', manifest, 1))
115 if '_dummy' in data:
116 del data['_dummy']
117 manifest = json.dumps(data, sort_keys=True, indent=2)
118
119 return manifest.encode('utf-8')
120
121 def readFile(params, files, path):
122 ignoredFiles = getIgnoredFiles(params)
123 if os.path.isdir(path):
124 for file in os.listdir(path):
125 if file in ignoredFiles:
126 continue
127 readFile(params, files, os.path.join(path, file))
128 else:
129 file = open(path, 'rb')
130 data = file.read()
131 file.close()
132
133 name = os.path.relpath(path, params['baseDir']).replace('\\', '/')
134 files[name] = data
135
136 def convertJS(params, files):
137 baseDir = params['baseDir']
138 hydraDir = os.path.join(baseDir, 'jshydra')
139 sys.path.append(hydraDir)
140 try:
141 import abp_rewrite
142 for file, sources in params['metadata'].items('convert_js'):
143 dirsep = file.find('/')
144 if dirsep >= 0:
145 # Not a top-level file, make sure it is inside an included director
146 dirname = file[0:dirsep]
147 if os.path.join(baseDir, dirname) not in getPackageFiles(params):
148 continue
149
150 sourceFiles = re.split(r'\s+', sources)
151 args = []
152 try:
153 argsStart = sourceFiles.index('--arg')
154 args = sourceFiles[argsStart + 1:]
155 sourceFiles = sourceFiles[0:argsStart]
156 except ValueError:
157 pass
158
159 sourceFiles = map(lambda f: os.path.abspath(os.path.join(baseDir, f)), sou rceFiles)
160 files[file] = abp_rewrite.doRewrite(sourceFiles, args)
161 finally:
162 sys.path.remove(hydraDir)
163
164 def packFiles(files):
126 buffer = StringIO() 165 buffer = StringIO()
127 zip = ZipFile(buffer, 'w', ZIP_DEFLATED) 166 zip = ZipFile(buffer, 'w', ZIP_DEFLATED)
128 addToZip(zip, filters, dir, '') 167 for file, data in files.iteritems():
168 zip.writestr(file, data)
129 zip.close() 169 zip.close()
130 return buffer.getvalue() 170 return buffer.getvalue()
131 171
132 def signBinary(zipdata, keyFile): 172 def signBinary(zipdata, keyFile):
133 import M2Crypto 173 import M2Crypto
134 if not os.path.exists(keyFile): 174 if not os.path.exists(keyFile):
135 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None) 175 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None)
136 key = M2Crypto.EVP.load_key(keyFile) 176 key = M2Crypto.EVP.load_key(keyFile)
137 key.sign_init() 177 key.sign_init()
138 key.sign_update(zipdata) 178 key.sign_update(zipdata)
139 return key.final() 179 return key.final()
140 180
141 def getPublicKey(keyFile): 181 def getPublicKey(keyFile):
142 import M2Crypto 182 import M2Crypto
143 return M2Crypto.EVP.load_key(keyFile).as_der() 183 return M2Crypto.EVP.load_key(keyFile).as_der()
144 184
145 def writePackage(outputFile, pubkey, signature, zipdata): 185 def writePackage(outputFile, pubkey, signature, zipdata):
146 file = open(outputFile, 'wb') 186 if isinstance(outputFile, basestring):
187 file = open(outputFile, 'wb')
188 else:
189 file = outputFile
147 if pubkey != None and signature != None: 190 if pubkey != None and signature != None:
148 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) 191 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature)))
149 file.write(pubkey) 192 file.write(pubkey)
150 file.write(signature) 193 file.write(signature)
151 file.write(zipdata) 194 file.write(zipdata)
152 file.close()
153 195
154 def createBuild(baseDir, outFile=None, buildNum=None, releaseBuild=False, keyFil e=None, experimentalAPI=False): 196 def createBuild(baseDir, outFile=None, buildNum=None, releaseBuild=False, keyFil e=None, experimentalAPI=False, devenv=False):
155 metadata = readMetadata(baseDir) 197 metadata = readMetadata(baseDir)
156 version = readVersion(baseDir)
157 if outFile == None: 198 if outFile == None:
158 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el se 'zip') 199 outFile = getDefaultFileName(baseDir, metadata, 'crx' if keyFile else 'zip')
159 200
160 filters = [] 201 version = metadata.get('general', 'version')
161 if not releaseBuild: 202 if not releaseBuild:
162 if buildNum == None: 203 if buildNum == None:
163 buildNum = getBuildNum(baseDir) 204 buildNum = getBuildNum(baseDir)
164 filters.append(lambda zip, dir, fileName, fileData: addBuildNumber(buildNum, zip, dir, fileName, fileData)) 205 if len(buildNum) > 0:
206 while version.count('.') < 2:
207 version += '.0'
208 version += '.' + buildNum
165 209
166 baseName = metadata.get('general', 'basename') 210 params = {
167 updateName = baseName + '-experimental' if experimentalAPI else baseName 211 'baseDir': baseDir,
168 filters.append(lambda zip, dir, fileName, fileData: setUpdateURL(updateName, zip, dir, fileName, fileData)) 212 'releaseBuild': releaseBuild,
169 if experimentalAPI: 213 'version': version,
170 filters.append(setExperimentalSettings) 214 'experimentalAPI': experimentalAPI,
171 filters.append(mergeContentScripts) 215 'devenv': devenv,
216 'metadata': metadata,
217 }
172 218
173 zipdata = packDirectory(baseDir, filters) 219 files = {}
220 files['manifest.json'] = createManifest(params)
221 for path in getPackageFiles(params):
222 if os.path.exists(path):
223 readFile(params, files, path)
224
225 if metadata.has_section('convert_js') and os.path.isdir(os.path.join(baseDir, 'jshydra')):
226 convertJS(params, files)
227
228 zipdata = packFiles(files)
174 signature = None 229 signature = None
175 pubkey = None 230 pubkey = None
176 if keyFile != None: 231 if keyFile != None:
177 signature = signBinary(zipdata, keyFile) 232 signature = signBinary(zipdata, keyFile)
178 pubkey = getPublicKey(keyFile) 233 pubkey = getPublicKey(keyFile)
179 writePackage(outFile, pubkey, signature, zipdata) 234 writePackage(outFile, pubkey, signature, zipdata)
235
236 def createDevEnv(baseDir):
237 fileBuffer = StringIO()
238 createBuild(baseDir, outFile=fileBuffer, devenv=True, releaseBuild=True)
239 zip = ZipFile(StringIO(fileBuffer.getvalue()), 'r')
240 zip.extractall(os.path.join(baseDir, 'devenv'))
241 zip.close()
OLDNEW
« no previous file with comments | « manifest.json.tmpl ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld