From 46d75768018d10dc4b5693b35cf58f502425cbbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Thu, 29 Jan 2026 16:45:14 +0100 Subject: [PATCH 1/6] fix: Switch to URL instead of url.resolve (#3256) (#3263) --- lib/process-release.js | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/lib/process-release.js b/lib/process-release.js index b92e8d5b83..75f3fc136a 100644 --- a/lib/process-release.js +++ b/lib/process-release.js @@ -1,9 +1,6 @@ -/* eslint-disable n/no-deprecated-api */ - 'use strict' const semver = require('semver') -const url = require('url') const path = require('path') const log = require('./log') @@ -74,11 +71,11 @@ function processRelease (argv, gyp, defaultVersion, defaultRelease) { } else { distBaseUrl = 'https://nodejs.org/dist' } - distBaseUrl += '/v' + version + '/' + distBaseUrl = new URL(distBaseUrl + '/v' + version + '/') // new style, based on process.release so we have a lot of the data we need if (defaultRelease && defaultRelease.headersUrl && !overrideDistUrl) { - baseUrl = url.resolve(defaultRelease.headersUrl, './') + baseUrl = new URL('./', defaultRelease.headersUrl) libUrl32 = resolveLibUrl(name, defaultRelease.libUrl || baseUrl || distBaseUrl, 'x86', versionSemver.major) libUrl64 = resolveLibUrl(name, defaultRelease.libUrl || baseUrl || distBaseUrl, 'x64', versionSemver.major) libUrlArm64 = resolveLibUrl(name, defaultRelease.libUrl || baseUrl || distBaseUrl, 'arm64', versionSemver.major) @@ -96,28 +93,28 @@ function processRelease (argv, gyp, defaultVersion, defaultRelease) { // have a *-headers.tar.gz file in its dist location, even some frankenstein // custom version canGetHeaders = semver.satisfies(versionSemver, headersTarballRange) - tarballUrl = url.resolve(baseUrl, name + '-v' + version + (canGetHeaders ? '-headers' : '') + '.tar.gz') + tarballUrl = new URL(name + '-v' + version + (canGetHeaders ? '-headers' : '') + '.tar.gz', baseUrl).href } return { version, semver: versionSemver, name, - baseUrl, + baseUrl: baseUrl.href, tarballUrl, - shasumsUrl: url.resolve(baseUrl, 'SHASUMS256.txt'), + shasumsUrl: new URL('SHASUMS256.txt', baseUrl).href, versionDir: (name !== 'node' ? name + '-' : '') + version, ia32: { - libUrl: libUrl32, - libPath: normalizePath(path.relative(new URL(baseUrl).pathname, new URL(libUrl32).pathname)) + libUrl: libUrl32.href, + libPath: normalizePath(path.relative(baseUrl.pathname, libUrl32.pathname)) }, x64: { - libUrl: libUrl64, - libPath: normalizePath(path.relative(new URL(baseUrl).pathname, new URL(libUrl64).pathname)) + libUrl: libUrl64.href, + libPath: normalizePath(path.relative(baseUrl.pathname, libUrl64.pathname)) }, arm64: { - libUrl: libUrlArm64, - libPath: normalizePath(path.relative(new URL(baseUrl).pathname, new URL(libUrlArm64).pathname)) + libUrl: libUrlArm64.href, + libPath: normalizePath(path.relative(baseUrl.pathname, libUrlArm64.pathname)) } } } @@ -127,20 +124,21 @@ function normalizePath (p) { } function resolveLibUrl (name, defaultUrl, arch, versionMajor) { - const base = url.resolve(defaultUrl, './') - const hasLibUrl = bitsre.test(defaultUrl) || (versionMajor === 3 && bitsreV3.test(defaultUrl)) + if (!defaultUrl.pathname) defaultUrl = new URL(defaultUrl) + const base = new URL('./', defaultUrl) + const hasLibUrl = bitsre.test(defaultUrl.pathname) || (versionMajor === 3 && bitsreV3.test(defaultUrl.pathname)) if (!hasLibUrl) { // let's assume it's a baseUrl then if (versionMajor >= 1) { - return url.resolve(base, 'win-' + arch + '/' + name + '.lib') + return new URL('win-' + arch + '/' + name + '.lib', base) } // prior to io.js@1.0.0 32-bit node.lib lives in /, 64-bit lives in /x64/ - return url.resolve(base, (arch === 'x86' ? '' : arch + '/') + name + '.lib') + return new URL((arch === 'x86' ? '' : arch + '/') + name + '.lib', base) } // else we have a proper url to a .lib, just make sure it's the right arch - return defaultUrl.replace(versionMajor === 3 ? bitsreV3 : bitsre, '/win-' + arch + '/') + return new URL(defaultUrl.pathname.replace(versionMajor === 3 ? bitsreV3 : bitsre, '/win-' + arch + '/'), defaultUrl) } module.exports = processRelease From 13814583a476c85a84d5ac902c7ffa310120cb88 Mon Sep 17 00:00:00 2001 From: Stefan Stojanovic Date: Fri, 27 Feb 2026 01:14:47 +0100 Subject: [PATCH 2/6] win: improve Add-Type with -IgnoreWarnings (#3280) Refs: https://github.com/nodejs/node-gyp/issues/3276 --- lib/find-visualstudio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/find-visualstudio.js b/lib/find-visualstudio.js index 22fc4013f9..efb8b02a59 100644 --- a/lib/find-visualstudio.js +++ b/lib/find-visualstudio.js @@ -247,7 +247,7 @@ class VisualStudioFinder { 'Unrestricted', '-NoProfile', '-Command', - '&{Add-Type -Path \'' + csFile + '\';' + '[VisualStudioConfiguration.Main]::PrintJson()}' + '&{Add-Type -IgnoreWarnings -Path \'' + csFile + '\';' + '[VisualStudioConfiguration.Main]::PrintJson()}' ] this.log.silly('Running', ps, psArgs) From 19da1583b3876dce8c97263b168d9dfef637b76b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:33:59 +0100 Subject: [PATCH 3/6] build(deps-dev): bump neostandard from 0.12.2 to 0.13.0 (#3289) Bumps [neostandard](https://github.com/neostandard/neostandard) from 0.12.2 to 0.13.0. - [Release notes](https://github.com/neostandard/neostandard/releases) - [Changelog](https://github.com/neostandard/neostandard/blob/main/CHANGELOG.md) - [Commits](https://github.com/neostandard/neostandard/compare/v0.12.2...v0.13.0) --- updated-dependencies: - dependency-name: neostandard dependency-version: 0.13.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5dcf156de..71e0790043 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "eslint": "^9.39.1", "mocha": "^11.7.5", "nan": "^2.23.1", - "neostandard": "^0.12.2", + "neostandard": "^0.13.0", "require-inject": "^1.4.4" }, "scripts": { From 393ec2be480195b585768eb18ac2d92b858b12db Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Tue, 21 Apr 2026 01:52:21 -0400 Subject: [PATCH 4/6] feat: replace make-fetch-happen with built-in fetch (#3302) * feat: replace make-fetch-happen with built-in fetch Use Node's built-in fetch for downloading headers/tarballs, with undici's EnvHttpProxyAgent providing --proxy, --noproxy and --cafile support (plus http_proxy/https_proxy/no_proxy env var handling). Drops 36 transitive dependencies. * fixup: address review feedback - Guard Readable.fromWeb against null body (204/304 responses) - Add socket error handlers to CONNECT tunnel in proxy test - Destroy socket in noproxy test's CONNECT handler so a regression fails fast instead of hanging * ci: fix Python lint and npm install in tests workflow Apply f-string fix from gyp-next#337 to resolve ruff F507 in simple_copy.py, and add npm@~11.10.0 pre-install step from #3300 to work around npm/cli#9151. * fix: apply cafile to proxied TLS connections EnvHttpProxyAgent forwards opts to an internal ProxyAgent for proxied requests, which reads origin TLS config from requestTls rather than connect. Set connect/requestTls/proxyTls so the custom CA is honored on both direct and proxied paths. Adds a test covering https origin behind an HTTP CONNECT proxy with a custom CA. --- .github/workflows/tests.yml | 4 +- gyp/pylib/gyp/simple_copy.py | 4 +- lib/download.js | 63 ++++++++++++++++++++++++---- package.json | 2 +- test/test-download.js | 79 ++++++++++++++++++++++++++++++++---- 5 files changed, 133 insertions(+), 19 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d530aed20d..8d679a3157 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -67,7 +67,9 @@ jobs: with: node-version: 22.x - name: Update npm - run: npm install npm@latest -g + run: | + npm install npm@~11.10.0 -g # Workaround for https://github.com/npm/cli/issues/9151 + npm install npm@latest -g - name: Install Dependencies run: npm install - name: Pack diff --git a/gyp/pylib/gyp/simple_copy.py b/gyp/pylib/gyp/simple_copy.py index 8b026642fc..2b9100f3e1 100644 --- a/gyp/pylib/gyp/simple_copy.py +++ b/gyp/pylib/gyp/simple_copy.py @@ -24,8 +24,8 @@ def deepcopy(x): return _deepcopy_dispatch[type(x)](x) except KeyError: raise Error( - "Unsupported type %s for deepcopy. Use copy.deepcopy " - + "or expand simple_copy support." % type(x) + f"Unsupported type {type(x)} for deepcopy. Use copy.deepcopy " + + "or expand simple_copy support." ) diff --git a/lib/download.js b/lib/download.js index ed0aa37f44..a9866d8a63 100644 --- a/lib/download.js +++ b/lib/download.js @@ -1,4 +1,5 @@ -const fetch = require('make-fetch-happen') +const { Readable } = require('stream') +const { EnvHttpProxyAgent } = require('undici') const { promises: fs } = require('graceful-fs') const log = require('./log') @@ -10,19 +11,65 @@ async function download (gyp, url) { 'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`, Connection: 'keep-alive' }, - proxy: gyp.opts.proxy, - noProxy: gyp.opts.noproxy + dispatcher: await createDispatcher(gyp) } - const cafile = gyp.opts.cafile - if (cafile) { - requestOpts.ca = await readCAFile(cafile) + let res + try { + res = await fetch(url, requestOpts) + } catch (err) { + // Built-in fetch wraps low-level errors in "TypeError: fetch failed" with + // the underlying error on .cause. Callers inspect .code (e.g. ENOTFOUND). + if (err.cause) { + throw err.cause + } + throw err } - const res = await fetch(url, requestOpts) log.http(res.status, res.url) - return res + const body = res.body ? Readable.fromWeb(res.body) : Readable.from([]) + return { + status: res.status, + url: res.url, + body, + text: async () => { + let data = '' + body.setEncoding('utf8') + for await (const chunk of body) { + data += chunk + } + return data + } + } +} + +async function createDispatcher (gyp) { + const env = process.env + const hasProxyEnv = env.http_proxy || env.HTTP_PROXY || env.https_proxy || env.HTTPS_PROXY + if (!gyp.opts.proxy && !gyp.opts.cafile && !hasProxyEnv) { + return undefined + } + + const opts = {} + if (gyp.opts.cafile) { + const ca = await readCAFile(gyp.opts.cafile) + // EnvHttpProxyAgent forwards opts to both its internal Agent (direct) and + // ProxyAgent (proxied). Agent reads TLS config from `connect`; ProxyAgent + // reads it from `requestTls` (origin) / `proxyTls` (proxy). Set all three + // so the custom CA is applied regardless of which path a request takes. + opts.connect = { ca } + opts.requestTls = { ca } + opts.proxyTls = { ca } + } + if (gyp.opts.proxy) { + opts.httpProxy = gyp.opts.proxy + opts.httpsProxy = gyp.opts.proxy + } + if (gyp.opts.noproxy) { + opts.noProxy = gyp.opts.noproxy + } + return new EnvHttpProxyAgent(opts) } async function readCAFile (filename) { diff --git a/package.json b/package.json index 71e0790043..92807b96aa 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,12 @@ "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", + "undici": "^6.25.0", "which": "^6.0.0" }, "engines": { diff --git a/test/test-download.js b/test/test-download.js index a746c98cc6..4078efe5cd 100644 --- a/test/test-download.js +++ b/test/test-download.js @@ -6,6 +6,7 @@ const fs = require('fs/promises') const path = require('path') const http = require('http') const https = require('https') +const net = require('net') const install = require('../lib/install') const { download, readCAFile } = require('../lib/download') const { FULL_TEST, devDir, platformTimeout } = require('./common') @@ -69,13 +70,24 @@ describe('download', function () { }) it('download over http with proxy', async function () { - const server = http.createServer((_, res) => { + const server = http.createServer((req, res) => { + assert.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`) res.end('ok') }) - const pserver = http.createServer((req, res) => { - assert.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`) - res.end('proxy ok') + let proxyUsed = false + const pserver = http.createServer() + pserver.on('connect', (req, clientSocket, head) => { + proxyUsed = true + const [targetHost, targetPort] = req.url.split(':') + const serverSocket = net.connect(targetPort, targetHost, () => { + clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n') + serverSocket.write(head) + serverSocket.pipe(clientSocket) + clientSocket.pipe(serverSocket) + }) + clientSocket.on('error', () => serverSocket.destroy()) + serverSocket.on('error', () => clientSocket.destroy()) }) after(() => Promise.all([ @@ -96,7 +108,56 @@ describe('download', function () { } const url = `http://${host}:${port}` const res = await download(gyp, url) - assert.strictEqual(await res.text(), 'proxy ok') + assert.strictEqual(await res.text(), 'ok') + assert.strictEqual(proxyUsed, true) + }) + + it('download over https with proxy and custom ca', async function () { + const cafile = path.join(__dirname, 'fixtures/ca-proxy.crt') + await fs.writeFile(cafile, certs['ca.crt'], 'utf8') + + const server = https.createServer({ + ca: await readCAFile(cafile), + cert: certs['server.crt'], + key: certs['server.key'] + }, (_, res) => res.end('ok')) + + let proxyUsed = false + const pserver = http.createServer() + pserver.on('connect', (req, clientSocket, head) => { + proxyUsed = true + const [targetHost, targetPort] = req.url.split(':') + const serverSocket = net.connect(targetPort, targetHost, () => { + clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n') + serverSocket.write(head) + serverSocket.pipe(clientSocket) + clientSocket.pipe(serverSocket) + }) + clientSocket.on('error', () => serverSocket.destroy()) + serverSocket.on('error', () => clientSocket.destroy()) + }) + + after(async () => { + await new Promise((resolve) => server.close(resolve)) + await new Promise((resolve) => pserver.close(resolve)) + await fs.unlink(cafile) + }) + + const host = 'localhost' + await new Promise((resolve) => server.listen(0, host, resolve)) + const { port } = server.address() + await new Promise((resolve) => pserver.listen(port + 1, host, resolve)) + const gyp = { + opts: { + cafile, + proxy: `http://${host}:${port + 1}`, + noproxy: 'bad' + }, + version: '42' + } + const res = await download(gyp, `https://${host}:${port}`) + assert.strictEqual(await res.text(), 'ok') + assert.strictEqual(proxyUsed, true) }) it('download over http with noproxy', async function () { @@ -105,8 +166,11 @@ describe('download', function () { res.end('ok') }) - const pserver = http.createServer((_, res) => { - res.end('proxy ok') + let proxyUsed = false + const pserver = http.createServer() + pserver.on('connect', (_, socket) => { + proxyUsed = true + socket.destroy() }) after(() => Promise.all([ @@ -128,6 +192,7 @@ describe('download', function () { const url = `http://${host}:${port}` const res = await download(gyp, url) assert.strictEqual(await res.text(), 'ok') + assert.strictEqual(proxyUsed, false) }) it('download with missing cafile', async function () { From f4242fb7bf7592d71848bae5c7f8597f2718dc3f Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 21 Apr 2026 03:52:55 -0400 Subject: [PATCH 5/6] feat: update gyp-next to v0.22.1 (#3295) * feat: update gyp-next to v0.22.0 * feat: update gyp-next to v0.22.1 --- gyp/.github/workflows/python_tests.yml | 21 ++++++++---- gyp/.github/workflows/release-please.yml | 8 ++--- gyp/.gitignore | 4 +++ gyp/.release-please-manifest.json | 2 +- gyp/CHANGELOG.md | 19 +++++++++++ gyp/pylib/gyp/MSVSVersion.py | 14 ++++++-- gyp/pylib/gyp/__init__.py | 5 ++- gyp/pylib/gyp/generator/msvs.py | 2 +- gyp/pylib/gyp/generator/ninja.py | 3 +- gyp/pylib/gyp/generator/ninja_test.py | 42 +++++++++++++++--------- gyp/pylib/gyp/mac_tool.py | 2 +- gyp/pylib/gyp/msvs_emulation.py | 2 +- gyp/pylib/packaging/metadata.py | 23 ++----------- gyp/pylib/packaging/tags.py | 16 ++------- gyp/pyproject.toml | 8 +++-- 15 files changed, 96 insertions(+), 75 deletions(-) diff --git a/gyp/.github/workflows/python_tests.yml b/gyp/.github/workflows/python_tests.yml index 812d32d7f7..0d13e16387 100644 --- a/gyp/.github/workflows/python_tests.yml +++ b/gyp/.github/workflows/python_tests.yml @@ -1,4 +1,3 @@ -# TODO: Enable os: windows-latest # TODO: Enable pytest --doctest-modules name: Python_tests @@ -8,17 +7,28 @@ on: workflow_dispatch: jobs: + Python_lint: + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@v6 + - name: Lint with ruff # See pyproject.toml for settings + uses: astral-sh/ruff-action@v3 + - run: ruff format --check --diff + Python_tests: runs-on: ${{ matrix.os }} strategy: fail-fast: false - max-parallel: 5 matrix: - os: [macos-15-intel, macos-latest, ubuntu-latest] # , windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + os: [macos-15-intel, macos-latest, ubuntu-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] include: - os: macos-26 python-version: 3.x + - os: ubuntu-24.04-arm # Ubuntu on ARM + python-version: 3.x + - os: windows-11-arm # Windows on ARM + python-version: 3.x steps: - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} @@ -32,9 +42,6 @@ jobs: python -m pip install --upgrade pip pip install --editable ".[dev]" - run: ./gyp -V && ./gyp --version && gyp -V && gyp --version - - name: Lint with ruff # See pyproject.toml for settings - uses: astral-sh/ruff-action@v3 - - run: ruff format --check --diff - name: Test with pytest # See pyproject.toml for settings run: pytest # - name: Run doctests with pytest diff --git a/gyp/.github/workflows/release-please.yml b/gyp/.github/workflows/release-please.yml index 81f8626c77..0022a3d894 100644 --- a/gyp/.github/workflows/release-please.yml +++ b/gyp/.github/workflows/release-please.yml @@ -28,7 +28,7 @@ jobs: - name: Build a binary wheel and a source tarball run: pipx run build - name: Store the distribution packages - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: python-package-distributions path: dist/ @@ -48,7 +48,7 @@ jobs: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ @@ -68,12 +68,12 @@ jobs: id-token: write # IMPORTANT: mandatory for sigstore steps: - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v3.2.0 + uses: sigstore/gh-action-sigstore-python@v3.3.0 with: inputs: >- ./dist/*.tar.gz diff --git a/gyp/.gitignore b/gyp/.gitignore index 5f71dbd435..dcf3cb43ea 100644 --- a/gyp/.gitignore +++ b/gyp/.gitignore @@ -144,3 +144,7 @@ static test/fixtures/out *.actual +*.sln +*.vcproj +!test/fixtures/expected-win32/**/*.sln +!test/fixtures/expected-win32/**/*.vcproj diff --git a/gyp/.release-please-manifest.json b/gyp/.release-please-manifest.json index c825abab69..9242a4094d 100644 --- a/gyp/.release-please-manifest.json +++ b/gyp/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.21.1" + ".": "0.22.1" } diff --git a/gyp/CHANGELOG.md b/gyp/CHANGELOG.md index 9a14685447..c7ddd862d1 100644 --- a/gyp/CHANGELOG.md +++ b/gyp/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.22.1](https://github.com/nodejs/gyp-next/compare/v0.22.0...v0.22.1) (2026-04-21) + + +### Bug Fixes + +* use floor division when escaping command-line arguments ([#338](https://github.com/nodejs/gyp-next/issues/338)) ([cadca24](https://github.com/nodejs/gyp-next/commit/cadca2416dc13e117e035ad6dd2b471a86d0430f)) + +## [0.22.0](https://github.com/nodejs/gyp-next/compare/v0.21.1...v0.22.0) (2026-04-02) + + +### Features + +* Windows ARM64 target architecture support ([#331](https://github.com/nodejs/gyp-next/issues/331)) ([652a346](https://github.com/nodejs/gyp-next/commit/652a346bbd3b077a4b08a3c37d48100ce200758a)) + + +### Bug Fixes + +* drop deprecated Python module pkg_resources ([#333](https://github.com/nodejs/gyp-next/issues/333)) ([5b180d5](https://github.com/nodejs/gyp-next/commit/5b180d52d03aff062bdea1ad0209b82271c7eb4a)) + ## [0.21.1](https://github.com/nodejs/gyp-next/compare/v0.21.0...v0.21.1) (2026-01-24) diff --git a/gyp/pylib/gyp/MSVSVersion.py b/gyp/pylib/gyp/MSVSVersion.py index 2d8e4ceab9..02e6e7ed92 100644 --- a/gyp/pylib/gyp/MSVSVersion.py +++ b/gyp/pylib/gyp/MSVSVersion.py @@ -87,7 +87,7 @@ def DefaultToolset(self): def _SetupScriptInternal(self, target_arch): """Returns a command (with arguments) to be used to set up the environment.""" - assert target_arch in ("x86", "x64"), "target_arch not supported" + assert target_arch in ("x86", "x64", "arm64"), "target_arch not supported" # If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the # depot_tools build tools and should run SetEnv.Cmd to set up the # environment. The check for WindowsSDKDir alone is not sufficient because @@ -109,8 +109,16 @@ def _SetupScriptInternal(self, target_arch): ) # Always use a native executable, cross-compiling if necessary. - host_arch = "amd64" if is_host_arch_x64 else "x86" - msvc_target_arch = "amd64" if target_arch == "x64" else "x86" + host_arch = ( + "amd64" + if is_host_arch_x64 + else ( + "arm64" + if os.environ.get("PROCESSOR_ARCHITECTURE") == "ARM64" + else "x86" + ) + ) + msvc_target_arch = {"x64": "amd64"}.get(target_arch, target_arch) arg = host_arch if host_arch != msvc_target_arch: arg += "_" + msvc_target_arch diff --git a/gyp/pylib/gyp/__init__.py b/gyp/pylib/gyp/__init__.py index 3a70cf076c..c0a2637e94 100755 --- a/gyp/pylib/gyp/__init__.py +++ b/gyp/pylib/gyp/__init__.py @@ -13,6 +13,7 @@ import shlex import sys import traceback +from importlib.metadata import version import gyp.input from gyp.common import GypError @@ -491,9 +492,7 @@ def gyp_main(args): options, build_files_arg = parser.parse_args(args) if options.version: - import pkg_resources # noqa: PLC0415 - - print(f"v{pkg_resources.get_distribution('gyp-next').version}") + print(f"v{version('gyp-next')}") return 0 build_files = build_files_arg diff --git a/gyp/pylib/gyp/generator/msvs.py b/gyp/pylib/gyp/generator/msvs.py index 0f14c05504..42bee33d9b 100644 --- a/gyp/pylib/gyp/generator/msvs.py +++ b/gyp/pylib/gyp/generator/msvs.py @@ -857,7 +857,7 @@ def _EscapeCommandLineArgumentForMSBuild(s): """Escapes a Windows command-line argument for use by MSBuild.""" def _Replace(match): - return (len(match.group(1)) / 2 * 4) * "\\" + '\\"' + return (len(match.group(1)) // 2 * 4) * "\\" + '\\"' # Escape all quotes so that they are interpreted literally. s = quote_replacer_regex2.sub(_Replace, s) diff --git a/gyp/pylib/gyp/generator/ninja.py b/gyp/pylib/gyp/generator/ninja.py index 4eac6cdb27..3ceaf470ce 100644 --- a/gyp/pylib/gyp/generator/ninja.py +++ b/gyp/pylib/gyp/generator/ninja.py @@ -246,7 +246,7 @@ def __init__( if flavor == "win": # See docstring of msvs_emulation.GenerateEnvironmentFiles(). self.win_env = {} - for arch in ("x86", "x64"): + for arch in ("x86", "x64", "arm64"): self.win_env[arch] = "environment." + arch # Relative path from build output dir to base dir. @@ -2339,6 +2339,7 @@ def GenerateOutputForConfig(target_list, target_dicts, data, params, config_name master_ninja.variable("rc", "rc.exe") master_ninja.variable("ml_x86", "ml.exe") master_ninja.variable("ml_x64", "ml64.exe") + master_ninja.variable("ml_arm64", "armasm64.exe") master_ninja.variable("mt", "mt.exe") else: master_ninja.variable("ld", CommandWithWrapper("LINK", wrappers, ld)) diff --git a/gyp/pylib/gyp/generator/ninja_test.py b/gyp/pylib/gyp/generator/ninja_test.py index 616bc7aaf0..8b590af8eb 100644 --- a/gyp/pylib/gyp/generator/ninja_test.py +++ b/gyp/pylib/gyp/generator/ninja_test.py @@ -11,26 +11,36 @@ from pathlib import Path from gyp.generator import ninja +from gyp.MSVSVersion import SelectVisualStudioVersion + + +def _has_visual_studio(): + """Check if Visual Studio can be detected by gyp's registry-based detection.""" + if not sys.platform.startswith("win"): + return False + try: + SelectVisualStudioVersion("auto", allow_fallback=False) + return True + except ValueError: + return False class TestPrefixesAndSuffixes(unittest.TestCase): + @unittest.skipUnless( + _has_visual_studio(), + "requires Windows with a Visual Studio installation detected via the registry", + ) def test_BinaryNamesWindows(self): - # These cannot run on non-Windows as they require a VS installation to - # correctly handle variable expansion. - if sys.platform.startswith("win"): - writer = ninja.NinjaWriter( - "foo", "wee", ".", ".", "build.ninja", ".", "build.ninja", "win" - ) - spec = {"target_name": "wee"} - self.assertTrue( - writer.ComputeOutputFileName(spec, "executable").endswith(".exe") - ) - self.assertTrue( - writer.ComputeOutputFileName(spec, "shared_library").endswith(".dll") - ) - self.assertTrue( - writer.ComputeOutputFileName(spec, "static_library").endswith(".lib") - ) + writer = ninja.NinjaWriter( + "foo", "wee", ".", ".", "build.ninja", ".", "build.ninja", "win" + ) + spec = {"target_name": "wee"} + for key, ext in { + "executable": ".exe", + "shared_library": ".dll", + "static_library": ".lib", + }.items(): + self.assertTrue(writer.ComputeOutputFileName(spec, key).endswith(ext)) def test_BinaryNamesLinux(self): writer = ninja.NinjaWriter( diff --git a/gyp/pylib/gyp/mac_tool.py b/gyp/pylib/gyp/mac_tool.py index 3710178e11..4c38f0586c 100755 --- a/gyp/pylib/gyp/mac_tool.py +++ b/gyp/pylib/gyp/mac_tool.py @@ -545,7 +545,7 @@ def _FindProvisioningProfile(self, profile, bundle_identifier): # If the user has multiple provisioning profiles installed that can be # used for ${bundle_identifier}, pick the most specific one (ie. the # provisioning profile whose pattern is the longest). - selected_key = max(valid_provisioning_profiles, key=lambda v: len(v)) + selected_key = max(valid_provisioning_profiles, key=len) return valid_provisioning_profiles[selected_key] def _LoadProvisioningProfile(self, profile_path): diff --git a/gyp/pylib/gyp/msvs_emulation.py b/gyp/pylib/gyp/msvs_emulation.py index 7c461a8fdf..f1c1581981 100644 --- a/gyp/pylib/gyp/msvs_emulation.py +++ b/gyp/pylib/gyp/msvs_emulation.py @@ -1174,7 +1174,7 @@ def GenerateEnvironmentFiles( meet your requirement (e.g. for custom toolchains), you can pass "-G ninja_use_custom_environment_files" to the gyp to suppress file generation and use custom environment files prepared by yourself.""" - archs = ("x86", "x64") + archs = ("x86", "x64", "arm64") if generator_flags.get("ninja_use_custom_environment_files", 0): cl_paths = {} for arch in archs: diff --git a/gyp/pylib/packaging/metadata.py b/gyp/pylib/packaging/metadata.py index 43f5c5b30d..38fa645b4c 100644 --- a/gyp/pylib/packaging/metadata.py +++ b/gyp/pylib/packaging/metadata.py @@ -21,27 +21,10 @@ from . import requirements, specifiers, utils, version as version_module T = typing.TypeVar("T") -if sys.version_info[:2] >= (3, 8): # pragma: no cover - from typing import Literal, TypedDict -else: # pragma: no cover - if typing.TYPE_CHECKING: - from typing_extensions import Literal, TypedDict - else: - try: - from typing_extensions import Literal, TypedDict - except ImportError: - - class Literal: - def __init_subclass__(*_args, **_kwargs): - pass - - class TypedDict: - def __init_subclass__(*_args, **_kwargs): - pass - +from typing import Literal, TypedDict try: - ExceptionGroup + ExceptionGroup # Added in Python 3.11+ except NameError: # pragma: no cover class ExceptionGroup(Exception): # noqa: N818 @@ -504,7 +487,7 @@ def __set_name__(self, _owner: "Metadata", name: str) -> None: self.raw_name = _RAW_TO_EMAIL_MAPPING[name] def __get__(self, instance: "Metadata", _owner: Type["Metadata"]) -> T: - # With Python 3.8, the caching can be replaced with functools.cached_property(). + # With Python 3.8+, the caching can be replaced with functools.cached_property(). # No need to check the cache as attribute lookup will resolve into the # instance's __dict__ before __get__ is called. cache = instance.__dict__ diff --git a/gyp/pylib/packaging/tags.py b/gyp/pylib/packaging/tags.py index 37f33b1ef8..f1da2b96d3 100644 --- a/gyp/pylib/packaging/tags.py +++ b/gyp/pylib/packaging/tags.py @@ -127,10 +127,8 @@ def _normalize_string(string: str) -> str: def _abi3_applies(python_version: PythonVersion) -> bool: """ Determine if the Python version supports abi3. - - PEP 384 was first implemented in Python 3.2. """ - return len(python_version) > 1 and tuple(python_version) >= (3, 2) + return len(python_version) > 1 def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: @@ -146,17 +144,7 @@ def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: has_ext = "_d.pyd" in EXTENSION_SUFFIXES if with_debug or (with_debug is None and (has_refcount or has_ext)): debug = "d" - if py_version < (3, 8): - with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) - if with_pymalloc or with_pymalloc is None: - pymalloc = "m" - if py_version < (3, 3): - unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) - if unicode_size == 4 or ( - unicode_size is None and sys.maxunicode == 0x10FFFF - ): - ucs4 = "u" - elif debug: + if debug: # Debug builds can also load "normal" extension modules. # We can also assume no UCS-4 or pymalloc requirement. abis.append(f"cp{version}") diff --git a/gyp/pyproject.toml b/gyp/pyproject.toml index fa30c8cf96..239bef7844 100644 --- a/gyp/pyproject.toml +++ b/gyp/pyproject.toml @@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta" [project] name = "gyp-next" -version = "0.21.1" +version = "0.22.1" authors = [ { name="Node.js contributors", email="ryzokuken@disroot.org" }, ] description = "A fork of the GYP build system for use in the Node.js projects" readme = "README.md" license = { file="LICENSE" } -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = ["packaging>=24.0", "setuptools>=69.5.1"] classifiers = [ "Development Status :: 3 - Alpha", @@ -21,10 +21,12 @@ classifiers = [ "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] [project.optional-dependencies] From 154a27ef069deafc8f680f3f77452e76343546f8 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Tue, 21 Apr 2026 04:19:19 -0400 Subject: [PATCH 6/6] chore(main): release 12.3.0 (#3277) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 41c2c8154a..af7e1d8db1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "12.2.0" + ".": "12.3.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a83b4dfcd..1b02c2184a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [12.3.0](https://github.com/nodejs/node-gyp/compare/v12.2.0...v12.3.0) (2026-04-21) + + +### Features + +* replace make-fetch-happen with built-in fetch ([#3302](https://github.com/nodejs/node-gyp/issues/3302)) ([393ec2b](https://github.com/nodejs/node-gyp/commit/393ec2be480195b585768eb18ac2d92b858b12db)) +* update gyp-next to v0.22.1 ([#3295](https://github.com/nodejs/node-gyp/issues/3295)) ([f4242fb](https://github.com/nodejs/node-gyp/commit/f4242fb7bf7592d71848bae5c7f8597f2718dc3f)) + + +### Bug Fixes + +* Switch to URL instead of url.resolve ([#3256](https://github.com/nodejs/node-gyp/issues/3256)) ([#3263](https://github.com/nodejs/node-gyp/issues/3263)) ([46d7576](https://github.com/nodejs/node-gyp/commit/46d75768018d10dc4b5693b35cf58f502425cbbe)) + + +### Core + +* **deps-dev:** bump neostandard from 0.12.2 to 0.13.0 ([#3289](https://github.com/nodejs/node-gyp/issues/3289)) ([19da158](https://github.com/nodejs/node-gyp/commit/19da1583b3876dce8c97263b168d9dfef637b76b)) +* improve Add-Type with -IgnoreWarnings ([#3280](https://github.com/nodejs/node-gyp/issues/3280)) ([1381458](https://github.com/nodejs/node-gyp/commit/13814583a476c85a84d5ac902c7ffa310120cb88)) + ## [12.2.0](https://github.com/nodejs/node-gyp/compare/v12.1.0...v12.2.0) (2026-01-26) diff --git a/package.json b/package.json index 92807b96aa..29d95ad41b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "bindings", "gyp" ], - "version": "12.2.0", + "version": "12.3.0", "installVersion": 11, "author": "Nathan Rajlich (http://tootallnate.net)", "repository": {