bcppcompiler.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. """distutils.bcppcompiler
  2. Contains BorlandCCompiler, an implementation of the abstract CCompiler class
  3. for the Borland C++ compiler.
  4. """
  5. # This implementation by Lyle Johnson, based on the original msvccompiler.py
  6. # module and using the directions originally published by Gordon Williams.
  7. # XXX looks like there's a LOT of overlap between these two classes:
  8. # someone should sit down and factor out the common code as
  9. # WindowsCCompiler! --GPW
  10. import os
  11. import warnings
  12. from distutils.errors import (
  13. DistutilsExecError,
  14. CompileError,
  15. LibError,
  16. LinkError,
  17. UnknownFileError,
  18. )
  19. from distutils.ccompiler import CCompiler, gen_preprocess_options
  20. from distutils.file_util import write_file
  21. from distutils.dep_util import newer
  22. from distutils import log
  23. warnings.warn(
  24. "bcppcompiler is deprecated and slated to be removed "
  25. "in the future. Please discontinue use or file an issue "
  26. "with pypa/distutils describing your use case.",
  27. DeprecationWarning,
  28. )
  29. class BCPPCompiler(CCompiler):
  30. """Concrete class that implements an interface to the Borland C/C++
  31. compiler, as defined by the CCompiler abstract class.
  32. """
  33. compiler_type = 'bcpp'
  34. # Just set this so CCompiler's constructor doesn't barf. We currently
  35. # don't use the 'set_executables()' bureaucracy provided by CCompiler,
  36. # as it really isn't necessary for this sort of single-compiler class.
  37. # Would be nice to have a consistent interface with UnixCCompiler,
  38. # though, so it's worth thinking about.
  39. executables = {}
  40. # Private class data (need to distinguish C from C++ source for compiler)
  41. _c_extensions = ['.c']
  42. _cpp_extensions = ['.cc', '.cpp', '.cxx']
  43. # Needed for the filename generation methods provided by the
  44. # base class, CCompiler.
  45. src_extensions = _c_extensions + _cpp_extensions
  46. obj_extension = '.obj'
  47. static_lib_extension = '.lib'
  48. shared_lib_extension = '.dll'
  49. static_lib_format = shared_lib_format = '%s%s'
  50. exe_extension = '.exe'
  51. def __init__(self, verbose=0, dry_run=0, force=0):
  52. super().__init__(verbose, dry_run, force)
  53. # These executables are assumed to all be in the path.
  54. # Borland doesn't seem to use any special registry settings to
  55. # indicate their installation locations.
  56. self.cc = "bcc32.exe"
  57. self.linker = "ilink32.exe"
  58. self.lib = "tlib.exe"
  59. self.preprocess_options = None
  60. self.compile_options = ['/tWM', '/O2', '/q', '/g0']
  61. self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0']
  62. self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x']
  63. self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']
  64. self.ldflags_static = []
  65. self.ldflags_exe = ['/Gn', '/q', '/x']
  66. self.ldflags_exe_debug = ['/Gn', '/q', '/x', '/r']
  67. # -- Worker methods ------------------------------------------------
  68. def compile( # noqa: C901
  69. self,
  70. sources,
  71. output_dir=None,
  72. macros=None,
  73. include_dirs=None,
  74. debug=0,
  75. extra_preargs=None,
  76. extra_postargs=None,
  77. depends=None,
  78. ):
  79. macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
  80. output_dir, macros, include_dirs, sources, depends, extra_postargs
  81. )
  82. compile_opts = extra_preargs or []
  83. compile_opts.append('-c')
  84. if debug:
  85. compile_opts.extend(self.compile_options_debug)
  86. else:
  87. compile_opts.extend(self.compile_options)
  88. for obj in objects:
  89. try:
  90. src, ext = build[obj]
  91. except KeyError:
  92. continue
  93. # XXX why do the normpath here?
  94. src = os.path.normpath(src)
  95. obj = os.path.normpath(obj)
  96. # XXX _setup_compile() did a mkpath() too but before the normpath.
  97. # Is it possible to skip the normpath?
  98. self.mkpath(os.path.dirname(obj))
  99. if ext == '.res':
  100. # This is already a binary file -- skip it.
  101. continue # the 'for' loop
  102. if ext == '.rc':
  103. # This needs to be compiled to a .res file -- do it now.
  104. try:
  105. self.spawn(["brcc32", "-fo", obj, src])
  106. except DistutilsExecError as msg:
  107. raise CompileError(msg)
  108. continue # the 'for' loop
  109. # The next two are both for the real compiler.
  110. if ext in self._c_extensions:
  111. input_opt = ""
  112. elif ext in self._cpp_extensions:
  113. input_opt = "-P"
  114. else:
  115. # Unknown file type -- no extra options. The compiler
  116. # will probably fail, but let it just in case this is a
  117. # file the compiler recognizes even if we don't.
  118. input_opt = ""
  119. output_opt = "-o" + obj
  120. # Compiler command line syntax is: "bcc32 [options] file(s)".
  121. # Note that the source file names must appear at the end of
  122. # the command line.
  123. try:
  124. self.spawn(
  125. [self.cc]
  126. + compile_opts
  127. + pp_opts
  128. + [input_opt, output_opt]
  129. + extra_postargs
  130. + [src]
  131. )
  132. except DistutilsExecError as msg:
  133. raise CompileError(msg)
  134. return objects
  135. # compile ()
  136. def create_static_lib(
  137. self, objects, output_libname, output_dir=None, debug=0, target_lang=None
  138. ):
  139. (objects, output_dir) = self._fix_object_args(objects, output_dir)
  140. output_filename = self.library_filename(output_libname, output_dir=output_dir)
  141. if self._need_link(objects, output_filename):
  142. lib_args = [output_filename, '/u'] + objects
  143. if debug:
  144. pass # XXX what goes here?
  145. try:
  146. self.spawn([self.lib] + lib_args)
  147. except DistutilsExecError as msg:
  148. raise LibError(msg)
  149. else:
  150. log.debug("skipping %s (up-to-date)", output_filename)
  151. # create_static_lib ()
  152. def link( # noqa: C901
  153. self,
  154. target_desc,
  155. objects,
  156. output_filename,
  157. output_dir=None,
  158. libraries=None,
  159. library_dirs=None,
  160. runtime_library_dirs=None,
  161. export_symbols=None,
  162. debug=0,
  163. extra_preargs=None,
  164. extra_postargs=None,
  165. build_temp=None,
  166. target_lang=None,
  167. ):
  168. # XXX this ignores 'build_temp'! should follow the lead of
  169. # msvccompiler.py
  170. (objects, output_dir) = self._fix_object_args(objects, output_dir)
  171. (libraries, library_dirs, runtime_library_dirs) = self._fix_lib_args(
  172. libraries, library_dirs, runtime_library_dirs
  173. )
  174. if runtime_library_dirs:
  175. log.warn(
  176. "I don't know what to do with 'runtime_library_dirs': %s",
  177. str(runtime_library_dirs),
  178. )
  179. if output_dir is not None:
  180. output_filename = os.path.join(output_dir, output_filename)
  181. if self._need_link(objects, output_filename):
  182. # Figure out linker args based on type of target.
  183. if target_desc == CCompiler.EXECUTABLE:
  184. startup_obj = 'c0w32'
  185. if debug:
  186. ld_args = self.ldflags_exe_debug[:]
  187. else:
  188. ld_args = self.ldflags_exe[:]
  189. else:
  190. startup_obj = 'c0d32'
  191. if debug:
  192. ld_args = self.ldflags_shared_debug[:]
  193. else:
  194. ld_args = self.ldflags_shared[:]
  195. # Create a temporary exports file for use by the linker
  196. if export_symbols is None:
  197. def_file = ''
  198. else:
  199. head, tail = os.path.split(output_filename)
  200. modname, ext = os.path.splitext(tail)
  201. temp_dir = os.path.dirname(objects[0]) # preserve tree structure
  202. def_file = os.path.join(temp_dir, '%s.def' % modname)
  203. contents = ['EXPORTS']
  204. for sym in export_symbols or []:
  205. contents.append(' {}=_{}'.format(sym, sym))
  206. self.execute(write_file, (def_file, contents), "writing %s" % def_file)
  207. # Borland C++ has problems with '/' in paths
  208. objects2 = map(os.path.normpath, objects)
  209. # split objects in .obj and .res files
  210. # Borland C++ needs them at different positions in the command line
  211. objects = [startup_obj]
  212. resources = []
  213. for file in objects2:
  214. (base, ext) = os.path.splitext(os.path.normcase(file))
  215. if ext == '.res':
  216. resources.append(file)
  217. else:
  218. objects.append(file)
  219. for ell in library_dirs:
  220. ld_args.append("/L%s" % os.path.normpath(ell))
  221. ld_args.append("/L.") # we sometimes use relative paths
  222. # list of object files
  223. ld_args.extend(objects)
  224. # XXX the command-line syntax for Borland C++ is a bit wonky;
  225. # certain filenames are jammed together in one big string, but
  226. # comma-delimited. This doesn't mesh too well with the
  227. # Unix-centric attitude (with a DOS/Windows quoting hack) of
  228. # 'spawn()', so constructing the argument list is a bit
  229. # awkward. Note that doing the obvious thing and jamming all
  230. # the filenames and commas into one argument would be wrong,
  231. # because 'spawn()' would quote any filenames with spaces in
  232. # them. Arghghh!. Apparently it works fine as coded...
  233. # name of dll/exe file
  234. ld_args.extend([',', output_filename])
  235. # no map file and start libraries
  236. ld_args.append(',,')
  237. for lib in libraries:
  238. # see if we find it and if there is a bcpp specific lib
  239. # (xxx_bcpp.lib)
  240. libfile = self.find_library_file(library_dirs, lib, debug)
  241. if libfile is None:
  242. ld_args.append(lib)
  243. # probably a BCPP internal library -- don't warn
  244. else:
  245. # full name which prefers bcpp_xxx.lib over xxx.lib
  246. ld_args.append(libfile)
  247. # some default libraries
  248. ld_args.append('import32')
  249. ld_args.append('cw32mt')
  250. # def file for export symbols
  251. ld_args.extend([',', def_file])
  252. # add resource files
  253. ld_args.append(',')
  254. ld_args.extend(resources)
  255. if extra_preargs:
  256. ld_args[:0] = extra_preargs
  257. if extra_postargs:
  258. ld_args.extend(extra_postargs)
  259. self.mkpath(os.path.dirname(output_filename))
  260. try:
  261. self.spawn([self.linker] + ld_args)
  262. except DistutilsExecError as msg:
  263. raise LinkError(msg)
  264. else:
  265. log.debug("skipping %s (up-to-date)", output_filename)
  266. # link ()
  267. # -- Miscellaneous methods -----------------------------------------
  268. def find_library_file(self, dirs, lib, debug=0):
  269. # List of effective library names to try, in order of preference:
  270. # xxx_bcpp.lib is better than xxx.lib
  271. # and xxx_d.lib is better than xxx.lib if debug is set
  272. #
  273. # The "_bcpp" suffix is to handle a Python installation for people
  274. # with multiple compilers (primarily Distutils hackers, I suspect
  275. # ;-). The idea is they'd have one static library for each
  276. # compiler they care about, since (almost?) every Windows compiler
  277. # seems to have a different format for static libraries.
  278. if debug:
  279. dlib = lib + "_d"
  280. try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib)
  281. else:
  282. try_names = (lib + "_bcpp", lib)
  283. for dir in dirs:
  284. for name in try_names:
  285. libfile = os.path.join(dir, self.library_filename(name))
  286. if os.path.exists(libfile):
  287. return libfile
  288. else:
  289. # Oops, didn't find it in *any* of 'dirs'
  290. return None
  291. # overwrite the one from CCompiler to support rc and res-files
  292. def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
  293. if output_dir is None:
  294. output_dir = ''
  295. obj_names = []
  296. for src_name in source_filenames:
  297. # use normcase to make sure '.rc' is really '.rc' and not '.RC'
  298. (base, ext) = os.path.splitext(os.path.normcase(src_name))
  299. if ext not in (self.src_extensions + ['.rc', '.res']):
  300. raise UnknownFileError(
  301. "unknown file type '{}' (from '{}')".format(ext, src_name)
  302. )
  303. if strip_dir:
  304. base = os.path.basename(base)
  305. if ext == '.res':
  306. # these can go unchanged
  307. obj_names.append(os.path.join(output_dir, base + ext))
  308. elif ext == '.rc':
  309. # these need to be compiled to .res-files
  310. obj_names.append(os.path.join(output_dir, base + '.res'))
  311. else:
  312. obj_names.append(os.path.join(output_dir, base + self.obj_extension))
  313. return obj_names
  314. # object_filenames ()
  315. def preprocess(
  316. self,
  317. source,
  318. output_file=None,
  319. macros=None,
  320. include_dirs=None,
  321. extra_preargs=None,
  322. extra_postargs=None,
  323. ):
  324. (_, macros, include_dirs) = self._fix_compile_args(None, macros, include_dirs)
  325. pp_opts = gen_preprocess_options(macros, include_dirs)
  326. pp_args = ['cpp32.exe'] + pp_opts
  327. if output_file is not None:
  328. pp_args.append('-o' + output_file)
  329. if extra_preargs:
  330. pp_args[:0] = extra_preargs
  331. if extra_postargs:
  332. pp_args.extend(extra_postargs)
  333. pp_args.append(source)
  334. # We need to preprocess: either we're being forced to, or the
  335. # source file is newer than the target (or the target doesn't
  336. # exist).
  337. if self.force or output_file is None or newer(source, output_file):
  338. if output_file:
  339. self.mkpath(os.path.dirname(output_file))
  340. try:
  341. self.spawn(pp_args)
  342. except DistutilsExecError as msg:
  343. print(msg)
  344. raise CompileError(msg)
  345. # preprocess()