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 #164159
This commit is contained in:
Родитель
9be4128f5b
Коммит
c536595a7f
|
@ -8,17 +8,28 @@ const getVersion_1 = require("../../lib/getVersion");
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const packageJson = require("../../../package.json");
|
||||
const root = path.dirname(path.dirname(path.dirname(__dirname)));
|
||||
const root = path.dirname(path.dirname(path.dirname(__dirname))) + '/../vscode-distro';
|
||||
const readJSON = (path) => JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
let productJsonPath;
|
||||
if (process.env.VSCODE_QUALITY === 'oss' || !process.env.VSCODE_QUALITY) {
|
||||
const isOSS = process.env.VSCODE_QUALITY === 'oss' || !process.env.VSCODE_QUALITY;
|
||||
if (isOSS) {
|
||||
productJsonPath = path.join(root, 'product.json');
|
||||
}
|
||||
else {
|
||||
productJsonPath = path.join(root, 'quality', process.env.VSCODE_QUALITY, 'product.json');
|
||||
}
|
||||
console.log('Loading product.json from', productJsonPath);
|
||||
const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8'));
|
||||
const product = readJSON(productJsonPath);
|
||||
const allProductsAndQualities = isOSS ? [product] : fs.readdirSync(path.join(root, 'quality'))
|
||||
.map(quality => ({ quality, json: readJSON(path.join(root, 'quality', quality, 'product.json')) }));
|
||||
const commit = (0, getVersion_1.getVersion)(root);
|
||||
const makeQualityMap = (m) => {
|
||||
const output = {};
|
||||
for (const { quality, json } of allProductsAndQualities) {
|
||||
output[quality] = m(json, quality);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
/**
|
||||
* Sets build environment variables for the CLI for current contextual info.
|
||||
*/
|
||||
|
@ -32,7 +43,18 @@ const setLauncherEnvironmentVars = () => {
|
|||
['VSCODE_CLI_UPDATE_ENDPOINT', product.updateUrl],
|
||||
['VSCODE_CLI_QUALITY', product.quality],
|
||||
['VSCODE_CLI_COMMIT', commit],
|
||||
[
|
||||
'VSCODE_CLI_WIN32_APP_IDS',
|
||||
!isOSS && JSON.stringify(makeQualityMap(json => Object.entries(json)
|
||||
.filter(([key]) => /^win32.*AppId$/.test(key))
|
||||
.map(([, value]) => String(value).replace(/[{}]/g, '')))),
|
||||
],
|
||||
[
|
||||
'VSCODE_CLI_QUALITY_DOWNLOAD_URIS',
|
||||
!isOSS && JSON.stringify(makeQualityMap(json => json.downloadUrl)),
|
||||
],
|
||||
]);
|
||||
console.log(JSON.stringify([...vars].reduce((obj, kv) => ({ ...obj, [kv[0]]: kv[1] }), {})));
|
||||
for (const [key, value] of vars) {
|
||||
if (value) {
|
||||
console.log(`##vso[task.setvariable variable=${key}]${value}`);
|
||||
|
|
|
@ -8,19 +8,32 @@ import * as fs from 'fs';
|
|||
import * as path from 'path';
|
||||
import * as packageJson from '../../../package.json';
|
||||
|
||||
const root = path.dirname(path.dirname(path.dirname(__dirname)));
|
||||
const root = path.dirname(path.dirname(path.dirname(__dirname))) + '/../vscode-distro';
|
||||
const readJSON = (path: string) => JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
|
||||
let productJsonPath: string;
|
||||
if (process.env.VSCODE_QUALITY === 'oss' || !process.env.VSCODE_QUALITY) {
|
||||
const isOSS = process.env.VSCODE_QUALITY === 'oss' || !process.env.VSCODE_QUALITY;
|
||||
if (isOSS) {
|
||||
productJsonPath = path.join(root, 'product.json');
|
||||
} else {
|
||||
productJsonPath = path.join(root, 'quality', process.env.VSCODE_QUALITY, 'product.json');
|
||||
productJsonPath = path.join(root, 'quality', process.env.VSCODE_QUALITY!, 'product.json');
|
||||
}
|
||||
|
||||
|
||||
console.log('Loading product.json from', productJsonPath);
|
||||
const product = JSON.parse(fs.readFileSync(productJsonPath, 'utf8'));
|
||||
const product = readJSON(productJsonPath);
|
||||
const allProductsAndQualities = isOSS ? [product] : fs.readdirSync(path.join(root, 'quality'))
|
||||
.map(quality => ({ quality, json: readJSON(path.join(root, 'quality', quality, 'product.json')) }));
|
||||
const commit = getVersion(root);
|
||||
|
||||
const makeQualityMap = <T>(m: (productJson: any, quality: string) => T): Record<string, T> => {
|
||||
const output: Record<string, T> = {};
|
||||
for (const { quality, json } of allProductsAndQualities) {
|
||||
output[quality] = m(json, quality);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets build environment variables for the CLI for current contextual info.
|
||||
*/
|
||||
|
@ -34,8 +47,22 @@ const setLauncherEnvironmentVars = () => {
|
|||
['VSCODE_CLI_UPDATE_ENDPOINT', product.updateUrl],
|
||||
['VSCODE_CLI_QUALITY', product.quality],
|
||||
['VSCODE_CLI_COMMIT', commit],
|
||||
[
|
||||
'VSCODE_CLI_WIN32_APP_IDS',
|
||||
!isOSS && JSON.stringify(
|
||||
makeQualityMap(json => Object.entries(json)
|
||||
.filter(([key]) => /^win32.*AppId$/.test(key))
|
||||
.map(([, value]) => String(value).replace(/[{}]/g, ''))),
|
||||
),
|
||||
],
|
||||
[
|
||||
'VSCODE_CLI_QUALITY_DOWNLOAD_URIS',
|
||||
!isOSS && JSON.stringify(makeQualityMap(json => json.downloadUrl)),
|
||||
],
|
||||
]);
|
||||
|
||||
console.log(JSON.stringify([...vars].reduce((obj, kv) => ({...obj, [kv[0]]: kv[1]}), {})));
|
||||
|
||||
for (const [key, value] of vars) {
|
||||
if (value) {
|
||||
console.log(`##vso[task.setvariable variable=${key}]${value}`);
|
||||
|
|
|
@ -240,6 +240,7 @@ dependencies = [
|
|||
"url",
|
||||
"uuid",
|
||||
"windows-service",
|
||||
"winreg",
|
||||
"zip",
|
||||
]
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ log = "0.4"
|
|||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-service = "0.5"
|
||||
winreg = "0.10"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tar = { version = "0.4" }
|
||||
|
|
|
@ -11,7 +11,6 @@ use cli::{
|
|||
commands::{args, tunnels, update, version, CommandContext},
|
||||
desktop, log as own_log,
|
||||
state::LauncherPaths,
|
||||
update_service::UpdateService,
|
||||
util::{
|
||||
errors::{wrap, AnyError},
|
||||
is_integrated_cli,
|
||||
|
@ -86,12 +85,7 @@ async fn main() -> Result<(), std::convert::Infallible> {
|
|||
args::VersionSubcommand::Use(use_version_args) => {
|
||||
version::switch_to(context, use_version_args).await
|
||||
}
|
||||
args::VersionSubcommand::Uninstall(uninstall_version_args) => {
|
||||
version::uninstall(context, uninstall_version_args).await
|
||||
}
|
||||
args::VersionSubcommand::List(list_version_args) => {
|
||||
version::list(context, list_version_args).await
|
||||
}
|
||||
args::VersionSubcommand::Show => version::show(context).await,
|
||||
},
|
||||
|
||||
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
|
||||
|
@ -126,9 +120,12 @@ where
|
|||
}
|
||||
|
||||
async fn start_code(context: CommandContext, args: Vec<String>) -> Result<i32, AnyError> {
|
||||
// todo: once the integrated CLI takes the place of the Node.js CLI, this should
|
||||
// redirect to the current installation without using the CodeVersionManager.
|
||||
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let version_manager = desktop::CodeVersionManager::new(&context.paths, platform);
|
||||
let update_service = UpdateService::new(context.log.clone(), context.http.clone());
|
||||
let version_manager =
|
||||
desktop::CodeVersionManager::new(context.log.clone(), &context.paths, platform);
|
||||
let version = match &context.args.editor_options.code_options.use_version {
|
||||
Some(v) => desktop::RequestedVersion::try_from(v.as_str())?,
|
||||
None => version_manager.get_preferred_version(),
|
||||
|
@ -137,16 +134,16 @@ async fn start_code(context: CommandContext, args: Vec<String>) -> Result<i32, A
|
|||
let binary = match version_manager.try_get_entrypoint(&version).await {
|
||||
Some(ep) => ep,
|
||||
None => {
|
||||
desktop::prompt_to_install(&version)?;
|
||||
version_manager.install(&update_service, &version).await?
|
||||
desktop::prompt_to_install(&version);
|
||||
return Ok(1);
|
||||
}
|
||||
};
|
||||
|
||||
let code = Command::new(binary)
|
||||
let code = Command::new(&binary)
|
||||
.args(args)
|
||||
.status()
|
||||
.map(|s| s.code().unwrap_or(1))
|
||||
.map_err(|e| wrap(e, "error running VS Code"))?;
|
||||
.map_err(|e| wrap(e, format!("error running VS Code from {}", binary.display())))?;
|
||||
|
||||
Ok(code)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
mod context;
|
||||
mod output;
|
||||
|
||||
pub mod args;
|
||||
pub mod tunnels;
|
||||
|
|
|
@ -253,10 +253,9 @@ pub struct VersionArgs {
|
|||
pub enum VersionSubcommand {
|
||||
/// Switches the instance of VS Code in use.
|
||||
Use(UseVersionArgs),
|
||||
/// Uninstalls a instance of VS Code.
|
||||
Uninstall(UninstallVersionArgs),
|
||||
/// Lists installed VS Code instances.
|
||||
List(OutputFormatOptions),
|
||||
|
||||
/// Shows the currently configured VS Code version.
|
||||
Show,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
|
@ -266,21 +265,9 @@ pub struct UseVersionArgs {
|
|||
#[clap(value_name = "stable | insiders | x.y.z | path")]
|
||||
pub name: String,
|
||||
|
||||
/// The directory the version should be installed into, if it's not already installed.
|
||||
/// The directory where the version can be found.
|
||||
#[clap(long, value_name = "path")]
|
||||
pub install_dir: Option<String>,
|
||||
|
||||
/// Reinstall the version even if it's already installed.
|
||||
#[clap(long)]
|
||||
pub reinstall: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct UninstallVersionArgs {
|
||||
/// The version of VS Code to uninstall. Can be "stable", "insiders", or a
|
||||
/// version number previous passed to `code version use <version>`.
|
||||
#[clap(value_name = "stable | insiders | x.y.z")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::fmt;
|
||||
use async_trait::async_trait;
|
||||
use std::fmt;
|
||||
use sysinfo::{Pid, SystemExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
@ -253,6 +253,9 @@ async fn serve_with_csa(
|
|||
info!(log, "checking for parent process {}", process_id);
|
||||
tokio::spawn(async move {
|
||||
let mut s = sysinfo::System::new();
|
||||
#[cfg(windows)]
|
||||
let pid = Pid::from(process_id as usize);
|
||||
#[cfg(unix)]
|
||||
let pid = Pid::from(process_id);
|
||||
while s.refresh_process(pid) {
|
||||
sleep(Duration::from_millis(2000)).await;
|
||||
|
|
|
@ -3,64 +3,64 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{
|
||||
desktop::{CodeVersionManager, RequestedVersion},
|
||||
desktop::{prompt_to_install, CodeVersionManager, RequestedVersion},
|
||||
log,
|
||||
update_service::UpdateService,
|
||||
util::{errors::AnyError, prereqs::PreReqChecker},
|
||||
util::{
|
||||
errors::{AnyError, NoInstallInUserProvidedPath},
|
||||
prereqs::PreReqChecker,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
args::{OutputFormatOptions, UninstallVersionArgs, UseVersionArgs},
|
||||
output::{Column, OutputTable},
|
||||
CommandContext,
|
||||
};
|
||||
use super::{args::UseVersionArgs, CommandContext};
|
||||
|
||||
pub async fn switch_to(ctx: CommandContext, args: UseVersionArgs) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(&ctx.paths, platform);
|
||||
let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform);
|
||||
let version = RequestedVersion::try_from(args.name.as_str())?;
|
||||
|
||||
if !args.reinstall && vm.try_get_entrypoint(&version).await.is_some() {
|
||||
vm.set_preferred_version(&version)?;
|
||||
print_now_using(&ctx.log, &version);
|
||||
return Ok(0);
|
||||
let maybe_path = match args.install_dir {
|
||||
Some(d) => Some(
|
||||
CodeVersionManager::get_entrypoint_for_install_dir(&PathBuf::from(&d))
|
||||
.await
|
||||
.ok_or(NoInstallInUserProvidedPath(d))?,
|
||||
),
|
||||
None => vm.try_get_entrypoint(&version).await,
|
||||
};
|
||||
|
||||
match maybe_path {
|
||||
Some(p) => {
|
||||
vm.set_preferred_version(version.clone(), p.clone()).await?;
|
||||
print_now_using(&ctx.log, &version, &p);
|
||||
Ok(0)
|
||||
}
|
||||
None => {
|
||||
prompt_to_install(&version);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn show(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform);
|
||||
|
||||
let version = vm.get_preferred_version();
|
||||
println!("Current quality: {}", version);
|
||||
match vm.try_get_entrypoint(&version).await {
|
||||
Some(p) => println!("Installation path: {}", p.display()),
|
||||
None => println!("No existing installation found"),
|
||||
}
|
||||
|
||||
let update_service = UpdateService::new(ctx.log.clone(), ctx.http.clone());
|
||||
vm.install(&update_service, &version).await?;
|
||||
vm.set_preferred_version(&version)?;
|
||||
print_now_using(&ctx.log, &version);
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn list(ctx: CommandContext, args: OutputFormatOptions) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(&ctx.paths, platform);
|
||||
|
||||
let mut name = Column::new("Installation");
|
||||
let mut command = Column::new("Command");
|
||||
for version in vm.list() {
|
||||
name.add_row(version.to_string());
|
||||
command.add_row(version.get_command());
|
||||
}
|
||||
args.format
|
||||
.print_table(OutputTable::new(vec![name, command]))
|
||||
.ok();
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn uninstall(ctx: CommandContext, args: UninstallVersionArgs) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(&ctx.paths, platform);
|
||||
let version = RequestedVersion::try_from(args.name.as_str())?;
|
||||
vm.uninstall(&version).await?;
|
||||
ctx.log
|
||||
.result(&format!("VS Code {} uninstalled successfully", version));
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn print_now_using(log: &log::Logger, version: &RequestedVersion) {
|
||||
log.result(&format!("Now using VS Code {}", version));
|
||||
fn print_now_using(log: &log::Logger, version: &RequestedVersion, path: &Path) {
|
||||
log.result(&format!(
|
||||
"Now using VS Code {} from {}",
|
||||
version,
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::options::Quality;
|
||||
|
||||
pub const CONTROL_PORT: u16 = 31545;
|
||||
pub const PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
|
@ -18,6 +22,12 @@ pub const VSCODE_CLI_UPDATE_ENDPOINT: Option<&'static str> =
|
|||
|
||||
pub const TUNNEL_SERVICE_USER_AGENT_ENV_VAR: &str = "TUNNEL_SERVICE_USER_AGENT";
|
||||
|
||||
// JSON map of quality names to arrays of app IDs used for them, for example, `{"stable":["ABC123"]}`
|
||||
const VSCODE_CLI_WIN32_APP_IDS: Option<&'static str> = option_env!("VSCODE_CLI_WIN32_APP_IDS");
|
||||
// JSON map of quality names to download URIs
|
||||
const VSCODE_CLI_QUALITY_DOWNLOAD_URIS: Option<&'static str> =
|
||||
option_env!("VSCODE_CLI_QUALITY_DOWNLOAD_URIS");
|
||||
|
||||
pub fn get_default_user_agent() -> String {
|
||||
format!(
|
||||
"vscode-server-launcher/{}",
|
||||
|
@ -31,4 +41,8 @@ lazy_static! {
|
|||
Ok(ua) if !ua.is_empty() => format!("{} {}", ua, get_default_user_agent()),
|
||||
_ => get_default_user_agent(),
|
||||
};
|
||||
pub static ref WIN32_APP_IDS: Option<HashMap<Quality, Vec<String>>> =
|
||||
VSCODE_CLI_WIN32_APP_IDS.and_then(|s| serde_json::from_str(s).unwrap());
|
||||
pub static ref QUALITY_DOWNLOAD_URIS: Option<HashMap<Quality, String>> =
|
||||
VSCODE_CLI_QUALITY_DOWNLOAD_URIS.and_then(|s| serde_json::from_str(s).unwrap());
|
||||
}
|
||||
|
|
|
@ -4,28 +4,22 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
ffi::OsString,
|
||||
fmt, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::remove_dir_all;
|
||||
|
||||
use crate::{
|
||||
options,
|
||||
constants::QUALITY_DOWNLOAD_URIS,
|
||||
log,
|
||||
options::{self, Quality},
|
||||
state::{LauncherPaths, PersistedState},
|
||||
update_service::{unzip_downloaded_release, Platform, Release, TargetKind, UpdateService},
|
||||
util::{
|
||||
errors::{
|
||||
wrap, AnyError, InvalidRequestedVersion, MissingEntrypointError,
|
||||
NoInstallInUserProvidedPath, UserCancelledInstallation, WrappedError,
|
||||
},
|
||||
http,
|
||||
input::{prompt_yn, ProgressBarReporter},
|
||||
},
|
||||
update_service::Platform,
|
||||
util::errors::{AnyError, InvalidRequestedVersion},
|
||||
};
|
||||
|
||||
/// Parsed instance that a user can request.
|
||||
|
@ -122,205 +116,309 @@ impl TryFrom<&str> for RequestedVersion {
|
|||
|
||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||
struct Stored {
|
||||
versions: Vec<RequestedVersion>,
|
||||
/// Map of requested versions to locations where those versions are installed.
|
||||
versions: Vec<(RequestedVersion, OsString)>,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
pub struct CodeVersionManager {
|
||||
state: PersistedState<Stored>,
|
||||
platform: Platform,
|
||||
storage_dir: PathBuf,
|
||||
log: log::Logger,
|
||||
}
|
||||
|
||||
impl CodeVersionManager {
|
||||
pub fn new(lp: &LauncherPaths, platform: Platform) -> Self {
|
||||
pub fn new(log: log::Logger, lp: &LauncherPaths, _platform: Platform) -> Self {
|
||||
CodeVersionManager {
|
||||
log,
|
||||
state: PersistedState::new(lp.root().join("versions.json")),
|
||||
storage_dir: lp.root().join("desktop"),
|
||||
platform,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to find the binary entrypoint for VS Code installed in the path.
|
||||
pub async fn get_entrypoint_for_install_dir(path: &Path) -> Option<PathBuf> {
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
// Look for all the possible paths in parallel
|
||||
for entry in DESKTOP_CLI_RELATIVE_PATH.split(',') {
|
||||
let my_path = path.join(entry);
|
||||
let my_tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
if tokio::fs::metadata(&my_path).await.is_ok() {
|
||||
my_tx.send(my_path).await.ok();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
drop(tx); // drop so rx gets None if no sender emits
|
||||
|
||||
rx.recv().await
|
||||
}
|
||||
|
||||
/// Sets the "version" as the persisted one for the user.
|
||||
pub fn set_preferred_version(&self, version: &RequestedVersion) -> Result<(), AnyError> {
|
||||
pub async fn set_preferred_version(
|
||||
&self,
|
||||
version: RequestedVersion,
|
||||
path: PathBuf,
|
||||
) -> Result<(), AnyError> {
|
||||
let mut stored = self.state.load();
|
||||
if let Some(i) = stored.versions.iter().position(|v| v == version) {
|
||||
stored.current = i;
|
||||
} else {
|
||||
stored.current = stored.versions.len();
|
||||
stored.versions.push(version.clone());
|
||||
}
|
||||
|
||||
stored.current = self.store_version_path(&mut stored, version, path);
|
||||
self.state.save(stored)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lists installed versions.
|
||||
pub fn list(&self) -> Vec<RequestedVersion> {
|
||||
self.state.load().versions
|
||||
}
|
||||
|
||||
/// Uninstalls a previously installed version.
|
||||
pub async fn uninstall(&self, version: &RequestedVersion) -> Result<(), AnyError> {
|
||||
let mut stored = self.state.load();
|
||||
if let Some(i) = stored.versions.iter().position(|v| v == version) {
|
||||
if i > stored.current && i > 0 {
|
||||
stored.current -= 1;
|
||||
}
|
||||
stored.versions.remove(i);
|
||||
self.state.save(stored)?;
|
||||
/// Stores or updates the path used for the given version. Returns the index
|
||||
/// that the path exists at.
|
||||
fn store_version_path(
|
||||
&self,
|
||||
state: &mut Stored,
|
||||
version: RequestedVersion,
|
||||
path: PathBuf,
|
||||
) -> usize {
|
||||
if let Some(i) = state.versions.iter().position(|(v, _)| v == &version) {
|
||||
state.versions[i].1 = path.into_os_string();
|
||||
i
|
||||
} else {
|
||||
state
|
||||
.versions
|
||||
.push((version.clone(), path.into_os_string()));
|
||||
state.versions.len() - 1
|
||||
}
|
||||
|
||||
remove_dir_all(self.get_install_dir(version))
|
||||
.await
|
||||
.map_err(|e| wrap(e, "error deleting vscode directory"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gets the currently preferred version based on set_preferred_version.
|
||||
pub fn get_preferred_version(&self) -> RequestedVersion {
|
||||
let stored = self.state.load();
|
||||
stored
|
||||
.versions
|
||||
.get(stored.current)
|
||||
.unwrap_or(&RequestedVersion::Quality(options::Quality::Stable))
|
||||
.clone()
|
||||
.map(|(v, _)| v.clone())
|
||||
.unwrap_or(RequestedVersion::Quality(options::Quality::Stable))
|
||||
}
|
||||
|
||||
/// Installs the release for the given request. This always runs and does not
|
||||
/// prompt, so you may want to use `try_get_entrypoint` first.
|
||||
pub async fn install(
|
||||
&self,
|
||||
update_service: &UpdateService,
|
||||
version: &RequestedVersion,
|
||||
) -> Result<PathBuf, AnyError> {
|
||||
let target_dir = self.get_install_dir(version);
|
||||
let release = get_release_for_request(update_service, version, self.platform).await?;
|
||||
install_release_into(update_service, &target_dir, &release).await?;
|
||||
|
||||
if let Some(p) = try_get_entrypoint(&target_dir).await {
|
||||
return Ok(p);
|
||||
/// Tries to get the entrypoint for the version, if one can be found.
|
||||
pub async fn try_get_entrypoint(&self, version: &RequestedVersion) -> Option<PathBuf> {
|
||||
let mut state = self.state.load();
|
||||
if let Some((_, install_path)) = state.versions.iter().find(|(v, _)| v == version) {
|
||||
let p = PathBuf::from(install_path);
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
|
||||
Err(MissingEntrypointError().into())
|
||||
}
|
||||
|
||||
/// Tries to get the entrypoint in the installed version, if one exists.
|
||||
pub async fn try_get_entrypoint(&self, version: &RequestedVersion) -> Option<PathBuf> {
|
||||
try_get_entrypoint(&self.get_install_dir(version)).await
|
||||
}
|
||||
|
||||
fn get_install_dir(&self, version: &RequestedVersion) -> PathBuf {
|
||||
let (name, quality) = match version {
|
||||
RequestedVersion::Path(path) => return PathBuf::from(path),
|
||||
RequestedVersion::Quality(quality) => (quality.get_machine_name(), quality),
|
||||
RequestedVersion::Version {
|
||||
quality,
|
||||
version: number,
|
||||
} => (number.as_str(), quality),
|
||||
RequestedVersion::Commit { commit, quality } => (commit.as_str(), quality),
|
||||
// For simple quality requests, see if that's installed already on the system
|
||||
let candidates = match &version {
|
||||
RequestedVersion::Quality(q) => match detect_installed_program(&self.log, *q) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
warning!(self.log, "error looking up installed applications: {}", e);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mut dir = self.storage_dir.join(name);
|
||||
if cfg!(target_os = "macos") {
|
||||
dir.push(format!("{}.app", quality.get_app_name()))
|
||||
let found = match candidates.into_iter().next() {
|
||||
Some(p) => p,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// stash the found path for faster lookup
|
||||
self.store_version_path(&mut state, version.clone(), found.clone());
|
||||
if let Err(e) = self.state.save(state) {
|
||||
debug!(self.log, "error caching version path: {}", e);
|
||||
}
|
||||
|
||||
dir
|
||||
Some(found)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows a nice UI prompt to users asking them if they want to install the
|
||||
/// requested version.
|
||||
pub fn prompt_to_install(version: &RequestedVersion) -> Result<(), AnyError> {
|
||||
if let RequestedVersion::Path(path) = version {
|
||||
return Err(NoInstallInUserProvidedPath(path.clone()).into());
|
||||
pub fn prompt_to_install(version: &RequestedVersion) {
|
||||
println!("No installation of VS Code {} was found.", version);
|
||||
|
||||
if let RequestedVersion::Quality(quality) = version {
|
||||
if let Some(uri) = QUALITY_DOWNLOAD_URIS.as_ref().and_then(|m| m.get(quality)) {
|
||||
// todo: on some platforms, we may be able to help automate installation. For example,
|
||||
// we can unzip the app ourselves on macOS and on windows we can download and spawn the GUI installer
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("Install it from your system's package manager or {}, restart your shell, and try again.", uri);
|
||||
#[cfg(target_os = "macos")]
|
||||
println!("Download and unzip it from {} and try again.", uri);
|
||||
#[cfg(target_os = "windows")]
|
||||
println!("Install it from {} and try again.", uri);
|
||||
}
|
||||
}
|
||||
|
||||
if !prompt_yn(&format!(
|
||||
"VS Code {} is not installed yet, install it now?",
|
||||
version
|
||||
))? {
|
||||
return Err(UserCancelledInstallation().into());
|
||||
println!();
|
||||
println!("If you already installed VS Code and we didn't detect it, run `{} --install-dir /path/to/installation`", version.get_command());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn detect_installed_program(log: &log::Logger, quality: Quality) -> io::Result<Vec<PathBuf>> {
|
||||
// easy, fast detection for where apps are usually installed
|
||||
let mut probable = PathBuf::from("/Applications");
|
||||
let app_name = quality.get_macos_app_name();
|
||||
probable.push(format!("{}.app", app_name));
|
||||
if probable.exists() {
|
||||
probable.extend(["Contents/Resources", "app", "bin", "code"]);
|
||||
return Ok(vec![probable]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
// _Much_ slower detection using the system_profiler (~10s for me). While the
|
||||
// profiler can output nicely structure plist xml, pulling in an xml parser
|
||||
// just for this is overkill. The default output looks something like...
|
||||
//
|
||||
// Visual Studio Code - Exploration 2:
|
||||
//
|
||||
// Version: 1.73.0-exploration
|
||||
// Obtained from: Identified Developer
|
||||
// Last Modified: 9/23/22, 10:16 AM
|
||||
// Kind: Intel
|
||||
// Signed by: Developer ID Application: Microsoft Corporation (UBF8T346G9), Developer ID Certification Authority, Apple Root CA
|
||||
// Location: /Users/connor/Downloads/Visual Studio Code - Exploration 2.app
|
||||
//
|
||||
// So, use a simple state machine that looks for the first line, and then for
|
||||
// the `Location:` line for the path.
|
||||
info!(log, "Searching for installations on your machine, this is done once and will take about 10 seconds...");
|
||||
|
||||
async fn get_release_for_request(
|
||||
update_service: &UpdateService,
|
||||
request: &RequestedVersion,
|
||||
platform: Platform,
|
||||
) -> Result<Release, WrappedError> {
|
||||
match request {
|
||||
RequestedVersion::Version {
|
||||
quality,
|
||||
version: number,
|
||||
} => update_service
|
||||
.get_release_by_semver_version(platform, TargetKind::Archive, *quality, number)
|
||||
.await
|
||||
.map_err(|e| wrap(e, "Could not get release")),
|
||||
RequestedVersion::Commit { commit, quality } => Ok(Release {
|
||||
platform,
|
||||
commit: commit.clone(),
|
||||
quality: *quality,
|
||||
name: "".to_string(),
|
||||
target: TargetKind::Archive,
|
||||
}),
|
||||
RequestedVersion::Quality(quality) => update_service
|
||||
.get_latest_commit(platform, TargetKind::Archive, *quality)
|
||||
.await
|
||||
.map_err(|e| wrap(e, "Could not get release")),
|
||||
_ => panic!("cannot get release info for a path"),
|
||||
let stdout = std::process::Command::new("system_profiler")
|
||||
.args(["SPApplicationsDataType", "-detailLevel", "mini"])
|
||||
.output()?
|
||||
.stdout;
|
||||
|
||||
enum State {
|
||||
LookingForName,
|
||||
LookingForLocation,
|
||||
}
|
||||
}
|
||||
|
||||
async fn install_release_into(
|
||||
update_service: &UpdateService,
|
||||
path: &Path,
|
||||
release: &Release,
|
||||
) -> Result<(), AnyError> {
|
||||
let tempdir =
|
||||
tempfile::tempdir().map_err(|e| wrap(e, "error creating temporary download dir"))?;
|
||||
let save_path = tempdir.path().join("vscode");
|
||||
|
||||
let stream = update_service.get_download_stream(release).await?;
|
||||
let pb = ProgressBar::new(1);
|
||||
pb.set_message("Downloading...");
|
||||
let progress = ProgressBarReporter::from(pb);
|
||||
http::download_into_file(&save_path, progress, stream).await?;
|
||||
|
||||
let pb = ProgressBar::new(1);
|
||||
pb.set_message("Unzipping...");
|
||||
let progress = ProgressBarReporter::from(pb);
|
||||
unzip_downloaded_release(&save_path, path, progress)?;
|
||||
|
||||
drop(tempdir);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to find the binary entrypoint for VS Code installed in the path.
|
||||
async fn try_get_entrypoint(path: &Path) -> Option<PathBuf> {
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
// Look for all the possible paths in parallel
|
||||
for entry in DESKTOP_CLI_RELATIVE_PATH.split(',') {
|
||||
let my_path = path.join(entry);
|
||||
let my_tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
if tokio::fs::metadata(&my_path).await.is_ok() {
|
||||
my_tx.send(my_path).await.ok();
|
||||
let mut state = State::LookingForName;
|
||||
let mut output: Vec<PathBuf> = vec![];
|
||||
const LOCATION_PREFIX: &str = "Location:";
|
||||
for mut line in String::from_utf8_lossy(&stdout).lines() {
|
||||
line = line.trim();
|
||||
match state {
|
||||
State::LookingForName => {
|
||||
if line.starts_with(app_name) && line.ends_with(':') {
|
||||
state = State::LookingForLocation;
|
||||
}
|
||||
}
|
||||
});
|
||||
State::LookingForLocation => {
|
||||
if line.starts_with(LOCATION_PREFIX) {
|
||||
output.push(
|
||||
[
|
||||
&line[LOCATION_PREFIX.len()..].trim(),
|
||||
"Contents/Resources",
|
||||
"app",
|
||||
"bin",
|
||||
"code",
|
||||
]
|
||||
.iter()
|
||||
.collect(),
|
||||
);
|
||||
state = State::LookingForName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drop(tx); // drop so rx gets None if no sender emits
|
||||
// Sort shorter paths to the front, preferring "more global" installs, and
|
||||
// incidentally preferring local installs over Parallels 'installs'.
|
||||
output.sort_by(|a, b| a.as_os_str().len().cmp(&b.as_os_str().len()));
|
||||
|
||||
rx.recv().await
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn detect_installed_program(_log: &log::Logger, quality: Quality) -> io::Result<Vec<PathBuf>> {
|
||||
use crate::constants::WIN32_APP_IDS;
|
||||
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||
use winreg::RegKey;
|
||||
|
||||
let mut output: Vec<PathBuf> = vec![];
|
||||
let app_ids = match WIN32_APP_IDS.as_ref().and_then(|m| m.get(&quality)) {
|
||||
Some(ids) => ids,
|
||||
None => return Ok(output),
|
||||
};
|
||||
|
||||
let scopes = [
|
||||
(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
|
||||
),
|
||||
(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
|
||||
),
|
||||
(
|
||||
HKEY_CURRENT_USER,
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
|
||||
),
|
||||
];
|
||||
|
||||
for (scope, key) in scopes {
|
||||
let cur_ver = match RegKey::predef(scope).open_subkey(key) {
|
||||
Ok(k) => k,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
for key in cur_ver.enum_keys().flatten() {
|
||||
if app_ids.iter().any(|id| key.contains(id)) {
|
||||
let sk = cur_ver.open_subkey(&key)?;
|
||||
if let Ok(location) = sk.get_value::<String, _>("InstallLocation") {
|
||||
output.push(
|
||||
[
|
||||
location.as_str(),
|
||||
"bin",
|
||||
match quality {
|
||||
Quality::Exploration => "code-exploration.cmd",
|
||||
Quality::Insiders => "code-insiders.cmd",
|
||||
Quality::Stable => "code.cmd",
|
||||
},
|
||||
]
|
||||
.iter()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
// Looks for the given binary name in the PATH, returning all candidate matches.
|
||||
// Based on https://github.dev/microsoft/vscode-js-debug/blob/7594d05518df6700df51771895fcad0ddc7f92f9/src/common/pathUtils.ts#L15
|
||||
#[cfg(target_os = "linux")]
|
||||
fn detect_installed_program(log: &log::Logger, quality: Quality) -> io::Result<Vec<PathBuf>> {
|
||||
let path = match std::env::var("PATH") {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
info!(log, "PATH is empty ({}), skipping detection", e);
|
||||
return Ok(vec![]);
|
||||
}
|
||||
};
|
||||
|
||||
let name = quality.get_commandline_name();
|
||||
let current_exe = std::env::current_exe().expect("expected to read current exe");
|
||||
let mut output = vec![];
|
||||
for dir in path.split(':') {
|
||||
let target: PathBuf = [dir, name].iter().collect();
|
||||
match std::fs::canonicalize(&target) {
|
||||
Ok(m) if m == current_exe => continue,
|
||||
Ok(_) => {},
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// note: intentionally store the non-canonicalized version, since if it's a
|
||||
// symlink, (1) it's probably desired to use it and (2) resolving the link
|
||||
// breaks snap installations.
|
||||
output.push(target);
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
const DESKTOP_CLI_RELATIVE_PATH: &str = if cfg!(target_os = "macos") {
|
||||
|
@ -347,7 +445,7 @@ mod tests {
|
|||
.expect("expected exe path");
|
||||
|
||||
let binary_file_path = if cfg!(target_os = "macos") {
|
||||
path.join(format!("{}.app/{}", quality.get_app_name(), bin))
|
||||
path.join(format!("{}/{}", quality.get_macos_app_name(), bin))
|
||||
} else {
|
||||
path.join(bin)
|
||||
};
|
||||
|
@ -369,6 +467,15 @@ mod tests {
|
|||
dir
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_detect_installed_program() {
|
||||
// developers can run this test and debug output manually; VS Code will not
|
||||
// be installed in CI, so the test only makes sure it doesn't error out
|
||||
let result = detect_installed_program(&log::Logger::test(), Quality::Insiders);
|
||||
println!("result: {:?}", result);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_requested_version_parses() {
|
||||
assert_eq!(
|
||||
|
@ -424,45 +531,45 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_preferred_version() {
|
||||
#[tokio::test]
|
||||
async fn test_set_preferred_version() {
|
||||
let dir = make_multiple_vscode_install();
|
||||
let lp = LauncherPaths::new_without_replacements(dir.path().to_owned());
|
||||
let vm1 = CodeVersionManager::new(&lp, Platform::LinuxARM64);
|
||||
let vm1 = CodeVersionManager::new(log::Logger::test(), &lp, Platform::LinuxARM64);
|
||||
|
||||
assert_eq!(
|
||||
vm1.get_preferred_version(),
|
||||
RequestedVersion::Quality(options::Quality::Stable)
|
||||
);
|
||||
vm1.set_preferred_version(&RequestedVersion::Quality(options::Quality::Exploration))
|
||||
.expect("expected to store");
|
||||
vm1.set_preferred_version(&RequestedVersion::Quality(options::Quality::Insiders))
|
||||
.expect("expected to store");
|
||||
vm1.set_preferred_version(
|
||||
RequestedVersion::Quality(options::Quality::Exploration),
|
||||
dir.path().join("desktop/stable"),
|
||||
)
|
||||
.await
|
||||
.expect("expected to store");
|
||||
vm1.set_preferred_version(
|
||||
RequestedVersion::Quality(options::Quality::Insiders),
|
||||
dir.path().join("desktop/stable"),
|
||||
)
|
||||
.await
|
||||
.expect("expected to store");
|
||||
assert_eq!(
|
||||
vm1.get_preferred_version(),
|
||||
RequestedVersion::Quality(options::Quality::Insiders)
|
||||
);
|
||||
|
||||
let vm2 = CodeVersionManager::new(&lp, Platform::LinuxARM64);
|
||||
let vm2 = CodeVersionManager::new(log::Logger::test(), &lp, Platform::LinuxARM64);
|
||||
assert_eq!(
|
||||
vm2.get_preferred_version(),
|
||||
RequestedVersion::Quality(options::Quality::Insiders)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vm2.list(),
|
||||
vec![
|
||||
RequestedVersion::Quality(options::Quality::Exploration),
|
||||
RequestedVersion::Quality(options::Quality::Insiders)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_gets_entrypoint() {
|
||||
let dir = make_multiple_vscode_install();
|
||||
let lp = LauncherPaths::new_without_replacements(dir.path().to_owned());
|
||||
let vm = CodeVersionManager::new(&lp, Platform::LinuxARM64);
|
||||
let vm = CodeVersionManager::new(log::Logger::test(), &lp, Platform::LinuxARM64);
|
||||
|
||||
assert!(vm
|
||||
.try_get_entrypoint(&RequestedVersion::Quality(options::Quality::Stable))
|
||||
|
@ -474,20 +581,4 @@ mod tests {
|
|||
.await
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_uninstall() {
|
||||
let dir = make_multiple_vscode_install();
|
||||
let lp = LauncherPaths::new_without_replacements(dir.path().to_owned());
|
||||
let vm = CodeVersionManager::new(&lp, Platform::LinuxARM64);
|
||||
|
||||
vm.uninstall(&RequestedVersion::Quality(options::Quality::Stable))
|
||||
.await
|
||||
.expect("expected to uninsetall");
|
||||
|
||||
assert!(vm
|
||||
.try_get_entrypoint(&RequestedVersion::Quality(options::Quality::Stable))
|
||||
.await
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
use chrono::Local;
|
||||
use opentelemetry::{
|
||||
sdk::trace::Tracer,
|
||||
trace::{SpanBuilder, Tracer as TraitTracer},
|
||||
sdk::trace::{Tracer, TracerProvider},
|
||||
trace::{SpanBuilder, Tracer as TraitTracer, TracerProvider as TracerProviderTrait},
|
||||
};
|
||||
use std::fmt;
|
||||
use std::{env, path::Path, sync::Arc};
|
||||
|
@ -186,6 +186,14 @@ impl LogSink for FileLogSink {
|
|||
}
|
||||
|
||||
impl Logger {
|
||||
pub fn test() -> Self {
|
||||
Self {
|
||||
tracer: TracerProvider::builder().build().tracer("codeclitest"),
|
||||
sink: vec![],
|
||||
prefix: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(tracer: Tracer, level: Level) -> Self {
|
||||
Self {
|
||||
tracer,
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::fmt;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(clap::ArgEnum, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(clap::ArgEnum, Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Quality {
|
||||
#[serde(rename = "stable")]
|
||||
Stable,
|
||||
|
@ -36,14 +36,22 @@ impl Quality {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_app_name(&self) -> &'static str {
|
||||
pub fn get_macos_app_name(&self) -> &'static str {
|
||||
match self {
|
||||
Quality::Insiders => "Visual Studio Code Insiders",
|
||||
Quality::Exploration => "Visual Studio Code Exploration",
|
||||
Quality::Insiders => "Visual Studio Code - Insiders",
|
||||
Quality::Exploration => "Visual Studio Code - Exploration",
|
||||
Quality::Stable => "Visual Studio Code",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_commandline_name(&self) -> &'static str {
|
||||
match self {
|
||||
Quality::Insiders => "code-insiders",
|
||||
Quality::Exploration => "code-exploration",
|
||||
Quality::Stable => "code",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn server_entrypoint(&self) -> &'static str {
|
||||
match self {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
use dialoguer::{theme::ColorfulTheme, Input, Password};
|
||||
use lazy_static::lazy_static;
|
||||
use std::{ffi::OsString, sync::Mutex, thread, time::Duration};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::mpsc;
|
||||
use windows_service::{
|
||||
define_windows_service,
|
||||
service::{
|
||||
|
@ -18,7 +18,7 @@ use windows_service::{
|
|||
service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
};
|
||||
|
||||
use crate::util::errors::{wrap, AnyError, WindowsNeedsElevation};
|
||||
use crate::{util::errors::{wrap, AnyError, WindowsNeedsElevation}, commands::tunnels::ShutdownSignal};
|
||||
use crate::{
|
||||
log::{self, FileLogSink},
|
||||
state::LauncherPaths,
|
||||
|
@ -203,7 +203,7 @@ fn service_main(_arguments: Vec<OsString>) -> Result<(), AnyError> {
|
|||
let mut service = SERVICE_IMPL.lock().unwrap().take().unwrap();
|
||||
|
||||
// Create a channel to be able to poll a stop event from the service worker loop.
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
|
||||
let mut shutdown_tx = Some(shutdown_tx);
|
||||
|
||||
// Define system service event handler that will be receiving service events.
|
||||
|
@ -211,7 +211,7 @@ fn service_main(_arguments: Vec<OsString>) -> Result<(), AnyError> {
|
|||
match control_event {
|
||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
ServiceControl::Stop => {
|
||||
shutdown_tx.take().and_then(|tx| tx.send(()).ok());
|
||||
shutdown_tx.take().and_then(|tx| tx.blocking_send(ShutdownSignal::CtrlC).ok());
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ pub fn is_integrated_cli() -> io::Result<bool> {
|
|||
None => return Ok(false),
|
||||
};
|
||||
|
||||
let expected_file = if cfg!(target_os = "darwin") {
|
||||
let expected_file = if cfg!(target_os = "macos") {
|
||||
"node_modules.asar"
|
||||
} else {
|
||||
"resources.pak"
|
||||
|
|
Загрузка…
Ссылка в новой задаче