nitc: rename `c_compiler_option` and cie to `cflags` and `ldflags`
[nit.git] / src / ffi / ffi.nit
index aa85bbd..cccb7a0 100644 (file)
@@ -1,6 +1,6 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2012-2013 Alexis Laferrière <alexis.laf@xymus.net>
+# Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This module implements the FFI with different languages
+# FFI concers common between the compilers and the interpreter.
+# Offers services to compile modules using foreign code. Mainly allows
+# to wrap foreign code in Nit methods.
 module ffi
 
+import modelbuilder
+
+import nitni
+
+intrude import ffi_base
+import extern_classes
+import header_dependency
+import pkgconfig
+import c_compiler_options
 import c
+import cpp
+import java
+import extra_java_files
+import objc
+
+redef class MModule
+       # Does this module uses the FFI?
+       var uses_ffi: Bool = false
+
+       # C compilation unit for the FFI files
+       private var ffi_ccu: nullable CCompilationUnit = null
+
+       # Foreign language used in this AModule
+       private var present_languages = new HashSet[FFILanguage]
 
-redef class MMSrcModule
-       redef fun compile_separate_module(cprogram: CProgram)
+       # Complete the compilation of the FFI code
+       fun finalize_ffi_wrapper(compdir: String, mainmodule: MModule)
        do
-               super
+               for language in ffi_callbacks.keys do
+                       for callback in ffi_callbacks[language] do
+                               language.compile_callback(callback, self, mainmodule, ffi_ccu.as(not null))
+                       end
+
+                       language.compile_to_files(self, compdir)
+               end
+
+               # include dependancies FFI
+               for mod in header_dependencies do
+                       if mod.uses_ffi then ffi_ccu.header_custom.add("#include \"{mod.c_name}._ffi.h\"\n")
+               end
+
+               ffi_ccu.write_as_impl(self, compdir)
+               for filename in ffi_ccu.files do
+                       var f = new ExternCFile(filename, cflags)
+                       f.pkgconfigs.add_all pkgconfigs
+                       ffi_files.add(f)
+               end
+       end
+
+       # Avoid the compile a ffi propdef more than once
+       # See `AMethPropdef::compile_ffi_method`
+       # FIXME find a better way
+       private var compiled_ffi_methods = new HashSet[AMethPropdef]
+end
+
+redef class AModule
 
-               if is_extern_hybrid then
-                       var visitor = new FFIVisitor( cprogram.program.tc, self )
-                       # TODO use cprogram to add generated files?
+       # Ensures all of the general foreign code of the module has been analyzed.
+       # Manages header blocks, extern class types and foreign dependancies between modules
+       fun ensure_compile_ffi_wrapper
+       do
+               var mmodule = mmodule
+               if mmodule == null or mmodule.ffi_ccu != null then return
+
+               # ready extern code compiler
+               var ffi_ccu = new CCompilationUnit
+               mmodule.ffi_ccu = ffi_ccu
+
+               # generate code
+               for block in n_extern_code_blocks do
+                       var language = block.language
+                       assert language != null
+                       mmodule.present_languages.add(language)
+                       language.compile_module_block(block, ffi_ccu, mmodule)
+               end
+
+               ffi_ccu.header_c_base.add( "#include \"{mmodule.c_name}._nitni.h\"\n" )
 
-                       # actually compile stub
-                       accept_ffi_visitor( visitor )
+               ffi_ccu.body_decl.add("#ifdef ANDROID\n")
+               ffi_ccu.body_decl.add(" #include <android/log.h>\n")
+               ffi_ccu.body_decl.add(" #define PRINT_ERROR(...) (void)__android_log_print(ANDROID_LOG_WARN, \"Nit\", __VA_ARGS__)\n")
+               ffi_ccu.body_decl.add("#else\n")
+               ffi_ccu.body_decl.add(" #define PRINT_ERROR(...) fprintf(stderr, __VA_ARGS__)\n")
+               ffi_ccu.body_decl.add("#endif\n")
 
-                       # write to file
-                       if uses_ffi then
-                               visitor.compile
+               for nclassdef in n_classdefs do
+                       # Does it declares an extern type?
+                       if nclassdef isa AStdClassdef and nclassdef.n_extern_code_block != null then
+                               mmodule.uses_ffi = true
+                               var language = nclassdef.n_extern_code_block.language
+                               assert language != null
+                               mmodule.present_languages.add(language)
+                               nclassdef.n_extern_code_block.language.compile_extern_class(
+                                       nclassdef.n_extern_code_block.as(not null), nclassdef, ffi_ccu, mmodule)
                        end
                end
        end
 end
 
-redef class FFIVisitor
-       fun compile
+redef class AMethPropdef
+       # Compile the necessary wrapper around this extern method or constructor
+       fun compile_ffi_method(mmodule: MModule)
        do
-               var compdir = tc.compdir.as(not null)
-               var base_name = "{mmmodule.cname}._ffi"
-               var c_file = "{base_name}.c"
-               var h_file = "{base_name}.h"
-
-               # header comments
-               var module_info = "/*\n\tExtern implementation of Nit module {mmmodule.name}\n*/\n"
-
-               # header file guard
-               var guard = "{mmmodule.cname.to_s.to_upper}_NIT_H"
-
-               # .h
-               var stream = new OFStream.open( "{compdir}/{h_file}" )
-               stream.write( module_info )
-               stream.write( "#include <{mmmodule.name}._nitni.h>\n\n" )
-               stream.write( "#ifndef {guard}\n" )
-               stream.write( "#define {guard}\n\n" )
-               compilation_unit.header_c_base.write_to_stream( stream )
-               compilation_unit.header_custom.write_to_stream( stream )
-               compilation_unit.header_c_types.write_to_stream( stream )
-               compilation_unit.header_decl.write_to_stream( stream )
-               stream.write( "#endif\n" )
-               stream.close
-
-               # .c
-               stream = new OFStream.open( "{compdir}/{c_file}" )
-               stream.write( module_info )
-               stream.write( "#include \"{h_file}\"\n" )
-               compilation_unit.body_decl.write_to_stream( stream )
-               compilation_unit.body_custom.write_to_stream( stream )
-               compilation_unit.body_impl.write_to_stream( stream )
-               stream.close
+               assert n_extern_code_block != null
+
+               if mmodule.compiled_ffi_methods.has(self) then return
+               mmodule.compiled_ffi_methods.add self
+
+               var language = n_extern_code_block.language
+               assert language != null
+               mmodule.present_languages.add(language)
+               n_extern_code_block.language.compile_extern_method(
+                       n_extern_code_block.as(not null), self, mmodule.ffi_ccu.as(not null), mmodule)
+       end
+end
+
+redef class VerifyNitniCallbacksPhase
+       redef fun process_npropdef(npropdef)
+       do
+               super
+
+               if not npropdef isa AMethPropdef then return
+
+               var code_block = npropdef.n_extern_code_block
+               if code_block == null then return
+
+               var lang = code_block.language
+               assert lang != null
+
+               # Associate callbacks used by an extern method to its foreign language
+               for callback in npropdef.foreign_callbacks.all do
+                       var map = npropdef.mpropdef.mclassdef.mmodule.ffi_callbacks
+                       if not map.keys.has(lang) then map[lang] = new HashSet[NitniCallback]
+                       map[lang].add(callback)
+               end
        end
 end