Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node-api: allow retrieval of add-on file name #37195

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions doc/api/n-api.md
Expand Up @@ -5958,6 +5958,30 @@ idempotent.

This API may only be called from the main thread.

## Miscellaneous utilities

## node_api_get_module_file_name

<!-- YAML
added: REPLACEME
-->

> 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 null-terminated string containing the absolute path of the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason that it might be better to make this a URL versus restricting to a path. For example are there any ES6 cases where a module might be loaded from a URL versus a file on disk?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of future-proofing I will change it to "the absolute path of the location from which the add-on was loaded."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that doesn't make it a full-fledged URL, complete with file://. Did you have that in mind?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking something like file:// to keep our options open. Not sure if there are any downsides?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from adding a little bit of parsing overhead, I can't think of any. I'll add that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

location from which the add-on was loaded. The string is owned by `env` and
must not be modified or freed.
gabrielschulhof marked this conversation as resolved.
Show resolved Hide resolved

`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
Expand Down
1 change: 1 addition & 0 deletions src/env.h
Expand Up @@ -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") \
Expand Down
45 changes: 39 additions & 6 deletions src/node_api.cc
Expand Up @@ -15,8 +15,9 @@
#include <memory>

struct node_napi_env__ : public napi_env__ {
explicit node_napi_env__(v8::Local<v8::Context> context):
napi_env__(context) {
explicit node_napi_env__(v8::Local<v8::Context> context,
const std::string& module_filename):
napi_env__(context), filename(module_filename) {
CHECK_NOT_NULL(node_env());
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -87,10 +92,11 @@ class BufferFinalizer : private Finalizer {
};
};

static inline napi_env NewEnv(v8::Local<v8::Context> context) {
static inline napi_env
NewEnv(v8::Local<v8::Context> 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,
Expand Down Expand Up @@ -552,16 +558,35 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> 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<v8::Value> filename_js;
v8::Local<v8::Object> 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) {
Expand Down Expand Up @@ -1257,3 +1282,11 @@ napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) {
CHECK_NOT_NULL(func);
return reinterpret_cast<v8impl::ThreadSafeFunction*>(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<node_napi_env>(env)->GetFilename();
return napi_clear_last_error(env);
}
3 changes: 3 additions & 0 deletions src/node_api.h
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion 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}`);
Expand Down
13 changes: 13 additions & 0 deletions test/node-api/test_general/test_general.c
@@ -1,3 +1,4 @@
#define NAPI_EXPERIMENTAL
#include <node_api.h>
#include <stdlib.h>
#include "../../js-native-api/common.h"
Expand All @@ -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(
Expand Down