From ad9e78d224f8a1be322c6a8046044f43db8d51c5 Mon Sep 17 00:00:00 2001 From: Garrett Summerfield Date: Fri, 27 Sep 2024 19:59:46 -0500 Subject: [PATCH 1/4] Update build systems for Linux, macOS Update GYP to build for Linux and macOS platforms, as well as ARM and ARM64 for Linux. Updated script to download CANBridge for Linux and macOS, including ARM platforms for Linux. --- binding.gyp | 110 ++++++++++++++++++++---- scripts/download-CanBridge.mjs | 151 +++++++++++++++++++++++++++++---- 2 files changed, 226 insertions(+), 35 deletions(-) diff --git a/binding.gyp b/binding.gyp index 919189e..05b9c17 100644 --- a/binding.gyp +++ b/binding.gyp @@ -5,7 +5,7 @@ 'sources': [ 'src/addon.cc', 'src/canWrapper.cc', - ], + ], 'include_dirs': [ "src/", "externalCompileTimeDeps/include", @@ -15,21 +15,88 @@ "NAPI_VERSION=<(napi_build_version)" ], 'dependencies': [" Look at Octokit: https://github.com/octokit/octokit.js + await Promise.all([ + 'CANBridge-linuxarm32.zip', + 'CANBridge-linuxarm64.zip', + 'CANBridge-linuxx86-64.zip', + 'CANBridge-osxuniversal.zip', + 'CANBridge-windowsx86-64.zip', + 'headers.zip' + ].map(filename => downloadCanBridgeArtifact(filename))); + console.log("CANBridge download completed"); + console.log("Extracting headers"); - + const zipFiles = fs.readdirSync(tempDir).filter(filename => filename.endsWith('.zip') && filename !== 'headers.zip'); + for (const filename of zipFiles) { + await unzipCanBridgeArtifact(filename, tempDir); + } const headersZip = new AdmZip(path.join(tempDir, "headers.zip")); + headersZip.extractAllTo(path.join(externalCompileTimeDepsPath, 'include')); + console.log("Headers extracted"); + + moveRuntimeDeps(); - await headersZip.extractAllTo(path.join(externalCompileTimeDepsPath, 'include'), true); - console.log(`Successfully downloaded CANBridge ${canBridgeTag}`); + moveCompileTimeDeps(); } catch (e) { if (axios.isAxiosError(e) && e.request) { - console.error(`Failed to download CANBridge file ${e.request.protocol}//${e.request.host}/${e.request.path}`); + console.error(`Failed to download CANBridge file ${e.request.protocol}//${e.request.host}${e.request.path}`); } else { - console.error(`Failed to download CANBridge`); + console.error(`Other error occurred: ${e.message}`); // For non-axios errors, the stacktrace will likely be helpful throw e; } process.exit(1); } finally { if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + fs.rmSync(tempDir, { recursive: true, force: true}); } } -async function downloadCanBridgeArtifact(filename, destDir) { +/** + * Move external compile time dependencies to the correct directory + * + * This function is used to move the external compile time dependencies to the correct directory based on the platform and architecture from downloaded artifacts + */ +function moveCompileTimeDeps() { + console.log("Moving external compile time dependencies to correct directories"); + if (!fs.existsSync(externalCompileTimeDepsPath)) { + fs.mkdirSync(externalCompileTimeDepsPath, { recursive: true }); + } + if (platform() === 'win32') { + const deps = ['CANBridge.lib', 'wpiHal.lib', 'wpiutil.lib']; + deps.forEach(dep => moveExternalCompileTimeDeps(path.join('win32-x64', dep))); + } else if (platform() === 'darwin') { + const deps = ['libCANBridge.a']; + deps.forEach(dep => moveExternalCompileTimeDeps(path.join('darwin-osxuniversal', dep))); + } else if (platform() === 'linux') { + const deps = ['libCANBridge.a']; + const archDepMap = { + x64: 'linux-x64', + arm64: 'linux-arm64', + arm: 'linux-arm32' + }; + deps.forEach(dep => moveExternalCompileTimeDeps(path.join(archDepMap[arch()], dep))); + } + console.log("External compile time dependencies moved to correct directories"); +} + +/** + * Move runtime dependencies to the correct directory + * + * This function is used to move the runtime dependencies to the correct directory based on the platform and architecture from downloaded artifacts + */ +function moveRuntimeDeps() { + console.log("Moving artifacts to correct directories"); + if (!fs.existsSync('prebuilds')) { + fs.mkdirSync('prebuilds', { recursive: true }); + } + if (platform() === 'win32') { + const deps = ['CANBridge.dll', 'wpiHal.dll', 'wpiutil.dll']; + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('win32-x64', dep), runtimeArtifactsPath.win)); + } else if (platform() === 'darwin') { + const deps = ['libCANBridge.dylib', 'libwpiHal.dylib', 'libwpiutil.dylib']; + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('darwin-osxuniversal', dep), runtimeArtifactsPath.osx)); + } else if (platform() === 'linux') { + const deps = ['libCANBridge.so', 'libwpiHal.so', 'libwpiutil.so']; + if (arch() === 'x64') { + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-x64', dep), runtimeArtifactsPath.linux)); + } + if (arch() === 'arm64') { + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-arm64', dep), runtimeArtifactsPath.linuxArm)); + } + if (arch() === 'arm') { + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-arm32', dep), runtimeArtifactsPath.linuxArm32)); + } + } + console.log("CANBridge artifacts moved to correct directories"); +} + +/** + * Download artifacts from the CANBridge GitHub release page + * + * @param {*} filename filename of the artifact to download + * @param {*} destDir destination directory to save the artifact, defaults to tempDir + */ +async function downloadCanBridgeArtifact(filename, destDir = tempDir) { fs.mkdirSync(destDir, { recursive: true }); const response = await axios.get(`${canBridgeReleaseAssetUrlPrefix}/${filename}`, { responseType: "stream" }); const fileStream = fs.createWriteStream(`${destDir}/${filename}`); @@ -51,3 +131,40 @@ async function downloadCanBridgeArtifact(filename, destDir) { fileStream.on('finish', resolve); }); } + +/** + * Unzip the CANBridge artifacts + * + * @param {string} filename - filename of the artifact to unzip + * @param {string} destDir - destination directory to unzip the artifact + */ +async function unzipCanBridgeArtifact(filename, destDir) { + const zip = new AdmZip(`${destDir}/${filename}`); + let filepath; + if (filename.includes('linuxarm32')) filepath = "linux-arm32"; + else if (filename.includes('linuxarm64')) filepath = "linux-arm64"; + else if (filename.includes('linuxx86-64')) filepath = "linux-x64"; + else if (filename.includes('osxuniversal')) filepath = "darwin-osxuniversal"; + else if (filename.includes('windowsx86-64')) filepath = "win32-x64"; + zip.extractAllTo(`${destDir}/${filepath}`); +} + +/** + * Move runtime artifacts to the correct directory + * + * @param {*} filename filename of the artifact to move + * @param {*} destDir destination directory to save the artifact + */ +function moveRuntimeArtifactsDeps(filename, destDir) { + fs.mkdirSync(destDir, { recursive: true }); + fs.renameSync(path.join(tempDir, filename), path.join(destDir, path.basename(filename))); +} + +/** + * Move External Compile Time Dependencies to the correct directory + * + * @param {*} filename filename of the artifact to move + */ +function moveExternalCompileTimeDeps(filename) { + fs.renameSync(path.join(tempDir, filename), path.join(externalCompileTimeDepsPath, path.basename(filename))); +} \ No newline at end of file From 57cd9337ef3e063efdd99f590dca77dad22b9e63 Mon Sep 17 00:00:00 2001 From: Garrett Summerfield Date: Fri, 27 Sep 2024 20:10:05 -0500 Subject: [PATCH 2/4] Add Dependabot, GitHub Actions workflows Added GitHub Actions workflows for building and releasing node-can-bridge. This can run specific tasks automatically to ensure builds are operational. This also includes making releases via pushing tags. Added Dependabot to update workflow Actions to ensure they are up to date to negate any security issues and deprecation issues with older Actions. --- .github/dependabot.yml | 7 +++ .github/workflows/build.yml | 84 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 52 ++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0f83d09 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Dependabot updates for GitHub Actions + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a71df48 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,84 @@ +name: Build + +on: + [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + build-docker: + strategy: + fail-fast: false + matrix: + include: + - container: wpilib/aarch64-cross-ubuntu:bullseye-22.04 + name: LinuxARM64 + platform-type: linuxarm64 + arch: arm64 + - container: wpilib/raspbian-cross-ubuntu:bullseye-22.04 + name: LinuxARM32 + platform-type: linuxarm32 + arch: arm32 + runs-on: ubuntu-latest + name: "Build - ${{ matrix.name }}" + container: ${{ matrix.container }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: npm install + + - name: Pretest + run: npm run pretest + + # Due to the nature of the build process, we can't run the tests in the container becauase external hardware is required + # If this were to be running on a local machine, the tests would be run here + #- name: Test + # run: npm test + + build-native: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: "Build - ${{ matrix.os }}" + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: npm install + + - name: Pretest + run: npm run pretest + + # Due to the nature of the build process, we can't run the tests in the container becauase external hardware is required + # If this were to be running on a local machine, the tests would be run here + #- name: Test + # run: npm test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..40a0c7e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Create release + +on: + push: + tags: + - 'v*' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + release: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: "Release - ${{ matrix.os }}" + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run build + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + files: dist/* + tag_name: ${{ github.ref }} + name: ${{ github.ref }} + body: | + This is a release for version ${{ github.ref }}. + It contains the compiled files from the build process. \ No newline at end of file From c84b41ebb3980960d449e3e3389399a962d6f570 Mon Sep 17 00:00:00 2001 From: QwertyChouskie Date: Wed, 18 Jun 2025 16:09:47 -0700 Subject: [PATCH 3/4] Update download-CanBridge.mjs to reflect final state of CANBridge work --- scripts/download-CanBridge.mjs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/scripts/download-CanBridge.mjs b/scripts/download-CanBridge.mjs index f9b20fa..bc97b0d 100644 --- a/scripts/download-CanBridge.mjs +++ b/scripts/download-CanBridge.mjs @@ -4,7 +4,8 @@ import axios from 'axios'; import AdmZip from 'adm-zip'; import { platform, arch } from 'os'; -const canBridgeTag = "v2.6.0"; +const canBridgeTag = "v2.7.0"; +const canBridgeSHA = "7481e94e4b233274a97398b4eb13406399b24db2"; const canBridgeReleaseAssetUrlPrefix = `https://github.com/REVrobotics/CANBridge/releases/download/${canBridgeTag}`; const externalCompileTimeDepsPath = 'externalCompileTimeDeps'; @@ -20,11 +21,11 @@ const tempDir = 'temp'; try { // TODO: Do not hardcode the filenames, instead get them from the GitHub API -> Look at Octokit: https://github.com/octokit/octokit.js await Promise.all([ - 'CANBridge-linuxarm32.zip', - 'CANBridge-linuxarm64.zip', - 'CANBridge-linuxx86-64.zip', - 'CANBridge-osxuniversal.zip', - 'CANBridge-windowsx86-64.zip', + `CANBridge-linuxarm32-${canBridgeSHA}.zip`, + `CANBridge-linuxarm64-${canBridgeSHA}.zip`, + `CANBridge-linuxx86-64-${canBridgeSHA}.zip`, + `CANBridge-osxuniversal-${canBridgeSHA}.zip`, + `CANBridge-windowsx86-64-${canBridgeSHA}.zip`, 'headers.zip' ].map(filename => downloadCanBridgeArtifact(filename))); console.log("CANBridge download completed"); @@ -69,10 +70,10 @@ function moveCompileTimeDeps() { } if (platform() === 'win32') { const deps = ['CANBridge.lib', 'wpiHal.lib', 'wpiutil.lib']; - deps.forEach(dep => moveExternalCompileTimeDeps(path.join('win32-x64', dep))); + deps.forEach(dep => moveExternalCompileTimeDeps(path.join('win32-x64', 'static', dep))); } else if (platform() === 'darwin') { const deps = ['libCANBridge.a']; - deps.forEach(dep => moveExternalCompileTimeDeps(path.join('darwin-osxuniversal', dep))); + deps.forEach(dep => moveExternalCompileTimeDeps(path.join('darwin-osxuniversal', 'static', dep))); } else if (platform() === 'linux') { const deps = ['libCANBridge.a']; const archDepMap = { @@ -80,7 +81,7 @@ function moveCompileTimeDeps() { arm64: 'linux-arm64', arm: 'linux-arm32' }; - deps.forEach(dep => moveExternalCompileTimeDeps(path.join(archDepMap[arch()], dep))); + deps.forEach(dep => moveExternalCompileTimeDeps(path.join(archDepMap[arch()], 'static', dep))); } console.log("External compile time dependencies moved to correct directories"); } @@ -97,20 +98,20 @@ function moveRuntimeDeps() { } if (platform() === 'win32') { const deps = ['CANBridge.dll', 'wpiHal.dll', 'wpiutil.dll']; - deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('win32-x64', dep), runtimeArtifactsPath.win)); + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('win32-x64', 'shared', dep), runtimeArtifactsPath.win)); } else if (platform() === 'darwin') { const deps = ['libCANBridge.dylib', 'libwpiHal.dylib', 'libwpiutil.dylib']; - deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('darwin-osxuniversal', dep), runtimeArtifactsPath.osx)); + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('darwin-osxuniversal', 'shared', dep), runtimeArtifactsPath.osx)); } else if (platform() === 'linux') { const deps = ['libCANBridge.so', 'libwpiHal.so', 'libwpiutil.so']; if (arch() === 'x64') { - deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-x64', dep), runtimeArtifactsPath.linux)); + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-x64', 'shared', dep), runtimeArtifactsPath.linux)); } if (arch() === 'arm64') { - deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-arm64', dep), runtimeArtifactsPath.linuxArm)); + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-arm64', 'shared', dep), runtimeArtifactsPath.linuxArm)); } if (arch() === 'arm') { - deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-arm32', dep), runtimeArtifactsPath.linuxArm32)); + deps.forEach(dep => moveRuntimeArtifactsDeps(path.join('linux-arm32', 'shared', dep), runtimeArtifactsPath.linuxArm32)); } } console.log("CANBridge artifacts moved to correct directories"); From 4c6c3ed291f15fa27c7c7bea75cbdd2e69441d97 Mon Sep 17 00:00:00 2001 From: QwertyChouskie Date: Wed, 18 Jun 2025 16:32:45 -0700 Subject: [PATCH 4/4] canWrapper: Fix compiler warning about string + int --- src/canWrapper.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canWrapper.cc b/src/canWrapper.cc index 10dfbbd..24f18e2 100644 --- a/src/canWrapper.cc +++ b/src/canWrapper.cc @@ -339,7 +339,7 @@ Napi::Number openStreamSession(const Napi::CallbackInfo& info) { try { rev::usb::CANStatus status = device->OpenStreamSession(&sessionHandle, filter, maxSize); if (status != rev::usb::CANStatus::kOk) { - Napi::Error::New(env, "Opening stream session failed with error code "+(int)status).ThrowAsJavaScriptException(); + Napi::Error::New(env, "Opening stream session failed with error code " + std::to_string((int)status)).ThrowAsJavaScriptException(); } else { return Napi::Number::New(env, sessionHandle); }