Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 1 | """Compiler abstraction model used by packaging. |
| 2 | |
| 3 | An abstract base class is defined in the ccompiler submodule, and |
| 4 | concrete implementations suitable for various platforms are defined in |
| 5 | the other submodules. The extension module is also placed in this |
| 6 | package. |
| 7 | |
| 8 | In general, code should not instantiate compiler classes directly but |
| 9 | use the new_compiler and customize_compiler functions provided in this |
| 10 | module. |
| 11 | |
| 12 | The compiler system has a registration API: get_default_compiler, |
| 13 | set_compiler, show_compilers. |
| 14 | """ |
| 15 | |
| 16 | import os |
| 17 | import sys |
| 18 | import re |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 19 | import sysconfig |
Tarek Ziade | 2bc55e4 | 2011-05-22 21:21:44 +0200 | [diff] [blame] | 20 | |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 21 | from packaging.util import resolve_name |
| 22 | from packaging.errors import PackagingPlatformError |
Tarek Ziade | 2bc55e4 | 2011-05-22 21:21:44 +0200 | [diff] [blame] | 23 | from packaging import logger |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 24 | |
| 25 | def customize_compiler(compiler): |
| 26 | """Do any platform-specific customization of a CCompiler instance. |
| 27 | |
| 28 | Mainly needed on Unix, so we can plug in the information that |
| 29 | varies across Unices and is stored in Python's Makefile. |
| 30 | """ |
| 31 | if compiler.name == "unix": |
| 32 | cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags = ( |
| 33 | sysconfig.get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS', |
| 34 | 'CCSHARED', 'LDSHARED', 'SO', 'AR', |
| 35 | 'ARFLAGS')) |
| 36 | |
| 37 | if 'CC' in os.environ: |
| 38 | cc = os.environ['CC'] |
| 39 | if 'CXX' in os.environ: |
| 40 | cxx = os.environ['CXX'] |
| 41 | if 'LDSHARED' in os.environ: |
| 42 | ldshared = os.environ['LDSHARED'] |
| 43 | if 'CPP' in os.environ: |
| 44 | cpp = os.environ['CPP'] |
| 45 | else: |
| 46 | cpp = cc + " -E" # not always |
| 47 | if 'LDFLAGS' in os.environ: |
| 48 | ldshared = ldshared + ' ' + os.environ['LDFLAGS'] |
| 49 | if 'CFLAGS' in os.environ: |
| 50 | cflags = opt + ' ' + os.environ['CFLAGS'] |
| 51 | ldshared = ldshared + ' ' + os.environ['CFLAGS'] |
| 52 | if 'CPPFLAGS' in os.environ: |
| 53 | cpp = cpp + ' ' + os.environ['CPPFLAGS'] |
| 54 | cflags = cflags + ' ' + os.environ['CPPFLAGS'] |
| 55 | ldshared = ldshared + ' ' + os.environ['CPPFLAGS'] |
| 56 | if 'AR' in os.environ: |
| 57 | ar = os.environ['AR'] |
| 58 | if 'ARFLAGS' in os.environ: |
| 59 | archiver = ar + ' ' + os.environ['ARFLAGS'] |
| 60 | else: |
| 61 | if ar_flags is not None: |
| 62 | archiver = ar + ' ' + ar_flags |
| 63 | else: |
| 64 | # see if its the proper default value |
| 65 | # mmm I don't want to backport the makefile |
| 66 | archiver = ar + ' rc' |
| 67 | |
| 68 | cc_cmd = cc + ' ' + cflags |
| 69 | compiler.set_executables( |
| 70 | preprocessor=cpp, |
| 71 | compiler=cc_cmd, |
| 72 | compiler_so=cc_cmd + ' ' + ccshared, |
| 73 | compiler_cxx=cxx, |
| 74 | linker_so=ldshared, |
| 75 | linker_exe=cc, |
| 76 | archiver=archiver) |
| 77 | |
| 78 | compiler.shared_lib_extension = so_ext |
| 79 | |
| 80 | |
| 81 | # Map a sys.platform/os.name ('posix', 'nt') to the default compiler |
| 82 | # type for that platform. Keys are interpreted as re match |
| 83 | # patterns. Order is important; platform mappings are preferred over |
| 84 | # OS names. |
| 85 | _default_compilers = ( |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 86 | # Platform string mappings |
| 87 | |
| 88 | # on a cygwin built python we can use gcc like an ordinary UNIXish |
| 89 | # compiler |
| 90 | ('cygwin.*', 'unix'), |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 91 | |
| 92 | # OS name mappings |
| 93 | ('posix', 'unix'), |
| 94 | ('nt', 'msvc'), |
Éric Araujo | 25987d0 | 2011-06-01 15:20:44 +0200 | [diff] [blame] | 95 | ) |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 96 | |
| 97 | def get_default_compiler(osname=None, platform=None): |
| 98 | """ Determine the default compiler to use for the given platform. |
| 99 | |
| 100 | osname should be one of the standard Python OS names (i.e. the |
| 101 | ones returned by os.name) and platform the common value |
| 102 | returned by sys.platform for the platform in question. |
| 103 | |
| 104 | The default values are os.name and sys.platform in case the |
| 105 | parameters are not given. |
| 106 | |
| 107 | """ |
| 108 | if osname is None: |
| 109 | osname = os.name |
| 110 | if platform is None: |
| 111 | platform = sys.platform |
| 112 | for pattern, compiler in _default_compilers: |
| 113 | if re.match(pattern, platform) is not None or \ |
| 114 | re.match(pattern, osname) is not None: |
| 115 | return compiler |
| 116 | # Defaults to Unix compiler |
| 117 | return 'unix' |
| 118 | |
| 119 | |
| 120 | # compiler mapping |
| 121 | # XXX useful to expose them? (i.e. get_compiler_names) |
| 122 | _COMPILERS = { |
| 123 | 'unix': 'packaging.compiler.unixccompiler.UnixCCompiler', |
| 124 | 'msvc': 'packaging.compiler.msvccompiler.MSVCCompiler', |
| 125 | 'cygwin': 'packaging.compiler.cygwinccompiler.CygwinCCompiler', |
| 126 | 'mingw32': 'packaging.compiler.cygwinccompiler.Mingw32CCompiler', |
| 127 | 'bcpp': 'packaging.compiler.bcppcompiler.BCPPCompiler', |
| 128 | } |
| 129 | |
| 130 | def set_compiler(location): |
| 131 | """Add or change a compiler""" |
| 132 | cls = resolve_name(location) |
| 133 | # XXX we want to check the class here |
| 134 | _COMPILERS[cls.name] = cls |
| 135 | |
| 136 | |
| 137 | def show_compilers(): |
| 138 | """Print list of available compilers (used by the "--help-compiler" |
| 139 | options to "build", "build_ext", "build_clib"). |
| 140 | """ |
| 141 | from packaging.fancy_getopt import FancyGetopt |
| 142 | compilers = [] |
| 143 | |
| 144 | for name, cls in _COMPILERS.items(): |
| 145 | if isinstance(cls, str): |
| 146 | cls = resolve_name(cls) |
| 147 | _COMPILERS[name] = cls |
| 148 | |
| 149 | compilers.append(("compiler=" + name, None, cls.description)) |
| 150 | |
| 151 | compilers.sort() |
| 152 | pretty_printer = FancyGetopt(compilers) |
| 153 | pretty_printer.print_help("List of available compilers:") |
| 154 | |
| 155 | |
| 156 | def new_compiler(plat=None, compiler=None, verbose=0, dry_run=False, |
| 157 | force=False): |
| 158 | """Generate an instance of some CCompiler subclass for the supplied |
| 159 | platform/compiler combination. 'plat' defaults to 'os.name' |
| 160 | (eg. 'posix', 'nt'), and 'compiler' defaults to the default compiler |
| 161 | for that platform. Currently only 'posix' and 'nt' are supported, and |
| 162 | the default compilers are "traditional Unix interface" (UnixCCompiler |
| 163 | class) and Visual C++ (MSVCCompiler class). Note that it's perfectly |
| 164 | possible to ask for a Unix compiler object under Windows, and a |
| 165 | Microsoft compiler object under Unix -- if you supply a value for |
| 166 | 'compiler', 'plat' is ignored. |
| 167 | """ |
| 168 | if plat is None: |
| 169 | plat = os.name |
| 170 | |
| 171 | try: |
| 172 | if compiler is None: |
| 173 | compiler = get_default_compiler(plat) |
| 174 | |
| 175 | cls = _COMPILERS[compiler] |
| 176 | except KeyError: |
| 177 | msg = "don't know how to compile C/C++ code on platform '%s'" % plat |
| 178 | if compiler is not None: |
| 179 | msg = msg + " with '%s' compiler" % compiler |
| 180 | raise PackagingPlatformError(msg) |
| 181 | |
| 182 | if isinstance(cls, str): |
| 183 | cls = resolve_name(cls) |
| 184 | _COMPILERS[compiler] = cls |
| 185 | |
| 186 | |
| 187 | # XXX The None is necessary to preserve backwards compatibility |
| 188 | # with classes that expect verbose to be the first positional |
| 189 | # argument. |
| 190 | return cls(None, dry_run, force) |
| 191 | |
| 192 | |
| 193 | def gen_preprocess_options(macros, include_dirs): |
| 194 | """Generate C pre-processor options (-D, -U, -I) as used by at least |
| 195 | two types of compilers: the typical Unix compiler and Visual C++. |
| 196 | 'macros' is the usual thing, a list of 1- or 2-tuples, where (name,) |
| 197 | means undefine (-U) macro 'name', and (name,value) means define (-D) |
| 198 | macro 'name' to 'value'. 'include_dirs' is just a list of directory |
| 199 | names to be added to the header file search path (-I). Returns a list |
| 200 | of command-line options suitable for either Unix compilers or Visual |
| 201 | C++. |
| 202 | """ |
| 203 | # XXX it would be nice (mainly aesthetic, and so we don't generate |
| 204 | # stupid-looking command lines) to go over 'macros' and eliminate |
| 205 | # redundant definitions/undefinitions (ie. ensure that only the |
| 206 | # latest mention of a particular macro winds up on the command |
| 207 | # line). I don't think it's essential, though, since most (all?) |
| 208 | # Unix C compilers only pay attention to the latest -D or -U |
| 209 | # mention of a macro on their command line. Similar situation for |
| 210 | # 'include_dirs'. I'm punting on both for now. Anyways, weeding out |
| 211 | # redundancies like this should probably be the province of |
| 212 | # CCompiler, since the data structures used are inherited from it |
| 213 | # and therefore common to all CCompiler classes. |
| 214 | |
| 215 | pp_opts = [] |
| 216 | for macro in macros: |
| 217 | |
| 218 | if not isinstance(macro, tuple) and 1 <= len(macro) <= 2: |
| 219 | raise TypeError( |
| 220 | "bad macro definition '%s': each element of 'macros'" |
| 221 | "list must be a 1- or 2-tuple" % macro) |
| 222 | |
| 223 | if len(macro) == 1: # undefine this macro |
| 224 | pp_opts.append("-U%s" % macro[0]) |
| 225 | elif len(macro) == 2: |
| 226 | if macro[1] is None: # define with no explicit value |
| 227 | pp_opts.append("-D%s" % macro[0]) |
| 228 | else: |
| 229 | # XXX *don't* need to be clever about quoting the |
| 230 | # macro value here, because we're going to avoid the |
| 231 | # shell at all costs when we spawn the command! |
| 232 | pp_opts.append("-D%s=%s" % macro) |
| 233 | |
| 234 | for dir in include_dirs: |
| 235 | pp_opts.append("-I%s" % dir) |
| 236 | |
| 237 | return pp_opts |
| 238 | |
| 239 | |
| 240 | def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries): |
| 241 | """Generate linker options for searching library directories and |
| 242 | linking with specific libraries. |
| 243 | |
| 244 | 'libraries' and 'library_dirs' are, respectively, lists of library names |
| 245 | (not filenames!) and search directories. Returns a list of command-line |
| 246 | options suitable for use with some compiler (depending on the two format |
| 247 | strings passed in). |
| 248 | """ |
| 249 | lib_opts = [] |
| 250 | |
| 251 | for dir in library_dirs: |
| 252 | lib_opts.append(compiler.library_dir_option(dir)) |
| 253 | |
| 254 | for dir in runtime_library_dirs: |
| 255 | opt = compiler.runtime_library_dir_option(dir) |
| 256 | if isinstance(opt, list): |
| 257 | lib_opts.extend(opt) |
| 258 | else: |
| 259 | lib_opts.append(opt) |
| 260 | |
| 261 | # XXX it's important that we *not* remove redundant library mentions! |
| 262 | # sometimes you really do have to say "-lfoo -lbar -lfoo" in order to |
| 263 | # resolve all symbols. I just hope we never have to say "-lfoo obj.o |
| 264 | # -lbar" to get things to work -- that's certainly a possibility, but a |
| 265 | # pretty nasty way to arrange your C code. |
| 266 | |
| 267 | for lib in libraries: |
| 268 | lib_dir, lib_name = os.path.split(lib) |
| 269 | if lib_dir != '': |
| 270 | lib_file = compiler.find_library_file([lib_dir], lib_name) |
| 271 | if lib_file is not None: |
| 272 | lib_opts.append(lib_file) |
| 273 | else: |
Tarek Ziade | 2bc55e4 | 2011-05-22 21:21:44 +0200 | [diff] [blame] | 274 | logger.warning("no library file corresponding to " |
Tarek Ziade | 1231a4e | 2011-05-19 13:07:25 +0200 | [diff] [blame] | 275 | "'%s' found (skipping)" % lib) |
| 276 | else: |
| 277 | lib_opts.append(compiler.library_option(lib)) |
| 278 | |
| 279 | return lib_opts |