diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 10f05bb69873bd..eb1c2782642995 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -5954,6 +5954,31 @@ idempotent. This API may only be called from the main thread. +## Miscellaneous utilities + +## node_api_get_module_file_name + + + +> Stability: 1 - Experimental + +```c +NAPI_EXTERN napi_status +node_api_get_module_file_name(napi_env env, const char** result); + +``` + +* `[in] env`: The environment that the API is invoked under. +* `[out] result`: A URL containing the absolute path of the + location from which the add-on was loaded. For a file on the local + file system it will start with `file://`. The string is null-terminated and + owned by `env` and must thus not be modified or freed. + +`result` may be an empty string if the add-on loading process fails to establish +the add-on's file name during loading. + [ABI Stability]: https://nodejs.org/en/docs/guides/abi-stability/ [AppVeyor]: https://www.appveyor.com [C++ Addons]: addons.md diff --git a/src/env.h b/src/env.h index f2fc681f56ed69..9e4e11e105632f 100644 --- a/src/env.h +++ b/src/env.h @@ -253,6 +253,7 @@ constexpr size_t kFsStatsBufferLength = V(fd_string, "fd") \ V(fields_string, "fields") \ V(file_string, "file") \ + V(filename_string, "filename") \ V(fingerprint256_string, "fingerprint256") \ V(fingerprint_string, "fingerprint") \ V(flags_string, "flags") \ diff --git a/src/node_api.cc b/src/node_api.cc index f1a5265b6a7234..8dbf48d466dfe1 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -15,8 +15,9 @@ #include struct node_napi_env__ : public napi_env__ { - explicit node_napi_env__(v8::Local context): - napi_env__(context) { + explicit node_napi_env__(v8::Local context, + const std::string& module_filename): + napi_env__(context), filename(module_filename) { CHECK_NOT_NULL(node_env()); } @@ -46,6 +47,10 @@ struct node_napi_env__ : public napi_env__ { }); }); } + + const char* GetFilename() const { return filename.c_str(); } + + std::string filename; }; typedef node_napi_env__* node_napi_env; @@ -87,10 +92,11 @@ class BufferFinalizer : private Finalizer { }; }; -static inline napi_env NewEnv(v8::Local context) { +static inline napi_env +NewEnv(v8::Local context, const std::string& module_filename) { node_napi_env result; - result = new node_napi_env__(context); + result = new node_napi_env__(context, module_filename); // TODO(addaleax): There was previously code that tried to delete the // napi_env when its v8::Context was garbage collected; // However, as long as N-API addons using this napi_env are in place, @@ -552,16 +558,35 @@ void napi_module_register_by_symbol(v8::Local exports, v8::Local module, v8::Local context, napi_addon_register_func init) { + node::Environment* node_env = node::Environment::GetCurrent(context); + std::string module_filename = ""; if (init == nullptr) { - node::Environment* node_env = node::Environment::GetCurrent(context); CHECK_NOT_NULL(node_env); node_env->ThrowError( "Module has no declared entry point."); return; } + // We set `env->filename` from `module.filename` here, but we could just as + // easily add a private property to `exports` in `process.dlopen`, which + // receives the file name from JS, and retrieve *that* here. Thus, we are not + // endorsing commonjs here by making use of `module.filename`. + v8::Local filename_js; + v8::Local modobj; + if (module->ToObject(context).ToLocal(&modobj) && + modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) && + filename_js->IsString()) { + node::Utf8Value filename(node_env->isolate(), filename_js); // Cast + + // Turn the absolute path into a URL. Currently the absolute path is always + // a file system path. + // TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we + // receive it as a URL already. + module_filename = std::string("file://") + (*filename); + } + // Create a new napi_env for this specific module. - napi_env env = v8impl::NewEnv(context); + napi_env env = v8impl::NewEnv(context, module_filename); napi_value _exports; env->CallIntoModule([&](napi_env env) { @@ -1257,3 +1282,11 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Ref(); } + +napi_status node_api_get_module_file_name(napi_env env, const char** result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = static_cast(env)->GetFilename(); + return napi_clear_last_error(env); +} diff --git a/src/node_api.h b/src/node_api.h index 786988e296b8b2..e7be240cdccd38 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -261,6 +261,9 @@ NAPI_EXTERN napi_status napi_add_async_cleanup_hook( NAPI_EXTERN napi_status napi_remove_async_cleanup_hook( napi_async_cleanup_hook_handle remove_handle); +NAPI_EXTERN napi_status +node_api_get_module_file_name(napi_env env, const char** result); + #endif // NAPI_EXPERIMENTAL EXTERN_C_END diff --git a/test/node-api/test_general/test.js b/test/node-api/test_general/test.js index 108d51a1e11ac9..dd409f010a3ada 100644 --- a/test/node-api/test_general/test.js +++ b/test/node-api/test_general/test.js @@ -1,9 +1,14 @@ 'use strict'; const common = require('../../common'); -const test_general = require(`./build/${common.buildType}/test_general`); +const filename = require.resolve(`./build/${common.buildType}/test_general`); +const test_general = require(filename); const assert = require('assert'); +// TODO(gabrielschulhof): This test may need updating if/when the filename +// becomes a full-fledged URL. +assert.strictEqual(test_general.filename, `file://${filename}`); + const [ major, minor, patch, release ] = test_general.testGetNodeVersion(); assert.strictEqual(process.version.split('-')[0], `v${major}.${minor}.${patch}`); diff --git a/test/node-api/test_general/test_general.c b/test/node-api/test_general/test_general.c index a7b7c34047c717..d430e2df4f3520 100644 --- a/test/node-api/test_general/test_general.c +++ b/test/node-api/test_general/test_general.c @@ -1,3 +1,4 @@ +#define NAPI_EXPERIMENTAL #include #include #include "../../js-native-api/common.h" @@ -20,9 +21,21 @@ static napi_value testGetNodeVersion(napi_env env, napi_callback_info info) { return result; } +static napi_value GetFilename(napi_env env, napi_callback_info info) { + const char* filename; + napi_value result; + + NODE_API_CALL(env, node_api_get_module_file_name(env, &filename)); + NODE_API_CALL(env, + napi_create_string_utf8(env, filename, NAPI_AUTO_LENGTH, &result)); + + return result; +} + static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor descriptors[] = { DECLARE_NODE_API_PROPERTY("testGetNodeVersion", testGetNodeVersion), + DECLARE_NODE_API_GETTER("filename", GetFilename), }; NODE_API_CALL(env, napi_define_properties(