Skip to content

Commit 725fad0

Browse files
committed
Store language aliases from linked CLI
1 parent b623f5f commit 725fad0

File tree

3 files changed

+189
-8
lines changed

3 files changed

+189
-8
lines changed

.github/actions/update-bundle/index.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import * as fs from 'fs';
21
import * as github from '@actions/github';
2+
import * as fs from 'fs';
3+
import * as path from 'path';
4+
5+
import {
6+
assertSupportedPlatform,
7+
updateKnownLanguageAliasesFromRelease,
8+
ReleaseInfo,
9+
} from './language-aliases';
310

411
interface BundleInfo {
512
bundleVersion: string;
@@ -13,14 +20,15 @@ interface Defaults {
1320
priorCliVersion: string;
1421
}
1522

16-
function getCodeQLCliVersionForRelease(release): string {
23+
const DEFAULTS_PATH = path.resolve(__dirname, '../../../src/defaults.json');
24+
25+
function getCodeQLCliVersionForRelease(release: ReleaseInfo): string {
1726
// We do not currently tag CodeQL bundles based on the CLI version they contain.
1827
// Instead, we use a marker file `cli-version-<version>.txt` to record the CLI version.
1928
// This marker file is uploaded as a release asset for all new CodeQL bundles.
2029
const cliVersionsFromMarkerFiles = release.assets
2130
.map((asset) => asset.name.match(/cli-version-(.*)\.txt/)?.[1])
22-
.filter((v) => v)
23-
.map((v) => v as string);
31+
.filter((v): v is string => typeof v === 'string');
2432
if (cliVersionsFromMarkerFiles.length > 1) {
2533
throw new Error(
2634
`Release ${release.tag_name} has multiple CLI version marker files.`
@@ -33,15 +41,15 @@ function getCodeQLCliVersionForRelease(release): string {
3341
return cliVersionsFromMarkerFiles[0];
3442
}
3543

36-
async function getBundleInfoFromRelease(release): Promise<BundleInfo> {
44+
async function getBundleInfoFromRelease(release: ReleaseInfo): Promise<BundleInfo> {
3745
return {
3846
bundleVersion: release.tag_name,
3947
cliVersion: getCodeQLCliVersionForRelease(release)
4048
};
4149
}
4250

4351
async function getNewDefaults(currentDefaults: Defaults): Promise<Defaults> {
44-
const release = github.context.payload.release;
52+
const release = github.context.payload.release as ReleaseInfo;
4553
console.log('Updating default bundle as a result of the following release: ' +
4654
`${JSON.stringify(release)}.`)
4755

@@ -55,11 +63,16 @@ async function getNewDefaults(currentDefaults: Defaults): Promise<Defaults> {
5563
}
5664

5765
async function main() {
58-
const previousDefaults: Defaults = JSON.parse(fs.readFileSync('../../../src/defaults.json', 'utf8'));
66+
assertSupportedPlatform();
67+
const previousDefaults: Defaults = JSON.parse(fs.readFileSync(DEFAULTS_PATH, 'utf8'));
5968
const newDefaults = await getNewDefaults(previousDefaults);
69+
const release = github.context.payload.release as ReleaseInfo;
6070
// Update the source file in the repository. Calling workflows should subsequently rebuild
6171
// the Action to update `lib/defaults.json`.
62-
fs.writeFileSync('../../../src/defaults.json', JSON.stringify(newDefaults, null, 2) + "\n");
72+
fs.writeFileSync(DEFAULTS_PATH, JSON.stringify(newDefaults, null, 2) + "\n");
73+
74+
// Keep the checked-in language aliases aligned with the released bundle.
75+
await updateKnownLanguageAliasesFromRelease(release);
6376
}
6477

6578
// Ideally, we'd await main() here, but that doesn't work well with `ts-node`.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { execFileSync } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
6+
export interface ReleaseAsset {
7+
name: string;
8+
browser_download_url?: string;
9+
}
10+
11+
export interface ReleaseInfo {
12+
tag_name: string;
13+
assets: ReleaseAsset[];
14+
}
15+
16+
interface BetterResolveLanguagesOutput {
17+
aliases?: {
18+
[alias: string]: string;
19+
};
20+
}
21+
22+
const KNOWN_LANGUAGE_ALIASES_PATH = path.resolve(
23+
__dirname,
24+
'../../../src/known-language-aliases.json'
25+
);
26+
27+
export function assertSupportedPlatform(
28+
platform: NodeJS.Platform = process.platform
29+
): void {
30+
if (platform !== 'linux' && platform !== 'darwin') {
31+
throw new Error(
32+
`The update-bundle script must run on Linux or macOS. Current platform: ${platform}.`
33+
);
34+
}
35+
}
36+
37+
export function sortAliases(aliases: Record<string, string>): Record<string, string> {
38+
return Object.fromEntries(
39+
Object.entries(aliases).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
40+
);
41+
}
42+
43+
export function getBundleArchiveDownloadUrl(release: ReleaseInfo): string {
44+
const bundleAssetName =
45+
process.platform === 'darwin'
46+
? 'codeql-bundle-osx64.tar.gz'
47+
: 'codeql-bundle-linux64.tar.gz';
48+
49+
const bundleAsset = release.assets.find(
50+
(asset: ReleaseAsset) => asset.name === bundleAssetName
51+
);
52+
53+
if (!bundleAsset?.browser_download_url) {
54+
throw new Error(
55+
`Failed to find ${bundleAssetName} for release ${release.tag_name}.`
56+
);
57+
}
58+
59+
return bundleAsset.browser_download_url;
60+
}
61+
62+
async function downloadBundleArchive(
63+
bundleArchiveUrl: string,
64+
outPath: string
65+
): Promise<void> {
66+
console.log(`Downloading CodeQL bundle from ${bundleArchiveUrl}`);
67+
const startedAt = Date.now();
68+
const response = await fetch(bundleArchiveUrl, {
69+
redirect: 'follow',
70+
});
71+
72+
if (!response.ok) {
73+
throw new Error(
74+
`Failed to download CodeQL bundle archive from ${bundleArchiveUrl}: ` +
75+
`${response.status} ${response.statusText}`
76+
);
77+
}
78+
79+
if (!response.body) {
80+
throw new Error(
81+
`Response body is null when downloading CodeQL bundle archive from ${bundleArchiveUrl}.`
82+
);
83+
}
84+
85+
const fileStream = fs.createWriteStream(outPath);
86+
// Stream the response body directly to disk to avoid buffering the entire
87+
// archive in memory.
88+
const reader = response.body.getReader();
89+
let totalBytes = 0;
90+
try {
91+
for (;;) {
92+
const { done, value } = await reader.read();
93+
if (done) break;
94+
fileStream.write(value);
95+
totalBytes += value.byteLength;
96+
}
97+
} finally {
98+
fileStream.end();
99+
await new Promise<void>((resolve, reject) => {
100+
fileStream.on('finish', resolve);
101+
fileStream.on('error', reject);
102+
});
103+
}
104+
105+
const elapsedSeconds = ((Date.now() - startedAt) / 1000).toFixed(1);
106+
const sizeMb = (totalBytes / (1024 * 1024)).toFixed(1);
107+
console.log(
108+
`Download complete (${sizeMb} MB in ${elapsedSeconds}s)`
109+
);
110+
}
111+
112+
function writeKnownLanguageAliases(aliases: Record<string, string>) {
113+
fs.writeFileSync(
114+
KNOWN_LANGUAGE_ALIASES_PATH,
115+
JSON.stringify(sortAliases(aliases), null, 2) + '\n'
116+
);
117+
}
118+
119+
export async function updateKnownLanguageAliasesFromRelease(
120+
release: ReleaseInfo
121+
): Promise<void> {
122+
console.log(
123+
`Updating known language aliases from ${release.tag_name}`
124+
);
125+
const bundleArchiveUrl = getBundleArchiveDownloadUrl(release);
126+
const bundleAssetName =
127+
process.platform === 'darwin'
128+
? 'codeql-bundle-osx64.tar.gz'
129+
: 'codeql-bundle-linux64.tar.gz';
130+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codeql-bundle-'));
131+
const archivePath = path.join(tempDir, bundleAssetName);
132+
const extractDir = path.join(tempDir, 'bundle');
133+
fs.mkdirSync(extractDir);
134+
135+
try {
136+
await downloadBundleArchive(bundleArchiveUrl, archivePath);
137+
console.log('Extracting CodeQL bundle archive');
138+
execFileSync('tar', ['-xzf', archivePath, '-C', extractDir], {
139+
stdio: 'inherit',
140+
});
141+
142+
const codeqlCliPath = path.join(extractDir, 'codeql', 'codeql');
143+
console.log('Resolving language aliases using bundled CodeQL CLI');
144+
const output = execFileSync(
145+
codeqlCliPath,
146+
['resolve', 'languages', '--format=betterjson', '--extractor-include-aliases'],
147+
{ encoding: 'utf8' }
148+
);
149+
150+
const resolvedLanguages = JSON.parse(output) as BetterResolveLanguagesOutput;
151+
writeKnownLanguageAliases(resolvedLanguages.aliases ?? {});
152+
console.log('Wrote src/known-language-aliases.json');
153+
} finally {
154+
console.log(`Cleaning up temporary files in ${tempDir}`);
155+
fs.rmSync(tempDir, { recursive: true, force: true });
156+
}
157+
}

src/known-language-aliases.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"c": "cpp",
3+
"c-c++": "cpp",
4+
"c-cpp": "cpp",
5+
"c#": "csharp",
6+
"c++": "cpp",
7+
"java-kotlin": "java",
8+
"javascript-typescript": "javascript",
9+
"kotlin": "java",
10+
"typescript": "javascript"
11+
}

0 commit comments

Comments
 (0)