mirror of
https://github.com/Microsoft/vscode
synced 2024-10-05 19:02:54 +00:00
cli: implement better self-updating
- Start separating a "standalone" CLI. This is a little awkward with clap- derive, but I got it working. Detection of whether the CLI _is_ standalone is still todo. - Remove the old ad-hoc update code for code-server, and use the update service instead. - Fix some of the "permission denied" errors people got while updating before. We need to rename the old running binary, not just overwrite it.
This commit is contained in:
parent
a9bcb15b75
commit
07453efc00
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -91,6 +91,9 @@
|
|||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
"editor.formatOnSave": true,
|
||||
},
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"cli/Cargo.toml"
|
||||
],
|
||||
"typescript.tsc.autoDetect": "off",
|
||||
"testing.autoRun.mode": "rerun",
|
||||
"conventionalCommits.scopes": [
|
||||
|
|
|
@ -7,6 +7,8 @@ parameters:
|
|||
default: './'
|
||||
- name: VSCODE_CLI_BINARY_NAME
|
||||
type: string
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: channel
|
||||
type: string
|
||||
default: stable
|
||||
|
@ -24,6 +26,11 @@ steps:
|
|||
targets: ${{ parameters.VSCODE_CLI_TARGETS }}
|
||||
channel: ${{ parameters.channel }}
|
||||
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: true
|
||||
|
||||
- ${{ each target in parameters.VSCODE_CLI_TARGETS }}:
|
||||
- script: cargo build --release --target ${{ target.target }} --bin=${{ parameters.VSCODE_CLI_BINARY_NAME }}
|
||||
displayName: Compile ${{ target.artifact }}
|
||||
|
@ -35,6 +42,8 @@ steps:
|
|||
VSCODE_CLI_ASSET_NAME: ${{ target.artifact }}
|
||||
VSCODE_CLI_AI_KEY: $(VSCODE_CLI_AI_KEY)
|
||||
VSCODE_CLI_AI_ENDPOINT: $(VSCODE_CLI_AI_ENDPOINT)
|
||||
VSCODE_CLI_COMMIT: $(VSCODE_CLI_COMMIT)
|
||||
VSCODE_QUALITY: $(VSCODE_QUALITY)
|
||||
CXX_aarch64-unknown-linux-musl: musl-g++
|
||||
CC_aarch64-unknown-linux-musl: musl-gcc
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ parameters:
|
|||
default: './'
|
||||
- name: VSCODE_CLI_BINARY_NAME
|
||||
type: string
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: channel
|
||||
type: string
|
||||
default: stable
|
||||
|
@ -17,6 +19,11 @@ steps:
|
|||
targets: ${{ parameters.VSCODE_CLI_TARGETS }}
|
||||
channel: ${{ parameters.channel }}
|
||||
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: true
|
||||
|
||||
- ${{ each target in parameters.VSCODE_CLI_TARGETS }}:
|
||||
- script: cargo build --release --target ${{ target.target }} --bin=${{ parameters.VSCODE_CLI_BINARY_NAME }}
|
||||
displayName: Compile ${{ target.artifact }}
|
||||
|
@ -28,6 +35,8 @@ steps:
|
|||
VSCODE_CLI_ASSET_NAME: ${{ target.artifact }}
|
||||
VSCODE_CLI_AI_KEY: $(VSCODE_CLI_AI_KEY)
|
||||
VSCODE_CLI_AI_ENDPOINT: $(VSCODE_CLI_AI_ENDPOINT)
|
||||
VSCODE_CLI_COMMIT: $(VSCODE_CLI_COMMIT)
|
||||
VSCODE_QUALITY: $(VSCODE_QUALITY)
|
||||
|
||||
- publish: ${{ parameters.VSCODE_CLI_DIR }}/target/${{ target.target }}/release/${{ parameters.VSCODE_CLI_BINARY_NAME }}
|
||||
artifact: ${{ target.artifact }}
|
||||
|
|
|
@ -7,6 +7,8 @@ parameters:
|
|||
default: './'
|
||||
- name: VSCODE_CLI_BINARY_NAME
|
||||
type: string
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: channel
|
||||
type: string
|
||||
default: stable
|
||||
|
@ -17,6 +19,11 @@ steps:
|
|||
targets: ${{ parameters.VSCODE_CLI_TARGETS }}
|
||||
channel: ${{ parameters.channel }}
|
||||
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: false
|
||||
|
||||
- ${{ each target in parameters.VSCODE_CLI_TARGETS }}:
|
||||
- script: cargo build --release --target ${{ target.target }} --bin=${{ parameters.VSCODE_CLI_BINARY_NAME }}
|
||||
displayName: Compile ${{ target.artifact }}
|
||||
|
@ -28,6 +35,8 @@ steps:
|
|||
VSCODE_CLI_ASSET_NAME: ${{ target.artifact }}
|
||||
VSCODE_CLI_AI_KEY: $(VSCODE_CLI_AI_KEY)
|
||||
VSCODE_CLI_AI_ENDPOINT: $(VSCODE_CLI_AI_ENDPOINT)
|
||||
VSCODE_CLI_COMMIT: $(VSCODE_CLI_COMMIT)
|
||||
VSCODE_QUALITY: $(VSCODE_QUALITY)
|
||||
${{ if eq(target, 'x86_64-pc-windows-msvc') }}:
|
||||
OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/deps/x64-windows-static-md/lib
|
||||
OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/deps/x64-windows-static-md/include
|
||||
|
|
|
@ -189,14 +189,11 @@ stages:
|
|||
- job: LinuxX86
|
||||
pool: vscode-1es-linux
|
||||
steps:
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: true
|
||||
- template: ./cli/compile-linux.yml
|
||||
parameters:
|
||||
VSCODE_CLI_DIR: $(Build.SourcesDirectory)/cli
|
||||
VSCODE_CLI_BINARY_NAME: ${{ variables.VSCODE_CLI_BINARY_NAME }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CLI_TARGETS:
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true) }}:
|
||||
- { target: x86_64-unknown-linux-musl, artifact: vscode_cli_alpine_x64_cli-unsigned }
|
||||
|
@ -217,14 +214,11 @@ stages:
|
|||
sudo apt update -y
|
||||
sudo apt install -y build-essential pkg-config
|
||||
displayName: Install build dependencies
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: true
|
||||
- template: ./cli/compile-linux.yml
|
||||
parameters:
|
||||
VSCODE_CLI_DIR: $(Build.SourcesDirectory)/cli
|
||||
VSCODE_CLI_BINARY_NAME: ${{ variables.VSCODE_CLI_BINARY_NAME }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CLI_TARGETS:
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true) }}:
|
||||
- { target: aarch64-unknown-linux-musl, artifact: vscode_cli_alpine_arm64_cli-unsigned }
|
||||
|
@ -236,14 +230,11 @@ stages:
|
|||
pool:
|
||||
vmImage: macOS-latest
|
||||
steps:
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: true
|
||||
- template: ./cli/compile-macos.yml
|
||||
parameters:
|
||||
VSCODE_CLI_DIR: $(Build.SourcesDirectory)/cli
|
||||
VSCODE_CLI_BINARY_NAME: ${{ variables.VSCODE_CLI_BINARY_NAME }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CLI_TARGETS:
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}:
|
||||
- { target: x86_64-apple-darwin, artifact: vscode_cli_darwin_x64_cli-unsigned }
|
||||
|
@ -254,10 +245,6 @@ stages:
|
|||
- job: Windows
|
||||
pool: vscode-1es-windows
|
||||
steps:
|
||||
- template: ./cli/prepare.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_IS_POSIX: false
|
||||
- template: ./cli/vcpkg-deps.yml
|
||||
parameters:
|
||||
targets:
|
||||
|
@ -271,6 +258,7 @@ stages:
|
|||
parameters:
|
||||
VSCODE_CLI_DIR: $(Build.SourcesDirectory)/cli
|
||||
VSCODE_CLI_BINARY_NAME: ${{ variables.VSCODE_CLI_BINARY_NAME }}
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
VSCODE_CLI_TARGETS:
|
||||
- ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}:
|
||||
- { target: x86_64-pc-windows-msvc, artifact: vscode_cli_win32_x64_cli-unsigned }
|
||||
|
|
|
@ -45,7 +45,7 @@ async fn main() -> Result<(), std::convert::Infallible> {
|
|||
parsed.global_options.log.unwrap_or(own_log::Level::Info)
|
||||
},
|
||||
),
|
||||
args: args::Cli {
|
||||
args: args::CliCore {
|
||||
global_options: parsed.global_options,
|
||||
subcommand: Some(args::Commands::Tunnel(parsed.tunnel_options.clone())),
|
||||
..Default::default()
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use cli::commands::args::{
|
||||
Cli, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand, InstallExtensionArgs,
|
||||
ListExtensionArgs, UninstallExtensionArgs,
|
||||
CliCore, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand,
|
||||
InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs,
|
||||
};
|
||||
|
||||
/// Tries to parse the argv using the legacy CLI interface, looking for its
|
||||
/// flags and generating a CLI with subcommands if those don't exist.
|
||||
pub fn try_parse_legacy(
|
||||
iter: impl IntoIterator<Item = impl Into<std::ffi::OsString>>,
|
||||
) -> Option<Cli> {
|
||||
) -> Option<CliCore> {
|
||||
let raw = clap_lex::RawArgs::new(iter);
|
||||
let mut cursor = raw.cursor();
|
||||
raw.next(&mut cursor); // Skip the bin
|
||||
|
@ -65,7 +65,7 @@ pub fn try_parse_legacy(
|
|||
// --status -> status
|
||||
|
||||
if args.contains_key("list-extensions") {
|
||||
Some(Cli {
|
||||
Some(CliCore {
|
||||
subcommand: Some(Commands::Extension(ExtensionArgs {
|
||||
subcommand: ExtensionSubcommand::List(ListExtensionArgs {
|
||||
category: get_first_arg_value("category"),
|
||||
|
@ -76,7 +76,7 @@ pub fn try_parse_legacy(
|
|||
..Default::default()
|
||||
})
|
||||
} else if let Some(exts) = args.remove("install-extension") {
|
||||
Some(Cli {
|
||||
Some(CliCore {
|
||||
subcommand: Some(Commands::Extension(ExtensionArgs {
|
||||
subcommand: ExtensionSubcommand::Install(InstallExtensionArgs {
|
||||
id_or_path: exts,
|
||||
|
@ -88,7 +88,7 @@ pub fn try_parse_legacy(
|
|||
..Default::default()
|
||||
})
|
||||
} else if let Some(exts) = args.remove("uninstall-extension") {
|
||||
Some(Cli {
|
||||
Some(CliCore {
|
||||
subcommand: Some(Commands::Extension(ExtensionArgs {
|
||||
subcommand: ExtensionSubcommand::Uninstall(UninstallExtensionArgs { id: exts }),
|
||||
desktop_code_options,
|
||||
|
@ -96,7 +96,7 @@ pub fn try_parse_legacy(
|
|||
..Default::default()
|
||||
})
|
||||
} else if args.contains_key("status") {
|
||||
Some(Cli {
|
||||
Some(CliCore {
|
||||
subcommand: Some(Commands::Status),
|
||||
..Default::default()
|
||||
})
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::process::Command;
|
|||
|
||||
use clap::Parser;
|
||||
use cli::{
|
||||
commands::{args, tunnels, version, CommandContext},
|
||||
commands::{args, tunnels, update, version, CommandContext},
|
||||
desktop, log as own_log,
|
||||
state::LauncherPaths,
|
||||
update_service::UpdateService,
|
||||
|
@ -26,68 +26,82 @@ use log::{Level, Metadata, Record};
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<(), std::convert::Infallible> {
|
||||
let raw_args = std::env::args_os().collect::<Vec<_>>();
|
||||
let parsed = try_parse_legacy(&raw_args).unwrap_or_else(|| args::Cli::parse_from(&raw_args));
|
||||
// todo: only parse to the standalone CLI if not integrated
|
||||
let parsed = try_parse_legacy(&raw_args)
|
||||
.map(|core| args::AnyCli::Integrated(args::IntegratedCli { core }))
|
||||
.unwrap_or_else(|| args::AnyCli::Standalone(args::StandaloneCli::parse_from(&raw_args)));
|
||||
|
||||
let core = parsed.core();
|
||||
let context = CommandContext {
|
||||
http: reqwest::Client::new(),
|
||||
paths: LauncherPaths::new(&parsed.global_options.cli_data_dir).unwrap(),
|
||||
paths: LauncherPaths::new(&core.global_options.cli_data_dir).unwrap(),
|
||||
log: own_log::Logger::new(
|
||||
SdkTracerProvider::builder().build().tracer("codecli"),
|
||||
if parsed.global_options.verbose {
|
||||
if core.global_options.verbose {
|
||||
own_log::Level::Trace
|
||||
} else {
|
||||
parsed.global_options.log.unwrap_or(own_log::Level::Info)
|
||||
core.global_options.log.unwrap_or(own_log::Level::Info)
|
||||
},
|
||||
),
|
||||
args: parsed,
|
||||
args: core.clone(),
|
||||
};
|
||||
|
||||
log::set_logger(Box::leak(Box::new(RustyLogger(context.log.clone()))))
|
||||
.map(|()| log::set_max_level(log::LevelFilter::Debug))
|
||||
.expect("expected to make logger");
|
||||
|
||||
let result = match context.args.subcommand.clone() {
|
||||
None => {
|
||||
let ca = context.args.get_base_code_args();
|
||||
start_code(context, ca).await
|
||||
}
|
||||
|
||||
Some(args::Commands::Extension(extension_args)) => {
|
||||
let mut ca = context.args.get_base_code_args();
|
||||
extension_args.add_code_args(&mut ca);
|
||||
start_code(context, ca).await
|
||||
}
|
||||
|
||||
Some(args::Commands::Status) => {
|
||||
let mut ca = context.args.get_base_code_args();
|
||||
ca.push("--status".to_string());
|
||||
start_code(context, ca).await
|
||||
}
|
||||
|
||||
Some(args::Commands::Version(version_args)) => match version_args.subcommand {
|
||||
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
|
||||
}
|
||||
let result = match parsed {
|
||||
args::AnyCli::Standalone(args::StandaloneCli {
|
||||
subcommand: Some(cmd),
|
||||
..
|
||||
}) => match cmd {
|
||||
args::StandaloneCommands::Update(args) => update::update(context, args).await,
|
||||
},
|
||||
args::AnyCli::Standalone(args::StandaloneCli { core: c, .. })
|
||||
| args::AnyCli::Integrated(args::IntegratedCli { core: c, .. }) => match c.subcommand {
|
||||
None => {
|
||||
let ca = context.args.get_base_code_args();
|
||||
start_code(context, ca).await
|
||||
}
|
||||
|
||||
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
|
||||
Some(args::TunnelSubcommand::Prune) => tunnels::prune(context).await,
|
||||
Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context).await,
|
||||
Some(args::TunnelSubcommand::Rename(rename_args)) => {
|
||||
tunnels::rename(context, rename_args).await
|
||||
Some(args::Commands::Extension(extension_args)) => {
|
||||
let mut ca = context.args.get_base_code_args();
|
||||
extension_args.add_code_args(&mut ca);
|
||||
start_code(context, ca).await
|
||||
}
|
||||
Some(args::TunnelSubcommand::User(user_command)) => {
|
||||
tunnels::user(context, user_command).await
|
||||
|
||||
Some(args::Commands::Status) => {
|
||||
let mut ca = context.args.get_base_code_args();
|
||||
ca.push("--status".to_string());
|
||||
start_code(context, ca).await
|
||||
}
|
||||
Some(args::TunnelSubcommand::Service(service_args)) => {
|
||||
tunnels::service(context, service_args).await
|
||||
}
|
||||
None => tunnels::serve(context, tunnel_args.serve_args).await,
|
||||
|
||||
Some(args::Commands::Version(version_args)) => match version_args.subcommand {
|
||||
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
|
||||
}
|
||||
},
|
||||
|
||||
Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
|
||||
Some(args::TunnelSubcommand::Prune) => tunnels::prune(context).await,
|
||||
Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context).await,
|
||||
Some(args::TunnelSubcommand::Rename(rename_args)) => {
|
||||
tunnels::rename(context, rename_args).await
|
||||
}
|
||||
Some(args::TunnelSubcommand::User(user_command)) => {
|
||||
tunnels::user(context, user_command).await
|
||||
}
|
||||
Some(args::TunnelSubcommand::Service(service_args)) => {
|
||||
tunnels::service(context, service_args).await
|
||||
}
|
||||
None => tunnels::serve(context, tunnel_args.serve_args).await,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -8,5 +8,6 @@ mod output;
|
|||
|
||||
pub mod args;
|
||||
pub mod tunnels;
|
||||
pub mod update;
|
||||
pub mod version;
|
||||
pub use context::CommandContext;
|
||||
|
|
|
@ -24,7 +24,14 @@ const TEMPLATE: &str = "
|
|||
name = "Visual Studio Code CLI",
|
||||
version = match constants::VSCODE_CLI_VERSION { Some(v) => v, None => "dev" },
|
||||
)]
|
||||
pub struct Cli {
|
||||
pub struct IntegratedCli {
|
||||
#[clap(flatten)]
|
||||
pub core: CliCore,
|
||||
}
|
||||
|
||||
/// Common CLI shared between intergated and standalone interfaces.
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct CliCore {
|
||||
/// One or more files, folders, or URIs to open.
|
||||
#[clap(name = "paths")]
|
||||
pub open_paths: Vec<String>,
|
||||
|
@ -42,7 +49,36 @@ pub struct Cli {
|
|||
pub subcommand: Option<Commands>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
#[derive(Parser, Debug, Default)]
|
||||
#[clap(
|
||||
help_template = TEMPLATE,
|
||||
long_about = None,
|
||||
name = "Visual Studio Code CLI",
|
||||
version = match constants::VSCODE_CLI_VERSION { Some(v) => v, None => "dev" },
|
||||
)]
|
||||
pub struct StandaloneCli {
|
||||
#[clap(flatten)]
|
||||
pub core: CliCore,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: Option<StandaloneCommands>,
|
||||
}
|
||||
|
||||
pub enum AnyCli {
|
||||
Integrated(IntegratedCli),
|
||||
Standalone(StandaloneCli),
|
||||
}
|
||||
|
||||
impl AnyCli {
|
||||
pub fn core(&self) -> &CliCore {
|
||||
match self {
|
||||
AnyCli::Integrated(cli) => &cli.core,
|
||||
AnyCli::Standalone(cli) => &cli.core,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliCore {
|
||||
pub fn get_base_code_args(&self) -> Vec<String> {
|
||||
let mut args = self.open_paths.clone();
|
||||
self.editor_options.add_code_args(&mut args);
|
||||
|
@ -52,8 +88,8 @@ impl Cli {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Cli> for CodeServerArgs {
|
||||
fn from(cli: &'a Cli) -> Self {
|
||||
impl<'a> From<&'a CliCore> for CodeServerArgs {
|
||||
fn from(cli: &'a CliCore) -> Self {
|
||||
let mut args = CodeServerArgs {
|
||||
log: cli.global_options.log,
|
||||
accept_server_license_terms: true,
|
||||
|
@ -77,6 +113,19 @@ impl<'a> From<&'a Cli> for CodeServerArgs {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum StandaloneCommands {
|
||||
/// Updates the VS Code CLI.
|
||||
Update(StandaloneUpdateArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct StandaloneUpdateArgs {
|
||||
/// Only check for updates, without actually updating the CLI.
|
||||
#[clap(long)]
|
||||
pub check: bool,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
|
||||
pub enum Commands {
|
||||
|
@ -234,7 +283,7 @@ pub struct UninstallVersionArgs {
|
|||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct EditorOptions {
|
||||
/// Compare two files with each other.
|
||||
#[clap(short, long, value_names = &["file", "file"])]
|
||||
|
@ -348,7 +397,7 @@ impl DesktopCodeOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct GlobalOptions {
|
||||
/// Directory where CLI metadata, such as VS Code installations, should be stored.
|
||||
#[clap(long, env = "VSCODE_CLI_DATA_DIR", global = true)]
|
||||
|
@ -389,7 +438,7 @@ impl GlobalOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct EditorTroubleshooting {
|
||||
/// Run CPU profiler during startup.
|
||||
#[clap(long)]
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
use crate::{log, state::LauncherPaths};
|
||||
|
||||
use super::args::Cli;
|
||||
use super::args::CliCore;
|
||||
|
||||
pub struct CommandContext {
|
||||
pub log: log::Logger,
|
||||
pub paths: LauncherPaths,
|
||||
pub args: Cli,
|
||||
pub args: CliCore,
|
||||
pub http: reqwest::Client,
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use tokio::sync::oneshot;
|
|||
|
||||
use super::{
|
||||
args::{
|
||||
AuthProvider, Cli, ExistingTunnelArgs, TunnelRenameArgs, TunnelServeArgs,
|
||||
AuthProvider, CliCore, ExistingTunnelArgs, TunnelRenameArgs, TunnelServeArgs,
|
||||
TunnelServiceSubCommands, TunnelUserSubCommands,
|
||||
},
|
||||
CommandContext,
|
||||
|
@ -57,11 +57,11 @@ impl From<ExistingTunnelArgs> for Option<dev_tunnels::ExistingTunnel> {
|
|||
}
|
||||
|
||||
struct TunnelServiceContainer {
|
||||
args: Cli,
|
||||
args: CliCore,
|
||||
}
|
||||
|
||||
impl TunnelServiceContainer {
|
||||
fn new(args: Cli) -> Self {
|
||||
fn new(args: CliCore) -> Self {
|
||||
Self { args }
|
||||
}
|
||||
}
|
||||
|
|
44
cli/src/commands/update.rs
Normal file
44
cli/src/commands/update.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
use crate::{
|
||||
self_update::SelfUpdate,
|
||||
update_service::UpdateService,
|
||||
util::{errors::AnyError, input::ProgressBarReporter},
|
||||
};
|
||||
|
||||
use super::{args::StandaloneUpdateArgs, CommandContext};
|
||||
|
||||
pub async fn update(ctx: CommandContext, args: StandaloneUpdateArgs) -> Result<i32, AnyError> {
|
||||
let update_service = UpdateService::new(ctx.log.clone(), ctx.http.clone());
|
||||
let update_service = SelfUpdate::new(&update_service)?;
|
||||
|
||||
let current_version = update_service.get_current_release().await?;
|
||||
if update_service.is_up_to_date_with(¤t_version) {
|
||||
ctx.log.result(format!(
|
||||
"VS Code is already to to date ({})",
|
||||
current_version.commit
|
||||
));
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
if args.check {
|
||||
ctx.log
|
||||
.result(format!("Update to {} is available", current_version));
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let pb = ProgressBar::new(1);
|
||||
pb.set_message("Downloading...");
|
||||
update_service
|
||||
.do_update(¤t_version, ProgressBarReporter::from(pb))
|
||||
.await?;
|
||||
ctx.log
|
||||
.result(format!("Successfully updated to {}", current_version));
|
||||
|
||||
Ok(0)
|
||||
}
|
|
@ -12,6 +12,8 @@ pub const VSCODE_CLI_VERSION: Option<&'static str> = option_env!("VSCODE_CLI_VER
|
|||
pub const VSCODE_CLI_ASSET_NAME: Option<&'static str> = option_env!("VSCODE_CLI_ASSET_NAME");
|
||||
pub const VSCODE_CLI_AI_KEY: Option<&'static str> = option_env!("VSCODE_CLI_AI_KEY");
|
||||
pub const VSCODE_CLI_AI_ENDPOINT: Option<&'static str> = option_env!("VSCODE_CLI_AI_ENDPOINT");
|
||||
pub const VSCODE_CLI_QUALITY: Option<&'static str> = option_env!("VSCODE_CLI_QUALITY");
|
||||
pub const VSCODE_CLI_COMMIT: Option<&'static str> = option_env!("VSCODE_CLI_COMMIT");
|
||||
pub const VSCODE_CLI_UPDATE_ENDPOINT: Option<&'static str> =
|
||||
option_env!("VSCODE_CLI_UPDATE_ENDPOINT");
|
||||
|
||||
|
|
|
@ -265,6 +265,7 @@ async fn get_release_for_request(
|
|||
platform,
|
||||
commit: commit.clone(),
|
||||
quality: *quality,
|
||||
name: "".to_string(),
|
||||
target: TargetKind::Archive,
|
||||
}),
|
||||
RequestedVersion::Quality(quality) => update_service
|
||||
|
|
|
@ -14,6 +14,6 @@ pub mod desktop;
|
|||
pub mod options;
|
||||
pub mod state;
|
||||
pub mod tunnels;
|
||||
pub mod update;
|
||||
pub mod self_update;
|
||||
pub mod update_service;
|
||||
pub mod util;
|
||||
|
|
|
@ -209,9 +209,9 @@ impl Logger {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn result(&self, message: &str) {
|
||||
pub fn result(&self, message: impl AsRef<str>) {
|
||||
for sink in &self.sink {
|
||||
sink.write_result(message);
|
||||
sink.write_result(message.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
117
cli/src/self_update.rs
Normal file
117
cli/src/self_update.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::{fs::rename, path::Path};
|
||||
use tempfile::tempdir;
|
||||
|
||||
use crate::{
|
||||
constants::{VSCODE_CLI_COMMIT, VSCODE_CLI_QUALITY},
|
||||
options::Quality,
|
||||
update_service::{Platform, Release, TargetKind, UpdateService},
|
||||
util::{
|
||||
errors::{wrap, AnyError, UpdatesNotConfigured},
|
||||
http,
|
||||
io::ReportCopyProgress,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct SelfUpdate<'a> {
|
||||
commit: &'static str,
|
||||
quality: Quality,
|
||||
platform: Platform,
|
||||
update_service: &'a UpdateService,
|
||||
}
|
||||
|
||||
impl<'a> SelfUpdate<'a> {
|
||||
pub fn new(update_service: &'a UpdateService) -> Result<Self, AnyError> {
|
||||
let commit = VSCODE_CLI_COMMIT
|
||||
.ok_or_else(|| UpdatesNotConfigured("unknown build commit".to_string()))?;
|
||||
|
||||
let quality = VSCODE_CLI_QUALITY
|
||||
.ok_or_else(|| UpdatesNotConfigured("no configured quality".to_string()))
|
||||
.and_then(|q| Quality::try_from(q).map_err(UpdatesNotConfigured))?;
|
||||
|
||||
let platform = Platform::env_default().ok_or_else(|| {
|
||||
UpdatesNotConfigured("Unknown platform, please report this error".to_string())
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
commit,
|
||||
quality,
|
||||
platform,
|
||||
update_service,
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the current release
|
||||
pub async fn get_current_release(&self) -> Result<Release, AnyError> {
|
||||
self.update_service
|
||||
.get_latest_commit(self.platform, TargetKind::Cli, self.quality)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets whether the given release is what this CLI is built against
|
||||
pub fn is_up_to_date_with(&self, release: &Release) -> bool {
|
||||
release.commit == self.commit
|
||||
}
|
||||
|
||||
/// Updates the CLI to the given release.
|
||||
pub async fn do_update(
|
||||
&self,
|
||||
release: &Release,
|
||||
progress: impl ReportCopyProgress,
|
||||
) -> Result<(), AnyError> {
|
||||
let stream = self.update_service.get_download_stream(release).await?;
|
||||
let target_path =
|
||||
std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?;
|
||||
let staging_path = target_path.with_extension(".update");
|
||||
|
||||
http::download_into_file(&staging_path, progress, stream).await?;
|
||||
|
||||
copy_file_metadata(&target_path, &staging_path)
|
||||
.map_err(|e| wrap(e, "failed to set file permissions"))?;
|
||||
|
||||
// Try to rename the old CLI to a tempdir, where it can get cleaned up by the
|
||||
// OS later. However, this can fail if the tempdir is on a different drive
|
||||
// than the installation dir. In this case just rename it to ".old".
|
||||
let disposal_dir = tempdir().map_err(|e| wrap(e, "Failed to create disposal dir"))?;
|
||||
if rename(&target_path, &disposal_dir.path().join("old-code-cli")).is_err() {
|
||||
rename(&target_path, &target_path.with_extension(".old"))
|
||||
.map_err(|e| wrap(e, "failed to rename old CLI"))?;
|
||||
}
|
||||
|
||||
rename(&staging_path, &target_path)
|
||||
.map_err(|e| wrap(e, "failed to rename newly installed CLI"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> {
|
||||
use std::fs::set_permissions;
|
||||
|
||||
let permissions = from.metadata()?.permissions();
|
||||
set_permissions(&to, permissions)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let metadata = from.metadata()?;
|
||||
set_permissions(&to, metadata.permissions())?;
|
||||
|
||||
// based on coreutils' chown https://github.com/uutils/coreutils/blob/72b4629916abe0852ad27286f4e307fbca546b6e/src/chown/chown.rs#L266-L281
|
||||
let s = std::ffi::CString::new(to.as_os_str().as_bytes()).unwrap();
|
||||
let ret = unsafe { libc::chown(s.as_ptr(), metadata.uid(), metadata.gid()) };
|
||||
if ret != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -187,6 +187,7 @@ impl ServerParamsRaw {
|
|||
commit: c.clone(),
|
||||
quality: self.quality,
|
||||
target,
|
||||
name: String::new(),
|
||||
platform: self.platform,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
use crate::constants::{CONTROL_PORT, PROTOCOL_VERSION, VSCODE_CLI_VERSION};
|
||||
use crate::log;
|
||||
use crate::self_update::SelfUpdate;
|
||||
use crate::state::LauncherPaths;
|
||||
use crate::update::Update;
|
||||
use crate::update_service::Platform;
|
||||
use crate::update_service::{Platform, UpdateService};
|
||||
use crate::util::errors::{
|
||||
wrap, AnyError, MismatchedLaunchModeError, NoAttachedServerError, ServerWriteError,
|
||||
};
|
||||
use crate::util::io::SilentCopyProgress;
|
||||
use crate::util::sync::{new_barrier, Barrier};
|
||||
use opentelemetry::trace::SpanKind;
|
||||
use opentelemetry::KeyValue;
|
||||
|
@ -617,13 +618,10 @@ async fn handle_update(
|
|||
ctx: &HandlerContext,
|
||||
params: &UpdateParams,
|
||||
) -> Result<UpdateResult, AnyError> {
|
||||
let updater = Update::new();
|
||||
let latest_release = updater.get_latest_release().await?;
|
||||
|
||||
let up_to_date = match VSCODE_CLI_VERSION {
|
||||
Some(v) => v == latest_release.version,
|
||||
None => true,
|
||||
};
|
||||
let update_service = UpdateService::new(ctx.log.clone(), reqwest::Client::new());
|
||||
let updater = SelfUpdate::new(&update_service)?;
|
||||
let latest_release = updater.get_current_release().await?;
|
||||
let up_to_date = updater.is_up_to_date_with(&latest_release);
|
||||
|
||||
if !params.do_update || up_to_date {
|
||||
return Ok(UpdateResult {
|
||||
|
@ -632,12 +630,10 @@ async fn handle_update(
|
|||
});
|
||||
}
|
||||
|
||||
info!(ctx.log, "Updating CLI from {}", latest_release.version);
|
||||
|
||||
let current_exe = std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?;
|
||||
info!(ctx.log, "Updating CLI to {}", latest_release);
|
||||
|
||||
updater
|
||||
.switch_to_release(&latest_release, ¤t_exe)
|
||||
.do_update(&latest_release, SilentCopyProgress())
|
||||
.await?;
|
||||
|
||||
Ok(UpdateResult {
|
||||
|
|
|
@ -26,15 +26,23 @@ pub struct UpdateService {
|
|||
|
||||
/// Describes a specific release, can be created manually or returned from the update service.
|
||||
pub struct Release {
|
||||
pub name: String,
|
||||
pub platform: Platform,
|
||||
pub target: TargetKind,
|
||||
pub quality: options::Quality,
|
||||
pub commit: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Release {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} (commit {})", self.name, self.commit)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateServerVersion {
|
||||
pub version: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
fn quality_download_segment(quality: options::Quality) -> &'static str {
|
||||
|
@ -57,7 +65,8 @@ impl UpdateService {
|
|||
quality: options::Quality,
|
||||
version: &str,
|
||||
) -> Result<Release, AnyError> {
|
||||
let update_endpoint = VSCODE_CLI_UPDATE_ENDPOINT.ok_or(UpdatesNotConfigured())?;
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let download_segment = target
|
||||
.download_segment(platform)
|
||||
.ok_or(UnsupportedPlatformError())?;
|
||||
|
@ -86,6 +95,7 @@ impl UpdateService {
|
|||
target,
|
||||
platform,
|
||||
quality,
|
||||
name: res.name,
|
||||
commit: res.version,
|
||||
})
|
||||
}
|
||||
|
@ -97,7 +107,8 @@ impl UpdateService {
|
|||
target: TargetKind,
|
||||
quality: options::Quality,
|
||||
) -> Result<Release, AnyError> {
|
||||
let update_endpoint = VSCODE_CLI_UPDATE_ENDPOINT.ok_or(UpdatesNotConfigured())?;
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let download_segment = target
|
||||
.download_segment(platform)
|
||||
.ok_or(UnsupportedPlatformError())?;
|
||||
|
@ -125,6 +136,7 @@ impl UpdateService {
|
|||
target,
|
||||
platform,
|
||||
quality,
|
||||
name: res.name,
|
||||
commit: res.version,
|
||||
})
|
||||
}
|
||||
|
@ -134,7 +146,8 @@ impl UpdateService {
|
|||
&self,
|
||||
release: &Release,
|
||||
) -> Result<reqwest::Response, AnyError> {
|
||||
let update_endpoint = VSCODE_CLI_UPDATE_ENDPOINT.ok_or(UpdatesNotConfigured())?;
|
||||
let update_endpoint =
|
||||
VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?;
|
||||
let download_segment = release
|
||||
.target
|
||||
.download_segment(release.platform)
|
||||
|
@ -182,6 +195,7 @@ pub enum TargetKind {
|
|||
Server,
|
||||
Archive,
|
||||
Web,
|
||||
Cli,
|
||||
}
|
||||
|
||||
impl TargetKind {
|
||||
|
@ -190,6 +204,7 @@ impl TargetKind {
|
|||
TargetKind::Server => Some(platform.headless()),
|
||||
TargetKind::Archive => platform.archive(),
|
||||
TargetKind::Web => Some(platform.web()),
|
||||
TargetKind::Cli => Some(platform.cli()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -235,6 +250,21 @@ impl Platform {
|
|||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn cli(&self) -> String {
|
||||
match self {
|
||||
Platform::LinuxAlpineARM64 => "cli-alpine-arm64",
|
||||
Platform::LinuxAlpineX64 => "cli-linux-alpine",
|
||||
Platform::LinuxX64 => "cli-linux-x64",
|
||||
Platform::LinuxARM64 => "cli-linux-arm64",
|
||||
Platform::LinuxARM32 => "cli-linux-armhf",
|
||||
Platform::DarwinX64 => "cli-darwin-x64",
|
||||
Platform::DarwinARM64 => "cli-darwin-arm64",
|
||||
Platform::WindowsX64 => "cli-win32-x64",
|
||||
Platform::WindowsX86 => "cli-win32-x84",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
pub fn web(&self) -> String {
|
||||
format!("{}-web", self.headless())
|
||||
}
|
||||
|
|
|
@ -317,11 +317,17 @@ impl std::fmt::Display for ServerHasClosed {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdatesNotConfigured();
|
||||
pub struct UpdatesNotConfigured(pub String);
|
||||
|
||||
impl UpdatesNotConfigured {
|
||||
pub fn no_url() -> Self {
|
||||
UpdatesNotConfigured("no service url".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UpdatesNotConfigured {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Update service is not configured")
|
||||
write!(f, "Update service is not configured: {}", self.0)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
|
|
Loading…
Reference in a new issue