blob: 6b8a2b52894c2f7aded460631e9b305b77efc536 [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
Georg Brandl38feaf02008-05-25 07:45:51 +00008import re, string, os, sets, glob, subprocess, sys, winreg, struct
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +00009
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000010# Partially taken from Wine
11datasizemask= 0x00ff
12type_valid= 0x0100
13type_localizable= 0x0200
14
15typemask= 0x0c00
16type_long= 0x0000
17type_short= 0x0400
18type_string= 0x0c00
19type_binary= 0x0800
20
21type_nullable= 0x1000
22type_key= 0x2000
23# XXX temporary, localizable?
24knownbits = datasizemask | type_valid | type_localizable | \
25 typemask | type_nullable | type_key
26
27# Summary Info Property IDs
28PID_CODEPAGE=1
29PID_TITLE=2
30PID_SUBJECT=3
31PID_AUTHOR=4
32PID_KEYWORDS=5
33PID_COMMENTS=6
34PID_TEMPLATE=7
35PID_LASTAUTHOR=8
36PID_REVNUMBER=9
37PID_LASTPRINTED=11
38PID_CREATE_DTM=12
39PID_LASTSAVE_DTM=13
40PID_PAGECOUNT=14
41PID_WORDCOUNT=15
42PID_CHARCOUNT=16
43PID_APPNAME=18
44PID_SECURITY=19
45
46def reset():
47 global _directories
48 _directories = sets.Set()
49
50def EnsureMSI():
51 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0)
52
53def EnsureMSM():
54 try:
55 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 1, 0)
56 except pywintypes.com_error:
57 win32com.client.gencache.EnsureModule('{0ADDA82F-2C26-11D2-AD65-00A0C9AF11A6}', 0, 2, 0)
58
59_Installer=None
60def MakeInstaller():
61 global _Installer
62 if _Installer is None:
63 EnsureMSI()
64 _Installer = win32com.client.Dispatch('WindowsInstaller.Installer',
65 resultCLSID='{000C1090-0000-0000-C000-000000000046}')
66 return _Installer
67
68_Merge=None
69def MakeMerge2():
70 global _Merge
71 if _Merge is None:
72 EnsureMSM()
73 _Merge = win32com.client.Dispatch("Msm.Merge2.1")
74 return _Merge
75
76class Table:
77 def __init__(self, name):
78 self.name = name
79 self.fields = []
80
81 def add_field(self, index, name, type):
82 self.fields.append((index,name,type))
83
84 def sql(self):
85 fields = []
86 keys = []
87 self.fields.sort()
88 fields = [None]*len(self.fields)
89 for index, name, type in self.fields:
90 index -= 1
91 unk = type & ~knownbits
92 if unk:
Collin Winter6afaeb72007-08-03 17:06:41 +000093 print("%s.%s unknown bits %x" % (self.name, name, unk))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +000094 size = type & datasizemask
95 dtype = type & typemask
96 if dtype == type_string:
97 if size:
98 tname="CHAR(%d)" % size
99 else:
100 tname="CHAR"
101 elif dtype == type_short:
102 assert size==2
103 tname = "SHORT"
104 elif dtype == type_long:
105 assert size==4
106 tname="LONG"
107 elif dtype == type_binary:
108 assert size==0
109 tname="OBJECT"
110 else:
111 tname="unknown"
Collin Winter6afaeb72007-08-03 17:06:41 +0000112 print("%s.%sunknown integer type %d" % (self.name, name, size))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000113 if type & type_nullable:
114 flags = ""
115 else:
116 flags = " NOT NULL"
117 if type & type_localizable:
118 flags += " LOCALIZABLE"
119 fields[index] = "`%s` %s%s" % (name, tname, flags)
120 if type & type_key:
121 keys.append("`%s`" % name)
122 fields = ", ".join(fields)
123 keys = ", ".join(keys)
124 return "CREATE TABLE %s (%s PRIMARY KEY %s)" % (self.name, fields, keys)
125
126 def create(self, db):
127 v = db.OpenView(self.sql())
128 v.Execute(None)
129 v.Close()
130
131class Binary:
132 def __init__(self, fname):
133 self.name = fname
134 def __repr__(self):
135 return 'msilib.Binary(os.path.join(dirname,"%s"))' % self.name
136
137def gen_schema(destpath, schemapath):
138 d = MakeInstaller()
139 schema = d.OpenDatabase(schemapath,
140 win32com.client.constants.msiOpenDatabaseModeReadOnly)
141
142 # XXX ORBER BY
143 v=schema.OpenView("SELECT * FROM _Columns")
144 curtable=None
145 tables = []
146 v.Execute(None)
147 f = open(destpath, "wt")
148 f.write("from msilib import Table\n")
149 while 1:
150 r=v.Fetch()
151 if not r:break
152 name=r.StringData(1)
153 if curtable != name:
154 f.write("\n%s = Table('%s')\n" % (name,name))
155 curtable = name
156 tables.append(name)
157 f.write("%s.add_field(%d,'%s',%d)\n" %
158 (name, r.IntegerData(2), r.StringData(3), r.IntegerData(4)))
159 v.Close()
160
161 f.write("\ntables=[%s]\n\n" % (", ".join(tables)))
162
163 # Fill the _Validation table
164 f.write("_Validation_records = [\n")
165 v = schema.OpenView("SELECT * FROM _Validation")
166 v.Execute(None)
167 while 1:
168 r = v.Fetch()
169 if not r:break
170 # Table, Column, Nullable
Georg Brandlbf82e372008-05-16 17:02:34 +0000171 f.write("(%r,%r,%r," %
172 (r.StringData(1), r.StringData(2), r.StringData(3)))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000173 def put_int(i):
174 if r.IsNull(i):f.write("None, ")
175 else:f.write("%d," % r.IntegerData(i))
176 def put_str(i):
177 if r.IsNull(i):f.write("None, ")
Georg Brandlbf82e372008-05-16 17:02:34 +0000178 else:f.write("%r," % r.StringData(i))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000179 put_int(4) # MinValue
180 put_int(5) # MaxValue
181 put_str(6) # KeyTable
182 put_int(7) # KeyColumn
183 put_str(8) # Category
184 put_str(9) # Set
185 put_str(10)# Description
186 f.write("),\n")
187 f.write("]\n\n")
188
Tim Peters94607dd2004-08-22 19:42:56 +0000189 f.close()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000190
191def gen_sequence(destpath, msipath):
192 dir = os.path.dirname(destpath)
193 d = MakeInstaller()
194 seqmsi = d.OpenDatabase(msipath,
195 win32com.client.constants.msiOpenDatabaseModeReadOnly)
196
197 v = seqmsi.OpenView("SELECT * FROM _Tables");
198 v.Execute(None)
199 f = open(destpath, "w")
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000200 f.write("import msilib,os;dirname=os.path.dirname(__file__)\n")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000201 tables = []
202 while 1:
203 r = v.Fetch()
204 if not r:break
205 table = r.StringData(1)
206 tables.append(table)
207 f.write("%s = [\n" % table)
208 v1 = seqmsi.OpenView("SELECT * FROM `%s`" % table)
209 v1.Execute(None)
210 info = v1.ColumnInfo(constants.msiColumnInfoTypes)
211 while 1:
212 r = v1.Fetch()
213 if not r:break
214 rec = []
215 for i in range(1,r.FieldCount+1):
216 if r.IsNull(i):
217 rec.append(None)
218 elif info.StringData(i)[0] in "iI":
219 rec.append(r.IntegerData(i))
220 elif info.StringData(i)[0] in "slSL":
221 rec.append(r.StringData(i))
222 elif info.StringData(i)[0]=="v":
223 size = r.DataSize(i)
224 bytes = r.ReadStream(i, size, constants.msiReadStreamBytes)
225 bytes = bytes.encode("latin-1") # binary data represented "as-is"
226 if table == "Binary":
227 fname = rec[0]+".bin"
228 open(os.path.join(dir,fname),"wb").write(bytes)
229 rec.append(Binary(fname))
230 else:
231 rec.append(bytes)
232 else:
Georg Brandlbf82e372008-05-16 17:02:34 +0000233 raise ValueError("Unsupported column type", info.StringData(i))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000234 f.write(repr(tuple(rec))+",\n")
235 v1.Close()
236 f.write("]\n\n")
237 v.Close()
Georg Brandlbf82e372008-05-16 17:02:34 +0000238 f.write("tables=%s\n" % repr(list(map(str,tables))))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000239 f.close()
240
241class _Unspecified:pass
242def change_sequence(seq, action, seqno=_Unspecified, cond = _Unspecified):
243 "Change the sequence number of an action in a sequence list"
244 for i in range(len(seq)):
245 if seq[i][0] == action:
246 if cond is _Unspecified:
247 cond = seq[i][1]
248 if seqno is _Unspecified:
249 seqno = seq[i][2]
250 seq[i] = (action, cond, seqno)
251 return
Collin Wintera817e582007-08-22 23:05:06 +0000252 raise ValueError("Action not found in sequence")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000253
254def add_data(db, table, values):
255 d = MakeInstaller()
256 v = db.OpenView("SELECT * FROM `%s`" % table)
257 count = v.ColumnInfo(0).FieldCount
258 r = d.CreateRecord(count)
259 for value in values:
260 assert len(value) == count, value
261 for i in range(count):
262 field = value[i]
Georg Brandlbf82e372008-05-16 17:02:34 +0000263 if isinstance(field, int):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000264 r.SetIntegerData(i+1,field)
Georg Brandlbf82e372008-05-16 17:02:34 +0000265 elif isinstance(field, str):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000266 r.SetStringData(i+1,field)
267 elif field is None:
268 pass
269 elif isinstance(field, Binary):
270 r.SetStream(i+1, field.name)
271 else:
Collin Wintera817e582007-08-22 23:05:06 +0000272 raise TypeError("Unsupported type %s" % field.__class__.__name__)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000273 v.Modify(win32com.client.constants.msiViewModifyInsert, r)
274 r.ClearData()
275 v.Close()
276
277def add_stream(db, name, path):
278 d = MakeInstaller()
279 v = db.OpenView("INSERT INTO _Streams (Name, Data) VALUES ('%s', ?)" % name)
280 r = d.CreateRecord(1)
281 r.SetStream(1, path)
282 v.Execute(r)
283 v.Close()
284
285def init_database(name, schema,
286 ProductName, ProductCode, ProductVersion,
Amaury Forgeot d'Arc35c86582008-06-17 21:11:29 +0000287 Manufacturer,
288 request_uac = False):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000289 try:
290 os.unlink(name)
291 except OSError:
292 pass
293 ProductCode = ProductCode.upper()
294 d = MakeInstaller()
295 # Create the database
296 db = d.OpenDatabase(name,
297 win32com.client.constants.msiOpenDatabaseModeCreate)
298 # Create the tables
299 for t in schema.tables:
300 t.create(db)
301 # Fill the validation table
302 add_data(db, "_Validation", schema._Validation_records)
303 # Initialize the summary information, allowing atmost 20 properties
304 si = db.GetSummaryInformation(20)
305 si.SetProperty(PID_TITLE, "Installation Database")
306 si.SetProperty(PID_SUBJECT, ProductName)
307 si.SetProperty(PID_AUTHOR, Manufacturer)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000308 si.SetProperty(PID_TEMPLATE, msi_type)
Martin v. Löwis1e3a2642004-09-10 11:55:32 +0000309 si.SetProperty(PID_REVNUMBER, gen_uuid())
Amaury Forgeot d'Arc35c86582008-06-17 21:11:29 +0000310 if request_uac:
311 wc = 2 # long file names, compressed, original media
312 else:
313 wc = 2 | 8 # +never invoke UAC
314 si.SetProperty(PID_WORDCOUNT, wc)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000315 si.SetProperty(PID_PAGECOUNT, 200)
316 si.SetProperty(PID_APPNAME, "Python MSI Library")
317 # XXX more properties
318 si.Persist()
319 add_data(db, "Property", [
320 ("ProductName", ProductName),
321 ("ProductCode", ProductCode),
322 ("ProductVersion", ProductVersion),
323 ("Manufacturer", Manufacturer),
324 ("ProductLanguage", "1033")])
325 db.Commit()
326 return db
327
328def add_tables(db, module):
329 for table in module.tables:
330 add_data(db, table, getattr(module, table))
331
332def make_id(str):
333 #str = str.replace(".", "_") # colons are allowed
334 str = str.replace(" ", "_")
335 str = str.replace("-", "_")
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000336 str = str.replace("+", "_")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000337 if str[0] in string.digits:
338 str = "_"+str
339 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
340 return str
341
342def gen_uuid():
343 return str(pythoncom.CreateGuid())
344
345class CAB:
346 def __init__(self, name):
347 self.name = name
348 self.file = open(name+".txt", "wt")
349 self.filenames = sets.Set()
350 self.index = 0
351
352 def gen_id(self, dir, file):
353 logical = _logical = make_id(file)
354 pos = 1
355 while logical in self.filenames:
356 logical = "%s.%d" % (_logical, pos)
357 pos += 1
358 self.filenames.add(logical)
359 return logical
360
361 def append(self, full, file, logical = None):
362 if os.path.isdir(full):
363 return
364 if not logical:
365 logical = self.gen_id(dir, file)
366 self.index += 1
367 if full.find(" ")!=-1:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000368 self.file.write('"%s" %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000369 else:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000370 self.file.write('%s %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000371 return self.index, logical
372
373 def commit(self, db):
374 self.file.close()
375 try:
376 os.unlink(self.name+".cab")
377 except OSError:
378 pass
379 for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"),
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000380 (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"),
381 (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"),
382 (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"),
383 ]:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000384 try:
Georg Brandl38feaf02008-05-25 07:45:51 +0000385 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, k)
386 dir = winreg.QueryValueEx(key, v)[0]
387 winreg.CloseKey(key)
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000388 except (WindowsError, IndexError):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000389 continue
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000390 cabarc = os.path.join(dir, r"Bin", "cabarc.exe")
391 if not os.path.exists(cabarc):
392 continue
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000393 break
394 else:
Collin Winter6afaeb72007-08-03 17:06:41 +0000395 print("WARNING: cabarc.exe not found in registry")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000396 cabarc = "cabarc.exe"
Guido van Rossum360e4b82007-05-14 22:51:27 +0000397 cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name)
398 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000399 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
400 for line in p.stdout:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000401 if line.startswith(" -- adding "):
402 sys.stdout.write(".")
403 else:
404 sys.stdout.write(line)
405 sys.stdout.flush()
406 if not os.path.exists(self.name+".cab"):
Collin Wintera817e582007-08-22 23:05:06 +0000407 raise IOError("cabarc failed")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000408 add_data(db, "Media",
409 [(1, self.index, None, "#"+self.name, None, None)])
410 add_stream(db, self.name, self.name+".cab")
411 os.unlink(self.name+".txt")
412 os.unlink(self.name+".cab")
413 db.Commit()
414
415_directories = sets.Set()
416class Directory:
417 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
418 """Create a new directory in the Directory table. There is a current component
419 at each point in time for the directory, which is either explicitly created
420 through start_component, or implicitly when files are added for the first
421 time. Files are added into the current component, and into the cab file.
422 To create a directory, a base directory object needs to be specified (can be
423 None), the path to the physical directory, and a logical directory name.
424 Default specifies the DefaultDir slot in the directory table. componentflags
425 specifies the default flags that new components get."""
426 index = 1
427 _logical = make_id(_logical)
428 logical = _logical
429 while logical in _directories:
430 logical = "%s%d" % (_logical, index)
431 index += 1
432 _directories.add(logical)
433 self.db = db
434 self.cab = cab
435 self.basedir = basedir
436 self.physical = physical
437 self.logical = logical
438 self.component = None
439 self.short_names = sets.Set()
440 self.ids = sets.Set()
441 self.keyfiles = {}
442 self.componentflags = componentflags
443 if basedir:
444 self.absolute = os.path.join(basedir.absolute, physical)
445 blogical = basedir.logical
446 else:
447 self.absolute = physical
448 blogical = None
449 add_data(db, "Directory", [(logical, blogical, default)])
450
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000451 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000452 """Add an entry to the Component table, and make this component the current for this
453 directory. If no component name is given, the directory name is used. If no feature
454 is given, the current feature is used. If no flags are given, the directory's default
455 flags are used. If no keyfile is given, the KeyPath is left null in the Component
456 table."""
457 if flags is None:
458 flags = self.componentflags
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000459 if uuid is None:
460 uuid = gen_uuid()
461 else:
462 uuid = uuid.upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000463 if component is None:
464 component = self.logical
465 self.component = component
466 if Win64:
467 flags |= 256
468 if keyfile:
469 keyid = self.cab.gen_id(self.absolute, keyfile)
470 self.keyfiles[keyfile] = keyid
471 else:
472 keyid = None
473 add_data(self.db, "Component",
474 [(component, uuid, self.logical, flags, None, keyid)])
475 if feature is None:
476 feature = current_feature
477 add_data(self.db, "FeatureComponents",
478 [(feature.id, component)])
479
480 def make_short(self, file):
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000481 file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000482 parts = file.split(".")
483 if len(parts)>1:
484 suffix = parts[-1].upper()
485 else:
486 suffix = None
487 prefix = parts[0].upper()
488 if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
489 if suffix:
490 file = prefix+"."+suffix
491 else:
492 file = prefix
493 assert file not in self.short_names
494 else:
495 prefix = prefix[:6]
496 if suffix:
497 suffix = suffix[:3]
498 pos = 1
499 while 1:
500 if suffix:
501 file = "%s~%d.%s" % (prefix, pos, suffix)
502 else:
503 file = "%s~%d" % (prefix, pos)
504 if file not in self.short_names: break
505 pos += 1
506 assert pos < 10000
507 if pos in (10, 100, 1000):
508 prefix = prefix[:-1]
509 self.short_names.add(file)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000510 return file
511
512 def add_file(self, file, src=None, version=None, language=None):
513 """Add a file to the current component of the directory, starting a new one
514 one if there is no current component. By default, the file name in the source
515 and the file table will be identical. If the src file is specified, it is
516 interpreted relative to the current directory. Optionally, a version and a
517 language can be specified for the entry in the File table."""
518 if not self.component:
519 self.start_component(self.logical, current_feature)
520 if not src:
521 # Allow relative paths for file if src is not specified
522 src = file
523 file = os.path.basename(file)
524 absolute = os.path.join(self.absolute, src)
525 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
Georg Brandlbf82e372008-05-16 17:02:34 +0000526 if file in self.keyfiles:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000527 logical = self.keyfiles[file]
528 else:
529 logical = None
530 sequence, logical = self.cab.append(absolute, file, logical)
531 assert logical not in self.ids
532 self.ids.add(logical)
533 short = self.make_short(file)
534 full = "%s|%s" % (short, file)
535 filesize = os.stat(absolute).st_size
536 # constants.msidbFileAttributesVital
537 # Compressed omitted, since it is the database default
538 # could add r/o, system, hidden
Tim Peters94607dd2004-08-22 19:42:56 +0000539 attributes = 512
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000540 add_data(self.db, "File",
541 [(logical, self.component, full, filesize, version,
542 language, attributes, sequence)])
543 if not version:
544 # Add hash if the file is not versioned
545 filehash = MakeInstaller().FileHash(absolute, 0)
546 add_data(self.db, "MsiFileHash",
547 [(logical, 0, filehash.IntegerData(1),
548 filehash.IntegerData(2), filehash.IntegerData(3),
549 filehash.IntegerData(4))])
550 # Automatically remove .pyc/.pyo files on uninstall (2)
551 # XXX: adding so many RemoveFile entries makes installer unbelievably
552 # slow. So instead, we have to use wildcard remove entries
553 # if file.endswith(".py"):
554 # add_data(self.db, "RemoveFile",
555 # [(logical+"c", self.component, "%sC|%sc" % (short, file),
556 # self.logical, 2),
557 # (logical+"o", self.component, "%sO|%so" % (short, file),
558 # self.logical, 2)])
559
560 def glob(self, pattern, exclude = None):
561 """Add a list of files to the current component as specified in the
562 glob pattern. Individual files can be excluded in the exclude list."""
563 files = glob.glob1(self.absolute, pattern)
564 for f in files:
565 if exclude and f in exclude: continue
566 self.add_file(f)
567 return files
568
569 def remove_pyc(self):
570 "Remove .pyc/.pyo files on uninstall"
571 add_data(self.db, "RemoveFile",
572 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
573 (self.component+"o", self.component, "*.pyo", self.logical, 2)])
574
Martin v. Löwis90cc5ab2008-06-02 10:04:16 +0000575 def removefile(self, key, pattern):
576 "Add a RemoveFile entry"
577 add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)])
578
579
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000580class Feature:
581 def __init__(self, db, id, title, desc, display, level = 1,
582 parent=None, directory = None, attributes=0):
583 self.id = id
584 if parent:
585 parent = parent.id
586 add_data(db, "Feature",
587 [(id, parent, title, desc, display,
588 level, directory, attributes)])
589 def set_current(self):
590 global current_feature
591 current_feature = self
592
593class Control:
594 def __init__(self, dlg, name):
595 self.dlg = dlg
596 self.name = name
597
598 def event(self, ev, arg, cond = "1", order = None):
599 add_data(self.dlg.db, "ControlEvent",
600 [(self.dlg.name, self.name, ev, arg, cond, order)])
601
602 def mapping(self, ev, attr):
603 add_data(self.dlg.db, "EventMapping",
604 [(self.dlg.name, self.name, ev, attr)])
605
606 def condition(self, action, condition):
607 add_data(self.dlg.db, "ControlCondition",
608 [(self.dlg.name, self.name, action, condition)])
609
610class RadioButtonGroup(Control):
611 def __init__(self, dlg, name, property):
612 self.dlg = dlg
613 self.name = name
614 self.property = property
615 self.index = 1
616
617 def add(self, name, x, y, w, h, text, value = None):
618 if value is None:
619 value = name
620 add_data(self.dlg.db, "RadioButton",
621 [(self.property, self.index, value,
622 x, y, w, h, text, None)])
623 self.index += 1
624
625class Dialog:
626 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
627 self.db = db
628 self.name = name
629 self.x, self.y, self.w, self.h = x,y,w,h
630 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
631
632 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
633 add_data(self.db, "Control",
634 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
635 return Control(self, name)
636
637 def text(self, name, x, y, w, h, attr, text):
638 return self.control(name, "Text", x, y, w, h, attr, None,
639 text, None, None)
640
641 def bitmap(self, name, x, y, w, h, text):
642 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
Tim Peters94607dd2004-08-22 19:42:56 +0000643
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000644 def line(self, name, x, y, w, h):
645 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
646
647 def pushbutton(self, name, x, y, w, h, attr, text, next):
648 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
649
650 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
651 add_data(self.db, "Control",
652 [(self.name, name, "RadioButtonGroup",
653 x, y, w, h, attr, prop, text, next, None)])
654 return RadioButtonGroup(self, name, prop)
655
656 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
Tim Peters94607dd2004-08-22 19:42:56 +0000657 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000658
659def pe_type(path):
Martin v. Löwisd149c212006-03-05 13:52:20 +0000660 header = open(path, "rb").read(1000)
661 # offset of PE header is at offset 0x3c
662 pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000663 assert header[pe_offset:pe_offset+4] == "PE\0\0"
664 machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
665 return machine
666
667def set_arch_from_file(path):
668 global msi_type, Win64, arch_ext
669 machine = pe_type(path)
670 if machine == 0x14c:
671 # i386
672 msi_type = "Intel"
673 Win64 = 0
674 arch_ext = ''
675 elif machine == 0x200:
676 # Itanium
677 msi_type = "Intel64"
678 Win64 = 1
679 arch_ext = '.ia64'
680 elif machine == 0x8664:
681 # AMD64
682 msi_type = "x64"
683 Win64 = 1
684 arch_ext = '.amd64'
685 else:
Collin Wintera817e582007-08-22 23:05:06 +0000686 raise ValueError("Unsupported architecture")
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000687 msi_type += ";1033"