gulpfile.reh.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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. 'use strict';
  6. const gulp = require('gulp');
  7. const path = require('path');
  8. const es = require('event-stream');
  9. const util = require('./lib/util');
  10. const { getVersion } = require('./lib/getVersion');
  11. const task = require('./lib/task');
  12. const optimize = require('./lib/optimize');
  13. const product = require('../product.json');
  14. const rename = require('gulp-rename');
  15. const replace = require('gulp-replace');
  16. const filter = require('gulp-filter');
  17. const { getProductionDependencies } = require('./lib/dependencies');
  18. const vfs = require('vinyl-fs');
  19. const packageJson = require('../package.json');
  20. const flatmap = require('gulp-flatmap');
  21. const gunzip = require('gulp-gunzip');
  22. const File = require('vinyl');
  23. const fs = require('fs');
  24. const glob = require('glob');
  25. const { compileBuildTask } = require('./gulpfile.compile');
  26. const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions');
  27. const { vscodeWebEntryPoints, vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web');
  28. const cp = require('child_process');
  29. const log = require('fancy-log');
  30. const REPO_ROOT = path.dirname(__dirname);
  31. const commit = getVersion(REPO_ROOT);
  32. const BUILD_ROOT = path.dirname(REPO_ROOT);
  33. const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote');
  34. // Targets
  35. const BUILD_TARGETS = [
  36. { platform: 'win32', arch: 'x64' },
  37. { platform: 'darwin', arch: 'x64' },
  38. { platform: 'darwin', arch: 'arm64' },
  39. { platform: 'linux', arch: 'x64' },
  40. { platform: 'linux', arch: 'armhf' },
  41. { platform: 'linux', arch: 'arm64' },
  42. { platform: 'alpine', arch: 'arm64' },
  43. // legacy: we use to ship only one alpine so it was put in the arch, but now we ship
  44. // multiple alpine images and moved to a better model (alpine as the platform)
  45. { platform: 'linux', arch: 'alpine' },
  46. ];
  47. const serverResources = [
  48. // Bootstrap
  49. 'out-build/bootstrap.js',
  50. 'out-build/bootstrap-fork.js',
  51. 'out-build/bootstrap-amd.js',
  52. 'out-build/bootstrap-node.js',
  53. // Performance
  54. 'out-build/vs/base/common/performance.js',
  55. // Process monitor
  56. 'out-build/vs/base/node/cpuUsage.sh',
  57. 'out-build/vs/base/node/ps.sh',
  58. // Terminal shell integration
  59. 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1',
  60. 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh',
  61. 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh',
  62. 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh',
  63. 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh',
  64. 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh',
  65. 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish',
  66. '!**/test/**'
  67. ];
  68. const serverWithWebResources = [
  69. // Include all of server...
  70. ...serverResources,
  71. // ...and all of web
  72. ...vscodeWebResourceIncludes
  73. ];
  74. const serverEntryPoints = [
  75. {
  76. name: 'vs/server/node/server.main',
  77. exclude: ['vs/css', 'vs/nls']
  78. },
  79. {
  80. name: 'vs/server/node/server.cli',
  81. exclude: ['vs/css', 'vs/nls']
  82. },
  83. {
  84. name: 'vs/workbench/api/node/extensionHostProcess',
  85. exclude: ['vs/css', 'vs/nls']
  86. },
  87. {
  88. name: 'vs/platform/files/node/watcher/watcherMain',
  89. exclude: ['vs/css', 'vs/nls']
  90. },
  91. {
  92. name: 'vs/platform/terminal/node/ptyHostMain',
  93. exclude: ['vs/css', 'vs/nls']
  94. }
  95. ];
  96. const serverWithWebEntryPoints = [
  97. // Include all of server
  98. ...serverEntryPoints,
  99. // Include workbench web
  100. ...vscodeWebEntryPoints
  101. ];
  102. function getNodeVersion() {
  103. const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8');
  104. const nodeVersion = /^target "(.*)"$/m.exec(yarnrc)[1];
  105. const internalNodeVersion = /^ms_build_id "(.*)"$/m.exec(yarnrc)[1];
  106. return { nodeVersion, internalNodeVersion };
  107. }
  108. function getNodeChecksum(nodeVersion, platform, arch, glibcPrefix) {
  109. let expectedName;
  110. switch (platform) {
  111. case 'win32':
  112. expectedName = `win-${arch}/node.exe`;
  113. break;
  114. case 'darwin':
  115. case 'alpine':
  116. case 'linux':
  117. expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`;
  118. break;
  119. }
  120. const nodeJsChecksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'nodejs.txt'), 'utf8');
  121. for (const line of nodeJsChecksums.split('\n')) {
  122. const [checksum, name] = line.split(/\s+/);
  123. if (name === expectedName) {
  124. return checksum;
  125. }
  126. }
  127. return undefined;
  128. }
  129. function extractAlpinefromDocker(nodeVersion, platform, arch) {
  130. const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node';
  131. log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`);
  132. const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' });
  133. return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } })]);
  134. }
  135. const { nodeVersion, internalNodeVersion } = getNodeVersion();
  136. BUILD_TARGETS.forEach(({ platform, arch }) => {
  137. gulp.task(task.define(`node-${platform}-${arch}`, () => {
  138. const nodePath = path.join('.build', 'node', `v${nodeVersion}`, `${platform}-${arch}`);
  139. if (!fs.existsSync(nodePath)) {
  140. util.rimraf(nodePath);
  141. return nodejs(platform, arch)
  142. .pipe(vfs.dest(nodePath));
  143. }
  144. return Promise.resolve(null);
  145. }));
  146. });
  147. const defaultNodeTask = gulp.task(`node-${process.platform}-${process.arch}`);
  148. if (defaultNodeTask) {
  149. gulp.task(task.define('node', defaultNodeTask));
  150. }
  151. function nodejs(platform, arch) {
  152. const { fetchUrls, fetchGithub } = require('./lib/fetch');
  153. const untar = require('gulp-untar');
  154. const crypto = require('crypto');
  155. if (arch === 'armhf') {
  156. arch = 'armv7l';
  157. } else if (arch === 'alpine') {
  158. platform = 'alpine';
  159. arch = 'x64';
  160. }
  161. log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`);
  162. const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? '';
  163. const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch, glibcPrefix);
  164. if (checksumSha256) {
  165. log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`);
  166. } else {
  167. log.warn(`Unable to verify integrity of downloaded node.js binary because no SHA256 checksum was found!`);
  168. }
  169. switch (platform) {
  170. case 'win32':
  171. return (product.nodejsRepository !== 'https://nodejs.org' ?
  172. fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `win-${arch}-node.exe`, checksumSha256 }) :
  173. fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 }))
  174. .pipe(rename('node.exe'));
  175. case 'darwin':
  176. case 'linux':
  177. return (product.nodejsRepository !== 'https://nodejs.org' ?
  178. fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`, checksumSha256 }) :
  179. fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 })
  180. ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))
  181. .pipe(filter('**/node'))
  182. .pipe(util.setExecutableBit('**'))
  183. .pipe(rename('node'));
  184. case 'alpine':
  185. return product.nodejsRepository !== 'https://nodejs.org' ?
  186. fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 })
  187. .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar())))
  188. .pipe(filter('**/node'))
  189. .pipe(util.setExecutableBit('**'))
  190. .pipe(rename('node'))
  191. : extractAlpinefromDocker(nodeVersion, platform, arch);
  192. }
  193. }
  194. function packageTask(type, platform, arch, sourceFolderName, destinationFolderName) {
  195. const destination = path.join(BUILD_ROOT, destinationFolderName);
  196. return () => {
  197. const json = require('gulp-json-editor');
  198. const src = gulp.src(sourceFolderName + '/**', { base: '.' })
  199. .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); }))
  200. .pipe(util.setExecutableBit(['**/*.sh']))
  201. .pipe(filter(['**', '!**/*.js.map']));
  202. const workspaceExtensionPoints = ['debuggers', 'jsonValidation'];
  203. const isUIExtension = (manifest) => {
  204. switch (manifest.extensionKind) {
  205. case 'ui': return true;
  206. case 'workspace': return false;
  207. default: {
  208. if (manifest.main) {
  209. return false;
  210. }
  211. if (manifest.contributes && Object.keys(manifest.contributes).some(key => workspaceExtensionPoints.indexOf(key) !== -1)) {
  212. return false;
  213. }
  214. // Default is UI Extension
  215. return true;
  216. }
  217. }
  218. };
  219. const localWorkspaceExtensions = glob.sync('extensions/*/package.json')
  220. .filter((extensionPath) => {
  221. if (type === 'reh-web') {
  222. return true; // web: ship all extensions for now
  223. }
  224. // Skip shipping UI extensions because the client side will have them anyways
  225. // and they'd just increase the download without being used
  226. const manifest = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, extensionPath)).toString());
  227. return !isUIExtension(manifest);
  228. }).map((extensionPath) => path.basename(path.dirname(extensionPath)))
  229. .filter(name => name !== 'vscode-api-tests' && name !== 'vscode-test-resolver'); // Do not ship the test extensions
  230. const marketplaceExtensions = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'product.json'), 'utf8')).builtInExtensions
  231. .filter(entry => !entry.platforms || new Set(entry.platforms).has(platform))
  232. .filter(entry => !entry.clientOnly)
  233. .map(entry => entry.name);
  234. const extensionPaths = [...localWorkspaceExtensions, ...marketplaceExtensions]
  235. .map(name => `.build/extensions/${name}/**`);
  236. const extensions = gulp.src(extensionPaths, { base: '.build', dot: true });
  237. const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true });
  238. const sources = es.merge(src, extensions, extensionsCommonDependencies)
  239. .pipe(filter(['**', '!**/*.js.map'], { dot: true }));
  240. let version = packageJson.version;
  241. const quality = product.quality;
  242. if (quality && quality !== 'stable') {
  243. version += '-' + quality;
  244. }
  245. const name = product.nameShort;
  246. const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' })
  247. .pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined }));
  248. const date = new Date().toISOString();
  249. const productJsonStream = gulp.src(['product.json'], { base: '.' })
  250. .pipe(json({ commit, date, version }));
  251. const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true });
  252. const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path));
  253. const productionDependencies = getProductionDependencies(REMOTE_FOLDER);
  254. const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat();
  255. const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true })
  256. // filter out unnecessary files, no source maps in server build
  257. .pipe(filter(['**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map']))
  258. .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore')))
  259. .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`)))
  260. .pipe(jsFilter)
  261. .pipe(util.stripSourceMappingURL())
  262. .pipe(jsFilter.restore);
  263. const nodePath = `.build/node/v${nodeVersion}/${platform}-${arch}`;
  264. const node = gulp.src(`${nodePath}/**`, { base: nodePath, dot: true });
  265. let web = [];
  266. if (type === 'reh-web') {
  267. web = [
  268. 'resources/server/favicon.ico',
  269. 'resources/server/code-192.png',
  270. 'resources/server/code-512.png',
  271. 'resources/server/manifest.json'
  272. ].map(resource => gulp.src(resource, { base: '.' }).pipe(rename(resource)));
  273. }
  274. const all = es.merge(
  275. packageJsonStream,
  276. productJsonStream,
  277. license,
  278. sources,
  279. deps,
  280. node,
  281. ...web
  282. );
  283. let result = all
  284. .pipe(util.skipDirectories())
  285. .pipe(util.fixWin32DirectoryPermissions());
  286. if (platform === 'win32') {
  287. result = es.merge(result,
  288. gulp.src('resources/server/bin/remote-cli/code.cmd', { base: '.' })
  289. .pipe(replace('@@VERSION@@', version))
  290. .pipe(replace('@@COMMIT@@', commit))
  291. .pipe(replace('@@APPNAME@@', product.applicationName))
  292. .pipe(rename(`bin/remote-cli/${product.applicationName}.cmd`)),
  293. gulp.src('resources/server/bin/helpers/browser.cmd', { base: '.' })
  294. .pipe(replace('@@VERSION@@', version))
  295. .pipe(replace('@@COMMIT@@', commit))
  296. .pipe(replace('@@APPNAME@@', product.applicationName))
  297. .pipe(rename(`bin/helpers/browser.cmd`)),
  298. gulp.src('resources/server/bin/code-server.cmd', { base: '.' })
  299. .pipe(rename(`bin/${product.serverApplicationName}.cmd`)),
  300. );
  301. } else if (platform === 'linux' || platform === 'alpine' || platform === 'darwin') {
  302. result = es.merge(result,
  303. gulp.src(`resources/server/bin/remote-cli/${platform === 'darwin' ? 'code-darwin.sh' : 'code-linux.sh'}`, { base: '.' })
  304. .pipe(replace('@@VERSION@@', version))
  305. .pipe(replace('@@COMMIT@@', commit))
  306. .pipe(replace('@@APPNAME@@', product.applicationName))
  307. .pipe(rename(`bin/remote-cli/${product.applicationName}`))
  308. .pipe(util.setExecutableBit()),
  309. gulp.src(`resources/server/bin/helpers/${platform === 'darwin' ? 'browser-darwin.sh' : 'browser-linux.sh'}`, { base: '.' })
  310. .pipe(replace('@@VERSION@@', version))
  311. .pipe(replace('@@COMMIT@@', commit))
  312. .pipe(replace('@@APPNAME@@', product.applicationName))
  313. .pipe(rename(`bin/helpers/browser.sh`))
  314. .pipe(util.setExecutableBit()),
  315. gulp.src(`resources/server/bin/${platform === 'darwin' ? 'code-server-darwin.sh' : 'code-server-linux.sh'}`, { base: '.' })
  316. .pipe(rename(`bin/${product.serverApplicationName}`))
  317. .pipe(util.setExecutableBit())
  318. );
  319. }
  320. if (platform === 'linux' && process.env['VSCODE_NODE_GLIBC'] === '-glibc-2.17') {
  321. result = es.merge(result,
  322. gulp.src(`resources/server/bin/helpers/check-requirements-linux-legacy.sh`, { base: '.' })
  323. .pipe(rename(`bin/helpers/check-requirements.sh`))
  324. .pipe(util.setExecutableBit())
  325. );
  326. } else if (platform === 'linux' || platform === 'alpine') {
  327. result = es.merge(result,
  328. gulp.src(`resources/server/bin/helpers/check-requirements-linux.sh`, { base: '.' })
  329. .pipe(rename(`bin/helpers/check-requirements.sh`))
  330. .pipe(util.setExecutableBit())
  331. );
  332. }
  333. return result.pipe(vfs.dest(destination));
  334. };
  335. }
  336. /**
  337. * @param {object} product The parsed product.json file contents
  338. */
  339. function tweakProductForServerWeb(product) {
  340. const result = { ...product };
  341. delete result.webEndpointUrlTemplate;
  342. return result;
  343. }
  344. ['reh', 'reh-web'].forEach(type => {
  345. const optimizeTask = task.define(`optimize-vscode-${type}`, task.series(
  346. util.rimraf(`out-vscode-${type}`),
  347. optimize.optimizeTask(
  348. {
  349. out: `out-vscode-${type}`,
  350. amd: {
  351. src: 'out-build',
  352. entryPoints: (type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints).flat(),
  353. otherSources: [],
  354. resources: type === 'reh' ? serverResources : serverWithWebResources,
  355. loaderConfig: optimize.loaderConfig(),
  356. inlineAmdImages: true,
  357. bundleInfo: undefined,
  358. fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product)
  359. },
  360. commonJS: {
  361. src: 'out-build',
  362. entryPoints: [
  363. 'out-build/server-main.js',
  364. 'out-build/server-cli.js'
  365. ],
  366. platform: 'node',
  367. external: [
  368. 'minimist',
  369. // TODO: we cannot inline `product.json` because
  370. // it is being changed during build time at a later
  371. // point in time (such as `checksums`)
  372. '../product.json',
  373. '../package.json'
  374. ]
  375. }
  376. }
  377. )
  378. ));
  379. const minifyTask = task.define(`minify-vscode-${type}`, task.series(
  380. optimizeTask,
  381. util.rimraf(`out-vscode-${type}-min`),
  382. optimize.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`)
  383. ));
  384. gulp.task(minifyTask);
  385. BUILD_TARGETS.forEach(buildTarget => {
  386. const dashed = (str) => (str ? `-${str}` : ``);
  387. const platform = buildTarget.platform;
  388. const arch = buildTarget.arch;
  389. ['', 'min'].forEach(minified => {
  390. const sourceFolderName = `out-vscode-${type}${dashed(minified)}`;
  391. const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`;
  392. const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(
  393. gulp.task(`node-${platform}-${arch}`),
  394. util.rimraf(path.join(BUILD_ROOT, destinationFolderName)),
  395. packageTask(type, platform, arch, sourceFolderName, destinationFolderName)
  396. ));
  397. gulp.task(serverTaskCI);
  398. const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series(
  399. compileBuildTask,
  400. compileExtensionsBuildTask,
  401. compileExtensionMediaBuildTask,
  402. minified ? minifyTask : optimizeTask,
  403. serverTaskCI
  404. ));
  405. gulp.task(serverTask);
  406. });
  407. });
  408. });