feat: create versioned resources for windows setup (#263998)

* feat: create versioned resources for windows setup

* chore: use inno_updater to remove old installation

* chore: remove old installation as part of setup

* chore: update explorer-command

* chore: prefer session-end

* chore: uninst delete updating_version

* chore: make session-ending write synchronous

* chore: cleanup updateService.win32.ts

* chore: invoke inno_updater gc path for non background update

* chore: move session-end path to runtime

* chore: use commit for updating_version

* chore: fix invalid string

* chore: set appUpdate path

* chore: update inno_updater

* chore: empty commit for testing

* chore: some cleanups

1) Check for session-ending flag in appx and tunnel callsites
2) Move gc for background update to cleanup phase in updateservice
3) Set update state to ready when there is a running inno_setup

* chore: disallow same version update

* chore: disallow application launch in the middle of update

* chore: empty commit for testing

* chore: bump inno_updater

* chore: empty commit for testing

* chore: move gc to update startup

* chore: move feature behind insider only check

* chore: bump inno_updater

* chore: bump explorer-command

* fix: build

* fix: gc for background update in system setup

* chore: create separate cli entrypoints for build

* fix: check for setup mutex created by inno

* chore: remove problematic updatingVersionPath deletion

* chore: remove redundant update check

* chore: bump inno_updater

* chore: fix build

* chore: bump inno updater
This commit is contained in:
Robo
2025-11-24 21:32:12 +09:00
committed by GitHub
parent 68a7504026
commit 4c5bfb172a
19 changed files with 2023 additions and 50 deletions

View File

@@ -43,11 +43,8 @@ async function main() {
// Package client
if (process.env['BUILT_CLIENT']) {
// Product version
const version = await $`node -p "require('../VSCode-win32-${arch}/resources/app/package.json').version"`;
printBanner('Package client');
const clientArchivePath = `.build/win32-${arch}/VSCode-win32-${arch}-${version}.zip`;
const clientArchivePath = `.build/win32-${arch}/VSCode-win32-${arch}.zip`;
await $`7z.exe a -tzip ${clientArchivePath} ../VSCode-win32-${arch}/* "-xr!CodeSignSummary*.md"`.pipe(process.stdout);
await $`7z.exe l ${clientArchivePath}`.pipe(process.stdout);
}

View File

@@ -192,7 +192,8 @@ steps:
$ErrorActionPreference = "Stop"
$ArtifactName = (gci -Path "$(Build.ArtifactStagingDirectory)/cli" | Select-Object -last 1).FullName
Expand-Archive -Path $ArtifactName -DestinationPath "$(Build.ArtifactStagingDirectory)/cli"
$AppProductJson = Get-Content -Raw -Path "$(Agent.BuildDirectory)\VSCode-win32-$(VSCODE_ARCH)\resources\app\product.json" | ConvertFrom-Json
$ProductJsonPath = (Get-ChildItem -Path "$(Agent.BuildDirectory)\VSCode-win32-$(VSCODE_ARCH)" -Name "product.json" -Recurse | Select-Object -First 1)
$AppProductJson = Get-Content -Raw -Path "$(Agent.BuildDirectory)\VSCode-win32-$(VSCODE_ARCH)\$ProductJsonPath" | ConvertFrom-Json
$CliAppName = $AppProductJson.tunnelApplicationName
$AppName = $AppProductJson.applicationName
Move-Item -Path "$(Build.ArtifactStagingDirectory)/cli/$AppName.exe" -Destination "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/bin/$CliAppName.exe"
@@ -249,7 +250,8 @@ steps:
- powershell: |
$ErrorActionPreference = "Stop"
$PackageJson = Get-Content -Raw -Path ..\VSCode-win32-$(VSCODE_ARCH)\resources\app\package.json | ConvertFrom-Json
$PackageJsonPath = (Get-ChildItem -Path "..\VSCode-win32-$(VSCODE_ARCH)" -Name "package.json" -Recurse | Select-Object -First 1)
$PackageJson = Get-Content -Raw -Path ..\VSCode-win32-$(VSCODE_ARCH)\$PackageJsonPath | ConvertFrom-Json
$Version = $PackageJson.version
mkdir $(Build.ArtifactStagingDirectory)\out\system-setup -Force
@@ -259,7 +261,7 @@ steps:
mv .build\win32-$(VSCODE_ARCH)\user-setup\VSCodeSetup.exe $(Build.ArtifactStagingDirectory)\out\user-setup\VSCodeUserSetup-$(VSCODE_ARCH)-$Version.exe
mkdir $(Build.ArtifactStagingDirectory)\out\archive -Force
mv .build\win32-$(VSCODE_ARCH)\VSCode-win32-$(VSCODE_ARCH)-$Version.zip $(Build.ArtifactStagingDirectory)\out\archive\VSCode-win32-$(VSCODE_ARCH)-$Version.zip
mv .build\win32-$(VSCODE_ARCH)\VSCode-win32-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)\out\archive\VSCode-win32-$(VSCODE_ARCH)-$Version.zip
mkdir $(Build.ArtifactStagingDirectory)\out\server -Force
mv .build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)\out\server\vscode-server-win32-$(VSCODE_ARCH).zip

View File

@@ -76,7 +76,8 @@ steps:
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
$AppRoot = "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)"
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
$ProductJsonPath = (Get-ChildItem -Path "$AppRoot" -Name "product.json" -Recurse | Select-Object -First 1)
$AppProductJson = Get-Content -Raw -Path "$AppRoot\$ProductJsonPath" | ConvertFrom-Json
$AppNameShort = $AppProductJson.nameShort
$env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"
$env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)"
@@ -98,7 +99,8 @@ steps:
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
$AppRoot = "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)"
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
$ProductJsonPath = (Get-ChildItem -Path "$AppRoot" -Name "product.json" -Recurse | Select-Object -First 1)
$AppProductJson = Get-Content -Raw -Path "$AppRoot\$ProductJsonPath" | ConvertFrom-Json
$AppNameShort = $AppProductJson.nameShort
$env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"
$env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)"

View File

@@ -1,4 +1,4 @@
11b36db4f244693381e52316261ce61678286f6bdfe2614c6352f6fecf3f060d code_explorer_command_arm64.dll
bfab3719038ca46bcd8afb9249a00f851dd08aa3cc8d13d01a917111a2a6d7c2 code_explorer_command_x64.dll
b5cd79c1e91390bdeefaf35cc5c62a6022220832e145781e5609913fac706ad9 code_insider_explorer_command_arm64.dll
f04335cc6fbe8425bd5516e6acbfa05ca706fd7566799a1e22fca1344c25351f code_insider_explorer_command_x64.dll
5dbdd08784067e4caf7d119f7bec05b181b155e1e9868dec5a6c5174ce59f8bd code_explorer_command_arm64.dll
c7b8dde71f62397fbcd1693e35f25d9ceab51b66e805b9f39efc78e02c6abf3c code_explorer_command_x64.dll
968a6fe75c7316d2e2176889dffed8b50e41ee3f1834751cf6387094709b00ef code_insider_explorer_command_arm64.dll
da071035467a64fabf8fc3762b52fa8cdb3f216aa2b252df5b25b8bdf96ec594 code_insider_explorer_command_x64.dll

View File

@@ -45,6 +45,7 @@ const glob = promisify(globCallback);
const rcedit = promisify(rceditCallback);
const root = path.dirname(import.meta.dirname);
const commit = getVersion(root);
const versionedResourcesFolder = (product.quality && product.quality === 'insider') ? commit.substring(0, 10) : '';
// Build
const vscodeEntryPoints = [
@@ -328,6 +329,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
deps
);
let customElectronConfig = {};
if (platform === 'win32') {
all = es.merge(all, gulp.src([
'resources/win32/bower.ico',
@@ -360,6 +362,12 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
'resources/win32/code_70x70.png',
'resources/win32/code_150x150.png'
], { base: '.' }));
if (quality && quality === 'insider') {
customElectronConfig = {
createVersionedResources: true,
productVersionString: `${versionedResourcesFolder}`,
};
}
} else if (platform === 'linux') {
const policyDest = gulp.src('.build/policies/linux/**', { base: '.build/policies/linux' })
.pipe(rename(f => f.dirname = `policies/${f.dirname}`));
@@ -377,7 +385,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
.pipe(util.skipDirectories())
.pipe(util.fixWin32DirectoryPermissions())
.pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523
.pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false }))
.pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false, ...customElectronConfig }))
.pipe(filter(['**', '!LICENSE', '!version'], { dot: true }));
if (platform === 'linux') {
@@ -393,19 +401,37 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
if (platform === 'win32') {
result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true }));
result = es.merge(result, gulp.src('resources/win32/bin/code.cmd', { base: 'resources/win32' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(rename(function (f) { f.basename = product.applicationName; })));
if (quality && quality === 'insider') {
result = es.merge(result, gulp.src('resources/win32/insider/bin/code.cmd', { base: 'resources/win32/insider' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder))
.pipe(rename(function (f) { f.basename = product.applicationName; })));
result = es.merge(result, gulp.src('resources/win32/bin/code.sh', { base: 'resources/win32' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@PRODNAME@@', product.nameLong))
.pipe(replace('@@VERSION@@', version))
.pipe(replace('@@COMMIT@@', commit))
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote'))
.pipe(replace('@@QUALITY@@', quality))
.pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; })));
result = es.merge(result, gulp.src('resources/win32/insider/bin/code.sh', { base: 'resources/win32/insider' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@PRODNAME@@', product.nameLong))
.pipe(replace('@@VERSION@@', version))
.pipe(replace('@@COMMIT@@', commit))
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder))
.pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote'))
.pipe(replace('@@QUALITY@@', quality))
.pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; })));
} else {
result = es.merge(result, gulp.src('resources/win32/bin/code.cmd', { base: 'resources/win32' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(rename(function (f) { f.basename = product.applicationName; })));
result = es.merge(result, gulp.src('resources/win32/bin/code.sh', { base: 'resources/win32' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@PRODNAME@@', product.nameLong))
.pipe(replace('@@VERSION@@', version))
.pipe(replace('@@COMMIT@@', commit))
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote'))
.pipe(replace('@@QUALITY@@', quality))
.pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; })));
}
result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' })
.pipe(rename(product.nameShort + '.VisualElementsManifest.xml')));
@@ -453,8 +479,8 @@ function patchWin32DependenciesTask(destinationFolderName) {
return async () => {
const deps = await glob('**/*.node', { cwd, ignore: 'extensions/node_modules/@parcel/watcher/**' });
const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, 'resources', 'app', 'package.json'), 'utf8'));
const product = JSON.parse(await fs.promises.readFile(path.join(cwd, 'resources', 'app', 'product.json'), 'utf8'));
const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'package.json'), 'utf8'));
const product = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'product.json'), 'utf8'));
const baseVersion = packageJson.version.replace(/-.*$/, '');
await Promise.all(deps.map(async dep => {

View File

@@ -8,6 +8,7 @@ import * as fs from 'fs';
import assert from 'assert';
import * as cp from 'child_process';
import * as util from './lib/util.ts';
import * as getVersionModule from './lib/getVersion.ts';
import * as task from './lib/task.ts';
import pkg from '../package.json' with { type: 'json' };
import product from '../product.json' with { type: 'json' };
@@ -15,11 +16,12 @@ import vfs from 'vinyl-fs';
import rcedit from 'rcedit';
import { createRequire } from 'module';
const { getVersion } = getVersionModule;
const require = createRequire(import.meta.url);
const repoPath = path.dirname(import.meta.dirname);
const commit = getVersion(repoPath);
const buildPath = (/** @type {string} */ arch) => path.join(path.dirname(repoPath), `VSCode-win32-${arch}`);
const setupDir = (/** @type {string} */ arch, /** @type {string} */ target) => path.join(repoPath, '.build', `win32-${arch}`, `${target}-setup`);
const issPath = path.join(import.meta.dirname, 'win32', 'code.iss');
const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup'))), 'bin', 'ISCC.exe');
const signWin32Path = path.join(repoPath, 'build', 'azure-pipelines', 'common', 'sign-win32.ts');
@@ -75,19 +77,26 @@ function buildWin32Setup(arch, target) {
const outputPath = setupDir(arch, target);
fs.mkdirSync(outputPath, { recursive: true });
const originalProductJsonPath = path.join(sourcePath, 'resources/app/product.json');
const quality = product.quality || 'dev';
let versionedResourcesFolder = '';
let issPath = path.join(import.meta.dirname, 'win32', 'code.iss');
if (quality && quality === 'insider') {
versionedResourcesFolder = commit.substring(0, 10);
issPath = path.join(import.meta.dirname, 'win32', 'code-insider.iss');
}
const originalProductJsonPath = path.join(sourcePath, versionedResourcesFolder, 'resources/app/product.json');
const productJsonPath = path.join(outputPath, 'product.json');
const productJson = JSON.parse(fs.readFileSync(originalProductJsonPath, 'utf8'));
productJson['target'] = target;
fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t'));
const quality = product.quality || 'dev';
const definitions = {
NameLong: product.nameLong,
NameShort: product.nameShort,
DirName: product.win32DirName,
Version: pkg.version,
RawVersion: pkg.version.replace(/-\w+$/, ''),
Commit: commit,
NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''),
ExeBasename: product.nameShort,
RegValueName: product.win32RegValueName,
@@ -108,10 +117,11 @@ function buildWin32Setup(arch, target) {
OutputDir: outputPath,
InstallTarget: target,
ProductJsonPath: productJsonPath,
VersionedResourcesFolder: versionedResourcesFolder,
Quality: quality
};
if (quality !== 'exploration') {
if (quality === 'stable' || quality === 'insider') {
definitions['AppxPackage'] = `${quality === 'stable' ? 'code' : 'code_insider'}_${arch}.appx`;
definitions['AppxPackageDll'] = `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`;
definitions['AppxPackageName'] = `${product.win32AppUserModelId}`;

View File

@@ -129,7 +129,7 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "inno_updater"
version = "0.16.0"
version = "0.18.2"
dependencies = [
"byteorder",
"crc",
@@ -546,4 +546,4 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.1",
]
]

View File

@@ -1,6 +1,6 @@
[package]
name = "inno_updater"
version = "0.16.0"
version = "0.18.2"
authors = ["Microsoft <monacotools@microsoft.com>"]
build = "build.rs"

1740
build/win32/code-insider.iss Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -43,12 +43,12 @@ export async function downloadExplorerDll(outDir: string, quality: string = 'sta
d(`downloading ${fileName}`);
const artifact = await downloadArtifact({
isGeneric: true,
version: 'v4.0.0-350164',
version: 'v5.0.0-377200',
artifactName: fileName,
checksums,
mirrorOptions: {
mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/',
customDir: 'v4.0.0-350164',
customDir: 'v5.0.0-377200',
customFilename: fileName
}
});

Binary file not shown.

View File

@@ -0,0 +1,7 @@
@echo off
setlocal
set VSCODE_DEV=
set ELECTRON_RUN_AS_NODE=1
"%~dp0..\@@NAME@@.exe" "%~dp0..\@@VERSIONFOLDER@@\resources\app\out\cli.js" %*
IF %ERRORLEVEL% NEQ 0 EXIT /b %ERRORLEVEL%
endlocal

View File

@@ -0,0 +1,63 @@
#!/usr/bin/env sh
#
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
if [ "$VSCODE_WSL_DEBUG_INFO" = true ]; then
set -x
fi
COMMIT="@@COMMIT@@"
APP_NAME="@@APPNAME@@"
QUALITY="@@QUALITY@@"
NAME="@@NAME@@"
SERVERDATAFOLDER="@@SERVERDATAFOLDER@@"
VERSIONFOLDER="@@VERSIONFOLDER@@"
VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")"
ELECTRON="$VSCODE_PATH/$NAME.exe"
IN_WSL=false
if [ -n "$WSL_DISTRO_NAME" ]; then
# $WSL_DISTRO_NAME is available since WSL builds 18362, also for WSL2
IN_WSL=true
else
WSL_BUILD=$(uname -r | sed -E 's/^[0-9.]+-([0-9]+)-Microsoft.*|.*/\1/')
if [ -n "$WSL_BUILD" ]; then
if [ "$WSL_BUILD" -ge 17063 ]; then
# WSLPATH is available since WSL build 17046
# WSLENV is available since WSL build 17063
IN_WSL=true
else
# If running under older WSL, don't pass cli.js to Electron as
# environment vars cannot be transferred from WSL to Windows
# See: https://github.com/microsoft/BashOnWindows/issues/1363
# https://github.com/microsoft/BashOnWindows/issues/1494
"$ELECTRON" "$@"
exit $?
fi
fi
fi
if [ $IN_WSL = true ]; then
export WSLENV="ELECTRON_RUN_AS_NODE/w:$WSLENV"
CLI=$(wslpath -m "$VSCODE_PATH/resources/app/out/cli.js")
# use the Remote WSL extension if installed
WSL_EXT_ID="ms-vscode-remote.remote-wsl"
ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt 2>/dev/null </dev/null
WSL_EXT_WLOC=$(cat /tmp/remote-wsl-loc.txt)
if [ -n "$WSL_EXT_WLOC" ]; then
# replace \r\n with \n in WSL_EXT_WLOC
WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode.sh
"$WSL_CODE" "$COMMIT" "$QUALITY" "$ELECTRON" "$APP_NAME" "$SERVERDATAFOLDER" "$@"
exit $?
fi
elif [ -x "$(command -v cygpath)" ]; then
CLI=$(cygpath -m "$VSCODE_PATH/$VERSIONFOLDER/resources/app/out/cli.js")
else
CLI="$VSCODE_PATH/$VERSIONFOLDER/resources/app/out/cli.js"
fi
ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@"
exit $?

View File

@@ -144,6 +144,14 @@ class CodeMain {
evt.join('instanceLockfile', promises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ }));
});
// Check if Inno Setup is running
const innoSetupActive = await this.checkInnoSetupMutex(productService);
if (innoSetupActive) {
const message = `${productService.nameShort} is currently being updated. Please wait for the update to complete before launching.`;
instantiationService.invokeFunction(this.quit, new Error(message));
return;
}
return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup();
});
} catch (error) {
@@ -487,6 +495,21 @@ class CodeMain {
lifecycleMainService.kill(exitCode);
}
private async checkInnoSetupMutex(productService: IProductService): Promise<boolean> {
if (!isWindows || !productService.win32MutexName || productService.quality !== 'insider') {
return false;
}
try {
const readyMutexName = `${productService.win32MutexName}setup`;
const mutex = await import('@vscode/windows-mutex');
return mutex.isActive(readyMutexName);
} catch (error) {
console.error('Failed to check Inno Setup mutex:', error);
return false;
}
}
//#region Command line arguments utilities
private resolveArgs(): NativeParsedArgs {

View File

@@ -13,6 +13,7 @@ import { ILogService } from '../../../../platform/log/common/log.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
import { FileOperationResult, IFileService, IFileStat, toFileOperationResult } from '../../../../platform/files/common/files.js';
import { getErrorMessage } from '../../../../base/common/errors.js';
import { IProductService } from '../../../../platform/product/common/productService.js';
const defaultExtensionsInitStatusKey = 'initializing-default-extensions';
@@ -23,6 +24,7 @@ export class DefaultExtensionsInitializer extends Disposable {
@IStorageService storageService: IStorageService,
@IFileService private readonly fileService: IFileService,
@ILogService private readonly logService: ILogService,
@IProductService private readonly productService: IProductService,
) {
super();
@@ -70,9 +72,15 @@ export class DefaultExtensionsInitializer extends Disposable {
}
private getDefaultExtensionVSIXsLocation(): URI {
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app
// extensionsPath = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\bootstrap\extensions
return URI.file(join(dirname(dirname(this.environmentService.appRoot)), 'bootstrap', 'extensions'));
if (this.productService.quality === 'insider') {
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\<version>\resources\app
// extensionsPath = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\<version>\bootstrap\extensions
return URI.file(join(dirname(dirname(dirname(this.environmentService.appRoot))), 'bootstrap', 'extensions'));
} else {
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app
// extensionsPath = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\bootstrap\extensions
return URI.file(join(dirname(dirname(this.environmentService.appRoot)), 'bootstrap', 'extensions'));
}
}
}

View File

@@ -175,9 +175,17 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
// appRoot = /Applications/Visual Studio Code - Insiders.app/Contents/Resources/app
// bin = /Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin
binParentLocation = this.environmentService.appRoot;
} else if (isWindows) {
if (this.productService.quality === 'insider') {
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\<version>\resources\app
// bin = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\bin
binParentLocation = dirname(dirname(dirname(this.environmentService.appRoot)));
} else {
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app
// bin = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\bin
binParentLocation = dirname(dirname(this.environmentService.appRoot));
}
} else {
// appRoot = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\resources\app
// bin = C:\Users\<name>\AppData\Local\Programs\Microsoft VS Code Insiders\bin
// appRoot = /usr/share/code-insiders/resources/app
// bin = /usr/share/code-insiders/bin
binParentLocation = dirname(dirname(this.environmentService.appRoot));

View File

@@ -48,7 +48,7 @@ export abstract class AbstractUpdateService implements IUpdateService {
constructor(
@ILifecycleMainService protected readonly lifecycleMainService: ILifecycleMainService,
@IConfigurationService protected configurationService: IConfigurationService,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IEnvironmentMainService protected environmentMainService: IEnvironmentMainService,
@IRequestService protected requestService: IRequestService,
@ILogService protected logService: ILogService,
@IProductService protected readonly productService: IProductService
@@ -105,6 +105,8 @@ export abstract class AbstractUpdateService implements IUpdateService {
this.setState(State.Idle(this.getUpdateType()));
await this.postInitialize();
if (updateMode === 'manual') {
this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference');
return;
@@ -230,6 +232,10 @@ export abstract class AbstractUpdateService implements IUpdateService {
// noop
}
protected async postInitialize(): Promise<void> {
// noop
}
protected abstract buildUpdateFeedUrl(quality: string): string | undefined;
protected abstract doCheckForUpdates(explicit: boolean): void;
}

View File

@@ -4,8 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { spawn } from 'child_process';
import * as fs from 'fs';
import { existsSync, unlinkSync } from 'fs';
import { mkdir, readFile, unlink } from 'fs/promises';
import { tmpdir } from 'os';
import { app } from 'electron';
import { timeout } from '../../../base/common/async.js';
import { CancellationToken } from '../../../base/common/cancellation.js';
import { memoize } from '../../../base/common/decorators.js';
@@ -40,7 +42,7 @@ interface IAvailableUpdate {
let _updateType: UpdateType | undefined = undefined;
function getUpdateType(): UpdateType {
if (typeof _updateType === 'undefined') {
_updateType = fs.existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))
_updateType = existsSync(path.join(path.dirname(process.execPath), 'unins000.exe'))
? UpdateType.Setup
: UpdateType.Archive;
}
@@ -55,7 +57,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
@memoize
get cachePath(): Promise<string> {
const result = path.join(tmpdir(), `vscode-${this.productService.quality}-${this.productService.target}-${process.arch}`);
return fs.promises.mkdir(result, { recursive: true }).then(() => result);
return mkdir(result, { recursive: true }).then(() => result);
}
constructor(
@@ -90,6 +92,14 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
}
protected override async initialize(): Promise<void> {
if (this.environmentMainService.isBuilt) {
const cachePath = await this.cachePath;
app.setPath('appUpdate', cachePath);
try {
await unlink(path.join(cachePath, 'session-ending.flag'));
} catch { }
}
if (this.productService.target === 'user' && await this.nativeHostMainService.isAdmin(undefined)) {
this.setState(State.Disabled(DisablementReason.RunningAsAdmin));
this.logService.info('update#ctor - updates are disabled due to running as Admin in user setup');
@@ -99,6 +109,49 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
await super.initialize();
}
protected override async postInitialize(): Promise<void> {
if (this.productService.quality !== 'insider') {
return;
}
// Check for pending update from previous session
// This can happen if the app is quit right after the update has been
// downloaded and before the update has been applied.
const exePath = app.getPath('exe');
const exeDir = path.dirname(exePath);
const updatingVersionPath = path.join(exeDir, 'updating_version');
if (await pfs.Promises.exists(updatingVersionPath)) {
try {
const updatingVersion = (await readFile(updatingVersionPath, 'utf8')).trim();
this.logService.info(`update#doCheckForUpdates - application was updating to version ${updatingVersion}`);
const updatePackagePath = await this.getUpdatePackagePath(updatingVersion);
if (await pfs.Promises.exists(updatePackagePath)) {
await this._applySpecificUpdate(updatePackagePath);
this.logService.info(`update#doCheckForUpdates - successfully applied update to version ${updatingVersion}`);
}
} catch (e) {
this.logService.error(`update#doCheckForUpdates - could not read ${updatingVersionPath}`, e);
} finally {
// updatingVersionPath will be deleted by inno setup.
}
} else {
const fastUpdatesEnabled = this.configurationService.getValue('update.enableWindowsBackgroundUpdates');
// GC for background updates in system setup happens via inno_setup since it requires
// elevated permissions.
if (fastUpdatesEnabled && this.productService.target === 'user' && this.productService.commit) {
const versionedResourcesFolder = this.productService.commit.substring(0, 10);
const innoUpdater = path.join(exeDir, versionedResourcesFolder, 'tools', 'inno_updater.exe');
await new Promise<void>(resolve => {
const child = spawn(innoUpdater, ['--gc', exePath, versionedResourcesFolder], {
stdio: ['ignore', 'ignore', 'ignore'],
windowsHide: true,
timeout: 2 * 60 * 1000
});
child.once('exit', () => resolve());
});
}
}
}
protected buildUpdateFeedUrl(quality: string): string | undefined {
let platform = `win32-${process.arch}`;
@@ -196,7 +249,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
const promises = versions.filter(filter).map(async one => {
try {
await fs.promises.unlink(path.join(cachePath, one));
await unlink(path.join(cachePath, one));
} catch (err) {
// ignore
}
@@ -218,11 +271,12 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
this.setState(State.Updating(update));
const cachePath = await this.cachePath;
const sessionEndFlagPath = path.join(cachePath, 'session-ending.flag');
this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${this.productService.quality}-${update.version}.flag`);
await pfs.Promises.writeFile(this.availableUpdate.updateFilePath, 'flag');
const child = spawn(this.availableUpdate.packagePath, ['/verysilent', '/log', `/update="${this.availableUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
const child = spawn(this.availableUpdate.packagePath, ['/verysilent', '/log', `/update="${this.availableUpdate.updateFilePath}"`, `/sessionend="${sessionEndFlagPath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
windowsVerbatimArguments: true
@@ -249,7 +303,7 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()');
if (this.availableUpdate.updateFilePath) {
fs.unlinkSync(this.availableUpdate.updateFilePath);
unlinkSync(this.availableUpdate.updateFilePath);
} else {
spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], {
detached: true,

View File

@@ -88,6 +88,28 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom
};
}
function findFilePath(root: string, path: string): string {
// First check if the path exists directly in the root
const directPath = join(root, path);
if (fs.existsSync(directPath)) {
return directPath;
}
// If not found directly, search through subdirectories
const entries = fs.readdirSync(root, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const found = join(root, entry.name, path);
if (fs.existsSync(found)) {
return found;
}
}
}
throw new Error(`Could not find ${path} in any subdirectory`);
}
export function getDevElectronPath(): string {
const buildPath = join(root, '.build');
const product = require(join(root, 'product.json'));
@@ -113,7 +135,8 @@ export function getBuildElectronPath(root: string): string {
return join(root, product.applicationName);
}
case 'win32': {
const product = require(join(root, 'resources', 'app', 'product.json'));
const productPath = findFilePath(root, join('resources', 'app', 'product.json'));
const product = require(productPath);
return join(root, `${product.nameShort}.exe`);
}
default:
@@ -125,6 +148,10 @@ export function getBuildVersion(root: string): string {
switch (process.platform) {
case 'darwin':
return require(join(root, 'Contents', 'Resources', 'app', 'package.json')).version;
case 'win32': {
const packagePath = findFilePath(root, join('resources', 'app', 'package.json'));
return require(packagePath).version;
}
default:
return require(join(root, 'resources', 'app', 'package.json')).version;
}