Merge: lib/android: intro WiFi services wrapper
authorJean Privat <jean@pryen.org>
Mon, 14 Mar 2016 18:45:19 +0000 (14:45 -0400)
committerJean Privat <jean@pryen.org>
Mon, 14 Mar 2016 18:45:19 +0000 (14:45 -0400)
Intro a simple wrapper for Android WiFi services. It allows to list WiFi networks in range, and could be extended with more features in the future.

It was partially generated by jwrapper (with the included command) and completed by hand. As usual with generated wrapper, the client of the library should refer to the corresponding Java/Android documentation to know more about these services.

Pull-Request: #1984
Reviewed-by: Jean Privat <jean@pryen.org>

18 files changed:
contrib/tnitter/src/tnitter_app.nit
lib/android/assets_and_resources.nit
lib/android/dalvik.nit
lib/android/http_request.nit
lib/android/intent/intent_api10.nit
lib/android/notification/notification.nit
lib/android/service/NitBroadcastReceiver.java [new file with mode: 0644]
lib/android/service/NitService.java [new file with mode: 0644]
lib/android/service/at_boot.nit [new file with mode: 0644]
lib/android/service/service.nit [new file with mode: 0644]
lib/android/shared_preferences/shared_preferences_api10.nit
lib/android/toast.nit
lib/android/vibration.nit
lib/app/http_request.nit
lib/libevent.nit
src/interpreter/dynamic_loading_ffi/on_demand_compiler.nit
tests/niti.skip
tests/nitvm.skip

index dcbfffe..b8a955a 100644 (file)
@@ -36,7 +36,7 @@ import json::serialization
 import model
 
 # Delay in seconds before the next request after an error
-fun request_delay_on_error: Int do return 60
+fun request_delay_on_error: Float do return 60.0
 
 redef class App
        redef fun on_create
@@ -106,13 +106,7 @@ abstract class AsyncTnitterRequest
        redef var rest_action
 
        # Should this request be delayed by `request_delay_on_error` seconds?
-       var delay: Bool
-
-       redef fun main
-       do
-               if delay then nanosleep(request_delay_on_error, 0)
-               return super
-       end
+       fun after_error(value: Bool) is autoinit do if value then delay = request_delay_on_error
 end
 
 # Async request to list latest posts, either immediately or by push notification
index baf4e4a..40dace2 100644 (file)
@@ -121,7 +121,7 @@ end
 class AssetManager
 
        # Native asset manager
-       private var native_assets_manager: NativeAssetManager = app.native_activity.assets.new_global_ref is lazy
+       private var native_assets_manager: NativeAssetManager = app.native_context.assets.new_global_ref is lazy
 
        # Close this asset manager
        fun close do native_assets_manager.close
@@ -400,8 +400,8 @@ end
 redef class App
        # Resource Manager used to manage resources placed in the `res` folder of the app
        var resource_manager: ResourcesManager is lazy do
-               var res = native_activity.resources
-               var pkg = native_activity.package_name
+               var res = native_context.resources
+               var pkg = native_context.package_name
                return new ResourcesManager.native(res, pkg.to_s)
        end
 
@@ -409,7 +409,7 @@ redef class App
        var asset_manager: AssetManager is lazy do return new AssetManager
 end
 
-redef extern class NativeActivity
+redef extern class NativeContext
 
        # Get the native AssetsManager of the application, used to initialize the nit's AssetManager
        private fun assets: NativeAssetManager in "Java" `{
index dc335dc..486d140 100644 (file)
@@ -20,8 +20,13 @@ module dalvik
 import activities
 
 redef class App
-       # The main Java Activity of this application
+       # Main Java Activity of this application
+       #
+       # Require: A Nit activity is currently running.
        fun native_activity: NativeActivity is abstract
+
+       # Current reference context, either an activity or a service
+       fun native_context: NativeContext do return native_activity
 end
 
 extern class JavaClassLoader in "Java" `{java.lang.ClassLoader`}
@@ -43,7 +48,7 @@ redef class Sys
        do
                var class_loader = self.class_loader
                if class_loader == null then
-                       find_class_loader app.native_activity
+                       find_class_loader app.native_context
                        class_loader = self.class_loader
                        assert class_loader != null
                end
@@ -54,35 +59,35 @@ redef class Sys
                return load_jclass_intern(class_loader, class_loader_method, name)
        end
 
-       private fun find_class_loader(native_activity: NativeActivity) import jni_env, class_loader=, JavaObject.as nullable, class_loader_method=, JMethodID.as nullable `{
+       private fun find_class_loader(native_context: NativeContext) import jni_env, class_loader=, JavaObject.as nullable, class_loader_method=, JMethodID.as nullable `{
                JNIEnv *env = Sys_jni_env(self);
 
                // Retrieve main activity
-               jclass class_activity = (*env)->GetObjectClass(env, native_activity);
-               if (class_activity == NULL) {
-                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed retrieving activity class");
+               jclass class_context = (*env)->GetObjectClass(env, native_context);
+               if (class_context == NULL) {
+                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed to retrieve activity class");
                        (*env)->ExceptionDescribe(env);
                        exit(1);
                }
 
-               jmethodID class_activity_getClassLoader = (*env)->GetMethodID(env, class_activity, "getClassLoader", "()Ljava/lang/ClassLoader;");
+               jmethodID class_activity_getClassLoader = (*env)->GetMethodID(env, class_context, "getClassLoader", "()Ljava/lang/ClassLoader;");
                if (class_activity_getClassLoader == NULL) {
-                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed retrieving 'getClassLoader' method");
+                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed to retrieve 'getClassLoader' method");
                        (*env)->ExceptionDescribe(env);
                        exit(1);
                }
 
                // Call activity.getClassLoader
-               jobject instance_class_loader = (*env)->CallObjectMethod(env, native_activity, class_activity_getClassLoader);
+               jobject instance_class_loader = (*env)->CallObjectMethod(env, native_context, class_activity_getClassLoader);
                if (instance_class_loader == NULL) {
-                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed retrieving class loader instance");
+                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed to retrieve class loader instance");
                        (*env)->ExceptionDescribe(env);
                        exit(1);
                }
 
                jclass class_class_loader = (*env)->GetObjectClass(env, instance_class_loader);
                if (class_class_loader == NULL) {
-                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed retrieving class of class loader");
+                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed to retrieve class of class loader");
                        (*env)->ExceptionDescribe(env);
                        exit(1);
                }
@@ -90,7 +95,7 @@ redef class Sys
                // Get the method ClassLoader.findClass
                jmethodID class_class_loader_findClass = (*env)->GetMethodID(env, class_class_loader, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
                if (class_class_loader_findClass == NULL) {
-                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed retrieving 'findClass' method");
+                       __android_log_print(ANDROID_LOG_ERROR, "Nit", "Failed to retrieve 'findClass' method");
                        (*env)->ExceptionDescribe(env);
                        exit(1);
                }
@@ -100,7 +105,7 @@ redef class Sys
                Sys_class_loader_method__assign(self, JMethodID_as_nullable(class_class_loader_findClass));
 
                // Clean up
-               (*env)->DeleteLocalRef(env, class_activity);
+               (*env)->DeleteLocalRef(env, class_context);
                (*env)->DeleteLocalRef(env, instance_class_loader);
                (*env)->DeleteLocalRef(env, class_class_loader);
        `}
index d7427c3..2b9fd14 100644 (file)
@@ -30,7 +30,15 @@ in "Java" `{
 `}
 
 redef class App
-       redef fun run_on_ui_thread(task) do app.native_activity.run_on_ui_thread task
+       redef fun run_on_ui_thread(task)
+       do
+               if app.activities.not_empty then
+                       app.native_activity.run_on_ui_thread task
+               else
+                       # There is no UI, it must be a service, run on the caller thread
+                       task.main
+               end
+       end
 end
 
 redef class Text
index 8c6a928..746c0c4 100644 (file)
@@ -1305,7 +1305,7 @@ class Intent
        redef fun to_s do return intent.to_native_s.to_s
 end
 
-redef extern class NativeActivity
+redef extern class NativeContext
        private fun start_activity(intent: NativeIntent) in "Java" `{ self.startActivity(intent); `}
        private fun start_service(intent: NativeIntent) in "Java" `{ self.startService(intent); `}
        private fun stop_service(intent: NativeIntent) in "Java" `{ self.stopService(intent); `}
@@ -1331,11 +1331,11 @@ end
 redef class App
 
        # Execute the intent and launch the appropriate application
-       fun start_activity(intent: Intent) do native_activity.start_activity(intent.intent)
+       fun start_activity(intent: Intent) do native_context.start_activity(intent.intent)
 
        # Start a service that will be running until the `stop_service` call
-       fun start_service(intent: Intent) do native_activity.start_service(intent.intent)
+       fun start_service(intent: Intent) do native_context.start_service(intent.intent)
 
        # Stop service
-       fun stop_service(intent: Intent) do native_activity.stop_service(intent.intent)
+       fun stop_service(intent: Intent) do native_context.stop_service(intent.intent)
 end
index efd7c5f..0b6cb66 100644 (file)
@@ -109,7 +109,7 @@ class Notification
                if id != null then
                        sys.jni_env.push_local_frame(8)
 
-                       var manager = app.native_activity.notification_manager
+                       var manager = app.native_context.notification_manager
                        manager.cancel(tag.to_java_string, id)
 
                        self.id = null
diff --git a/lib/android/service/NitBroadcastReceiver.java b/lib/android/service/NitBroadcastReceiver.java
new file mode 100644 (file)
index 0000000..bd358cf
--- /dev/null
@@ -0,0 +1,29 @@
+/* This file is part of NIT ( http://www.nitlanguage.org ).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nit.app;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+// BroadcastReceiver launching NitService
+public class NitBroadcastReceiver extends BroadcastReceiver {
+
+       @Override
+       public void onReceive(Context context, Intent intent) {
+               context.startService(new Intent(context, NitService.class));
+       }
+}
diff --git a/lib/android/service/NitService.java b/lib/android/service/NitService.java
new file mode 100644 (file)
index 0000000..6603b8e
--- /dev/null
@@ -0,0 +1,58 @@
+/* This file is part of NIT ( http://www.nitlanguage.org ).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package nit.app;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+// Service implemented in Nit
+public class NitService extends Service {
+
+       protected int nitService = 0;
+
+       static {
+               System.loadLibrary("main");
+       }
+
+       @Override
+       public int onStartCommand(Intent intent, int flags, int id) {
+               return nitOnStartCommand(nitService, intent, flags, id);
+       }
+
+       @Override
+       public void onCreate() {
+               nitService = nitNewService();
+               nitOnCreate(nitService);
+               super.onCreate();
+       }
+
+       @Override
+       public void onDestroy() {
+               nitOnDestroy(nitService);
+               super.onDestroy();
+       }
+
+       @Override
+       public IBinder onBind(Intent arg) {
+               return null;
+       }
+
+       protected native int nitNewService();
+       protected native int nitOnStartCommand(int nitService, Intent intent, int flags, int id);
+       protected native void nitOnCreate(int nitService);
+       protected native void nitOnDestroy(int nitService);
+}
diff --git a/lib/android/service/at_boot.nit b/lib/android/service/at_boot.nit
new file mode 100644 (file)
index 0000000..a2c1868
--- /dev/null
@@ -0,0 +1,30 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Import this module to launch `Service` at device boot
+module at_boot is
+       extra_java_files "NitBroadcastReceiver.java"
+       android_manifest """
+<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+"""
+       android_manifest_application """
+<receiver android:name="nit.app.NitBroadcastReceiver">
+       <intent-filter>
+               <action android:name="android.intent.action.BOOT_COMPLETED" />
+       </intent-filter>
+</receiver>
+"""
+end
+
+import service
diff --git a/lib/android/service/service.nit b/lib/android/service/service.nit
new file mode 100644 (file)
index 0000000..b27bdac
--- /dev/null
@@ -0,0 +1,159 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Android service support for _app.nit_ centered around the class `Service`
+module service is
+       extra_java_files "NitService.java"
+       android_manifest_application """<service android:name="nit.app.NitService"></service>"""
+end
+
+import android::nit_activity
+
+in "C" `{
+       // Nit's App running instance, declared in `nit_activity`
+       extern App global_app;
+
+       JNIEXPORT jint JNICALL Java_nit_app_NitService_nitNewService
+               (JNIEnv *env, jobject java_service)
+       {
+               // Pin a ref to the Java service in the Java GC
+               java_service = (*env)->NewGlobalRef(env, java_service);
+
+               // Create the service in Nit and pin it in the Nit GC
+               Service nit_service = App_register_service(global_app, java_service);
+
+               // FIXME replace the previous call to register_service by
+               // the following line when #1941 is fixed:
+               //Service nit_service = new_Service(java_service);
+
+               Service_incr_ref(nit_service);
+
+               return (jint)(void*)nit_service;
+       }
+
+       JNIEXPORT jint JNICALL Java_nit_app_NitService_nitOnStartCommand
+               (JNIEnv *env, jobject java_service, jint nit_service, jobject intent, jint flags, jint id)
+       {
+               return Service_on_start_command((Service)nit_service, intent, flags, id);
+       }
+
+       JNIEXPORT void JNICALL Java_nit_app_NitService_nitOnCreate
+               (JNIEnv *env, jobject java_service, jint nit_service)
+       {
+               Service_on_create((Service)nit_service);
+       }
+
+       JNIEXPORT void JNICALL Java_nit_app_NitService_nitOnDestroy
+               (JNIEnv *env, jobject java_service, jint nit_service)
+       {
+               Service_on_destroy((Service)nit_service);
+
+               // Unpin the service instances in both Nit and Java
+               java_service = Service_native((Service)nit_service);
+               (*env)->DeleteGlobalRef(env, java_service);
+               Service_decr_ref(nit_service);
+       }
+`}
+
+redef class App
+
+       # Current instance of `Service`, if any
+       var service: nullable Service = null
+
+       # Launch `Service` in the background, it will be set as `service` when ready
+       fun start_service do native_context.start_service
+
+       # Register a service from Java/C
+       #
+       # FIXME remove when #1941 is fixed
+       private fun register_service(java_service: NativeService): Service
+       do
+               return new Service(java_service.new_global_ref)
+       end
+
+       # Prioritize an activity context if one is running, fallback on a service
+       redef fun native_context
+       do
+               if activities.not_empty then return super
+
+               var service = service
+               assert service != null
+               return service.native
+       end
+
+       # Dummy method to force the compilation of callbacks from C
+       #
+       # The callbacks are used in the launch of a Nit service from C code.
+       private fun force_service_callbacks_in_c import Service, register_service,
+               Service.on_start_command, Service.on_create, Service.on_destroy, Service.native `{ `}
+
+       redef fun setup
+       do
+               super
+
+               # Call the dummy method to force their compilation in global compilation
+               force_service_callbacks_in_c
+       end
+end
+
+# Android service with its life-cycle callbacks
+class Service
+       # Java service with the Android context of this service
+       var native: NativeService
+
+       # The service has been created
+       fun on_create do app.service = self
+
+       # The service received a start command (may happen more then once per service)
+       #
+       # Should return either `start_sticky`, `start_not_sticky` or `start_redeliver_intent`.
+       fun on_start_command(intent: JavaObject, flags, id: Int): Int do return start_sticky
+
+       # The service is being destroyed
+       fun on_destroy do app.service = null
+end
+
+# Wrapper of Java class `android.app.Service`
+extern class NativeService in "Java" `{ android.app.Service `}
+       super NativeContext
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = NativeService_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+end
+
+redef class NativeContext
+       private fun start_service in "Java" `{
+               android.content.Intent indent =
+                       new android.content.Intent(self, nit.app.NitService.class);
+               self.startService(intent);
+       `}
+end
+
+# The service is explicitly started and stopped, it is restarted if stopped by the system
+#
+# Return value of `Service::on_start_command`.
+fun start_sticky: Int in "Java" `{ return android.app.Service.START_STICKY; `}
+
+# The service may be stopped by the system and will not be restarted
+#
+# Return value of `Service::on_start_command`.
+fun start_not_sticky: Int in "Java" `{ return android.app.Service.START_NOT_STICKY; `}
+
+# The service is explicitly started and stopped, it is restarted with the last intent if stopped by the system
+#
+# Return value of `Service::on_start_command`.
+fun start_redeliver_intent: Int in "Java" `{ return android.app.Service.START_REDELIVER_INTENT; `}
index ca3a2b9..b268087 100644 (file)
@@ -144,7 +144,6 @@ end
 
 # Provides services to save and load data for the android platform
 class SharedPreferences
-       protected var context: NativeActivity
        protected var shared_preferences: NativeSharedPreferences
        protected var editor: NativeSharedPreferencesEditor
 
@@ -153,9 +152,8 @@ class SharedPreferences
 
        protected init(app: App, file_name: String, mode: Int)
        do
-               self.context = app.native_activity
                sys.jni_env.push_local_frame(1)
-               setup(file_name.to_java_string, mode)
+               setup(file_name.to_java_string, mode, app.native_context)
                sys.jni_env.pop_local_frame
        end
 
@@ -174,13 +172,13 @@ class SharedPreferences
                self.editor = editor.new_global_ref
        end
 
-       private fun setup(file_name: JavaString, mode: Int) import context, set_vars in "Java" `{
-               Activity context = (Activity) SharedPreferences_context(self);
+       private fun setup(file_name: JavaString, mode: Int, context: NativeContext) import set_vars in "Java" `{
                SharedPreferences sp;
 
                // Uses default SharedPreferences if file_name is an empty String
                if (file_name.equals("")) {
-                       sp = context.getPreferences((int)mode);
+                       Activity activity = (Activity)context;
+                       sp = activity.getPreferences((int)mode);
                } else {
                        sp = context.getSharedPreferences(file_name, (int)mode);
                }
@@ -405,6 +403,6 @@ end
 
 redef class App
        var shared_preferences: SharedPreferences is lazy do
-               return new SharedPreferences.privately(self, "")
+               return new SharedPreferences.privately(self, "app.nit")
        end
 end
index 21c2cf9..3cf745c 100644 (file)
@@ -28,13 +28,12 @@ redef class App
        fun toast(message: String, is_long: Bool)
        do
                var jstr = message.to_java_string
-               native_toast(jstr, is_long)
+               native_toast(jstr, is_long, native_activity)
                jstr.delete_local_ref
        end
 
-       private fun native_toast(message: JavaString, is_long: Bool)
-       import native_activity in "Java" `{
-               final android.app.Activity context = App_native_activity(self);
+       private fun native_toast(message: JavaString, is_long: Bool, native_activity: NativeActivity) in "Java" `{
+               final android.app.Activity context = native_activity;
                final CharSequence final_message = message;
                final int duration = is_long? Toast.LENGTH_LONG: Toast.LENGTH_SHORT;
 
index 99ff7ad..cef49ee 100644 (file)
@@ -49,10 +49,10 @@ end
 redef class App
        # Get the handle to this device vibrator as a global ref
        var vibrator: Vibrator is lazy do
-               var v = vibrator_native(native_activity)
+               var v = vibrator_native(native_context)
                return v.new_global_ref
        end
-       private fun vibrator_native(context: NativeActivity): Vibrator in "Java" `{
+       private fun vibrator_native(context: NativeContext): Vibrator in "Java" `{
                android.os.Vibrator v = (android.os.Vibrator)
                        context.getSystemService(android.content.Context.VIBRATOR_SERVICE);
                return v;
index 4b62f4a..9a5feca 100644 (file)
@@ -48,6 +48,9 @@ class AsyncHttpRequest
        # Should the response content be deserialized from JSON?
        var deserialize_json = true is writable
 
+       # Delay in seconds before sending this request
+       var delay = 0.0 is writable
+
        redef fun start
        do
                before
@@ -56,6 +59,9 @@ class AsyncHttpRequest
 
        redef fun main
        do
+               var delay = delay
+               if delay > 0.0 then delay.sleep
+
                var uri = rest_server_uri / rest_action
 
                # Execute REST request
index c01d417..84d21d7 100644 (file)
@@ -26,6 +26,7 @@ in "C header" `{
        #include <sys/types.h>
        #include <fcntl.h>
        #include <errno.h>
+       #include <string.h>
        #include <sys/socket.h>
 
        #include <event2/listener.h>
@@ -34,6 +35,10 @@ in "C header" `{
 `}
 
 in "C" `{
+
+// Protect callbacks for compatibility with light FFI
+#ifdef Connection_decr_ref
+
        // Callback forwarded to 'Connection.write_callback'
        static void c_write_cb(struct bufferevent *bev, Connection ctx) {
                Connection_write_callback(ctx);
@@ -58,6 +63,8 @@ in "C" `{
        {
                ConnectionFactory_accept_connection(ctx, listener, fd, address, socklen);
        }
+#endif
+
 `}
 
 # Structure to hold information and state for a Libevent dispatch loop.
index ab9e845..e826f05 100644 (file)
@@ -20,6 +20,7 @@ import c_tools
 import nitni
 import ffi
 import naive_interpreter
+import pkgconfig
 import debugger_socket # To linearize `ToolContext::init`
 
 redef class ToolContext
@@ -43,12 +44,16 @@ redef class AMethPropdef
                        n_extern_code_block.is_c) then return false
 
                for mparam in mpropdef.msignature.mparameters do
-                       var mtype = mparam.mtype
-                       if not mtype.is_cprimitive then
+                       if not mparam.mtype.is_cprimitive then
                                return false
                        end
                end
 
+               var return_mtype = mpropdef.msignature.return_mtype
+               if return_mtype != null and not return_mtype.is_cprimitive then
+                       return false
+               end
+
                return true
        end
 end
@@ -117,13 +122,29 @@ redef class AModule
                var srcs = [for file in ccu.files do new ExternCFile(file, ""): ExternFile]
                srcs.add_all mmodule.ffi_files
 
-               if mmodule.pkgconfigs.not_empty then
-                       fatal(v, "NOT YET IMPLEMENTED annotation `pkgconfig`")
-                       return false
-               end
-
+               # Compiler options specific to this module
                var ldflags = mmodule.ldflags[""].join(" ")
-               # TODO pkgconfig
+
+               # Protect pkg-config
+               var pkgconfigs = mmodule.pkgconfigs
+               var pkg_command = ""
+               if not pkgconfigs.is_empty then
+                       var cmd = "which pkg-config >/dev/null"
+                       if system(cmd) != 0 then
+                               v.fatal "FFI Error: Command `pkg-config` not found. Please install it"
+                               return false
+                       end
+
+                       for p in pkgconfigs do
+                               cmd = "pkg-config --exists '{p}'"
+                               if system(cmd) != 0 then
+                                       v.fatal "FFI Error: package {p} is not found by `pkg-config`. Please install it."
+                                       return false
+                               end
+                       end
+
+                       pkg_command = "`pkg-config --cflags --libs {pkgconfigs.join(" ")}`"
+               end
 
                # Compile each source file to an object file (or equivalent)
                var object_files = new Array[String]
@@ -132,9 +153,8 @@ redef class AModule
                end
 
                # Link everything in a shared library
-               # TODO customize the compiler
-               var cmd = "{v.c_compiler} -Wall -shared -o {foreign_code_lib_path} {object_files.join(" ")} {ldflags}"
-               if sys.system(cmd) != 0 then
+               var cmd = "{v.c_compiler} -Wall -shared -o {foreign_code_lib_path} {object_files.join(" ")} {ldflags} {pkg_command}"
+               if system(cmd) != 0 then
                        v.fatal "FFI Error: Failed to link native code using `{cmd}`"
                        return false
                end
index 0d45e5d..dae2c37 100644 (file)
@@ -36,3 +36,4 @@ test_ropebuffer
 ui_test
 test_text_stat
 nitsaf_args
+test_ffi_c_lots_of_refs
index 39ce010..6497642 100644 (file)
@@ -36,3 +36,4 @@ test_ropebuffer
 ui_test
 test_text_stat
 nitsaf.args
+test_ffi_c_lots_of_refs