OLD | NEW |
(Empty) | |
| 1 """SCons.Tool.packaging.msi |
| 2 |
| 3 The msi packager. |
| 4 """ |
| 5 |
| 6 # |
| 7 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The S
Cons Foundation |
| 8 # |
| 9 # Permission is hereby granted, free of charge, to any person obtaining |
| 10 # a copy of this software and associated documentation files (the |
| 11 # "Software"), to deal in the Software without restriction, including |
| 12 # without limitation the rights to use, copy, modify, merge, publish, |
| 13 # distribute, sublicense, and/or sell copies of the Software, and to |
| 14 # permit persons to whom the Software is furnished to do so, subject to |
| 15 # the following conditions: |
| 16 # |
| 17 # The above copyright notice and this permission notice shall be included |
| 18 # in all copies or substantial portions of the Software. |
| 19 # |
| 20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
| 21 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
| 22 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 23 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
| 24 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| 25 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| 26 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 27 |
| 28 __revision__ = "src/engine/SCons/Tool/packaging/msi.py 5134 2010/08/16 23:02:40
bdeegan" |
| 29 |
| 30 import os |
| 31 import SCons |
| 32 from SCons.Action import Action |
| 33 from SCons.Builder import Builder |
| 34 |
| 35 from xml.dom.minidom import * |
| 36 from xml.sax.saxutils import escape |
| 37 |
| 38 from SCons.Tool.packaging import stripinstallbuilder |
| 39 |
| 40 # |
| 41 # Utility functions |
| 42 # |
| 43 def convert_to_id(s, id_set): |
| 44 """ Some parts of .wxs need an Id attribute (for example: The File and |
| 45 Directory directives. The charset is limited to A-Z, a-z, digits, |
| 46 underscores, periods. Each Id must begin with a letter or with a |
| 47 underscore. Google for "CNDL0015" for information about this. |
| 48 |
| 49 Requirements: |
| 50 * the string created must only contain chars from the target charset. |
| 51 * the string created must have a minimal editing distance from the |
| 52 original string. |
| 53 * the string created must be unique for the whole .wxs file. |
| 54 |
| 55 Observation: |
| 56 * There are 62 chars in the charset. |
| 57 |
| 58 Idea: |
| 59 * filter out forbidden characters. Check for a collision with the help |
| 60 of the id_set. Add the number of the number of the collision at the |
| 61 end of the created string. Furthermore care for a correct start of |
| 62 the string. |
| 63 """ |
| 64 charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789_.' |
| 65 if s[0] in '0123456789.': |
| 66 s += '_'+s |
| 67 id = [c for c in s if c in charset] |
| 68 |
| 69 # did we already generate an id for this file? |
| 70 try: |
| 71 return id_set[id][s] |
| 72 except KeyError: |
| 73 # no we did not so initialize with the id |
| 74 if id not in id_set: id_set[id] = { s : id } |
| 75 # there is a collision, generate an id which is unique by appending |
| 76 # the collision number |
| 77 else: id_set[id][s] = id + str(len(id_set[id])) |
| 78 |
| 79 return id_set[id][s] |
| 80 |
| 81 def is_dos_short_file_name(file): |
| 82 """ examine if the given file is in the 8.3 form. |
| 83 """ |
| 84 fname, ext = os.path.splitext(file) |
| 85 proper_ext = len(ext) == 0 or (2 <= len(ext) <= 4) # the ext contains the do
t |
| 86 proper_fname = file.isupper() and len(fname) <= 8 |
| 87 |
| 88 return proper_ext and proper_fname |
| 89 |
| 90 def gen_dos_short_file_name(file, filename_set): |
| 91 """ see http://support.microsoft.com/default.aspx?scid=kb;en-us;Q142982 |
| 92 |
| 93 These are no complete 8.3 dos short names. The ~ char is missing and |
| 94 replaced with one character from the filename. WiX warns about such |
| 95 filenames, since a collision might occur. Google for "CNDL1014" for |
| 96 more information. |
| 97 """ |
| 98 # guard this to not confuse the generation |
| 99 if is_dos_short_file_name(file): |
| 100 return file |
| 101 |
| 102 fname, ext = os.path.splitext(file) # ext contains the dot |
| 103 |
| 104 # first try if it suffices to convert to upper |
| 105 file = file.upper() |
| 106 if is_dos_short_file_name(file): |
| 107 return file |
| 108 |
| 109 # strip forbidden characters. |
| 110 forbidden = '."/[]:;=, ' |
| 111 fname = [c for c in fname if c not in forbidden] |
| 112 |
| 113 # check if we already generated a filename with the same number: |
| 114 # thisis1.txt, thisis2.txt etc. |
| 115 duplicate, num = not None, 1 |
| 116 while duplicate: |
| 117 shortname = "%s%s" % (fname[:8-len(str(num))].upper(),\ |
| 118 str(num)) |
| 119 if len(ext) >= 2: |
| 120 shortname = "%s%s" % (shortname, ext[:4].upper()) |
| 121 |
| 122 duplicate, num = shortname in filename_set, num+1 |
| 123 |
| 124 assert( is_dos_short_file_name(shortname) ), 'shortname is %s, longname is %
s' % (shortname, file) |
| 125 filename_set.append(shortname) |
| 126 return shortname |
| 127 |
| 128 def create_feature_dict(files): |
| 129 """ X_MSI_FEATURE and doc FileTag's can be used to collect files in a |
| 130 hierarchy. This function collects the files into this hierarchy. |
| 131 """ |
| 132 dict = {} |
| 133 |
| 134 def add_to_dict( feature, file ): |
| 135 if not SCons.Util.is_List( feature ): |
| 136 feature = [ feature ] |
| 137 |
| 138 for f in feature: |
| 139 if f not in dict: |
| 140 dict[ f ] = [ file ] |
| 141 else: |
| 142 dict[ f ].append( file ) |
| 143 |
| 144 for file in files: |
| 145 if hasattr( file, 'PACKAGING_X_MSI_FEATURE' ): |
| 146 add_to_dict(file.PACKAGING_X_MSI_FEATURE, file) |
| 147 elif hasattr( file, 'PACKAGING_DOC' ): |
| 148 add_to_dict( 'PACKAGING_DOC', file ) |
| 149 else: |
| 150 add_to_dict( 'default', file ) |
| 151 |
| 152 return dict |
| 153 |
| 154 def generate_guids(root): |
| 155 """ generates globally unique identifiers for parts of the xml which need |
| 156 them. |
| 157 |
| 158 Component tags have a special requirement. Their UUID is only allowed to |
| 159 change if the list of their contained resources has changed. This allows |
| 160 for clean removal and proper updates. |
| 161 |
| 162 To handle this requirement, the uuid is generated with an md5 hashing the |
| 163 whole subtree of a xml node. |
| 164 """ |
| 165 from hashlib import md5 |
| 166 |
| 167 # specify which tags need a guid and in which attribute this should be store
d. |
| 168 needs_id = { 'Product' : 'Id', |
| 169 'Package' : 'Id', |
| 170 'Component' : 'Guid', |
| 171 } |
| 172 |
| 173 # find all XMl nodes matching the key, retrieve their attribute, hash their |
| 174 # subtree, convert hash to string and add as a attribute to the xml node. |
| 175 for (key,value) in needs_id.items(): |
| 176 node_list = root.getElementsByTagName(key) |
| 177 attribute = value |
| 178 for node in node_list: |
| 179 hash = md5(node.toxml()).hexdigest() |
| 180 hash_str = '%s-%s-%s-%s-%s' % ( hash[:8], hash[8:12], hash[12:16], h
ash[16:20], hash[20:] ) |
| 181 node.attributes[attribute] = hash_str |
| 182 |
| 183 |
| 184 |
| 185 def string_wxsfile(target, source, env): |
| 186 return "building WiX file %s"%( target[0].path ) |
| 187 |
| 188 def build_wxsfile(target, source, env): |
| 189 """ compiles a .wxs file from the keywords given in env['msi_spec'] and |
| 190 by analyzing the tree of source nodes and their tags. |
| 191 """ |
| 192 file = open(target[0].abspath, 'w') |
| 193 |
| 194 try: |
| 195 # Create a document with the Wix root tag |
| 196 doc = Document() |
| 197 root = doc.createElement( 'Wix' ) |
| 198 root.attributes['xmlns']='http://schemas.microsoft.com/wix/2003/01/wi' |
| 199 doc.appendChild( root ) |
| 200 |
| 201 filename_set = [] # this is to circumvent duplicates in the shortnames |
| 202 id_set = {} # this is to circumvent duplicates in the ids |
| 203 |
| 204 # Create the content |
| 205 build_wxsfile_header_section(root, env) |
| 206 build_wxsfile_file_section(root, source, env['NAME'], env['VERSION'], en
v['VENDOR'], filename_set, id_set) |
| 207 generate_guids(root) |
| 208 build_wxsfile_features_section(root, source, env['NAME'], env['VERSION']
, env['SUMMARY'], id_set) |
| 209 build_wxsfile_default_gui(root) |
| 210 build_license_file(target[0].get_dir(), env) |
| 211 |
| 212 # write the xml to a file |
| 213 file.write( doc.toprettyxml() ) |
| 214 |
| 215 # call a user specified function |
| 216 if 'CHANGE_SPECFILE' in env: |
| 217 env['CHANGE_SPECFILE'](target, source) |
| 218 |
| 219 except KeyError, e: |
| 220 raise SCons.Errors.UserError( '"%s" package field for MSI is missing.' %
e.args[0] ) |
| 221 |
| 222 # |
| 223 # setup function |
| 224 # |
| 225 def create_default_directory_layout(root, NAME, VERSION, VENDOR, filename_set): |
| 226 """ Create the wix default target directory layout and return the innermost |
| 227 directory. |
| 228 |
| 229 We assume that the XML tree delivered in the root argument already contains |
| 230 the Product tag. |
| 231 |
| 232 Everything is put under the PFiles directory property defined by WiX. |
| 233 After that a directory with the 'VENDOR' tag is placed and then a |
| 234 directory with the name of the project and its VERSION. This leads to the |
| 235 following TARGET Directory Layout: |
| 236 C:\<PFiles>\<Vendor>\<Projectname-Version>\ |
| 237 Example: C:\Programme\Company\Product-1.2\ |
| 238 """ |
| 239 doc = Document() |
| 240 d1 = doc.createElement( 'Directory' ) |
| 241 d1.attributes['Id'] = 'TARGETDIR' |
| 242 d1.attributes['Name'] = 'SourceDir' |
| 243 |
| 244 d2 = doc.createElement( 'Directory' ) |
| 245 d2.attributes['Id'] = 'ProgramFilesFolder' |
| 246 d2.attributes['Name'] = 'PFiles' |
| 247 |
| 248 d3 = doc.createElement( 'Directory' ) |
| 249 d3.attributes['Id'] = 'VENDOR_folder' |
| 250 d3.attributes['Name'] = escape( gen_dos_short_file_name( VENDOR, filenam
e_set ) ) |
| 251 d3.attributes['LongName'] = escape( VENDOR ) |
| 252 |
| 253 d4 = doc.createElement( 'Directory' ) |
| 254 project_folder = "%s-%s" % ( NAME, VERSION ) |
| 255 d4.attributes['Id'] = 'MY_DEFAULT_FOLDER' |
| 256 d4.attributes['Name'] = escape( gen_dos_short_file_name( project_folder,
filename_set ) ) |
| 257 d4.attributes['LongName'] = escape( project_folder ) |
| 258 |
| 259 d1.childNodes.append( d2 ) |
| 260 d2.childNodes.append( d3 ) |
| 261 d3.childNodes.append( d4 ) |
| 262 |
| 263 root.getElementsByTagName('Product')[0].childNodes.append( d1 ) |
| 264 |
| 265 return d4 |
| 266 |
| 267 # |
| 268 # mandatory and optional file tags |
| 269 # |
| 270 def build_wxsfile_file_section(root, files, NAME, VERSION, VENDOR, filename_set,
id_set): |
| 271 """ builds the Component sections of the wxs file with their included files. |
| 272 |
| 273 Files need to be specified in 8.3 format and in the long name format, long |
| 274 filenames will be converted automatically. |
| 275 |
| 276 Features are specficied with the 'X_MSI_FEATURE' or 'DOC' FileTag. |
| 277 """ |
| 278 root = create_default_directory_layout( root, NAME, VERSION, VENDOR, f
ilename_set ) |
| 279 components = create_feature_dict( files ) |
| 280 factory = Document() |
| 281 |
| 282 def get_directory( node, dir ): |
| 283 """ returns the node under the given node representing the directory. |
| 284 |
| 285 Returns the component node if dir is None or empty. |
| 286 """ |
| 287 if dir == '' or not dir: |
| 288 return node |
| 289 |
| 290 Directory = node |
| 291 dir_parts = dir.split(os.path.sep) |
| 292 |
| 293 # to make sure that our directory ids are unique, the parent folders are |
| 294 # consecutively added to upper_dir |
| 295 upper_dir = '' |
| 296 |
| 297 # walk down the xml tree finding parts of the directory |
| 298 dir_parts = [d for d in dir_parts if d != ''] |
| 299 for d in dir_parts[:]: |
| 300 already_created = [c for c in Directory.childNodes |
| 301 if c.nodeName == 'Directory' |
| 302 and c.attributes['LongName'].value == escape(d)] |
| 303 |
| 304 if already_created != []: |
| 305 Directory = already_created[0] |
| 306 dir_parts.remove(d) |
| 307 upper_dir += d |
| 308 else: |
| 309 break |
| 310 |
| 311 for d in dir_parts: |
| 312 nDirectory = factory.createElement( 'Directory' ) |
| 313 nDirectory.attributes['LongName'] = escape( d ) |
| 314 nDirectory.attributes['Name'] = escape( gen_dos_short_file_name(
d, filename_set ) ) |
| 315 upper_dir += d |
| 316 nDirectory.attributes['Id'] = convert_to_id( upper_dir, id_set
) |
| 317 |
| 318 Directory.childNodes.append( nDirectory ) |
| 319 Directory = nDirectory |
| 320 |
| 321 return Directory |
| 322 |
| 323 for file in files: |
| 324 drive, path = os.path.splitdrive( file.PACKAGING_INSTALL_LOCATION ) |
| 325 filename = os.path.basename( path ) |
| 326 dirname = os.path.dirname( path ) |
| 327 |
| 328 h = { |
| 329 # tagname : default value |
| 330 'PACKAGING_X_MSI_VITAL' : 'yes', |
| 331 'PACKAGING_X_MSI_FILEID' : convert_to_id(filename, id_set), |
| 332 'PACKAGING_X_MSI_LONGNAME' : filename, |
| 333 'PACKAGING_X_MSI_SHORTNAME' : gen_dos_short_file_name(filename, file
name_set), |
| 334 'PACKAGING_X_MSI_SOURCE' : file.get_path(), |
| 335 } |
| 336 |
| 337 # fill in the default tags given above. |
| 338 for k,v in [ (k, v) for (k,v) in h.items() if not hasattr(file, k) ]: |
| 339 setattr( file, k, v ) |
| 340 |
| 341 File = factory.createElement( 'File' ) |
| 342 File.attributes['LongName'] = escape( file.PACKAGING_X_MSI_LONGNAME ) |
| 343 File.attributes['Name'] = escape( file.PACKAGING_X_MSI_SHORTNAME ) |
| 344 File.attributes['Source'] = escape( file.PACKAGING_X_MSI_SOURCE ) |
| 345 File.attributes['Id'] = escape( file.PACKAGING_X_MSI_FILEID ) |
| 346 File.attributes['Vital'] = escape( file.PACKAGING_X_MSI_VITAL ) |
| 347 |
| 348 # create the <Component> Tag under which this file should appear |
| 349 Component = factory.createElement('Component') |
| 350 Component.attributes['DiskId'] = '1' |
| 351 Component.attributes['Id'] = convert_to_id( filename, id_set ) |
| 352 |
| 353 # hang the component node under the root node and the file node |
| 354 # under the component node. |
| 355 Directory = get_directory( root, dirname ) |
| 356 Directory.childNodes.append( Component ) |
| 357 Component.childNodes.append( File ) |
| 358 |
| 359 # |
| 360 # additional functions |
| 361 # |
| 362 def build_wxsfile_features_section(root, files, NAME, VERSION, SUMMARY, id_set): |
| 363 """ This function creates the <features> tag based on the supplied xml tree. |
| 364 |
| 365 This is achieved by finding all <component>s and adding them to a default ta
rget. |
| 366 |
| 367 It should be called after the tree has been built completly. We assume |
| 368 that a MY_DEFAULT_FOLDER Property is defined in the wxs file tree. |
| 369 |
| 370 Furthermore a top-level with the name and VERSION of the software will be cr
eated. |
| 371 |
| 372 An PACKAGING_X_MSI_FEATURE can either be a string, where the feature |
| 373 DESCRIPTION will be the same as its title or a Tuple, where the first |
| 374 part will be its title and the second its DESCRIPTION. |
| 375 """ |
| 376 factory = Document() |
| 377 Feature = factory.createElement('Feature') |
| 378 Feature.attributes['Id'] = 'complete' |
| 379 Feature.attributes['ConfigurableDirectory'] = 'MY_DEFAULT_FOLDER' |
| 380 Feature.attributes['Level'] = '1' |
| 381 Feature.attributes['Title'] = escape( '%s %s' % (NAME, VERSI
ON) ) |
| 382 Feature.attributes['Description'] = escape( SUMMARY ) |
| 383 Feature.attributes['Display'] = 'expand' |
| 384 |
| 385 for (feature, files) in create_feature_dict(files).items(): |
| 386 SubFeature = factory.createElement('Feature') |
| 387 SubFeature.attributes['Level'] = '1' |
| 388 |
| 389 if SCons.Util.is_Tuple(feature): |
| 390 SubFeature.attributes['Id'] = convert_to_id( feature[0], id_set ) |
| 391 SubFeature.attributes['Title'] = escape(feature[0]) |
| 392 SubFeature.attributes['Description'] = escape(feature[1]) |
| 393 else: |
| 394 SubFeature.attributes['Id'] = convert_to_id( feature, id_set ) |
| 395 if feature=='default': |
| 396 SubFeature.attributes['Description'] = 'Main Part' |
| 397 SubFeature.attributes['Title'] = 'Main Part' |
| 398 elif feature=='PACKAGING_DOC': |
| 399 SubFeature.attributes['Description'] = 'Documentation' |
| 400 SubFeature.attributes['Title'] = 'Documentation' |
| 401 else: |
| 402 SubFeature.attributes['Description'] = escape(feature) |
| 403 SubFeature.attributes['Title'] = escape(feature) |
| 404 |
| 405 # build the componentrefs. As one of the design decision is that every |
| 406 # file is also a component we walk the list of files and create a |
| 407 # reference. |
| 408 for f in files: |
| 409 ComponentRef = factory.createElement('ComponentRef') |
| 410 ComponentRef.attributes['Id'] = convert_to_id( os.path.basename(f.ge
t_path()), id_set ) |
| 411 SubFeature.childNodes.append(ComponentRef) |
| 412 |
| 413 Feature.childNodes.append(SubFeature) |
| 414 |
| 415 root.getElementsByTagName('Product')[0].childNodes.append(Feature) |
| 416 |
| 417 def build_wxsfile_default_gui(root): |
| 418 """ this function adds a default GUI to the wxs file |
| 419 """ |
| 420 factory = Document() |
| 421 Product = root.getElementsByTagName('Product')[0] |
| 422 |
| 423 UIRef = factory.createElement('UIRef') |
| 424 UIRef.attributes['Id'] = 'WixUI_Mondo' |
| 425 Product.childNodes.append(UIRef) |
| 426 |
| 427 UIRef = factory.createElement('UIRef') |
| 428 UIRef.attributes['Id'] = 'WixUI_ErrorProgressText' |
| 429 Product.childNodes.append(UIRef) |
| 430 |
| 431 def build_license_file(directory, spec): |
| 432 """ creates a License.rtf file with the content of "X_MSI_LICENSE_TEXT" |
| 433 in the given directory |
| 434 """ |
| 435 name, text = '', '' |
| 436 |
| 437 try: |
| 438 name = spec['LICENSE'] |
| 439 text = spec['X_MSI_LICENSE_TEXT'] |
| 440 except KeyError: |
| 441 pass # ignore this as X_MSI_LICENSE_TEXT is optional |
| 442 |
| 443 if name!='' or text!='': |
| 444 file = open( os.path.join(directory.get_path(), 'License.rtf'), 'w' ) |
| 445 file.write('{\\rtf') |
| 446 if text!='': |
| 447 file.write(text.replace('\n', '\\par ')) |
| 448 else: |
| 449 file.write(name+'\\par\\par') |
| 450 file.write('}') |
| 451 file.close() |
| 452 |
| 453 # |
| 454 # mandatory and optional package tags |
| 455 # |
| 456 def build_wxsfile_header_section(root, spec): |
| 457 """ Adds the xml file node which define the package meta-data. |
| 458 """ |
| 459 # Create the needed DOM nodes and add them at the correct position in the tr
ee. |
| 460 factory = Document() |
| 461 Product = factory.createElement( 'Product' ) |
| 462 Package = factory.createElement( 'Package' ) |
| 463 |
| 464 root.childNodes.append( Product ) |
| 465 Product.childNodes.append( Package ) |
| 466 |
| 467 # set "mandatory" default values |
| 468 if 'X_MSI_LANGUAGE' not in spec: |
| 469 spec['X_MSI_LANGUAGE'] = '1033' # select english |
| 470 |
| 471 # mandatory sections, will throw a KeyError if the tag is not available |
| 472 Product.attributes['Name'] = escape( spec['NAME'] ) |
| 473 Product.attributes['Version'] = escape( spec['VERSION'] ) |
| 474 Product.attributes['Manufacturer'] = escape( spec['VENDOR'] ) |
| 475 Product.attributes['Language'] = escape( spec['X_MSI_LANGUAGE'] ) |
| 476 Package.attributes['Description'] = escape( spec['SUMMARY'] ) |
| 477 |
| 478 # now the optional tags, for which we avoid the KeyErrror exception |
| 479 if 'DESCRIPTION' in spec: |
| 480 Package.attributes['Comments'] = escape( spec['DESCRIPTION'] ) |
| 481 |
| 482 if 'X_MSI_UPGRADE_CODE' in spec: |
| 483 Package.attributes['X_MSI_UPGRADE_CODE'] = escape( spec['X_MSI_UPGRADE_C
ODE'] ) |
| 484 |
| 485 # We hardcode the media tag as our current model cannot handle it. |
| 486 Media = factory.createElement('Media') |
| 487 Media.attributes['Id'] = '1' |
| 488 Media.attributes['Cabinet'] = 'default.cab' |
| 489 Media.attributes['EmbedCab'] = 'yes' |
| 490 root.getElementsByTagName('Product')[0].childNodes.append(Media) |
| 491 |
| 492 # this builder is the entry-point for .wxs file compiler. |
| 493 wxs_builder = Builder( |
| 494 action = Action( build_wxsfile, string_wxsfile ), |
| 495 ensure_suffix = '.wxs' ) |
| 496 |
| 497 def package(env, target, source, PACKAGEROOT, NAME, VERSION, |
| 498 DESCRIPTION, SUMMARY, VENDOR, X_MSI_LANGUAGE, **kw): |
| 499 # make sure that the Wix Builder is in the environment |
| 500 SCons.Tool.Tool('wix').generate(env) |
| 501 |
| 502 # get put the keywords for the specfile compiler. These are the arguments |
| 503 # given to the package function and all optional ones stored in kw, minus |
| 504 # the the source, target and env one. |
| 505 loc = locals() |
| 506 del loc['kw'] |
| 507 kw.update(loc) |
| 508 del kw['source'], kw['target'], kw['env'] |
| 509 |
| 510 # strip the install builder from the source files |
| 511 target, source = stripinstallbuilder(target, source, env) |
| 512 |
| 513 # put the arguments into the env and call the specfile builder. |
| 514 env['msi_spec'] = kw |
| 515 specfile = wxs_builder(* [env, target, source], **kw) |
| 516 |
| 517 # now call the WiX Tool with the built specfile added as a source. |
| 518 msifile = env.WiX(target, specfile) |
| 519 |
| 520 # return the target and source tuple. |
| 521 return (msifile, source+[specfile]) |
| 522 |
| 523 # Local Variables: |
| 524 # tab-width:4 |
| 525 # indent-tabs-mode:nil |
| 526 # End: |
| 527 # vim: set expandtab tabstop=4 shiftwidth=4: |
OLD | NEW |