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