Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ jobs:
- name: Checkout nodejs_api
uses: actions/checkout@v4
with:
fetch-depth: 1
path: tools/nodejs_api
submodules: recursive

- name: Fetch ladybug source archive
shell: bash
Expand All @@ -45,13 +47,16 @@ jobs:
# it into the working directory, excluding tools/nodejs_api since we
# already have it checked out there.

- name: Fetch dataset archive
shell: bash
run: |
mkdir -p dataset
curl -fsSL https://github.com/ladybugdb/dataset/archive/refs/heads/main.tar.gz \
| tar -xz --strip-components=1 -C dataset
# Tests reference ../../dataset/ relative to tools/nodejs_api.
- name: Setup ccache (non-Windows)
if: ${{ matrix.platform != 'win32' }}
uses: hendrikmuhs/ccache-action@v1.2
with:
key: nodejs-${{ runner.os }}-${{ runner.arch }}-${{ github.ref }}
max-size: 2G
create-symlink: true
restore-keys: |
nodejs-${{ runner.os }}-${{ runner.arch }}-refs/heads/main
nodejs-${{ runner.os }}-${{ runner.arch }}-

- name: Install Node.js dependencies (non-Windows)
if: ${{ matrix.platform != 'win32' }}
Expand Down Expand Up @@ -117,6 +122,8 @@ jobs:
env:
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.mac_env && '13.3' || '' }}
CMAKE_OSX_ARCHITECTURES: ${{ matrix.mac_env && matrix.arch || '' }}
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache

- name: Run Node.js tests
working-directory: tools/nodejs_api
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "dataset"]
path = dataset
url = https://github.com/ladybugdb/dataset.git
47 changes: 44 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
cmake_minimum_required(VERSION 3.15)
project(lbugjs LANGUAGES CXX C)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_definitions(-DNAPI_VERSION=6)
add_definitions(-DNODE_RUNTIME=node)
add_definitions(-DBUILDING_NODE_EXTENSION)

set(LBUG_SOURCE_DIR "" CACHE PATH "Path to the Ladybug source tree used for standalone Node.js builds")
set(LBUG_BUILD_DIR "" CACHE PATH "Path to the Ladybug build tree used for standalone Node.js builds")
set(LBUG_NODEJS_EXTRA_LINK_LIBS "" CACHE STRING "Additional link libraries for standalone Node.js builds against a precompiled liblbug")

# If on Windows use npx.cmd instead of npx
if(WIN32)
set(NPX_CMD npx.cmd)
Expand All @@ -27,6 +38,12 @@ execute_process(
OUTPUT_VARIABLE CMAKE_JS_SRC
ERROR_QUIET
)
execute_process(
COMMAND node -p "process.config.variables.node_prefix || ''"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE NODE_PREFIX
ERROR_QUIET
)

# Without this windows breaks with: Invalid character escape '\P'.
string(REPLACE "\\" "/" CMAKE_JS_INC "${CMAKE_JS_INC}")
Expand All @@ -46,6 +63,7 @@ string(REPLACE "\\" "/" CMAKE_JS_SRC "${CMAKE_JS_SRC}")
string(STRIP "${CMAKE_JS_INC}" CMAKE_JS_INC)
string(STRIP "${CMAKE_JS_LIB}" CMAKE_JS_LIB)
string(STRIP "${CMAKE_JS_SRC}" CMAKE_JS_SRC)
string(STRIP "${NODE_PREFIX}" NODE_PREFIX)
# Extract last line: remove everything up to and including the last newline.
string(REGEX REPLACE ".*\n" "" CMAKE_JS_INC "${CMAKE_JS_INC}")
string(REGEX REPLACE ".*\n" "" CMAKE_JS_LIB "${CMAKE_JS_LIB}")
Expand All @@ -61,6 +79,12 @@ endif()
if(NOT CMAKE_JS_SRC MATCHES "^(/|[A-Za-z]:)")
set(CMAKE_JS_SRC "")
endif()
if(NOT CMAKE_JS_INC AND NODE_PREFIX MATCHES "^(/|[A-Za-z]:)")
set(CMAKE_JS_INC "${NODE_PREFIX}/include/node")
endif()
if(WIN32 AND NOT CMAKE_JS_LIB AND NODE_PREFIX MATCHES "^(/|[A-Za-z]:)")
set(CMAKE_JS_LIB "${NODE_PREFIX}/lib/node.lib")
endif()

# Print CMAKE_JS variables
message(STATUS "CMake.js configurations: LIB=${CMAKE_JS_LIB}, INC=${CMAKE_JS_INC}, SRC=${CMAKE_JS_SRC}")
Expand Down Expand Up @@ -90,15 +114,29 @@ if(NOT TARGET lbug)
if(NOT EXISTS "${LBUG_NODEJS_PRECOMPILED_LIB_PATH}")
message(FATAL_ERROR "Precompiled liblbug archive not found: ${LBUG_NODEJS_PRECOMPILED_LIB_PATH}")
endif()

set(LBUG_NODEJS_SOURCE_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/src/include")
set(LBUG_NODEJS_BUILD_INCLUDE_DIR "${PROJECT_BINARY_DIR}/src/include")
if(LBUG_SOURCE_DIR)
set(LBUG_NODEJS_SOURCE_INCLUDE_DIR "${LBUG_SOURCE_DIR}/src/include")
endif()
if(LBUG_BUILD_DIR)
set(LBUG_NODEJS_BUILD_INCLUDE_DIR "${LBUG_BUILD_DIR}/src/include")
elseif(LBUG_SOURCE_DIR)
set(LBUG_NODEJS_BUILD_INCLUDE_DIR "${LBUG_SOURCE_DIR}/build/release/src/include")
endif()

add_library(lbug STATIC IMPORTED GLOBAL)
set_target_properties(lbug PROPERTIES IMPORTED_LOCATION "${LBUG_NODEJS_PRECOMPILED_LIB_PATH}")
target_include_directories(lbug INTERFACE
"${PROJECT_SOURCE_DIR}/src/include"
"${PROJECT_BINARY_DIR}/src/include")
"${LBUG_NODEJS_SOURCE_INCLUDE_DIR}"
"${LBUG_NODEJS_BUILD_INCLUDE_DIR}")
if(WIN32)
target_compile_definitions(lbug INTERFACE LBUG_STATIC_DEFINE)
endif()
set(NODEJS_LBUG_LINK_DEPS "$<LINK_ONLY:lbug_link_deps>")
if(TARGET lbug_link_deps)
set(NODEJS_LBUG_LINK_DEPS "$<LINK_ONLY:lbug_link_deps>")
endif()
endif()

add_library(lbugjs SHARED ${CPP_SOURCE_FILES})
Expand All @@ -117,3 +155,6 @@ else()
target_link_options(lbugjs PRIVATE -Wl,--export-dynamic)
endif()
target_link_libraries(lbugjs PRIVATE lbug ${NODEJS_LBUG_LINK_DEPS} ${CMAKE_JS_LIB})
if(LBUG_NODEJS_EXTRA_LINK_LIBS)
target_link_libraries(lbugjs PRIVATE ${LBUG_NODEJS_EXTRA_LINK_LIBS})
endif()
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Both CommonJS (`require`) and ES Modules (`import`) are fully supported.
### Install Dev Dependencies

```bash
git submodule update --init --recursive
npm install --include=dev
```

Expand All @@ -90,6 +91,14 @@ npm install --include=dev
npm run build
```

By default, the standalone build looks for a sibling Ladybug checkout at
`../ladybug` via `LBUG_SOURCE_DIR`, and reuses the prebuilt static library from
`../ladybug/build/release`. Override that location like this:

```bash
LBUG_SOURCE_DIR=/path/to/ladybug npm run build
```

### Run Tests

```bash
Expand Down
139 changes: 128 additions & 11 deletions build.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,146 @@
const fs = require("fs");
const os = require("os");
const path = require("path");
const { execSync } = require("child_process");
const { execFileSync, execSync } = require("child_process");

const SRC_PATH = path.resolve(__dirname, "../..");
const THREADS = require("os").cpus().length;
const THREADS = os.cpus().length;
const DEFAULT_LBUG_SOURCE_DIR = path.resolve(__dirname, "../ladybug");

function resolveExistingPath(candidate) {
if (!candidate) {
return null;
}
const resolved = path.resolve(candidate);
return fs.existsSync(resolved) ? resolved : null;
}

function getDefaultBuildDir(lbugSourceDir) {
return lbugSourceDir ? path.join(lbugSourceDir, "build", "release") : null;
}

function getDefaultPrecompiledLibPath(lbugBuildDir) {
if (!lbugBuildDir) {
return null;
}
const candidates = process.platform === "win32"
? [
path.join(lbugBuildDir, "src", "Release", "lbug.lib"),
path.join(lbugBuildDir, "src", "lbug.lib"),
path.join(lbugBuildDir, "src", "Release", "liblbug.lib"),
path.join(lbugBuildDir, "src", "liblbug.lib"),
]
: [path.join(lbugBuildDir, "src", "liblbug.a")];
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
}

function getDefaultExtraLinkLibs(lbugBuildDir, precompiledLibPath) {
if (!lbugBuildDir || !precompiledLibPath) {
return [];
}

const linkTxtCandidates = [
path.join(lbugBuildDir, "tools", "python_api", "CMakeFiles", "_lbug.dir", "link.txt"),
path.join(lbugBuildDir, "src", "CMakeFiles", "lbug_shared.dir", "link.txt"),
];
const tokenPattern = /"[^"]+"|\S+/g;

for (const linkTxtPath of linkTxtCandidates) {
if (!fs.existsSync(linkTxtPath)) {
continue;
}
const linkTxtDir = path.dirname(linkTxtPath);
const linkBaseDir = linkTxtPath.includes(`${path.sep}tools${path.sep}python_api${path.sep}`)
? path.join(lbugBuildDir, "tools", "python_api")
: linkTxtPath.includes(`${path.sep}src${path.sep}CMakeFiles${path.sep}`)
? path.join(lbugBuildDir, "src")
: linkTxtDir;
const tokens = fs.readFileSync(linkTxtPath, "utf8").match(tokenPattern) || [];
const libs = [];
let sawPrecompiledLib = false;

for (const rawToken of tokens) {
const token = rawToken.replace(/^"(.*)"$/, "$1");
const resolvedToken = token.startsWith("-")
? token
: fs.existsSync(path.resolve(linkBaseDir, token))
? path.resolve(linkBaseDir, token)
: path.resolve(linkTxtDir, token);

if (!sawPrecompiledLib) {
if (resolvedToken === precompiledLibPath) {
sawPrecompiledLib = true;
}
continue;
}

if (token.startsWith("-l")) {
libs.push(token);
continue;
}
if (!/\.(a|lib|dylib|so|tbd)$/.test(token)) {
continue;
}
if (resolvedToken === precompiledLibPath) {
continue;
}
libs.push(resolvedToken);
}

if (libs.length > 0) {
return [...new Set(libs)];
}
}

return [];
}

console.log(`Using ${THREADS} threads to build Lbug.`);

const env = { ...process.env };
const precompiledLibPath = env.LBUG_NODEJS_PRECOMPILED_LIB_PATH;
const lbugSourceDir = resolveExistingPath(env.LBUG_SOURCE_DIR || DEFAULT_LBUG_SOURCE_DIR);
const lbugBuildDir = resolveExistingPath(env.LBUG_BUILD_DIR || getDefaultBuildDir(lbugSourceDir));
const precompiledLibPath = resolveExistingPath(
env.LBUG_NODEJS_PRECOMPILED_LIB_PATH || getDefaultPrecompiledLibPath(lbugBuildDir)
);
const extraLinkLibs = getDefaultExtraLinkLibs(lbugBuildDir, precompiledLibPath);

if (lbugSourceDir) {
env.LBUG_SOURCE_DIR = lbugSourceDir;
}
if (lbugBuildDir) {
env.LBUG_BUILD_DIR = lbugBuildDir;
}

const cmakeArgs = env.EXTRA_CMAKE_FLAGS
? env.EXTRA_CMAKE_FLAGS.trim().split(/\s+/).filter(Boolean)
: [];
if (lbugSourceDir) {
cmakeArgs.push(`-DLBUG_SOURCE_DIR=${lbugSourceDir}`);
}
if (lbugBuildDir) {
cmakeArgs.push(`-DLBUG_BUILD_DIR=${lbugBuildDir}`);
}
if (precompiledLibPath) {
const extraFlags = [
env.EXTRA_CMAKE_FLAGS,
cmakeArgs.push(
"-DBUILD_LBUG=FALSE",
"-DBUILD_SHELL=FALSE",
"-DLBUG_NODEJS_USE_PRECOMPILED_LIB=TRUE",
`-DLBUG_NODEJS_PRECOMPILED_LIB_PATH=${precompiledLibPath}`,
].filter(Boolean).join(" ");
env.EXTRA_CMAKE_FLAGS = extraFlags;
`-DLBUG_NODEJS_PRECOMPILED_LIB_PATH=${precompiledLibPath}`
);
console.log(`Using precompiled liblbug from ${precompiledLibPath}.`);
}
if (extraLinkLibs.length > 0) {
cmakeArgs.push(`-DLBUG_NODEJS_EXTRA_LINK_LIBS=${extraLinkLibs.join(";")}`);
}

execSync("npm run clean", { stdio: "inherit" });
execSync(`make nodejs NUM_THREADS=${THREADS}`, {
cwd: SRC_PATH,
execFileSync("cmake", ["-S", ".", "-B", "build", ...cmakeArgs], {
cwd: __dirname,
env,
stdio: "inherit",
});
execFileSync("cmake", ["--build", "build", "-j", `${THREADS}`], {
cwd: __dirname,
env,
stdio: "inherit",
});
1 change: 1 addition & 0 deletions dataset
Submodule dataset added at 555311
Loading
Loading