blob: c208b9107494a23071fcf30a106216760f13eb8f [file] [log] [blame]
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +00001# Microsoft Installer Library
2# (C) 2003 Martin v. Loewis
3
4import win32com.client.gencache
5import win32com.client
6import pythoncom, pywintypes
7from win32com.client import constants
Martin v. Löwis49561ad2010-08-28 13:39:09 +00008import re, string, os, sets, glob, subprocess, sys, _winreg, struct, _msi
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +00009
10try:
11 basestring
12except NameError:
13 basestring = (str, unicode)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000014
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000015# Partially taken from Wine
16datasizemask= 0x00ff
17type_valid= 0x0100
18type_localizable= 0x0200
19
20typemask= 0x0c00
21type_long= 0x0000
22type_short= 0x0400
23type_string= 0x0c00
24type_binary= 0x0800
25
26type_nullable= 0x1000
27type_key= 0x2000
28# XXX temporary, localizable?
29knownbits = datasizemask | type_valid | type_localizable | \
30 typemask | type_nullable | type_key
31
32# Summary Info Property IDs
33PID_CODEPAGE=1
34PID_TITLE=2
35PID_SUBJECT=3
36PID_AUTHOR=4
37PID_KEYWORDS=5
38PID_COMMENTS=6
39PID_TEMPLATE=7
40PID_LASTAUTHOR=8
41PID_REVNUMBER=9
42PID_LASTPRINTED=11
43PID_CREATE_DTM=12
44PID_LASTSAVE_DTM=13
45PID_PAGECOUNT=14
46PID_WORDCOUNT=15
47PID_CHARCOUNT=16
48PID_APPNAME=18
49PID_SECURITY=19
50
51def reset():
52 global _directories
53 _directories = sets.Set()
54
55def EnsureMSI():
56 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0)
57
58def EnsureMSM():
59 try:
60 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 1, 0)
61 except pywintypes.com_error:
62 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 2, 0)
63
64_Installer=None
65def MakeInstaller():
66 global _Installer
67 if _Installer is None:
68 EnsureMSI()
69 _Installer = win32com.client.Dispatch('WindowsInstaller.Installer',
70 resultCLSID='{000C1090-0000-0000-C000-000000000046}')
71 return _Installer
72
73_Merge=None
74def MakeMerge2():
75 global _Merge
76 if _Merge is None:
77 EnsureMSM()
78 _Merge = win32com.client.Dispatch("Msm.Merge2.1")
79 return _Merge
80
81class Table:
82 def __init__(self, name):
83 self.name = name
84 self.fields = []
85
86 def add_field(self, index, name, type):
87 self.fields.append((index,name,type))
88
89 def sql(self):
90 fields = []
91 keys = []
92 self.fields.sort()
93 fields = [None]*len(self.fields)
94 for index, name, type in self.fields:
95 index -= 1
96 unk = type & ~knownbits
97 if unk:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +000098 print "%s.%s unknown bits %x" % (self.name, name, unk)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000099 size = type & datasizemask
100 dtype = type & typemask
101 if dtype == type_string:
102 if size:
103 tname="CHAR(%d)" % size
104 else:
105 tname="CHAR"
106 elif dtype == type_short:
107 assert size==2
108 tname = "SHORT"
109 elif dtype == type_long:
110 assert size==4
111 tname="LONG"
112 elif dtype == type_binary:
113 assert size==0
114 tname="OBJECT"
115 else:
116 tname="unknown"
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000117 print "%s.%sunknown integer type %d" % (self.name, name, size)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000118 if type & type_nullable:
119 flags = ""
120 else:
121 flags = " NOT NULL"
122 if type & type_localizable:
123 flags += " LOCALIZABLE"
124 fields[index] = "`%s` %s%s" % (name, tname, flags)
125 if type & type_key:
126 keys.append("`%s`" % name)
127 fields = ", ".join(fields)
128 keys = ", ".join(keys)
129 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys)
130
131 def create(self, db):
132 v = db.OpenView(self.sql())
133 v.Execute(None)
134 v.Close()
135
136class Binary:
137 def __init__(self, fname):
138 self.name = fname
139 def __repr__(self):
140 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name
141
142def gen_schema(destpath, schemapath):
143 d = MakeInstaller()
144 schema = d.OpenDatabase(schemapath,
145 win32com.client.constants.msiOpenDatabaseModeReadOnly)
146
147 # XXX ORBER BY
148 v=schema.OpenView("SELECT * FROM _Columns")
149 curtable=None
150 tables = []
151 v.Execute(None)
152 f = open(destpath, "wt")
153 f.write("from msilib import Table\n")
154 while 1:
155 r=v.Fetch()
156 if not r:break
157 name=r.StringData(1)
158 if curtable != name:
159 f.write("\n%s = Table('%s')\n" % (name,name))
160 curtable = name
161 tables.append(name)
162 f.write("%s.add_field(%d,'%s',%d)\n" %
163 (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4)))
164 v.Close()
165
166 f.write("\ntables=[%s]\n\n" % (", ".join(tables)))
167
168 # Fill the _Validation table
169 f.write("_Validation_records = [\n")
170 v = schema.OpenView("SELECT * FROM _Validation")
171 v.Execute(None)
172 while 1:
173 r = v.Fetch()
174 if not r:break
175 # Table, Column, Nullable
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000176 f.write("(%s,%s,%s," %
177 (`r.StringData(1)`, `r.StringData(2)`, `r.StringData(3)`))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000178 def put_int(i):
179 if r.IsNull(i):f.write("None, ")
180 else:f.write("%d," % r.IntegerData(i))
181 def put_str(i):
182 if r.IsNull(i):f.write("None, ")
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000183 else:f.write("%s," % `r.StringData(i)`)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000184 put_int(4) # MinValue
185 put_int(5) # MaxValue
186 put_str(6) # KeyTable
187 put_int(7) # KeyColumn
188 put_str(8) # Category
189 put_str(9) # Set
190 put_str(10)# Description
191 f.write("),\n")
192 f.write("]\n\n")
193
Tim Peters94607dd2004-08-22 19:42:56 +0000194 f.close()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000195
196def gen_sequence(destpath, msipath):
197 dir = os.path.dirname(destpath)
198 d = MakeInstaller()
199 seqmsi = d.OpenDatabase(msipath,
200 win32com.client.constants.msiOpenDatabaseModeReadOnly)
201
202 v = seqmsi.OpenView("SELECT * FROM _Tables");
203 v.Execute(None)
204 f = open(destpath, "w")
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000205 print >>f, "import msilib,os;dirname=os.path.dirname(__file__)"
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000206 tables = []
207 while 1:
208 r = v.Fetch()
209 if not r:break
210 table = r.StringData(1)
211 tables.append(table)
212 f.write("%s = [\n" % table)
213 v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table)
214 v1.Execute(None)
215 info = v1.ColumnInfo(constants.msiColumnInfoTypes)
216 while 1:
217 r = v1.Fetch()
218 if not r:break
219 rec = []
220 for i in range(1,r.FieldCount+1):
221 if r.IsNull(i):
222 rec.append(None)
223 elif info.StringData(i)[0] in "iI":
224 rec.append(r.IntegerData(i))
225 elif info.StringData(i)[0] in "slSL":
226 rec.append(r.StringData(i))
227 elif info.StringData(i)[0]=="v":
228 size = r.DataSize(i)
229 bytes = r.ReadStream(i, size, constants.msiReadStreamBytes)
230 bytes = bytes.encode("latin-1") # binary data represented "as-is"
231 if table == "Binary":
232 fname = rec[0]+".bin"
233 open(os.path.join(dir,fname),"wb").write(bytes)
234 rec.append(Binary(fname))
235 else:
236 rec.append(bytes)
237 else:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000238 raise "Unsupported column type", info.StringData(i)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000239 f.write(repr(tuple(rec))+",\n")
240 v1.Close()
241 f.write("]\n\n")
242 v.Close()
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000243 f.write("tables=%s\n" % repr(map(str,tables)))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000244 f.close()
245
246class _Unspecified:pass
247def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified):
248 "Change the sequence number of an action in a sequence list"
249 for i in range(len(seq)):
250 if seq[i][0] == action:
251 if cond is _Unspecified:
252 cond = seq[i][1]
253 if seqno is _Unspecified:
254 seqno = seq[i][2]
255 seq[i] = (action, cond, seqno)
256 return
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000257 raise ValueError, "Action not found in sequence"
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000258
259def add_data(db, table, values):
260 d = MakeInstaller()
261 v = db.OpenView("SELECT * FROM `%s`" % table)
262 count = v.ColumnInfo(0).FieldCount
263 r = d.CreateRecord(count)
264 for value in values:
265 assert len(value) == count, value
266 for i in range(count):
267 field = value[i]
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000268 if isinstance(field, (int, long)):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000269 r.SetIntegerData(i+1,field)
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000270 elif isinstance(field, basestring):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000271 r.SetStringData(i+1,field)
272 elif field is None:
273 pass
274 elif isinstance(field, Binary):
275 r.SetStream(i+1, field.name)
276 else:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000277 raise TypeError, "Unsupported type %s" % field.__class__.__name__
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000278 v.Modify(win32com.client.constants.msiViewModifyInsert, r)
279 r.ClearData()
280 v.Close()
281
282def add_stream(db, name, path):
283 d = MakeInstaller()
284 v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name)
285 r = d.CreateRecord(1)
286 r.SetStream(1, path)
287 v.Execute(r)
288 v.Close()
289
290def init_database(name, schema,
291 ProductName, ProductCode, ProductVersion,
Amaury Forgeot d'Arc35c86582008-06-17 21:11:29 +0000292 Manufacturer,
293 request_uac = False):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000294 try:
295 os.unlink(name)
296 except OSError:
297 pass
298 ProductCode = ProductCode.upper()
299 d = MakeInstaller()
300 # Create the database
301 db = d.OpenDatabase(name,
302 win32com.client.constants.msiOpenDatabaseModeCreate)
303 # Create the tables
304 for t in schema.tables:
305 t.create(db)
306 # Fill the validation table
307 add_data(db, "_Validation", schema._Validation_records)
Ezio Melotti7c4a7e62013-08-26 01:32:56 +0300308 # Initialize the summary information, allowing at most 20 properties
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000309 si = db.GetSummaryInformation(20)
310 si.SetProperty(PID_TITLE, "Installation Database")
311 si.SetProperty(PID_SUBJECT, ProductName)
312 si.SetProperty(PID_AUTHOR, Manufacturer)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000313 si.SetProperty(PID_TEMPLATE, msi_type)
Martin v. Löwis1e3a2642004-09-10 11:55:32 +0000314 si.SetProperty(PID_REVNUMBER, gen_uuid())
Amaury Forgeot d'Arc35c86582008-06-17 21:11:29 +0000315 if request_uac:
316 wc = 2 # long file names, compressed, original media
317 else:
318 wc = 2 | 8 # +never invoke UAC
319 si.SetProperty(PID_WORDCOUNT, wc)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000320 si.SetProperty(PID_PAGECOUNT, 200)
321 si.SetProperty(PID_APPNAME, "Python MSI Library")
322 # XXX more properties
323 si.Persist()
324 add_data(db, "Property", [
325 ("ProductName", ProductName),
326 ("ProductCode", ProductCode),
327 ("ProductVersion", ProductVersion),
328 ("Manufacturer", Manufacturer),
329 ("ProductLanguage", "1033")])
330 db.Commit()
331 return db
332
333def add_tables(db, module):
334 for table in module.tables:
335 add_data(db, table, getattr(module, table))
336
337def make_id(str):
338 #str = str.replace(".", "_") # colons are allowed
339 str = str.replace(" ", "_")
340 str = str.replace("-", "_")
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000341 str = str.replace("+", "_")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000342 if str[0] in string.digits:
343 str = "_"+str
344 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
345 return str
346
347def gen_uuid():
348 return str(pythoncom.CreateGuid())
349
350class CAB:
351 def __init__(self, name):
352 self.name = name
Martin v. Löwis49561ad2010-08-28 13:39:09 +0000353 self.files = []
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000354 self.filenames = sets.Set()
355 self.index = 0
356
357 def gen_id(self, dir, file):
358 logical = _logical = make_id(file)
359 pos = 1
360 while logical in self.filenames:
361 logical = "%s.%d" % (_logical, pos)
362 pos += 1
363 self.filenames.add(logical)
364 return logical
365
366 def append(self, full, file, logical = None):
367 if os.path.isdir(full):
368 return
369 if not logical:
370 logical = self.gen_id(dir, file)
371 self.index += 1
Martin v. Löwis49561ad2010-08-28 13:39:09 +0000372 self.files.append((full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000373 return self.index, logical
374
375 def commit(self, db):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000376 try:
377 os.unlink(self.name+".cab")
378 except OSError:
379 pass
Martin v. Löwis49561ad2010-08-28 13:39:09 +0000380 _msi.FCICreate(self.name+".cab", self.files)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000381 add_data(db, "Media",
382 [(1, self.index, None, "#"+self.name, None, None)])
383 add_stream(db, self.name, self.name+".cab")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000384 os.unlink(self.name+".cab")
385 db.Commit()
386
387_directories = sets.Set()
388class Directory:
389 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
390 """Create a new directory in the Directory table. There is a current component
391 at each point in time for the directory, which is either explicitly created
392 through start_component, or implicitly when files are added for the first
393 time. Files are added into the current component, and into the cab file.
394 To create a directory, a base directory object needs to be specified (can be
395 None), the path to the physical directory, and a logical directory name.
396 Default specifies the DefaultDir slot in the directory table. componentflags
397 specifies the default flags that new components get."""
398 index = 1
399 _logical = make_id(_logical)
400 logical = _logical
401 while logical in _directories:
402 logical = "%s%d" % (_logical, index)
403 index += 1
404 _directories.add(logical)
405 self.db = db
406 self.cab = cab
407 self.basedir = basedir
408 self.physical = physical
409 self.logical = logical
410 self.component = None
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100411 self.short_names = {}
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000412 self.ids = sets.Set()
413 self.keyfiles = {}
414 self.componentflags = componentflags
415 if basedir:
416 self.absolute = os.path.join(basedir.absolute, physical)
417 blogical = basedir.logical
418 else:
419 self.absolute = physical
420 blogical = None
Martin v. Löwis797721b2010-08-03 18:35:55 +0000421 # initially assume that all files in this directory are unpackaged
422 # as files from self.absolute get added, this set is reduced
423 self.unpackaged_files = set()
424 for f in os.listdir(self.absolute):
425 if os.path.isfile(os.path.join(self.absolute, f)):
426 self.unpackaged_files.add(f)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000427 add_data(db, "Directory", [(logical, blogical, default)])
428
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000429 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000430 """Add an entry to the Component table, and make this component the current for this
431 directory. If no component name is given, the directory name is used. If no feature
432 is given, the current feature is used. If no flags are given, the directory's default
433 flags are used. If no keyfile is given, the KeyPath is left null in the Component
434 table."""
435 if flags is None:
436 flags = self.componentflags
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000437 if uuid is None:
438 uuid = gen_uuid()
439 else:
440 uuid = uuid.upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000441 if component is None:
442 component = self.logical
443 self.component = component
444 if Win64:
445 flags |= 256
446 if keyfile:
447 keyid = self.cab.gen_id(self.absolute, keyfile)
448 self.keyfiles[keyfile] = keyid
449 else:
450 keyid = None
451 add_data(self.db, "Component",
452 [(component, uuid, self.logical, flags, None, keyid)])
453 if feature is None:
454 feature = current_feature
455 add_data(self.db, "FeatureComponents",
456 [(feature.id, component)])
457
458 def make_short(self, file):
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100459 long = file
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000460 file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100461 parts = file.split(".", 1)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000462 if len(parts)>1:
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100463 suffix = parts[1].upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000464 else:
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100465 suffix = ''
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000466 prefix = parts[0].upper()
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100467 if len(prefix) <= 8 and '.' not in suffix and len(suffix) <= 3:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000468 if suffix:
469 file = prefix+"."+suffix
470 else:
471 file = prefix
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100472 assert file not in self.short_names, (file, self.short_names[file])
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000473 else:
474 prefix = prefix[:6]
475 if suffix:
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100476 # last three characters of last suffix
477 suffix = suffix.rsplit('.')[-1][:3]
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000478 pos = 1
479 while 1:
480 if suffix:
481 file = "%s~%d.%s" % (prefix, pos, suffix)
482 else:
483 file = "%s~%d" % (prefix, pos)
484 if file not in self.short_names: break
485 pos += 1
486 assert pos < 10000
487 if pos in (10, 100, 1000):
488 prefix = prefix[:-1]
Martin v. Löwis61f996b2012-02-21 18:49:10 +0100489 self.short_names[file] = long
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000490 return file
491
492 def add_file(self, file, src=None, version=None, language=None):
493 """Add a file to the current component of the directory, starting a new one
Terry Jan Reedy65e69b32013-03-11 17:23:46 -0400494 if there is no current component. By default, the file name in the source
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000495 and the file table will be identical. If the src file is specified, it is
496 interpreted relative to the current directory. Optionally, a version and a
497 language can be specified for the entry in the File table."""
498 if not self.component:
499 self.start_component(self.logical, current_feature)
500 if not src:
501 # Allow relative paths for file if src is not specified
502 src = file
503 file = os.path.basename(file)
504 absolute = os.path.join(self.absolute, src)
Martin v. Löwis797721b2010-08-03 18:35:55 +0000505 if absolute.startswith(self.absolute):
506 # mark file as packaged
507 relative = absolute[len(self.absolute)+1:]
508 if relative in self.unpackaged_files:
509 self.unpackaged_files.remove(relative)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000510 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000511 if self.keyfiles.has_key(file):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000512 logical = self.keyfiles[file]
513 else:
514 logical = None
515 sequence, logical = self.cab.append(absolute, file, logical)
516 assert logical not in self.ids
517 self.ids.add(logical)
518 short = self.make_short(file)
519 full = "%s|%s" % (short, file)
520 filesize = os.stat(absolute).st_size
521 # constants.msidbFileAttributesVital
522 # Compressed omitted, since it is the database default
523 # could add r/o, system, hidden
Tim Peters94607dd2004-08-22 19:42:56 +0000524 attributes = 512
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000525 add_data(self.db, "File",
526 [(logical, self.component, full, filesize, version,
527 language, attributes, sequence)])
528 if not version:
529 # Add hash if the file is not versioned
530 filehash = MakeInstaller().FileHash(absolute, 0)
531 add_data(self.db, "MsiFileHash",
532 [(logical, 0, filehash.IntegerData(1),
533 filehash.IntegerData(2), filehash.IntegerData(3),
534 filehash.IntegerData(4))])
535 # Automatically remove .pyc/.pyo files on uninstall (2)
536 # XXX: adding so many RemoveFile entries makes installer unbelievably
537 # slow. So instead, we have to use wildcard remove entries
538 # if file.endswith(".py"):
539 # add_data(self.db, "RemoveFile",
540 # [(logical+"c", self.component, "%sC|%sc" % (short, file),
541 # self.logical, 2),
542 # (logical+"o", self.component, "%sO|%so" % (short, file),
543 # self.logical, 2)])
544
545 def glob(self, pattern, exclude = None):
546 """Add a list of files to the current component as specified in the
547 glob pattern. Individual files can be excluded in the exclude list."""
548 files = glob.glob1(self.absolute, pattern)
549 for f in files:
550 if exclude and f in exclude: continue
551 self.add_file(f)
552 return files
553
554 def remove_pyc(self):
Martin v. Löwis2e3d5392010-08-06 10:43:31 +0000555 "Remove .pyc/.pyo files from __pycache__ on uninstall"
556 directory = self.logical + "_pycache"
557 add_data(self.db, "Directory", [(directory, self.logical, "__PYCA~1|__pycache__")])
558 flags = 256 if Win64 else 0
559 add_data(self.db, "Component",
560 [(directory, gen_uuid(), directory, flags, None, None)])
561 add_data(self.db, "FeatureComponents", [(current_feature.id, directory)])
562 add_data(self.db, "CreateFolder", [(directory, directory)])
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000563 add_data(self.db, "RemoveFile",
Martin v. Löwis2e3d5392010-08-06 10:43:31 +0000564 [(self.component, self.component, "*.*", directory, 2),
565 ])
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000566
Martin v. Löwis90cc5ab2008-06-02 10:04:16 +0000567 def removefile(self, key, pattern):
568 "Add a RemoveFile entry"
569 add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)])
570
571
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000572class Feature:
573 def __init__(self, db, id, title, desc, display, level = 1,
574 parent=None, directory = None, attributes=0):
575 self.id = id
576 if parent:
577 parent = parent.id
578 add_data(db, "Feature",
579 [(id, parent, title, desc, display,
580 level, directory, attributes)])
581 def set_current(self):
582 global current_feature
583 current_feature = self
584
585class Control:
586 def __init__(self, dlg, name):
587 self.dlg = dlg
588 self.name = name
589
590 def event(self, ev, arg, cond = "1", order = None):
591 add_data(self.dlg.db, "ControlEvent",
592 [(self.dlg.name, self.name, ev, arg, cond, order)])
593
594 def mapping(self, ev, attr):
595 add_data(self.dlg.db, "EventMapping",
596 [(self.dlg.name, self.name, ev, attr)])
597
598 def condition(self, action, condition):
599 add_data(self.dlg.db, "ControlCondition",
600 [(self.dlg.name, self.name, action, condition)])
601
602class RadioButtonGroup(Control):
603 def __init__(self, dlg, name, property):
604 self.dlg = dlg
605 self.name = name
606 self.property = property
607 self.index = 1
608
609 def add(self, name, x, y, w, h, text, value = None):
610 if value is None:
611 value = name
612 add_data(self.dlg.db, "RadioButton",
613 [(self.property, self.index, value,
614 x, y, w, h, text, None)])
615 self.index += 1
616
617class Dialog:
618 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
619 self.db = db
620 self.name = name
621 self.x, self.y, self.w, self.h = x,y,w,h
622 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
623
624 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
625 add_data(self.db, "Control",
626 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
627 return Control(self, name)
628
629 def text(self, name, x, y, w, h, attr, text):
630 return self.control(name, "Text", x, y, w, h, attr, None,
631 text, None, None)
632
633 def bitmap(self, name, x, y, w, h, text):
634 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
Tim Peters94607dd2004-08-22 19:42:56 +0000635
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000636 def line(self, name, x, y, w, h):
637 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
638
639 def pushbutton(self, name, x, y, w, h, attr, text, next):
640 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
641
642 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
643 add_data(self.db, "Control",
644 [(self.name, name, "RadioButtonGroup",
645 x, y, w, h, attr, prop, text, next, None)])
646 return RadioButtonGroup(self, name, prop)
647
648 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
Tim Peters94607dd2004-08-22 19:42:56 +0000649 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000650
651def pe_type(path):
Martin v. Löwisd149c212006-03-05 13:52:20 +0000652 header = open(path, "rb").read(1000)
653 # offset of PE header is at offset 0x3c
654 pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000655 assert header[pe_offset:pe_offset+4] == "PE\0\0"
656 machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
657 return machine
658
659def set_arch_from_file(path):
660 global msi_type, Win64, arch_ext
661 machine = pe_type(path)
662 if machine == 0x14c:
663 # i386
664 msi_type = "Intel"
665 Win64 = 0
666 arch_ext = ''
667 elif machine == 0x200:
668 # Itanium
669 msi_type = "Intel64"
670 Win64 = 1
671 arch_ext = '.ia64'
672 elif machine == 0x8664:
673 # AMD64
674 msi_type = "x64"
675 Win64 = 1
676 arch_ext = '.amd64'
677 else:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000678 raise ValueError, "Unsupported architecture"
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000679 msi_type += ";1033"