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

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>]
14 # [--print_wxs]
15 #
16 # This script assumes that wix is either in path or passed in as --wix_bin.
17 # You can get wix from http://wixtoolset.org/.
18
19 import optparse
20 import os
21 import shutil
22 import subprocess
23 import sys
24 import utils
25 import zipfile
26
27 # This should _never_ change, please don't change this value.
28 UPGRADE_CODE = '7bacdc33-2e76-4f36-a206-ea58220c0b44'
29
30 # The content of the xml
31 xml_content = []
32
33 # The components we want to add to our feature.
34 feature_components = []
35
36 # Indentation level, each level is indented 2 spaces
37 current_indentation = 0
38
39 def GetOptions():
40 options = optparse.OptionParser(usage='usage: %prog [options]')
41 options.add_option("--zip_file_location",
42 help='Where the zip file including the editor is located.')
43 options.add_option("--msi_location",
44 help='Where to store the resulting msi.')
45 options.add_option("--version",
46 help='The version specified as Major.Minor.Build.Patch.')
47 options.add_option("--wix_bin",
48 help='The location of the wix binary files.')
49 options.add_option("--print_wxs", action="store_true", dest="print_wxs",
50 default=False,
51 help="Prints the generated wxs to stdout.")
52 return options.parse_args()
53
54 # We combine the build and patch into a single entry since
55 # the windows installer does _not_ consider a change in Patch
56 # to require a new install.
57 # In addition to that, the limits on the size are:
58 # Major: 256
59 # Minor: 256
60 # Build: 65536
61 # To circumvent this we create the version like this:
62 # Major.Minor.X
63 # where X is Build<<9 + Patch
64 # Example version 1.2.4.14 will go to 1.2.2062
65 def GetMicrosoftProductVersion(version):
66 version_parts = version.split('.')
67 if len(version_parts) is not 4:
68 raise Exception(
69 "Version string (%s) does not follow specification" % version)
70 (major, minor, build, patch) = map(int, version_parts)
71
72 if build > 127 or patch > 511:
73 raise Exception('Build/Patch can not be above 127/511')
74 if major > 255 or minor > 255:
75 raise Exception('Major/Minor can not be above 256')
76
77 combined = (build << 9) + patch
78 return '%s.%s.%s' % (major, minor, combined)
79
80 # Append using the current indentation level
81 def Append(data, new_line=True):
82 str = ((' ' * current_indentation) +
83 data +
84 ('\n' if new_line else ''))
85 xml_content.append(str)
86
87 # Append without any indentation at the current position
88 def AppendRaw(data, new_line=True):
89 xml_content.append(data + ('\n' if new_line else ''))
90
91 def AppendComment(comment):
92 Append('<!--%s-->' % comment)
93
94 def AppendBlankLine():
95 Append('')
96
97 def GetContent():
98 return ''.join(xml_content)
99
100 def XmlHeader():
101 Append('<?xml version="1.0" encoding="UTF-8"?>')
102
103 def TagIndent(str, indentation_string):
104 return ' ' * len(indentation_string) + str
105
106 def IncreaseIndentation():
107 global current_indentation
108 current_indentation += 1
109
110 def DecreaseIndentation():
111 global current_indentation
112 current_indentation -= 1
113
114 class WixAndProduct(object):
115 def __init__(self, version):
116 self.version = version
117 self.product_name = 'Dart Editor'
118 self.manufacturer = 'Google Inc.'
119 self.upgrade_code = UPGRADE_CODE
120
121 def __enter__(self):
122 self.start_wix()
123 self.start_product()
124
125 def __exit__(self, *_):
126 self.close_product()
127 self.close_wix()
128
129 def get_product_id(self):
130 # This needs to change on every install to guarantee that
131 # we get a full uninstall + reinstall
132 # We let wix choose. If we need to do patch releases later on
133 # we need to retain the value over several installs.
134 return '*'
135
136 def start_product(self):
137 product = '<Product '
138 Append(product, new_line=False)
139 AppendRaw('Id="%s"' % self.get_product_id())
140 Append(TagIndent('Version="%s"' % self.version, product))
141 Append(TagIndent('Name="%s"' % self.product_name, product))
142 Append(TagIndent('UpgradeCode="%s"' % self.upgrade_code,
143 product))
144 Append(TagIndent('Language="1033"', product))
145 Append(TagIndent('Manufacturer="%s"' % self.manufacturer,
146 product),
147 new_line=False)
148 AppendRaw('>')
149 IncreaseIndentation()
150
151 def close_product(self):
152 DecreaseIndentation()
153 Append('</Product>')
154
155 def start_wix(self):
156 Append('<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">')
157 IncreaseIndentation()
158
159 def close_wix(self):
160 DecreaseIndentation()
161 Append('</Wix>')
162
163 class Directory(object):
164 def __init__(self, id, name=None):
165 self.id = id
166 self.name = name
167
168 def __enter__(self):
169 directory = '<Directory '
170 Append(directory, new_line=False)
171 AppendRaw('Id="%s"' % self.id, new_line=self.name is not None)
172 if self.name:
173 Append(TagIndent('Name="%s"' % self.name, directory), new_line=False)
174 AppendRaw('>')
175 IncreaseIndentation()
176
177 def __exit__(self, *_):
178 DecreaseIndentation()
179 Append('</Directory>')
180
181 class Component(object):
182 def __init__(self, id):
183 self.id = 'CMP_%s' % id
184
185 def __enter__(self):
186 component = '<Component '
187 Append(component, new_line=False)
188 AppendRaw('Id="%s"' % self.id)
189 Append(TagIndent('Guid="*">', component))
190 IncreaseIndentation()
191
192 def __exit__(self, *_):
193 DecreaseIndentation()
194 Append('</Component>')
195 feature_components.append(self.id)
196
197 class Feature(object):
198 def __enter__(self):
199 feature = '<Feature '
200 Append(feature, new_line=False)
201 AppendRaw('Id="MainFeature"')
202 Append(TagIndent('Title="Dart Editor"', feature))
203 # Install by default
204 Append(TagIndent('Level="1">', feature))
205 IncreaseIndentation()
206
207 def __exit__(self, *_):
208 DecreaseIndentation()
209 Append('</Feature>')
210
211 def Package():
212 package = '<Package '
213 Append(package, new_line=False)
214 AppendRaw('InstallerVersion="301"')
215 Append(TagIndent('Compressed="yes" />', package))
216
217 def MediaTemplate():
218 Append('<MediaTemplate EmbedCab="yes" />')
219
220 def File(name, id):
221 file = '<File '
222 Append(file, new_line=False)
223 AppendRaw('Id="FILE_%s"' % id)
224 Append(TagIndent('Source="%s"' % name, file))
225 Append(TagIndent('KeyPath="yes" />', file))
226
227 def Shortcut(id, name, ref):
228 shortcut = '<Shortcut '
229 Append(shortcut, new_line=False)
230 AppendRaw('Id="%s"' % id)
231 Append(TagIndent('Name="%s"' % name, shortcut))
232 Append(TagIndent('Target="%s" />' % ref, shortcut))
233
234 def RemoveFolder(id):
235 remove = '<RemoveFolder '
236 Append(remove, new_line=False)
237 AppendRaw('Id="%s"' % id)
238 Append(TagIndent('On="uninstall" />', remove))
239
240 def RegistryEntry(location):
241 registry = '<RegistryValue '
242 Append(registry, new_line=False)
243 AppendRaw('Root="HKCU"')
244 Append(TagIndent('Key="Software\\Microsoft\\%s"' % location, registry))
245 Append(TagIndent('Name="installed"', registry))
246 Append(TagIndent('Type="integer"', registry))
247 Append(TagIndent('Value="1"', registry))
248 Append(TagIndent('KeyPath="yes" />', registry))
249
250
251 def MajorUpgrade():
252 upgrade = '<MajorUpgrade '
253 Append(upgrade, new_line=False)
254 down_message = 'You already have a never version installed.'
255 AppendRaw('DowngradeErrorMessage="%s" />' % down_message)
256
257
258 # This is a very simplistic id generation.
259 # Unfortunately there is no easy way to generate good names,
260 # since there is a 72 character limit, and we have way longer
261 # paths. We don't really have an issue with files and ids across
262 # releases since we do full installs.
263 counter = 0
264 def FileToId(name):
265 global counter
266 counter += 1
267 return '%s' % counter
268
269 def ListFiles(path):
270 for entry in os.listdir(path):
271 full_path = os.path.join(path, entry)
272 id = FileToId(full_path)
273 if os.path.isdir(full_path):
274 with Directory('DIR_%s' % id, entry):
275 ListFiles(full_path)
276 elif os.path.isfile(full_path):
277 # We assume 1 file per component, a File is always a KeyPath.
278 # A KeyPath on a file makes sure that we can always repair and
279 # remove that file in a consistent manner. A component
280 # can only have one child with a KeyPath.
281 with Component(id):
282 File(full_path, id)
283
284 def ComponentRefs():
285 for component in feature_components:
286 Append('<ComponentRef Id="%s" />' % component)
287
288 def ExtractZipFile(zip, temp_dir):
289 print 'Extracting files'
290 f = zipfile.ZipFile(zip)
291 f.extractall(temp_dir)
292 f.close()
293
294 def GenerateInstaller(wxs_content, options, temp_dir):
295 wxs_file = os.path.join(temp_dir, 'installer.wxs')
296 wixobj_file = os.path.join(temp_dir, 'installer.wixobj')
297 print 'Saving wxs output to: %s' % wxs_file
298 with open(wxs_file, 'w') as f:
299 f.write(wxs_content)
300
301 candle_bin = 'candle.exe'
302 light_bin = 'light.exe'
303 if options.wix_bin:
304 candle_bin = os.path.join(options.wix_bin, 'candle.exe')
305 light_bin = os.path.join(options.wix_bin, 'light.exe')
306 print 'Calling candle on %s' % wxs_file
307 subprocess.check_call('%s %s -o %s' % (candle_bin, wxs_file,
308 wixobj_file))
309 print 'Calling light on %s' % wixobj_file
310 subprocess.check_call('%s %s -o %s' % (light_bin, wixobj_file,
311 options.msi_location))
312 print 'Created msi file to %s' % options.msi_location
313
314 def Main(argv):
315 if sys.platform != 'win32':
316 raise Exception("This script can only be run on windows")
317 (options, args) = GetOptions()
318 if not options.version:
319 raise Exception('You must supply a version')
320 if not os.path.isfile(options.zip_file_location):
321 raise Exception('You must pass in a valid zip file')
322
323 version = GetMicrosoftProductVersion(options.version)
324 with utils.TempDir('installer') as temp_dir:
325 ExtractZipFile(options.zip_file_location, temp_dir)
326 print "Generating wix XML"
327 XmlHeader()
328 with WixAndProduct(version):
329 AppendBlankLine()
330 Package()
331 MediaTemplate()
332 AppendComment('We always do a major upgrade, at least for now')
333 MajorUpgrade()
334
335 AppendComment('Directory structure')
336 with Directory('TARGETDIR', 'SourceDir'):
337 with Directory('ProgramFilesFolder'):
338 with Directory('RootInstallDir', 'Dart Editor'):
339 AppendComment("Add all files and directories")
340 print 'Installing files and directories in xml'
341 ListFiles(os.path.join(temp_dir, 'dart'))
342 AppendBlankLine()
343 AppendComment("Create shortcuts")
344 with Directory('ProgramMenuFolder'):
345 with Directory('ShortcutFolder', 'Dart Editor'):
346 with Component('shortcut'):
347 # When generating a shortcut we need an entry with
348 # a KeyPath (RegistryEntry) below - to be able to remove
349 # the shortcut again. The RemoveFolder tag is needed
350 # to clean up everything
351 Shortcut('editor_shortcut', 'Dart Editor',
352 '[RootInstallDir]DartEditor.exe')
353 RemoveFolder('RemoveShortcuts')
354 RegistryEntry('DartEditor')
355 with Feature():
356 # We have only one feature and that consist of all the
357 # files=components we have listed above"
358 ComponentRefs()
359 xml = GetContent()
360 if options.print_wxs:
361 print xml
362 GenerateInstaller(xml, options, temp_dir)
363
364 if __name__ == '__main__':
365 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