Skip to content

Commit 2138622

Browse files
authored
build: add watch/compile tasks for CLI (microsoft#182344)
* build: add watch/compile tasks for CLI I spent time this morning working on the 'developer experience' of the CLI in vscode, mainly getting the CLI to cross-compile chasing our initial idea of having it auto-build in a devcontainer. After some effort and disabling tunnels connections (to avoid having to pull in OpenSSL which is a huge pain to cross compile), I was able to get it to cross-compile from Linux to Windows, using the mingw linker. I could probably figure out how to get macOS working as well with more effort. However, I'm not a big fan of this, effectively it's one more 'platform' of build we need to support and test. I think a better approach is downloading the latest compiled CLI from the update server instead, as needed. That's what this PR does. It just places the CLI where it _would_ normally get compiled to by cargo; so far we don't need to do anything special outside of that. A notice is shown to users if this fallback happens. * update from review
1 parent 2929ec4 commit 2138622

File tree

7 files changed

+255
-114
lines changed

7 files changed

+255
-114
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ vscode.lsif
1616
vscode.db
1717
/.profile-oss
1818
/cli/target
19+
/cli/openssl
1920
product.overrides.json

build/gulpfile.cli.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
'use strict';
7+
8+
//@ts-check
9+
10+
const es = require('event-stream');
11+
const gulp = require('gulp');
12+
const path = require('path');
13+
const fancyLog = require('fancy-log');
14+
const ansiColors = require('ansi-colors');
15+
const cp = require('child_process');
16+
const { tmpdir } = require('os');
17+
const { promises: fs, existsSync, mkdirSync, rmSync } = require('fs');
18+
19+
const task = require('./lib/task');
20+
const watcher = require('./lib/watch');
21+
const { debounce } = require('./lib/util');
22+
const createReporter = require('./lib/reporter').createReporter;
23+
24+
const root = 'cli';
25+
const rootAbs = path.resolve(__dirname, '..', root);
26+
const src = `${root}/src`;
27+
const targetCliPath = path.join(root, 'target', 'debug', process.platform === 'win32' ? 'code.exe' : 'code');
28+
29+
const platformOpensslDirName =
30+
process.platform === 'win32' ? (
31+
process.arch === 'arm64'
32+
? 'arm64-windows-static-md'
33+
: process.arch === 'ia32'
34+
? 'x86-windows-static-md'
35+
: 'x64-windows-static-md')
36+
: process.platform === 'darwin' ? (
37+
process.arch === 'arm64'
38+
? 'arm64-osx'
39+
: 'x64-osx')
40+
: (process.arch === 'arm64'
41+
? 'arm64-linux'
42+
: process.arch === 'arm'
43+
? 'arm-linux'
44+
: 'x64-linux');
45+
const platformOpensslDir = path.join(rootAbs, 'openssl', 'package', 'out', platformOpensslDirName);
46+
47+
const hasLocalRust = (() => {
48+
/** @type boolean | undefined */
49+
let result = undefined;
50+
return () => {
51+
if (result !== undefined) {
52+
return result;
53+
}
54+
55+
try {
56+
const r = cp.spawnSync('cargo', ['--version']);
57+
result = r.status === 0;
58+
} catch (e) {
59+
result = false;
60+
}
61+
62+
return result;
63+
};
64+
})();
65+
66+
const debounceEsStream = (fn, duration = 100) => {
67+
let handle = undefined;
68+
let pending = [];
69+
const sendAll = (pending) => (event, ...args) => {
70+
for (const stream of pending) {
71+
pending.emit(event, ...args);
72+
}
73+
};
74+
75+
return es.map(function (_, callback) {
76+
console.log('defer');
77+
if (handle !== undefined) {
78+
clearTimeout(handle);
79+
}
80+
81+
handle = setTimeout(() => {
82+
handle = undefined;
83+
84+
const previous = pending;
85+
pending = [];
86+
fn()
87+
.on('error', sendAll('error'))
88+
.on('data', sendAll('data'))
89+
.on('end', sendAll('end'));
90+
}, duration);
91+
92+
pending.push(this);
93+
});
94+
};
95+
96+
const compileFromSources = (callback) => {
97+
const proc = cp.spawn('cargo', ['--color', 'always', 'build'], {
98+
cwd: root,
99+
stdio: ['ignore', 'pipe', 'pipe'],
100+
env: existsSync(platformOpensslDir) ? { OPENSSL_DIR: platformOpensslDir, ...process.env } : process.env
101+
});
102+
103+
/** @type Buffer[] */
104+
const stdoutErr = [];
105+
proc.stdout.on('data', d => stdoutErr.push(d));
106+
proc.stderr.on('data', d => stdoutErr.push(d));
107+
proc.on('error', callback);
108+
proc.on('exit', code => {
109+
if (code !== 0) {
110+
callback(Buffer.concat(stdoutErr).toString());
111+
} else {
112+
callback();
113+
}
114+
});
115+
};
116+
117+
const acquireBuiltOpenSSL = (callback) => {
118+
const untar = require('gulp-untar');
119+
const gunzip = require('gulp-gunzip');
120+
const dir = path.join(tmpdir(), 'vscode-openssl-download');
121+
mkdirSync(dir, { recursive: true });
122+
123+
cp.spawnSync(
124+
process.platform === 'win32' ? 'npm.cmd' : 'npm',
125+
['pack', '@vscode/openssl-prebuilt'],
126+
{ stdio: ['ignore', 'ignore', 'inherit'], cwd: dir }
127+
);
128+
129+
gulp.src('*.tgz', { cwd: dir })
130+
.pipe(gunzip())
131+
.pipe(untar())
132+
.pipe(gulp.dest(`${root}/openssl`))
133+
.on('error', callback)
134+
.on('end', () => {
135+
rmSync(dir, { recursive: true, force: true });
136+
callback();
137+
});
138+
};
139+
140+
const compileWithOpenSSLCheck = (/** @type import('./lib/reporter').IReporter */ reporter) => es.map((_, callback) => {
141+
compileFromSources(err => {
142+
if (!err) {
143+
// no-op
144+
} else if (err.toString().includes('Could not find directory of OpenSSL installation') && !existsSync(platformOpensslDir)) {
145+
fancyLog(ansiColors.yellow(`[cli]`), 'OpenSSL libraries not found, acquiring prebuilt bits...');
146+
acquireBuiltOpenSSL(err => {
147+
if (err) {
148+
callback(err);
149+
} else {
150+
compileFromSources(err => {
151+
if (err) {
152+
reporter(err.toString());
153+
}
154+
callback(null, '');
155+
});
156+
}
157+
});
158+
} else {
159+
reporter(err.toString());
160+
}
161+
162+
callback(null, '');
163+
});
164+
});
165+
166+
const warnIfRustNotInstalled = () => {
167+
if (!hasLocalRust()) {
168+
fancyLog(ansiColors.yellow(`[cli]`), 'No local Rust install detected, compilation may fail.');
169+
fancyLog(ansiColors.yellow(`[cli]`), 'Get rust from: https://rustup.rs/');
170+
}
171+
};
172+
173+
const compileCliTask = task.define('compile-cli', () => {
174+
warnIfRustNotInstalled();
175+
const reporter = createReporter('cli');
176+
return gulp.src(`${root}/Cargo.toml`)
177+
.pipe(compileWithOpenSSLCheck(reporter))
178+
.pipe(reporter.end(true));
179+
});
180+
181+
182+
const watchCliTask = task.define('watch-cli', () => {
183+
warnIfRustNotInstalled();
184+
return watcher(`${src}/**`, { read: false })
185+
.pipe(debounce(compileCliTask));
186+
});
187+
188+
gulp.task(compileCliTask);
189+
gulp.task(watchCliTask);

build/lib/util.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/lib/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function incremental(streamProvider: IStreamProvider, initial: NodeJS.Rea
7777
return es.duplex(input, output);
7878
}
7979

80-
export function debounce(task: () => NodeJS.ReadWriteStream): NodeJS.ReadWriteStream {
80+
export function debounce(task: () => NodeJS.ReadWriteStream, duration = 500): NodeJS.ReadWriteStream {
8181
const input = es.through();
8282
const output = es.through();
8383
let state = 'idle';
@@ -99,7 +99,7 @@ export function debounce(task: () => NodeJS.ReadWriteStream): NodeJS.ReadWriteSt
9999

100100
run();
101101

102-
const eventuallyRun = _debounce(() => run(), 500);
102+
const eventuallyRun = _debounce(() => run(), duration);
103103

104104
input.on('data', () => {
105105
if (state === 'idle') {

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@
4545
"valid-layers-check": "node build/lib/layersChecker.js",
4646
"update-distro": "node build/npm/update-distro.mjs",
4747
"web": "echo 'yarn web' is replaced by './scripts/code-server' or './scripts/code-web'",
48+
"compile-cli": "gulp compile-cli",
4849
"compile-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-web",
4950
"watch-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-web",
51+
"watch-cli": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-cli",
5052
"eslint": "node build/eslint",
5153
"stylelint": "node build/stylelint",
5254
"playwright-install": "node build/azure-pipelines/common/installPlaywright.js",

test/smoke/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"mocha": "node ../node_modules/mocha/bin/mocha"
1212
},
1313
"dependencies": {
14-
"@vscode/test-electron": "^2.2.1",
14+
"@vscode/test-electron": "^2.3.2",
1515
"mkdirp": "^1.0.4",
1616
"ncp": "^2.0.0",
1717
"node-fetch": "^2.6.7",

0 commit comments

Comments
 (0)