diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 97eebf40530..f7774496ba2 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -175,9 +175,9 @@ steps: displayName: Transpile client and extensions - ${{ else }}: - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), eq(parameters.VSCODE_QUALITY, 'insider')) }}: - - powershell: node build/win32/explorer-appx-fetcher .build/win32/appx - displayName: Download Explorer Sparse Package + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}: + - powershell: node build/win32/explorer-dll-fetcher .build/win32/appx + displayName: Download Explorer dll - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -190,6 +190,23 @@ steps: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build client + # Note: the appx prepare step has to follow Build client step since build step replaces the template + # strings in the raw manifest file at resources/win32/appx/AppxManifest.xml and places it under + # /appx/manifest, we need a separate step to prepare the appx package with the + # final contents. In our case only the manifest file is bundled into the appx package. + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # Add Windows SDK to path + $sdk = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64" + $env:PATH = "$sdk;$env:PATH" + $AppxName = if ('$(VSCODE_QUALITY)' -eq 'stable') { 'code' } else { 'code_insider' } + makeappx pack /d "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" /p "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/${AppxName}_$(VSCODE_ARCH).appx" /nv + # Remove the raw manifest folder + Remove-Item -Path "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" -Recurse -Force + displayName: Prepare appx package + - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" diff --git a/build/checksums/explorer-dll.txt b/build/checksums/explorer-dll.txt new file mode 100644 index 00000000000..fb8ad756847 --- /dev/null +++ b/build/checksums/explorer-dll.txt @@ -0,0 +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 diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 22090d74318..25d89164517 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -404,8 +404,21 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' }) .pipe(rename(f => f.dirname = `policies/${f.dirname}`))); - if (quality === 'insider') { + if (quality !== 'exploration') { result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' })); + const rawVersion = version.replace(/-\w+$/, '').split('.'); + const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`; + result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' }) + .pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId)) + .pipe(replace('@@AppxPackageVersion@@', appxVersion)) + .pipe(replace('@@AppxPackageDisplayName@@', product.nameLong)) + .pipe(replace('@@AppxPackageDescription@@', product.win32NameVersion)) + .pipe(replace('@@ApplicationIdShort@@', product.win32RegValueName)) + .pipe(replace('@@ApplicationExe@@', product.nameShort + '.exe')) + .pipe(replace('@@FileExplorerContextMenuID@@', quality === 'stable' ? 'OpenWithCode' : 'OpenWithCodeInsiders')) + .pipe(replace('@@FileExplorerContextMenuCLSID@@', product.win32ContextMenu[arch].clsid)) + .pipe(replace('@@FileExplorerContextMenuDLL@@', `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`)) + .pipe(rename(f => f.dirname = `appx/manifest`))); } } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 4a9a644d00d..643bf54914b 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -112,10 +112,9 @@ function buildWin32Setup(arch, target) { Quality: quality }; - if (quality === 'insider') { - definitions['AppxPackage'] = `code_insiders_explorer_${arch}.appx`; - definitions['AppxPackageFullname'] = `Microsoft.${product.win32RegValueName}_1.0.0.0_neutral__8wekyb3d8bbwe`; - definitions['AppxPackageName'] = `Microsoft.${product.win32RegValueName}`; + if (quality !== 'exploration') { + definitions['AppxPackage'] = `${quality === 'stable' ? 'code' : 'code_insider'}_${arch}.appx`; + definitions['AppxPackageName'] = `${product.win32AppUserModelId}`; } packageInnoSetup(issPath, { definitions }, cb); diff --git a/build/win32/code.iss b/build/win32/code.iss index 1a48e7421bf..2a349c60376 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -94,8 +94,8 @@ Name: "{app}"; AfterInstall: DisableAppDirInheritance Source: "*"; Excludes: "\CodeSignSummary*.md,\tools,\tools\*,\appx,\appx\*,\resources\app\product.json"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "tools\*"; DestDir: "{app}\tools"; Flags: ignoreversion Source: "{#ProductJsonPath}"; DestDir: "{code:GetDestDir}\resources\app"; Flags: ignoreversion -#ifdef AppxPackageFullname -Source: "appx\*"; DestDir: "{app}\appx"; BeforeInstall: RemoveAppxPackage; AfterInstall: AddAppxPackage; Flags: ignoreversion; Check: IsWindows11OrLater and QualityIsInsiders +#ifdef AppxPackageName +Source: "appx\*"; DestDir: "{app}\appx"; BeforeInstall: RemoveAppxPackage; AfterInstall: AddAppxPackage; Flags: ignoreversion; Check: IsWindows11OrLater #endif [Icons] @@ -1466,26 +1466,26 @@ begin Result := False; end; -#ifdef AppxPackageFullname +#ifdef AppxPackageName var - Line: String; + AppxPackageFullname: String; procedure ExecAndGetFirstLineLog(const S: String; const Error, FirstLine: Boolean); begin - if not Error and (Line = '') and (Trim(S) <> '') then - Line := S; + if not Error and (AppxPackageFullname = '') and (Trim(S) <> '') then + AppxPackageFullname := S; Log(S); end; function AppxPackageInstalled(var ResultCode: Integer): Boolean; begin - Line := ''; + AppxPackageFullname := ''; try - ExecAndLogOutput('powershell.exe', '-Command ' + AddQuotes('Get-AppxPackage -Name ''{#AppxPackageName}'''), '', SW_HIDE, ewWaitUntilTerminated, ResultCode, @ExecAndGetFirstLineLog); + ExecAndLogOutput('powershell.exe', '-Command ' + AddQuotes('Get-AppxPackage -Name ''{#AppxPackageName}'' | Select-Object -ExpandProperty PackageFullName'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode, @ExecAndGetFirstLineLog); except Log(GetExceptionMessage); end; - if (Line <> '') then + if (AppxPackageFullname <> '') then Result := True else Result := False @@ -1495,7 +1495,7 @@ procedure AddAppxPackage(); var AddAppxPackageResultCode: Integer; begin - if not AppxPackageInstalled(AddAppxPackageResultCode) and WizardIsTaskSelected('addcontextmenufiles') then begin + if not AppxPackageInstalled(AddAppxPackageResultCode) then begin ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Add-AppxPackage -Path ''' + ExpandConstant('{app}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\appx') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode); end; end; @@ -1505,7 +1505,7 @@ var RemoveAppxPackageResultCode: Integer; begin if AppxPackageInstalled(RemoveAppxPackageResultCode) then begin - ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Remove-AppxPackage -Package ''{#AppxPackageFullname}'''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode); + ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Remove-AppxPackage -Package ''' + AppxPackageFullname + ''''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode); end; end; #endif @@ -1517,7 +1517,7 @@ var begin if CurStep = ssPostInstall then begin -#ifdef AppxPackageFullname +#ifdef AppxPackageName if not WizardIsTaskSelected('addcontextmenufiles') then begin RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\{#RegValueName}ContextMenu'); end else begin @@ -1606,15 +1606,12 @@ var Parts: TArrayOfString; NewPath: string; i: Integer; - ResultCode: Integer; begin if not CurUninstallStep = usUninstall then begin exit; end; -#ifdef AppxPackageFullname - if AppxPackageInstalled(ResultCode) then begin - RemoveAppxPackage(); - end; +#ifdef AppxPackageName + RemoveAppxPackage(); #endif if not RegQueryStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', Path) then begin diff --git a/build/win32/explorer-appx-fetcher.js b/build/win32/explorer-dll-fetcher.js similarity index 55% rename from build/win32/explorer-appx-fetcher.js rename to build/win32/explorer-dll-fetcher.js index 78d2317147e..dfb9ce97ff4 100644 --- a/build/win32/explorer-appx-fetcher.js +++ b/build/win32/explorer-dll-fetcher.js @@ -7,45 +7,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.downloadExplorerAppx = downloadExplorerAppx; +exports.downloadExplorerDll = downloadExplorerDll; const fs_1 = __importDefault(require("fs")); const debug_1 = __importDefault(require("debug")); -const extract_zip_1 = __importDefault(require("extract-zip")); const path_1 = __importDefault(require("path")); const get_1 = require("@electron/get"); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const d = (0, debug_1.default)('explorer-appx-fetcher'); -async function downloadExplorerAppx(outDir, quality = 'stable', targetArch = 'x64') { - const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code'; - const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`; - if (await fs_1.default.existsSync(path_1.default.resolve(outDir, 'resources.pri'))) { - return; - } +const product_json_1 = __importDefault(require("../../product.json")); +const d = (0, debug_1.default)('explorer-dll-fetcher'); +async function downloadExplorerDll(outDir, quality = 'stable', targetArch = 'x64') { + const fileNamePrefix = quality === 'insider' ? 'code_insider' : 'code'; + const fileName = `${fileNamePrefix}_explorer_command_${targetArch}.dll`; if (!await fs_1.default.existsSync(outDir)) { await fs_1.default.mkdirSync(outDir, { recursive: true }); } + // Read and parse checksums file + const checksumsFilePath = path_1.default.join(path_1.default.dirname(__dirname), 'checksums', 'explorer-dll.txt'); + const checksumsContent = fs_1.default.readFileSync(checksumsFilePath, 'utf8'); + const checksums = {}; + checksumsContent.split('\n').forEach(line => { + const trimmedLine = line.trim(); + if (trimmedLine) { + const [checksum, filename] = trimmedLine.split(/\s+/); + if (checksum && filename) { + checksums[filename] = checksum; + } + } + }); d(`downloading ${fileName}`); const artifact = await (0, get_1.downloadArtifact)({ isGeneric: true, - version: '3.0.4', + version: 'v4.0.0-350164', artifactName: fileName, - unsafelyDisableChecksums: true, + checksums, mirrorOptions: { mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', - customDir: '3.0.4', + customDir: 'v4.0.0-350164', customFilename: fileName } }); - d(`unpacking from ${fileName}`); - await (0, extract_zip_1.default)(artifact, { dir: fs_1.default.realpathSync(outDir) }); + d(`moving ${artifact} to ${outDir}`); + await fs_1.default.copyFileSync(artifact, path_1.default.join(outDir, fileName)); } async function main(outputDir) { const arch = process.env['VSCODE_ARCH']; if (!outputDir) { throw new Error('Required build env not set'); } - const product = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, 'product.json'), 'utf8')); - await downloadExplorerAppx(outputDir, product.quality, arch); + await downloadExplorerDll(outputDir, product_json_1.default.quality, arch); } if (require.main === module) { main(process.argv[2]).catch(err => { @@ -53,4 +61,4 @@ if (require.main === module) { process.exit(1); }); } -//# sourceMappingURL=explorer-appx-fetcher.js.map \ No newline at end of file +//# sourceMappingURL=explorer-dll-fetcher.js.map \ No newline at end of file diff --git a/build/win32/explorer-appx-fetcher.ts b/build/win32/explorer-dll-fetcher.ts similarity index 51% rename from build/win32/explorer-appx-fetcher.ts rename to build/win32/explorer-dll-fetcher.ts index 95121cd6503..724a35bc568 100644 --- a/build/win32/explorer-appx-fetcher.ts +++ b/build/win32/explorer-dll-fetcher.ts @@ -7,41 +7,50 @@ import fs from 'fs'; import debug from 'debug'; -import extract from 'extract-zip'; import path from 'path'; import { downloadArtifact } from '@electron/get'; +import product from '../../product.json'; -const root = path.dirname(path.dirname(__dirname)); +const d = debug('explorer-dll-fetcher'); -const d = debug('explorer-appx-fetcher'); - -export async function downloadExplorerAppx(outDir: string, quality: string = 'stable', targetArch: string = 'x64'): Promise { - const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code'; - const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`; - - if (await fs.existsSync(path.resolve(outDir, 'resources.pri'))) { - return; - } +export async function downloadExplorerDll(outDir: string, quality: string = 'stable', targetArch: string = 'x64'): Promise { + const fileNamePrefix = quality === 'insider' ? 'code_insider' : 'code'; + const fileName = `${fileNamePrefix}_explorer_command_${targetArch}.dll`; if (!await fs.existsSync(outDir)) { await fs.mkdirSync(outDir, { recursive: true }); } + // Read and parse checksums file + const checksumsFilePath = path.join(path.dirname(__dirname), 'checksums', 'explorer-dll.txt'); + const checksumsContent = fs.readFileSync(checksumsFilePath, 'utf8'); + const checksums: Record = {}; + + checksumsContent.split('\n').forEach(line => { + const trimmedLine = line.trim(); + if (trimmedLine) { + const [checksum, filename] = trimmedLine.split(/\s+/); + if (checksum && filename) { + checksums[filename] = checksum; + } + } + }); + d(`downloading ${fileName}`); const artifact = await downloadArtifact({ isGeneric: true, - version: '3.0.4', + version: 'v4.0.0-350164', artifactName: fileName, - unsafelyDisableChecksums: true, + checksums, mirrorOptions: { mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', - customDir: '3.0.4', + customDir: 'v4.0.0-350164', customFilename: fileName } }); - d(`unpacking from ${fileName}`); - await extract(artifact, { dir: fs.realpathSync(outDir) }); + d(`moving ${artifact} to ${outDir}`); + await fs.copyFileSync(artifact, path.join(outDir, fileName)); } async function main(outputDir?: string): Promise { @@ -51,8 +60,7 @@ async function main(outputDir?: string): Promise { throw new Error('Required build env not set'); } - const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); - await downloadExplorerAppx(outputDir, (product as any).quality, arch); + await downloadExplorerDll(outputDir, (product as any).quality, arch); } if (require.main === module) { diff --git a/resources/win32/appx/AppxManifest.xml b/resources/win32/appx/AppxManifest.xml new file mode 100644 index 00000000000..6d057c42414 --- /dev/null +++ b/resources/win32/appx/AppxManifest.xml @@ -0,0 +1,89 @@ + + + + + @@AppxPackageDisplayName@@ + Microsoft Corporation + resources\app\resources\win32\code_150x150.png + true + disabled + disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +