root/trunk/dcompiler.py

Revision 119, 21.4 kB (checked in by KirkMcDonald, 1 year ago)

* Merged 2.4 and 2.5 Python headers into a single file.
* This is prep work for D 2.0 support.

Line 
1 # DSR:2005.10.27.23.51
2
3 # XXX:
4 # These two will have to wait until DMD can create shared libraries on Linux,
5 # because DSR doesn't have (the non-free version of) MSVC 2003, which is
6 # necessary to create a debug build or a UCS4 build of Python 2.4 on Windows:
7 # - Handle distutils debug builds responsibly (make sure both -debug and -g are
8 #   passed through to DMD, even if optimizations are requested).  Also make
9 #   sure that extensions built with this module work under debug builds of
10 #   Python.
11 # - Try out a UCS4 build of Python to make sure that works.
12
13 import os, os.path, sys
14
15 from distutils import ccompiler as cc
16 from distutils.ccompiler import gen_lib_options
17 from distutils.errors import (
18     DistutilsExecError, DistutilsFileError, DistutilsPlatformError,
19     CompileError, LibError, LinkError, UnknownFileError
20 )
21
22 _isPlatWin = sys.platform.lower().startswith('win')
23
24 _infraDir = os.path.join(os.path.dirname(__file__), 'infrastructure')
25
26 from pyd_support import make_pydmain, make_pyddef
27
28 _pydFiles = [
29     'class_wrap.d',
30     'ctor_wrap.d',
31     'def.d',
32     'dg_convert.d',
33     'exception.d',
34     'func_wrap.d',
35     'iteration.d',
36     'lib_abstract.d',
37     'make_object.d',
38     'make_wrapper.d',
39     'op_wrap.d',
40     'pyd.d',
41     'pydobject.d',
42     'struct_wrap.d',
43 ]
44
45 _stFiles = [
46     'coroutine.d',
47     'stackcontext.d',
48     'stackthread.d',
49     'tls.d',
50 ]
51
52 _metaFiles = [
53     'Default.d',
54     'Demangle.d',
55     'Nameof.d',
56     'Util.d',
57 ]
58
59 _pyVerXDotY = '.'.join(str(v) for v in sys.version_info[:2]) # e.g., '2.4'
60 _pyVerXY = _pyVerXDotY.replace('.', '') # e.g., '24'
61
62
63 class DCompiler(cc.CCompiler):
64
65     src_extensions = ['.d']
66     obj_extension = (_isPlatWin and '.obj') or '.o'
67     static_lib_extension = (_isPlatWin and '.lib') or '.a'
68     shared_lib_extension = (_isPlatWin and '.pyd') or '.so'
69     static_lib_format = (_isPlatWin and '%s%s') or 'lib%s%s'
70     shared_lib_format = '%s%s'
71     exe_extension = (_isPlatWin and '.exe') or ''
72
73     def __init__(self, *args, **kwargs):
74         cc.CCompiler.__init__(self, *args, **kwargs)
75         # Get DMD/GDC specific info
76         self._initialize()
77         # _binpath
78         try:
79             dBin = os.environ[self._env_var]
80             if not os.path.isfile(dBin):
81                 self.warn("Environment variable %s provided, but file '%s' does not exist." % (self._env_var, dBin))
82                 raise KeyError
83         except KeyError:
84             if _isPlatWin:
85                 # The environment variable wasn't supplied, so search the PATH.
86                 # Windows requires the full path for reasons that escape me at
87                 # the moment.
88                 dBin = _findInPath(self.compiler_type + self.exe_extension)
89                 if dBin is None:
90                     raise DistutilsFileError('You must either set the %s'
91                         ' environment variable to the full path of the %s'
92                         ' executable, or place the executable on the PATH.' %
93                         (self._env_var, self.compiler_type)
94                     )
95             else:
96                 # Just run it via the PATH directly in Linux
97                 dBin = self.compiler_type
98         self._binpath = dBin
99         # _unicodeOpt
100         self._unicodeOpt = self._versionOpt % ('Python_Unicode_UCS' + ((sys.maxunicode == 0xFFFF and '2') or '4'))
101
102     def _initialize(self):
103         # It is intended that this method be implemented by subclasses.
104         raise NotImplementedError, "Cannot initialize DCompiler, use DMDDCompiler or GDCDCompiler instead."
105
106     def _def_file(self, output_dir, output_filename):
107         """A list of options used to tell the linker how to make a dll/so. In
108         DMD, it is the .def file. In GDC, it is
109         ['-shared', '-Wl,-soname,blah.so'] or similar."""
110         raise NotImplementedError, "Cannot initialize DCompiler, use DMDDCompiler or GDCDCompiler instead."
111
112     def _lib_file(self, libraries):
113         return ''
114
115     def find_library_file(self, dirs, lib, debug=0):
116         shared_f = self.library_filename(lib, lib_type='shared')
117         static_f = self.library_filename(lib, lib_type='static')
118
119         for dir in dirs:
120             shared = os.path.join(dir, shared_f)
121             static = os.path.join(dir, static_f)
122
123             if os.path.exists(shared):
124                 return shared
125             elif os.path.exists(static):
126                 return static
127
128         return None
129
130     def compile(self, sources,
131         output_dir=None, macros=None, include_dirs=None, debug=0,
132         extra_preargs=None, extra_postargs=None, depends=None
133     ):
134         macros = macros or []
135         include_dirs = include_dirs or []
136         extra_preargs = extra_preargs or []
137         extra_postargs = extra_postargs or []
138
139         if not os.path.exists(output_dir):
140             os.makedirs(output_dir)
141
142         binpath = _qp(self._binpath)
143         compileOpts = self._compileOpts
144         outputOpts = self._outputOpts
145
146         includePathOpts = []
147
148         # All object files will be placed in one of three directories:
149         # infra   - All of the infrastructure's object files.
150         # project - The project's own object files.
151         # outside - Any source files specified by the project which are not
152         #           contained in the project's own directory.
153         orig_sources = sources
154         sources = []
155         for source in orig_sources:
156             if os.path.abspath(source).startswith(os.getcwd()):
157                 sources.append((source, 'project'))
158             else:
159                 sources.append((source, 'outside'))
160
161         # To sources, add the appropriate D header file python.d, as well as
162         # any platform-specific boilerplate.
163         pythonHeaderPath = os.path.join(_infraDir, 'python', 'python.d')
164         # Add the python header's directory to the include path
165         includePathOpts += self._includeOpts
166         includePathOpts[-1] = includePathOpts[-1] % os.path.join(_infraDir, 'python')
167         if not os.path.isfile(pythonHeaderPath):
168             raise DistutilsPlatformError('Required D translation of Python'
169                 ' header files "%s" is missing.' % pythonHeaderPath
170             )
171         sources.append((pythonHeaderPath, 'infra'))
172
173         # flags = (with_pyd, with_st, with_meta, with_main)
174         with_pyd, with_st, with_meta, with_main = [f for f, category in macros if category == 'aux'][0]
175         # And Pyd!
176         if with_pyd:
177             # If we're not using StackThreads, don't use iteration.d in Pyd
178             if not with_st or not self._st_support:
179                 _pydFiles.remove('iteration.d');
180             for file in _pydFiles:
181                 filePath = os.path.join(_infraDir, 'pyd', file)
182                 if not os.path.isfile(filePath):
183                     raise DistutilsPlatformError("Required Pyd source file '%s' is"
184                         " missing." % filePath
185                     )
186                 sources.append((filePath, 'infra'))
187         # If using PydMain, parse the template file
188         if with_main:
189             name = [n for n, category in macros if category == 'name'][0]
190             # Store the finished pydmain.d file alongside the object files
191             infra_output_dir = os.path.join(output_dir, 'infra')
192             if not os.path.exists(infra_output_dir):
193                 os.makedirs(infra_output_dir)
194             mainFilename = os.path.join(infra_output_dir, 'pydmain.d')
195             make_pydmain(mainFilename, name)
196             sources.append((mainFilename, 'infra'))
197         # And StackThreads
198         if self._st_support and with_st:
199             for file in _stFiles:
200                 filePath = os.path.join(_infraDir, 'st', file)
201                 if not os.path.isfile(filePath):
202                     raise DistutilsPlatformError("Required StackThreads source"
203                         " file '%s' is missing." % filePath
204                     )
205                 sources.append((filePath, 'infra'))
206             # Add the version conditional for st
207             macros.append(('Pyd_with_StackThreads', 'version'))
208         # And meta
209         if with_meta:
210             for file in _metaFiles:
211                 filePath = os.path.join(_infraDir, 'meta', file)
212                 if not os.path.isfile(filePath):
213                     raise DistutilsPlatformError("Required meta source file"
214                         " '%s' is missing." % filePath
215                     )
216                 sources.append((filePath, 'infra'))
217         # Add the infraDir to the include path for pyd, st, and meta.
218         if True in (with_pyd, with_st, with_meta):
219             includePathOpts += self._includeOpts
220             includePathOpts[-1] = includePathOpts[-1] % os.path.join(_infraDir)
221        
222         # Add DLL/SO boilerplate code file.
223         if _isPlatWin:
224             boilerplatePath = os.path.join(_infraDir, 'd',
225                 'python_dll_windows_boilerplate.d'
226             )
227         else:
228             boilerplatePath = os.path.join(_infraDir, 'd',
229                 'python_so_linux_boilerplate.d'
230             )
231         if not os.path.isfile(boilerplatePath):
232             raise DistutilsFileError('Required supporting code file "%s"'
233                 ' is missing.' % boilerplatePath
234             )
235         sources.append((boilerplatePath, 'infra'))
236
237         # Extension subclass DExtension will have packed any user-supplied
238         # version and debug flags into macros; we extract them and convert them
239         # into the appropriate command-line args.
240         versionFlags = [name for (name, category) in macros if category == 'version']
241         debugFlags = [name for (name, category) in macros if category == 'debug']
242         userVersionAndDebugOpts = (
243               [self._versionOpt % v for v in versionFlags] +
244               [self._debugOpt   % v for v in debugFlags]
245         )
246
247         # Python version option allows extension writer to take advantage of
248         # Python/C API features available only in recent version of Python with
249         # a version statement like:
250         #   version(Python_2_4_Or_Later) {
251         #     Py_ConvenientCallOnlyAvailableInPython24AndLater();
252         #   } else {
253         #     // Do it the hard way...
254         #   }
255         pythonVersionOpt = self._versionOpt % ('Python_%d_%d_Or_Later' % sys.version_info[:2])
256
257         # Optimization opts
258         args = [a.lower() for a in sys.argv[1:]]
259         optimize = ('-o' in args or '--optimize' in args)
260         if debug:
261             optimizationOpts = self._debugOptimizeOpts
262         elif optimize:
263             optimizationOpts = self._releaseOptimizeOpts
264         else:
265             optimizationOpts = self._defaultOptimizeOpts
266
267         print 'sources: ', [os.path.basename(s) for s, t in sources]
268
269         objFiles = []
270         for source, source_type in sources:
271             outOpts = outputOpts[:]
272             objFilename = os.path.splitext(source)[0] + self.obj_extension
273             if source_type == 'project':
274                 objName = os.path.join(output_dir, 'project', objFilename)
275             elif source_type == 'outside':
276                 objName = os.path.join(output_dir, 'outside', os.path.basename(objFilename))
277             else: # infra
278                 objName = os.path.join(output_dir, 'infra', os.path.basename(objFilename))
279             if not os.path.exists(os.path.dirname(objName)):
280                 os.makedirs(os.path.dirname(objName))
281             objFiles.append(objName)
282             outOpts[-1] = outOpts[-1] % _qp(objName)
283             cmdElements = (
284                 [binpath] + extra_preargs + compileOpts +
285                 [pythonVersionOpt, self._unicodeOpt] + optimizationOpts +
286                 includePathOpts + outOpts + userVersionAndDebugOpts +
287                 [_qp(source)] + extra_postargs
288             )
289             cmdElements = [el for el in cmdElements if el]
290             try:
291                 self.spawn(cmdElements)
292             except DistutilsExecError, msg:
293                 raise CompileError(msg)
294         return objFiles
295
296     def link (self,
297         target_desc, objects, output_filename,
298         output_dir=None,
299         libraries=None, library_dirs=None, runtime_library_dirs=None,
300         export_symbols=None, debug=0,
301         extra_preargs=None, extra_postargs=None,
302         build_temp=None, target_lang=None
303     ):
304         # Distutils defaults to None for "unspecified option list"; we want
305         # empty lists in that case (this substitution is done here in the body
306         # rather than by changing the default parameters in case distutils
307         # passes None explicitly).
308         libraries = libraries or []
309         library_dirs = library_dirs or []
310         runtime_library_dirs = runtime_library_dirs or []
311         export_symbols = export_symbols or []
312         extra_preargs = extra_preargs or []
313         extra_postargs = extra_postargs or []
314
315         binpath = self._binpath
316         outputOpts = self._outputOpts[:]
317         objectOpts = [_qp(fn) for fn in objects]
318
319         (objects, output_dir) = self._fix_object_args (objects, output_dir)
320         (libraries, library_dirs, runtime_library_dirs) = \
321             self._fix_lib_args (libraries, library_dirs, runtime_library_dirs)
322         if runtime_library_dirs:
323             self.warn('This CCompiler implementation does nothing with'
324                 ' "runtime_library_dirs": ' + str(runtime_library_dirs)
325             )
326
327         if output_dir and os.path.basename(output_filename) == output_filename:
328             output_filename = os.path.join(output_dir, output_filename)
329         else:
330             if not output_filename:
331                 raise DistutilsFileError, 'Neither output_dir nor' \
332                     ' output_filename was specified.'
333             output_dir = os.path.dirname(output_filename)
334             if not output_dir:
335                 raise DistutilsFileError, 'Unable to guess output_dir on the'\
336                     ' bases of output_filename "%s" alone.' % output_filename
337
338         # Format the output filename option
339         # (-offilename in DMD, -o filename in GDC)
340         outputOpts[-1] = outputOpts[-1] % _qp(output_filename)
341
342         if not os.path.exists(output_dir):
343             os.makedirs(output_dir)
344
345         if not self._need_link(objects, output_filename):
346             print "All binary output files are up to date."
347             return
348
349         # The .def file (on Windows) or -shared and -soname (on Linux)
350         sharedOpts = self._def_file(build_temp, output_filename)
351
352         # The python .lib file, if needed
353         pythonLibOpt = self._lib_file(libraries)
354         if pythonLibOpt:
355             pythonLibOpt = _qp(pythonLibOpt)
356
357         if target_desc != cc.CCompiler.SHARED_OBJECT:
358             raise LinkError('This CCompiler implementation does not know'
359                 ' how to link anything except an extension module (that is, a'
360                 ' shared object file).'
361             )
362
363         # Library linkage options
364         print "library_dirs:", library_dirs
365         print "runtime_library_dirs:", runtime_library_dirs
366         print "libraries:", libraries
367         libOpts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
368
369         # Optimization opts
370         args = [a.lower() for a in sys.argv[1:]]
371         optimize = ('-o' in args or '--optimize' in args)
372         if debug:
373             optimizationOpts = self._debugOptimizeOpts
374         elif optimize:
375             optimizationOpts = self._releaseOptimizeOpts
376         else:
377             optimizationOpts = self._defaultOptimizeOpts
378
379         cmdElements = (
380             [binpath] + extra_preargs + self._linkOpts + optimizationOpts +
381             outputOpts + [pythonLibOpt] + objectOpts + libOpts + sharedOpts +
382             extra_postargs
383         )
384         cmdElements = [el for el in cmdElements if el]
385
386         try:
387             self.spawn(cmdElements)
388         except DistutilsExecError, msg:
389             raise CompileError(msg)
390
391 class DMDDCompiler(DCompiler):
392     compiler_type = 'dmd'
393
394     executables = {
395         'preprocessor' : None,
396         'compiler'     : ['dmd'],
397         'compiler_so'  : ['dmd'],
398         'linker_so'    : ['dmd'],
399         'linker_exe'   : ['dmd'],
400     }
401
402     _env_var = 'DMD_BIN'
403
404     def _initialize(self):
405         # _compileOpts
406         self._compileOpts = ['-c']
407         # _outputOpts
408         self._outputOpts = ['-of%s']
409         # _linkOpts
410         self._linkOpts = []
411         # _includeOpts
412         self._includeOpts = ['-I%s']
413         # _versionOpt
414         self._versionOpt = '-version=%s'
415         # _debugOpt
416         self._debugOpt = '-debug=%s'
417         # _defaultOptimizeOpts
418         self._defaultOptimizeOpts = ['-debug']
419         # _debugOptimizeOpts
420         self._debugOptimizeOpts = self._defaultOptimizeOpts + ['-unittest', '-g']
421         # _releaseOptimizeOpts
422         self._releaseOptimizeOpts = ['-version=Optimized', '-release', '-O', '-inline']
423         # StackThreads support
424         self._st_support = True
425
426     #def link_opts(self,
427
428     def _def_file(self, output_dir, output_filename):
429         if _isPlatWin:
430             # Automatically create a .def file:
431             defFilePath = os.path.join(output_dir, 'infra', 'python_dll_def.def')
432             make_pyddef(
433                 defFilePath,
434                 os.path.basename(output_filename)
435             )
436             return [defFilePath]
437         else:
438             return []
439
440     def _lib_file(self, libraries):
441         if _isPlatWin:
442             # The DMD-compatible .lib file can be generated with implib.exe
443             # (from the Digital Mars "Basic Utilities" package) using a command
444             # series similar to the following:
445             #   cd C:\Windows\system32
446             #   \path\to\dm\bin\implib.exe /system python24_digitalmars.lib python24.dll
447             #
448             # I chose not to incorporate automatic .lib generation into this
449             # code because Python X.Y releases are fairly infrequent, so it's
450             # more convenient to distribute a pre-extracted .lib file to the
451             # users and spare them the need for the "Basic Utilities" package.
452             pythonDMDLibPath = _qp(os.path.join(_infraDir, 'python',
453                 'python%s_digitalmars.lib' % _pyVerXY
454             ))
455             if not os.path.isfile(pythonDMDLibPath):
456                 raise DistutilsFileError('The DMD-compatible Python .lib file'
457                     ' which should be located at "%s" is missing.  Try'
458                     ' downloading a more recent version of celeriD that'
459                     ' contains a .lib file appropriate for your Python version.'
460                     % pythonDMDLibPath
461                 )
462             pythonLibOpt = _qp(pythonDMDLibPath)
463
464             # distutils will normally request that the library 'pythonXY' be
465             # linked against.  Since D requires a different .lib file from the
466             # one used by the C compiler that built Python, and we've just
467             # dealt with that requirement, we take the liberty of removing the
468             # distutils-requested pythonXY.lib.
469             if 'python' + _pyVerXY in libraries:
470                 libraries.remove('python' + _pyVerXY)
471             return pythonLibOpt
472         else:
473             return ''
474
475     def library_dir_option(self, dir):
476         self.warn("Don't know how to set library search path for DMD.")
477         #raise DistutilsPlatformError, "Don't know how to set library search path for DMD."
478
479     def runtime_library_dir_option(self, dir):
480         self.warn("Don't know how to set runtime library search path for DMD.")
481         #raise DistutilsPlayformError, "Don't know how to set runtime library search path for DMD."
482
483     def library_option(self, lib):
484         if _isPlatWin:
485             return self.library_filename(lib)
486         else:
487             return '-L-l' + lib
488
489 class GDCDCompiler(DCompiler):
490     compiler_type = 'gdc'
491
492     executables = {
493         'preprocessor' : None,
494         'compiler'     : ['gdc'],
495         'compiler_so'  : ['gdc'],
496         'linker_so'    : ['gdc'],
497         'linker_exe'   : ['gdc'],
498     }
499
500     _env_var = 'GDC_BIN'
501
502     def _initialize(self):
503         # _compileOpts
504         self._compileOpts = ['-fPIC', '-c']
505         # _outputOpts
506         self._outputOpts = ['-o', '%s']
507         # _linkOpts
508         self._linkOpts = ['-fPIC', '-nostartfiles', '-shared']
509         # _includeOpts
510         self._includeOpts = ['-I', '%s']
511         # _versionOpt
512         self._versionOpt = '-fversion=%s'
513         # _debugOpt
514         self._debugOpt = '-fdebug=%s'
515         # _defaultOptimizeOpts
516         self._defaultOptimizeOpts = ['-fdebug']
517         # _debugOptimizeOpts
518         self._debugOptimizeOpts = self._defaultOptimizeOpts + ['-g', '-funittest']
519         # _releaseOptimizeOpts
520         self._releaseOptimizeOpts = ['-fversion=Optimized', '-frelease', '-O3', '-finline-functions']
521         # StackThreads support
522         self._st_support = False
523
524     def _def_file(self, output_dir, output_filename):
525         return ['-Wl,-soname,' + os.path.basename(output_filename)]
526
527     def library_dir_option(self, dir):
528         return '-L' + dir
529
530     def runtime_library_dir_option(self, dir):
531         return '-Wl,-R' + dir
532
533     def library_option(self, lib):
534         return '-l' + lib
535
536 # Utility functions:
537 def _findInPath(fileName, startIn=None):
538     # Find a file named fileName in the PATH, starting in startIn.
539     try:
540         path = os.environ['PATH']
541     except KeyError:
542         pass
543     else:
544         pathDirs = path.split(os.pathsep)
545         if startIn:
546             if startIn in pathDirs:
547       &nb