Skip to content

Commit 955a042

Browse files
committed
cli: fallback to system installs in the standalone CLI
The standalone CLI should detect and fall back to using and system-installed VS Code instance, rather than trying to download zips and manage its own VS Code instances. There are three approaches used for discovery: - On Windows, we can easily and quickly read the register to find installed versions based on their app ID. - On macOS, we initially look in `/Applications` and fall back to the slow `system_profiler` command to list app .app's if that fails. - On Linux, we just look in the PATH. I believe all Linux installers (snap, dep, rpm) automatically add VS Code to the user's PATH. Failing this, the user can also manually specify their installation dir, using the command `code version use stable --install-dir /path/to/vscode`. Fixes microsoft#164159
1 parent 370050a commit 955a042

File tree

13 files changed

+382
-273
lines changed

13 files changed

+382
-273
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ log = "0.4"
4949

5050
[target.'cfg(windows)'.dependencies]
5151
windows-service = "0.5"
52+
winreg = "0.10"
5253

5354
[target.'cfg(target_os = "linux")'.dependencies]
5455
tar = { version = "0.4" }

src/bin/code/main.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ use cli::{
1111
commands::{args, tunnels, update, version, CommandContext},
1212
desktop, log as own_log,
1313
state::LauncherPaths,
14-
update_service::UpdateService,
1514
util::{
1615
errors::{wrap, AnyError},
1716
is_integrated_cli,
@@ -86,12 +85,7 @@ async fn main() -> Result<(), std::convert::Infallible> {
8685
args::VersionSubcommand::Use(use_version_args) => {
8786
version::switch_to(context, use_version_args).await
8887
}
89-
args::VersionSubcommand::Uninstall(uninstall_version_args) => {
90-
version::uninstall(context, uninstall_version_args).await
91-
}
92-
args::VersionSubcommand::List(list_version_args) => {
93-
version::list(context, list_version_args).await
94-
}
88+
args::VersionSubcommand::Show => version::show(context).await,
9589
},
9690

9791
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
@@ -126,9 +120,12 @@ where
126120
}
127121

128122
async fn start_code(context: CommandContext, args: Vec<String>) -> Result<i32, AnyError> {
123+
// todo: once the integrated CLI takes the place of the Node.js CLI, this should
124+
// redirect to the current installation without using the CodeVersionManager.
125+
129126
let platform = PreReqChecker::new().verify().await?;
130-
let version_manager = desktop::CodeVersionManager::new(&context.paths, platform);
131-
let update_service = UpdateService::new(context.log.clone(), context.http.clone());
127+
let version_manager =
128+
desktop::CodeVersionManager::new(context.log.clone(), &context.paths, platform);
132129
let version = match &context.args.editor_options.code_options.use_version {
133130
Some(v) => desktop::RequestedVersion::try_from(v.as_str())?,
134131
None => version_manager.get_preferred_version(),
@@ -137,16 +134,16 @@ async fn start_code(context: CommandContext, args: Vec<String>) -> Result<i32, A
137134
let binary = match version_manager.try_get_entrypoint(&version).await {
138135
Some(ep) => ep,
139136
None => {
140-
desktop::prompt_to_install(&version)?;
141-
version_manager.install(&update_service, &version).await?
137+
desktop::prompt_to_install(&version);
138+
return Ok(1);
142139
}
143140
};
144141

145-
let code = Command::new(binary)
142+
let code = Command::new(&binary)
146143
.args(args)
147144
.status()
148145
.map(|s| s.code().unwrap_or(1))
149-
.map_err(|e| wrap(e, "error running VS Code"))?;
146+
.map_err(|e| wrap(e, format!("error running VS Code from {}", binary.display())))?;
150147

151148
Ok(code)
152149
}

src/commands.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
mod context;
7-
mod output;
87

98
pub mod args;
109
pub mod tunnels;

src/commands/args.rs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,9 @@ pub struct VersionArgs {
253253
pub enum VersionSubcommand {
254254
/// Switches the instance of VS Code in use.
255255
Use(UseVersionArgs),
256-
/// Uninstalls a instance of VS Code.
257-
Uninstall(UninstallVersionArgs),
258-
/// Lists installed VS Code instances.
259-
List(OutputFormatOptions),
256+
257+
/// Shows the currently configured VS Code version.
258+
Show,
260259
}
261260

262261
#[derive(Args, Debug, Clone)]
@@ -266,21 +265,9 @@ pub struct UseVersionArgs {
266265
#[clap(value_name = "stable | insiders | x.y.z | path")]
267266
pub name: String,
268267

269-
/// The directory the version should be installed into, if it's not already installed.
268+
/// The directory where the version can be found.
270269
#[clap(long, value_name = "path")]
271270
pub install_dir: Option<String>,
272-
273-
/// Reinstall the version even if it's already installed.
274-
#[clap(long)]
275-
pub reinstall: bool,
276-
}
277-
278-
#[derive(Args, Debug, Clone)]
279-
pub struct UninstallVersionArgs {
280-
/// The version of VS Code to uninstall. Can be "stable", "insiders", or a
281-
/// version number previous passed to `code version use <version>`.
282-
#[clap(value_name = "stable | insiders | x.y.z")]
283-
pub name: String,
284271
}
285272

286273
#[derive(Args, Debug, Default, Clone)]

src/commands/tunnels.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
use std::fmt;
76
use async_trait::async_trait;
7+
use std::fmt;
88
use sysinfo::{Pid, SystemExt};
99
use tokio::sync::mpsc;
1010
use tokio::time::{sleep, Duration};
@@ -253,6 +253,9 @@ async fn serve_with_csa(
253253
info!(log, "checking for parent process {}", process_id);
254254
tokio::spawn(async move {
255255
let mut s = sysinfo::System::new();
256+
#[cfg(windows)]
257+
let pid = Pid::from(process_id as usize);
258+
#[cfg(unix)]
256259
let pid = Pid::from(process_id);
257260
while s.refresh_process(pid) {
258261
sleep(Duration::from_millis(2000)).await;

src/commands/version.rs

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,64 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
use std::path::{Path, PathBuf};
7+
68
use crate::{
7-
desktop::{CodeVersionManager, RequestedVersion},
9+
desktop::{prompt_to_install, CodeVersionManager, RequestedVersion},
810
log,
9-
update_service::UpdateService,
10-
util::{errors::AnyError, prereqs::PreReqChecker},
11+
util::{
12+
errors::{AnyError, NoInstallInUserProvidedPath},
13+
prereqs::PreReqChecker,
14+
},
1115
};
1216

13-
use super::{
14-
args::{OutputFormatOptions, UninstallVersionArgs, UseVersionArgs},
15-
output::{Column, OutputTable},
16-
CommandContext,
17-
};
17+
use super::{args::UseVersionArgs, CommandContext};
1818

1919
pub async fn switch_to(ctx: CommandContext, args: UseVersionArgs) -> Result<i32, AnyError> {
2020
let platform = PreReqChecker::new().verify().await?;
21-
let vm = CodeVersionManager::new(&ctx.paths, platform);
21+
let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform);
2222
let version = RequestedVersion::try_from(args.name.as_str())?;
2323

24-
if !args.reinstall && vm.try_get_entrypoint(&version).await.is_some() {
25-
vm.set_preferred_version(&version)?;
26-
print_now_using(&ctx.log, &version);
27-
return Ok(0);
28-
}
24+
let maybe_path = match args.install_dir {
25+
Some(d) => Some(
26+
CodeVersionManager::get_entrypoint_for_install_dir(&PathBuf::from(&d))
27+
.await
28+
.ok_or(NoInstallInUserProvidedPath(d))?,
29+
),
30+
None => vm.try_get_entrypoint(&version).await,
31+
};
2932

30-
let update_service = UpdateService::new(ctx.log.clone(), ctx.http.clone());
31-
vm.install(&update_service, &version).await?;
32-
vm.set_preferred_version(&version)?;
33-
print_now_using(&ctx.log, &version);
34-
Ok(0)
33+
match maybe_path {
34+
Some(p) => {
35+
vm.set_preferred_version(version.clone(), p.clone()).await?;
36+
print_now_using(&ctx.log, &version, &p);
37+
Ok(0)
38+
}
39+
None => {
40+
prompt_to_install(&version);
41+
Ok(1)
42+
}
43+
}
3544
}
3645

37-
pub async fn list(ctx: CommandContext, args: OutputFormatOptions) -> Result<i32, AnyError> {
46+
pub async fn show(ctx: CommandContext) -> Result<i32, AnyError> {
3847
let platform = PreReqChecker::new().verify().await?;
39-
let vm = CodeVersionManager::new(&ctx.paths, platform);
48+
let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform);
4049

41-
let mut name = Column::new("Installation");
42-
let mut command = Column::new("Command");
43-
for version in vm.list() {
44-
name.add_row(version.to_string());
45-
command.add_row(version.get_command());
50+
let version = vm.get_preferred_version();
51+
println!("Current quality: {}", version);
52+
match vm.try_get_entrypoint(&version).await {
53+
Some(p) => println!("Installation path: {}", p.display()),
54+
None => println!("No existing installation found"),
4655
}
47-
args.format
48-
.print_table(OutputTable::new(vec![name, command]))
49-
.ok();
5056

5157
Ok(0)
5258
}
5359

54-
pub async fn uninstall(ctx: CommandContext, args: UninstallVersionArgs) -> Result<i32, AnyError> {
55-
let platform = PreReqChecker::new().verify().await?;
56-
let vm = CodeVersionManager::new(&ctx.paths, platform);
57-
let version = RequestedVersion::try_from(args.name.as_str())?;
58-
vm.uninstall(&version).await?;
59-
ctx.log
60-
.result(&format!("VS Code {} uninstalled successfully", version));
61-
Ok(0)
62-
}
63-
64-
fn print_now_using(log: &log::Logger, version: &RequestedVersion) {
65-
log.result(&format!("Now using VS Code {}", version));
60+
fn print_now_using(log: &log::Logger, version: &RequestedVersion, path: &Path) {
61+
log.result(&format!(
62+
"Now using VS Code {} from {}",
63+
version,
64+
path.display()
65+
));
6666
}

src/constants.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
use std::collections::HashMap;
7+
68
use lazy_static::lazy_static;
79

10+
use crate::options::Quality;
11+
812
pub const CONTROL_PORT: u16 = 31545;
913
pub const PROTOCOL_VERSION: u32 = 1;
1014

@@ -18,6 +22,12 @@ pub const VSCODE_CLI_UPDATE_ENDPOINT: Option<&'static str> =
1822

1923
pub const TUNNEL_SERVICE_USER_AGENT_ENV_VAR: &str = "TUNNEL_SERVICE_USER_AGENT";
2024

25+
// JSON map of quality names to arrays of app IDs used for them, for example, `{"stable":["ABC123"]}`
26+
const VSCODE_CLI_WIN32_APP_IDS: Option<&'static str> = option_env!("VSCODE_CLI_WIN32_APP_IDS");
27+
// JSON map of quality names to download URIs
28+
const VSCODE_CLI_QUALITY_DOWNLOAD_URIS: Option<&'static str> =
29+
option_env!("VSCODE_CLI_QUALITY_DOWNLOAD_URIS");
30+
2131
pub fn get_default_user_agent() -> String {
2232
format!(
2333
"vscode-server-launcher/{}",
@@ -31,4 +41,8 @@ lazy_static! {
3141
Ok(ua) if !ua.is_empty() => format!("{} {}", ua, get_default_user_agent()),
3242
_ => get_default_user_agent(),
3343
};
44+
pub static ref WIN32_APP_IDS: Option<HashMap<Quality, Vec<String>>> =
45+
VSCODE_CLI_WIN32_APP_IDS.and_then(|s| serde_json::from_str(s).unwrap());
46+
pub static ref QUALITY_DOWNLOAD_URIS: Option<HashMap<Quality, String>> =
47+
VSCODE_CLI_QUALITY_DOWNLOAD_URIS.and_then(|s| serde_json::from_str(s).unwrap());
3448
}

0 commit comments

Comments
 (0)