fix(packaging): 修复 Windows 安装与固件打包并完善客户端文案
- fix(installer): 修复低完整性产物无法写入临时目录的问题,并在 CI 打包前后检查完整性标签 - fix(firmware): 从本次 Cargo ELF 打包并同步配套镜像,修正短目录说明与 LLVM 搜索顺序 - fix(installer): 修复中文完成页和页眉遮挡,侧栏使用等比缩放的原始 Logo - style(client): 应用图标增加圆角,统一“裁判系统客户端”命名 - fix(config): 安装提示改用可选本地热点配置,排除 Wi-Fi 凭据 - docs: 润色项目说明并统一裁判系统术语 - test(packaging): 补充 Windows 完整性和固件打包回归,覆盖外部输出目录、旧镜像与重复生成 验证:Windows NSIS 构建及原生页面预览;通用与本地安装配置编译;固件 release 构建、重复打包和客户端离线校验;6 项固件打包回归与 Windows 权限回归;git diff --check。未执行实机烧录。
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Render the existing SVG brand sources with Tauri, then export PNG/ICO/ICNS/BMP.
|
||||
|
||||
Requires the installed desktop Node dependencies and Python's Pillow package.
|
||||
Text is rasterized with Pillow so exports do not depend on the SVG renderer's
|
||||
font discovery. No dependency is added to the application or its build hooks.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
DESKTOP = Path(__file__).resolve().parents[1]
|
||||
ICONS = DESKTOP / "src-tauri/icons"
|
||||
INSTALLER = DESKTOP / "src-tauri/installer"
|
||||
SVG = "{http://www.w3.org/2000/svg}"
|
||||
ET.register_namespace("", SVG[1:-1])
|
||||
|
||||
|
||||
def system_font(monospace=False, bold=False):
|
||||
fonts = Path(os.environ.get("WINDIR", "C:/Windows")) / "Fonts"
|
||||
candidates = (
|
||||
[fonts / ("consolab.ttf" if bold else "consola.ttf"),
|
||||
Path("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf")]
|
||||
if monospace else
|
||||
[fonts / ("msyhbd.ttc" if bold else "msyh.ttc"),
|
||||
Path("/System/Library/Fonts/PingFang.ttc"),
|
||||
Path("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc")]
|
||||
)
|
||||
for path in candidates:
|
||||
if path.exists():
|
||||
return path
|
||||
raise RuntimeError("Set --sans-font and --mono-font to installed fonts supporting the artwork's text.")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--sans-font", type=Path)
|
||||
parser.add_argument("--mono-font", type=Path)
|
||||
args = parser.parse_args()
|
||||
node = shutil.which("node")
|
||||
cli = DESKTOP / "node_modules/@tauri-apps/cli/tauri.js"
|
||||
if not node or not cli.exists():
|
||||
raise RuntimeError("Install Node and run pnpm install --frozen-lockfile in desktop/ first.")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="rmic-brand-") as temporary:
|
||||
scratch = Path(temporary)
|
||||
|
||||
def render(root, size):
|
||||
source = scratch / "source.svg"
|
||||
ET.ElementTree(root).write(source, encoding="utf-8", xml_declaration=True)
|
||||
subprocess.run([node, str(cli), "icon", str(source), "--output", str(scratch),
|
||||
"--png", str(size)], cwd=DESKTOP, check=True, capture_output=True)
|
||||
with Image.open(scratch / f"{size}x{size}.png") as image:
|
||||
return image.convert("RGBA")
|
||||
|
||||
original = ET.parse(ICONS / "icon.svg").getroot()
|
||||
images = {}
|
||||
for size in (16, 20, 24, 32, 48, 64, 128, 256, 512, 1024):
|
||||
root = copy.deepcopy(original)
|
||||
scale = 0.97 if size <= 48 else 0.88 if size <= 128 else 0.82
|
||||
group = root.find(f"{SVG}g")
|
||||
group.set("transform", group.get("transform").replace("scale(0.82)", f"scale({scale})"))
|
||||
images[size] = render(root, size)
|
||||
for filename, size in (("32x32.png", 32), ("128x128.png", 128),
|
||||
("128x128@2x.png", 256), ("icon.png", 512)):
|
||||
images[size].save(ICONS / filename)
|
||||
|
||||
def png(size):
|
||||
stream = io.BytesIO()
|
||||
images[size].save(stream, format="PNG")
|
||||
return stream.getvalue()
|
||||
|
||||
# Supply every ICO size separately, preserving optical scaling at 16–48px.
|
||||
entries, payloads = [], []
|
||||
sizes = (16, 20, 24, 32, 48, 64, 128, 256)
|
||||
offset = 6 + 16 * len(sizes)
|
||||
for size in sizes:
|
||||
data = png(size)
|
||||
entries.append(struct.pack("<BBBBHHII", size % 256, size % 256, 0, 0, 1, 32, len(data), offset))
|
||||
payloads.append(data)
|
||||
offset += len(data)
|
||||
(ICONS / "icon.ico").write_bytes(struct.pack("<HHH", 0, 1, len(sizes)) + b"".join(entries + payloads))
|
||||
chunks = []
|
||||
for kind, size in ((b"icp4", 16), (b"icp5", 32), (b"icp6", 64), (b"ic07", 128),
|
||||
(b"ic08", 256), (b"ic09", 512), (b"ic10", 1024),
|
||||
(b"ic11", 32), (b"ic12", 64), (b"ic13", 256), (b"ic14", 512)):
|
||||
data = png(size)
|
||||
chunks.append(kind + struct.pack(">I", 8 + len(data)) + data)
|
||||
body = b"".join(chunks)
|
||||
(ICONS / "icon.icns").write_bytes(b"icns" + struct.pack(">I", 8 + len(body)) + body)
|
||||
|
||||
for name in ("header", "sidebar"):
|
||||
root = ET.parse(INSTALLER / f"{name}.svg").getroot()
|
||||
width, height = int(root.get("width")), int(root.get("height"))
|
||||
text = []
|
||||
for element in list(root):
|
||||
if element.tag == f"{SVG}text":
|
||||
text.append(element)
|
||||
root.remove(element)
|
||||
elif element.tag == f"{SVG}image":
|
||||
# Installer art may reference the unframed original mark.
|
||||
# Preserve its own viewBox instead of replacing every image
|
||||
# with the square application icon.
|
||||
source = (INSTALLER / element.get("href")).resolve()
|
||||
logo = ET.parse(source).getroot()
|
||||
for attr in ("x", "y", "width", "height"):
|
||||
logo.set(attr, element.get(attr))
|
||||
logo.set("preserveAspectRatio", "xMidYMid meet")
|
||||
index = list(root).index(element)
|
||||
root.remove(element)
|
||||
root.insert(index, logo)
|
||||
edge = max(width, height)
|
||||
root.set("width", str(edge))
|
||||
root.set("height", str(edge))
|
||||
root.set("viewBox", f"0 0 {edge} {edge}")
|
||||
factor = 4
|
||||
image = render(root, edge * factor)
|
||||
draw = ImageDraw.Draw(image)
|
||||
for element in text:
|
||||
mono = element.get("font-family") == "monospace"
|
||||
bold = element.get("font-weight") == "700"
|
||||
font_path = (args.mono_font if mono else args.sans_font) or system_font(mono, bold)
|
||||
font = ImageFont.truetype(str(font_path), round(float(element.get("font-size")) * factor))
|
||||
draw.text((float(element.get("x")) * factor, float(element.get("y")) * factor),
|
||||
element.text, font=font, fill=element.get("fill"), anchor="ls")
|
||||
image = image.crop((0, 0, width * factor, height * factor)).convert("RGB")
|
||||
image.resize((width, height), Image.Resampling.LANCZOS).save(INSTALLER / f"{name}.png")
|
||||
# MUI scales these to the dialog's font/DPI. Supply 2x bitmaps so
|
||||
# Windows does not have to enlarge the raster text on modern screens.
|
||||
image.resize((width * 2, height * 2), Image.Resampling.LANCZOS).save(INSTALLER / f"{name}.bmp")
|
||||
print("Generated rounded PNG/ICO/ICNS icons and 24-bit NSIS header/sidebar artwork.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,137 @@
|
||||
# Windows uses the executable's integrity label when creating its process. A Low
|
||||
# label inherited from a sandboxed workspace prevents NSIS from writing to TEMP,
|
||||
# before .onInit or any installer hook can run.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputDir,
|
||||
[switch]$CheckProcessOnly,
|
||||
[switch]$CheckOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# whoami's group names are localized; the mandatory-label SID is stable.
|
||||
$groups = (& whoami.exe /groups) -join "`n"
|
||||
if ($LASTEXITCODE -ne 0 -or $groups -notmatch 'S-1-16-(\d+)') {
|
||||
throw 'Cannot determine the Windows process integrity level.'
|
||||
}
|
||||
if ([int]$Matches[1] -lt 8192) {
|
||||
throw 'Windows packaging requires Medium integrity or higher. Start the terminal / CI runner outside the Low integrity sandbox. Redirecting TEMP or changing directory write permissions does not fix executable integrity labels.'
|
||||
}
|
||||
if ($CheckProcessOnly) { return }
|
||||
|
||||
if (-not ('RmicBundleIntegrity' -as [type])) {
|
||||
$compilerOptions = @{}
|
||||
if ($PSVersionTable.PSEdition -eq 'Desktop') {
|
||||
# Framework CodeDOM otherwise tries to label its private temp directory
|
||||
# High when an admin token has been capped at Medium by an exe label.
|
||||
$parameters = New-Object System.CodeDom.Compiler.CompilerParameters
|
||||
$parameters.ReferencedAssemblies.Add('System.dll') | Out-Null
|
||||
$parameters.TempFiles = New-Object System.CodeDom.Compiler.TempFileCollection([IO.Path]::GetTempPath(), $false)
|
||||
$compilerOptions.CompilerParameters = $parameters
|
||||
}
|
||||
Add-Type @compilerOptions -TypeDefinition @'
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class RmicBundleIntegrity {
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint GetNamedSecurityInfoW(string name, int type, uint info,
|
||||
out IntPtr owner, out IntPtr group, out IntPtr dacl, out IntPtr sacl, out IntPtr sd);
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool ConvertSecurityDescriptorToStringSecurityDescriptorW(
|
||||
IntPtr sd, uint revision, uint info, out IntPtr text, out uint length);
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr LocalFree(IntPtr memory);
|
||||
|
||||
public static string ReadLabel(string path) {
|
||||
// LABEL_SECURITY_INFORMATION reads only the label, without requiring the
|
||||
// SeSecurityPrivilege needed to read an entire SACL (Get-Acl -Audit).
|
||||
IntPtr owner, group, dacl, sacl, sd, text = IntPtr.Zero;
|
||||
uint error = GetNamedSecurityInfoW(path, 1, 0x10, out owner, out group,
|
||||
out dacl, out sacl, out sd);
|
||||
if (error != 0) throw new Win32Exception((int)error, path);
|
||||
try {
|
||||
uint length;
|
||||
if (!ConvertSecurityDescriptorToStringSecurityDescriptorW(sd, 1, 0x10,
|
||||
out text, out length)) throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
return Marshal.PtrToStringUni(text);
|
||||
} finally {
|
||||
if (text != IntPtr.Zero) LocalFree(text);
|
||||
LocalFree(sd);
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
}
|
||||
|
||||
function Assert-MediumIntegrity([string]$Path) {
|
||||
$label = [RmicBundleIntegrity]::ReadLabel($Path)
|
||||
# An absent label is Medium. Windows may render known SIDs as SDDL aliases.
|
||||
if ($label -match ';(LW|UN|S-1-16-(\d+))\)') {
|
||||
if ($Matches[1] -in @('LW', 'UN') -or [int]$Matches[2] -lt 8192) {
|
||||
throw "Low / Untrusted integrity label on $Path. Run prepare-windows-bundle.ps1 from a normal Windows terminal before distributing this artifact."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hostOutputDir = $null
|
||||
if (-not $OutputDir) {
|
||||
# Ask Cargo so CARGO_TARGET_DIR and Cargo's target-dir setting are respected.
|
||||
$manifest = Join-Path $PSScriptRoot '../src-tauri/Cargo.toml'
|
||||
$metadata = & cargo +stable metadata --no-deps --format-version 1 --manifest-path $manifest
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Cannot resolve the Cargo output directory.' }
|
||||
$target = $env:TAURI_ENV_TARGET_TRIPLE
|
||||
if (-not $target) { $target = 'x86_64-pc-windows-msvc' }
|
||||
$profile = if ($env:TAURI_ENV_DEBUG -eq 'true') { 'debug' } else { 'release' }
|
||||
$targetDir = ($metadata | ConvertFrom-Json).target_directory
|
||||
$OutputDir = Join-Path $targetDir "$target/$profile"
|
||||
# Cross-target Cargo builds also run host build scripts from target/<profile>.
|
||||
# Old Low executables there can fail before the bundling hook is reached.
|
||||
$hostOutputDir = Join-Path $targetDir $profile
|
||||
}
|
||||
$OutputDir = [IO.Path]::GetFullPath($OutputDir)
|
||||
|
||||
# Limit changes to build outputs. Do not relabel the workspace, TEMP, user data,
|
||||
# or DACLs, and do not request elevation for the installer.
|
||||
$directories = @(
|
||||
$OutputDir
|
||||
(Join-Path $OutputDir 'bundle')
|
||||
(Join-Path $OutputDir 'bundle/nsis')
|
||||
(Join-Path $OutputDir 'nsis')
|
||||
)
|
||||
if ($hostOutputDir) { $directories = @($hostOutputDir) + $directories }
|
||||
if (Test-Path -LiteralPath (Join-Path $OutputDir 'nsis')) {
|
||||
$directories += @(Get-ChildItem -LiteralPath (Join-Path $OutputDir 'nsis') -Directory -Recurse |
|
||||
Select-Object -ExpandProperty FullName)
|
||||
}
|
||||
foreach ($directory in $directories) {
|
||||
if (-not $CheckOnly) {
|
||||
New-Item -ItemType Directory -Force -Path $directory | Out-Null
|
||||
& icacls.exe $directory /setintegritylevel '(OI)(CI)M' | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "Cannot set Medium integrity on $directory" }
|
||||
}
|
||||
Assert-MediumIntegrity $directory
|
||||
}
|
||||
|
||||
$executables = @(Get-ChildItem -LiteralPath $OutputDir -Filter '*.exe' -File)
|
||||
foreach ($directory in @('nsis', 'bundle/nsis')) {
|
||||
$executables += @(Get-ChildItem -LiteralPath (Join-Path $OutputDir $directory) -Filter '*.exe' -File -Recurse)
|
||||
}
|
||||
if ($hostOutputDir -and (Test-Path -LiteralPath (Join-Path $hostOutputDir 'build'))) {
|
||||
$executables += @(Get-ChildItem -LiteralPath (Join-Path $hostOutputDir 'build') -Filter '*.exe' -File -Recurse)
|
||||
}
|
||||
foreach ($executable in $executables) {
|
||||
if (-not $CheckOnly) {
|
||||
# Also replace explicit Low labels left by an earlier Low integrity build.
|
||||
& icacls.exe $executable.FullName /setintegritylevel M | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "Cannot set Medium integrity on $($executable.FullName)" }
|
||||
}
|
||||
Assert-MediumIntegrity $executable.FullName
|
||||
}
|
||||
if ($CheckOnly -and -not (Get-ChildItem -LiteralPath (Join-Path $OutputDir 'bundle/nsis') -Filter '*.exe' -File)) {
|
||||
throw "No NSIS installer found in $OutputDir/bundle/nsis."
|
||||
}
|
||||
Write-Host "Windows bundle integrity verified: $OutputDir ($($executables.Count) executables)."
|
||||
@@ -0,0 +1,158 @@
|
||||
# Integration regression: actual Windows labels, child process tokens, and an
|
||||
# NSIS bootstrap that writes to TEMP and exits without installing anything.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$NsisCompiler = (Join-Path $env:LOCALAPPDATA 'tauri/NSIS/makensis.exe')
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
$prepare = Join-Path $PSScriptRoot 'prepare-windows-bundle.ps1'
|
||||
& $prepare -CheckProcessOnly
|
||||
if (-not (Test-Path -LiteralPath $NsisCompiler)) { throw "NSIS compiler not found: $NsisCompiler" }
|
||||
|
||||
function Assert([bool]$Condition, [string]$Message) {
|
||||
if (-not $Condition) { throw $Message }
|
||||
}
|
||||
|
||||
$testId = [Guid]::NewGuid().ToString('N')
|
||||
$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
|
||||
$fixture = Join-Path $tempRoot "rmic bundle test $testId"
|
||||
$probeFile = Join-Path $tempRoot "rmic-nsis-temp-$testId.txt"
|
||||
$installer = Join-Path $fixture 'bundle/nsis/probe-setup.exe'
|
||||
$main = Join-Path $fixture 'cam-desktop.exe'
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $fixture 'bundle/nsis'), (Join-Path $fixture 'nsis/x64') | Out-Null
|
||||
Copy-Item -LiteralPath (Join-Path $env:WINDIR 'System32/whoami.exe') -Destination $main
|
||||
$accessBefore = (Get-Acl -LiteralPath $main).Sddl
|
||||
|
||||
# A small real NSIS executable exercises its early TEMP validation and
|
||||
# plugin extraction without touching app files, registry, or shortcuts.
|
||||
$source = Join-Path $fixture 'probe.nsi'
|
||||
@"
|
||||
Unicode true
|
||||
Name "RMIC temporary directory probe"
|
||||
OutFile "$($installer.Replace('$', '$$'))"
|
||||
RequestExecutionLevel user
|
||||
SilentInstall silent
|
||||
Section
|
||||
InitPluginsDir
|
||||
FileOpen `$0 "`$TEMP\rmic-nsis-temp-$testId.txt" w
|
||||
IfErrors failed
|
||||
FileWrite `$0 "NSIS TEMP writable"
|
||||
FileClose `$0
|
||||
SetErrorLevel 0
|
||||
Quit
|
||||
failed:
|
||||
SetErrorLevel 3
|
||||
SectionEnd
|
||||
"@ | Set-Content -LiteralPath $source -Encoding UTF8
|
||||
& $NsisCompiler /V2 $source
|
||||
Assert ($LASTEXITCODE -eq 0) 'Cannot compile the NSIS TEMP probe.'
|
||||
|
||||
& icacls.exe $fixture /setintegritylevel '(OI)(CI)L' | Out-Null
|
||||
Assert ($LASTEXITCODE -eq 0) 'Cannot create a Low integrity fixture.'
|
||||
& icacls.exe $main /setintegritylevel L | Out-Null
|
||||
Assert ($LASTEXITCODE -eq 0) 'Cannot create an explicit Low executable label.'
|
||||
$before = (& $main /groups) -join "`n"
|
||||
Assert ($LASTEXITCODE -eq 0 -and $before -match 'S-1-16-4096') 'Low executable did not start with a Low token.'
|
||||
|
||||
$rejected = $false
|
||||
try { & $prepare -OutputDir $fixture -CheckOnly } catch {
|
||||
if ($_.Exception.Message -notmatch 'Low / Untrusted') { throw }
|
||||
$rejected = $true
|
||||
}
|
||||
Assert $rejected 'Read-only artifact verification accepted a Low label.'
|
||||
Assert ([RmicBundleIntegrity]::ReadLabel($installer) -match ';LW\)') 'Read-only verification changed the installer.'
|
||||
|
||||
# A Low process must fail before it tries to modify any output labels.
|
||||
$lowShell = Join-Path $fixture 'low-shell.exe'
|
||||
Copy-Item -LiteralPath (Join-Path $env:WINDIR 'System32/WindowsPowerShell/v1.0/powershell.exe') -Destination $lowShell
|
||||
# Windows PowerShell 5 treats redirected native stderr as ErrorRecord objects.
|
||||
# This command is expected to fail, so collect its diagnostic before asserting.
|
||||
try {
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$lowOutput = (& $lowShell -NoProfile -ExecutionPolicy Bypass -File $prepare -CheckProcessOnly 2>&1) -join "`n"
|
||||
} finally {
|
||||
$ErrorActionPreference = 'Stop'
|
||||
}
|
||||
Assert ($LASTEXITCODE -ne 0 -and $lowOutput -match 'requires Medium integrity') 'Low packaging process was not rejected.'
|
||||
|
||||
& $prepare -OutputDir $fixture
|
||||
& $prepare -OutputDir $fixture -CheckOnly
|
||||
& $prepare -OutputDir $fixture
|
||||
$after = (& $main /groups) -join "`n"
|
||||
Assert ($LASTEXITCODE -eq 0 -and $after -match 'S-1-16-(\d+)' -and [int]$Matches[1] -ge 8192) 'Repaired executable still starts below Medium integrity.'
|
||||
Assert ((Get-Acl -LiteralPath $main).Sddl -eq $accessBefore) 'Repair changed the executable DACL.'
|
||||
|
||||
# The repaired shell now starts at Medium even from an elevated test runner.
|
||||
# Verify that repairing owned Low outputs does not require elevation.
|
||||
$mediumFixture = Join-Path $fixture 'medium repair'
|
||||
New-Item -ItemType Directory -Path $mediumFixture | Out-Null
|
||||
& icacls.exe $mediumFixture /setintegritylevel '(OI)(CI)L' | Out-Null
|
||||
Assert ($LASTEXITCODE -eq 0) 'Cannot prepare the Medium-process repair fixture.'
|
||||
$mediumOutput = (& $lowShell -NoProfile -ExecutionPolicy Bypass -File $prepare -OutputDir $mediumFixture 2>&1) -join "`n"
|
||||
Assert ($LASTEXITCODE -eq 0) "Repair failed at Medium integrity: $mediumOutput"
|
||||
Assert ([RmicBundleIntegrity]::ReadLabel($mediumFixture) -match ';ME\)') 'Medium process did not repair the output label.'
|
||||
|
||||
$probe = Start-Process -FilePath $installer -ArgumentList '/S' -PassThru -WindowStyle Hidden
|
||||
try {
|
||||
Assert ($probe.WaitForExit(15000)) 'NSIS TEMP probe timed out.'
|
||||
Assert ($probe.ExitCode -eq 0) "NSIS TEMP probe failed: $($probe.ExitCode)"
|
||||
Assert ((Get-Content -LiteralPath $probeFile -Raw) -eq 'NSIS TEMP writable') 'NSIS could not write to the normal TEMP directory.'
|
||||
} finally {
|
||||
if (-not $probe.HasExited) { $probe.Kill(); $probe.WaitForExit() }
|
||||
$probe.Dispose()
|
||||
}
|
||||
|
||||
$emptyOutput = Join-Path $fixture 'empty'
|
||||
& $prepare -OutputDir $emptyOutput
|
||||
$rejected = $false
|
||||
try { & $prepare -OutputDir $emptyOutput -CheckOnly } catch {
|
||||
if ($_.Exception.Message -notmatch 'No NSIS installer') { throw }
|
||||
$rejected = $true
|
||||
}
|
||||
Assert $rejected 'Artifact verification accepted missing installers.'
|
||||
|
||||
# Exercise automatic Cargo output discovery, debug target selection and the
|
||||
# separate host build-script directory, without invoking a real Rust build.
|
||||
$savedEnvironment = @{}
|
||||
foreach ($name in @('CARGO_TARGET_DIR', 'TAURI_ENV_TARGET_TRIPLE', 'TAURI_ENV_DEBUG')) {
|
||||
$savedEnvironment[$name] = @{
|
||||
Exists = Test-Path -LiteralPath "Env:$name"
|
||||
Value = [Environment]::GetEnvironmentVariable($name, 'Process')
|
||||
}
|
||||
}
|
||||
try {
|
||||
$env:CARGO_TARGET_DIR = Join-Path $fixture 'cargo cache'
|
||||
$env:TAURI_ENV_TARGET_TRIPLE = 'x86_64-pc-windows-msvc'
|
||||
$env:TAURI_ENV_DEBUG = 'true'
|
||||
$hostBuildDir = Join-Path $env:CARGO_TARGET_DIR 'debug/build/probe'
|
||||
New-Item -ItemType Directory -Force -Path $hostBuildDir | Out-Null
|
||||
$hostScript = Join-Path $hostBuildDir 'build-script-build.exe'
|
||||
Copy-Item -LiteralPath $main -Destination $hostScript
|
||||
& icacls.exe $env:CARGO_TARGET_DIR /setintegritylevel '(OI)(CI)L' | Out-Null
|
||||
Assert ($LASTEXITCODE -eq 0) 'Cannot create Low host build outputs.'
|
||||
& $prepare
|
||||
Assert ([RmicBundleIntegrity]::ReadLabel($hostScript) -match ';ME\)') 'Host build script was not repaired.'
|
||||
$targetOutput = Join-Path $env:CARGO_TARGET_DIR 'x86_64-pc-windows-msvc/debug/bundle/nsis'
|
||||
Assert (Test-Path -LiteralPath $targetOutput) 'Cargo target directory or debug profile was ignored.'
|
||||
} finally {
|
||||
foreach ($name in $savedEnvironment.Keys) {
|
||||
if ($savedEnvironment[$name].Exists) {
|
||||
Set-Item -LiteralPath "Env:$name" -Value $savedEnvironment[$name].Value
|
||||
} else {
|
||||
Remove-Item -LiteralPath "Env:$name" -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host 'PASS: Low token reproduction, process guard, read-only rejection, label repair, unchanged DACL, repeat repair, NSIS TEMP bootstrap, missing artifacts, custom Cargo output and host build scripts.'
|
||||
} finally {
|
||||
# Only delete this test's uniquely named fixture, after checking its absolute path.
|
||||
$resolvedFixture = [IO.Path]::GetFullPath($fixture)
|
||||
if ($resolvedFixture.StartsWith($tempRoot.TrimEnd('\') + '\', [StringComparison]::OrdinalIgnoreCase) -and
|
||||
(Split-Path -Leaf $resolvedFixture) -eq "rmic bundle test $testId") {
|
||||
if (Test-Path -LiteralPath $resolvedFixture) { Remove-Item -LiteralPath $resolvedFixture -Recurse -Force }
|
||||
}
|
||||
if (Test-Path -LiteralPath $probeFile) { Remove-Item -LiteralPath $probeFile -Force }
|
||||
}
|
||||
Reference in New Issue
Block a user