codekingpro/portable-devtools
114k
1"""distutils.bcppcompiler2 3Contains BorlandCCompiler, an implementation of the abstract CCompiler class4for the Borland C++ compiler.5"""6 7# This implementation by Lyle Johnson, based on the original msvccompiler.py8# module and using the directions originally published by Gordon Williams.9 10# XXX looks like there's a LOT of overlap between these two classes:11# someone should sit down and factor out the common code as12# WindowsCCompiler! --GPW13 14import os15import warnings16 17from ._log import log18from ._modified import newer19from .ccompiler import CCompiler, gen_preprocess_options20from .errors import (21 CompileError,22 DistutilsExecError,23 LibError,24 LinkError,25 UnknownFileError,26)27from .file_util import write_file28 29warnings.warn(30 "bcppcompiler is deprecated and slated to be removed "31 "in the future. Please discontinue use or file an issue "32 "with pypa/distutils describing your use case.",33 DeprecationWarning,34)35 36 37class BCPPCompiler(CCompiler):38 """Concrete class that implements an interface to the Borland C/C++39 compiler, as defined by the CCompiler abstract class.40 """41 42 compiler_type = 'bcpp'43 44 # Just set this so CCompiler's constructor doesn't barf. We currently45 # don't use the 'set_executables()' bureaucracy provided by CCompiler,46 # as it really isn't necessary for this sort of single-compiler class.47 # Would be nice to have a consistent interface with UnixCCompiler,48 # though, so it's worth thinking about.49 executables = {}50 51 # Private class data (need to distinguish C from C++ source for compiler)52 _c_extensions = ['.c']53 _cpp_extensions = ['.cc', '.cpp', '.cxx']54 55 # Needed for the filename generation methods provided by the56 # base class, CCompiler.57 src_extensions = _c_extensions + _cpp_extensions58 obj_extension = '.obj'59 static_lib_extension = '.lib'60 shared_lib_extension = '.dll'61 static_lib_format = shared_lib_format = '%s%s'62 exe_extension = '.exe'63 64 def __init__(self, verbose=0, dry_run=0, force=0):65 super().__init__(verbose, dry_run, force)66 67 # These executables are assumed to all be in the path.68 # Borland doesn't seem to use any special registry settings to69 # indicate their installation locations.70 71 self.cc = "bcc32.exe"72 self.linker = "ilink32.exe"73 self.lib = "tlib.exe"74 75 self.preprocess_options = None76 self.compile_options = ['/tWM', '/O2', '/q', '/g0']77 self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0']78 79 self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x']80 self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']81 self.ldflags_static = []82 self.ldflags_exe = ['/Gn', '/q', '/x']83 self.ldflags_exe_debug = ['/Gn', '/q', '/x', '/r']84 85 # -- Worker methods ------------------------------------------------86 87 def compile( # noqa: C90188 self,89 sources,90 output_dir=None,91 macros=None,92 include_dirs=None,93 debug=0,94 extra_preargs=None,95 extra_postargs=None,96 depends=None,97 ):98 macros, objects, extra_postargs, pp_opts, build = self._setup_compile(99 output_dir, macros, include_dirs, sources, depends, extra_postargs100 )101 compile_opts = extra_preargs or []102 compile_opts.append('-c')103 if debug:104 compile_opts.extend(self.compile_options_debug)105 else:106 compile_opts.extend(self.compile_options)107 108 for obj in objects:109 try:110 src, ext = build[obj]111 except KeyError:112 continue113 # XXX why do the normpath here?114 src = os.path.normpath(src)115 obj = os.path.normpath(obj)116 # XXX _setup_compile() did a mkpath() too but before the normpath.117 # Is it possible to skip the normpath?118 self.mkpath(os.path.dirname(obj))119 120 if ext == '.res':121 # This is already a binary file -- skip it.122 continue # the 'for' loop123 if ext == '.rc':124 # This needs to be compiled to a .res file -- do it now.125 try:126 self.spawn(["brcc32", "-fo", obj, src])127 except DistutilsExecError as msg:128 raise CompileError(msg)129 continue # the 'for' loop130 131 # The next two are both for the real compiler.132 if ext in self._c_extensions:133 input_opt = ""134 elif ext in self._cpp_extensions:135 input_opt = "-P"136 else:137 # Unknown file type -- no extra options. The compiler138 # will probably fail, but let it just in case this is a139 # file the compiler recognizes even if we don't.140 input_opt = ""141 142 output_opt = "-o" + obj143 144 # Compiler command line syntax is: "bcc32 [options] file(s)".145 # Note that the source file names must appear at the end of146 # the command line.147 try:148 self.spawn(149 [self.cc]150 + compile_opts151 + pp_opts152 + [input_opt, output_opt]153 + extra_postargs154 + [src]155 )156 except DistutilsExecError as msg:157 raise CompileError(msg)158 159 return objects160 161 # compile ()162 163 def create_static_lib(164 self, objects, output_libname, output_dir=None, debug=0, target_lang=None165 ):166 (objects, output_dir) = self._fix_object_args(objects, output_dir)167 output_filename = self.library_filename(output_libname, output_dir=output_dir)168 169 if self._need_link(objects, output_filename):170 lib_args = [output_filename, '/u'] + objects171 if debug:172 pass # XXX what goes here?173 try:174 self.spawn([self.lib] + lib_args)175 except DistutilsExecError as msg:176 raise LibError(msg)177 else:178 log.debug("skipping %s (up-to-date)", output_filename)179 180 # create_static_lib ()181 182 def link( # noqa: C901183 self,184 target_desc,185 objects,186 output_filename,187 output_dir=None,188 libraries=None,189 library_dirs=None,190 runtime_library_dirs=None,191 export_symbols=None,192 debug=0,193 extra_preargs=None,194 extra_postargs=None,195 build_temp=None,196 target_lang=None,197 ):198 # XXX this ignores 'build_temp'! should follow the lead of199 # msvccompiler.py200 201 (objects, output_dir) = self._fix_object_args(objects, output_dir)202 (libraries, library_dirs, runtime_library_dirs) = self._fix_lib_args(203 libraries, library_dirs, runtime_library_dirs204 )205 206 if runtime_library_dirs:207 log.warning(208 "I don't know what to do with 'runtime_library_dirs': %s",209 str(runtime_library_dirs),210 )211 212 if output_dir is not None:213 output_filename = os.path.join(output_dir, output_filename)214 215 if self._need_link(objects, output_filename):216 # Figure out linker args based on type of target.217 if target_desc == CCompiler.EXECUTABLE:218 startup_obj = 'c0w32'219 if debug:220 ld_args = self.ldflags_exe_debug[:]221 else:222 ld_args = self.ldflags_exe[:]223 else:224 startup_obj = 'c0d32'225 if debug:226 ld_args = self.ldflags_shared_debug[:]227 else:228 ld_args = self.ldflags_shared[:]229 230 # Create a temporary exports file for use by the linker231 if export_symbols is None:232 def_file = ''233 else:234 head, tail = os.path.split(output_filename)235 modname, ext = os.path.splitext(tail)236 temp_dir = os.path.dirname(objects[0]) # preserve tree structure237 def_file = os.path.join(temp_dir, '%s.def' % modname)238 contents = ['EXPORTS']239 for sym in export_symbols or []:240 contents.append(f' {sym}=_{sym}')241 self.execute(write_file, (def_file, contents), "writing %s" % def_file)242 243 # Borland C++ has problems with '/' in paths244 objects2 = map(os.path.normpath, objects)245 # split objects in .obj and .res files246 # Borland C++ needs them at different positions in the command line247 objects = [startup_obj]248 resources = []249 for file in objects2:250 (base, ext) = os.path.splitext(os.path.normcase(file))251 if ext == '.res':252 resources.append(file)253 else:254 objects.append(file)255 256 for ell in library_dirs:257 ld_args.append("/L%s" % os.path.normpath(ell))258 ld_args.append("/L.") # we sometimes use relative paths259 260 # list of object files261 ld_args.extend(objects)262 263 # XXX the command-line syntax for Borland C++ is a bit wonky;264 # certain filenames are jammed together in one big string, but265 # comma-delimited. This doesn't mesh too well with the266 # Unix-centric attitude (with a DOS/Windows quoting hack) of267 # 'spawn()', so constructing the argument list is a bit268 # awkward. Note that doing the obvious thing and jamming all269 # the filenames and commas into one argument would be wrong,270 # because 'spawn()' would quote any filenames with spaces in271 # them. Arghghh!. Apparently it works fine as coded...272 273 # name of dll/exe file274 ld_args.extend([',', output_filename])275 # no map file and start libraries276 ld_args.append(',,')277 278 for lib in libraries:279 # see if we find it and if there is a bcpp specific lib280 # (xxx_bcpp.lib)281 libfile = self.find_library_file(library_dirs, lib, debug)282 if libfile is None:283 ld_args.append(lib)284 # probably a BCPP internal library -- don't warn285 else:286 # full name which prefers bcpp_xxx.lib over xxx.lib287 ld_args.append(libfile)288 289 # some default libraries290 ld_args.extend(('import32', 'cw32mt'))291 292 # def file for export symbols293 ld_args.extend([',', def_file])294 # add resource files295 ld_args.append(',')296 ld_args.extend(resources)297 298 if extra_preargs:299 ld_args[:0] = extra_preargs300 if extra_postargs:301 ld_args.extend(extra_postargs)302 303 self.mkpath(os.path.dirname(output_filename))304 try:305 self.spawn([self.linker] + ld_args)306 except DistutilsExecError as msg:307 raise LinkError(msg)308 309 else:310 log.debug("skipping %s (up-to-date)", output_filename)311 312 # link ()313 314 # -- Miscellaneous methods -----------------------------------------315 316 def find_library_file(self, dirs, lib, debug=0):317 # List of effective library names to try, in order of preference:318 # xxx_bcpp.lib is better than xxx.lib319 # and xxx_d.lib is better than xxx.lib if debug is set320 #321 # The "_bcpp" suffix is to handle a Python installation for people322 # with multiple compilers (primarily Distutils hackers, I suspect323 # ;-). The idea is they'd have one static library for each324 # compiler they care about, since (almost?) every Windows compiler325 # seems to have a different format for static libraries.326 if debug:327 dlib = lib + "_d"328 try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib)329 else:330 try_names = (lib + "_bcpp", lib)331 332 for dir in dirs:333 for name in try_names:334 libfile = os.path.join(dir, self.library_filename(name))335 if os.path.exists(libfile):336 return libfile337 else:338 # Oops, didn't find it in *any* of 'dirs'339 return None340 341 # overwrite the one from CCompiler to support rc and res-files342 def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):343 if output_dir is None:344 output_dir = ''345 obj_names = []346 for src_name in source_filenames:347 # use normcase to make sure '.rc' is really '.rc' and not '.RC'348 (base, ext) = os.path.splitext(os.path.normcase(src_name))349 if ext not in (self.src_extensions + ['.rc', '.res']):350 raise UnknownFileError(f"unknown file type '{ext}' (from '{src_name}')")351 if strip_dir:352 base = os.path.basename(base)353 if ext == '.res':354 # these can go unchanged355 obj_names.append(os.path.join(output_dir, base + ext))356 elif ext == '.rc':357 # these need to be compiled to .res-files358 obj_names.append(os.path.join(output_dir, base + '.res'))359 else:360 obj_names.append(os.path.join(output_dir, base + self.obj_extension))361 return obj_names362 363 # object_filenames ()364 365 def preprocess(366 self,367 source,368 output_file=None,369 macros=None,370 include_dirs=None,371 extra_preargs=None,372 extra_postargs=None,373 ):374 (_, macros, include_dirs) = self._fix_compile_args(None, macros, include_dirs)375 pp_opts = gen_preprocess_options(macros, include_dirs)376 pp_args = ['cpp32.exe'] + pp_opts377 if output_file is not None:378 pp_args.append('-o' + output_file)379 if extra_preargs:380 pp_args[:0] = extra_preargs381 if extra_postargs:382 pp_args.extend(extra_postargs)383 pp_args.append(source)384 385 # We need to preprocess: either we're being forced to, or the386 # source file is newer than the target (or the target doesn't387 # exist).388 if self.force or output_file is None or newer(source, output_file):389 if output_file:390 self.mkpath(os.path.dirname(output_file))391 try:392 self.spawn(pp_args)393 except DistutilsExecError as msg:394 print(msg)395 raise CompileError(msg)396 397 # preprocess()398 