blob: 38fef8c54a18221a1ac07f0407b1307ddc235b4e [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 Brandlfdb12a32009-04-07 21:27:29 +00008import re, string, os, sets, glob, subprocess, sys, _winreg, struct
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)
308 # Initialize the summary information, allowing atmost 20 properties
309 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
353 self.file = open(name+".txt", "wt")
354 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
372 if full.find(" ")!=-1:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000373 print >>self.file, '"%s" %s' % (full, logical)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000374 else:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000375 print >>self.file, '%s %s' % (full, logical)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000376 return self.index, logical
377
378 def commit(self, db):
379 self.file.close()
380 try:
381 os.unlink(self.name+".cab")
382 except OSError:
383 pass
384 for k, v in [(r"Software\Microsoft\VisualStudio\7.1\Setup\VS", "VS7CommonBinDir"),
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000385 (r"Software\Microsoft\VisualStudio\8.0\Setup\VS", "VS7CommonBinDir"),
386 (r"Software\Microsoft\VisualStudio\9.0\Setup\VS", "VS7CommonBinDir"),
387 (r"Software\Microsoft\Win32SDK\Directories", "Install Dir"),
388 ]:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000389 try:
Georg Brandlfdb12a32009-04-07 21:27:29 +0000390 key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, k)
391 dir = _winreg.QueryValueEx(key, v)[0]
392 _winreg.CloseKey(key)
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000393 except (WindowsError, IndexError):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000394 continue
Christian Heimes52ca6cc2007-12-04 15:54:13 +0000395 cabarc = os.path.join(dir, r"Bin", "cabarc.exe")
396 if not os.path.exists(cabarc):
397 continue
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000398 break
399 else:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000400 print "WARNING: cabarc.exe not found in registry"
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000401 cabarc = "cabarc.exe"
Guido van Rossum360e4b82007-05-14 22:51:27 +0000402 cmd = r'"%s" -m lzx:21 n %s.cab @%s.txt' % (cabarc, self.name, self.name)
403 p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
Martin v. Löwis4c6c7ca2007-08-30 15:23:04 +0000404 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
405 for line in p.stdout:
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000406 if line.startswith(" -- adding "):
407 sys.stdout.write(".")
408 else:
409 sys.stdout.write(line)
410 sys.stdout.flush()
411 if not os.path.exists(self.name+".cab"):
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000412 raise IOError, "cabarc failed"
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000413 add_data(db, "Media",
414 [(1, self.index, None, "#"+self.name, None, None)])
415 add_stream(db, self.name, self.name+".cab")
416 os.unlink(self.name+".txt")
417 os.unlink(self.name+".cab")
418 db.Commit()
419
420_directories = sets.Set()
421class Directory:
422 def __init__(self, db, cab, basedir, physical, _logical, default, componentflags=None):
423 """Create a new directory in the Directory table. There is a current component
424 at each point in time for the directory, which is either explicitly created
425 through start_component, or implicitly when files are added for the first
426 time. Files are added into the current component, and into the cab file.
427 To create a directory, a base directory object needs to be specified (can be
428 None), the path to the physical directory, and a logical directory name.
429 Default specifies the DefaultDir slot in the directory table. componentflags
430 specifies the default flags that new components get."""
431 index = 1
432 _logical = make_id(_logical)
433 logical = _logical
434 while logical in _directories:
435 logical = "%s%d" % (_logical, index)
436 index += 1
437 _directories.add(logical)
438 self.db = db
439 self.cab = cab
440 self.basedir = basedir
441 self.physical = physical
442 self.logical = logical
443 self.component = None
444 self.short_names = sets.Set()
445 self.ids = sets.Set()
446 self.keyfiles = {}
447 self.componentflags = componentflags
448 if basedir:
449 self.absolute = os.path.join(basedir.absolute, physical)
450 blogical = basedir.logical
451 else:
452 self.absolute = physical
453 blogical = None
Martin v. Löwis797721b2010-08-03 18:35:55 +0000454 # initially assume that all files in this directory are unpackaged
455 # as files from self.absolute get added, this set is reduced
456 self.unpackaged_files = set()
457 for f in os.listdir(self.absolute):
458 if os.path.isfile(os.path.join(self.absolute, f)):
459 self.unpackaged_files.add(f)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000460 add_data(db, "Directory", [(logical, blogical, default)])
461
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000462 def start_component(self, component = None, feature = None, flags = None, keyfile = None, uuid=None):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000463 """Add an entry to the Component table, and make this component the current for this
464 directory. If no component name is given, the directory name is used. If no feature
465 is given, the current feature is used. If no flags are given, the directory's default
466 flags are used. If no keyfile is given, the KeyPath is left null in the Component
467 table."""
468 if flags is None:
469 flags = self.componentflags
Martin v. Löwis141f41a2005-03-15 00:39:40 +0000470 if uuid is None:
471 uuid = gen_uuid()
472 else:
473 uuid = uuid.upper()
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000474 if component is None:
475 component = self.logical
476 self.component = component
477 if Win64:
478 flags |= 256
479 if keyfile:
480 keyid = self.cab.gen_id(self.absolute, keyfile)
481 self.keyfiles[keyfile] = keyid
482 else:
483 keyid = None
484 add_data(self.db, "Component",
485 [(component, uuid, self.logical, flags, None, keyid)])
486 if feature is None:
487 feature = current_feature
488 add_data(self.db, "FeatureComponents",
489 [(feature.id, component)])
490
491 def make_short(self, file):
Martin v. Löwisf38e0d02008-06-13 14:11:59 +0000492 file = re.sub(r'[\?|><:/*"+,;=\[\]]', '_', file) # restrictions on short names
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000493 parts = file.split(".")
494 if len(parts)>1:
495 suffix = parts[-1].upper()
496 else:
497 suffix = None
498 prefix = parts[0].upper()
499 if len(prefix) <= 8 and (not suffix or len(suffix)<=3):
500 if suffix:
501 file = prefix+"."+suffix
502 else:
503 file = prefix
504 assert file not in self.short_names
505 else:
506 prefix = prefix[:6]
507 if suffix:
508 suffix = suffix[:3]
509 pos = 1
510 while 1:
511 if suffix:
512 file = "%s~%d.%s" % (prefix, pos, suffix)
513 else:
514 file = "%s~%d" % (prefix, pos)
515 if file not in self.short_names: break
516 pos += 1
517 assert pos < 10000
518 if pos in (10, 100, 1000):
519 prefix = prefix[:-1]
520 self.short_names.add(file)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000521 return file
522
523 def add_file(self, file, src=None, version=None, language=None):
524 """Add a file to the current component of the directory, starting a new one
525 one if there is no current component. By default, the file name in the source
526 and the file table will be identical. If the src file is specified, it is
527 interpreted relative to the current directory. Optionally, a version and a
528 language can be specified for the entry in the File table."""
529 if not self.component:
530 self.start_component(self.logical, current_feature)
531 if not src:
532 # Allow relative paths for file if src is not specified
533 src = file
534 file = os.path.basename(file)
535 absolute = os.path.join(self.absolute, src)
Martin v. Löwis797721b2010-08-03 18:35:55 +0000536 if absolute.startswith(self.absolute):
537 # mark file as packaged
538 relative = absolute[len(self.absolute)+1:]
539 if relative in self.unpackaged_files:
540 self.unpackaged_files.remove(relative)
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000541 assert not re.search(r'[\?|><:/*]"', file) # restrictions on long names
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000542 if self.keyfiles.has_key(file):
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000543 logical = self.keyfiles[file]
544 else:
545 logical = None
546 sequence, logical = self.cab.append(absolute, file, logical)
547 assert logical not in self.ids
548 self.ids.add(logical)
549 short = self.make_short(file)
550 full = "%s|%s" % (short, file)
551 filesize = os.stat(absolute).st_size
552 # constants.msidbFileAttributesVital
553 # Compressed omitted, since it is the database default
554 # could add r/o, system, hidden
Tim Peters94607dd2004-08-22 19:42:56 +0000555 attributes = 512
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000556 add_data(self.db, "File",
557 [(logical, self.component, full, filesize, version,
558 language, attributes, sequence)])
559 if not version:
560 # Add hash if the file is not versioned
561 filehash = MakeInstaller().FileHash(absolute, 0)
562 add_data(self.db, "MsiFileHash",
563 [(logical, 0, filehash.IntegerData(1),
564 filehash.IntegerData(2), filehash.IntegerData(3),
565 filehash.IntegerData(4))])
566 # Automatically remove .pyc/.pyo files on uninstall (2)
567 # XXX: adding so many RemoveFile entries makes installer unbelievably
568 # slow. So instead, we have to use wildcard remove entries
569 # if file.endswith(".py"):
570 # add_data(self.db, "RemoveFile",
571 # [(logical+"c", self.component, "%sC|%sc" % (short, file),
572 # self.logical, 2),
573 # (logical+"o", self.component, "%sO|%so" % (short, file),
574 # self.logical, 2)])
575
576 def glob(self, pattern, exclude = None):
577 """Add a list of files to the current component as specified in the
578 glob pattern. Individual files can be excluded in the exclude list."""
579 files = glob.glob1(self.absolute, pattern)
580 for f in files:
581 if exclude and f in exclude: continue
582 self.add_file(f)
583 return files
584
585 def remove_pyc(self):
586 "Remove .pyc/.pyo files on uninstall"
587 add_data(self.db, "RemoveFile",
588 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
589 (self.component+"o", self.component, "*.pyo", self.logical, 2)])
590
Martin v. Löwis90cc5ab2008-06-02 10:04:16 +0000591 def removefile(self, key, pattern):
592 "Add a RemoveFile entry"
593 add_data(self.db, "RemoveFile", [(self.component+key, self.component, pattern, self.logical, 2)])
594
595
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000596class Feature:
597 def __init__(self, db, id, title, desc, display, level = 1,
598 parent=None, directory = None, attributes=0):
599 self.id = id
600 if parent:
601 parent = parent.id
602 add_data(db, "Feature",
603 [(id, parent, title, desc, display,
604 level, directory, attributes)])
605 def set_current(self):
606 global current_feature
607 current_feature = self
608
609class Control:
610 def __init__(self, dlg, name):
611 self.dlg = dlg
612 self.name = name
613
614 def event(self, ev, arg, cond = "1", order = None):
615 add_data(self.dlg.db, "ControlEvent",
616 [(self.dlg.name, self.name, ev, arg, cond, order)])
617
618 def mapping(self, ev, attr):
619 add_data(self.dlg.db, "EventMapping",
620 [(self.dlg.name, self.name, ev, attr)])
621
622 def condition(self, action, condition):
623 add_data(self.dlg.db, "ControlCondition",
624 [(self.dlg.name, self.name, action, condition)])
625
626class RadioButtonGroup(Control):
627 def __init__(self, dlg, name, property):
628 self.dlg = dlg
629 self.name = name
630 self.property = property
631 self.index = 1
632
633 def add(self, name, x, y, w, h, text, value = None):
634 if value is None:
635 value = name
636 add_data(self.dlg.db, "RadioButton",
637 [(self.property, self.index, value,
638 x, y, w, h, text, None)])
639 self.index += 1
640
641class Dialog:
642 def __init__(self, db, name, x, y, w, h, attr, title, first, default, cancel):
643 self.db = db
644 self.name = name
645 self.x, self.y, self.w, self.h = x,y,w,h
646 add_data(db, "Dialog", [(name, x,y,w,h,attr,title,first,default,cancel)])
647
648 def control(self, name, type, x, y, w, h, attr, prop, text, next, help):
649 add_data(self.db, "Control",
650 [(self.name, name, type, x, y, w, h, attr, prop, text, next, help)])
651 return Control(self, name)
652
653 def text(self, name, x, y, w, h, attr, text):
654 return self.control(name, "Text", x, y, w, h, attr, None,
655 text, None, None)
656
657 def bitmap(self, name, x, y, w, h, text):
658 return self.control(name, "Bitmap", x, y, w, h, 1, None, text, None, None)
Tim Peters94607dd2004-08-22 19:42:56 +0000659
Martin v. Löwis8ffe9ab2004-08-22 13:34:34 +0000660 def line(self, name, x, y, w, h):
661 return self.control(name, "Line", x, y, w, h, 1, None, None, None, None)
662
663 def pushbutton(self, name, x, y, w, h, attr, text, next):
664 return self.control(name, "PushButton", x, y, w, h, attr, None, text, next, None)
665
666 def radiogroup(self, name, x, y, w, h, attr, prop, text, next):
667 add_data(self.db, "Control",
668 [(self.name, name, "RadioButtonGroup",
669 x, y, w, h, attr, prop, text, next, None)])
670 return RadioButtonGroup(self, name, prop)
671
672 def checkbox(self, name, x, y, w, h, attr, prop, text, next):
Tim Peters94607dd2004-08-22 19:42:56 +0000673 return self.control(name, "CheckBox", x, y, w, h, attr, prop, text, next, None)
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000674
675def pe_type(path):
Martin v. Löwisd149c212006-03-05 13:52:20 +0000676 header = open(path, "rb").read(1000)
677 # offset of PE header is at offset 0x3c
678 pe_offset = struct.unpack("<i", header[0x3c:0x40])[0]
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000679 assert header[pe_offset:pe_offset+4] == "PE\0\0"
680 machine = struct.unpack("<H", header[pe_offset+4:pe_offset+6])[0]
681 return machine
682
683def set_arch_from_file(path):
684 global msi_type, Win64, arch_ext
685 machine = pe_type(path)
686 if machine == 0x14c:
687 # i386
688 msi_type = "Intel"
689 Win64 = 0
690 arch_ext = ''
691 elif machine == 0x200:
692 # Itanium
693 msi_type = "Intel64"
694 Win64 = 1
695 arch_ext = '.ia64'
696 elif machine == 0x8664:
697 # AMD64
698 msi_type = "x64"
699 Win64 = 1
700 arch_ext = '.amd64'
701 else:
Martin v. Löwis3b6cc0952008-07-18 18:40:42 +0000702 raise ValueError, "Unsupported architecture"
Martin v. Löwis856bf9a2006-02-14 20:42:55 +0000703 msi_type += ";1033"