|
1 """distutils.command.bdist_wininst |
|
2 |
|
3 Implements the Distutils 'bdist_wininst' command: create a windows installer |
|
4 exe-program.""" |
|
5 |
|
6 # This module should be kept compatible with Python 2.1. |
|
7 |
|
8 __revision__ = "$Id: bdist_wininst.py 63755 2008-05-28 01:54:55Z mark.hammond $" |
|
9 |
|
10 import sys, os, string |
|
11 from distutils.core import Command |
|
12 from distutils.util import get_platform |
|
13 from distutils.dir_util import create_tree, remove_tree |
|
14 from distutils.errors import * |
|
15 from distutils.sysconfig import get_python_version |
|
16 from distutils import log |
|
17 |
|
18 class bdist_wininst (Command): |
|
19 |
|
20 description = "create an executable installer for MS Windows" |
|
21 |
|
22 user_options = [('bdist-dir=', None, |
|
23 "temporary directory for creating the distribution"), |
|
24 ('plat-name=', 'p', |
|
25 "platform name to embed in generated filenames " |
|
26 "(default: %s)" % get_platform()), |
|
27 ('keep-temp', 'k', |
|
28 "keep the pseudo-installation tree around after " + |
|
29 "creating the distribution archive"), |
|
30 ('target-version=', None, |
|
31 "require a specific python version" + |
|
32 " on the target system"), |
|
33 ('no-target-compile', 'c', |
|
34 "do not compile .py to .pyc on the target system"), |
|
35 ('no-target-optimize', 'o', |
|
36 "do not compile .py to .pyo (optimized)" |
|
37 "on the target system"), |
|
38 ('dist-dir=', 'd', |
|
39 "directory to put final built distributions in"), |
|
40 ('bitmap=', 'b', |
|
41 "bitmap to use for the installer instead of python-powered logo"), |
|
42 ('title=', 't', |
|
43 "title to display on the installer background instead of default"), |
|
44 ('skip-build', None, |
|
45 "skip rebuilding everything (for testing/debugging)"), |
|
46 ('install-script=', None, |
|
47 "basename of installation script to be run after" |
|
48 "installation or before deinstallation"), |
|
49 ('pre-install-script=', None, |
|
50 "Fully qualified filename of a script to be run before " |
|
51 "any files are installed. This script need not be in the " |
|
52 "distribution"), |
|
53 ('user-access-control=', None, |
|
54 "specify Vista's UAC handling - 'none'/default=no " |
|
55 "handling, 'auto'=use UAC if target Python installed for " |
|
56 "all users, 'force'=always use UAC"), |
|
57 ] |
|
58 |
|
59 boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', |
|
60 'skip-build'] |
|
61 |
|
62 def initialize_options (self): |
|
63 self.bdist_dir = None |
|
64 self.plat_name = None |
|
65 self.keep_temp = 0 |
|
66 self.no_target_compile = 0 |
|
67 self.no_target_optimize = 0 |
|
68 self.target_version = None |
|
69 self.dist_dir = None |
|
70 self.bitmap = None |
|
71 self.title = None |
|
72 self.skip_build = 0 |
|
73 self.install_script = None |
|
74 self.pre_install_script = None |
|
75 self.user_access_control = None |
|
76 |
|
77 # initialize_options() |
|
78 |
|
79 |
|
80 def finalize_options (self): |
|
81 if self.bdist_dir is None: |
|
82 if self.skip_build and self.plat_name: |
|
83 # If build is skipped and plat_name is overridden, bdist will |
|
84 # not see the correct 'plat_name' - so set that up manually. |
|
85 bdist = self.distribution.get_command_obj('bdist') |
|
86 bdist.plat_name = self.plat_name |
|
87 # next the command will be initialized using that name |
|
88 bdist_base = self.get_finalized_command('bdist').bdist_base |
|
89 self.bdist_dir = os.path.join(bdist_base, 'wininst') |
|
90 if not self.target_version: |
|
91 self.target_version = "" |
|
92 if not self.skip_build and self.distribution.has_ext_modules(): |
|
93 short_version = get_python_version() |
|
94 if self.target_version and self.target_version != short_version: |
|
95 raise DistutilsOptionError, \ |
|
96 "target version can only be %s, or the '--skip_build'" \ |
|
97 " option must be specified" % (short_version,) |
|
98 self.target_version = short_version |
|
99 |
|
100 self.set_undefined_options('bdist', |
|
101 ('dist_dir', 'dist_dir'), |
|
102 ('plat_name', 'plat_name'), |
|
103 ) |
|
104 |
|
105 if self.install_script: |
|
106 for script in self.distribution.scripts: |
|
107 if self.install_script == os.path.basename(script): |
|
108 break |
|
109 else: |
|
110 raise DistutilsOptionError, \ |
|
111 "install_script '%s' not found in scripts" % \ |
|
112 self.install_script |
|
113 # finalize_options() |
|
114 |
|
115 |
|
116 def run (self): |
|
117 if (sys.platform != "win32" and |
|
118 (self.distribution.has_ext_modules() or |
|
119 self.distribution.has_c_libraries())): |
|
120 raise DistutilsPlatformError \ |
|
121 ("distribution contains extensions and/or C libraries; " |
|
122 "must be compiled on a Windows 32 platform") |
|
123 |
|
124 if not self.skip_build: |
|
125 self.run_command('build') |
|
126 |
|
127 install = self.reinitialize_command('install', reinit_subcommands=1) |
|
128 install.root = self.bdist_dir |
|
129 install.skip_build = self.skip_build |
|
130 install.warn_dir = 0 |
|
131 install.plat_name = self.plat_name |
|
132 |
|
133 install_lib = self.reinitialize_command('install_lib') |
|
134 # we do not want to include pyc or pyo files |
|
135 install_lib.compile = 0 |
|
136 install_lib.optimize = 0 |
|
137 |
|
138 if self.distribution.has_ext_modules(): |
|
139 # If we are building an installer for a Python version other |
|
140 # than the one we are currently running, then we need to ensure |
|
141 # our build_lib reflects the other Python version rather than ours. |
|
142 # Note that for target_version!=sys.version, we must have skipped the |
|
143 # build step, so there is no issue with enforcing the build of this |
|
144 # version. |
|
145 target_version = self.target_version |
|
146 if not target_version: |
|
147 assert self.skip_build, "Should have already checked this" |
|
148 target_version = sys.version[0:3] |
|
149 plat_specifier = ".%s-%s" % (self.plat_name, target_version) |
|
150 build = self.get_finalized_command('build') |
|
151 build.build_lib = os.path.join(build.build_base, |
|
152 'lib' + plat_specifier) |
|
153 |
|
154 # Use a custom scheme for the zip-file, because we have to decide |
|
155 # at installation time which scheme to use. |
|
156 for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): |
|
157 value = string.upper(key) |
|
158 if key == 'headers': |
|
159 value = value + '/Include/$dist_name' |
|
160 setattr(install, |
|
161 'install_' + key, |
|
162 value) |
|
163 |
|
164 log.info("installing to %s", self.bdist_dir) |
|
165 install.ensure_finalized() |
|
166 |
|
167 # avoid warning of 'install_lib' about installing |
|
168 # into a directory not in sys.path |
|
169 sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) |
|
170 |
|
171 install.run() |
|
172 |
|
173 del sys.path[0] |
|
174 |
|
175 # And make an archive relative to the root of the |
|
176 # pseudo-installation tree. |
|
177 from tempfile import mktemp |
|
178 archive_basename = mktemp() |
|
179 fullname = self.distribution.get_fullname() |
|
180 arcname = self.make_archive(archive_basename, "zip", |
|
181 root_dir=self.bdist_dir) |
|
182 # create an exe containing the zip-file |
|
183 self.create_exe(arcname, fullname, self.bitmap) |
|
184 if self.distribution.has_ext_modules(): |
|
185 pyversion = get_python_version() |
|
186 else: |
|
187 pyversion = 'any' |
|
188 self.distribution.dist_files.append(('bdist_wininst', pyversion, |
|
189 self.get_installer_filename(fullname))) |
|
190 # remove the zip-file again |
|
191 log.debug("removing temporary file '%s'", arcname) |
|
192 os.remove(arcname) |
|
193 |
|
194 if not self.keep_temp: |
|
195 remove_tree(self.bdist_dir, dry_run=self.dry_run) |
|
196 |
|
197 # run() |
|
198 |
|
199 def get_inidata (self): |
|
200 # Return data describing the installation. |
|
201 |
|
202 lines = [] |
|
203 metadata = self.distribution.metadata |
|
204 |
|
205 # Write the [metadata] section. |
|
206 lines.append("[metadata]") |
|
207 |
|
208 # 'info' will be displayed in the installer's dialog box, |
|
209 # describing the items to be installed. |
|
210 info = (metadata.long_description or '') + '\n' |
|
211 |
|
212 # Escape newline characters |
|
213 def escape(s): |
|
214 return string.replace(s, "\n", "\\n") |
|
215 |
|
216 for name in ["author", "author_email", "description", "maintainer", |
|
217 "maintainer_email", "name", "url", "version"]: |
|
218 data = getattr(metadata, name, "") |
|
219 if data: |
|
220 info = info + ("\n %s: %s" % \ |
|
221 (string.capitalize(name), escape(data))) |
|
222 lines.append("%s=%s" % (name, escape(data))) |
|
223 |
|
224 # The [setup] section contains entries controlling |
|
225 # the installer runtime. |
|
226 lines.append("\n[Setup]") |
|
227 if self.install_script: |
|
228 lines.append("install_script=%s" % self.install_script) |
|
229 lines.append("info=%s" % escape(info)) |
|
230 lines.append("target_compile=%d" % (not self.no_target_compile)) |
|
231 lines.append("target_optimize=%d" % (not self.no_target_optimize)) |
|
232 if self.target_version: |
|
233 lines.append("target_version=%s" % self.target_version) |
|
234 if self.user_access_control: |
|
235 lines.append("user_access_control=%s" % self.user_access_control) |
|
236 |
|
237 title = self.title or self.distribution.get_fullname() |
|
238 lines.append("title=%s" % escape(title)) |
|
239 import time |
|
240 import distutils |
|
241 build_info = "Built %s with distutils-%s" % \ |
|
242 (time.ctime(time.time()), distutils.__version__) |
|
243 lines.append("build_info=%s" % build_info) |
|
244 return string.join(lines, "\n") |
|
245 |
|
246 # get_inidata() |
|
247 |
|
248 def create_exe (self, arcname, fullname, bitmap=None): |
|
249 import struct |
|
250 |
|
251 self.mkpath(self.dist_dir) |
|
252 |
|
253 cfgdata = self.get_inidata() |
|
254 |
|
255 installer_name = self.get_installer_filename(fullname) |
|
256 self.announce("creating %s" % installer_name) |
|
257 |
|
258 if bitmap: |
|
259 bitmapdata = open(bitmap, "rb").read() |
|
260 bitmaplen = len(bitmapdata) |
|
261 else: |
|
262 bitmaplen = 0 |
|
263 |
|
264 file = open(installer_name, "wb") |
|
265 file.write(self.get_exe_bytes()) |
|
266 if bitmap: |
|
267 file.write(bitmapdata) |
|
268 |
|
269 # Convert cfgdata from unicode to ascii, mbcs encoded |
|
270 try: |
|
271 unicode |
|
272 except NameError: |
|
273 pass |
|
274 else: |
|
275 if isinstance(cfgdata, unicode): |
|
276 cfgdata = cfgdata.encode("mbcs") |
|
277 |
|
278 # Append the pre-install script |
|
279 cfgdata = cfgdata + "\0" |
|
280 if self.pre_install_script: |
|
281 script_data = open(self.pre_install_script, "r").read() |
|
282 cfgdata = cfgdata + script_data + "\n\0" |
|
283 else: |
|
284 # empty pre-install script |
|
285 cfgdata = cfgdata + "\0" |
|
286 file.write(cfgdata) |
|
287 |
|
288 # The 'magic number' 0x1234567B is used to make sure that the |
|
289 # binary layout of 'cfgdata' is what the wininst.exe binary |
|
290 # expects. If the layout changes, increment that number, make |
|
291 # the corresponding changes to the wininst.exe sources, and |
|
292 # recompile them. |
|
293 header = struct.pack("<iii", |
|
294 0x1234567B, # tag |
|
295 len(cfgdata), # length |
|
296 bitmaplen, # number of bytes in bitmap |
|
297 ) |
|
298 file.write(header) |
|
299 file.write(open(arcname, "rb").read()) |
|
300 |
|
301 # create_exe() |
|
302 |
|
303 def get_installer_filename(self, fullname): |
|
304 # Factored out to allow overriding in subclasses |
|
305 if self.target_version: |
|
306 # if we create an installer for a specific python version, |
|
307 # it's better to include this in the name |
|
308 installer_name = os.path.join(self.dist_dir, |
|
309 "%s.%s-py%s.exe" % |
|
310 (fullname, self.plat_name, self.target_version)) |
|
311 else: |
|
312 installer_name = os.path.join(self.dist_dir, |
|
313 "%s.%s.exe" % (fullname, self.plat_name)) |
|
314 return installer_name |
|
315 # get_installer_filename() |
|
316 |
|
317 def get_exe_bytes (self): |
|
318 from distutils.msvccompiler import get_build_version |
|
319 # If a target-version other than the current version has been |
|
320 # specified, then using the MSVC version from *this* build is no good. |
|
321 # Without actually finding and executing the target version and parsing |
|
322 # its sys.version, we just hard-code our knowledge of old versions. |
|
323 # NOTE: Possible alternative is to allow "--target-version" to |
|
324 # specify a Python executable rather than a simple version string. |
|
325 # We can then execute this program to obtain any info we need, such |
|
326 # as the real sys.version string for the build. |
|
327 cur_version = get_python_version() |
|
328 if self.target_version and self.target_version != cur_version: |
|
329 # If the target version is *later* than us, then we assume they |
|
330 # use what we use |
|
331 # string compares seem wrong, but are what sysconfig.py itself uses |
|
332 if self.target_version > cur_version: |
|
333 bv = get_build_version() |
|
334 else: |
|
335 if self.target_version < "2.4": |
|
336 bv = 6.0 |
|
337 else: |
|
338 bv = 7.1 |
|
339 else: |
|
340 # for current version - use authoritative check. |
|
341 bv = get_build_version() |
|
342 |
|
343 # wininst-x.y.exe is in the same directory as this file |
|
344 directory = os.path.dirname(__file__) |
|
345 # we must use a wininst-x.y.exe built with the same C compiler |
|
346 # used for python. XXX What about mingw, borland, and so on? |
|
347 if self.plat_name == 'win32': |
|
348 sfix = '' |
|
349 else: |
|
350 sfix = self.plat_name[3:] # strip 'win' - leaves eg '-amd64' |
|
351 filename = os.path.join(directory, "wininst-%.1f%s.exe" % (bv, sfix)) |
|
352 return open(filename, "rb").read() |
|
353 # class bdist_wininst |