import os import sys import re import copy from distutils import util from distutils.command import install from distutils.core import DEBUG from distutils.errors import DistutilsOptionError from Ft.Lib.DistExt import Util INSTALL_LOCATIONS = { 'install_lib' : 'Python modules (including C extensions)', 'install_headers' : 'C/C++ header files', 'install_scripts' : 'Executable scripts (for PATH environment variable)', 'install_data' : 'Examples, demos and other miscellaneous data files', 'install_sysconf': 'Configuration files', 'install_localstate' : 'Machine-specific variable data space', 'install_devel' : 'Developer files (regression tests)', 'install_text' : 'Text documentation', 'install_html' : 'HTML documentation', 'install_l10n' : 'Natural language message catalogs', } # Change the existing "installation schemes" by changing the 'install_data' # location and adding locations for our additional commands. INSTALL_SCHEMES = copy.deepcopy(install.INSTALL_SCHEMES) INSTALL_SCHEMES['unix_prefix'].update({ 'data' : '$base/share/$dist_name', 'sysconf' : '$base/etc/$dist_name', 'localstate' : '$platbase/var/$dist_name', 'devel' : '$platbase/lib/$dist_name', 'l10n' : '$base/share/locale', 'man' : '$base/man', 'docs' : '$base/share/doc/$dist_name', }) # The "home" scheme is also updated to include the Python version in the # 'install_lib' location. INSTALL_SCHEMES['unix_home'].update({ 'purelib' : '$base/lib/python$py_version_short', 'platlib' : '$base/lib/python$py_version_short', 'data' : '$base/share/$dist_name', 'sysconf' : '$base/share/etc/$dist_name', 'localstate' : '$base/share/var/$dist_name', 'devel' : '$base/lib/$dist_name', 'l10n' : '$base/share/locale', 'man' : '$base/share/man', 'docs' : '$base/share/doc/$dist_name', }) # Update the "other" schemes for name, scheme in INSTALL_SCHEMES.iteritems(): if name in ('unix_prefix', 'unix_home'): continue scheme.update({ 'data' : '$base/Share/$dist_name', 'sysconf' : '$base/Share/Settings/$dist_name', 'localstate' : '$base/Share/$dist_name', 'devel' : '$base/Share/$dist_name', 'l10n' : '$base/Share/Locale', 'man' : '$base/Share/Help', 'docs' : '$base/Share/Doc/$dist_name', }) # Add the installation scheme for FHS "local" directories INSTALL_SCHEMES['unix_local'] = { 'purelib' : '/usr/local/lib/python$py_version_short/site-packages', 'platlib' : '/usr/local/lib/python$py_version_short/site-packages', 'headers' : '/usr/local/include/$dist_name', 'scripts' : '/usr/local/bin', 'data' : '/usr/local/share/$dist_name', 'sysconf' : '/usr/local/etc/$dist_name', 'localstate' : '/var/local/lib/$dist_name', 'devel' : '/usr/local/lib/$dist_name', 'l10n' : '/usr/local/share/locale', 'man' : '/usr/local/share/man', 'docs' : '/usr/local/share/doc/$dist_name', } # Add the installation scheme for FHS "system" directories INSTALL_SCHEMES['unix_system'] = { 'purelib' : '/usr/lib/python$py_version_short/site-packages', 'platlib' : '/usr/lib/python$py_version_short/site-packages', 'headers' : '/usr/include/$dist_name', 'scripts' : '/usr/bin', 'data' : '/usr/share/$dist_name', 'sysconf' : '/etc/$dist_name', 'localstate' : '/var/lib/$dist_name', 'devel' : '/usr/lib/$dist_name', 'l10n' : '/usr/share/locale', 'man' : '/usr/share/man', 'docs' : '/usr/share/doc/$dist_name', } # The scheme used when no installation scheme options are given. It uses the # values set by the 'config' command. INSTALL_SCHEMES['default'] = { 'base' : '$exec', 'platbase' : '$exec_prefix', 'purelib' : '$pythonlibdir', 'platlib' : '$pythonlibdir', 'headers' : '$includedir', 'scripts' : '$bindir', 'data' : '$datadir', 'sysconf' : '$sysconfdir', 'localstate' : '$localstatedir', 'devel' : '$libdir', 'l10n' : '$localedir', 'man' : '$mandir', 'docs' : '$docdir', } # A special scheme for ZIPs or Python Eggs (or anything using PEP 302 # import hooks); entries that are None indicate that they are unused INSTALL_SCHEMES['zip'] = { 'base' : '', 'platbase' : '', 'purelib' : '$base', 'platlib' : '$base', 'headers' : None, 'scripts' : None, 'data' : '$base/Share', 'sysconf' : None, 'localstate' : None, 'devel' : None, 'l10n' : '$base/Share/Locale', 'man' : None, 'docs' : None, } SCHEME_KEYS = install.SCHEME_KEYS + ( 'sysconf', 'localstate', 'devel', 'l10n', 'man', 'docs') def GetBundleScheme(): scheme = INSTALL_SCHEMES['zip'].copy() scheme['base'] = scheme['platbase'] = os.sep for key in SCHEME_KEYS: value = scheme[key] if value: value = util.subst_vars(value, scheme) value = util.convert_path(value) pathname = os.sep.join([ path for path in value.split(os.sep) if path not in ('', '.') ]) if value.startswith(os.sep): pathname = os.sep + pathname scheme[key] = pathname scheme['lib'] = scheme['purelib'] return scheme class Install(install.install): command_name = 'install' user_options = install.install.user_options + [ ('with-docs', None, 'enable documentation install'), ('without-docs', None, 'disable documentation install'), ] # PKG-INFO is created for source distributions, so allow "developer" # friendly features to be enabled/disabled. index = -1 - int(os.path.exists('PKG-INFO')) user_options[index] = (user_options[index][0], user_options[index][1], user_options[index][2] + ' [default]') # inject the scheme selection options at an appropriate place index = 0 while user_options[index][0] != 'exec-prefix=': index += 1 if os.name == 'posix': user_options[index+1:index+1] = [ ('local', None, '(Unix only) Use FHS /usr/local installation scheme [default]'), ('system', None, '(Unix only) Use FHS /usr system installation scheme'), ] else: del user_options[index] # inject the "install-XXX" options at an appropriate place index = len(user_options) while not user_options[index-1][0].startswith('install-'): index -= 1 user_options[index:index] = [ ('install-sysconf=', None, 'installation directory for read-only host-specific data'), ('install-localstate=', None, 'installation directory for modifiable host-specific data'), ('install-devel=', None, 'installation directory for development files (regression tests)'), ('install-l10n=', None, 'installation directory for message catalogs'), #('install-man=', None, # 'installation directory for Unix man pages'), ('install-docs=', None, 'installation directory for documentation files'), #('install-info=', None, # 'installation directory for GNU info pages'), ] boolean_options = install.install.boolean_options + ['with-docs'] if os.name == 'posix': boolean_options += ['local', 'system'] negative_opt = install.install.negative_opt.copy() negative_opt['without-docs'] = 'with-docs' def initialize_options(self): install.install.initialize_options(self) self.local = None self.system = None self.install_sysconf = None self.install_localstate = None self.install_devel = None self.install_l10n = None self.install_man = None self.install_docs = None self.install_info = None self.with_docs = None # options not selectable from the command-line self.scheme = None self.no_report = None return def finalize_options(self): if self.scheme is None and (self.install_base is not None or self.install_platbase is not None): if ((self.install_lib is None and self.install_purelib is None and self.install_platlib is None) or self.install_headers is None or self.install_scripts is None or self.install_data is None): raise DistutilsOptionError, \ ("install-base or install-platbase supplied, but " "installation scheme is incomplete") self.scheme = "custom" if self.with_docs is None: self.with_docs = self.distribution.source_package install.install.finalize_options(self) return if sys.version < '2.3': def dump_dirs(self, msg): if not DEBUG: return from distutils.fancy_getopt import longopt_xlate print msg + ":" for opt in self.user_options: opt_name = opt[0] if opt_name[-1] == "=": opt_name = opt_name[0:-1] if opt_name in self.negative_opt: opt_name = self.negative_opt[opt_name] val = not getattr(self, opt_name.translate(longopt_xlate)) else: opt_name = opt_name.translate(longopt_xlate) val = getattr(self, opt_name) print " %s: %s" % (opt_name, val) def finalize_unix(self): # Only one of local, system, home, prefix/exec-prefix may be given # The easiest determine this is to just count the Nones if ((self.local is not None) + (self.system is not None) + (self.home is not None) + (self.prefix is not None or self.exec_prefix is not None)) > 1: raise DistutilsOptionError("only one of --local, --system, --home" " or --prefix/exec-prefix allowed") if self.scheme is not None: return elif self.local: self.install_base = self.install_platbase = '/usr/local' self.select_scheme('unix_local') elif self.system: self.install_base = self.install_platbase = '/usr' self.select_scheme('unix_system') elif self.home is not None or self.prefix is not None: install.install.finalize_unix(self) else: # use the directories defined by the 'config' command self.select_scheme('default') return def finalize_other(self): if self.scheme is not None: return elif self.home is not None or self.prefix is not None: install.install.finalize_other(self) else: # use the directories defined by the 'config' command self.select_scheme('default') return def get_scheme(self, name): return INSTALL_SCHEMES[name] def get_scheme_keys(self): return SCHEME_KEYS def select_scheme(self, name): scheme = self.get_scheme(name) for key in self.get_scheme_keys(): attrname = 'install_' + key if getattr(self, attrname) is None: setattr(self, attrname, scheme[key]) self.scheme = name return def expand_basedirs(self): dist = self.distribution.main_distribution if dist is not None: self.config_vars['dist_name'] = dist.get_name() self.config_vars['dist_version'] = dist.get_version() self.config_vars['dist_fullname'] = dist.get_fullname() config = self.get_finalized_command('config') self.config_vars.update(config.config_vars) return install.install.expand_basedirs(self) def expand_dirs(self): install.install.expand_dirs(self) self._expand_attrs(['install_sysconf', 'install_localstate', 'install_devel', 'install_l10n', #'install_man', 'install_docs', #'install_info', ]) def convert_paths(self, *names): extras = [ key for key in self.get_scheme_keys() if key not in names ] names = [ name for name in list(names) + extras if getattr(self, 'install_' + name) is not None ] return install.install.convert_paths(self, *names) def change_roots(self, *names): extras = [ key for key in self.get_scheme_keys() if key not in names ] names = [ name for name in list(names) + extras if getattr(self, 'install_' + name) is not None ] return install.install.change_roots(self, *names) def run(self): # Disable the standard warning so we can display it as part of the # installation report. warn_dir = self.warn_dir self.warn_dir = False install.install.run(self) # Restore the previous value self.warn_dir = warn_dir report = self.get_installation_report() if report: # level 3 is WARN, Distutils default level is 2 (INFO) so this # will still be displayed if '--quiet' is given. self.announce(report, 3) return def get_installation_report(self): lines = [] # Only display the installation locations if installed from a source # distribution (aka "user mode"). if self.distribution.source_package and not self.no_report: package = '%s version %s' % (self.distribution.get_name(), self.distribution.get_version()) lines.extend(['-'*72, '%s has been successfully installed!' % package, '']) for cmd_name in self.get_sub_commands(): try: header = INSTALL_LOCATIONS[cmd_name] except KeyError: # Unknown install command, skip it continue cmd = self.distribution.get_command_obj(cmd_name) try: install_dir = getattr(cmd, 'install_dir') except AttributeError: install_dir = getattr(self, cmd_name) # Strip off trailing separator if install_dir.endswith(os.sep): install_dir = install_dir[:-len(os.sep)] # Only output location if something is installed there if cmd.get_outputs(): lines.extend([header, ' '*4 + install_dir, '']) # Check if the Python library code is installed to a directory that # is on Python's search path. lib_dir = Util.NormalizePath(self.install_lib) sys_path = map(Util.NormalizePath, sys.path) if self.warn_dir and (lib_dir not in sys_path): lines.append("WARNING: The installation directory %r must be on" " Python's search path (sys.path) --" " This can be done by adding it to PYTHONPATH or by" "modifying sys.path in your code." % self.install_lib) return '\n'.join(lines) # -- Reporting methods --------------------------------------------- def get_source_files(self): files = [] for cmd_name in self.get_sub_commands(): cmd = self.get_finalized_command(cmd_name) files.extend(cmd.get_source_files()) return files # -- Predicates for sub-command list ------------------------------- def has_sysconf(self): return self.distribution.has_sysconf() def has_localstate(self): return self.distribution.has_localstate() def has_text(self): return self.distribution.has_text() def has_docs(self): return self.with_docs and self.distribution.has_docs() def has_devel(self): return self.distribution.has_devel() def has_l10n(self): return self.distribution.has_l10n() def has_config(self): return self.distribution.config_module is not None # a list of commands this command might have to run to do its work. sub_commands = install.install.sub_commands + [ ('install_sysconf', has_sysconf), ('install_localstate', has_localstate), ('install_devel', has_devel), #('install_man', has_scripts), ('install_text', has_text), ('install_html', has_docs), #('install_info', has_docs), ('install_l10n', has_l10n), ('install_config', has_config), ] # Ensure that .egg-info files are provided for by the tool-chain. # (new in 2.5 or setuptools) if 'install_egg_info' not in dict(sub_commands): sub_commands.append(('install_egg_info', None))