fix(updater): separate Windows 7 offline update path

This commit is contained in:
zipg 2026-07-27 16:44:47 +08:00 committed by GitHub
parent 0b7a886cfd
commit 0aced0012c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1851 additions and 24 deletions

View File

@ -0,0 +1,65 @@
[CmdletBinding()]
param(
[string]$CacheRoot = (Join-Path $env:LOCALAPPDATA "tauri"),
[string]$DownloadDirectory = $env:RUNNER_TEMP
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$runtimeVersion = "109.0.1518.140"
$runtimeUrl = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/updt/2023/09/microsoftedgestandaloneinstallerx64_1c890b4b8dd6b7c93da98ebdc08ecdc5e30e50cb.exe"
$runtimeSha256 = "eac95c8095ec5f9971eade9827d8fb67fd251f5c16e702b5312d31067e39119b"
$evergreenUrl = "https://go.microsoft.com/fwlink/?linkid=2124701"
if ([string]::IsNullOrWhiteSpace($CacheRoot)) {
throw "A Tauri cache root is required."
}
if ([string]::IsNullOrWhiteSpace($DownloadDirectory)) {
$DownloadDirectory = [System.IO.Path]::GetTempPath()
}
New-Item -ItemType Directory -Force -Path $DownloadDirectory | Out-Null
$downloadPath = Join-Path $DownloadDirectory "MicrosoftEdgeWebView2Runtime-$runtimeVersion-x64.exe"
if (Test-Path $downloadPath) {
$downloadHash = (Get-FileHash -LiteralPath $downloadPath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($downloadHash -ne $runtimeSha256) {
Remove-Item -LiteralPath $downloadPath -Force
}
}
if (!(Test-Path $downloadPath)) {
Write-Host "Downloading WebView2 Runtime $runtimeVersion for Windows 7..."
Invoke-WebRequest -Uri $runtimeUrl -OutFile $downloadPath
}
$actualHash = (Get-FileHash -LiteralPath $downloadPath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actualHash -ne $runtimeSha256) {
throw "WebView2 Runtime SHA-256 mismatch. Expected $runtimeSha256, got $actualHash."
}
# Tauri 2.11 does not expose an offline-installer path override. It resolves the
# Evergreen URL and reuses a matching cache entry, so place the verified 109
# installer at that exact location before bundling.
$response = Invoke-WebRequest -Uri $evergreenUrl -Method Head
$resolvedUrl = $response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri
$match = [regex]::Match(
$resolvedUrl,
"/filestreamingservice/files/(?<guid>[^/]+)/(?<filename>[^/?]+)"
)
if (!$match.Success) {
throw "Unexpected Evergreen WebView2 URL: $resolvedUrl"
}
$cacheDirectory = Join-Path $CacheRoot (Join-Path "x64" $match.Groups["guid"].Value)
$cachePath = Join-Path $cacheDirectory $match.Groups["filename"].Value
New-Item -ItemType Directory -Force -Path $cacheDirectory | Out-Null
Copy-Item -LiteralPath $downloadPath -Destination $cachePath -Force
$cacheHash = (Get-FileHash -LiteralPath $cachePath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($cacheHash -ne $runtimeSha256) {
throw "Cached WebView2 Runtime SHA-256 mismatch. Expected $runtimeSha256, got $cacheHash."
}
Write-Host "Prepared WebView2 Runtime $runtimeVersion at $cachePath"

View File

@ -79,6 +79,60 @@ jobs:
- name: Node package publish dry run
run: pnpm publish:dry-run
windows-win7-bundle:
needs: changes
if: needs.changes.outputs.windows_win7_bundle == 'true'
runs-on: windows-2022
timeout-minutes: 90
env:
CARGO_INCREMENTAL: "0"
steps:
- uses: actions/checkout@v5
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 10.27.0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22.13.0
cache: pnpm
- name: Install frontend dependencies
run: pnpm install --frozen-lockfile
- name: Setup Rust for Windows 7
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly-2026-07-22
components: rust-src
- name: Build frontend
run: pnpm build
- name: Build DBX for Windows 7
run: cargo build --locked --package dbx --release --target x86_64-win7-windows-msvc -Z build-std=std,panic_abort
- name: Prepare WebView2 109 offline runtime
shell: pwsh
run: ./.github/scripts/prepare-webview2-win7-runtime.ps1
- name: Bundle Windows 7 offline installer
shell: pwsh
run: |
$bundleDir = "target/x86_64-win7-windows-msvc/release/bundle/nsis"
pnpm tauri bundle --bundles nsis --target x86_64-win7-windows-msvc --config src-tauri/tauri.webview2-win7-offline.conf.json
$installer = Get-ChildItem $bundleDir -Filter "*.exe" |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
if (!$installer) {
Write-Error "Missing Windows 7 WebView2 offline installer in ${bundleDir}"
exit 1
}
Get-FileHash -LiteralPath $installer.FullName -Algorithm SHA256
rust-fmt-clippy:
needs: changes
if: needs.changes.outputs.rust == 'true'
@ -299,6 +353,7 @@ jobs:
jdbc: ${{ steps.filter.outputs.jdbc }}
agents: ${{ steps.filter.outputs.agents }}
nix: ${{ steps.filter.outputs.nix }}
windows_win7_bundle: ${{ steps.filter.outputs.windows_win7_bundle }}
steps:
- uses: actions/checkout@v5
with:
@ -350,6 +405,17 @@ jobs:
- 'flake.lock'
- '.github/workflows/ci.yml'
- '.github/workflows/update-nix-pnpm-hash.yml'
windows_win7_bundle:
- '.github/scripts/prepare-webview2-win7-runtime.ps1'
- '.github/workflows/ci.yml'
- '.github/workflows/release.yml'
- 'src-tauri/tauri.webview2-win7-offline.conf.json'
- 'src-tauri/windows/nsis/**'
- 'src-tauri/src/commands/update.rs'
- 'crates/dbx-core/src/update.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- 'vendor/ctor/**'
- name: Select Rust feature coverage
id: rust-mode

View File

@ -335,6 +335,112 @@ jobs:
Copy-Item $installer.FullName $offlineName -Force
gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber
build-windows-7-offline:
runs-on: windows-2022
env:
CARGO_INCREMENTAL: "0"
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Install frontend dependencies
run: pnpm install
- name: Setup Rust for Windows 7
uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly-2026-07-22
components: rust-src
- name: Setup sccache
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
with:
version: "v0.10.0"
- name: Configure S3 sccache
if: env.SCCACHE_GHA_ENABLED != 'true'
shell: bash
env:
CACHE_BUCKET: ${{ secrets.SCCACHE_S3_BUCKET }}
CACHE_ENDPOINT: ${{ secrets.SCCACHE_S3_ENDPOINT }}
CACHE_REGION: ${{ secrets.SCCACHE_S3_REGION }}
CACHE_KEY_PREFIX: ${{ secrets.SCCACHE_S3_KEY_PREFIX }}
CACHE_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }}
CACHE_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }}
run: |
{
echo "SCCACHE_BUCKET=${CACHE_BUCKET}"
echo "SCCACHE_ENDPOINT=${CACHE_ENDPOINT}"
echo "SCCACHE_REGION=${CACHE_REGION}"
echo "SCCACHE_S3_KEY_PREFIX=${CACHE_KEY_PREFIX}"
echo "SCCACHE_S3_USE_SSL=true"
echo "AWS_ACCESS_KEY_ID=${CACHE_ACCESS_KEY_ID}"
echo "AWS_SECRET_ACCESS_KEY=${CACHE_SECRET_ACCESS_KEY}"
} >> "$GITHUB_ENV"
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./ -> target"
shared-key: release-x86_64-win7-windows-msvc
add-rust-environment-hash-key: true
cache-targets: false
cache-on-failure: true
- name: Build frontend
run: pnpm build
- name: Build DBX for Windows 7
run: cargo build --locked --package dbx --release --target x86_64-win7-windows-msvc -Z build-std=std,panic_abort
- name: Prepare WebView2 109 offline runtime
shell: pwsh
run: ./.github/scripts/prepare-webview2-win7-runtime.ps1
- name: Bundle and upload Windows 7 offline installer
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$version = "${env:GITHUB_REF_NAME}".TrimStart("v")
$bundleDir = "target/x86_64-win7-windows-msvc/release/bundle/nsis"
$offlineName = "DBX_${version}_x64-win7-webview2-109-offline-setup.exe"
$cargoMetadata = cargo metadata --no-deps --format-version 1 | ConvertFrom-Json
$appVersion = ($cargoMetadata.packages | Where-Object { $_.name -eq "dbx" } | Select-Object -First 1).version
if (!$appVersion -or $appVersion -ne $version) {
Write-Error "Release tag version $version does not match the built DBX package version $appVersion"
exit 1
}
pnpm tauri bundle --bundles nsis --target x86_64-win7-windows-msvc --config src-tauri/tauri.webview2-win7-offline.conf.json
$installer = Get-ChildItem $bundleDir -Filter "*.exe" |
Sort-Object LastWriteTimeUtc -Descending |
Select-Object -First 1
if (!$installer) {
Write-Error "Missing Windows 7 WebView2 offline installer in ${bundleDir}"
exit 1
}
Copy-Item $installer.FullName $offlineName -Force
gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber
- name: Show sccache stats
if: always()
continue-on-error: true
shell: bash
run: ${SCCACHE_PATH} --show-stats
static-browser:
# Fully static musl builds of the browser (dbx-web) variant. No glibc
# dependency, so the tarball runs on any Linux distribution (verified in
@ -438,7 +544,7 @@ jobs:
--repo "${GITHUB_REPOSITORY}" --clobber
cleanup-release-signatures:
needs: build
needs: [build, build-windows-7-offline]
runs-on: ubuntu-latest
steps:
- name: Remove standalone updater signature assets

2
Cargo.lock generated
View File

@ -1674,8 +1674,6 @@ dependencies = [
[[package]]
name = "ctor"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
dependencies = [
"ctor-proc-macro",
"dtor",

View File

@ -1,8 +1,12 @@
[workspace]
resolver = "2"
members = ["src-tauri", "crates/dbx-core", "crates/dbx-web", "crates/dbx-mcp", "crates/dbx-cli"]
exclude = ["vendor/ctor"]
[patch.crates-io]
# Tauri 2.11 uses ctor 0.8, which excludes Rust's win7 vendor. This vendors
# upstream rust-ctor#443 until tauri-utils moves to ctor 1.x.
ctor = { path = "vendor/ctor" }
tokio-postgres = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", branch = "master" }
postgres-types = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", branch = "master" }
postgres-protocol = { git = "https://github.com/t8y2/tokio-postgres-gaussdb.git", branch = "master" }

View File

@ -14,6 +14,7 @@ const mountedApps: App[] = [];
interface DialogState {
open: boolean;
portableMode: boolean;
manualUpdateOnly: boolean;
isDownloadingUpdate: boolean;
downloadProgress: number;
updateDownloaded: boolean;
@ -30,6 +31,7 @@ async function mountDialog(activeTaskCount: number, initialState: Partial<Dialog
const state = reactive<DialogState>({
open: true,
portableMode: false,
manualUpdateOnly: false,
isDownloadingUpdate: false,
downloadProgress: 0,
updateDownloaded: false,
@ -67,6 +69,7 @@ async function mountDialog(activeTaskCount: number, initialState: Partial<Dialog
latest_version: "0.5.61",
update_available: true,
portable_mode: state.portableMode,
manual_update_only: state.manualUpdateOnly,
release_name: "DBX v0.5.61",
release_url: "https://github.com/t8y2/dbx/releases/tag/v0.5.61",
release_notes: "",
@ -136,6 +139,23 @@ describe("UpdateDialog active task guard", () => {
expect(downloadButton()?.disabled).toBe(false);
});
it("routes Windows 7 builds to the dedicated installer", async () => {
await mountDialog(0, { manualUpdateOnly: true });
expect(document.body.textContent).toContain("WebView2 109 offline installer");
expect(downloadButton()).toBeUndefined();
expect(buttonWithText("Open Release")).toBeDefined();
});
it("prevents Windows 7 portable builds from installing the regular x64 portable update", async () => {
await mountDialog(0, { portableMode: true, manualUpdateOnly: true });
expect(document.body.textContent).toContain("WebView2 109 offline installer");
expect(document.body.textContent).not.toContain("signed portable ZIP");
expect(downloadButton()).toBeUndefined();
expect(buttonWithText("Open Release")).toBeDefined();
});
it("retains the downloaded update and enables installation only after tasks finish", async () => {
await mountDialog(1, { updateDownloaded: true, downloadProgress: 100 });

View File

@ -106,9 +106,12 @@ watch(
<code class="bg-muted px-1 py-0.5 rounded text-[11px]">docker compose pull && docker compose up -d</code>
{{ t("updates.toUpdate") }}
</p>
<p v-if="isDesktop && updateInfo?.update_available && updateInfo.portable_mode" class="text-xs text-muted-foreground">
<p v-if="isDesktop && updateInfo?.update_available && updateInfo.portable_mode && !updateInfo.manual_update_only" class="text-xs text-muted-foreground">
{{ t("updates.portableAutomaticUpdate") }}
</p>
<p v-if="isDesktop && updateInfo?.update_available && updateInfo.manual_update_only" class="text-xs text-muted-foreground">
{{ t("updates.windows7ManualUpdate") }}
</p>
<div v-if="canDownloadAndInstallUpdate(updateInfo, isDesktop) && activeTaskCount > 0" role="alert" class="flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-300">
<AlertTriangle class="mt-0.5 h-4 w-4 shrink-0" />
<span>{{ t("updates.activeTasksBlockUpdate", { count: activeTaskCount }) }}</span>

View File

@ -19,7 +19,7 @@ export function shouldOpenUpdateDialog(options: { silent?: boolean }) {
}
export function canDownloadAndInstallUpdate(info: api.UpdateInfo | null, isDesktop: boolean) {
return isDesktop && info?.update_available === true;
return isDesktop && info?.update_available === true && info.manual_update_only !== true;
}
export function normalizeUpdateDownloadSource(value: unknown): SettingsUpdateDownloadSource {

View File

@ -71,6 +71,7 @@ export default {
downloadAndInstall: "Download & Install",
activeTasksBlockUpdate: "{count} task(s) are still running. Wait for them to finish before updating DBX.",
portableAutomaticUpdate: "DBX will download the signed portable ZIP, replace only DBX.exe after exit, and restart automatically. portable.dbx and data will be kept.",
windows7ManualUpdate: "Windows 7 requires the dedicated WebView2 109 offline installer. Open the release page and download the Windows 7 package to update.",
downloading: "Downloading {progress}%",
downloadFailed: "Update download failed: {error}",
installing: "Installing update...",

View File

@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "Descargar e instalar",
activeTasksBlockUpdate: "Hay {count} tarea(s) en ejecución. Espera a que terminen antes de actualizar DBX.",
portableAutomaticUpdate: "DBX descargará el ZIP portable firmado, reemplazará solo DBX.exe después de salir y se reiniciará automáticamente. portable.dbx y data se conservarán.",
windows7ManualUpdate: "Windows 7 requiere el instalador sin conexión dedicado de WebView2 109. Abre la página de lanzamiento y descarga el paquete para Windows 7.",
downloading: "Descargando {progress}%",
downloadFailed: "Error al descargar la actualización: {error}",
installing: "Instalando actualización...",

View File

@ -72,6 +72,7 @@ export default withEnglishFallback({
downloadAndInstall: "Scarica e Installa",
activeTasksBlockUpdate: "Ci sono {count} attività in esecuzione. Attendi che terminino prima di aggiornare DBX.",
portableAutomaticUpdate: "DBX scaricherà lo ZIP portatile firmato, sostituirà solo DBX.exe dopo l'uscita e si riavvierà automaticamente. portable.dbx e i dati verranno mantenuti.",
windows7ManualUpdate: "Windows 7 richiede il programma di installazione offline dedicato WebView2 109. Apri la pagina della release e scarica il pacchetto per Windows 7.",
downloading: "Download in corso {progress}%",
downloadFailed: "Download dell'aggiornamento non riuscito: {error}",
installing: "Installazione dell'aggiornamento...",

View File

@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "ダウンロード & インストール",
activeTasksBlockUpdate: "{count} 件のタスクが実行中です。完了してから DBX を更新してください。",
portableAutomaticUpdate: "DBX は署名済みのポータブル ZIP をダウンロードし、終了後に DBX.exe のみを置き換えて自動的に再起動します。portable.dbx とデータは保持されます。",
windows7ManualUpdate: "Windows 7 では専用の WebView2 109 オフラインインストーラーが必要です。リリースページから Windows 7 用パッケージをダウンロードして更新してください。",
downloading: "ダウンロード中 {progress}%",
downloadFailed: "アップデートのダウンロードに失敗しました: {error}",
installing: "アップデートをインストール中...",

View File

@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "Baixar e Instalar",
activeTasksBlockUpdate: "Há {count} tarefa(s) em execução. Aguarde a conclusão antes de atualizar o DBX.",
portableAutomaticUpdate: "O DBX baixará o ZIP portátil assinado, substituirá apenas o DBX.exe após sair e reiniciará automaticamente. O portable.dbx e os dados serão mantidos.",
windows7ManualUpdate: "O Windows 7 requer o instalador offline dedicado do WebView2 109. Abra a página da versão e baixe o pacote para Windows 7.",
downloading: "Baixando {progress}%",
downloadFailed: "Falha ao baixar a atualização: {error}",
installing: "Instalando atualização...",

View File

@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "下载并安装",
activeTasksBlockUpdate: "有 {count} 个任务正在执行,请等待任务完成后再更新 DBX。",
portableAutomaticUpdate: "DBX 将下载已签名的便携版 ZIP退出后仅替换 DBX.exe 并自动重启。portable.dbx 和 data 会保留。",
windows7ManualUpdate: "Windows 7 需要使用专用的 WebView2 109 离线安装包更新。请打开下载页并下载 Windows 7 安装包。",
downloading: "下载中 {progress}%",
downloadFailed: "更新下载失败:{error}",
installing: "正在安装更新...",

View File

@ -73,6 +73,7 @@ export default withEnglishFallback({
downloadAndInstall: "下載並安裝",
activeTasksBlockUpdate: "有 {count} 個任務正在執行,請等待任務完成後再更新 DBX。",
portableAutomaticUpdate: "DBX 將下載已簽署的可攜版 ZIP結束後僅替換 DBX.exe 並自動重新啟動。portable.dbx 和 data 會保留。",
windows7ManualUpdate: "Windows 7 需要使用專用的 WebView2 109 離線安裝套件更新。請開啟下載頁並下載 Windows 7 安裝套件。",
downloading: "下載中 {progress}%",
downloadFailed: "更新下載失敗:{error}",
installing: "正在安裝更新...",

View File

@ -0,0 +1,65 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import { shouldAbortWindowsWebView2RuntimeFallback } from "@/lib/app/windowsWebView2RuntimePolicy";
const template = readFileSync(resolve(process.cwd(), "src-tauri/windows/nsis/installer.nsi"), "utf8");
describe("Windows offline installer template", () => {
it.each([
{
name: "continues after installer failure when a compatible Runtime remains",
input: { installerExitCode: 1, runtimeDetected: true, runtimeMeetsMinimum: true },
expected: false,
},
{
name: "aborts after installer failure when the Runtime is missing",
input: { installerExitCode: 1, runtimeDetected: false, runtimeMeetsMinimum: false },
expected: true,
},
{
name: "aborts after installer failure when the Runtime is below the minimum",
input: { installerExitCode: 1, runtimeDetected: true, runtimeMeetsMinimum: false },
expected: true,
},
{
name: "continues after a successful installer even before the registry refresh",
input: { installerExitCode: 0, runtimeDetected: false, runtimeMeetsMinimum: false },
expected: false,
},
])("$name", ({ input, expected }) => {
expect(shouldAbortWindowsWebView2RuntimeFallback(input)).toBe(expected);
});
it("binds the tested fallback inputs to the NSIS Runtime recheck contract", () => {
expect(template).toContain(`!macro ShouldAbortWebView2OfflineInstall INSTALL_RESULT RUNTIME_VERSION MINIMUM_COMPARISON RESULT
StrCpy \${RESULT} 0
\${If} \${INSTALL_RESULT} <> 0
\${If} \${RUNTIME_VERSION} == ""
StrCpy \${RESULT} 1
\${ElseIf} \${MINIMUM_COMPARISON} = 1
StrCpy \${RESULT} 1
\${EndIf}
\${EndIf}
!macroend`);
expect(template).toContain(`!insertmacro ReadWebView2RuntimeVersion $4
\${If} $4 != ""
!if "\${MINIMUMWEBVIEW2VERSION}" != ""
\${VersionCompare} "\${MINIMUMWEBVIEW2VERSION}" "$4" $R0
!endif
\${EndIf}
\${EndIf}
!insertmacro ShouldAbortWebView2OfflineInstall $1 $4 $R0 $R1
\${If} $R1 = 1
Abort "$(webview2AbortError)"
\${EndIf}`);
});
it("uses the same registry detection before and after Runtime installation", () => {
const runtimeChecks = template.match(/!insertmacro ReadWebView2RuntimeVersion \$4/g) ?? [];
expect(runtimeChecks).toHaveLength(2);
expect(template).toContain('ReadRegStr ${RESULT} HKLM "SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}" "pv"');
expect(template).toContain('ReadRegStr ${RESULT} HKCU "SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\${WEBVIEW2APPGUID}" "pv"');
});
});

View File

@ -0,0 +1,9 @@
export interface WindowsWebView2RuntimeFallbackInput {
installerExitCode: number;
runtimeDetected: boolean;
runtimeMeetsMinimum: boolean;
}
export function shouldAbortWindowsWebView2RuntimeFallback(input: WindowsWebView2RuntimeFallbackInput): boolean {
return input.installerExitCode !== 0 && (!input.runtimeDetected || !input.runtimeMeetsMinimum);
}

View File

@ -1492,6 +1492,7 @@ export interface UpdateInfo {
latest_version: string;
update_available: boolean;
portable_mode: boolean;
manual_update_only: boolean;
release_name: string;
release_url: string;
release_notes: string;

View File

@ -42,6 +42,7 @@ pub struct UpdateInfo {
pub latest_version: String,
pub update_available: bool,
pub portable_mode: bool,
pub manual_update_only: bool,
pub release_name: String,
pub release_url: String,
pub release_notes: String,
@ -292,6 +293,7 @@ pub fn build_update_info(release: TauriRelease, current_version: &str) -> Update
UpdateInfo {
update_available: is_newer_version(&latest_version, current_version),
portable_mode: false,
manual_update_only: false,
current_version: current_version.to_string(),
release_name,
release_url,

View File

@ -21,6 +21,7 @@ const GITHUB_RELEASE_DOWNLOAD_PREFIX: &str = "https://github.com/t8y2/dbx/releas
const UPDATE_DOWNLOAD_PROGRESS_EVENT: &str = "update-download-progress";
const MAX_PORTABLE_ARCHIVE_BYTES: usize = 512 * 1024 * 1024;
const MAX_PORTABLE_SIGNATURE_BYTES: usize = 64 * 1024;
const IS_WINDOWS_7_TARGET: bool = cfg!(target_vendor = "win7");
#[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
@ -208,9 +209,14 @@ pub async fn check_for_updates(
let current_version = env!("CARGO_PKG_VERSION");
let mut info = dbx_core::update::build_update_info(release, current_version);
info.portable_mode = crate::data_dir::is_portable_mode();
info.manual_update_only = requires_manual_update(IS_WINDOWS_7_TARGET);
Ok(info)
}
fn requires_manual_update(is_windows_7_target: bool) -> bool {
is_windows_7_target
}
#[tauri::command]
pub async fn fetch_changelog(lang: Option<String>) -> Result<dbx_core::changelog::ChangelogData, String> {
let lang = lang.unwrap_or_else(|| "en".to_string());
@ -229,7 +235,11 @@ pub async fn download_update(
source: UpdateDownloadSource,
latest_version: Option<String>,
) -> Result<(), String> {
let portable_version = if crate::data_dir::is_portable_mode() {
let portable_mode = crate::data_dir::is_portable_mode();
if requires_manual_update(IS_WINDOWS_7_TARGET) {
return Err("Windows 7 builds must be updated with the dedicated Windows 7 offline installer.".to_string());
}
let portable_version = if portable_mode {
let requested_version =
latest_version.as_deref().ok_or_else(|| "Latest version is required for portable updates.".to_string())?;
Some(update_portable::validate_requested_portable_version(requested_version, env!("CARGO_PKG_VERSION"))?)
@ -446,10 +456,16 @@ async fn update_url_is_available(url: &str) -> bool {
#[cfg(test)]
mod tests {
use super::{
tag_version, UpdateDownloadSource, CNB_RELEASE_DOWNLOAD_PREFIX, GITHUB_RELEASE_DOWNLOAD_PREFIX,
OFFICIAL_UPDATE_ENDPOINTS, R2_LATEST_RELEASE_DOWNLOAD_PREFIX,
requires_manual_update, tag_version, UpdateDownloadSource, CNB_RELEASE_DOWNLOAD_PREFIX,
GITHUB_RELEASE_DOWNLOAD_PREFIX, OFFICIAL_UPDATE_ENDPOINTS, R2_LATEST_RELEASE_DOWNLOAD_PREFIX,
};
#[test]
fn all_windows_7_builds_require_manual_updates() {
assert!(requires_manual_update(true));
assert!(!requires_manual_update(false));
}
#[test]
fn normalizes_update_tag_versions() {
assert_eq!(tag_version("0.5.39"), "v0.5.39");

View File

@ -0,0 +1,11 @@
{
"bundle": {
"createUpdaterArtifacts": false,
"windows": {
"webviewInstallMode": {
"silent": true,
"type": "offlineInstaller"
}
}
}
}

View File

@ -65,6 +65,29 @@ ${StrLoc}
!define STARTMENUFOLDER "{{start_menu_folder}}"
!searchreplace WEBVIEW2LOADERSRCPATH "${MAINBINARYSRCPATH}" "\${MAINBINARYNAME}.exe" "\WebView2Loader.dll"
!macro ReadWebView2RuntimeVersion RESULT
StrCpy ${RESULT} ""
${If} ${RunningX64}
ReadRegStr ${RESULT} HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${Else}
ReadRegStr ${RESULT} HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${EndIf}
${If} ${RESULT} == ""
ReadRegStr ${RESULT} HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${EndIf}
!macroend
!macro ShouldAbortWebView2OfflineInstall INSTALL_RESULT RUNTIME_VERSION MINIMUM_COMPARISON RESULT
StrCpy ${RESULT} 0
${If} ${INSTALL_RESULT} <> 0
${If} ${RUNTIME_VERSION} == ""
StrCpy ${RESULT} 1
${ElseIf} ${MINIMUM_COMPARISON} = 1
StrCpy ${RESULT} 1
${EndIf}
${EndIf}
!macroend
Var PassiveMode
Var UpdateMode
Var NoShortcutMode
@ -542,15 +565,36 @@ Section EarlyChecks
SectionEnd
Section WebView2
; Offline packages carry the runtime they were built for. Always run that
; installer so an existing stale runtime is upgraded without network access.
!if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
DetailPrint "$(installingWebview2)"
ExecWait '"$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" ${WEBVIEW2INSTALLERARGS} /install' $1
Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
StrCpy $4 ""
StrCpy $R0 0
${If} $1 = 0
DetailPrint "$(webview2InstallSuccess)"
${Else}
DetailPrint "$(webview2InstallError)"
; Enterprise policy can make the bundled installer return a non-zero code.
; Continue when a usable Runtime is already registered on the machine.
!insertmacro ReadWebView2RuntimeVersion $4
${If} $4 != ""
!if "${MINIMUMWEBVIEW2VERSION}" != ""
${VersionCompare} "${MINIMUMWEBVIEW2VERSION}" "$4" $R0
!endif
${EndIf}
${EndIf}
!insertmacro ShouldAbortWebView2OfflineInstall $1 $4 $R0 $R1
${If} $R1 = 1
Abort "$(webview2AbortError)"
${EndIf}
!else
; Check if Webview2 is already installed and skip this section
${If} ${RunningX64}
ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${Else}
ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${EndIf}
${If} $4 == ""
ReadRegStr $4 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\${WEBVIEW2APPGUID}" "pv"
${EndIf}
!insertmacro ReadWebView2RuntimeVersion $4
${If} $4 == ""
; Webview2 installation
@ -580,14 +624,6 @@ Section WebView2
Goto install_webview2
!endif
!if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
DetailPrint "$(installingWebview2)"
StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
Goto install_webview2
!endif
Goto webview2_done
install_webview2:
@ -631,6 +667,7 @@ Section WebView2
${EndIf}
!endif
${EndIf}
!endif
SectionEnd
Section Install

63
vendor/ctor/Cargo.toml vendored Normal file
View File

@ -0,0 +1,63 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "ctor"
version = "0.8.0"
authors = ["Matt Mastracci <matthew@mastracci.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "__attribute__((constructor)) for Rust"
readme = "README.md"
categories = ["no-std"]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/mmastrac/rust-ctor"
[features]
__no_warn_on_missing_unsafe = ["dtor?/__no_warn_on_missing_unsafe"]
default = [
"std",
"dtor",
"proc_macro",
"__no_warn_on_missing_unsafe",
]
dtor = ["dep:dtor"]
proc_macro = [
"dep:ctor-proc-macro",
"dtor?/proc_macro",
]
std = ["dtor?/std"]
used_linker = ["dtor?/used_linker"]
[lib]
name = "ctor"
path = "src/lib.rs"
[[example]]
name = "example"
path = "src/example.rs"
[dependencies.ctor-proc-macro]
version = "=0.0.7"
optional = true
[dependencies.dtor]
version = "0.3.0"
optional = true
default-features = false
[dev-dependencies.libc-print]
version = "0.1.20"

201
vendor/ctor/LICENSE-APACHE vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

5
vendor/ctor/LICENSE-MIT vendored Normal file
View File

@ -0,0 +1,5 @@
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

144
vendor/ctor/README.md vendored Normal file
View File

@ -0,0 +1,144 @@
# rust-ctor
![Build Status](https://github.com/mmastrac/rust-ctor/actions/workflows/rust.yml/badge.svg)
`ctor` [![docs.rs](https://docs.rs/ctor/badge.svg)](https://docs.rs/ctor)
[![crates.io](https://img.shields.io/crates/v/ctor.svg)](https://crates.io/crates/ctor)
`dtor` [![docs.rs](https://docs.rs/dtor/badge.svg)](https://docs.rs/dtor)
[![crates.io](https://img.shields.io/crates/v/dtor.svg)](https://crates.io/crates/dtor)
Module initialization/teardown functions for Rust (like
`__attribute__((constructor))` in C/C++) for Linux, OSX, FreeBSD, NetBSD,
Illumos, OpenBSD, DragonFlyBSD, Android, iOS, WASM, and Windows.
This library currently requires **Rust > 1.56.0** at a minimum for edition 2021
support. Library versions 0.2.x should work for edition 2018, and 1.0 is planned
to be released as 2021-only.
## Zero Dependency
As of `ctor 0.3.0+`, `ctor` has no dependencies (other than the
`ctor-proc-macro` crate). The proc macro in this crate calls into the
declarative macro that does the majority of the work.
## Support
This library works and is regularly tested on Linux, OSX, Windows, and FreeBSD,
with both `+crt-static` and `-crt-static` where possible. Other platforms are
supported but not tested as part of the automatic builds. This library will also
work as expected in both `bin` and `cdylib` outputs, ie: the `ctor` and `dtor`
will run at executable or library startup/shutdown respectively.
This library supports WASM targets, but the MSRV for this target is 1.85.
## Features
| Feature | Description | Default |
| ------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- |
| `std` | Enable support for the standard library. This is required for static ctor variables, but not for functions. | Yes |
| `proc_macro` | Enable support for the proc macro. Required for `#[ctor]` and `#[dtor]` macros, but not for `ctor!` and `dtor!` forms. | Yes |
| `dtor` | Include `#[dtor]` support in the `ctor` crate. | Yes |
| `used_linker` | Enable support for `#[used(linker)]` (nightly only). | No |
## Warnings
Rust's philosophy is that nothing happens before or after main and this library
explicitly subverts that. The code that runs in the `ctor` and `dtor` functions
should be careful to limit itself to `libc` functions and code that does not
rely on Rust's stdlib services.
For example, using stdout in a `dtor` function is a guaranteed panic. Consider
using the [`libc-print` crate](https://crates.io/crates/libc-print) for output
to stderr/stdout during `#[ctor]` and `#[dtor]` methods. Other issues may
involve signal processing or panic handling in that early code.
Some linker configurations may cause `#[ctor]` and `#[dtor]` functions to be
stripped from the final binary. The `used_linker` feature may prevent this, but
is not supported outside of nightly Rust. Often, a simple `use module_with_ctor`
is sufficient to ensure the linker does not strip the function.
On some platforms, unloading of shared libraries may not actually happen until
process exit, even if explicitly unloaded. The rules for this are arcane and
difficult to understand. For example, thread-local storage on OSX will affect
this (see
[this comment](https://github.com/rust-lang/rust/issues/28794#issuecomment-368693049)).
## Examples
Marks the function `foo` as a module constructor, called when a static library
is loaded or an executable is started:
```rust
static INITED: AtomicBool = AtomicBool::new(false);
#[ctor]
fn foo() {
INITED.store(true, Ordering::SeqCst);
}
```
Creates a `HashMap` populated with strings when a static library is loaded or an
executable is started (new in `0.1.7`):
`static` items are equivalent to `std::sync::OnceLock`, with an automatic deref
implementation and eager initialization at startup time. `#[ctor]` on `static`
items requires the default `std` feature.
```rust
#[ctor]
/// This is an immutable static, evaluated at init time
static STATIC_CTOR: HashMap<u32, &'static str> = {
let mut m = HashMap::new();
m.insert(0, "foo");
m.insert(1, "bar");
m.insert(2, "baz");
m
};
```
Print a message at shutdown time. Note that Rust may have shut down some stdlib
services at this time.
```rust
#[dtor]
unsafe fn shutdown() {
// Using println or eprintln here will panic as Rust has shut down
libc::printf("Shutting down!\n\0".as_ptr() as *const i8);
}
```
## Under the Hood
The `#[ctor]` macro makes use of linker sections to ensure that a function is
run at startup time.
The above example translates into the following Rust code (approximately):
```rust
#[used]
#[cfg_attr(target_os = "linux", link_section = ".init_array")]
#[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func,mod_init_funcs")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
/* ... other platforms elided ... */
static FOO: extern fn() = {
extern fn foo() { /* ... */ };
foo
};
```
The `#[dtor]` macro effectively creates a constructor that calls `libc::atexit`
with the provided function, ie roughly equivalent to:
```rust
#[ctor]
fn dtor_atexit() {
libc::atexit(dtor);
}
```
## Inspiration
Idea inspired by
[this code](https://github.com/neon-bindings/neon/blob/2277e943a619579c144c1da543874f4a7ec39879/src/lib.rs#L42)
in the Neon project.

111
vendor/ctor/src/example.rs vendored Normal file
View File

@ -0,0 +1,111 @@
//! This example demonstrates the various types of ctor/dtor in an executable
//! context.
#![cfg_attr(feature = "used_linker", feature(used_with_arg))]
use ctor::{ctor, dtor};
use libc_print::*;
use std::collections::HashMap;
#[ctor]
/// This is an immutable static, evaluated at init time
static STATIC_CTOR: HashMap<u32, &'static str> = unsafe {
let mut m = HashMap::new();
_ = m.insert(0, "foo");
_ = m.insert(1, "bar");
_ = m.insert(2, "baz");
libc_eprintln!("STATIC_CTOR");
m
};
#[ctor(anonymous)]
unsafe fn anonymous_ctor() {
libc_eprintln!("ctor_anonymous (#1)");
// We can still reference the function itself
let f = anonymous_ctor;
}
#[ctor(anonymous)]
unsafe fn anonymous_ctor() {
libc_eprintln!("ctor_anonymous (#2)");
}
const _: () = {
#[ctor]
unsafe fn anonymous_ctor() {
libc_eprintln!("ctor_anonymous (#3)");
let f = anonymous_ctor;
}
#[dtor]
unsafe fn anonymous_dtor() {
libc_eprintln!("dtor_anonymous");
let f = anonymous_dtor;
}
};
#[ctor]
#[allow(unsafe_code)]
unsafe fn ctor() {
libc_eprintln!("ctor");
// We can still reference the function itself
let f = ctor;
}
#[ctor]
#[allow(unsafe_code)]
unsafe fn ctor_unsafe() {
libc_eprintln!("ctor_unsafe");
}
#[dtor]
#[allow(unsafe_code)]
unsafe fn dtor() {
libc_eprintln!("dtor");
// We can still reference the function itself
let f = dtor;
}
#[dtor]
#[allow(unsafe_code)]
unsafe fn dtor_unsafe() {
libc_eprintln!("dtor_unsafe");
}
#[dtor(anonymous)]
unsafe fn anonymous_dtor() {
libc_eprintln!("dtor_anonymous (#1)");
let f = anonymous_dtor;
}
#[dtor(anonymous)]
unsafe fn anonymous_dtor() {
libc_eprintln!("dtor_anonymous (#2");
let f = anonymous_dtor;
}
/// A module with a static ctor/dtor
pub mod module {
use ctor::*;
use libc_print::*;
#[ctor]
pub(crate) static STATIC_CTOR: u8 = unsafe {
libc_eprintln!("module::STATIC_CTOR");
42
};
#[dtor]
#[allow(unsafe_code)]
unsafe fn dtor_module() {
libc_eprintln!("module::dtor_module");
}
}
/// Executable main which demonstrates the various types of ctor/dtor.
pub fn main() {
use libc_print::*;
libc_eprintln!("main!");
libc_eprintln!("STATIC_CTOR = {:?}", *STATIC_CTOR);
libc_eprintln!("module::STATIC_CTOR = {:?}", *module::STATIC_CTOR);
}

251
vendor/ctor/src/lib.rs vendored Normal file
View File

@ -0,0 +1,251 @@
//! Procedural macro for defining global constructor/destructor functions.
//!
//! This provides module initialization/teardown functions for Rust (like
//! `__attribute__((constructor))` in C/C++) for Linux, OSX, and Windows via
//! the `#[ctor]` and `#[dtor]` macros.
//!
//! This library works and is regularly tested on Linux, OSX and Windows, with both `+crt-static` and `-crt-static`.
//! Other platforms are supported but not tested as part of the automatic builds. This library will also work as expected in both
//! `bin` and `cdylib` outputs, ie: the `ctor` and `dtor` will run at executable or library
//! startup/shutdown respectively.
//!
//! This library currently requires Rust > `1.31.0` at a minimum for the
//! procedural macro support.
#![no_std]
#![recursion_limit = "256"]
#[cfg(feature = "std")]
extern crate std;
#[doc(hidden)]
#[allow(unused)]
pub use macros::__support;
mod macros;
pub use macros::features;
/// Declarative forms of the `#[ctor]` and `#[dtor]` macros.
///
/// The declarative forms wrap and parse a proc_macro-like syntax like so, and
/// are identical in expansion to the undecorated procedural macros. The
/// declarative forms support the same attribute parameters as the procedural
/// macros.
///
/// ```rust
/// # mod test { use ctor::*; use libc_print::*;
/// ctor::declarative::ctor! {
/// #[ctor]
/// fn foo() {
/// libc_println!("Hello, world!");
/// }
/// }
/// # }
///
/// // ... the above is identical to:
///
/// # mod test_2 { use ctor::*; use libc_print::*;
/// #[ctor]
/// fn foo() {
/// libc_println!("Hello, world!");
/// }
/// # }
/// ```
pub mod declarative {
#[doc(inline)]
pub use crate::__support::ctor_parse as ctor;
#[doc(inline)]
#[cfg(feature = "dtor")]
pub use crate::__support::dtor_parse as dtor;
}
/// Marks a function or static variable as a library/executable constructor.
/// This uses OS-specific linker sections to call a specific function at load
/// time.
///
/// # Important notes
///
/// Rust does not make any guarantees about stdlib support for life-before or
/// life-after main. This means that the `ctor` crate may not work as expected
/// in some cases, such as when used in an `async` runtime or making use of
/// stdlib services.
///
/// Multiple startup functions/statics are supported, but the invocation order
/// is not guaranteed.
///
/// The `ctor` crate assumes it is available as a direct dependency, with
/// `extern crate ctor`. If you re-export `ctor` items as part of your crate,
/// you can use the `crate_path` parameter to redirect the macro's output to the
/// correct crate.
///
/// # Attribute parameters
///
/// - `crate_path = ::path::to::ctor::crate`: The path to the `ctor` crate
/// containing the support macros. If you re-export `ctor` items as part of
/// your crate, you can use this to redirect the macro's output to the
/// correct crate.
/// - `used(linker)`: (Advanced) Mark the function as being used in the link
/// phase.
/// - `link_section = "section"`: The section to place the constructor in.
/// - `anonymous`: Do not give the constructor a name in the generated code
/// (allows for multiple constructors with the same name).
/// - `priority = N`: The priority of the constructor. Higher-N-priority
/// constructors are run last. This is not supported on all platforms.
///
/// # Examples
///
/// Print a startup message (using `libc_print` for safety):
///
/// ```rust
/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
/// # extern crate ctor;
/// # use ctor::*;
/// use libc_print::std_name::println;
///
/// #[ctor]
/// unsafe fn foo() {
/// // Using libc_print which is safe in `#[ctor]`
/// println!("Hello, world!");
/// }
///
/// # fn main() {
/// println!("main()");
/// # }
/// ```
///
/// Make changes to `static` variables:
///
/// ```rust
/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
/// # extern crate ctor;
/// # mod test {
/// # use ctor::*;
/// # use std::sync::atomic::{AtomicBool, Ordering};
/// static INITED: AtomicBool = AtomicBool::new(false);
///
/// #[ctor]
/// unsafe fn set_inited() {
/// INITED.store(true, Ordering::SeqCst);
/// }
/// # }
/// ```
///
/// Initialize a `HashMap` at startup time:
///
/// ```rust
/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
/// # extern crate ctor;
/// # mod test {
/// # use std::collections::HashMap;
/// # use ctor::*;
/// #[ctor]
/// pub static STATIC_CTOR: HashMap<u32, String> = unsafe {
/// let mut m = HashMap::new();
/// for i in 0..100 {
/// m.insert(i, format!("x*100={}", i*100));
/// }
/// m
/// };
/// # }
/// # pub fn main() {
/// # assert_eq!(test::STATIC_CTOR.len(), 100);
/// # assert_eq!(test::STATIC_CTOR[&20], "x*100=2000");
/// # }
/// ```
///
/// # Details
///
/// The `#[ctor]` macro makes use of linker sections to ensure that a function
/// is run at startup time.
///
/// ```rust
/// # #![cfg_attr(feature="used_linker", feature(used_with_arg))]
/// # extern crate ctor;
/// # mod test {
/// # use ctor::*;
/// #[ctor]
///
/// unsafe fn my_init_fn() {
/// /* ... */
/// }
/// # }
/// ```
///
/// The above example translates into the following Rust code (approximately):
///
/// ```rust
/// # fn my_init_fn() {}
/// #[used]
/// #[cfg_attr(target_os = "linux", link_section = ".init_array")]
/// #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func,mod_init_funcs")]
/// #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
/// /* ... other platforms elided ... */
/// static INIT_FN: extern fn() = {
/// extern fn init_fn() { my_init_fn(); };
/// init_fn
/// };
/// ```
///
/// For `static` items, the macro generates a `std::sync::OnceLock` that is
/// initialized at startup time. `#[ctor]` on `static` items requires the
/// default `std` feature.
///
/// ```rust
/// # extern crate ctor;
/// # mod test {
/// # use ctor::*;
/// # use std::collections::HashMap;
/// #[ctor]
/// static FOO: HashMap<u32, String> = unsafe {
/// let mut m = HashMap::new();
/// for i in 0..100 {
/// m.insert(i, format!("x*100={}", i*100));
/// }
/// m
/// };
/// # }
/// ```
///
/// The above example translates into the following Rust code (approximately),
/// which eagerly initializes the `HashMap` inside a `OnceLock` at startup time:
///
/// ```rust
/// # extern crate ctor;
/// # mod test {
/// # use ctor::ctor;
/// # use std::collections::HashMap;
/// static FOO: FooStatic = FooStatic { value: ::std::sync::OnceLock::new() };
/// struct FooStatic {
/// value: ::std::sync::OnceLock<HashMap<u32, String>>,
/// }
///
/// impl ::core::ops::Deref for FooStatic {
/// type Target = HashMap<u32, String>;
/// fn deref(&self) -> &Self::Target {
/// self.value.get_or_init(|| unsafe {
/// let mut m = HashMap::new();
/// for i in 0..100 {
/// m.insert(i, format!("x*100={}", i*100));
/// }
/// m
/// })
/// }
/// }
///
/// #[ctor]
/// unsafe fn init_foo_ctor() {
/// _ = &*FOO;
/// }
/// # }
/// ```
#[doc(inline)]
#[cfg(feature = "proc_macro")]
pub use ctor_proc_macro::ctor;
/// Re-exported `#[dtor]` proc-macro from `dtor` crate.
///
/// See [`::dtor`] for more details.
#[doc(inline)]
#[cfg(feature = "dtor")]
pub use dtor::__dtor_from_ctor as dtor;

642
vendor/ctor/src/macros/mod.rs vendored Normal file
View File

@ -0,0 +1,642 @@
#[doc(hidden)]
#[allow(unused)]
pub mod __support {
/// Return type for the constructor. Why is this needed?
///
/// On Windows, `.CRT$XIA` … `.CRT$XIZ` constructors are required to return a `usize` value. We don't know
/// if the user is putting this function into a retval-requiring section or a non-retval section, so we
/// just return a `usize` value which is always valid and just ignored if not needed.
///
/// Miri is pedantic about this, so we just return `()` if we're running under miri.
///
/// See <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/initterm-initterm-e?view=msvc-170>
#[cfg(all(windows, not(miri)))]
pub type CtorRetType = usize;
#[cfg(any(not(windows), miri))]
pub type CtorRetType = ();
pub use crate::__ctor_call as ctor_call;
pub use crate::__ctor_entry as ctor_entry;
pub use crate::__ctor_link_section as ctor_link_section;
pub use crate::__ctor_link_section_attr as ctor_link_section_attr;
pub use crate::__ctor_parse as ctor_parse;
pub use crate::__dtor_entry as dtor_entry;
pub use crate::__dtor_parse as dtor_parse;
pub use crate::__if_has_feature as if_has_feature;
pub use crate::__if_unsafe as if_unsafe;
pub use crate::__get_priority as get_priority;
pub use crate::__unify_features as unify_features;
}
/// Parse a `#[ctor]`-annotated item as if it were a proc-macro.
///
/// This macro supports both the `fn` and `static` forms of the `#[ctor]`
/// attribute, including attribute parameters.
///
/// ```rust
/// # #[cfg(any())] // disabled due to code sharing between ctor/dtor
/// # mod test {
/// # use ctor::declarative::ctor;
/// ctor! {
/// /// Create a ctor with a link section
/// # #[cfg(any())]
/// #[ctor(link_section = ".ctors")]
/// unsafe fn foo() { /* ... */ }
/// }
///
/// ctor! {
/// #[ctor]
/// # #[cfg(any())]
/// static FOO: std::collections::HashMap<u32, String> = unsafe {
/// let mut m = std::collections::HashMap::new();
/// m.insert(1, "foo".to_string());
/// m
/// };
/// }
/// # }
/// ```
#[doc(hidden)]
#[macro_export]
macro_rules! __ctor_parse {
(#[ctor $(($($meta:tt)*))?] $(#[$imeta:meta])* pub ( $($extra:tt)* ) $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::ctor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[pub($($extra)*)], item=$($item)*);
};
(#[ctor $(($($meta:tt)*))?] $(#[$imeta:meta])* pub $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::ctor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[pub], item=$($item)*);
};
(#[ctor $(($($meta:tt)*))?] $(#[$imeta:meta])* fn $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::ctor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[], item=fn $($item)*);
};
(#[ctor $(($($meta:tt)*))?] $(#[$imeta:meta])* unsafe $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::ctor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[], item=unsafe $($item)*);
};
(#[ctor $(($($meta:tt)*))?] $(#[$imeta:meta])* static $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::ctor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[], item=static $($item)*);
};
// Reorder attributes that aren't `#[ctor]`
(#[$imeta:meta] $($rest:tt)*) => {
$crate::__support::ctor_parse!(__reorder__(#[$imeta],), $($rest)*);
};
(__reorder__($(#[$imeta:meta],)*), #[ctor $(($($meta:tt)*))?] $($rest:tt)*) => {
$crate::__support::ctor_parse!(#[ctor $(($($meta)*))?] $(#[$imeta])* $($rest)*);
};
(__reorder__($(#[$imeta:meta],)*), #[$imeta2:meta] $($rest:tt)*) => {
$crate::__support::ctor_parse!(__reorder__($(#[$imeta],)*#[$imeta2],), $($rest)*);
};
}
/// Parse a `#[dtor]`-annotated item as if it were a proc-macro.
///
/// ```rust
/// # #[cfg(any())] mod test {
/// dtor! {
/// #[dtor]
/// unsafe fn foo() { /* ... */ }
/// }
/// # }
#[doc(hidden)]
#[macro_export]
macro_rules! __dtor_parse {
(#[dtor $(($($meta:tt)*))?] $(#[$imeta:meta])* pub ( $($extra:tt)* ) $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::dtor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[pub($($extra)*)], item=$($item)*);
};
(#[dtor $(($($meta:tt)*))?] $(#[$imeta:meta])* pub $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::dtor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[pub], item=$($item)*);
};
(#[dtor $(($($meta:tt)*))?] $(#[$imeta:meta])* fn $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::dtor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[], item=fn $($item)*);
};
(#[dtor $(($($meta:tt)*))?] $(#[$imeta:meta])* unsafe $($item:tt)*) => {
$crate::__support::unify_features!(next=$crate::__support::dtor_entry, meta=[$($($meta)*)?], features=[], imeta=$(#[$imeta])*, vis=[], item=unsafe $($item)*);
};
// Reorder attributes that aren't `#[dtor]`
(#[$imeta:meta] $($rest:tt)*) => {
$crate::__support::dtor_parse!(__reorder__(#[$imeta],), $($rest)*);
};
(__reorder__($(#[$imeta:meta],)*), #[dtor $(($($meta:tt)*))?] $($rest:tt)*) => {
$crate::__support::dtor_parse!(#[dtor $(($($meta)*))?] $(#[$imeta])* $($rest)*);
};
(__reorder__($(#[$imeta:meta],)*), #[$imeta2:meta] $($rest:tt)*) => {
$crate::__support::dtor_parse!(__reorder__($(#[$imeta],)*#[$imeta2],), $($rest)*);
};
}
/// A macro that generates the appropriate feature extraction macros.
macro_rules! declare_features {
( $(#[doc = $doc1:literal])* crate = $crate_features:tt; $(#[doc = $doc2:literal])* attr = $attrs:tt; ) => {
declare_features!( __ crate $crate_features );
};
( __ crate [$(
$( #[doc = $doc:literal] )*
$feature_name:ident $feature_name_str:literal = $feature_include_macro:ident ;
)*] ) => {
/// # Crate features
///
$(
#[doc = concat!("<code>", stringify!($feature_name), "</code>: ")]
$( #[doc = $doc] )*
#[doc = "\n"]
)*
pub mod features {
}
$(
#[doc(hidden)]
#[macro_export]
#[cfg(feature = $feature_name_str)]
macro_rules! $feature_include_macro {
($true:item $false:item) => {
$true
};
}
#[doc(hidden)]
#[macro_export]
#[cfg(not(feature = $feature_name_str))]
macro_rules! $feature_include_macro {
($true:item $false:item) => {
$false
};
}
)*
};
}
declare_features!(
/// Crate features: name/name as string/include macro.
crate = [
/// Enable support for the standard library. This is required for static ctor variables, but not for functions.
std "std" = __include_std_feature;
/// Mark all ctor functions with `used(linker)`.
used_linker "used_linker" = __include_used_linker_feature;
/// Enable support for the proc-macro `#[ctor]` and `#[dtor]` attributes.
proc_macro "proc_macro" = __include_proc_macro_feature;
/// Do not warn when a ctor or dtor is missing the `unsafe` keyword.
__no_warn_on_missing_unsafe "__no_warn_on_missing_unsafe" = __include_no_warn_on_missing_unsafe_feature;
];
/// Attributes.
attr = [
/// Marks a ctor/dtor as unsafe. This will become a warning in 1.0.
unsafe = [unsafe];
/// Place the initialization function pointer in a custom link section. This may cause the initialization function
/// to fail to run or run earlier or later than other `ctor` functions.
link_section = [link_section($section:literal)];
/// Specify a custom crate path for the `ctor` crate. Used when re-exporting the ctor macro.
crate_path = [crate_path = $path:path];
/// Make the ctor function anonymous.
anonymous = [anonymous];
/// Mark this function with `used(linker)`.
used_linker = [used(linker)];
/// Set the ctor priority to a given value.
priority = [priority = $priority:literal];
];
);
/// Extract #[ctor/dtor] attribute parameters and crate features and turn them
/// into a unified feature array.
///
/// Supported attributes:
///
/// - `used(linker)` -> crate feature: `used_linker`
/// - `std` -> crate feature: `std`
/// - `link_section = ...` -> feature: `(link_section = ...)`
/// - `crate_path = ...` -> feature: `(crate_path = ...)`
#[doc(hidden)]
#[macro_export]
macro_rules! __unify_features {
// Entry
(next=$next_macro:path, meta=[$($meta:tt)*], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__support::unify_features!(std, next=$next_macro, meta=[$($meta)*], features=[$($features)*], $($rest)*);
};
// Add std feature if cfg(feature="std")
(std, next=$next_macro:path, meta=[$($meta:tt)*], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__include_std_feature!(
$crate::__support::unify_features!(used_linker, next=$next_macro, meta=[$($meta)*], features=[std,$($features)*], $($rest)*);
$crate::__support::unify_features!(used_linker, next=$next_macro, meta=[$($meta)*], features=[$($features)*], $($rest)*);
);
};
// Add used_linker feature if cfg(feature="used_linker")
(used_linker, next=$next_macro:path, meta=[$($meta:tt)*], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__include_used_linker_feature!(
$crate::__support::unify_features!(__no_warn_on_missing_unsafe, next=$next_macro, meta=[$($meta)*], features=[used_linker,$($features)*], $($rest)*);
$crate::__support::unify_features!(__no_warn_on_missing_unsafe, next=$next_macro, meta=[$($meta)*], features=[$($features)*], $($rest)*);
);
};
// Add __no_warn_on_missing_unsafe feature if cfg(feature="__no_warn_on_missing_unsafe")
(__no_warn_on_missing_unsafe, next=$next_macro:path, meta=[$($meta:tt)*], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__include_no_warn_on_missing_unsafe_feature!(
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($meta)*], features=[__no_warn_on_missing_unsafe,$($features)*], $($rest)*);
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($meta)*], features=[$($features)*], $($rest)*);
);
};
// Parse meta into features
(continue, next=$next_macro:path, meta=[used(linker) $(, $($meta:tt)* )?], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($($meta)*)?], features=[used_linker,$($features)*], $($rest)*);
};
(continue, next=$next_macro:path, meta=[link_section = $section:tt $(, $($meta:tt)* )?], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($($meta)*)?], features=[(link_section=($section)),$($features)*], $($rest)*);
};
(continue, next=$next_macro:path, meta=[crate_path = $path:path $(, $($meta:tt)* )?], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($($meta)*)?], features=[(crate_path=$path),$($features)*], $($rest)*);
};
(continue, next=$next_macro:path, meta=[anonymous $(, $($meta:tt)* )?], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($($meta)*)?], features=[anonymous,$($features)*], $($rest)*);
};
(continue, next=$next_macro:path, meta=[priority = $priority:literal $(, $($meta:tt)* )?], features=[$($features:tt)*], $($rest:tt)*) => {
$crate::__support::unify_features!(continue, next=$next_macro, meta=[$($($meta)*)?], features=[(priority=($priority)),$($features)*], $($rest)*);
};
(continue, next=$next_macro:path, meta=[$unknown_meta:meta $($meta:tt)*], features=[$($features:tt)*], $($rest:tt)*) => {
compile_error!(concat!("Unknown attribute parameter: ", stringify!($unknown_meta)));
};
(continue, next=$next_macro:path, meta=[], features=[$($features:tt)*], $($rest:tt)*) => {
$next_macro!(features=[$($features)*], $($rest)*);
};
}
/// If the features array contains the requested feature, generates `if_true`, else `if_false`.
///
/// This macro matches the features recursively.
///
/// Example: `[(link_section = ".ctors") , used_linker , __warn_on_missing_unsafe ,]`
#[doc(hidden)]
#[macro_export]
#[allow(unknown_lints, edition_2024_expr_fragment_specifier)]
macro_rules! __if_has_feature {
(std, [std, $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $($if_true)* };
(used_linker, [used_linker, $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $($if_true)* };
(__no_warn_on_missing_unsafe, [__no_warn_on_missing_unsafe, $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $($if_true)* };
(anonymous, [anonymous, $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $($if_true)* };
((link_section(c)), [(link_section=($section:literal)), $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { #[link_section = $section] $($if_true)* };
((priority(p)), [(priority=($priority:literal)), $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $($if_true)* };
// Fallback rules
($anything:tt, [$x:ident, $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $crate::__support::if_has_feature!($anything, [$($rest)*], {$($if_true)*}, {$($if_false)*}); };
($anything:tt, [($x:ident=$y:tt), $($rest:tt)*], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $crate::__support::if_has_feature!($anything, [$($rest)*], {$($if_true)*}, {$($if_false)*}); };
($anything:tt, [], {$($if_true:tt)*}, {$($if_false:tt)*}) => { $($if_false)* };
}
#[doc(hidden)]
#[macro_export]
macro_rules! __if_unsafe {
(, {$($if_unsafe:tt)*}, {$($if_safe:tt)*}) => { $($if_safe)* };
(unsafe, {$($if_unsafe:tt)*}, {$($if_safe:tt)*}) => { $($if_unsafe)* };
}
#[doc(hidden)]
#[macro_export]
macro_rules! __get_priority {
($next:path, $args:tt, [(priority=($priority:literal)), $($rest:tt)*]) => { $next!($args, (".", $priority)); };
($next:path, $args:tt, [$x:ident, $($rest:tt)*]) => { $crate::__support::get_priority!($next, $args, [$($rest)*]); };
($next:path, $args:tt, [($x:ident=$y:tt), $($rest:tt)*]) => { $crate::__support::get_priority!($next, $args, [$($rest)*]); };
($next:path, $args:tt, []) => { $next!($args, ("")); };
}
#[doc(hidden)]
#[macro_export]
#[allow(unknown_lints, edition_2024_expr_fragment_specifier)]
macro_rules! __ctor_entry {
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=unsafe fn $($item:tt)*) => {
$crate::__support::ctor_entry!(features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=unsafe, item=fn $($item)*);
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=fn $($item:tt)*) => {
$crate::__support::ctor_entry!(features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=, item=fn $($item)*);
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=static $ident:ident : $ty:ty = $(unsafe)? $({ $lit:literal })? $($lit2:literal)? ;) => {
compile_error!(concat!("Use `const ", stringify!($ident), " = ", stringify!($($lit)?$($lit2)?), ";` or `static ", stringify!($ident), ": ", stringify!($ty), " = ", stringify!($($lit)?$($lit2)?), ";` instead"));
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=static $ident:ident : $ty:ty = unsafe $($item:tt)*) => {
$crate::__support::ctor_entry!(features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=unsafe, item=static $ident: $ty = $($item)*);
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=static $ident:ident : $ty:ty = $($item:tt)*) => {
$crate::__support::ctor_entry!(features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=, item=static $ident: $ty = $($item)*);
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=fn $ident:ident() $block:block) => {
$crate::__support::if_has_feature!(anonymous, $features, {
$crate::__support::ctor_entry!(unnamed, features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=$($unsafe)?, item=fn $ident() $block);
}, {
$crate::__support::ctor_entry!(named, features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=$($unsafe)?, item=fn $ident() $block);
});
};
(unnamed,features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=fn $ident:ident() $block:block) => {
const _: () = {
$crate::__support::ctor_entry!(named, features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=$($unsafe)?, item=fn $ident() $block);
};
};
(named, features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=fn $ident:ident() $block:block) => {
$(#[$fnmeta])*
#[allow(unused)]
$($vis)* $($unsafe)? fn $ident() {
#[allow(unsafe_code)]
{
$crate::__support::if_unsafe!($($unsafe)?, {}, {
$crate::__support::if_has_feature!( __warn_on_missing_unsafe, $features, {
#[deprecated="ctor deprecation note:\n\n \
Use of #[ctor] without `unsafe fn` is deprecated. As code execution before main\n\
is unsupported by most Rust runtime functions, these functions must be marked\n\
`unsafe`."]
const fn ctor_without_unsafe_is_deprecated() {}
#[allow(unused)]
static UNSAFE_WARNING: () = ctor_without_unsafe_is_deprecated();
}, {});
});
$crate::__support::ctor_call!(
features=$features,
{ unsafe { $ident(); } }
);
}
#[cfg(target_family = "wasm")]
{
static __CTOR__INITILIZED: ::core::sync::atomic::AtomicBool = ::core::sync::atomic::AtomicBool::new(false);
if __CTOR__INITILIZED.swap(true, ::core::sync::atomic::Ordering::Relaxed) {
return;
}
}
$block
}
};
(features=$features:tt, imeta=$(#[$imeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=static $ident:ident : $ty:ty = $block:block;) => {
$crate::__support::if_has_feature!(std, $features, {
$(#[$imeta])*
$($vis)* static $ident: $ident::Static<$ty> = $ident::Static::<$ty> {
_storage: {
$crate::__support::ctor_call!(
features=$features,
{ _ = &*$ident; }
);
::std::sync::OnceLock::new()
}
};
impl ::core::ops::Deref for $ident::Static<$ty> {
type Target = $ty;
fn deref(&self) -> &$ty {
fn init() -> $ty $block
self._storage.get_or_init(move || {
init()
})
}
}
#[doc(hidden)]
#[allow(non_upper_case_globals, non_snake_case)]
#[allow(unsafe_code)]
mod $ident {
$crate::__support::if_unsafe!($($unsafe)?, {}, {
$crate::__support::if_has_feature!( __no_warn_on_missing_unsafe, $features, {
#[deprecated="ctor deprecation note:\n\n \
Use of #[ctor] without `unsafe { ... }` is deprecated. As code execution before main\n\
is unsupported by most Rust runtime functions, these functions must be marked\n\
`unsafe`."]
const fn ctor_without_unsafe_is_deprecated() {}
#[allow(unused)]
static UNSAFE_WARNING: () = ctor_without_unsafe_is_deprecated();
}, {});
});
#[allow(non_camel_case_types, unreachable_pub)]
pub struct Static<T> {
pub _storage: ::std::sync::OnceLock<T>
}
}
}, {
compile_error!("`#[ctor]` on `static` items requires the `std` feature");
});
};
}
// Code note:
// You might wonder why we don't use `__attribute__((destructor))`/etc for
// dtor. Unfortunately mingw doesn't appear to properly support section-based
// hooks for shutdown, ie:
// https://github.com/Alexpux/mingw-w64/blob/d0d7f784833bbb0b2d279310ddc6afb52fe47a46/mingw-w64-crt/crt/crtdll.c
// In addition, OSX has removed support for section-based shutdown hooks after
// warning about it for a number of years:
// https://reviews.llvm.org/D45578
#[doc(hidden)]
#[macro_export]
macro_rules! __dtor_entry {
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=fn $ident:ident() $block:block) => {
$crate::__support::dtor_entry!(features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=, item=fn $ident() $block);
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], item=unsafe fn $ident:ident() $block:block) => {
$crate::__support::dtor_entry!(features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=unsafe, item=fn $ident() $block);
};
(features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=fn $ident:ident() $block:block) => {
$crate::__support::if_has_feature!(anonymous, $features, {
$crate::__support::dtor_entry!(unnamed, features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=$($unsafe)?, item=fn $ident() $block);
}, {
$crate::__support::dtor_entry!(named, features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=$($unsafe)?, item=fn $ident() $block);
});
};
(unnamed, features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=fn $ident:ident() $block:block) => {
const _: () = {
$crate::__support::dtor_entry!(named, features=$features, imeta=$(#[$fnmeta])*, vis=[$($vis)*], unsafe=$($unsafe)?, item=fn $ident() $block);
};
};
(named, features=$features:tt, imeta=$(#[$fnmeta:meta])*, vis=[$($vis:tt)*], unsafe=$($unsafe:ident)?, item=fn $ident:ident() $block:block) => {
$(#[$fnmeta])*
#[allow(unused)]
$($vis)* $($unsafe)? fn $ident() {
#[allow(unsafe_code)]
{
$crate::__support::if_unsafe!($($unsafe)?, {}, {
$crate::__support::if_has_feature!( __warn_on_missing_unsafe, $features, {
#[deprecated="dtor deprecation note:\n\n \
Use of #[dtor] without `unsafe fn` is deprecated. As code execution after main\n\
is unsupported by most Rust runtime functions, these functions must be marked\n\
`unsafe`."]
const fn dtor_without_unsafe_is_deprecated() {}
#[allow(unused)]
static UNSAFE_WARNING: () = dtor_without_unsafe_is_deprecated();
}, {});
});
$crate::__support::ctor_call!(
features=$features,
{ unsafe { do_atexit(__dtor); } }
);
$crate::__support::ctor_link_section!(
exit,
features=$features,
(""),
/*unsafe*/ extern "C" fn __dtor(
#[cfg(target_vendor = "apple")] _: *const u8
) { unsafe { $ident() } }
);
#[cfg(not(target_vendor = "apple"))]
#[inline(always)]
unsafe fn do_atexit(cb: unsafe extern fn()) {
/*unsafe*/ extern "C" {
fn atexit(cb: unsafe extern fn());
}
unsafe {
atexit(cb);
}
}
// For platforms that have __cxa_atexit, we register the dtor as scoped to dso_handle
#[cfg(target_vendor = "apple")]
#[inline(always)]
unsafe fn do_atexit(cb: /*unsafe*/ extern "C" fn(_: *const u8)) {
/*unsafe*/ extern "C" {
static __dso_handle: *const u8;
fn __cxa_atexit(cb: /*unsafe*/ extern "C" fn(_: *const u8), arg: *const u8, dso_handle: *const u8);
}
unsafe {
__cxa_atexit(cb, ::core::ptr::null(), __dso_handle);
}
}
}
$block
}
};
}
/// Annotate a block with its appropriate link section.
#[doc(hidden)]
#[macro_export]
macro_rules! __ctor_call {
(features=$features:tt, { $($block:tt)+ } ) => {
$crate::__support::get_priority!($crate::__support::ctor_call, [features=$features, { $($block)+ }], $features);
};
([features=$features:tt, { $($block:tt)+ }], $priority:tt) => {
$crate::__support::ctor_link_section!(
array,
features=$features,
$priority,
#[allow(non_upper_case_globals, non_snake_case)]
#[doc(hidden)]
static f: /*unsafe*/ extern "C" fn() -> $crate::__support::CtorRetType =
{
$crate::__support::ctor_link_section!(
startup,
features=$features,
(""),
#[allow(non_snake_case)]
/*unsafe*/ extern "C" fn f() -> $crate::__support::CtorRetType {
$($block)+;
::core::default::Default::default()
}
);
f
};
);
}
}
/// Annotate a block with its appropriate link section.
#[doc(hidden)]
#[macro_export]
macro_rules! __ctor_link_section {
($section:ident, features=$features:tt, $priority:tt, $($block:tt)+) => {
$crate::__support::if_has_feature!(used_linker, $features, {
$crate::__support::ctor_link_section_attr!($section, $features, used(linker), $priority, $($block)+);
}, {
$crate::__support::ctor_link_section_attr!($section, $features, used, $priority, $($block)+);
});
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "illumos",
target_os = "haiku",
target_vendor = "apple",
target_family = "wasm",
target_arch = "xtensa",
target_os = "windows"
)))]
compile_error!("#[ctor]/#[dtor] is not supported on the current target");
}
}
/// Apply either the default cfg-based link section attributes, or
/// the overridden link_section attribute.
#[doc(hidden)]
#[macro_export]
macro_rules! __ctor_link_section_attr {
(array, $features:tt, $used:meta, ($($priority:tt)*), $item:item) => {
$crate::__support::if_has_feature!((priority(p)), $features, {
#[cfg(target_vendor="apple")]
const _: () = {
#[deprecated(note = "The priority parameter is not supported on target_vendor = \"apple\"")]
const fn ctor_priority_unsupported() {}
ctor_priority_unsupported();
};
}, {});
$crate::__support::if_has_feature!((link_section(c)), $features, {
#[allow(unsafe_code)]
#[$used]
$item
}, {
#[allow(unsafe_code)]
$crate::__support::ctor_link_section_attr!(
[[any(
target_os = "linux",
target_os = "android",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "illumos",
target_os = "haiku",
target_family = "wasm"
), (concat!(".init_array", $($priority)*))],
[target_arch = "xtensa", (concat!(".ctors", $($priority)*))],
// macOS/Darwin do not support the priority parameter in the link section
[target_vendor = "apple", (concat!("__DATA,__mod_init_func,mod_init_funcs"))],
[all(target_os = "windows", any(target_env = "gnu", target_env = "msvc")), (concat!(".CRT$XCU", $($priority)*))],
// cygwin support: rustc 1.85 does not like the explicit target_os = "cygwin" condition (https://github.com/mmastrac/rust-ctor/issues/356)
// We can work around this by excluding gnu and msvc target envs
[all(target_os = "windows", not(any(target_env = "gnu", target_env = "msvc"))), (concat!(".ctors", $($priority)*))]
],
#[$used]
$item
);
});
};
(startup, $features:tt, $used:meta, $priority:tt, $item:item) => {
#[cfg(not(clippy))]
$crate::__support::ctor_link_section_attr!([[any(target_os = "linux", target_os = "android"), (".text.startup")]], $item);
#[cfg(clippy)]
$item
};
(exit, $features:tt, $used:meta, $priority:tt, $item:item) => {
#[cfg(not(clippy))]
$crate::__support::ctor_link_section_attr!([[any(target_os = "linux", target_os = "android"), (".text.exit")]], $item);
#[cfg(clippy)]
$item
};
([$( [$cond:meta, ($($literal:tt)*) ] ),+], $item:item) => {
$( #[cfg_attr($cond, link_section = $($literal)*)] )+
$item
};
}