[gjs/wip/require: 1/6] bootstrap: Add a (private for now) CommonJS-style imports system



commit ffb9bdc531c8026c71b11dfe71c2864a816db500
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Thu Jan 2 15:59:16 2014 -0500

    bootstrap: Add a (private for now) CommonJS-style imports system
    
    Use a native JS hook to add an implementation of the CommonJS Modules
    1.1.1 specification, using require(). This is independent from the
    existing imports system.
    
    This implementation has one minor deviation from the spec to be more
    compatible with the old imports system, and that is that modules are
    not run in their own private namespace, but instead directly in their
    "exports" namespace. That is, "this" and "exports" are the same object
    within the module. The "module" variable is also on "exports".
    This means that we can port the majority of modules over without
    requiring that they be rewritten to use exports, which doesn't exist
    in old versions of gjs.
    
    The existing imports system will be written on top of this system.
    
    We're not going to expose it to users just yet, as ES6 modules might
    replace it, but the goal is to rewrite the new system on top of it.

 Makefile-modules.am       |    7 ++-
 gjs/bootstrap.cpp         |   42 ++++++++++++
 gjs/console.cpp           |    2 +-
 gjs/context.cpp           |    6 ++
 gjs/context.h             |    2 +
 gjs/importer.cpp          |    2 +-
 gjs/importer.h            |    2 +
 libgjs-private/gjs-util.h |    4 +-
 modules/bootstrap.js      |  137 +++++++++++++++++++++++++++++++++++++++-
 modules/importer.cpp      |  153 +++++++++++++++++++++++++++++++++++++++++++++
 modules/importer.h        |   38 +++++++++++
 modules/modules.cpp       |    2 +
 12 files changed, 388 insertions(+), 9 deletions(-)
---
diff --git a/Makefile-modules.am b/Makefile-modules.am
index 4b6e356..4151c5d 100644
--- a/Makefile-modules.am
+++ b/Makefile-modules.am
@@ -1,6 +1,5 @@
 
-NATIVE_MODULES = libconsole.la libsystem.la libmodules_resources.la
-
+NATIVE_MODULES = libconsole.la libsystem.la libmodules_resources.la libimporter.la
 if ENABLE_CAIRO
 NATIVE_MODULES += libcairoNative.la
 endif
@@ -56,3 +55,7 @@ libsystem_la_SOURCES = modules/system.h modules/system.cpp
 libconsole_la_CPPFLAGS = $(JS_NATIVE_MODULE_CFLAGS)
 libconsole_la_LIBADD = $(JS_NATIVE_MODULE_LIBADD) $(READLINE_LIBS)
 libconsole_la_SOURCES = modules/console.h modules/console.cpp
+
+libimporter_la_CPPFLAGS = $(JS_NATIVE_MODULE_CFLAGS)
+libimporter_la_LIBADD = $(JS_NATIVE_MODULE_LIBADD)
+libimporter_la_SOURCES = modules/importer.h modules/importer.cpp
diff --git a/gjs/bootstrap.cpp b/gjs/bootstrap.cpp
index b049ec4..406c26f 100644
--- a/gjs/bootstrap.cpp
+++ b/gjs/bootstrap.cpp
@@ -26,9 +26,48 @@
 #include <gjs/gjs.h>
 
 #include "bootstrap.h"
+#include "native.h"
 
 #include <gio/gio.h>
 
+/* The bootstrap process is the thing that sets up the import system.
+ * As such, we give it a hook to import any native modules it may need.
+ *
+ * The rest of the functionality that the bootstrap code needs should be
+ * in independent native modules which can be imported by this API,
+ * rather than in the bootstrap environment.
+ */
+
+static JSBool
+import_native_module(JSContext *context,
+                     unsigned   argc,
+                     jsval     *vp)
+{
+    JSBool ret = JS_FALSE;
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    char *module_name = NULL;
+    JSObject *module_obj;
+
+    if (!gjs_parse_call_args(context, "importNativeModule", "s", args,
+                             "moduleName", &module_name))
+        goto out;
+
+    if (!gjs_import_native_module(context, module_name, &module_obj))
+        goto out;
+
+    ret = JS_TRUE;
+    args.rval().setObjectOrNull(module_obj);
+
+ out:
+    g_free(module_name);
+    return ret;
+}
+
+static JSFunctionSpec environment_funcs[] = {
+    { "importNativeModule", JSOP_WRAPPER (import_native_module), 1, GJS_MODULE_PROP_FLAGS },
+    { NULL },
+};
+
 static gboolean
 define_bootstrap_environment(JSContext  *context,
                              JSObject  **environment_out)
@@ -38,6 +77,9 @@ define_bootstrap_environment(JSContext  *context,
     if (!environment)
         return FALSE;
 
+    if (!JS_DefineFunctions(context, environment, &environment_funcs[0]))
+        return FALSE;
+
     *environment_out = environment;
     return TRUE;
 }
diff --git a/gjs/console.cpp b/gjs/console.cpp
index 50adca8..2320e55 100644
--- a/gjs/console.cpp
+++ b/gjs/console.cpp
@@ -96,7 +96,7 @@ main(int argc, char **argv)
         filename = "<command line>";
         program_name = argv[0];
     } else if (argc <= 1) {
-        script = g_strdup("const Console = imports.console; Console.interact();");
+        script = g_strdup("const Console = require('console'); Console.interact();");
         len = strlen(script);
         filename = "<stdin>";
         program_name = argv[0];
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 6564440..ed11882 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -792,3 +792,9 @@ gjs_get_import_global(JSContext *context)
     GjsContext *gjs_context = (GjsContext *) JS_GetContextPrivate(context);
     return gjs_context->global;
 }
+
+const char **
+gjs_context_get_search_path(GjsContext *context)
+{
+    return (const char **) context->search_path;
+}
diff --git a/gjs/context.h b/gjs/context.h
index ccd8bc6..acd07d1 100644
--- a/gjs/context.h
+++ b/gjs/context.h
@@ -77,6 +77,8 @@ void            gjs_context_gc                    (GjsContext  *context);
 
 void            gjs_dumpstack                     (void);
 
+const char **   gjs_context_get_search_path       (GjsContext *context);
+
 G_END_DECLS
 
 #endif  /* __GJS_CONTEXT_H__ */
diff --git a/gjs/importer.cpp b/gjs/importer.cpp
index 3b163e3..e421dda 100644
--- a/gjs/importer.cpp
+++ b/gjs/importer.cpp
@@ -997,7 +997,7 @@ importer_new(JSContext *context,
     return importer;
 }
 
-static G_CONST_RETURN char * G_CONST_RETURN *
+G_CONST_RETURN char * G_CONST_RETURN *
 gjs_get_search_path(void)
 {
     char **search_path;
diff --git a/gjs/importer.h b/gjs/importer.h
index afd4ab1..8f11fb8 100644
--- a/gjs/importer.h
+++ b/gjs/importer.h
@@ -29,6 +29,7 @@
 #endif
 
 #include <glib.h>
+#include <gio/gio.h>
 #include "gjs/jsapi-util.h"
 
 G_BEGIN_DECLS
@@ -44,6 +45,7 @@ JSObject* gjs_define_importer      (JSContext   *context,
                                     const char **initial_search_path,
                                     gboolean     add_standard_search_path);
 
+G_CONST_RETURN char * G_CONST_RETURN * gjs_get_search_path (void);
 
 G_END_DECLS
 
diff --git a/libgjs-private/gjs-util.h b/libgjs-private/gjs-util.h
index 18ca87e..aef6594 100644
--- a/libgjs-private/gjs-util.h
+++ b/libgjs-private/gjs-util.h
@@ -28,10 +28,10 @@
 
 G_BEGIN_DECLS
 
-/* For imports.format */
+/* For 'format' */
 char * gjs_format_int_alternative_output (int n);
 
-/* For imports.gettext */
+/* For 'gettext' */
 void gjs_textdomain     (const char *domain);
 void gjs_bindtextdomain (const char *domain,
                          const char *location);
diff --git a/modules/bootstrap.js b/modules/bootstrap.js
index 45ab533..77d167d 100644
--- a/modules/bootstrap.js
+++ b/modules/bootstrap.js
@@ -1,6 +1,137 @@
-(function(exports) {
+(function(exports, importNativeModule) {
     "use strict";
 
-    // Do early initialization here.
+    const Importer = importNativeModule('_importer');
+    const Gio = Importer.importGIModule('Gio', '2.0');
 
-})(window);
+    let _exports = {};
+
+    function loadNativeModule(moduleID) {
+        _exports[moduleID] = importNativeModule(moduleID);
+    }
+
+    function runOverridesForGIModule(module, moduleID) {
+        let overridesModuleName = ('overrides/' + moduleID);
+        loadJSModule(overridesModuleName);
+        let overridesModule = _exports[overridesModuleName];
+        if (!overridesModule)
+            return;
+
+        let initFunc = overridesModule._init;
+        if (!initFunc)
+            return;
+
+        initFunc.call(module);
+    }
+
+    function importGIModuleWithOverrides(moduleID, moduleVersion) {
+        let exportedID = 'gi/' + moduleID;
+        if (_exports[exportedID])
+            return _exports[exportedID];
+
+        let module = Importer.importGIModule(moduleID, moduleVersion);
+        _exports[exportedID] = module;
+        _exports[exportedID + '/' + moduleVersion] = module;
+        runOverridesForGIModule(module, moduleID);
+        return module;
+    }
+
+    function loadGIModule(moduleID) {
+        if (!moduleID.startsWith('gi/'))
+            return;
+
+        let giModuleID = moduleID.slice(3);
+
+        let moduleVersion;
+        if (giModuleID.indexOf('/') >= 0)
+            [giModuleID, moduleVersion] = giModuleID.split('/', 2);
+        else
+            moduleVersion = null;
+
+        importGIModuleWithOverrides(giModuleID, moduleVersion);
+    }
+
+    function createModuleScope(id, uri) {
+        let module = {};
+
+        Object.defineProperty(module, "id", { value: id,
+                                              configurable: false,
+                                              writable: false });
+        Object.defineProperty(module, "uri", { value: uri,
+                                               configurable: false,
+                                               writable: false });
+
+        let scope = {};
+        scope.module = module;
+
+        // XXX -- for compatibility with the old module system, we don't
+        // give modules their own private namespace, but simply export
+        // the module scope directly.
+        //
+        // This should eventually go away, when we fully adopt CommonJS.
+        scope.exports = scope;
+
+        return scope;
+    }
+
+    function loadJSModule(moduleID) {
+        function getModuleContents(modulePath) {
+            let file = Gio.File.new_for_commandline_arg(modulePath);
+
+            let success, script;
+            try {
+                [success, script] = file.load_contents(null);
+            } catch(e) {
+                return null;
+            }
+
+            return script;
+        }
+
+        function evalModule(modulePath, script) {
+            let scope = createModuleScope(moduleID, modulePath);
+            _exports[moduleID] = scope;
+
+            let evalSuccess = false;
+            try {
+                // Don't catch errors for the eval, as those should propagate
+                // back up to the user...
+                Importer.evalWithScope(scope, script, modulePath);
+                evalSuccess = true;
+            } finally {
+                if (!evalSuccess)
+                    delete _exports[moduleID];
+            }
+        }
+
+        for (let path of require.paths) {
+            let modulePath = path + '/' + moduleID + '.js';
+            let script = getModuleContents(modulePath);
+            if (!script)
+                continue;
+
+            evalModule(modulePath, script);
+        }
+    }
+
+    let require = function require(moduleID) {
+        if (_exports[moduleID])
+            return _exports[moduleID];
+
+        const FINDERS = [
+            loadNativeModule,
+            loadGIModule,
+            loadJSModule,
+        ];
+
+        for (let finder of FINDERS) {
+            finder(moduleID);
+            if (_exports[moduleID])
+                return _exports[moduleID];
+        }
+
+        throw new Error("Could not load module '" + moduleID + "'");
+    }
+    require.paths = Importer.getBuiltinSearchPath();
+
+})(window, importNativeModule);
diff --git a/modules/importer.cpp b/modules/importer.cpp
new file mode 100644
index 0000000..10b3ed8
--- /dev/null
+++ b/modules/importer.cpp
@@ -0,0 +1,153 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include "importer.h"
+#include <gjs/gjs-module.h>
+#include <gjs/byteArray.h>
+#include "gi/ns.h"
+
+static JSBool
+import_gi_module(JSContext *context,
+                 unsigned   argc,
+                 jsval     *vp)
+{
+    JSBool ret = JS_FALSE;
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    jsval retval = JSVAL_VOID;
+    char *module_name = NULL;
+    char *module_version = NULL;
+    JSObject *module_obj;
+
+    if (!gjs_parse_call_args(context, "importGIModule", "s?s", args,
+                             "moduleName", &module_name,
+                             "moduleVersion", &module_version))
+        goto out;
+
+    if (!gjs_import_gi_module(context, module_name, module_version, &module_obj))
+        goto out;
+
+    ret = JS_TRUE;
+    args.rval().setObject(*module_obj);
+
+ out:
+    g_free(module_name);
+    g_free(module_version);
+    return ret;
+}
+
+static JSBool
+eval_with_scope(JSContext *context,
+                unsigned   argc,
+                jsval     *vp)
+{
+    JSBool ret = JS_FALSE;
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    JSObject *scope;
+    JSObject *script_obj;
+    guint8 *script;
+    gsize script_len;
+    char *filename = NULL;
+    jsval retval;
+
+    if (!gjs_parse_call_args(context, "evalWithScope", "oos", args,
+                             "scope", &scope,
+                             "script", &script_obj,
+                             "filename", &filename))
+        goto out;
+
+    gjs_byte_array_peek_data (context, script_obj, &script, &script_len);
+
+    if (!gjs_eval_with_scope(context, scope, (const char *) script, script_len, filename, &retval))
+        goto out;
+
+    ret = JS_TRUE;
+    args.rval().set(retval);
+
+ out:
+    g_free(filename);
+    return ret;
+}
+
+static JSBool
+get_builtin_search_path(JSContext *context,
+                        unsigned   argc,
+                        jsval     *vp)
+{
+    GjsContext *gjs_context = GJS_CONTEXT(JS_GetContextPrivate(context));
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    const char **context_search_path;
+    int context_search_path_length;
+    const char **global_search_path;
+    int global_search_path_length;
+    GArray *elems;
+    JSObject *search_path_obj;
+    int i;
+
+    context_search_path = gjs_context_get_search_path(gjs_context);
+    context_search_path_length = context_search_path ? g_strv_length((char **) context_search_path) : 0;
+    global_search_path = (const char **) gjs_get_search_path();
+    global_search_path_length = global_search_path ? g_strv_length((char **) global_search_path) : 0;
+
+    elems = g_array_sized_new(FALSE, FALSE, sizeof(jsval),
+                              context_search_path_length + global_search_path_length);
+
+    for (i = 0; i < context_search_path_length; i++) {
+        jsval element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, context_search_path[i]));
+        g_array_append_val(elems, element);
+    }
+
+    for (i = 0; i < global_search_path_length; i++) {
+        jsval element = STRING_TO_JSVAL(JS_NewStringCopyZ(context, global_search_path[i]));
+        g_array_append_val(elems, element);
+    }
+
+    search_path_obj = JS_NewArrayObject(context, elems->len, (jsval *)elems->data);
+    g_array_free(elems, TRUE);
+
+    args.rval().setObject(*search_path_obj);
+    return JS_TRUE;
+}
+
+static JSFunctionSpec module_funcs[] = {
+    { "importGIModule", JSOP_WRAPPER (import_gi_module), 2, GJS_MODULE_PROP_FLAGS },
+    { "evalWithScope", JSOP_WRAPPER (eval_with_scope), 3, GJS_MODULE_PROP_FLAGS },
+    { "getBuiltinSearchPath", JSOP_WRAPPER (get_builtin_search_path), 0, GJS_MODULE_PROP_FLAGS },
+    { NULL },
+};
+
+JSBool
+gjs_js_define_importer_stuff(JSContext  *context,
+                             JSObject  **module_out)
+{
+    JSObject *module;
+
+    module = JS_NewObject(context, NULL, NULL, NULL);
+
+    if (!JS_DefineFunctions(context, module, &module_funcs[0]))
+        return JS_FALSE;
+
+    *module_out = module;
+    return JS_TRUE;
+}
diff --git a/modules/importer.h b/modules/importer.h
new file mode 100644
index 0000000..5fa25b0
--- /dev/null
+++ b/modules/importer.h
@@ -0,0 +1,38 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __GJS_MODULE_IMPORTER_H__
+#define __GJS_MODULE_IMPORTER_H__
+
+#include <config.h>
+#include <glib.h>
+#include "gjs/jsapi-util.h"
+
+G_BEGIN_DECLS
+
+JSBool        gjs_js_define_importer_stuff   (JSContext      *context,
+                                              JSObject      **module_out);
+
+G_END_DECLS
+
+#endif  /* __GJS_MODULE_IMPORTER_H__ */
diff --git a/modules/modules.cpp b/modules/modules.cpp
index aae3569..0ccc432 100644
--- a/modules/modules.cpp
+++ b/modules/modules.cpp
@@ -32,6 +32,7 @@
 
 #include "system.h"
 #include "console.h"
+#include "importer.h"
 
 void
 gjs_register_static_modules (void)
@@ -40,5 +41,6 @@ gjs_register_static_modules (void)
     gjs_register_native_module("cairoNative", gjs_js_define_cairo_stuff);
 #endif
     gjs_register_native_module("system", gjs_js_define_system_stuff);
+    gjs_register_native_module("_importer", gjs_js_define_importer_stuff);
     gjs_register_native_module("console", gjs_define_console_stuff);
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]