Skip to content

Commit f569209

Browse files
Gabriel Schulhofmhdawson
authored andcommitted
node-api: allow retrieval of add-on file name
Unlike JS-only modules, native add-ons are always associated with a dynamic shared object from which they are loaded. Being able to retrieve its absolute path is important to native-only add-ons, i.e. add-ons that are not themselves being loaded from a JS-only module located in the same package as the native add-on itself. Currently, the file name is obtained at environment construction time from the JS `module.filename`. Nevertheless, the presence of `module` is not required, because the file name could also be passed in via a private property added onto `exports` from the `process.dlopen` binding. As an attempt at future-proofing, the file name is provided as a URL, i.e. prefixed with the `file://` protocol. Fixes: nodejs/node-addon-api#449 PR-URL: #37195 Backport-PR-URL: #37328 Co-authored-by: Michael Dawson <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent 86f34ee commit f569209

File tree

6 files changed

+87
-7
lines changed

6 files changed

+87
-7
lines changed

‎doc/api/n-api.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5791,6 +5791,31 @@ idempotent.
57915791

57925792
This API may only be called from the main thread.
57935793

5794+
## Miscellaneous utilities
5795+
5796+
## node_api_get_module_file_name
5797+
5798+
<!-- YAML
5799+
added: REPLACEME
5800+
-->
5801+
5802+
> Stability: 1 - Experimental
5803+
5804+
```c
5805+
NAPI_EXTERN napi_status
5806+
node_api_get_module_file_name(napi_env env, const char** result);
5807+
5808+
```
5809+
5810+
* `[in] env`: The environment that the API is invoked under.
5811+
* `[out] result`: A URL containing the absolute path of the
5812+
location from which the add-on was loaded. For a file on the local
5813+
file system it will start with `file://`. The string is null-terminated and
5814+
owned by `env` and must thus not be modified or freed.
5815+
5816+
`result` may be an empty string if the add-on loading process fails to establish
5817+
the add-on's file name during loading.
5818+
57945819
[ABI Stability]: https://nodejs.org/en/docs/guides/abi-stability/
57955820
[AppVeyor]: https://www.appveyor.com
57965821
[C++ Addons]: addons.html

‎src/env.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ constexpr size_t kFsStatsBufferLength =
253253
V(fd_string, "fd") \
254254
V(fields_string, "fields") \
255255
V(file_string, "file") \
256+
V(filename_string, "filename") \
256257
V(fingerprint256_string, "fingerprint256") \
257258
V(fingerprint_string, "fingerprint") \
258259
V(flags_string, "flags") \

‎src/node_api.cc‎

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
#include<memory>
1313

1414
structnode_napi_env__ : publicnapi_env__{
15-
explicitnode_napi_env__(v8::Local<v8::Context> context):
16-
napi_env__(context){
15+
explicitnode_napi_env__(v8::Local<v8::Context> context,
16+
const std::string& module_filename):
17+
napi_env__(context), filename(module_filename){
1718
CHECK_NOT_NULL(node_env());
1819
}
1920

@@ -43,6 +44,10 @@ struct node_napi_env__ : public napi_env__{
4344
});
4445
});
4546
}
47+
48+
constchar* GetFilename() const{return filename.c_str()}
49+
50+
std::string filename;
4651
};
4752

4853
typedef node_napi_env__* node_napi_env;
@@ -84,10 +89,11 @@ class BufferFinalizer : private Finalizer{
8489
};
8590
};
8691

87-
staticinline napi_env NewEnv(v8::Local<v8::Context> context){
92+
staticinline napi_env
93+
NewEnv(v8::Local<v8::Context> context, const std::string& module_filename){
8894
node_napi_env result;
8995

90-
result = newnode_napi_env__(context);
96+
result = newnode_napi_env__(context, module_filename);
9197
// TODO(addaleax): There was previously code that tried to delete the
9298
// napi_env when its v8::Context was garbage collected;
9399
// However, as long as N-API addons using this napi_env are in place,
@@ -454,16 +460,35 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
454460
v8::Local<v8::Value> module,
455461
v8::Local<v8::Context> context,
456462
napi_addon_register_func init){
463+
node::Environment* node_env = node::Environment::GetCurrent(context);
464+
std::string module_filename = "";
457465
if (init == nullptr){
458-
node::Environment* node_env = node::Environment::GetCurrent(context);
459466
CHECK_NOT_NULL(node_env);
460467
node_env->ThrowError(
461468
"Module has no declared entry point.");
462469
return;
463470
}
464471

472+
// We set `env->filename` from `module.filename` here, but we could just as
473+
// easily add a private property to `exports` in `process.dlopen`, which
474+
// receives the file name from JS, and retrieve *that* here. Thus, we are not
475+
// endorsing commonjs here by making use of `module.filename`.
476+
v8::Local<v8::Value> filename_js;
477+
v8::Local<v8::Object> modobj;
478+
if (module->ToObject(context).ToLocal(&modobj) &&
479+
modobj->Get(context, node_env->filename_string()).ToLocal(&filename_js) &&
480+
filename_js->IsString()){
481+
node::Utf8Value filename(node_env->isolate(), filename_js); // Cast
482+
483+
// Turn the absolute path into a URL. Currently the absolute path is always
484+
// a file system path.
485+
// TODO(gabrielschulhof): Pass the `filename` through unchanged if/when we
486+
// receive it as a URL already.
487+
module_filename = std::string("file://") + (*filename);
488+
}
489+
465490
// Create a new napi_env for this specific module.
466-
napi_env env = v8impl::NewEnv(context);
491+
napi_env env = v8impl::NewEnv(context, module_filename);
467492

468493
napi_value _exports;
469494
env->CallIntoModule([&](napi_env env){
@@ -1154,3 +1179,11 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func){
11541179
CHECK_NOT_NULL(func);
11551180
returnreinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref();
11561181
}
1182+
1183+
napi_status node_api_get_module_file_name(napi_env env, constchar** result){
1184+
CHECK_ENV(env);
1185+
CHECK_ARG(env, result);
1186+
1187+
*result = static_cast<node_napi_env>(env)->GetFilename();
1188+
returnnapi_clear_last_error(env);
1189+
}

‎src/node_api.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,9 @@ NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
261261
NAPI_EXTERNnapi_statusnapi_remove_async_cleanup_hook(
262262
napi_async_cleanup_hook_handleremove_handle);
263263

264+
NAPI_EXTERNnapi_status
265+
node_api_get_module_file_name(napi_envenv, constchar**result);
266+
264267
#endif// NAPI_EXPERIMENTAL
265268

266269
EXTERN_C_END

‎test/node-api/test_general/test.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
'use strict';
22

33
constcommon=require('../../common');
4-
consttest_general=require(`./build/${common.buildType}/test_general`);
4+
constfilename=require.resolve(`./build/${common.buildType}/test_general`);
5+
consttest_general=require(filename);
56
constassert=require('assert');
67

8+
// TODO(gabrielschulhof): This test may need updating if/when the filename
9+
// becomes a full-fledged URL.
10+
assert.strictEqual(test_general.filename,`file://${filename}`);
11+
712
const[major,minor,patch,release]=test_general.testGetNodeVersion();
813
assert.strictEqual(process.version.split('-')[0],
914
`v${major}.${minor}.${patch}`);

‎test/node-api/test_general/test_general.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#defineNAPI_EXPERIMENTAL
12
#include<node_api.h>
23
#include<stdlib.h>
34
#include"../../js-native-api/common.h"
@@ -21,9 +22,21 @@ static napi_value testGetNodeVersion(napi_env env, napi_callback_info info){
2122
returnresult;
2223
}
2324

25+
staticnapi_valueGetFilename(napi_envenv, napi_callback_infoinfo){
26+
constchar*filename;
27+
napi_valueresult;
28+
29+
NAPI_CALL(env, node_api_get_module_file_name(env, &filename));
30+
NAPI_CALL(env,
31+
napi_create_string_utf8(env, filename, NAPI_AUTO_LENGTH, &result));
32+
33+
returnresult;
34+
}
35+
2436
staticnapi_valueInit(napi_envenv, napi_valueexports){
2537
napi_property_descriptordescriptors[] ={
2638
DECLARE_NAPI_PROPERTY("testGetNodeVersion", testGetNodeVersion),
39+
DECLARE_NAPI_GETTER("filename", GetFilename),
2740
};
2841

2942
NAPI_CALL(env, napi_define_properties(

0 commit comments

Comments
(0)