Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(164)

Side by Side Diff: tools/create_windows_installer.py

Issue 43273003: Add script for generating a windows installer from an archived editor bundle. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | 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
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
4 # for details. All rights reserved. Use of this source code is governed by a
5 # BSD-style license that can be found in the LICENSE file.
6
7 # A script to generate a windows installer for the editor bundle.
8 # As input the script takes a zip file, a version and the location
9 # to store the resulting msi file in.
10 #
11 # Usage: ./tools/create_windows_installer.py --version <version>
12 # --zip_file_location <zip_file> --msi_location <output>
13 # [--wix_bin <wix_bin_location>]
kustermann 2013/10/25 09:39:07 Add "--print_wxs" as well here.
ricow1 2013/10/28 08:57:23 Done.
14 #
15 # This script assumes that wix is either in path or passed in as --wis_bin.
kustermann 2013/10/25 09:39:07 wis_bin -> wix_bin
ricow1 2013/10/28 08:57:23 Done.
16 # You can get wix from http://wixtoolset.org/.
17
18 import optparse
19 import os
20 import shutil
21 import subprocess
22 import sys
23 import utils
24 import zipfile
25
26 # This should _never_ change, please don't change this value.
27 UPGRADE_CODE = '7bacdc33-2e76-4f36-a206-ea58220c0b44'
28
29 # The content of the xml
30 xml_content = []
31
32 # The components we want to add to our feature.
33 feature_components = []
34
35 # Indentation level, each level is indented 2 spaces
36 current_indentation = 0
37
38 def GetOptions():
39 options = optparse.OptionParser(usage='usage: %prog [options]')
40 options.add_option("--zip_file_location",
41 help='Where the zip file including the editor is located.')
42 options.add_option("--msi_location",
43 help='Where to store the resulting msi.')
44 options.add_option("--version",
45 help='The version specified as Major.Minor.Build.Patch.')
46 options.add_option("--wix_bin",
47 help='The location of the wix binary files.')
48 options.add_option("--print_wxs", action="store_true", dest="print_wxs",
49 default=False,
50 help="Prints the generated wxs to stdout.")
51 return options.parse_args()
52
53 # We combine the build and patch into a single entry since
54 # the windows installer does _not_ consider a change in Patch
55 # to require a new install.
56 # In addition to that, the limits on the size are:
57 # Major: 256
58 # Minor: 256
59 # Build: 65536
60 # To circumvent this we create the version like this:
61 # Major.Minor.X
62 # where X is Build<<9 + Patch
63 # Example version 1.2.4.14 will go to 1.2.2062
64 def GetVersion(version):
kustermann 2013/10/25 09:39:07 GetVersion -> GetMicrosoftProductVersion
ricow1 2013/10/28 08:57:23 Done.
65 split_string = version.split('.')
kustermann 2013/10/25 09:39:07 split_string -> version_parts
ricow1 2013/10/28 08:57:23 Done.
66 if len(split_string) is not 4:
67 raise Exception(
68 "Version string (%s) does not follow specification" % version)
69 # Convert all to int to check that they are integers
kustermann 2013/10/25 09:39:07 Remove comment, that's obvious.
ricow1 2013/10/28 08:57:23 Done.
70 major = int(split_string[0])
71 minor = int(split_string[1])
72 build = int(split_string[2])
73 patch = int(split_string[3])
kustermann 2013/10/25 09:39:07 (major, minor, build, patch) = map(int, version_pa
ricow1 2013/10/28 08:57:23 Done.
74 if build > 127 or patch > 511:
75 raise Exception('Build/Patch can not be above 127/511')
76 if major > 255 or minor > 255:
77 raise Exception('Major/Minor can not be above 256')
78
79 combined = (build << 9) + patch
80 return '%s.%s.%s' % (major, minor, combined)
81
82 # Append using the current indentation level
83 def Append(data, new_line=True):
84 to_append = []
85 for i in xrange(current_indentation):
86 to_append.append(' ')
87 to_append.append(data)
88 if new_line:
89 to_append.append('\n')
90 xml_content.append(''.join(to_append))
kustermann 2013/10/25 09:39:07 Make it simpler, something like: def Append(data,
ricow1 2013/10/28 08:57:23 Done.
91
92 # Append without any indentation at the current position
93 def AppendRaw(data, new_line=True):
94 if new_line:
95 xml_content.append(data + '\n')
96 else:
97 xml_content.append(data)
kustermann 2013/10/25 09:39:07 Maybe: xml_content.append(data + ('\n' if new_line
ricow1 2013/10/28 08:57:23 Done.
98
99 def AppendComment(comment):
100 Append('<!--%s-->' % comment)
kustermann 2013/10/25 09:39:07 Indentation
ricow1 2013/10/28 08:57:23 Done.
101
102 def AppendBlankLine():
103 Append('')
104
105 def GetContent():
106 return ''.join(xml_content)
107
108 def XmlHeader():
109 Append('<?xml version="1.0" encoding="UTF-8"?>')
110
111 def TagIndent(str, indentation_string):
112 to_append = []
113 for x in indentation_string:
114 to_append.append(' ')
115 to_append.append(str)
116 return ''.join(to_append)
kustermann 2013/10/25 09:39:07 return ' ' * len(indentation_string) + str
ricow1 2013/10/28 08:57:23 Done.
117
118 def IncreaseIndentation():
119 global current_indentation
120 current_indentation += 1
121
122 def DecreaseIndentation():
123 global current_indentation
124 current_indentation -= 1
125
126
127 GUID_PREFETCH_SIZE = 200
kustermann 2013/10/25 09:39:07 remove this GUID code, since you don't use it.
ricow1 2013/10/28 08:57:23 Done.
128 GUID_CACHE = []
129 def FillGuidCache():
130 p = subprocess.Popen("uuidgen -n%s" % GUID_PREFETCH_SIZE,
131 stdout=subprocess.PIPE,
132 stderr=subprocess.PIPE,
133 shell=True)
134 output, stderr = p.communicate()
135 if p.returncode != 0:
136 raise Exception(
137 "Could not get GUIDs, make sure uuidgen is installed")
138
139 for guid in output.splitlines():
140 GUID_CACHE.append(guid)
141
142 def GetGuid():
143 if len(GUID_CACHE) == 0:
144 FillGuidCache()
145 return GUID_CACHE.pop()
146
147
148 class WixAndProduct(object):
149 def get_product_id(self):
150 # This needs to change on every install to guarantee that
151 # we get a full uninstall + reinstall
152 # We let wix choose. If we need to do patch releases later on
153 # we need to retain the value over several installs.
154 return '*'
155
156 def get_product_name(self):
157 return 'Dart Editor'
158
159 def get_manufacturer(self):
160 return "Google"
161
162 def get_upgrade_code(self):
163 return UPGRADE_CODE
kustermann 2013/10/25 09:39:07 You could remove these two methods and use instanc
ricow1 2013/10/28 08:57:23 Done.
164
165 def start_product(self):
166 product = '<Product '
167 Append(product, new_line=False)
168 AppendRaw('Id="%s"' % self.get_product_id())
169 Append(TagIndent('Version="%s"' % self.version, product))
170 Append(TagIndent('Name="%s"' % self.get_product_name(), product))
171 Append(TagIndent('UpgradeCode="%s"' % self.get_upgrade_code(),
172 product))
173 Append(TagIndent('Language="1033"', product))
174 Append(TagIndent('Manufacturer="%s"' % self.get_manufacturer(),
175 product),
kustermann 2013/10/25 09:39:07 You always create these things manually, you could
ricow1 2013/10/28 08:57:23 Thought about it but decided against it to make th
176 new_line=False)
177 AppendRaw('>')
178 IncreaseIndentation()
179
180 def close_product(self):
181 DecreaseIndentation()
182 Append('</Product>')
183
184 def start_wix(self):
185 Append('<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">')
186 IncreaseIndentation()
kustermann 2013/10/25 09:39:07 You could extract the <Wix ...></Wix> to a differe
ricow1 2013/10/28 08:57:23 That would be 2 extra line, I really can't do that
187
188 def close_wix(self):
189 DecreaseIndentation()
190 Append('</Wix>')
191
192 def __init__(self, version):
193 self.version = version
194
195 def __enter__(self):
196 self.start_wix()
197 self.start_product()
198
199 def __exit__(self, *_):
200 self.close_product()
201 self.close_wix()
kustermann 2013/10/25 09:39:07 Move these three methods up (constructors should a
ricow1 2013/10/28 08:57:23 Done.
202
203 class Directory(object):
204 def __init__(self, id, name=None):
205 self.id = id
206 self.name = name
207
208 def __enter__(self):
209 directory = '<Directory '
210 Append(directory, new_line=False)
211 AppendRaw('Id="%s"' % self.id, new_line=self.name is not None)
212 if self.name:
213 Append(TagIndent('Name="%s"' % self.name, directory), new_line=False)
214 AppendRaw('>')
215 IncreaseIndentation()
216
217 def __exit__(self, *_):
218 DecreaseIndentation()
219 Append('</Directory>')
220
221 class Component(object):
222 def __init__(self, id):
223 self.id = 'CMP_%s' % id
224
225 def __exit__(self, *_):
226 DecreaseIndentation()
227 Append('</Component>')
228 feature_components.append(self.id)
229
230 def __enter__(self):
231 component = '<Component '
232 Append(component, new_line=False)
233 AppendRaw('Id="%s"' % self.id)
234 Append(TagIndent('Guid="*">', component))
235 IncreaseIndentation()
236
237 class Feature(object):
238 def __exit__(self, *_):
kustermann 2013/10/25 09:39:07 Sometimes you have "__enter__" before "__exit__" a
ricow1 2013/10/28 08:57:23 Done.
239 DecreaseIndentation()
240 Append('</Feature>')
241
242 def __enter__(self):
243 feature = '<Feature '
244 Append(feature, new_line=False)
245 AppendRaw('Id="MainFeature"')
246 Append(TagIndent('Title="Dart Editor"', feature))
247 # Install by default
248 Append(TagIndent('Level="1">', feature))
249 IncreaseIndentation()
250
251 def Package():
252 package = '<Package '
253 Append(package, new_line=False)
254 AppendRaw('InstallerVersion="301"')
255 Append(TagIndent('Compressed="yes" />', package))
256
257 def MediaTemplate():
258 Append('<MediaTemplate EmbedCab="yes" />')
259
260 def File(name, id):
261 file = '<File '
262 Append(file, new_line=False)
263 AppendRaw('Id="FILE_%s"' % id)
264 Append(TagIndent('Source="%s"' % name, file))
265 Append(TagIndent('KeyPath="yes" />', file))
266
267 def Shortcut(id, name, ref):
268 shortcut = '<Shortcut '
269 Append(shortcut, new_line=False)
270 AppendRaw('Id="%s"' % id)
271 Append(TagIndent('Name="%s"' % name, shortcut))
272 Append(TagIndent('Target="%s" />' % ref, shortcut))
273
274 def RemoveFolder(id):
275 remove = '<RemoveFolder '
276 Append(remove, new_line=False)
277 AppendRaw('Id="%s"' % id)
278 Append(TagIndent('On="uninstall" />', remove))
279
280 def RegistryEntry(location):
281 registry = '<RegistryValue '
282 Append(registry, new_line=False)
283 AppendRaw('Root="HKCU"')
284 Append(TagIndent('Key="Software\Microsoft\%s"' % location, registry))
kustermann 2013/10/25 09:39:07 Make it a raw string or two \\.
ricow1 2013/10/28 08:57:23 Done.
285 Append(TagIndent('Name="installed"', registry))
286 Append(TagIndent('Type="integer"', registry))
287 Append(TagIndent('Value="1"', registry))
288 Append(TagIndent('KeyPath="yes" />', registry))
289
290
291 def MajorUpgrade():
292 upgrade = '<MajorUpgrade '
293 Append(upgrade, new_line=False)
294 down_message = 'You already have a never version installed.'
295 AppendRaw('DowngradeErrorMessage="%s" />' % down_message)
296
297
298 # This is a very simplistic id generation.
299 # Unfortunately there is no easy way to generate good names,
300 # since there is a 72 character limit, and we have way longer
301 # paths. We don't really have an issue with files and ids across
302 # releases since we do full installs.
303 counter = 0
304 def FileToId(name):
305 global counter
306 counter += 1
307 return '%s' % counter
308
309 def InstallFiles(path):
310 for entry in os.listdir(path):
311 full_path = os.path.join(path, entry)
312 id = FileToId(full_path)
313 if os.path.isdir(full_path):
314 with Directory('DIR_%s' % id, entry):
315 InstallFiles(full_path)
316 elif os.path.isfile(full_path):
317 # We assume 1 file per component, a File is always a KeyPath
kustermann 2013/10/25 09:39:07 Nobody knows what a 'KeyPath' is, you could add a
ricow1 2013/10/28 08:57:23 Done.
318 with Component(id):
319 File(full_path, id)
320
321 def ComponentRefs():
322 for component in feature_components:
323 Append('<ComponentRef Id="%s" />' % component)
324
325 def ExtractZipFile(zip, temp_dir):
326 print 'Extracting files'
327 if utils.IsWindows():
kustermann 2013/10/25 09:39:07 That is strange, I'd remove this and put an if s
ricow1 2013/10/28 08:57:23 Done.
328 f = zipfile.ZipFile(zip)
329 f.extractall(temp_dir)
330 f.close()
331
332 def GenerateInstaller(wxs_content, options, temp_dir):
333 wxs_file = os.path.join(temp_dir, 'installer.wxs')
kustermann 2013/10/25 09:39:07 Indentation
ricow1 2013/10/28 08:57:23 Done.
334 wixobj_file = os.path.join(temp_dir, 'installer.wixobj')
335 msi_file = os.path.join(temp_dir, 'installer.msi')
336 print 'Saving wxs output to: %s' % wxs_file
kustermann 2013/10/25 09:39:07 This comment is useless, since after running this
ricow1 2013/10/28 08:57:23 I disagree, it make you be able to see the progres
337 with open(wxs_file, 'w') as f:
338 f.write(wxs_content)
339
340 candle_bin = 'candle.exe'
341 light_bin = 'light.exe'
342 if options.wix_bin:
343 candle_bin = os.path.join(options.wix_bin, 'candle.exe')
344 light_bin = os.path.join(options.wix_bin, 'light.exe')
345 print 'Calling candle on %s' % wxs_file
346 subprocess.call('%s %s -o %s' % (candle_bin, wxs_file, wixobj_file))
kustermann 2013/10/25 09:39:07 Please use 'subprocess.check_call' or veryfiy that
ricow1 2013/10/28 08:57:23 Done.
347 print 'Calling light on %s' % wixobj_file
348 subprocess.call('%s %s -o %s' % (light_bin, wixobj_file, msi_file))
kustermann 2013/10/25 09:39:07 msi_file -> options.msi_location + remove msi_file
ricow1 2013/10/28 08:57:23 Done.
349 print 'Copying msi file to ' % options.msi_location
350 shutil.copyfile(msi_file, options.msi_location)
351
352 def Main(argv):
353 (options, args) = GetOptions()
354 if not options.version:
355 raise Exception('You must supply a version')
356 version = GetVersion(options.version)
kustermann 2013/10/25 09:39:07 You could validate here that the zip file exists.
ricow1 2013/10/28 08:57:23 Done.
357
358 with utils.TempDir('installer') as temp_dir:
359 ExtractZipFile(options.zip_file_location, temp_dir)
360 print "Generating wix XML"
361 XmlHeader()
362 with WixAndProduct(version):
363 AppendBlankLine()
364 root_dir = 'RootInstallDir'
kustermann 2013/10/25 09:39:07 You could actually remove this variable, and repla
ricow1 2013/10/28 08:57:23 ok
365 Package()
366 MediaTemplate()
367 AppendComment('We always do a major upgrade, at least for now')
368 MajorUpgrade()
369
370 AppendComment('Directory structure')
371 with Directory('TARGETDIR', 'SourceDir'):
372 with Directory('ProgramFilesFolder'):
373 with Directory(root_dir, 'Dart Editor'):
374 AppendComment("Add all files and directories")
375 print 'Installing files and directories in xml'
376 InstallFiles(os.path.join(temp_dir, 'dart'))
kustermann 2013/10/25 09:39:07 Maybe rename to ListFiles?
ricow1 2013/10/28 08:57:23 Done.
377 AppendBlankLine()
378 AppendComment("Create shortcuts")
379 with Directory('ProgramMenuFolder'):
380 with Directory('ShortcutFolder', 'Dart Editor'):
381 with Component('shortcut'):
382 Shortcut('editor_shortcut', 'Dart Editor',
383 '[%s]DartEditor.exe' % root_dir)
384 RemoveFolder('RemoveShortcuts')
385 RegistryEntry('DartEditor')
kustermann 2013/10/25 09:39:07 Add a comment explaining how these three calls fit
ricow1 2013/10/28 08:57:23 Done.
386
387 with Feature():
kustermann 2013/10/25 09:39:07 Add a comment, like "We have only one feature and
ricow1 2013/10/28 08:57:23 Done.
388 ComponentRefs()
389 xml = GetContent()
390 if options.print_wxs:
391 print xml
392 GenerateInstaller(xml, options, temp_dir)
393
394 if __name__ == '__main__':
395 sys.exit(Main(sys.argv))
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698