Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper scripts for updating headers and symbols.js #7

Merged
merged 10 commits into from
Feb 15, 2023
Merged
52 changes: 52 additions & 0 deletions .github/workflows/sync-headers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Header Sync

on:
workflow_dispatch: null
schedule:
- cron: "0 0 * * *"

permissions:
contents: write
pull-requests: write

jobs:
build:
runs-on: ubuntu-latest
name: Update headers from nodejs/node
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- shell: bash
id: check-changes
name: Check Changes
run: |
COMMIT_MESSAGE=$(npm run --silent update-headers)
VERSION=${COMMIT_MESSAGE##* }
echo $COMMIT_MESSAGE
npm run --silent write-symbols
CHANGED_FILES=$(git diff --name-only)
BRANCH_NAME="update-headers/${VERSION}"
if [ -z "$CHANGED_FILES" ]; then
echo "No changes exist. Nothing to do."
else
echo "Changes exist. Checking if branch exists: $BRANCH_NAME"
if git ls-remote --exit-code --heads $GITHUB_SERVER_URL/$GITHUB_REPOSITORY $BRANCH_NAME >/dev/null; then
echo "Branch exists. Nothing to do."
else
echo "Branch does not exists."
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "COMMIT_MESSAGE=$COMMIT_MESSAGE" >> $GITHUB_OUTPUT
fi
fi
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
if: ${{ steps.check-changes.outputs.BRANCH_NAME }}
with:
branch: ${{ steps.check-changes.outputs.BRANCH_NAME }}
commit-message: ${{ steps.check-changes.outputs.COMMIT_MESSAGE }}
title: ${{ steps.check-changes.outputs.COMMIT_MESSAGE }}
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
body: null
delete-branch: true
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
scripts/
.github/
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
"url": "git://github.com/nodejs/node-api-headers.git"
},
"scripts": {
"update-headers": "node --no-warnings scripts/update-headers.js",
"write-symbols": "node --no-warnings scripts/write-symbols.js"
},
"version": "0.0.2",
"support": true
Expand Down
55 changes: 55 additions & 0 deletions scripts/update-headers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

const { createWriteStream } = require('fs');
const { Readable } = require('stream');
const { finished } = require('stream/promises');
const { resolve } = require('path');
const { parseArgs } = require('util')

async function getLatestReleaseVersion() {
const response = await fetch('https://nodejs.org/download/release/index.json');
const json = await response.json();
return json[0].version;
}

async function main() {
const { values: { tag, verbose } } = parseArgs({
options: {
tag: {
type: "string",
short: "t",
default: await getLatestReleaseVersion()
},
verbose: {
type: "boolean",
short: "v",
},
},
});

console.log(`Update headers from nodejs/node tag ${tag}`);

const files = ['js_native_api_types.h', 'js_native_api.h', 'node_api_types.h', 'node_api.h'];

for (const filename of files) {
const url = `https://raw.githubusercontent.com/nodejs/node/${tag}/src/${filename}`;
const path = resolve(__dirname, '..', 'include', filename);

if (verbose) {
console.log(` ${url} -> ${path}`);
}

const response = await fetch(url);
if (!response.ok) {
throw new Error(`Fetch of ${url} returned ${response.status} ${response.statusText}`);
}

const stream = createWriteStream(path);
await finished(Readable.fromWeb(response.body).pipe(stream));
}
}

main().catch(e => {
console.error(e);
process.exitCode = 1;
});
180 changes: 180 additions & 0 deletions scripts/write-symbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
'use strict';

const { spawn } = require('child_process');
const { resolve: resolvePath } = require('path');
const { writeFile } = require('fs/promises');

/** @typedef {{ js_native_api_symbols: string[]; node_api_symbols: string[]; }} SymbolInfo */

/**
* @param {number} [version]
* @returns {Promise<SymbolInfo>}
*/
async function getSymbolsForVersion(version) {
try {
const { exitCode, stdout, stderr } = await new Promise((resolve, reject) => {
const spawned = spawn('clang',
['-Xclang', '-ast-dump=json', '-fsyntax-only', '-fno-diagnostics-color', `-DNAPI_VERSION=${version}`, resolvePath(__dirname, '..', 'include', 'node_api.h')]
);

let stdout = '';
let stderr = '';

spawned.stdout?.on('data', (data) => {
stdout += data.toString('utf-8');
});
spawned.stderr?.on('data', (data) => {
stderr += data.toString('utf-8');
});

spawned.on('exit', function (exitCode) {
resolve({ exitCode, stdout, stderr });
});

spawned.on('error', function (err) {
reject(err);
});
});

if (exitCode !== 0) {
throw new Error(`clang exited with non-zero exit code ${exitCode}. stderr: ${stderr ? stderr : '<empty>'}`);
}

const ast = JSON.parse(stdout);

/** @type {SymbolInfo} */
const symbols = { js_native_api_symbols: [], node_api_symbols: [] };

for (const statement of ast.inner) {
if (statement.kind !== 'FunctionDecl') {
continue;
}

const name = statement.name;
const file = statement.loc.includedFrom?.file;

if (file) {
symbols.js_native_api_symbols.push(name);
} else {
symbols.node_api_symbols.push(name);
}
}

symbols.js_native_api_symbols.sort();
symbols.node_api_symbols.sort();

return symbols;
} catch (err) {
if (err.code === 'ENOENT') {
throw new Error('This tool requires clang to be installed.');
}
throw err;
}
}

/** @returns {Promise<{maxVersion: number, symbols: {[x: string]: SymbolInfo}}>} */
async function getAllSymbols() {
/** @type {{[x: string]: SymbolInfo}} */
const allSymbols = {};
let version = 1;

console.log('Processing symbols from clang:')
while (true) {
const symbols = await getSymbolsForVersion(version);

if (version > 1) {
const previousSymbols = allSymbols[`v${version - 1}`];
if (previousSymbols.js_native_api_symbols.length == symbols.js_native_api_symbols.length && previousSymbols.node_api_symbols.length === symbols.node_api_symbols.length) {
--version;
break;
}
}
allSymbols[`v${version}`] = symbols;
console.log(` v${version}: ${symbols.js_native_api_symbols.length} js_native_api_symbols, ${symbols.node_api_symbols.length} node_api_symbols`);
++version;
}

return {
maxVersion: version,
symbols: allSymbols
};
}

/**
* @param {SymbolInfo} previousSymbols
* @param {SymbolInfo} currentSymbols
* @returns {SymbolInfo}
*/
function getUniqueSymbols(previousSymbols, currentSymbols) {
/** @type {SymbolInfo} */
const symbols = { js_native_api_symbols: [], node_api_symbols: [] };
for (const symbol of currentSymbols.js_native_api_symbols) {
if (!previousSymbols.js_native_api_symbols.includes(symbol)) {
symbols.js_native_api_symbols.push(symbol);
}
}
for (const symbol of currentSymbols.node_api_symbols) {
if (!previousSymbols.node_api_symbols.includes(symbol)) {
symbols.node_api_symbols.push(symbol);
}
}
return symbols;
}

/**
* @param {string[]} strings
*/
function joinStrings(strings, prependNewLine = false) {
if (strings.length === 0) return '';
return `${prependNewLine ? ',\n ' : ''}'${strings.join("',\n '")}'`;
}

async function getSymbolData() {
const { maxVersion, symbols } = await getAllSymbols();

let data = `'use strict'

const v1 = {
js_native_api_symbols: [
${joinStrings(symbols.v1.js_native_api_symbols)}
],
node_api_symbols: [
${joinStrings(symbols.v1.node_api_symbols)}
]
}
`;

for (let version = 2; version <= maxVersion; ++version) {
const newSymbols = getUniqueSymbols(symbols[`v${version - 1}`], symbols[`v${version}`]);

data += `
const ${`v${version}`} = {
js_native_api_symbols: [
...v${version - 1}.js_native_api_symbols${joinStrings(newSymbols.js_native_api_symbols, true)}
],
node_api_symbols: [
...v${version - 1}.node_api_symbols${joinStrings(newSymbols.node_api_symbols, true)}
]
}
`;
}

data += `
module.exports = {
${new Array(maxVersion).fill(undefined).map((_, i) => `v${i + 1}`).join(',\n ')}
}
`
return data;
}

async function main() {
const path = resolvePath(__dirname, '../symbols.js');
const data = await getSymbolData();
console.log(`Writing symbols to ${path}`)
return writeFile(path, data);
}

main().catch(e => {
console.error(e);
process.exitCode = 1;
});