Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/gulpfile.reh.ts
4770 views
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 gulp from 'gulp';
7
import * as path from 'path';
8
import es from 'event-stream';
9
import * as util from './lib/util.ts';
10
import { getVersion } from './lib/getVersion.ts';
11
import * as task from './lib/task.ts';
12
import * as optimize from './lib/optimize.ts';
13
import { inlineMeta } from './lib/inlineMeta.ts';
14
import product from '../product.json' with { type: 'json' };
15
import rename from 'gulp-rename';
16
import replace from 'gulp-replace';
17
import filter from 'gulp-filter';
18
import { getProductionDependencies } from './lib/dependencies.ts';
19
import { readISODate } from './lib/date.ts';
20
import vfs from 'vinyl-fs';
21
import packageJson from '../package.json' with { type: 'json' };
22
import flatmap from 'gulp-flatmap';
23
import gunzip from 'gulp-gunzip';
24
import untar from 'gulp-untar';
25
import File from 'vinyl';
26
import * as fs from 'fs';
27
import glob from 'glob';
28
import { compileBuildWithManglingTask } from './gulpfile.compile.ts';
29
import { cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileExtensionMediaBuildTask } from './gulpfile.extensions.ts';
30
import { vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } from './gulpfile.vscode.web.ts';
31
import * as cp from 'child_process';
32
import log from 'fancy-log';
33
import buildfile from './buildfile.ts';
34
import { fetchUrls, fetchGithub } from './lib/fetch.ts';
35
import jsonEditor from 'gulp-json-editor';
36
37
38
const REPO_ROOT = path.dirname(import.meta.dirname);
39
const commit = getVersion(REPO_ROOT);
40
const BUILD_ROOT = path.dirname(REPO_ROOT);
41
const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote');
42
43
// Targets
44
45
const BUILD_TARGETS = [
46
{ platform: 'win32', arch: 'x64' },
47
{ platform: 'win32', arch: 'arm64' },
48
{ platform: 'darwin', arch: 'x64' },
49
{ platform: 'darwin', arch: 'arm64' },
50
{ platform: 'linux', arch: 'x64' },
51
{ platform: 'linux', arch: 'armhf' },
52
{ platform: 'linux', arch: 'arm64' },
53
{ platform: 'alpine', arch: 'arm64' },
54
// legacy: we use to ship only one alpine so it was put in the arch, but now we ship
55
// multiple alpine images and moved to a better model (alpine as the platform)
56
{ platform: 'linux', arch: 'alpine' },
57
];
58
59
const serverResourceIncludes = [
60
61
// NLS
62
'out-build/nls.messages.json',
63
'out-build/nls.keys.json',
64
65
// Process monitor
66
'out-build/vs/base/node/cpuUsage.sh',
67
'out-build/vs/base/node/ps.sh',
68
69
// External Terminal
70
'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt',
71
72
// Terminal shell integration
73
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1',
74
'out-build/vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1',
75
'out-build/vs/workbench/contrib/terminal/common/scripts/GitTabExpansion.psm1',
76
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh',
77
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh',
78
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh',
79
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh',
80
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh',
81
'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish',
82
83
];
84
85
const serverResourceExcludes = [
86
'!out-build/vs/**/{electron-browser,electron-main,electron-utility}/**',
87
'!out-build/vs/editor/standalone/**',
88
'!out-build/vs/workbench/**/*-tb.png',
89
'!**/test/**'
90
];
91
92
const serverResources = [
93
...serverResourceIncludes,
94
...serverResourceExcludes
95
];
96
97
const serverWithWebResourceIncludes = [
98
...serverResourceIncludes,
99
'out-build/vs/code/browser/workbench/*.html',
100
...vscodeWebResourceIncludes
101
];
102
103
const serverWithWebResourceExcludes = [
104
...serverResourceExcludes,
105
'!out-build/vs/code/**/*-dev.html'
106
];
107
108
const serverWithWebResources = [
109
...serverWithWebResourceIncludes,
110
...serverWithWebResourceExcludes
111
];
112
const serverEntryPoints = buildfile.codeServer;
113
114
const webEntryPoints = [
115
buildfile.workerEditor,
116
buildfile.workerExtensionHost,
117
buildfile.workerNotebook,
118
buildfile.workerLanguageDetection,
119
buildfile.workerLocalFileSearch,
120
buildfile.workerOutputLinks,
121
buildfile.workerBackgroundTokenization,
122
buildfile.keyboardMaps,
123
buildfile.codeWeb
124
].flat();
125
126
const serverWithWebEntryPoints = [
127
128
// Include all of server
129
...serverEntryPoints,
130
131
// Include all of web
132
...webEntryPoints,
133
].flat();
134
135
const bootstrapEntryPoints = [
136
'out-build/server-main.js',
137
'out-build/server-cli.js',
138
'out-build/bootstrap-fork.js'
139
];
140
141
function getNodeVersion() {
142
const npmrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.npmrc'), 'utf8');
143
const nodeVersion = /^target="(.*)"$/m.exec(npmrc)![1];
144
const internalNodeVersion = /^ms_build_id="(.*)"$/m.exec(npmrc)![1];
145
return { nodeVersion, internalNodeVersion };
146
}
147
148
function getNodeChecksum(expectedName: string): string | undefined {
149
const nodeJsChecksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'nodejs.txt'), 'utf8');
150
for (const line of nodeJsChecksums.split('\n')) {
151
const [checksum, name] = line.split(/\s+/);
152
if (name === expectedName) {
153
return checksum;
154
}
155
}
156
return undefined;
157
}
158
159
function extractAlpinefromDocker(nodeVersion: string, platform: string, arch: string) {
160
const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node';
161
log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`);
162
const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' });
163
// eslint-disable-next-line local/code-no-dangerous-type-assertions
164
return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } as fs.Stats })]);
165
}
166
167
const { nodeVersion, internalNodeVersion } = getNodeVersion();
168
169
BUILD_TARGETS.forEach(({ platform, arch }) => {
170
gulp.task(task.define(`node-${platform}-${arch}`, () => {
171
const nodePath = path.join('.build', 'node', `v${nodeVersion}`, `${platform}-${arch}`);
172
173
if (!fs.existsSync(nodePath)) {
174
util.rimraf(nodePath);
175
176
return nodejs(platform, arch)!
177
.pipe(vfs.dest(nodePath));
178
}
179
180
return Promise.resolve(null);
181
}));
182
});
183
184
const defaultNodeTask = gulp.task(`node-${process.platform}-${process.arch}`);
185
186
if (defaultNodeTask) {
187
// eslint-disable-next-line local/code-no-any-casts
188
gulp.task(task.define('node', defaultNodeTask as any));
189
}
190
191
function nodejs(platform: string, arch: string): NodeJS.ReadWriteStream | undefined {
192
193
if (arch === 'armhf') {
194
arch = 'armv7l';
195
} else if (arch === 'alpine') {
196
platform = 'alpine';
197
arch = 'x64';
198
}
199
200
log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`);
201
202
const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? '';
203
let expectedName: string | undefined;
204
switch (platform) {
205
case 'win32':
206
expectedName = product.nodejsRepository !== 'https://nodejs.org' ?
207
`win-${arch}-node.exe` : `win-${arch}/node.exe`;
208
break;
209
210
case 'darwin':
211
expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`;
212
break;
213
case 'linux':
214
expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`;
215
break;
216
case 'alpine':
217
expectedName = `node-v${nodeVersion}-linux-${arch}-musl.tar.gz`;
218
break;
219
}
220
const checksumSha256 = expectedName ? getNodeChecksum(expectedName) : undefined;
221
222
if (checksumSha256) {
223
log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`);
224
} else {
225
log.warn(`Unable to verify integrity of downloaded node.js binary because no SHA256 checksum was found!`);
226
}
227
228
switch (platform) {
229
case 'win32':
230
return (product.nodejsRepository !== 'https://nodejs.org' ?
231
fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName!, checksumSha256 }) :
232
fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 }))
233
.pipe(rename('node.exe'));
234
case 'darwin':
235
case 'linux':
236
return (product.nodejsRepository !== 'https://nodejs.org' ?
237
fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName!, checksumSha256 }) :
238
fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 })
239
).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))
240
.pipe(filter('**/node'))
241
.pipe(util.setExecutableBit('**'))
242
.pipe(rename('node'));
243
case 'alpine':
244
return product.nodejsRepository !== 'https://nodejs.org' ?
245
fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName!, checksumSha256 })
246
.pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))
247
.pipe(filter('**/node'))
248
.pipe(util.setExecutableBit('**'))
249
.pipe(rename('node'))
250
: extractAlpinefromDocker(nodeVersion, platform, arch);
251
}
252
}
253
254
function packageTask(type: string, platform: string, arch: string, sourceFolderName: string, destinationFolderName: string) {
255
const destination = path.join(BUILD_ROOT, destinationFolderName);
256
257
return () => {
258
const src = gulp.src(sourceFolderName + '/**', { base: '.' })
259
.pipe(rename(function (path) { path.dirname = path.dirname!.replace(new RegExp('^' + sourceFolderName), 'out'); }))
260
.pipe(util.setExecutableBit(['**/*.sh']))
261
.pipe(filter(['**', '!**/*.{js,css}.map']));
262
263
const workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
264
const isUIExtension = (manifest: { extensionKind?: string; main?: string; contributes?: Record<string, unknown> }) => {
265
switch (manifest.extensionKind) {
266
case 'ui': return true;
267
case 'workspace': return false;
268
default: {
269
if (manifest.main) {
270
return false;
271
}
272
if (manifest.contributes && Object.keys(manifest.contributes).some(key => workspaceExtensionPoints.indexOf(key) !== -1)) {
273
return false;
274
}
275
// Default is UI Extension
276
return true;
277
}
278
}
279
};
280
const localWorkspaceExtensions = glob.sync('extensions/*/package.json')
281
.filter((extensionPath) => {
282
if (type === 'reh-web') {
283
return true; // web: ship all extensions for now
284
}
285
286
// Skip shipping UI extensions because the client side will have them anyways
287
// and they'd just increase the download without being used
288
const manifest = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, extensionPath)).toString());
289
return !isUIExtension(manifest);
290
}).map((extensionPath) => path.basename(path.dirname(extensionPath)))
291
.filter(name => name !== 'vscode-api-tests' && name !== 'vscode-test-resolver'); // Do not ship the test extensions
292
const builtInExtensions: Array<{ name: string; platforms?: string[]; clientOnly?: boolean }> = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'product.json'), 'utf8')).builtInExtensions;
293
const marketplaceExtensions = builtInExtensions
294
.filter(entry => !entry.platforms || new Set(entry.platforms).has(platform))
295
.filter(entry => !entry.clientOnly)
296
.map(entry => entry.name);
297
const extensionPaths = [...localWorkspaceExtensions, ...marketplaceExtensions]
298
.map(name => `.build/extensions/${name}/**`);
299
300
const extensions = gulp.src(extensionPaths, { base: '.build', dot: true });
301
const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true });
302
const sources = es.merge(src, extensions, extensionsCommonDependencies)
303
.pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true }));
304
305
let version = packageJson.version;
306
const quality = (product as typeof product & { quality?: string }).quality;
307
308
if (quality && quality !== 'stable') {
309
version += '-' + quality;
310
}
311
312
const name = product.nameShort;
313
314
let packageJsonContents = '';
315
const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' })
316
.pipe(jsonEditor({ name, version, dependencies: undefined, optionalDependencies: undefined, type: 'module' }))
317
.pipe(es.through(function (file) {
318
packageJsonContents = file.contents.toString();
319
this.emit('data', file);
320
}));
321
322
let productJsonContents = '';
323
const productJsonStream = gulp.src(['product.json'], { base: '.' })
324
.pipe(jsonEditor({ commit, date: readISODate('out-build'), version }))
325
.pipe(es.through(function (file) {
326
productJsonContents = file.contents.toString();
327
this.emit('data', file);
328
}));
329
330
const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true });
331
332
const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path));
333
334
const productionDependencies = getProductionDependencies(REMOTE_FOLDER);
335
const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat();
336
const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true })
337
// filter out unnecessary files, no source maps in server build
338
.pipe(filter(['**', '!**/package-lock.json', '!**/*.{js,css}.map']))
339
.pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore')))
340
.pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`)))
341
.pipe(jsFilter)
342
.pipe(util.stripSourceMappingURL())
343
.pipe(jsFilter.restore);
344
345
const nodePath = `.build/node/v${nodeVersion}/${platform}-${arch}`;
346
const node = gulp.src(`${nodePath}/**`, { base: nodePath, dot: true });
347
348
let web: NodeJS.ReadWriteStream[] = [];
349
if (type === 'reh-web') {
350
web = [
351
'resources/server/favicon.ico',
352
'resources/server/code-192.png',
353
'resources/server/code-512.png',
354
'resources/server/manifest.json'
355
].map(resource => gulp.src(resource, { base: '.' }).pipe(rename(resource)));
356
}
357
358
const all = es.merge(
359
packageJsonStream,
360
productJsonStream,
361
license,
362
sources,
363
deps,
364
node,
365
...web
366
);
367
368
let result = all
369
.pipe(util.skipDirectories())
370
.pipe(util.fixWin32DirectoryPermissions());
371
372
if (platform === 'win32') {
373
result = es.merge(result,
374
gulp.src('resources/server/bin/remote-cli/code.cmd', { base: '.' })
375
.pipe(replace('@@VERSION@@', version))
376
.pipe(replace('@@COMMIT@@', commit || ''))
377
.pipe(replace('@@APPNAME@@', product.applicationName))
378
.pipe(rename(`bin/remote-cli/${product.applicationName}.cmd`)),
379
gulp.src('resources/server/bin/helpers/browser.cmd', { base: '.' })
380
.pipe(replace('@@VERSION@@', version))
381
.pipe(replace('@@COMMIT@@', commit || ''))
382
.pipe(replace('@@APPNAME@@', product.applicationName))
383
.pipe(rename(`bin/helpers/browser.cmd`)),
384
gulp.src('resources/server/bin/code-server.cmd', { base: '.' })
385
.pipe(rename(`bin/${product.serverApplicationName}.cmd`)),
386
);
387
} else if (platform === 'linux' || platform === 'alpine' || platform === 'darwin') {
388
result = es.merge(result,
389
gulp.src(`resources/server/bin/remote-cli/${platform === 'darwin' ? 'code-darwin.sh' : 'code-linux.sh'}`, { base: '.' })
390
.pipe(replace('@@VERSION@@', version))
391
.pipe(replace('@@COMMIT@@', commit || ''))
392
.pipe(replace('@@APPNAME@@', product.applicationName))
393
.pipe(rename(`bin/remote-cli/${product.applicationName}`))
394
.pipe(util.setExecutableBit()),
395
gulp.src(`resources/server/bin/helpers/${platform === 'darwin' ? 'browser-darwin.sh' : 'browser-linux.sh'}`, { base: '.' })
396
.pipe(replace('@@VERSION@@', version))
397
.pipe(replace('@@COMMIT@@', commit || ''))
398
.pipe(replace('@@APPNAME@@', product.applicationName))
399
.pipe(rename(`bin/helpers/browser.sh`))
400
.pipe(util.setExecutableBit()),
401
gulp.src(`resources/server/bin/${platform === 'darwin' ? 'code-server-darwin.sh' : 'code-server-linux.sh'}`, { base: '.' })
402
.pipe(rename(`bin/${product.serverApplicationName}`))
403
.pipe(util.setExecutableBit())
404
);
405
}
406
407
if (platform === 'linux' || platform === 'alpine') {
408
result = es.merge(result,
409
gulp.src(`resources/server/bin/helpers/check-requirements-linux.sh`, { base: '.' })
410
.pipe(rename(`bin/helpers/check-requirements.sh`))
411
.pipe(util.setExecutableBit())
412
);
413
}
414
415
result = inlineMeta(result, {
416
targetPaths: bootstrapEntryPoints,
417
packageJsonFn: () => packageJsonContents,
418
productJsonFn: () => productJsonContents
419
});
420
421
return result.pipe(vfs.dest(destination));
422
};
423
}
424
425
/**
426
* @param product The parsed product.json file contents
427
*/
428
function tweakProductForServerWeb(product: typeof import('../product.json')) {
429
const result: typeof product & { webEndpointUrlTemplate?: string } = { ...product };
430
delete result.webEndpointUrlTemplate;
431
return result;
432
}
433
434
['reh', 'reh-web'].forEach(type => {
435
const bundleTask = task.define(`bundle-vscode-${type}`, task.series(
436
util.rimraf(`out-vscode-${type}`),
437
optimize.bundleTask(
438
{
439
out: `out-vscode-${type}`,
440
esm: {
441
src: 'out-build',
442
entryPoints: [
443
...(type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints),
444
...bootstrapEntryPoints
445
],
446
resources: type === 'reh' ? serverResources : serverWithWebResources,
447
fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product)
448
}
449
}
450
)
451
));
452
453
const minifyTask = task.define(`minify-vscode-${type}`, task.series(
454
bundleTask,
455
util.rimraf(`out-vscode-${type}-min`),
456
optimize.minifyTask(`out-vscode-${type}`, `https://main.vscode-cdn.net/sourcemaps/${commit}/core`)
457
));
458
gulp.task(minifyTask);
459
460
BUILD_TARGETS.forEach(buildTarget => {
461
const dashed = (str: string) => (str ? `-${str}` : ``);
462
const platform = buildTarget.platform;
463
const arch = buildTarget.arch;
464
465
['', 'min'].forEach(minified => {
466
const sourceFolderName = `out-vscode-${type}${dashed(minified)}`;
467
const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`;
468
469
const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(
470
compileNativeExtensionsBuildTask,
471
gulp.task(`node-${platform}-${arch}`) as task.Task,
472
util.rimraf(path.join(BUILD_ROOT, destinationFolderName)),
473
packageTask(type, platform, arch, sourceFolderName, destinationFolderName)
474
));
475
gulp.task(serverTaskCI);
476
477
const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series(
478
compileBuildWithManglingTask,
479
cleanExtensionsBuildTask,
480
compileNonNativeExtensionsBuildTask,
481
compileExtensionMediaBuildTask,
482
minified ? minifyTask : bundleTask,
483
serverTaskCI
484
));
485
gulp.task(serverTask);
486
});
487
});
488
});
489
490