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: Fixed jshydra failures Created Dec. 20, 2012, 2:21 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): 87
115 addToZip(zip, filters, filePath, baseName + file + '/') 88 if metadata.has_option('general', 'permissions'):
89 templateData['permissions'] = re.split(r'\s+', metadata.get('general', 'perm issions'))
90 if params['experimentalAPI']:
91 templateData['permissions'].append('experimental')
92
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 if 'abp_rewrite' in sys.modules:
142 import abp_rewrite
143 reload(abp_rewrite.utils)
144 reload(abp_rewrite)
116 else: 145 else:
117 handle = open(filePath, 'rb') 146 import abp_rewrite
118 fileData = handle.read()
119 handle.close()
120 147
121 for filter in filters: 148 for file, sources in params['metadata'].items('convert_js'):
122 fileData = filter(zip, dir, baseName + file, fileData) 149 dirsep = file.find('/')
123 zip.writestr(baseName + file, fileData) 150 if dirsep >= 0:
151 # Not a top-level file, make sure it is inside an included director
152 dirname = file[0:dirsep]
153 if os.path.join(baseDir, dirname) not in getPackageFiles(params):
154 continue
124 155
125 def packDirectory(dir, filters): 156 sourceFiles = re.split(r'\s+', sources)
157 args = []
158 try:
159 argsStart = sourceFiles.index('--arg')
160 args = sourceFiles[argsStart + 1:]
161 sourceFiles = sourceFiles[0:argsStart]
162 except ValueError:
163 pass
164
165 sourceFiles = map(lambda f: os.path.abspath(os.path.join(baseDir, f)), sou rceFiles)
166 files[file] = abp_rewrite.doRewrite(sourceFiles, args)
167 finally:
168 sys.path.remove(hydraDir)
169
170 def packFiles(files):
126 buffer = StringIO() 171 buffer = StringIO()
127 zip = ZipFile(buffer, 'w', ZIP_DEFLATED) 172 zip = ZipFile(buffer, 'w', ZIP_DEFLATED)
128 addToZip(zip, filters, dir, '') 173 for file, data in files.iteritems():
174 zip.writestr(file, data)
129 zip.close() 175 zip.close()
130 return buffer.getvalue() 176 return buffer.getvalue()
131 177
132 def signBinary(zipdata, keyFile): 178 def signBinary(zipdata, keyFile):
133 import M2Crypto 179 import M2Crypto
134 if not os.path.exists(keyFile): 180 if not os.path.exists(keyFile):
135 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None) 181 M2Crypto.RSA.gen_key(1024, 65537, callback=lambda x: None).save_key(keyFile, cipher=None)
136 key = M2Crypto.EVP.load_key(keyFile) 182 key = M2Crypto.EVP.load_key(keyFile)
137 key.sign_init() 183 key.sign_init()
138 key.sign_update(zipdata) 184 key.sign_update(zipdata)
139 return key.final() 185 return key.final()
140 186
141 def getPublicKey(keyFile): 187 def getPublicKey(keyFile):
142 import M2Crypto 188 import M2Crypto
143 return M2Crypto.EVP.load_key(keyFile).as_der() 189 return M2Crypto.EVP.load_key(keyFile).as_der()
144 190
145 def writePackage(outputFile, pubkey, signature, zipdata): 191 def writePackage(outputFile, pubkey, signature, zipdata):
146 file = open(outputFile, 'wb') 192 if isinstance(outputFile, basestring):
193 file = open(outputFile, 'wb')
194 else:
195 file = outputFile
147 if pubkey != None and signature != None: 196 if pubkey != None and signature != None:
148 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature))) 197 file.write(struct.pack('<4sIII', 'Cr24', 2, len(pubkey), len(signature)))
149 file.write(pubkey) 198 file.write(pubkey)
150 file.write(signature) 199 file.write(signature)
151 file.write(zipdata) 200 file.write(zipdata)
152 file.close()
153 201
154 def createBuild(baseDir, outFile=None, buildNum=None, releaseBuild=False, keyFil e=None, experimentalAPI=False): 202 def createBuild(baseDir, outFile=None, buildNum=None, releaseBuild=False, keyFil e=None, experimentalAPI=False, devenv=False):
155 metadata = readMetadata(baseDir) 203 metadata = readMetadata(baseDir)
156 version = readVersion(baseDir)
157 if outFile == None: 204 if outFile == None:
158 outFile = getDefaultFileName(baseDir, metadata, version, 'crx' if keyFile el se 'zip') 205 outFile = getDefaultFileName(baseDir, metadata, 'crx' if keyFile else 'zip')
159 206
160 filters = [] 207 version = metadata.get('general', 'version')
161 if not releaseBuild: 208 if not releaseBuild:
162 if buildNum == None: 209 if buildNum == None:
163 buildNum = getBuildNum(baseDir) 210 buildNum = getBuildNum(baseDir)
164 filters.append(lambda zip, dir, fileName, fileData: addBuildNumber(buildNum, zip, dir, fileName, fileData)) 211 if len(buildNum) > 0:
212 while version.count('.') < 2:
213 version += '.0'
214 version += '.' + buildNum
165 215
166 baseName = metadata.get('general', 'basename') 216 params = {
167 updateName = baseName + '-experimental' if experimentalAPI else baseName 217 'baseDir': baseDir,
168 filters.append(lambda zip, dir, fileName, fileData: setUpdateURL(updateName, zip, dir, fileName, fileData)) 218 'releaseBuild': releaseBuild,
169 if experimentalAPI: 219 'version': version,
170 filters.append(setExperimentalSettings) 220 'experimentalAPI': experimentalAPI,
171 filters.append(mergeContentScripts) 221 'devenv': devenv,
222 'metadata': metadata,
223 }
172 224
173 zipdata = packDirectory(baseDir, filters) 225 files = {}
226 files['manifest.json'] = createManifest(params)
227 for path in getPackageFiles(params):
228 if os.path.exists(path):
229 readFile(params, files, path)
230
231 if metadata.has_section('convert_js') and os.path.isdir(os.path.join(baseDir, 'jshydra')):
232 convertJS(params, files)
233
234 zipdata = packFiles(files)
174 signature = None 235 signature = None
175 pubkey = None 236 pubkey = None
176 if keyFile != None: 237 if keyFile != None:
177 signature = signBinary(zipdata, keyFile) 238 signature = signBinary(zipdata, keyFile)
178 pubkey = getPublicKey(keyFile) 239 pubkey = getPublicKey(keyFile)
179 writePackage(outFile, pubkey, signature, zipdata) 240 writePackage(outFile, pubkey, signature, zipdata)
241
242 def createDevEnv(baseDir):
243 fileBuffer = StringIO()
244 createBuild(baseDir, outFile=fileBuffer, devenv=True, releaseBuild=True)
245 zip = ZipFile(StringIO(fileBuffer.getvalue()), 'r')
246 zip.extractall(os.path.join(baseDir, 'devenv'))
247 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