Skip to content

Commit

Permalink
Initial commit, add base for NodeJS and constructor for MetaCall, sti…
Browse files Browse the repository at this point in the history
…ll a lot of work to do.
  • Loading branch information
viferga committed Nov 21, 2024
1 parent 433e310 commit ac3c3f6
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 52 deletions.
6 changes: 2 additions & 4 deletions source/metacall/include/metacall/metacall.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,8 @@ struct metacall_initialize_configuration_type;

struct metacall_initialize_configuration_type
{
char *tag;
void *options; // TODO: We should use a MetaCall value MAP here and merge it with the configuration.
// By this way loaders will be able to access this information in the backend and we
// can use a weak API in order to implement this successfully
const char *tag; /* Tag referring to the loader */
void *options; /* Value of type Map that will be merged merged into the configuration of the loader */
};

typedef void *(*metacall_await_callback)(void *, void *);
Expand Down
23 changes: 23 additions & 0 deletions source/metacall/source/metacall.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

#include <environment/environment_variable.h>

#include <portability/portability_constructor.h>

#include <stdio.h>
#include <string.h>

Expand Down Expand Up @@ -68,6 +70,27 @@ static int metacall_plugin_extension_load(void);
static void *metacallv_method(void *target, const char *name, method_invoke_ptr call, vector v, void *args[], size_t size);
static type_id *metacall_type_ids(void *args[], size_t size);

/* -- Costructors -- */

portability_constructor(metacall_constructor)
{
const char *metacall_host = environment_variable_get("METACALL_HOST", NULL);

if (metacall_host != NULL)
{
struct metacall_initialize_configuration_type config[] = {
{ metacall_host, NULL /* TODO: Initialize the map and define { host: true } */ },
{ NULL, NULL }
};

if (metacall_initialize_ex(config) != 0)
{
log_write("metacall", LOG_LEVEL_ERROR, "MetaCall host constructor failed to initialize");
exit(1);
}
}
}

/* -- Methods -- */

const char *metacall_serial(void)
Expand Down
5 changes: 3 additions & 2 deletions source/portability/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,17 @@ set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source")
set(headers
${include_path}/portability.h
${include_path}/portability_assert.h
${include_path}/portability_path.h
${include_path}/portability_constructor.h
${include_path}/portability_executable_path.h
${include_path}/portability_library_path.h
${include_path}/portability_path.h
)

set(sources
${source_path}/portability.c
${source_path}/portability_path.c
${source_path}/portability_executable_path.c
${source_path}/portability_library_path.c
${source_path}/portability_path.c
)

# Group source files
Expand Down
1 change: 1 addition & 0 deletions source/portability/include/portability/portability.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <portability/portability_assert.h>
#include <portability/portability_executable_path.h>
#include <portability/portability_library_path.h>
#include <portability/portability_path.h>

#ifdef __cplusplus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
*
*/

#ifndef PORTABILITY_COMPILER_DETECTION_H
#define PORTABILITY_COMPILER_DETECTION_H 1
#ifndef PORTABILITY_COMPILER_H
#define PORTABILITY_COMPILER_H 1

/* TODO: This needs to be implemented properly, including another file for architecture and operative system detection */

Expand Down Expand Up @@ -490,4 +490,4 @@
// PORTABILITY_THREAD_LOCAL not defined for this configuration.
#endif

#endif /* PORTABILITY_COMPILER_DETECTION_H */
#endif /* PORTABILITY_COMPILER_H */
79 changes: 79 additions & 0 deletions source/portability/include/portability/portability_constructor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Portability Library by Parra Studios
* A generic cross-platform portability utility.
*
* Copyright (C) 2016 - 2024 Vicente Eduardo Ferrer Garcia <vic798@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

#ifndef PORTABILITY_CONSTRUCTOR_H
#define PORTABILITY_CONSTRUCTOR_H 1

/* -- Headers -- */

#include <portability/portability_api.h>

#ifdef __cplusplus
extern "C" {
#endif

/* -- Headers -- */

#include <assert.h>

#include <preprocessor/preprocessor_concatenation.h>
#include <preprocessor/preprocessor_stringify.h>

/* -- Macros -- */

#ifndef portability_constructor

#ifdef __cplusplus
#define portability_constructor(ctor) \
static void ctor(void); \
static struct PREPROCESSOR_CONCAT(ctor, _type) \
{ \
PREPROCESSOR_CONCAT(ctor, _type) \
(void) \
{ \
ctor(); \
} \
} PREPROCESSOR_CONCAT(ctor, _ctor); \
static void ctor(void)
#elif defined(_MSC_VER)
/* TODO: Test MSVC version in release mode */
#pragma section(".CRT$XCU", read)
#define portability_constructor_impl(ctor, prefix) \
static void ctor(void); \
__declspec(allocate(".CRT$XCU")) void (*PREPROCESSOR_CONCAT(ctor, _ptr))(void) = ctor; \
__pragma(comment(linker, "/include:" prefix PREPROCESSOR_STRINGIFY(ctor) "_ptr")) static void ctor(void)
#ifdef _WIN64
#define portability_constructor(ctor) portability_constructor_impl(ctor, "")
#else
#define portability_constructor(ctor) portability_constructor_impl(ctor, "_")
#endif
#else
#define portability_constructor(ctor) \
static void ctor(void) __attribute__((constructor)); \
static void ctor(void)
#endif

#endif

#ifdef __cplusplus
}
#endif

#endif /* PORTABILITY_CONSTRUCTOR_H */
43 changes: 37 additions & 6 deletions source/ports/node_port/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,6 @@ if(NOT OPTION_BUILD_CLI OR NOT OPTION_BUILD_LOADERS OR NOT OPTION_BUILD_LOADERS_
return()
endif()

set(node_port_test "${target}_test")

#
# Define test
#

if(OPTION_BUILD_THREAD_SANITIZER AND OPTION_BUILD_LOADERS_CS)
# TODO: This test fails when run with thread sanitizer:
#
Expand All @@ -121,6 +115,14 @@ if(OPTION_BUILD_THREAD_SANITIZER AND OPTION_BUILD_LOADERS_CS)
return()
endif()

#
# Define test
#

set(node_port_test "${target}_test")

message(STATUS "Test ${node_port_test}")

add_test(NAME ${target}
COMMAND ${CMAKE_COMMAND} -D "EXECUTABLE=$<TARGET_FILE:metacallcli>" -D "INPUT=${CMAKE_CURRENT_SOURCE_DIR}/test/commands/node_port.txt" -P "${CMAKE_SOURCE_DIR}/source/cli/metacallcli/test/commands/command_runner.cmake"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
Expand Down Expand Up @@ -213,3 +215,32 @@ test_environment_variables(${target}
${TESTS_ENVIRONMENT_VARIABLES_RS}
${TESTS_ENVIRONMENT_VARIABLES_OPENSSL}
)

#
# Test importing NodeJS Port from node.exe
#

set(node_port_test_exec "${node_port_test}_executable")

message(STATUS "Test ${node_port_test_exec}")

add_test(NAME ${node_port_test_exec}
COMMAND ${NodeJS_EXECUTABLE} test/index.js
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)

# Define test labels
set_property(TEST ${node_port_test_exec}
PROPERTY LABELS ${node_port_test_exec}
)

# Environment variables
test_environment_variables(${node_port_test_exec}
""
${TESTS_ENVIRONMENT_VARIABLES}
${TESTS_ENVIRONMENT_VARIABLES_COB}
${TESTS_ENVIRONMENT_VARIABLES_C}
${TESTS_ENVIRONMENT_VARIABLES_RS}
${TESTS_ENVIRONMENT_VARIABLES_OPENSSL}
"METACALL_INSTALL_PATH=${PROJECT_OUTPUT_DIR}"
)
142 changes: 105 additions & 37 deletions source/ports/node_port/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,55 +22,123 @@

const mod = require('module');
const path = require('path');
const fs = require('fs').promises;
const { URL } = require('url'); /* TODO: RPC Loader */

const addon = (() => {
try {
/* This forces metacall port to be run always by metacall cli */
return process._linkedBinding('node_loader_port_module');
} catch (e) {
console.error('MetaCall failed to load, probably you are importing this file from NodeJS directly.');
console.error('You should use MetaCall CLI instead. Install it from: https://github.com/metacall/install');
throw e;

/* TODO: Until we find a better way to do this, we should disable it */
/*
const write = (data, cb) => {
if (!process.stdout.write(data)) {
process.stdout.once('drain', cb);
} else {
process.nextTick(cb);
async function findFilesRecursively(dirPattern, filePattern, depthLimit = Infinity) {
const stack = [{ dir: dirPattern, depth: 0 }];
const files = [];
const dirRegex = new RegExp(dirPattern);
const fileRegex = new RegExp(filePattern);

while (stack.length > 0) {
const { dir, depth } = stack.pop();

try {
if (!dirRegex.test(dir)) {
continue;
}
};

// Notify synchronously that we are launching MetaCall
write('NodeJS detected, launching MetaCall...\n', () => {
try {
const { spawnSync } = require('child_process');
const args = [...process.argv];
if (depth > depthLimit) {
continue;
}

args.shift();
const items = await fs.readdir(dir);

const result = spawnSync('metacall', args, {});
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = await fs.stat(fullPath);

if (result.error && result.error.code === 'ENOENT') {
write('MetaCall not found. Please install MetaCall from: https://github.com/metacall/install and run it again.\n', () => {
process.exit(1);
});
if (stat.isDirectory()) {
stack.push({ dir: fullPath, depth: depth + 1 });
} else if (stat.isFile() && fileRegex.test(item)) {
files.push(fullPath);
}
}
} catch (err) {
console.error(`Error reading directory ${dir}:`, err);
}
}

process.exit(result.status !== null ? result.status : 1);
} catch (e) {
const message = 'MetaCall failed to load, probably you are importing this file from NodeJS directly.\n'
+ e.message + '\n'
+ 'Install MetaCall from: https://github.com/metacall/install and run it again.\n';
return files;
}

write(message, () => {
throw e;
});
const platformInstallPaths = () => {
switch (process.platform) {
case 'win32':
return {
paths: [ path.join(process.env['LOCALAPPDATA'], 'MetaCall', 'metacall') ],
name: 'metacall.dll'
}
});
case 'darwin':
return {
paths: [ '/opt/homebrew/lib/', '/usr/local/lib/' ],
name: 'libmetacall.dylib'
}
case 'linux':
return {
paths: [ '/usr/local/lib/', '/gnu/lib/' ],
name: 'libmetacall.so'
}
}

throw new Error(`Platform ${process.platform} not supported`)
}

const searchPaths = () => {
const customPath = process.env['METACALL_INSTALL_PATH'];

if (customPath) {
return {
paths: [ customPath ],
name: /^(lib)?metacall(d)?\.(so|dylib|dll)$/
}
}

return platformInstallPaths()
}

const findLibrary = async () => {
const searchData = searchPaths();

for (const p of searchData.paths) {
const files = await findFilesRecursively(p, searchData.name, 0);

if (files.length !== 0) {
return files[0];
}
}

throw new Error('MetaCall library not found, if you have it in a special folder, define it through METACALL_INSTALL_PATH')
}

const addon = (() => {
try {
/* If the binding can be loaded, it means MetaCall is being
* imported from the node_loader, in that case the runtime
* was initialized by node_loader itself and we can proceed.
*/
return process._linkedBinding('node_loader_port_module');
} catch (e) {
/* If the port cannot be found, it means MetaCall port has
* been imported for the first time from node.exe, the
* runtime in this case has been initialized by node.exe,
* and MetaCall is not initialized
*/
process.env['METACALL_HOST'] = 'node';

findLibrary().then(library => {
const { constants } = require('os');
const m = { exports: {} };

process.dlopen(m, library, constants.dlopen.RTLD_GLOBAL | constants.dlopen.RTLD_NOW);

// TODO: What to do with m? should we use process._linkedBinding instead, no?

}).catch(err => {
console.log(err);
process.exit(1);
});
}
})();

Expand Down

0 comments on commit ac3c3f6

Please sign in to comment.