Skip to content

Commit c6092b0

Browse files
authored
fix: incorrect platform used on arm64 Linux (#145)
We previously duplicated the "architecture" in the "platform" field. This PR unifies that into the single "platform" field (which was already exposed by consumer to specify the architecture for some things) which resolves the issue. Also pulls in vitest to add tests that the download is successful on all platforms.
1 parent a81577e commit c6092b0

File tree

7 files changed

+406
-63
lines changed

7 files changed

+406
-63
lines changed

lib/download.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { existsSync, promises as fs } from 'fs';
2+
import { tmpdir } from 'os';
3+
import { join } from 'path';
4+
import { afterAll, beforeAll, describe, test } from 'vitest';
5+
import { downloadAndUnzipVSCode } from './download';
6+
import { SilentReporter } from './progress';
7+
8+
const platforms = ['darwin', 'darwin-arm64', 'win32-archive', 'win32-x64-archive', 'linux-x64', 'linux-arm64', 'linux-armhf'];
9+
10+
describe('sane downloads', () => {
11+
const testTempDir = join(tmpdir(), 'vscode-test-download');
12+
13+
beforeAll(async () => {
14+
await fs.mkdir(testTempDir, { recursive: true })
15+
});
16+
17+
for (const platform of platforms) {
18+
test.concurrent(platform, async () => {
19+
const location = await downloadAndUnzipVSCode({
20+
platform,
21+
version: 'stable',
22+
cachePath: testTempDir,
23+
reporter: new SilentReporter(),
24+
});
25+
26+
if (!existsSync(location)) {
27+
throw new Error(`expected ${location} to exist for ${platform}`);
28+
}
29+
})
30+
}
31+
32+
afterAll(async () => {
33+
try {
34+
await fs.rmdir(testTempDir, { recursive: true });
35+
} catch {
36+
// ignored
37+
}
38+
});
39+
});

lib/download.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as del from './del';
1414
import { ConsoleReporter, ProgressReporter, ProgressReportStage } from './progress';
1515
import * as request from './request';
1616
import {
17-
downloadDirToExecutablePath, getLatestInsidersMetadata, getVSCodeDownloadUrl, insidersDownloadDirMetadata, insidersDownloadDirToExecutablePath, systemDefaultArchitecture, systemDefaultPlatform
17+
downloadDirToExecutablePath, getLatestInsidersMetadata, getVSCodeDownloadUrl, insidersDownloadDirMetadata, insidersDownloadDirToExecutablePath, systemDefaultPlatform
1818
} from './util';
1919

2020
const extensionRoot = process.cwd();
@@ -42,19 +42,12 @@ async function isValidVersion(version: string) {
4242
// eslint-disable-next-line @typescript-eslint/ban-types
4343
type StringLiteralUnion<T extends U, U = string> = T | (U & {});
4444
export type DownloadVersion = StringLiteralUnion<'insiders' | 'stable'>;
45-
export type DownloadPlatform = StringLiteralUnion<'darwin' | 'win32-archive' | 'win32-x64-archive' | 'linux-x64'>;
46-
47-
export const enum DownloadArchitecture {
48-
X64 = 'x64',
49-
X86 = 'ia32',
50-
ARM64 = 'arm64',
51-
}
45+
export type DownloadPlatform = StringLiteralUnion<'darwin' | 'darwin-arm64' | 'win32-archive' | 'win32-x64-archive' | 'linux-x64' | 'linux-arm64' | 'linux-armhf'>;
5246

5347
export interface DownloadOptions {
5448
readonly cachePath: string;
5549
readonly version: DownloadVersion;
5650
readonly platform: DownloadPlatform;
57-
readonly architecture: DownloadArchitecture;
5851
readonly reporter?: ProgressReporter;
5952
readonly extractSync?: boolean;
6053
}
@@ -71,7 +64,7 @@ async function downloadVSCodeArchive(options: DownloadOptions) {
7164
fs.mkdirSync(options.cachePath);
7265
}
7366

74-
const downloadUrl = getVSCodeDownloadUrl(options.version, options.platform, options.architecture);
67+
const downloadUrl = getVSCodeDownloadUrl(options.version, options.platform);
7568
options.reporter?.report({ stage: ProgressReportStage.ResolvingCDNLocation, url: downloadUrl });
7669
const res = await request.getStream(downloadUrl)
7770
if (res.statusCode !== 302) {
@@ -151,7 +144,7 @@ async function unzipVSCode(reporter: ProgressReporter, extractDir: string, extra
151144
fs.mkdirSync(extractDir);
152145
}
153146

154-
await spawnDecompressorChild('tar', ['-xzf', '-', '-C', extractDir], stream);
147+
await spawnDecompressorChild('tar', ['-xzf', '-', '--strip-components=1', '-C', extractDir], stream);
155148
}
156149
}
157150

@@ -177,7 +170,6 @@ export async function download(options: Partial<DownloadOptions> = {}): Promise<
177170
let version = options?.version;
178171
const {
179172
platform = systemDefaultPlatform,
180-
architecture = systemDefaultArchitecture,
181173
cachePath = defaultCachePath,
182174
reporter = new ConsoleReporter(process.stdout.isTTY),
183175
extractSync = false,
@@ -237,7 +229,7 @@ export async function download(options: Partial<DownloadOptions> = {}): Promise<
237229
}
238230

239231
try {
240-
const { stream, format } = await downloadVSCodeArchive({ version, architecture, platform, cachePath, reporter });
232+
const { stream, format } = await downloadVSCodeArchive({ version, platform, cachePath, reporter });
241233
await unzipVSCode(reporter, downloadedPath, extractSync, stream, format);
242234
reporter.report({ stage: ProgressReportStage.NewInstallComplete, downloadedPath })
243235
} catch (err) {

lib/runTest.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@ export interface TestOptions {
3030
version?: DownloadVersion;
3131

3232
/**
33-
* The VS Code platform to download. If not specified, defaults to:
34-
* - Windows: `win32-archive`
35-
* - macOS: `darwin`
36-
* - Linux: `linux-x64`
33+
* The VS Code platform to download. If not specified, it defaults to the
34+
* current platform.
3735
*
38-
* Possible values are: `win32-archive`, `win32-x64-archive`, `darwin` and `linux-x64`.
36+
* Possible values are:
37+
* - `win32-archive`
38+
* - `win32-x64-archive`
39+
* - `win32-arm64-archive `
40+
* - `darwin`
41+
* - `darwin-arm64`
42+
* - `linux-x64`
43+
* - `linux-arm64`
44+
* - `linux-armhf`
3945
*/
4046
platform?: DownloadPlatform;
4147

@@ -184,8 +190,8 @@ async function innerRunTests(
184190
} else if (code !== 0) {
185191
reject('Failed');
186192
} else {
187-
console.log('Done\n');
188-
resolve(code ?? -1);
193+
console.log('Done\n');
194+
resolve(code ?? -1);
189195
}
190196
}
191197

lib/util.ts

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,53 +7,41 @@ import * as path from 'path';
77
import { URL } from 'url';
88
import * as https from 'https';
99
import * as request from './request';
10-
import { DownloadArchitecture, DownloadPlatform } from './download';
10+
import { DownloadPlatform } from './download';
1111
import * as createHttpsProxyAgent from 'https-proxy-agent';
1212
import * as createHttpProxyAgent from 'http-proxy-agent';
1313
import { readFileSync } from 'fs';
1414
import { getProfileArguments, TestOptions } from './runTest';
1515

1616
export let systemDefaultPlatform: DownloadPlatform;
1717

18+
const windowsPlatforms = new Set<DownloadPlatform>(['win32-archive', 'win32-x64-archive', 'win32-arm64-archive']);
19+
const darwinPlatforms = new Set<DownloadPlatform>(['darwin-arm64', 'darwin']);
20+
1821
switch (process.platform) {
1922
case 'darwin':
20-
systemDefaultPlatform = 'darwin';
23+
systemDefaultPlatform = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin';
2124
break;
2225
case 'win32':
23-
systemDefaultPlatform = 'win32-archive';
26+
systemDefaultPlatform = process.arch === 'arm64'
27+
? 'win32-arm64-archive'
28+
: process.arch === 'ia32'
29+
? 'win32-archive'
30+
: 'win32-x64-archive';
2431
break;
2532
default:
26-
systemDefaultPlatform = 'linux-x64';
33+
systemDefaultPlatform = process.arch === 'arm64'
34+
? 'linux-arm64'
35+
: process.arch === 'arm'
36+
? 'linux-armhf'
37+
: 'linux-x64';
2738
}
2839

29-
export const systemDefaultArchitecture = process.arch === 'arm64'
30-
? DownloadArchitecture.ARM64
31-
: process.arch === 'ia32'
32-
? DownloadArchitecture.X86
33-
: DownloadArchitecture.X64;
34-
35-
export function getVSCodeDownloadUrl(version: string, platform = systemDefaultPlatform, architecture = systemDefaultArchitecture) {
36-
37-
let downloadSegment: string;
38-
switch (platform) {
39-
case 'darwin':
40-
downloadSegment = architecture === DownloadArchitecture.ARM64 ? 'darwin-arm64' : 'darwin';
41-
break;
42-
case 'win32-archive':
43-
downloadSegment = architecture === DownloadArchitecture.ARM64 ? 'win32-arm64-archive' : 'win32-archive';
44-
break;
45-
case 'linux-x64':
46-
downloadSegment = architecture === DownloadArchitecture.ARM64 ? 'linux-arm64' : 'linux-x64';
47-
break;
48-
default:
49-
downloadSegment = platform;
50-
break;
51-
}
52-
40+
export function getVSCodeDownloadUrl(version: string, platform = systemDefaultPlatform) {
5341
if (version === 'insiders') {
54-
return `https://update.code.visualstudio.com/latest/${downloadSegment}/insider`;
42+
return `https://update.code.visualstudio.com/latest/${platform}/insider`;
5543
}
56-
return `https://update.code.visualstudio.com/${version}/${downloadSegment}/stable`;
44+
return `https://update.code.visualstudio.com/${version}/${platform}/stable`;
5745
}
5846

5947
let PROXY_AGENT: createHttpProxyAgent.HttpProxyAgent | undefined = undefined;
@@ -82,33 +70,33 @@ export function urlToOptions(url: string): https.RequestOptions {
8270
}
8371

8472
export function downloadDirToExecutablePath(dir: string, platform: DownloadPlatform) {
85-
if (platform === 'win32-archive' || platform === 'win32-x64-archive') {
73+
if (windowsPlatforms.has(platform)) {
8674
return path.resolve(dir, 'Code.exe');
87-
} else if (platform === 'darwin') {
75+
} else if (darwinPlatforms.has(platform)) {
8876
return path.resolve(dir, 'Visual Studio Code.app/Contents/MacOS/Electron');
8977
} else {
90-
return path.resolve(dir, 'VSCode-linux-x64/code');
78+
return path.resolve(dir, 'code');
9179
}
9280
}
9381

9482
export function insidersDownloadDirToExecutablePath(dir: string, platform: DownloadPlatform) {
95-
if (platform === 'win32-archive' || platform === 'win32-x64-archive') {
83+
if (windowsPlatforms.has(platform)) {
9684
return path.resolve(dir, 'Code - Insiders.exe');
97-
} else if (platform === 'darwin') {
85+
} else if (darwinPlatforms.has(platform)) {
9886
return path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron');
9987
} else {
100-
return path.resolve(dir, 'VSCode-linux-x64/code-insiders');
88+
return path.resolve(dir, 'code-insiders');
10189
}
10290
}
10391

10492
export function insidersDownloadDirMetadata(dir: string, platform: DownloadPlatform) {
10593
let productJsonPath;
106-
if (platform === 'win32-archive' || platform === 'win32-x64-archive') {
94+
if (windowsPlatforms.has(platform)) {
10795
productJsonPath = path.resolve(dir, 'resources/app/product.json');
108-
} else if (platform === 'darwin') {
96+
} else if (darwinPlatforms.has(platform)) {
10997
productJsonPath = path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/Resources/app/product.json');
11098
} else {
111-
productJsonPath = path.resolve(dir, 'VSCode-linux-x64/resources/app/product.json');
99+
productJsonPath = path.resolve(dir, 'resources/app/product.json');
112100
}
113101
const productJson = JSON.parse(readFileSync(productJsonPath, 'utf-8'));
114102

@@ -140,13 +128,13 @@ export async function getLatestInsidersMetadata(platform: string) {
140128
* Usually you will want {@link resolveCliArgsFromVSCodeExecutablePath} instead.
141129
*/
142130
export function resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath: string, platform: DownloadPlatform) {
143-
if (platform === 'win32-archive' || platform === 'win32-x64-archive') {
131+
if (windowsPlatforms.has(platform)) {
144132
if (vscodeExecutablePath.endsWith('Code - Insiders.exe')) {
145133
return path.resolve(vscodeExecutablePath, '../bin/code-insiders.cmd');
146134
} else {
147135
return path.resolve(vscodeExecutablePath, '../bin/code.cmd');
148136
}
149-
} else if (platform === 'darwin') {
137+
} else if (darwinPlatforms.has(platform)) {
150138
return path.resolve(vscodeExecutablePath, '../../../Contents/Resources/app/bin/code');
151139
} else {
152140
if (vscodeExecutablePath.endsWith('code-insiders')) {

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"compile": "tsc -p ./",
66
"watch": "tsc -w -p ./",
77
"prepublish": "tsc -p ./",
8-
"test": "eslint lib --ext ts && tsc --noEmit"
8+
"test": "eslint lib --ext ts && vitest && tsc --noEmit"
99
},
1010
"main": "./out/index.js",
1111
"engines": {
@@ -25,7 +25,8 @@
2525
"@typescript-eslint/parser": "^4.13.0",
2626
"eslint": "^7.17.0",
2727
"eslint-plugin-header": "^3.1.0",
28-
"typescript": "^4.3.5"
28+
"typescript": "^4.3.5",
29+
"vitest": "^0.10.2"
2930
},
3031
"license": "MIT",
3132
"author": "Visual Studio Code Team",

vitest.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { defineConfig } from 'vitest/config';
7+
8+
export default defineConfig({
9+
test: {
10+
include: ['lib/**/*.test.ts'],
11+
testTimeout: 120_000,
12+
},
13+
});

0 commit comments

Comments
 (0)