Skip to content

Commit 659f11c

Browse files
authored
Register LLVM component for LLDB-MI (#14704)
1 parent 3202675 commit 659f11c

5 files changed

Lines changed: 201 additions & 7 deletions

File tree

.github/workflows/job-compile-and-test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ jobs:
3838
run: yarn test-yarn-lock && yarn verify-yarn-lock
3939
working-directory: Extension
4040

41+
- name: Validate LLDB-MI component manifest
42+
run: yarn test-lldb-mi-component-manifest && yarn verify-lldb-mi-component-manifest
43+
working-directory: Extension
44+
4145
- name: Install Dependencies
4246
run: yarn install ${{ inputs.yarn-args }}
4347
working-directory: Extension
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import process from 'node:process';
4+
import { fileURLToPath, pathToFileURL, URL } from 'node:url';
5+
6+
const componentParameters = [
7+
{ repository: 'llvm_repo', commit: 'llvm_commit' },
8+
{ repository: 'lldb_mi_repo', commit: 'lldb_mi_commit' }
9+
];
10+
11+
function parseScalarParameter(template, parameterName) {
12+
const matches = [...template.matchAll(new RegExp(`^ ${parameterName}:\\s*(.*?)\\s*$`, 'gm'))];
13+
if (matches.length !== 1) {
14+
throw new Error(`Expected ${parameterName} to appear exactly once in the LLDB-MI template.`);
15+
}
16+
17+
const serializedValue = matches[0][1];
18+
if (serializedValue.startsWith('"')) {
19+
const value = JSON.parse(serializedValue);
20+
if (typeof value !== 'string') {
21+
throw new Error(`Expected ${parameterName} to be a string.`);
22+
}
23+
return value;
24+
}
25+
if (serializedValue.startsWith("'")) {
26+
if (!serializedValue.endsWith("'")) {
27+
throw new Error(`Expected ${parameterName} to be a valid scalar value.`);
28+
}
29+
return serializedValue.slice(1, -1).replaceAll("''", "'");
30+
}
31+
if (!serializedValue || /\s/.test(serializedValue)) {
32+
throw new Error(`Expected ${parameterName} to be a non-empty scalar value.`);
33+
}
34+
return serializedValue;
35+
}
36+
37+
function normalizeRepositoryUrl(repositoryUrl) {
38+
const normalizedUrl = new URL(repositoryUrl);
39+
normalizedUrl.hash = '';
40+
normalizedUrl.search = '';
41+
normalizedUrl.pathname = normalizedUrl.pathname.replace(/\/+$/, '').replace(/\.git$/i, '');
42+
return normalizedUrl.href.replace(/\/$/, '');
43+
}
44+
45+
function getGitRegistrations(manifest) {
46+
if (!Array.isArray(manifest.registrations)) {
47+
throw new Error('Component manifest does not contain a registrations array.');
48+
}
49+
50+
const registrations = new Map();
51+
for (const registration of manifest.registrations) {
52+
const component = registration?.component;
53+
if (component?.type !== 'git') {
54+
continue;
55+
}
56+
57+
const repositoryUrl = component.git?.repositoryUrl;
58+
const commitHash = component.git?.commitHash;
59+
if (typeof repositoryUrl !== 'string' || typeof commitHash !== 'string') {
60+
throw new Error('Git component registrations require repositoryUrl and commitHash strings.');
61+
}
62+
63+
const normalizedRepositoryUrl = normalizeRepositoryUrl(repositoryUrl);
64+
if (registrations.has(normalizedRepositoryUrl)) {
65+
throw new Error(`Component manifest contains duplicate registrations for ${repositoryUrl}.`);
66+
}
67+
registrations.set(normalizedRepositoryUrl, { repositoryUrl, commitHash });
68+
}
69+
return registrations;
70+
}
71+
72+
function validateLldbMiComponentManifest(template, manifest) {
73+
const registrations = getGitRegistrations(manifest);
74+
const errors = [];
75+
76+
for (const parameters of componentParameters) {
77+
const repositoryUrl = parseScalarParameter(template, parameters.repository);
78+
const commitHash = parseScalarParameter(template, parameters.commit);
79+
if (!/^[0-9a-f]{40}$/.test(commitHash)) {
80+
errors.push(`${parameters.commit} must be a 40-character lowercase Git commit hash.`);
81+
continue;
82+
}
83+
84+
const registration = registrations.get(normalizeRepositoryUrl(repositoryUrl));
85+
if (!registration) {
86+
errors.push(`${parameters.repository} references ${repositoryUrl}, which is missing from the component manifest.`);
87+
} else if (registration.commitHash !== commitHash) {
88+
errors.push(`${parameters.commit} is ${commitHash}, but the component manifest registers ${registration.commitHash} for ${registration.repositoryUrl}.`);
89+
}
90+
}
91+
92+
if (errors.length > 0) {
93+
throw new Error(`LLDB-MI component manifest validation failed:\n${errors.map(error => ` ${error}`).join('\n')}`);
94+
}
95+
}
96+
97+
const invokedUrl = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : undefined;
98+
if (invokedUrl === import.meta.url) {
99+
const extensionRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
100+
const manifestPath = process.argv[2] ?? path.join(extensionRoot, 'cgmanifest.json');
101+
const templatePath = process.argv[3] ?? path.join(extensionRoot, '..', 'Build', 'lldb-mi', 'lldb-mi.template.yml');
102+
103+
try {
104+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
105+
const template = fs.readFileSync(templatePath, 'utf8');
106+
validateLldbMiComponentManifest(template, manifest);
107+
} catch (error) {
108+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
109+
process.exitCode = 1;
110+
}
111+
}
112+
113+
export { normalizeRepositoryUrl, parseScalarParameter, validateLldbMiComponentManifest };
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import assert from 'node:assert/strict';
2+
import test from 'node:test';
3+
import { parseScalarParameter, validateLldbMiComponentManifest } from './verifyLldbMiComponentManifest.mjs';
4+
5+
const template = `parameters:
6+
llvm_repo: https://github.com/llvm/llvm-project.git
7+
llvm_commit: 0d44201451f03ba907cdb268ddddfc3fa38a0ebd
8+
lldb_mi_repo: https://github.com/lldb-tools/lldb-mi.git
9+
lldb_mi_commit: 2388bd74133bc21eac59b2e2bf97f2a30770a315
10+
11+
jobs:
12+
`;
13+
14+
function createManifest(llvmCommit = '0d44201451f03ba907cdb268ddddfc3fa38a0ebd') {
15+
return {
16+
registrations: [
17+
{
18+
component: {
19+
type: 'git',
20+
git: {
21+
repositoryUrl: 'https://github.com/lldb-tools/lldb-mi',
22+
commitHash: '2388bd74133bc21eac59b2e2bf97f2a30770a315'
23+
}
24+
}
25+
},
26+
{
27+
component: {
28+
type: 'git',
29+
git: {
30+
repositoryUrl: 'https://github.com/llvm/llvm-project',
31+
commitHash: llvmCommit
32+
}
33+
}
34+
}
35+
]
36+
};
37+
}
38+
39+
test('accepts matching build and component manifest pins', () => {
40+
assert.doesNotThrow(() => validateLldbMiComponentManifest(template, createManifest()));
41+
});
42+
43+
test('reports a missing repository registration', () => {
44+
const manifest = createManifest();
45+
manifest.registrations.pop();
46+
47+
assert.throws(
48+
() => validateLldbMiComponentManifest(template, manifest),
49+
/llvm_repo references https:\/\/github\.com\/llvm\/llvm-project\.git, which is missing/
50+
);
51+
});
52+
53+
test('reports a stale registered commit', () => {
54+
assert.throws(
55+
() => validateLldbMiComponentManifest(template, createManifest('1111111111111111111111111111111111111111')),
56+
/llvm_commit is 0d44201451f03ba907cdb268ddddfc3fa38a0ebd, but the component manifest registers 1111111111111111111111111111111111111111/
57+
);
58+
});
59+
60+
test('requires each build parameter exactly once', () => {
61+
assert.throws(
62+
() => parseScalarParameter(`${template} llvm_repo: https://example.com/duplicate.git\n`, 'llvm_repo'),
63+
/Expected llvm_repo to appear exactly once/
64+
);
65+
});

Extension/cgmanifest.json

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
{
22
"$schema": "https://json.schemastore.org/component-detection-manifest.json",
3-
"Registrations": [
3+
"registrations": [
44
{
5-
"Component": {
6-
"Type": "git",
7-
"Git": {
8-
"RepositoryUrl": "https://github.com/lldb-tools/lldb-mi",
9-
"CommitHash": "2388bd74133bc21eac59b2e2bf97f2a30770a315"
5+
"component": {
6+
"type": "git",
7+
"git": {
8+
"repositoryUrl": "https://github.com/lldb-tools/lldb-mi",
9+
"commitHash": "2388bd74133bc21eac59b2e2bf97f2a30770a315"
1010
}
1111
}
12+
},
13+
{
14+
"component": {
15+
"type": "git",
16+
"git": {
17+
"repositoryUrl": "https://github.com/llvm/llvm-project",
18+
"commitHash": "0d44201451f03ba907cdb268ddddfc3fa38a0ebd"
19+
}
20+
},
21+
"developmentDependency": true
1222
}
1323
],
14-
"Version": 1
24+
"version": 1
1525
}

Extension/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7163,7 +7163,9 @@
71637163
"scripts": "ts-node -T .scripts/scripts.ts",
71647164
"show": "ts-node -T .scripts/clean.ts show",
71657165
"clean": "ts-node -T .scripts/clean.ts",
7166+
"test-lldb-mi-component-manifest": "node --test .scripts/verifyLldbMiComponentManifest.test.mjs",
71667167
"test-yarn-lock": "node --test .scripts/verifyYarnLock.test.mjs",
7168+
"verify-lldb-mi-component-manifest": "node .scripts/verifyLldbMiComponentManifest.mjs",
71677169
"verify-yarn-lock": "node .scripts/verifyYarnLock.mjs",
71687170
"test": "yarn install && (yarn verify prep --quiet || yarn prep) && (yarn verify compiled --quiet || yarn build) && ts-node -T .scripts/test.ts",
71697171
"code": "yarn install && (yarn verify compiled --quiet || yarn build) && yarn verify binaries && ts-node -T .scripts/code.ts",

0 commit comments

Comments
 (0)