pre-Py3k fixes
[cython.git] / Cython / Compiler / Main.py
1 #
2 #   Cython Top Level
3 #
4
5 import os, sys
6 if sys.version_info[:2] < (2, 2):
7     print >>sys.stderr, "Sorry, Cython requires Python 2.2 or later"
8     sys.exit(1)
9
10 import os
11 from time import time
12 import Version
13 from Scanning import PyrexScanner
14 import Errors
15 from Errors import PyrexError, CompileError, error
16 import Parsing
17 from Symtab import BuiltinScope, ModuleScope
18 import Code
19 from Cython.Utils import replace_suffix
20 from Cython import Utils
21
22 verbose = 0
23
24 class Context:
25     #  This class encapsulates the context needed for compiling
26     #  one or more Cython implementation files along with their
27     #  associated and imported declaration files. It includes
28     #  the root of the module import namespace and the list
29     #  of directories to search for include files.
30     #
31     #  modules               {string : ModuleScope}
32     #  include_directories   [string]
33     
34     def __init__(self, include_directories):
35         #self.modules = {"__builtin__" : BuiltinScope()}
36         import Builtin
37         self.modules = {"__builtin__" : Builtin.builtin_scope}
38         self.include_directories = include_directories
39         
40     def find_module(self, module_name, 
41             relative_to = None, pos = None, need_pxd = 1):
42         # Finds and returns the module scope corresponding to
43         # the given relative or absolute module name. If this
44         # is the first time the module has been requested, finds
45         # the corresponding .pxd file and process it.
46         # If relative_to is not None, it must be a module scope,
47         # and the module will first be searched for relative to
48         # that module, provided its name is not a dotted name.
49         debug_find_module = 0
50         if debug_find_module:
51             print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
52                     module_name, relative_to, pos, need_pxd))
53         scope = None
54         pxd_pathname = None
55         if "." not in module_name and relative_to:
56             if debug_find_module:
57                 print("...trying relative import")
58             scope = relative_to.lookup_submodule(module_name)
59             if not scope:
60                 qualified_name = relative_to.qualify_name(module_name)
61                 pxd_pathname = self.find_pxd_file(qualified_name, pos)
62                 if pxd_pathname:
63                     scope = relative_to.find_submodule(module_name)
64         if not scope:
65             if debug_find_module:
66                 print("...trying absolute import")
67             scope = self
68             for name in module_name.split("."):
69                 scope = scope.find_submodule(name)
70         if debug_find_module:
71             print("...scope =", scope)
72         if not scope.pxd_file_loaded:
73             if debug_find_module:
74                 print("...pxd not loaded")
75             scope.pxd_file_loaded = 1
76             if not pxd_pathname:
77                 if debug_find_module:
78                     print("...looking for pxd file")
79                 pxd_pathname = self.find_pxd_file(module_name, pos)
80                 if debug_find_module:
81                     print("......found ", pxd_pathname)
82                 if not pxd_pathname and need_pxd:
83                     error(pos, "'%s.pxd' not found" % module_name)
84             if pxd_pathname:
85                 try:
86                     if debug_find_module:
87                         print("Context.find_module: Parsing %s" % pxd_pathname)
88                     pxd_tree = self.parse(pxd_pathname, scope.type_names, pxd = 1,
89                                           full_module_name = module_name)
90                     pxd_tree.analyse_declarations(scope)
91                 except CompileError:
92                     pass
93         return scope
94     
95     def find_pxd_file(self, module_name, pos):
96         # Search include directories for the .pxd file
97         # corresponding to the given (full) module name.
98         if "." in module_name:
99             pxd_filename = "%s.pxd" % os.path.join(*module_name.split('.'))
100         else:
101             pxd_filename = "%s.pxd" % module_name
102         return self.search_include_directories(pxd_filename, pos)
103     
104     def find_include_file(self, filename, pos):
105         # Search list of include directories for filename.
106         # Reports an error and returns None if not found.
107         path = self.search_include_directories(filename, pos)
108         if not path:
109             error(pos, "'%s' not found" % filename)
110         return path
111     
112     def search_include_directories(self, filename, pos):
113         # Search the list of include directories for the given
114         # file name. If a source file position is given, first
115         # searches the directory containing that file. Returns
116         # None if not found, but does not report an error.
117         dirs = self.include_directories
118         if pos:
119             here_dir = os.path.dirname(pos[0])
120             dirs = [here_dir] + dirs
121         for dir in dirs:
122             path = os.path.join(dir, filename)
123             if os.path.exists(path):
124                 return path
125         return None
126
127     def lookup_submodule(self, name):
128         # Look up a top-level module. Returns None if not found.
129         return self.modules.get(name, None)
130
131     def find_submodule(self, name):
132         # Find a top-level module, creating a new one if needed.
133         scope = self.lookup_submodule(name)
134         if not scope:
135             scope = ModuleScope(name, 
136                 parent_module = None, context = self)
137             self.modules[name] = scope
138         return scope
139
140     def parse(self, source_filename, type_names, pxd, full_module_name):
141         # Parse the given source file and return a parse tree.
142         f = open(source_filename, "rU")
143         s = PyrexScanner(f, source_filename, 
144             type_names = type_names, context = self)
145         try:
146             tree = Parsing.p_module(s, pxd, full_module_name)
147         finally:
148             f.close()
149         if Errors.num_errors > 0:
150             raise CompileError
151         return tree
152
153     def extract_module_name(self, path, options):
154         # Get the module name out of a source file pathname.
155         _, tail = os.path.split(path)
156         name, _ = os.path.splitext(tail)
157         return name
158
159     def compile(self, source, options = None, full_module_name = None):
160         # Compile a Pyrex implementation file in this context
161         # and return a CompilationResult.
162         if not options:
163             options = default_options
164         result = CompilationResult()
165         cwd = os.getcwd()
166
167         if full_module_name is None:
168             full_module_name, _ = os.path.splitext(source.replace('/', '.'))
169
170         source = os.path.join(cwd, source)
171         
172         if options.use_listing_file:
173             result.listing_file = replace_suffix(source, ".lis")
174             Errors.open_listing_file(result.listing_file,
175                 echo_to_stderr = options.errors_to_stderr)
176         else:
177             Errors.open_listing_file(None)
178         if options.output_file:
179             result.c_file = os.path.join(cwd, options.output_file)
180         else:
181             if options.cplus:
182                 c_suffix = ".cpp"
183             else:
184                 c_suffix = ".c"
185             result.c_file = replace_suffix(source, c_suffix)
186         c_stat = None
187         if result.c_file:
188             try:
189                 c_stat = os.stat(result.c_file)
190             except EnvironmentError:
191                 pass
192         module_name = full_module_name # self.extract_module_name(source, options)
193         initial_pos = (source, 1, 0)
194         scope = self.find_module(module_name, pos = initial_pos, need_pxd = 0)
195         errors_occurred = False
196         try:
197             tree = self.parse(source, scope.type_names, pxd = 0, full_module_name = full_module_name)
198             tree.process_implementation(scope, options, result)
199         except CompileError:
200             errors_occurred = True
201         Errors.close_listing_file()
202         result.num_errors = Errors.num_errors
203         if result.num_errors > 0:
204             errors_occurred = True
205         if errors_occurred and result.c_file:
206             try:
207                 #os.unlink(result.c_file)
208                 Utils.castrate_file(result.c_file, c_stat)
209             except EnvironmentError:
210                 pass
211             result.c_file = None
212         if result.c_file and not options.c_only and c_compile:
213             result.object_file = c_compile(result.c_file,
214                 verbose_flag = options.show_version,
215                 cplus = options.cplus)
216             if not options.obj_only and c_link:
217                 result.extension_file = c_link(result.object_file,
218                     extra_objects = options.objects,
219                     verbose_flag = options.show_version,
220                     cplus = options.cplus)
221         return result
222
223 #------------------------------------------------------------------------
224 #
225 #  Main Python entry point
226 #
227 #------------------------------------------------------------------------
228
229 class CompilationOptions:
230     """
231     Options to the Cython compiler:
232     
233     show_version      boolean   Display version number
234     use_listing_file  boolean   Generate a .lis file
235     errors_to_stderr  boolean   Echo errors to stderr when using .lis
236     include_path      [string]  Directories to search for include files
237     output_file       string    Name of generated .c file
238     generate_pxi      boolean   Generate .pxi file for public declarations
239     
240     Following options are experimental and only used on MacOSX:
241     
242     c_only            boolean   Stop after generating C file (default)
243     obj_only          boolean   Stop after compiling to .o file
244     objects           [string]  Extra .o files to link with
245     cplus             boolean   Compile as c++ code
246     """
247     
248     def __init__(self, defaults = None, **kw):
249         self.include_path = []
250         self.objects = []
251         if defaults:
252             if isinstance(defaults, CompilationOptions):
253                 defaults = defaults.__dict__
254         else:
255             defaults = default_options
256         self.__dict__.update(defaults)
257         self.__dict__.update(kw)
258
259
260 class CompilationResult:
261     """
262     Results from the Cython compiler:
263     
264     c_file           string or None   The generated C source file
265     h_file           string or None   The generated C header file
266     i_file           string or None   The generated .pxi file
267     api_file         string or None   The generated C API .h file
268     listing_file     string or None   File of error messages
269     object_file      string or None   Result of compiling the C file
270     extension_file   string or None   Result of linking the object file
271     num_errors       integer          Number of compilation errors
272     """
273     
274     def __init__(self):
275         self.c_file = None
276         self.h_file = None
277         self.i_file = None
278         self.api_file = None
279         self.listing_file = None
280         self.object_file = None
281         self.extension_file = None
282
283
284 def compile(source, options = None, c_compile = 0, c_link = 0,
285             full_module_name = None):
286     """
287     compile(source, options = default_options)
288     
289     Compile the given Cython implementation file and return
290     a CompilationResult object describing what was produced.
291     """
292     if not options:
293         options = default_options
294     options = CompilationOptions(defaults = options)
295     if c_compile:
296         options.c_only = 0
297     if c_link:
298         options.obj_only = 0
299     context = Context(options.include_path)
300     return context.compile(source, options, full_module_name)
301
302 #------------------------------------------------------------------------
303 #
304 #  Main command-line entry point
305 #
306 #------------------------------------------------------------------------
307
308 def main(command_line = 0):
309     args = sys.argv[1:]
310     any_failures = 0
311     if command_line:
312         from CmdLine import parse_command_line
313         options, sources = parse_command_line(args)
314     else:
315         options = default_options
316         sources = args
317     if options.show_version:
318         print >>sys.stderr, "Cython version %s" % Version.version
319     context = Context(options.include_path)
320     for source in sources:
321         try:
322             result = context.compile(source, options)
323             if result.num_errors > 0:
324                 any_failures = 1
325         except PyrexError, e:
326             print >>sys.stderr, e
327             any_failures = 1
328     if any_failures:
329         sys.exit(1)
330
331 #------------------------------------------------------------------------
332 #
333 #  Set the default options depending on the platform
334 #
335 #------------------------------------------------------------------------
336
337 default_options = dict(
338     show_version = 0,
339     use_listing_file = 0,
340     errors_to_stderr = 1,
341     c_only = 1,
342     obj_only = 1,
343     cplus = 0,
344     output_file = None,
345     generate_pxi = 0)
346     
347 if sys.platform == "mac":
348     from Cython.Mac.MacSystem import c_compile, c_link, CCompilerError
349     default_options['use_listing_file'] = 1
350 elif sys.platform == "darwin":
351     from Cython.Mac.DarwinSystem import c_compile, c_link, CCompilerError
352 else:
353     c_compile = None
354     c_link = None
355
356