blob: 9245a22986f520bcfb0c1403a2c3152bfc633b06 [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,
287 Manufacturer):
288 try:
289 os.unlink(name)
290 except OSError:
291 pass
292 ProductCode = ProductCode.upper()
293 d = MakeInstaller()
294 # Create the database
295 db = d.OpenDatabase(name,
296 win32com.client.constants.msiOpenDatabaseModeCreate)
297 # Create the tables
298 for t in schema.tables:
299 t.create(db)
300 # Fill the validation table
301 add_data(db, "_Validation", schema._Validation_records)
302 # Initialize the summary information, allowing atmost 20 properties
303 si = db.GetSummaryInformation(20)
304 si.SetProperty(PID_TITLE, "Installation Database")
305 si.SetProperty(PID_SUBJECT, ProductName)
306 si.SetProperty(PID_AUTHOR, Manufacturer)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000307 si.SetProperty(PID_TEMPLATE, msi_type)
Martin v. Löwis1e3a2642004-09-10 11:55:32 +0000308 si.SetProperty(PID_REVNUMBER, gen_uuid())
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000309 si.SetProperty(PID_WORDCOUNT, 2) # long file names, compressed, original media
310 si.SetProperty(PID_PAGECOUNT, 200)
311 si.SetProperty(PID_APPNAME, "Python MSI Library")
312 # XXX more properties
313 si.Persist()
314 add_data(db, "Property", [
315 ("ProductName", ProductName),
316 ("ProductCode", ProductCode),
317 ("ProductVersion", ProductVersion),
318 ("Manufacturer", Manufacturer),
319 ("ProductLanguage", "1033")])
320 db.Commit()
321 return db
322
323def add_tables(db, module):
324 for table in module.tables:
325 add_data(db, table, getattr(module, table))
326
327def make_id(str):
328 #str = str.replace(".", "_") # colons are allowed
329 str = str.replace(" ", "_")
330 str = str.replace("-", "_")
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000331 str = str.replace("+", "_")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000332 if str[0] in string.digits:
333 str = "_"+str
334 assert re.match("^[A-Za-z_][A-Za-z0-9_.]*$", str), "FILE"+str
335 return str
336
337def gen_uuid():
338 return str(pythoncom.CreateGuid())
339
340class CAB:
341 def __init__(self, name):
342 self.name = name
343 self.file = open(name+".txt", "wt")
344 self.filenames = sets.Set()
345 self.index = 0
346
347 def gen_id(self, dir, file):
348 logical = _logical = make_id(file)
349 pos = 1
350 while logical in self.filenames:
351 logical = "%s.%d" % (_logical, pos)
352 pos += 1
353 self.filenames.add(logical)
354 return logical
355
356 def append(self, full, file, logical = None):
357 if os.path.isdir(full):
358 return
359 if not logical:
360 logical = self.gen_id(dir, file)
361 self.index += 1
362 if full.find(" ")!=-1:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000363 self.file.write('"%s" %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000364 else:
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000365 self.file.write('%s %s\n' % (full, logical))
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000366 return self.index, logical
367
368 def commit(self, db):
369 self.file.close()
370 try:
371 os.unlink(self.name+".cab")
372 except OSError:
373 pass
374 for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"),
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000375 (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"),
376 (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"),
377 (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"),
378 ]:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000379 try:
Georg Brandl38feaf02008-05-25 07:45:51 +0000380 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, k)
381 dir = winreg.QueryValueEx(key, v)[0]
382 winreg.CloseKey(key)
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000383 except (WindowsError, IndexError):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000384 continue
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000385 cabarc = os.path.join(dir, r"Bin", "cabarc.exe")
386 if not os.path.exists(cabarc):
387 continue
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000388 break
389 else:
Collin Winter6afaeb72007-08-03 17:06:41 +0000390 print("WARNING: cabarc.exe not found in registry")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000391 cabarc = "cabarc.exe"
Guido van Rossum360e4b82007-05-14 22:51:27 +0000392 cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name)
393 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000394 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
395 for line in p.stdout:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000396 if line.startswith(" -- adding "):
397 sys.stdout.write(".")
398 else:
399 sys.stdout.write(line)
400 sys.stdout.flush()
401 if not os.path.exists(self.name+".cab"):
Collin Wintera817e582007-08-22 23:05:06 +0000402 raise IOError("cabarc failed")
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000403 add_data(db, "Media",
404 [(1, self.index, None, "#"+self.name, None, None)])
405 add_stream(db, self.name, self.name+".cab")
406 os.unlink(self.name+".txt")
407 os.unlink(self.name+".cab")
408 db.Commit()
409
410_directories = sets.Set()
411class Directory:
412 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
413 """Create a new directory in the Directory table. There is a current component
414 at each point in time for the directory, which is either explicitly created
415 through start_component, or implicitly when files are added for the first
416 time. Files are added into the current component, and into the cab file.
417 To create a directory, a base directory object needs to be specified (can be
418 None), the path to the physical directory, and a logical directory name.
419 Default specifies the DefaultDir slot in the directory table. componentflags
420 specifies the default flags that new components get."""
421 index = 1
422 _logical = make_id(_logical)
423 logical = _logical
424 while logical in _directories:
425 logical = "%s%d" % (_logical, index)
426 index += 1
427 _directories.add(logical)
428 self.db = db
429 self.cab = cab
430 self.basedir = basedir
431 self.physical = physical
432 self.logical = logical
433 self.component = None
434 self.short_names = sets.Set()
435 self.ids = sets.Set()
436 self.keyfiles = {}
437 self.componentflags = componentflags
438 if basedir:
439 self.absolute = os.path.join(basedir.absolute, physical)
440 blogical = basedir.logical
441 else:
442 self.absolute = physical
443 blogical = None
444 add_data(db, "Directory", [(logical, blogical, default)])
445
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000446 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000447 """Add an entry to the Component table, and make this component the current for this
448 directory. If no component name is given, the directory name is used. If no feature
449 is given, the current feature is used. If no flags are given, the directory's default
450 flags are used. If no keyfile is given, the KeyPath is left null in the Component
451 table."""
452 if flags is None:
453 flags = self.componentflags
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000454 if uuid is None:
455 uuid = gen_uuid()
456 else:
457 uuid = uuid.upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000458 if component is None:
459 component = self.logical
460 self.component = component
461 if Win64:
462 flags |= 256
463 if keyfile:
464 keyid = self.cab.gen_id(self.absolute, keyfile)
465 self.keyfiles[keyfile] = keyid
466 else:
467 keyid = None
468 add_data(self.db, "Component",
469 [(component, uuid, self.logical, flags, None, keyid)])
470 if feature is None:
471 feature = current_feature
472 add_data(self.db, "FeatureComponents",
473 [(feature.id, component)])
474
475 def make_short(self, file):
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000476 file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000477 parts = file.split(".")
478 if len(parts)>1:
479 suffix = parts[-1].upper()
480 else:
481 suffix = None
482 prefix = parts[0].upper()
483 if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
484 if suffix:
485 file = prefix+"."+suffix
486 else:
487 file = prefix
488 assert file not in self.short_names
489 else:
490 prefix = prefix[:6]
491 if suffix:
492 suffix = suffix[:3]
493 pos = 1
494 while 1:
495 if suffix:
496 file = "%s~%d.%s" % (prefix, pos, suffix)
497 else:
498 file = "%s~%d" % (prefix, pos)
499 if file not in self.short_names: break
500 pos += 1
501 assert pos < 10000
502 if pos in (10, 100, 1000):
503 prefix = prefix[:-1]
504 self.short_names.add(file)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000505 return file
506
507 def add_file(self, file, src=None, version=None, language=None):
508 """Add a file to the current component of the directory, starting a new one
509 one if there is no current component. By default, the file name in the source
510 and the file table will be identical. If the src file is specified, it is
511 interpreted relative to the current directory. Optionally, a version and a
512 language can be specified for the entry in the File table."""
513 if not self.component:
514 self.start_component(self.logical, current_feature)
515 if not src:
516 # Allow relative paths for file if src is not specified
517 src = file
518 file = os.path.basename(file)
519 absolute = os.path.join(self.absolute, src)
520 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
Georg Brandlbf82e372008-05-16 17:02:34 +0000521 if file in self.keyfiles:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000522 logical = self.keyfiles[file]
523 else:
524 logical = None
525 sequence, logical = self.cab.append(absolute, file, logical)
526 assert logical not in self.ids
527 self.ids.add(logical)
528 short = self.make_short(file)
529 full = "%s|%s" % (short, file)
530 filesize = os.stat(absolute).st_size
531 # constants.msidbFileAttributesVital
532 # Compressed omitted, since it is the database default
533 # could add r/o, system, hidden
Tim Peters94607dd2004-08-22 19:42:56 +0000534 attributes = 512
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000535 add_data(self.db, "File",
536 [(logical, self.component, full, filesize, version,
537 language, attributes, sequence)])
538 if not version:
539 # Add hash if the file is not versioned
540 filehash = MakeInstaller().FileHash(absolute, 0)
541 add_data(self.db, "MsiFileHash",
542 [(logical, 0, filehash.IntegerData(1),
543 filehash.IntegerData(2), filehash.IntegerData(3),
544 filehash.IntegerData(4))])
545 # Automatically remove .pyc/.pyo files on uninstall (2)
546 # XXX: adding so many RemoveFile entries makes installer unbelievably
547 # slow. So instead, we have to use wildcard remove entries
548 # if file.endswith(".py"):
549 # add_data(self.db, "RemoveFile",
550 # [(logical+"c", self.component, "%sC|%sc" % (short, file),
551 # self.logical, 2),
552 # (logical+"o", self.component, "%sO|%so" % (short, file),
553 # self.logical, 2)])
554
555 def glob(self, pattern, exclude = None):
556 """Add a list of files to the current component as specified in the
557 glob pattern. Individual files can be excluded in the exclude list."""
558 files = glob.glob1(self.absolute, pattern)
559 for f in files:
560 if exclude and f in exclude: continue
561 self.add_file(f)
562 return files
563
564 def remove_pyc(self):
565 "Remove .pyc/.pyo files on uninstall"
566 add_data(self.db, "RemoveFile",
567 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
568 (self.component+"o", self.component, "*.pyo", self.logical, 2)])
569
Martin v. Löwis90cc5ab2008-06-02 10:04:16 +0000570 def removefile(self, key, pattern):
571 "Add a RemoveFile entry"
572 add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)])
573
574
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000575class Feature:
576 def __init__(self, db, id, title, desc, display, level = 1,
577 parent=None, directory = None, attributes=0):
578 self.id = id
579 if parent:
580 parent = parent.id
581 add_data(db, "Feature",
582 [(id, parent, title, desc, display,
583 level, directory, attributes)])
584 def set_current(self):
585 global current_feature
586 current_feature = self
587
588class Control:
589 def __init__(self, dlg, name):
590 self.dlg = dlg
591 self.name = name
592
593 def event(self, ev, arg, cond = "1", order = None):
594 add_data(self.dlg.db, "ControlEvent",
595 [(self.dlg.name, self.name, ev, arg, cond, order)])
596
597 def mapping(self, ev, attr):
598 add_data(self.dlg.db, "EventMapping",
599 [(self.dlg.name, self.name, ev, attr)])
600
601 def condition(self, action, condition):
602 add_data(self.dlg.db, "ControlCondition",
603 [(self.dlg.name, self.name, action, condition)])
604
605class RadioButtonGroup(Control):
606 def __init__(self, dlg, name, property):
607 self.dlg = dlg
608 self.name = name
609 self.property = property
610 self.index = 1
611
612 def add(self, name, x, y, w, h, text, value = None):
613 if value is None:
614 value = name
615 add_data(self.dlg.db, "RadioButton",
616 [(self.property, self.index, value,
617 x, y, w, h, text, None)])
618 self.index += 1
619
620class Dialog:
621 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
622 self.db = db
623 self.name = name
624 self.x, self.y, self.w, self.h = x,y,w,h
625 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
626
627 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
628 add_data(self.db, "Control",
629 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
630 return Control(self, name)
631
632 def text(self, name, x, y, w, h, attr, text):
633 return self.control(name, "Text", x, y, w, h, attr, None,
634 text, None, None)
635
636 def bitmap(self, name, x, y, w, h, text):
637 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
Tim Peters94607dd2004-08-22 19:42:56 +0000638
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000639 def line(self, name, x, y, w, h):
640 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
641
642 def pushbutton(self, name, x, y, w, h, attr, text, next):
643 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
644
645 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
646 add_data(self.db, "Control",
647 [(self.name, name, "RadioButtonGroup",
648 x, y, w, h, attr, prop, text, next, None)])
649 return RadioButtonGroup(self, name, prop)
650
651 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
Tim Peters94607dd2004-08-22 19:42:56 +0000652 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000653
654def pe_type(path):
Martin v. Löwisd149c212006-03-05 13:52:20 +0000655 header = open(path, "rb").read(1000)
656 # offset of PE header is at offset 0x3c
657 pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000658 assert header[pe_offset:pe_offset+4] == "PE\0\0"
659 machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
660 return machine
661
662def set_arch_from_file(path):
663 global msi_type, Win64, arch_ext
664 machine = pe_type(path)
665 if machine == 0x14c:
666 # i386
667 msi_type = "Intel"
668 Win64 = 0
669 arch_ext = ''
670 elif machine == 0x200:
671 # Itanium
672 msi_type = "Intel64"
673 Win64 = 1
674 arch_ext = '.ia64'
675 elif machine == 0x8664:
676 # AMD64
677 msi_type = "x64"
678 Win64 = 1
679 arch_ext = '.amd64'
680 else:
Collin Wintera817e582007-08-22 23:05:06 +0000681 raise ValueError("Unsupported architecture")
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000682 msi_type += ";1033"