Files
vscode/cli/src/update_service.rs
T
Robo 8748be1f1a feat: update to Electron v42 (#316661)
* feat: update to Electron v42

* chore: drop support for arm 32-bit server

* chore: update types/node to v24.x

* chore: temporarily lock the target version for build/

* chore: update v8-source-location.patch

* chore: fix clippy

* chore: cleanup armhf server ci config

* fix: broken lock file

* fix: c++ version requirement for sysroot builds

* fix: msvc compilation of native modules

* fix: handle rejections for fire-and-forget loadurl

* fix: windows build

* ci: fix teardown of daemon process on windows

```
2026-05-15T20:55:09.7717127Z Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76
```

* chore: update deb and rpm dependencies

* chore: update version info

* spec: improve reliablity of offscreencanvas tests

* spec: retry EPERM failures on teardown

* chore: update x86_64 rpm deps

* ci: exclude server binskim for armhf

* temp: bump distro

* test: ignore deprecation warnings treated as errors

* chore: update lockfile

* fix: externalize electron from auth extension bundles

 Error: Cannot find module 'c:\Users\cloudtest\AppData\Local\Temp\vscode-sanityQvCaze\vscode-server-win32-x64-web\extensions\github-authentication\dist\install.js'
     at Module._resolveFilename (node:internal/modules/cjs/loader:1476:15)
     at wrapResolveFilename (node:internal/modules/cjs/loader:1049:27)
     at defaultResolveImplForCJSLoading (node:internal/modules/cjs/loader:1073:10)
     at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1094:12)
     at Module._load (node:internal/modules/cjs/loader:1262:25)
     at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
     at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
     at node:internal/main/run_main_module:33:47 {
   code: 'MODULE_NOT_FOUND',
   requireStack: []
 }

 Node.js v24.15.0

* test: make wsl sanity tests reliable

* chore: bump electron@42.1.0

* temp: bump distro

* chore: bump electron@42.2.0

* chore: bump distro

* chore: update debian dependencies

* Revert "test: make wsl sanity tests reliable"

This reverts commit b3f2b63e83.

* test: do not fail for deprecation warnings

* chore: patch node24 server binary for wsl1

* chore: address review feedback

* chore: revert global navigation error handler in browserview

* chore: bump distro
2026-05-27 14:57:41 +02:00

335 lines
9.4 KiB
Rust

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
use std::{fmt, path::Path};
use serde::{Deserialize, Serialize};
use crate::{
constants::VSCODE_CLI_UPDATE_ENDPOINT,
log, options,
util::{
errors::{wrap, AnyError, CodeError, WrappedError},
http::{BoxedHttp, SimpleResponse},
io::ReportCopyProgress,
tar::{self, has_gzip_header},
zipper,
},
};
/// Implementation of the VS Code Update service for use in the CLI.
#[derive(Clone)]
pub struct UpdateService {
client: BoxedHttp,
log: log::Logger,
}
/// Describes a specific release, can be created manually or returned from the update service.
#[derive(Clone, Eq, PartialEq)]
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 {
match quality {
options::Quality::Stable => "stable",
options::Quality::Insiders => "insider",
options::Quality::Exploration => "exploration",
}
}
fn get_update_endpoint() -> Result<String, CodeError> {
if let Ok(url) = std::env::var("VSCODE_CLI_UPDATE_URL") {
if !url.is_empty() {
return Ok(url);
}
}
VSCODE_CLI_UPDATE_ENDPOINT
.map(|s| s.to_string())
.ok_or(CodeError::UpdatesNotConfigured("no service url"))
}
impl UpdateService {
pub fn new(log: log::Logger, http: BoxedHttp) -> Self {
UpdateService { client: http, log }
}
pub async fn get_release_by_semver_version(
&self,
platform: Platform,
target: TargetKind,
quality: options::Quality,
version: &str,
) -> Result<Release, AnyError> {
let update_endpoint = get_update_endpoint()?;
let download_segment = target
.download_segment(platform)
.ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?;
let download_url = format!(
"{}/api/versions/{}/{}/{}",
&update_endpoint,
version,
download_segment,
quality_download_segment(quality),
);
let mut response = self.client.make_request("GET", download_url).await?;
if !response.status_code.is_success() {
return Err(response.into_err().await.into());
}
let res = response.json::<UpdateServerVersion>().await?;
debug!(self.log, "Resolved version {} to {}", version, res.version);
Ok(Release {
target,
platform,
quality,
name: res.name,
commit: res.version,
})
}
/// Gets the latest commit for the target of the given quality.
pub async fn get_latest_commit(
&self,
platform: Platform,
target: TargetKind,
quality: options::Quality,
) -> Result<Release, AnyError> {
let update_endpoint = get_update_endpoint()?;
let download_segment = target
.download_segment(platform)
.ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?;
let download_url = format!(
"{}/api/latest/{}/{}",
&update_endpoint,
download_segment,
quality_download_segment(quality),
);
let mut response = self.client.make_request("GET", download_url).await?;
if !response.status_code.is_success() {
return Err(response.into_err().await.into());
}
let res = response.json::<UpdateServerVersion>().await?;
debug!(self.log, "Resolved quality {} to {}", quality, res.version);
Ok(Release {
target,
platform,
quality,
name: res.name,
commit: res.version,
})
}
/// Gets the download stream for the release.
pub async fn get_download_stream(&self, release: &Release) -> Result<SimpleResponse, AnyError> {
let update_endpoint = get_update_endpoint()?;
let download_segment = release
.target
.download_segment(release.platform)
.ok_or_else(|| CodeError::UnsupportedPlatform(release.platform.to_string()))?;
let download_url = format!(
"{}/commit:{}/{}/{}",
&update_endpoint,
release.commit,
download_segment,
quality_download_segment(release.quality),
);
let response = self.client.make_request("GET", download_url).await?;
if !response.status_code.is_success() {
return Err(response.into_err().await.into());
}
Ok(response)
}
}
pub fn unzip_downloaded_release<T>(
compressed_file: &Path,
target_dir: &Path,
reporter: T,
) -> Result<(), WrappedError>
where
T: ReportCopyProgress,
{
match has_gzip_header(compressed_file) {
Ok((f, true)) => tar::decompress_tarball(f, target_dir, reporter),
Ok((f, false)) => zipper::unzip_file(f, target_dir, reporter),
Err(e) => Err(wrap(e, "error checking for gzip header")),
}
}
#[derive(Eq, PartialEq, Copy, Clone)]
pub enum TargetKind {
Server,
Archive,
Web,
Cli,
}
impl TargetKind {
fn download_segment(&self, platform: Platform) -> Option<String> {
match *self {
TargetKind::Server => platform.headless(),
TargetKind::Archive => platform.archive(),
TargetKind::Web => platform.web(),
TargetKind::Cli => Some(platform.cli()),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Platform {
LinuxAlpineX64,
LinuxAlpineARM64,
LinuxX64,
LinuxX64Legacy,
LinuxARM64,
LinuxARM64Legacy,
LinuxARM32,
LinuxARM32Legacy,
DarwinX64,
DarwinARM64,
WindowsX64,
WindowsX86,
WindowsARM64,
}
impl Platform {
pub fn archive(&self) -> Option<String> {
match self {
Platform::LinuxX64 => Some("linux-x64".to_owned()),
Platform::LinuxARM64 => Some("linux-arm64".to_owned()),
Platform::LinuxARM32 => Some("linux-armhf".to_owned()),
Platform::DarwinX64 => Some("darwin".to_owned()),
Platform::DarwinARM64 => Some("darwin-arm64".to_owned()),
Platform::WindowsX64 => Some("win32-x64-archive".to_owned()),
Platform::WindowsX86 => Some("win32-archive".to_owned()),
Platform::WindowsARM64 => Some("win32-arm64-archive".to_owned()),
_ => None,
}
}
pub fn headless(&self) -> Option<String> {
let name = match self {
Platform::LinuxAlpineARM64 => "server-alpine-arm64",
Platform::LinuxAlpineX64 => "server-linux-alpine",
Platform::LinuxX64 => "server-linux-x64",
Platform::LinuxX64Legacy => "server-linux-legacy-x64",
Platform::LinuxARM64 => "server-linux-arm64",
Platform::LinuxARM64Legacy => "server-linux-legacy-arm64",
// No remote server is built for arm32 since Node.js dropped
// 32-bit Linux on armv7 in v24.
Platform::LinuxARM32 | Platform::LinuxARM32Legacy => return None,
Platform::DarwinX64 => "server-darwin",
Platform::DarwinARM64 => "server-darwin-arm64",
Platform::WindowsX64 => "server-win32-x64",
Platform::WindowsX86 => "server-win32",
Platform::WindowsARM64 => "server-win32-arm64",
};
Some(name.to_owned())
}
pub fn cli(&self) -> String {
match self {
Platform::LinuxAlpineARM64 => "cli-alpine-arm64",
Platform::LinuxAlpineX64 => "cli-alpine-x64",
Platform::LinuxX64 => "cli-linux-x64",
Platform::LinuxX64Legacy => "cli-linux-x64",
Platform::LinuxARM64 => "cli-linux-arm64",
Platform::LinuxARM64Legacy => "cli-linux-arm64",
Platform::LinuxARM32 => "cli-linux-armhf",
Platform::LinuxARM32Legacy => "cli-linux-armhf",
Platform::DarwinX64 => "cli-darwin-x64",
Platform::DarwinARM64 => "cli-darwin-arm64",
Platform::WindowsARM64 => "cli-win32-arm64",
Platform::WindowsX64 => "cli-win32-x64",
Platform::WindowsX86 => "cli-win32",
}
.to_owned()
}
pub fn web(&self) -> Option<String> {
self.headless().map(|h| format!("{h}-web"))
}
pub fn env_default() -> Option<Platform> {
if cfg!(all(
target_os = "linux",
target_arch = "x86_64",
target_env = "musl"
)) {
Some(Platform::LinuxAlpineX64)
} else if cfg!(all(
target_os = "linux",
target_arch = "aarch64",
target_env = "musl"
)) {
Some(Platform::LinuxAlpineARM64)
} else if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
Some(Platform::LinuxX64)
} else if cfg!(all(target_os = "linux", target_arch = "arm")) {
Some(Platform::LinuxARM32)
} else if cfg!(all(target_os = "linux", target_arch = "aarch64")) {
Some(Platform::LinuxARM64)
} else if cfg!(all(target_os = "macos", target_arch = "x86_64")) {
Some(Platform::DarwinX64)
} else if cfg!(all(target_os = "macos", target_arch = "aarch64")) {
Some(Platform::DarwinARM64)
} else if cfg!(all(target_os = "windows", target_arch = "x86_64")) {
Some(Platform::WindowsX64)
} else if cfg!(all(target_os = "windows", target_arch = "x86")) {
Some(Platform::WindowsX86)
} else if cfg!(all(target_os = "windows", target_arch = "aarch64")) {
Some(Platform::WindowsARM64)
} else {
None
}
}
}
impl fmt::Display for Platform {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Platform::LinuxAlpineARM64 => "LinuxAlpineARM64",
Platform::LinuxAlpineX64 => "LinuxAlpineX64",
Platform::LinuxX64 => "LinuxX64",
Platform::LinuxX64Legacy => "LinuxX64Legacy",
Platform::LinuxARM64 => "LinuxARM64",
Platform::LinuxARM64Legacy => "LinuxARM64Legacy",
Platform::LinuxARM32 => "LinuxARM32",
Platform::LinuxARM32Legacy => "LinuxARM32Legacy",
Platform::DarwinX64 => "DarwinX64",
Platform::DarwinARM64 => "DarwinARM64",
Platform::WindowsX64 => "WindowsX64",
Platform::WindowsX86 => "WindowsX86",
Platform::WindowsARM64 => "WindowsARM64",
})
}
}