[gjs: 1/2] Implement system.programPath API




commit f6d0013666b7c55a4812592db0a80aa21acdd570
Author: Evan Welsh <contact evanwelsh com>
Date:   Sun Jan 31 16:22:04 2021 -0800

    Implement system.programPath API
    
    This API exposes the full path of the executed program from gjs-console.

 gjs/atoms.h                                |  1 +
 gjs/console.cpp                            | 10 +++++++---
 gjs/context-private.h                      |  3 +++
 gjs/context.cpp                            | 17 +++++++++++++++++
 installed-tests/js/testSystem.js           |  6 ++++++
 installed-tests/scripts/testCommandLine.sh | 16 +++++++++++++++-
 modules/system.cpp                         | 19 ++++++++++++++++---
 7 files changed, 65 insertions(+), 7 deletions(-)
---
diff --git a/gjs/atoms.h b/gjs/atoms.h
index 7fe061f1..424f6ea2 100644
--- a/gjs/atoms.h
+++ b/gjs/atoms.h
@@ -53,6 +53,7 @@ class JSTracer;
     macro(param_spec, "ParamSpec") \
     macro(parent_module, "__parentModule__") \
     macro(program_invocation_name, "programInvocationName") \
+    macro(program_path, "programPath") \
     macro(prototype, "prototype") \
     macro(search_path, "searchPath") \
     macro(signal_id, "signalId") \
diff --git a/gjs/console.cpp b/gjs/console.cpp
index e355bf85..7c39f39e 100644
--- a/gjs/console.cpp
+++ b/gjs/console.cpp
@@ -286,6 +286,7 @@ main(int argc, char **argv)
         exit(0);
     }
 
+    GjsAutoChar program_path = nullptr;
     gjs_argc = g_strv_length(gjs_argv);
     if (command != NULL) {
         script = command;
@@ -308,10 +309,13 @@ main(int argc, char **argv)
         /* All unprocessed options should be in script_argv */
         g_assert(gjs_argc == 2);
         error = NULL;
-        if (!g_file_get_contents(gjs_argv[1], &script, &len, &error)) {
+        GjsAutoUnref<GFile> input = g_file_new_for_commandline_arg(gjs_argv[1]);
+        if (!g_file_load_contents(input, nullptr, &script, &len, nullptr,
+                                  &error)) {
             g_printerr("%s\n", error->message);
             exit(1);
         }
+        program_path = g_file_get_path(input);
         filename = gjs_argv[1];
         program_name = gjs_argv[1];
     }
@@ -347,8 +351,8 @@ main(int argc, char **argv)
 
     js_context = GJS_CONTEXT(g_object_new(
         GJS_TYPE_CONTEXT, "search-path", include_path, "program-name",
-        program_name, "profiler-enabled", enable_profiler, "exec-as-module",
-        exec_as_module, nullptr));
+        program_name, "program-path", program_path.get(), "profiler-enabled",
+        enable_profiler, "exec-as-module", exec_as_module, nullptr));
 
     env_coverage_output_path = g_getenv("GJS_COVERAGE_OUTPUT");
     if (env_coverage_output_path != NULL) {
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 3a1db048..6f5cba47 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -75,6 +75,7 @@ class GjsContextPrivate : public JS::JobQueue {
     GThread* m_owner_thread;
 
     char* m_program_name;
+    char* m_program_path;
 
     char** m_search_path;
 
@@ -180,6 +181,8 @@ class GjsContextPrivate : public JS::JobQueue {
     [[nodiscard]] bool sweeping() const { return m_in_gc_sweep; }
     [[nodiscard]] const char* program_name() const { return m_program_name; }
     void set_program_name(char* value) { m_program_name = value; }
+    GJS_USE const char* program_path(void) const { return m_program_path; }
+    void set_program_path(char* value) { m_program_path = value; }
     void set_search_path(char** value) { m_search_path = value; }
     void set_should_profile(bool value) { m_should_profile = value; }
     void set_execute_as_module(bool value) { m_exec_as_module = value; }
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 379f623d..59310a69 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -127,6 +127,7 @@ GjsContextPrivate* GjsContextPrivate::from_current_context() {
 
 enum {
     PROP_0,
+    PROP_PROGRAM_PATH,
     PROP_SEARCH_PATH,
     PROP_PROGRAM_NAME,
     PROP_PROFILER_ENABLED,
@@ -248,6 +249,15 @@ gjs_context_class_init(GjsContextClass *klass)
                                     pspec);
     g_param_spec_unref(pspec);
 
+    pspec = g_param_spec_string(
+        "program-path", "Executed File Path",
+        "The full path of the launched file or NULL if GJS was launched from "
+        "the C API or interactive console.",
+        nullptr, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+    g_object_class_install_property(object_class, PROP_PROGRAM_PATH, pspec);
+    g_param_spec_unref(pspec);
+
     /**
      * GjsContext:profiler-enabled:
      *
@@ -418,6 +428,7 @@ void GjsContextPrivate::dispose(void) {
 
 GjsContextPrivate::~GjsContextPrivate(void) {
     g_clear_pointer(&m_search_path, g_strfreev);
+    g_clear_pointer(&m_program_path, g_free);
     g_clear_pointer(&m_program_name, g_free);
 }
 
@@ -600,6 +611,9 @@ gjs_context_get_property (GObject     *object,
     case PROP_PROGRAM_NAME:
         g_value_set_string(value, gjs->program_name());
         break;
+    case PROP_PROGRAM_PATH:
+        g_value_set_string(value, gjs->program_path());
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -621,6 +635,9 @@ gjs_context_set_property (GObject      *object,
     case PROP_PROGRAM_NAME:
         gjs->set_program_name(g_value_dup_string(value));
         break;
+    case PROP_PROGRAM_PATH:
+        gjs->set_program_path(g_value_dup_string(value));
+        break;
     case PROP_PROFILER_ENABLED:
         gjs->set_should_profile(g_value_get_boolean(value));
         break;
diff --git a/installed-tests/js/testSystem.js b/installed-tests/js/testSystem.js
index 9cd5b8ef..e805f7bb 100644
--- a/installed-tests/js/testSystem.js
+++ b/installed-tests/js/testSystem.js
@@ -55,3 +55,9 @@ describe('System.dumpHeap()', function () {
         expect(() => System.dumpHeap('/does/not/exist')).toThrow();
     });
 });
+
+describe('System.programPath', function () {
+    it('is null when executed from minijasmine', function () {
+        expect(System.programPath).toBe(null);
+    });
+});
diff --git a/installed-tests/scripts/testCommandLine.sh b/installed-tests/scripts/testCommandLine.sh
index 539e6929..f3bb9c9f 100755
--- a/installed-tests/scripts/testCommandLine.sh
+++ b/installed-tests/scripts/testCommandLine.sh
@@ -65,6 +65,16 @@ import 'gi://Gio?version=2.0';
 import 'gi://Gio?version=75.94';
 EOF
 
+# this JS script is used to test ARGV handling
+cat <<EOF >argv.js
+const System = imports.system;
+
+if (System.programPath.endsWith('/argv.js'))
+    System.exit(0);
+else
+    System.exit(1);
+EOF
+
 total=0
 
 report () {
@@ -106,6 +116,10 @@ $gjs -c 'imports.system.exit(42)'
 test $? -eq 42
 report "System.exit(42) should exit with the correct exit code"
 
+# Test the System.programPath works in gjs-console
+$gjs argv.js
+report "System.programPath should end in '/argv.js' when gjs argv.js is run"
+
 # FIXME: should check -eq 42 specifically, but in debug mode we will be
 # hitting an assertion. For this reason, skip when running under valgrind
 # since nothing will be freed. Also suppress LSan for the same reason.
@@ -265,6 +279,6 @@ rm -f coverage.lcov
 $gjs -m doublegi.js 2>&1 | grep -q 'already loaded'
 report "avoid statically importing two versions of the same module"
 
-rm -f exit.js help.js promise.js awaitcatch.js doublegi.js
+rm -f exit.js help.js promise.js awaitcatch.js doublegi.js argv.js
 
 echo "1..$total"
diff --git a/modules/system.cpp b/modules/system.cpp
index 6195fe34..936584c2 100644
--- a/modules/system.cpp
+++ b/modules/system.cpp
@@ -20,6 +20,7 @@
 #include <js/PropertySpec.h>
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
+#include <js/Value.h>     // for NullValue
 #include <jsapi.h>        // for JS_DefinePropertyById, JS_DefineF...
 #include <jsfriendapi.h>  // for DumpHeap, IgnoreNurseryObjects
 
@@ -203,13 +204,25 @@ gjs_js_define_system_stuff(JSContext              *context,
 
     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
     const char* program_name = gjs->program_name();
+    const char* program_path = gjs->program_path();
 
-    JS::RootedValue value(context);
-    return gjs_string_from_utf8(context, program_name, &value) &&
+    JS::RootedValue v_program_invocation_name(context);
+    JS::RootedValue v_program_path(context, JS::NullValue());
+    if (program_path) {
+        if (!gjs_string_from_utf8(context, program_path, &v_program_path))
+            return false;
+    }
+
+    return gjs_string_from_utf8(context, program_name,
+                                &v_program_invocation_name) &&
            /* The name is modeled after program_invocation_name, part of glibc
             */
            JS_DefinePropertyById(context, module,
-                                 gjs->atoms().program_invocation_name(), value,
+                                 gjs->atoms().program_invocation_name(),
+                                 v_program_invocation_name,
+                                 GJS_MODULE_PROP_FLAGS | JSPROP_READONLY) &&
+           JS_DefinePropertyById(context, module, gjs->atoms().program_path(),
+                                 v_program_path,
                                  GJS_MODULE_PROP_FLAGS | JSPROP_READONLY) &&
            JS_DefinePropertyById(context, module, gjs->atoms().version(),
                                  GJS_VERSION,


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