From d9fb25c936fcba32b51a8eb02aa7a646ef16f512 Mon Sep 17 00:00:00 2001
From: Feng Yu <F3n67u@outlook.com>
Date: Sun, 22 May 2022 21:28:41 +0800
Subject: [PATCH] tools: refactor build-addons.js to ESM

PR-URL: https://github.com/nodejs/node/pull/43099
Reviewed-By: Darshan Sen <raisinten@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
---
 .github/label-pr-config.yml            |  2 +-
 Makefile                               |  4 +-
 doc/contributing/collaborator-guide.md |  2 +-
 tools/build-addons.js                  | 58 ------------------------
 tools/build-addons.mjs                 | 63 ++++++++++++++++++++++++++
 vcbuild.bat                            |  6 +--
 6 files changed, 70 insertions(+), 65 deletions(-)
 delete mode 100644 tools/build-addons.js
 create mode 100755 tools/build-addons.mjs

diff --git a/.github/label-pr-config.yml b/.github/label-pr-config.yml
index a8d22980f6db18..5f83de231262a5 100644
--- a/.github/label-pr-config.yml
+++ b/.github/label-pr-config.yml
@@ -67,7 +67,7 @@ subSystemLabels:
   /^tools\/make-v8/: tools, v8 engine, needs-ci
   /^tools\/v8_gypfiles/: tools, v8 engine, needs-ci
   /^tools\/(code_cache|snapshot)/: needs-ci
-  /^tools\/build-addons.js/: needs-ci
+  /^tools\/build-addons.mjs/: needs-ci
   # all other tool changes should be marked as such
   /^tools\//: tools
   /^\.eslint|\.remark|\.editorconfig/: tools
diff --git a/Makefile b/Makefile
index a6549a8474c215..9f7ab7129d0647 100644
--- a/Makefile
+++ b/Makefile
@@ -366,13 +366,13 @@ ADDONS_BINDING_SOURCES := \
 	$(filter-out test/addons/??_*/*.h, $(wildcard test/addons/*/*.h))
 
 ADDONS_PREREQS := config.gypi \
-	deps/npm/node_modules/node-gyp/package.json tools/build-addons.js \
+	deps/npm/node_modules/node-gyp/package.json tools/build-addons.mjs \
 	deps/uv/include/*.h deps/v8/include/*.h \
 	src/node.h src/node_buffer.h src/node_object_wrap.h src/node_version.h
 
 define run_build_addons
 env npm_config_loglevel=$(LOGLEVEL) npm_config_nodedir="$$PWD" \
-	npm_config_python="$(PYTHON)" $(NODE) "$$PWD/tools/build-addons" \
+	npm_config_python="$(PYTHON)" $(NODE) "$$PWD/tools/build-addons.mjs" \
 	"$$PWD/deps/npm/node_modules/node-gyp/bin/node-gyp.js" \
 	$1
 touch $2
diff --git a/doc/contributing/collaborator-guide.md b/doc/contributing/collaborator-guide.md
index 4550c2e7d1565c..546e0e71ba1372 100644
--- a/doc/contributing/collaborator-guide.md
+++ b/doc/contributing/collaborator-guide.md
@@ -232,7 +232,7 @@ There are some other files that touch the build chain. Changes in the following
 files also qualify as affecting the `node` binary:
 
 * `tools/*.py`
-* `tools/build-addons.js`
+* `tools/build-addons.mjs`
 * `*.gyp`
 * `*.gypi`
 * `configure`
diff --git a/tools/build-addons.js b/tools/build-addons.js
deleted file mode 100644
index 1d4bcbc917972c..00000000000000
--- a/tools/build-addons.js
+++ /dev/null
@@ -1,58 +0,0 @@
-'use strict';
-
-// Usage: e.g. node build-addons.js <path to node-gyp> <directory>
-
-const child_process = require('child_process');
-const path = require('path');
-const fs = require('fs').promises;
-const util = require('util');
-
-const execFile = util.promisify(child_process.execFile);
-
-const parallelization = +process.env.JOBS || require('os').cpus().length;
-const nodeGyp = process.argv[2];
-
-async function runner(directoryQueue) {
-  if (directoryQueue.length === 0)
-    return;
-
-  const dir = directoryQueue.shift();
-  const next = () => runner(directoryQueue);
-
-  try {
-    // Only run for directories that have a `binding.gyp`.
-    // (https://github.com/nodejs/node/issues/14843)
-    await fs.stat(path.join(dir, 'binding.gyp'));
-  } catch (err) {
-    if (err.code === 'ENOENT' || err.code === 'ENOTDIR')
-      return next();
-    throw err;
-  }
-
-  console.log(`Building addon in ${dir}`);
-  const { stdout, stderr } =
-    await execFile(process.execPath, [nodeGyp, 'rebuild', `--directory=${dir}`],
-                   {
-                     stdio: 'inherit',
-                     env: { ...process.env, MAKEFLAGS: '-j1' }
-                   });
-
-  // We buffer the output and print it out once the process is done in order
-  // to avoid interleaved output from multiple builds running at once.
-  process.stdout.write(stdout);
-  process.stderr.write(stderr);
-
-  return next();
-}
-
-async function main(directory) {
-  const directoryQueue = (await fs.readdir(directory))
-    .map((subdir) => path.join(directory, subdir));
-
-  const runners = [];
-  for (let i = 0; i < parallelization; ++i)
-    runners.push(runner(directoryQueue));
-  return Promise.all(runners);
-}
-
-main(process.argv[3]).catch((err) => setImmediate(() => { throw err; }));
diff --git a/tools/build-addons.mjs b/tools/build-addons.mjs
new file mode 100755
index 00000000000000..9f757bd798bcf0
--- /dev/null
+++ b/tools/build-addons.mjs
@@ -0,0 +1,63 @@
+#!/usr/bin/env node
+
+// Usage: e.g. node build-addons.mjs <path to node-gyp> <directory>
+
+import child_process from 'node:child_process';
+import path from 'node:path';
+import fs from 'node:fs/promises';
+import util from 'node:util';
+import process from 'node:process';
+import os from 'node:os';
+
+const execFile = util.promisify(child_process.execFile);
+
+const parallelization = +process.env.JOBS || os.cpus().length;
+const nodeGyp = process.argv[2];
+const directory = process.argv[3];
+
+async function buildAddon(dir) {
+  try {
+    // Only run for directories that have a `binding.gyp`.
+    // (https://github.com/nodejs/node/issues/14843)
+    await fs.stat(path.join(dir, 'binding.gyp'));
+  } catch (err) {
+    if (err.code === 'ENOENT' || err.code === 'ENOTDIR')
+      return;
+    throw err;
+  }
+
+  console.log(`Building addon in ${dir}`);
+  const { stdout, stderr } =
+    await execFile(process.execPath, [nodeGyp, 'rebuild', `--directory=${dir}`],
+                   {
+                     stdio: 'inherit',
+                     env: { ...process.env, MAKEFLAGS: '-j1' }
+                   });
+
+  // We buffer the output and print it out once the process is done in order
+  // to avoid interleaved output from multiple builds running at once.
+  process.stdout.write(stdout);
+  process.stderr.write(stderr);
+}
+
+async function parallel(jobQueue, limit) {
+  const next = async () => {
+    if (jobQueue.length === 0) {
+      return;
+    }
+    const job = jobQueue.shift();
+    await job();
+    await next();
+  };
+
+  const workerCnt = Math.min(limit, jobQueue.length);
+  await Promise.all(Array.from({ length: workerCnt }, next));
+}
+
+const jobs = [];
+for await (const dirent of await fs.opendir(directory)) {
+  if (dirent.isDirectory()) {
+    jobs.push(() => buildAddon(path.join(directory, dirent.name)));
+  }
+}
+await parallel(jobs, parallelization);
diff --git a/vcbuild.bat b/vcbuild.bat
index 96306e407e1a19..49fa899de7178e 100644
--- a/vcbuild.bat
+++ b/vcbuild.bat
@@ -608,7 +608,7 @@ if %errorlevel% neq 0 exit /b %errorlevel%
 :: building addons
 setlocal
 set npm_config_nodedir=%~dp0
-"%node_exe%" "%~dp0tools\build-addons.js" "%~dp0deps\npm\node_modules\node-gyp\bin\node-gyp.js" "%~dp0test\addons"
+"%node_exe%" "%~dp0tools\build-addons.mjs" "%~dp0deps\npm\node_modules\node-gyp\bin\node-gyp.js" "%~dp0test\addons"
 if errorlevel 1 exit /b 1
 endlocal
 
@@ -626,7 +626,7 @@ for /d %%F in (test\js-native-api\??_*) do (
 :: building js-native-api
 setlocal
 set npm_config_nodedir=%~dp0
-"%node_exe%" "%~dp0tools\build-addons.js" "%~dp0deps\npm\node_modules\node-gyp\bin\node-gyp.js" "%~dp0test\js-native-api"
+"%node_exe%" "%~dp0tools\build-addons.mjs" "%~dp0deps\npm\node_modules\node-gyp\bin\node-gyp.js" "%~dp0test\js-native-api"
 if errorlevel 1 exit /b 1
 endlocal
 goto build-node-api-tests
@@ -645,7 +645,7 @@ for /d %%F in (test\node-api\??_*) do (
 :: building node-api
 setlocal
 set npm_config_nodedir=%~dp0
-"%node_exe%" "%~dp0tools\build-addons.js" "%~dp0deps\npm\node_modules\node-gyp\bin\node-gyp.js" "%~dp0test\node-api"
+"%node_exe%" "%~dp0tools\build-addons.mjs" "%~dp0deps\npm\node_modules\node-gyp\bin\node-gyp.js" "%~dp0test\node-api"
 if errorlevel 1 exit /b 1
 endlocal
 goto run-tests