bcppcompiler.py 14.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
"""distutils.bcppcompiler

Contains BorlandCCompiler, an implementation of the abstract CCompiler class
for the Borland C++ compiler.
"""

# This implementation by Lyle Johnson, based on the original msvccompiler.py
# module and using the directions originally published by Gordon Williams.

# XXX looks like there's a LOT of overlap between these two classes:
# someone should sit down and factor out the common code as
# WindowsCCompiler!  --GPW

14

15 16 17 18 19 20
import os
from distutils.errors import \
     DistutilsExecError, DistutilsPlatformError, \
     CompileError, LibError, LinkError, UnknownFileError
from distutils.ccompiler import \
     CCompiler, gen_preprocess_options, gen_lib_options
21
from distutils.file_util import write_file
22
from distutils.dep_util import newer
23
from distutils import log
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

class BCPPCompiler(CCompiler) :
    """Concrete class that implements an interface to the Borland C/C++
    compiler, as defined by the CCompiler abstract class.
    """

    compiler_type = 'bcpp'

    # Just set this so CCompiler's constructor doesn't barf.  We currently
    # don't use the 'set_executables()' bureaucracy provided by CCompiler,
    # as it really isn't necessary for this sort of single-compiler class.
    # Would be nice to have a consistent interface with UnixCCompiler,
    # though, so it's worth thinking about.
    executables = {}

    # Private class data (need to distinguish C from C++ source for compiler)
    _c_extensions = ['.c']
    _cpp_extensions = ['.cc', '.cpp', '.cxx']

    # Needed for the filename generation methods provided by the
    # base class, CCompiler.
    src_extensions = _c_extensions + _cpp_extensions
    obj_extension = '.obj'
    static_lib_extension = '.lib'
    shared_lib_extension = '.dll'
    static_lib_format = shared_lib_format = '%s%s'
    exe_extension = '.exe'


    def __init__ (self,
                  verbose=0,
                  dry_run=0,
                  force=0):

        CCompiler.__init__ (self, verbose, dry_run, force)

        # These executables are assumed to all be in the path.
        # Borland doesn't seem to use any special registry settings to
        # indicate their installation locations.

        self.cc = "bcc32.exe"
65
        self.linker = "ilink32.exe"
66 67 68
        self.lib = "tlib.exe"

        self.preprocess_options = None
Greg Ward's avatar
Greg Ward committed
69 70
        self.compile_options = ['/tWM', '/O2', '/q', '/g0']
        self.compile_options_debug = ['/tWM', '/Od', '/q', '/g0']
71 72 73 74

        self.ldflags_shared = ['/Tpd', '/Gn', '/q', '/x']
        self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']
        self.ldflags_static = []
75 76
        self.ldflags_exe = ['/Gn', '/q', '/x']
        self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r']
77 78 79 80


    # -- Worker methods ------------------------------------------------

81 82 83
    def compile(self, sources,
                output_dir=None, macros=None, include_dirs=None, debug=0,
                extra_preargs=None, extra_postargs=None, depends=None):
84

85 86 87
        macros, objects, extra_postargs, pp_opts, build = \
                self._setup_compile(output_dir, macros, include_dirs, sources,
                                    depends, extra_postargs)
88 89 90 91 92 93
        compile_opts = extra_preargs or []
        compile_opts.append ('-c')
        if debug:
            compile_opts.extend (self.compile_options_debug)
        else:
            compile_opts.extend (self.compile_options)
Fred Drake's avatar
Fred Drake committed
94

95 96 97 98 99
        for obj in objects:
            try:
                src, ext = build[obj]
            except KeyError:
                continue
100 101 102 103 104 105 106 107 108 109 110 111
            # XXX why do the normpath here?
            src = os.path.normpath(src)
            obj = os.path.normpath(obj)
            # XXX _setup_compile() did a mkpath() too but before the normpath.
            # Is it possible to skip the normpath?
            self.mkpath(os.path.dirname(obj))

            if ext == '.res':
                # This is already a binary file -- skip it.
                continue # the 'for' loop
            if ext == '.rc':
                # This needs to be compiled to a .res file -- do it now.
112
                try:
113
                    self.spawn (["brcc32", "-fo", obj, src])
114
                except DistutilsExecError as msg:
115
                    raise CompileError(msg)
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
                continue # the 'for' loop

            # The next two are both for the real compiler.
            if ext in self._c_extensions:
                input_opt = ""
            elif ext in self._cpp_extensions:
                input_opt = "-P"
            else:
                # Unknown file type -- no extra options.  The compiler
                # will probably fail, but let it just in case this is a
                # file the compiler recognizes even if we don't.
                input_opt = ""

            output_opt = "-o" + obj

            # Compiler command line syntax is: "bcc32 [options] file(s)".
            # Note that the source file names must appear at the end of
            # the command line.
            try:
                self.spawn ([self.cc] + compile_opts + pp_opts +
                            [input_opt, output_opt] +
                            extra_postargs + [src])
138
            except DistutilsExecError as msg:
139
                raise CompileError(msg)
140 141 142 143 144 145 146 147 148 149 150

        return objects

    # compile ()


    def create_static_lib (self,
                           objects,
                           output_libname,
                           output_dir=None,
                           debug=0,
151
                           target_lang=None):
152 153 154 155 156 157 158 159 160 161

        (objects, output_dir) = self._fix_object_args (objects, output_dir)
        output_filename = \
            self.library_filename (output_libname, output_dir=output_dir)

        if self._need_link (objects, output_filename):
            lib_args = [output_filename, '/u'] + objects
            if debug:
                pass                    # XXX what goes here?
            try:
Fred Drake's avatar
Fred Drake committed
162
                self.spawn ([self.lib] + lib_args)
163
            except DistutilsExecError as msg:
164
                raise LibError(msg)
165
        else:
166
            log.debug("skipping %s (up-to-date)", output_filename)
167 168

    # create_static_lib ()
Fred Drake's avatar
Fred Drake committed
169 170


171
    def link (self,
Fred Drake's avatar
Fred Drake committed
172
              target_desc,
173 174 175 176 177 178 179 180 181 182
              objects,
              output_filename,
              output_dir=None,
              libraries=None,
              library_dirs=None,
              runtime_library_dirs=None,
              export_symbols=None,
              debug=0,
              extra_preargs=None,
              extra_postargs=None,
183 184
              build_temp=None,
              target_lang=None):
185

186 187 188
        # XXX this ignores 'build_temp'!  should follow the lead of
        # msvccompiler.py

189 190 191 192 193
        (objects, output_dir) = self._fix_object_args (objects, output_dir)
        (libraries, library_dirs, runtime_library_dirs) = \
            self._fix_lib_args (libraries, library_dirs, runtime_library_dirs)

        if runtime_library_dirs:
194 195
            log.warn("I don't know what to do with 'runtime_library_dirs': %s",
                     str(runtime_library_dirs))
196

197 198 199 200 201
        if output_dir is not None:
            output_filename = os.path.join (output_dir, output_filename)

        if self._need_link (objects, output_filename):

202 203 204 205 206 207 208
            # Figure out linker args based on type of target.
            if target_desc == CCompiler.EXECUTABLE:
                startup_obj = 'c0w32'
                if debug:
                    ld_args = self.ldflags_exe_debug[:]
                else:
                    ld_args = self.ldflags_exe[:]
209
            else:
210 211 212 213 214 215
                startup_obj = 'c0d32'
                if debug:
                    ld_args = self.ldflags_shared_debug[:]
                else:
                    ld_args = self.ldflags_shared[:]

216 217

            # Create a temporary exports file for use by the linker
218 219 220 221 222 223 224 225 226 227 228 229
            if export_symbols is None:
                def_file = ''
            else:
                head, tail = os.path.split (output_filename)
                modname, ext = os.path.splitext (tail)
                temp_dir = os.path.dirname(objects[0]) # preserve tree structure
                def_file = os.path.join (temp_dir, '%s.def' % modname)
                contents = ['EXPORTS']
                for sym in (export_symbols or []):
                    contents.append('  %s=_%s' % (sym, sym))
                self.execute(write_file, (def_file, contents),
                             "writing %s" % def_file)
230

Greg Ward's avatar
Greg Ward committed
231
            # Borland C++ has problems with '/' in paths
232 233 234 235 236 237 238 239 240 241 242
            objects2 = map(os.path.normpath, objects)
            # split objects in .obj and .res files
            # Borland C++ needs them at different positions in the command line
            objects = [startup_obj]
            resources = []
            for file in objects2:
                (base, ext) = os.path.splitext(os.path.normcase(file))
                if ext == '.res':
                    resources.append(file)
                else:
                    objects.append(file)
Fred Drake's avatar
Fred Drake committed
243 244


245
            for l in library_dirs:
Fred Drake's avatar
Fred Drake committed
246
                ld_args.append("/L%s" % os.path.normpath(l))
247 248
            ld_args.append("/L.") # we sometimes use relative paths

Fred Drake's avatar
Fred Drake committed
249 250
            # list of object files
            ld_args.extend(objects)
251

252 253 254 255 256 257 258 259 260 261
            # XXX the command-line syntax for Borland C++ is a bit wonky;
            # certain filenames are jammed together in one big string, but
            # comma-delimited.  This doesn't mesh too well with the
            # Unix-centric attitude (with a DOS/Windows quoting hack) of
            # 'spawn()', so constructing the argument list is a bit
            # awkward.  Note that doing the obvious thing and jamming all
            # the filenames and commas into one argument would be wrong,
            # because 'spawn()' would quote any filenames with spaces in
            # them.  Arghghh!.  Apparently it works fine as coded...

262
            # name of dll/exe file
263
            ld_args.extend([',',output_filename])
Fred Drake's avatar
Fred Drake committed
264
            # no map file and start libraries
Greg Ward's avatar
Greg Ward committed
265
            ld_args.append(',,')
266 267

            for lib in libraries:
Fred Drake's avatar
Fred Drake committed
268
                # see if we find it and if there is a bcpp specific lib
269
                # (xxx_bcpp.lib)
270 271 272 273 274 275 276
                libfile = self.find_library_file(library_dirs, lib, debug)
                if libfile is None:
                    ld_args.append(lib)
                    # probably a BCPP internal library -- don't warn
                else:
                    # full name which prefers bcpp_xxx.lib over xxx.lib
                    ld_args.append(libfile)
277 278 279 280 281

            # some default libraries
            ld_args.append ('import32')
            ld_args.append ('cw32mt')

282 283
            # def file for export symbols
            ld_args.extend([',',def_file])
284 285 286 287
            # add resource files
            ld_args.append(',')
            ld_args.extend(resources)

Fred Drake's avatar
Fred Drake committed
288

289 290 291
            if extra_preargs:
                ld_args[:0] = extra_preargs
            if extra_postargs:
292
                ld_args.extend(extra_postargs)
293 294 295

            self.mkpath (os.path.dirname (output_filename))
            try:
296
                self.spawn ([self.linker] + ld_args)
297
            except DistutilsExecError as msg:
298
                raise LinkError(msg)
299 300

        else:
301
            log.debug("skipping %s (up-to-date)", output_filename)
302

303
    # link ()
304 305 306 307

    # -- Miscellaneous methods -----------------------------------------


308
    def find_library_file (self, dirs, lib, debug=0):
309
        # List of effective library names to try, in order of preference:
310
        # xxx_bcpp.lib is better than xxx.lib
311
        # and xxx_d.lib is better than xxx.lib if debug is set
312
        #
313
        # The "_bcpp" suffix is to handle a Python installation for people
314 315 316 317 318 319
        # with multiple compilers (primarily Distutils hackers, I suspect
        # ;-).  The idea is they'd have one static library for each
        # compiler they care about, since (almost?) every Windows compiler
        # seems to have a different format for static libraries.
        if debug:
            dlib = (lib + "_d")
Greg Ward's avatar
Greg Ward committed
320
            try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib)
321
        else:
Greg Ward's avatar
Greg Ward committed
322
            try_names = (lib + "_bcpp", lib)
323

324
        for dir in dirs:
325 326 327
            for name in try_names:
                libfile = os.path.join(dir, self.library_filename(name))
                if os.path.exists(libfile):
328
                    return libfile
329 330 331 332
        else:
            # Oops, didn't find it in *any* of 'dirs'
            return None

333 334 335 336 337 338 339 340 341 342 343
    # overwrite the one from CCompiler to support rc and res-files
    def object_filenames (self,
                          source_filenames,
                          strip_dir=0,
                          output_dir=''):
        if output_dir is None: output_dir = ''
        obj_names = []
        for src_name in source_filenames:
            # use normcase to make sure '.rc' is really '.rc' and not '.RC'
            (base, ext) = os.path.splitext (os.path.normcase(src_name))
            if ext not in (self.src_extensions + ['.rc','.res']):
344 345
                raise UnknownFileError("unknown file type '%s' (from '%s')" % \
                      (ext, src_name))
346 347 348 349 350 351 352 353 354 355 356 357 358 359
            if strip_dir:
                base = os.path.basename (base)
            if ext == '.res':
                # these can go unchanged
                obj_names.append (os.path.join (output_dir, base + ext))
            elif ext == '.rc':
                # these need to be compiled to .res-files
                obj_names.append (os.path.join (output_dir, base + '.res'))
            else:
                obj_names.append (os.path.join (output_dir,
                                            base + self.obj_extension))
        return obj_names

    # object_filenames ()
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388

    def preprocess (self,
                    source,
                    output_file=None,
                    macros=None,
                    include_dirs=None,
                    extra_preargs=None,
                    extra_postargs=None):

        (_, macros, include_dirs) = \
            self._fix_compile_args(None, macros, include_dirs)
        pp_opts = gen_preprocess_options(macros, include_dirs)
        pp_args = ['cpp32.exe'] + pp_opts
        if output_file is not None:
            pp_args.append('-o' + output_file)
        if extra_preargs:
            pp_args[:0] = extra_preargs
        if extra_postargs:
            pp_args.extend(extra_postargs)
        pp_args.append(source)

        # We need to preprocess: either we're being forced to, or the
        # source file is newer than the target (or the target doesn't
        # exist).
        if self.force or output_file is None or newer(source, output_file):
            if output_file:
                self.mkpath(os.path.dirname(output_file))
            try:
                self.spawn(pp_args)
389
            except DistutilsExecError as msg:
390
                print(msg)
391
                raise CompileError(msg)
392 393

    # preprocess()