diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ced1b54 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,15 @@ +* text=auto eol=lf +*.bat -text +*.jar binary +*.dex binary +*.apk binary +*.zip binary +tools/busybox binary +tools/cmd binary +tools/find binary +tools/jq binary +tools/keycheck binary +tools/smbclient binary +tools/speednative binary +tools/tar binary +tools/zstd binary diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml new file mode 100644 index 0000000..9adab4d --- /dev/null +++ b/.github/workflows/android-ci.yml @@ -0,0 +1,83 @@ +name: Android checks and build + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: android-ci-${{ github.event.pull_request.head.ref || github.ref_name }} + cancel-in-progress: true + +jobs: + source-checks: + name: Source, shell syntax and runtime SHA + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.13' + - name: Install mksh + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y mksh + - name: Check source and committed tools + run: python ci/check_repository.py check --shells + + android-build: + name: Android arm64 Dex and Rust + needs: source-checks + runs-on: windows-2022 + timeout-minutes: 45 + defaults: + run: + shell: pwsh + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6 + with: + python-version: '3.13' + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: '17' + - uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-windows-jdk17-${{ hashFiles('dex/**/*.gradle.kts', 'dex/**/*.toml', 'dex/**/gradle-wrapper.properties') }} + restore-keys: gradle-windows-jdk17- + - name: Build and package checked Android artifacts + run: | + New-Item -ItemType Directory -Path ci-output -Force | Out-Null + & pwsh -NoProfile -File ./ci/build_android.ps1 *>&1 | Tee-Object -FilePath ci-output/build.log + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + - name: Upload matching runtime and source snapshot + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: speedbackup-android-${{ github.sha }} + path: | + ci-output/SpeedBackup_*_CI_RUNTIME.zip + ci-output/SpeedBackup_*_SOURCE_SNAPSHOT.zip + ci-output/SHA256SUMS.txt + ci-output/BUILD_INFO.json + if-no-files-found: error + retention-days: 14 + - name: Upload build diagnostics + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: android-build-log-${{ github.sha }} + path: ci-output/build.log + if-no-files-found: warn + retention-days: 14 diff --git a/.gitignore b/.gitignore index 8b13789..e7e1399 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ - +/ci-output/ diff --git a/ci/build_android.ps1 b/ci/build_android.ps1 new file mode 100644 index 0000000..947498a --- /dev/null +++ b/ci/build_android.ps1 @@ -0,0 +1,50 @@ +[CmdletBinding()] +param() +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$rootDir = Split-Path -Parent $PSScriptRoot +$outputDir = Join-Path $rootDir 'ci-output' +$null = New-Item -ItemType Directory -Path $outputDir -Force +Push-Location -LiteralPath $rootDir +try { + $config = Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot 'toolchain.json') | ConvertFrom-Json + & python (Join-Path $PSScriptRoot 'check_repository.py') check + if ($LASTEXITCODE -ne 0) { throw 'Repository checks failed.' } + + $sdkRoot = $env:ANDROID_HOME + if (-not $sdkRoot) { $sdkRoot = $env:ANDROID_SDK_ROOT } + if (-not $sdkRoot -or -not (Test-Path -LiteralPath $sdkRoot)) { throw 'Android SDK is missing on the runner.' } + $sdkManager = Join-Path $sdkRoot 'cmdline-tools/latest/bin/sdkmanager.bat' + if (-not (Test-Path -LiteralPath $sdkManager)) { throw "sdkmanager not found: $sdkManager" } + $env:ANDROID_HOME = $sdkRoot + $env:ANDROID_SDK_ROOT = $sdkRoot + & $sdkManager --version + if ($LASTEXITCODE -ne 0) { throw 'sdkmanager failed.' } + 1..100 | ForEach-Object { 'y' } | & $sdkManager --licenses + if ($LASTEXITCODE -ne 0) { throw 'Android SDK license acceptance failed.' } + & $sdkManager "platforms;android-$($config.compile_sdk)" "build-tools;$($config.build_tools)" "ndk;$($config.ndk)" + if ($LASTEXITCODE -ne 0) { throw 'Android SDK/NDK installation failed.' } + + & rustup toolchain install $config.rust --profile minimal --target $config.rust_target + if ($LASTEXITCODE -ne 0) { throw 'Pinned Rust installation failed.' } + $env:RUSTUP_TOOLCHAIN = $config.rust + & rustc --version + if ($LASTEXITCODE -ne 0) { throw 'rustc failed.' } + + # New source layouts may provide generated component-version metadata. + $syncVersion = Join-Path $rootDir 'sync_version.ps1' + if (Test-Path -LiteralPath $syncVersion) { + & pwsh -NoProfile -File $syncVersion -Check + if ($LASTEXITCODE -ne 0) { throw 'Generated version metadata is out of sync.' } + } + + & pwsh -NoProfile -File (Join-Path $rootDir 'dex/build_dex.ps1') -JavaHome $env:JAVA_HOME -SdkRoot $sdkRoot -NoDaemon + if ($LASTEXITCODE -ne 0) { throw 'Dex build failed.' } + & pwsh -NoProfile -File (Join-Path $rootDir 'rust/build.ps1') -Sdk $sdkRoot -Ndk (Join-Path $sdkRoot "ndk/$($config.ndk)") + if ($LASTEXITCODE -ne 0) { throw 'Rust build failed.' } + + & python (Join-Path $PSScriptRoot 'check_repository.py') package + if ($LASTEXITCODE -ne 0) { throw 'Artifact validation/packaging failed.' } +} finally { + Pop-Location +} diff --git a/ci/check_repository.py b/ci/check_repository.py new file mode 100644 index 0000000..67116b5 --- /dev/null +++ b/ci/check_repository.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +"""CI source/runtime integrity and packaging checks; never runs Android tools.""" +import argparse +import hashlib +import json +import re +import shutil +import struct +import subprocess +import sys +import tempfile +import tomllib +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +REQUIRED_TOOLS = { + 'busybox', 'classes.dex', 'cmd', 'dex_check.sh', 'find', 'jq', + 'keycheck', 'smbclient', 'speednative', 'tar', 'zstd', +} +TEXT_SUFFIXES = {'.sh', '.rs', '.java', '.kt', '.kts', '.ps1', '.toml', '.yml', '.yaml'} + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def sha256(data): + return hashlib.sha256(data).hexdigest() + + +def git(*args): + return subprocess.check_output(['git', *args], cwd=ROOT) + + +def runtime_table(script): + blocks = list(re.finditer(r"(?m)^\s*cat <<'SB_TOOL_SHA_TABLE'\n(.*?)^SB_TOOL_SHA_TABLE$", script, re.S)) + require(len(blocks) == 1, 'Expected exactly one runtime SHA table') + entries = {} + for line in blocks[0].group(1).splitlines(): + match = re.fullmatch(r'([A-Za-z0-9_.-]+) ([0-9a-f]{64})', line) + require(match is not None, f'Malformed runtime SHA row: {line!r}') + name, digest = match.groups() + require(name not in entries, f'Duplicate runtime SHA row: {name}') + entries[name] = digest + require(REQUIRED_TOOLS <= entries.keys(), 'Missing required runtime SHA rows') + return entries + + +def verify_runtime(directory): + script = (directory / 'tools.sh').read_bytes().decode('utf-8') + require('\r' not in script, 'tools.sh must use LF') + entries = runtime_table(script) + for name, expected in entries.items(): + path = directory / name + require(path.is_file() and not path.is_symlink(), f'Missing regular runtime file: {name}') + actual = sha256(path.read_bytes()) + require(actual == expected, f'{name}: runtime SHA mismatch; expected {expected}, got {actual}') + print(f'Runtime SHA: {len(entries)} files match') + + +def check_versions(): + config = ROOT / 'versions.properties' + if not config.exists(): + print('Versions: existing legacy scheme (no versions.properties)') + return + values = {} + for line in config.read_text(encoding='utf-8').splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + key, value = line.split('=', 1) + require(key not in values, f'Duplicate version key: {key}') + require(re.fullmatch(r'v[1-9][0-9]{2}', value), f'Invalid version: {key}={value}') + values[key] = value + required = {'build', 'script', 'dex', 'cgfreezer', 'eventwait', 'filewatch', + 'netwatch', 'procwait', 'speedscan', 'uidexec', 'unixsock'} + require(required == values.keys(), 'Missing or unknown component version keys') + require((ROOT / 'VERSION').read_text().strip() == values['build'], 'VERSION differs from build') + script = (ROOT / 'tools/tools.sh').read_text(encoding='utf-8') + require(f'speedbackup_script_version="{values["script"]}"' in script, 'Script function version differs') + if re.search(r'(?m)^speedbackup_patch_build=', script): + require(f'speedbackup_patch_build="{values["build"]}"' in script, 'Script build version differs') + for name in ('rust/build.rs', 'read_versions.ps1', 'sync_version.ps1', 'sync_artifacts.ps1', 'dex/release-version.properties'): + require((ROOT / name).is_file(), f'Missing version build input: {name}') + dex_versions = dict(line.split('=', 1) for line in + (ROOT / 'dex/release-version.properties').read_text(encoding='utf-8').splitlines() + if line.strip() and not line.lstrip().startswith('#')) + require(dex_versions.get('version') == values['dex'] and + dex_versions.get('build') == values['build'], 'Dex component/build version differs') + self_check = (ROOT / 'tools/dex_check.sh').read_text(encoding='utf-8') + require(f'DEX_CHECK_VERSION="{values["script"]}"' in self_check and + f'DEX_CHECK_BUILD="{values["build"]}"' in self_check, 'Self-check component/build version differs') + cargo = tomllib.loads((ROOT / 'rust/Cargo.toml').read_text(encoding='utf-8')) + require(cargo['package']['version'] == f'{int(values["build"][1:])}.0.0', 'Cargo build version differs') + print('Versions: centralized component metadata checked') + + +def check(shells=False): + names = git('ls-files', '-z').decode('utf-8').rstrip('\0').split('\0') + for name in ['start.sh', 'tools/tools.sh', 'tools/dex_check.sh', 'tools/soc.json', + 'rust/Cargo.toml', 'rust/Cargo.lock', 'rust/src/main.rs', 'rust/build.ps1', + 'dex/build_dex.ps1', 'dex/gradlew', 'dex/gradlew.bat', + 'dex/gradle/wrapper/gradle-wrapper.jar', 'dex/gradle/wrapper/gradle-wrapper.properties']: + require((ROOT / name).is_file(), f'Missing required file: {name}') + for name in names: + path = ROOT / name + if path.suffix not in TEXT_SUFFIXES and name != 'dex/gradlew': + continue + data = path.read_bytes() + require(not data.startswith(b'\xef\xbb\xbf'), f'Unexpected UTF-8 BOM: {name}') + require(b'\r' not in data, f'Expected LF line endings: {name}') + data.decode('utf-8') + json.loads((ROOT / 'tools/soc.json').read_text(encoding='utf-8')) + cargo = tomllib.loads((ROOT / 'rust/Cargo.toml').read_text(encoding='utf-8')) + lock = tomllib.loads((ROOT / 'rust/Cargo.lock').read_text(encoding='utf-8')) + packages = [p for p in lock['package'] if p['name'] == cargo['package']['name']] + require(len(packages) == 1 and packages[0]['version'] == cargo['package']['version'], 'Cargo.lock package version differs') + with zipfile.ZipFile(ROOT / 'dex/gradle/wrapper/gradle-wrapper.jar') as wrapper: + require(wrapper.testzip() is None, 'Corrupt Gradle wrapper JAR') + require('org/gradle/wrapper/GradleWrapperMain.class' in wrapper.namelist(), 'Missing wrapper entry point') + wrapper_props = (ROOT / 'dex/gradle/wrapper/gradle-wrapper.properties').read_text(encoding='utf-8') + require(re.search(r'(?m)^distributionSha256Sum=[0-9a-f]{64}$', wrapper_props), + 'Gradle distribution SHA256 must be pinned') + verify_runtime(ROOT / 'tools') + check_versions() + if shells: + for shell in ('bash', 'mksh'): + require(shutil.which(shell), f'{shell} is not installed') + for name in names: + if name.endswith('.sh') or name == 'dex/gradlew': + subprocess.run([shell, '-n', str(ROOT / name)], cwd=ROOT, check=True) + print('Shell syntax: Bash and mksh passed (scripts not executed)') + print('Repository checks passed') + + +def package(): + dex = ROOT / 'dex/classes.dex' + native = ROOT / 'rust/out/speednative' + require(dex.is_file() and native.is_file(), 'Missing fresh build outputs') + require(dex.read_bytes()[:4] == b'dex\n', 'Invalid Dex magic') + elf = native.read_bytes() + require(elf[:6] == b'\x7fELF\x02\x01' and struct.unpack_from(' - - + + + \ No newline at end of file diff --git a/dex/hiddenapi/src/main/aidl/android/app/INotificationManager.aidl b/dex/hiddenapi/src/main/aidl/android/app/INotificationManager.aidl index d3b7eb2..24882cc 100755 --- a/dex/hiddenapi/src/main/aidl/android/app/INotificationManager.aidl +++ b/dex/hiddenapi/src/main/aidl/android/app/INotificationManager.aidl @@ -1,30 +1,30 @@ -/* //device/java/android/android/app/INotificationManager.aidl -** -** Copyright 2007, The Android Open Source Project -** -** 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. -*/ - -package android.app; - -import android.app.NotificationChannel; -import android.content.pm.ParceledListSlice; - -/** {@hide} */ -interface INotificationManager { - void createNotificationChannels(String pkg, in ParceledListSlice channelsList); - void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, - in Notification notification, int userId); -} - +/* //device/java/android/android/app/INotificationManager.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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. +*/ + +package android.app; + +import android.app.NotificationChannel; +import android.content.pm.ParceledListSlice; + +/** {@hide} */ +interface INotificationManager { + void createNotificationChannels(String pkg, in ParceledListSlice channelsList); + void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, + in Notification notification, int userId); +} + // https://cs.android.com/android/platform/superproject/+/android-8.0.0_r51:frameworks/base/core/java/android/app/INotificationManager.aidl \ No newline at end of file diff --git a/dex/hiddenapi/src/main/aidl/android/content/pm/ParceledListSlice.aidl b/dex/hiddenapi/src/main/aidl/android/content/pm/ParceledListSlice.aidl index 43ac264..b749114 100755 --- a/dex/hiddenapi/src/main/aidl/android/content/pm/ParceledListSlice.aidl +++ b/dex/hiddenapi/src/main/aidl/android/content/pm/ParceledListSlice.aidl @@ -1,21 +1,21 @@ -/* - * Copyright 2011, The Android Open Source Project - * - * 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. - */ - -package android.content.pm; - -parcelable ParceledListSlice; - +/* + * Copyright 2011, The Android Open Source Project + * + * 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. + */ + +package android.content.pm; + +parcelable ParceledListSlice; + // https://cs.android.com/android/platform/superproject/+/android-8.0.0_r51:frameworks/base/core/java/android/content/pm/ParceledListSlice.aidl \ No newline at end of file diff --git a/docs/CI.md b/docs/CI.md new file mode 100644 index 0000000..07912c7 --- /dev/null +++ b/docs/CI.md @@ -0,0 +1,48 @@ +# Android 自動檢查與編譯 + +工作流程:`.github/workflows/android-ci.yml`。 + +## 何時執行 + +- 推送至 `master`。 +- 對 `master` 開啟或更新 Pull Request。 +- 在 Actions 的 **Android checks and build** 按 **Run workflow**。 + +同一分支有更新時取消舊工作,避免重複編譯。工作流程只需要 `contents: read`,不需要設定私人 Token 或手機憑證。 + +## 檢查內容 + +1. **Ubuntu 24.04**:UTF-8/LF、必要檔案、JSON/Cargo.lock、Gradle wrapper JAR、Bash 與 mksh 語法、所有已提交工具與 `tools.sh` SHA 表的一致性。 +2. **Windows 2022**:安裝 JDK 17、Android SDK 34、Build Tools 34.0.0、NDK r30 `30.0.16248370`、Rust `1.96.0`,從來源編譯 Dex 與 Android arm64 `speednative`。 +3. Dex 沿用建置腳本的單一 Dex/必要 capability 檢查;Rust 沿用 ELF 架構、PIE、16 KiB segment、Android API 28/NDK r30 note 檢查。 +4. 在獨立目錄組裝 CI 執行包,替換新 Dex/Rust,更新包內工具 SHA,重新核對 ZIP 實際位元組。 + +`ci/toolchain.json` 集中記錄 Rust/NDK/SDK 版本。Java major 與 Python minor 固定於工作流程;官方 Actions 固定到 commit SHA。 + +`versions.properties` 集中維護各組件版本;檢查器會核對腳本、Dex、Cargo 與自檢腳本,建置前亦執行 `sync_version.ps1 -Check`。更新來源時,應一起提交根目錄的 `build.ps1`、`VERSION`、`versions.properties`、`read_versions.ps1`、`sync_version.ps1`、`sync_artifacts.ps1`,以及完整 `dex/`、`rust/` 來源與相符的 `tools/` 產物。Rust 的 `src/` 與 `build.rs` 都是必要來源。同步腳本同時支援 GitHub 的 `tools/` 與 FULL_SOURCE 根目錄佈局;自行建置後仍需將產物複製至 `tools/`,CI 會在獨立打包目錄完成這一步。 + +## 下載產物 + +成功後,在該次 Actions 的 **Artifacts** 下載 `speedbackup-android-`,保留14天,包含: + +- `SpeedBackup__CI_RUNTIME.zip`:`start.sh`、完整 `tools/`、說明與授權;Dex/Rust 是本次新編譯,其他工具沿用該 commit 已提交且 SHA 驗證成功的版本。 +- `SpeedBackup__SOURCE_SNAPSHOT.zip`:由 `git archive` 生成的同一 commit 原始快照。 +- `SHA256SUMS.txt`、`BUILD_INFO.json`:外層校驗碼、來源 commit、工具鏈與包內工具雜湊。 + +另有獨立的 `android-build-log-`,失敗時也盡量保存建置日誌。 + +這是 CI 產物,尚未設定自動發布 GitHub Release。`SOURCE_SNAPSHOT` 如實保存倉庫內容;目前倉庫沒有 tar/zstd/smbclient 的完整建置來源,因此不將它標為涵蓋所有工具的 `FULL_SOURCE`。原有完整源碼交付包仍獨立提供。 + +## 驗證範圍 + +- 語法檢查不執行備份/恢復腳本;Rust 建置會編譯既有 Android 測試,但不在 Windows 執行 Android ELF。 +- 工作流程不連接手機或家中 SMB/WebDAV,不含私人資料或裝置設定。 +- AppState、SSAID、Root、SELinux、凍結守護與不同 ROM 的實際行為仍需備用機驗收。CI 通過表示上述來源、語法、編譯與產物校驗通過。 + +## 本次初始化修正 + +最初的 `7ead439` 有 tar/zstd SHA 不一致;使用者的後續 `bc6c4c6` 更新已同步這兩筆記錄及全部 11 個工具。本 PR 以該更新為基底,保留新版二進位檔。 + +`bc6c4c6` 的腳本已使用 v780,版本設定與自檢脚本仍標記 v778;本次將兩者同步至 v780。Dex 及八個 Rust 子程序仍保留各自的功能版本。 + +實際 CI 也發現舊自製 Gradle wrapper 會優先使用主機的 `GRADLE_HOME`,令宣告的 8.2 被 runner 的 9.7.1 取代,導致 AGP 8.2.2 編譯失敗。本次改用 Gradle 8.2 官方 wrapper,並固定發行 ZIP 的 SHA256;建置步驟完整保存子程序輸出與錯誤碼。 diff --git a/rust/build.ps1 b/rust/build.ps1 index 8d67240..037bd74 100755 --- a/rust/build.ps1 +++ b/rust/build.ps1 @@ -294,6 +294,8 @@ try { } [IO.File]::WriteAllText((Join-Path $OutDir 'BUILD_INFO.json'), ($info | ConvertTo-Json -Depth 4) + "`n", $utf8) $toolsFile = Join-Path (Split-Path -Parent $PSScriptRoot) 'tools.sh' + $repositoryTools = Join-Path (Split-Path -Parent $PSScriptRoot) 'tools/tools.sh' + if (Test-Path -LiteralPath $repositoryTools -PathType Leaf) { $toolsFile = $repositoryTools } if (Test-Path -LiteralPath $toolsFile -PathType Leaf) { $text = [IO.File]::ReadAllText($toolsFile) $table = [regex]::Match($text, "(?ms)^\tcat <<'SB_TOOL_SHA_TABLE'\r?\n.*?^SB_TOOL_SHA_TABLE\r?$") diff --git a/sync_artifacts.ps1 b/sync_artifacts.ps1 index bb6d70e..25a9db4 100755 --- a/sync_artifacts.ps1 +++ b/sync_artifacts.ps1 @@ -6,10 +6,11 @@ param( Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' & (Join-Path $PSScriptRoot 'sync_version.ps1') -Check -$artifacts = [ordered]@{ 'dex_check.sh' = 'dex_check.sh' } +$runtimePrefix = if (Test-Path -LiteralPath (Join-Path $PSScriptRoot 'tools/tools.sh') -PathType Leaf) { 'tools/' } else { '' } +$artifacts = [ordered]@{ 'dex_check.sh' = ($runtimePrefix + 'dex_check.sh') } if ($Component -in @('All', 'Dex')) { $artifacts['classes.dex'] = 'dex/classes.dex' } if ($Component -in @('All', 'Rust')) { $artifacts['speednative'] = 'rust/out/speednative' } -$scriptPath = Join-Path $PSScriptRoot 'tools.sh' +$scriptPath = Join-Path $PSScriptRoot ($runtimePrefix + 'tools.sh') $body = [IO.File]::ReadAllText($scriptPath) foreach ($entry in $artifacts.GetEnumerator()) { $path = Join-Path $PSScriptRoot $entry.Value diff --git a/sync_version.ps1 b/sync_version.ps1 index 8bc0d7d..938d52e 100755 --- a/sync_version.ps1 +++ b/sync_version.ps1 @@ -3,6 +3,9 @@ param([switch]$Check) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $rootDir = $PSScriptRoot +$runtimePrefix = if (Test-Path -LiteralPath (Join-Path $rootDir 'tools/tools.sh') -PathType Leaf) { 'tools/' } else { '' } +$scriptRelative = $runtimePrefix + 'tools.sh' +$selfCheckRelative = $runtimePrefix + 'dex_check.sh' $utf8 = New-Object System.Text.UTF8Encoding($false) . (Join-Path $rootDir 'read_versions.ps1') $versions = Read-SpeedBackupVersions $rootDir @@ -22,17 +25,20 @@ function Replace-One([string]$body, [string]$pattern, [string]$value) { if ($regex.Matches($body).Count -ne 1) { throw "Version assignment missing or duplicated: $pattern" } return $regex.Replace($body, [Text.RegularExpressions.MatchEvaluator]{ param($m) $value }) } -$body = Read-Source 'tools.sh' +$body = Read-Source $scriptRelative # backup_version is a numeric updater timestamp; keep its ordering independent. -$body = Replace-One $body '(?m)^speedbackup_patch_build="[^"]*"$' ('speedbackup_patch_build="' + $release + '"') +# Older FULL_SOURCE layouts still carry a separate build variable. +if ([regex]::IsMatch($body, '(?m)^speedbackup_patch_build=')) { + $body = Replace-One $body '(?m)^speedbackup_patch_build="[^"]*"$' ('speedbackup_patch_build="' + $release + '"') +} $body = Replace-One $body '(?m)^speedbackup_script_version="[^"]*"$' ('speedbackup_script_version="' + $versions['script'] + '"') -$pending['tools.sh'] = $body -$body = Read-Source 'dex_check.sh' +$pending[$scriptRelative] = $body +$body = Read-Source $selfCheckRelative $body = Replace-One $body '(?m)^DEX_CHECK_VERSION="[^"]*"$' ('DEX_CHECK_VERSION="' + $versions['script'] + '"') $body = Replace-One $body '(?m)^DEX_CHECK_BUILD="[^"]*"$' ('DEX_CHECK_BUILD="' + $release + '"') $body = Replace-One $body '(?m)^SELFTEST_SCRIPT_VERSION=.*$' 'SELFTEST_SCRIPT_VERSION="${SELFTEST_SCRIPT_VERSION:-$DEX_CHECK_VERSION}"' $body = Replace-One $body '(?m)^SPEEDBACKUP_PATCH_BUILD=.*$' 'SPEEDBACKUP_PATCH_BUILD="${SPEEDBACKUP_PATCH_BUILD:-$DEX_CHECK_BUILD}"' -$pending['dex_check.sh'] = $body +$pending[$selfCheckRelative] = $body $pending['dex/app/src/main/java/com/xayah/dex/DexBuildInfo.java'] = @" package com.xayah.dex; @@ -51,9 +57,9 @@ $pending['rust/Cargo.toml'] = Replace-One (Read-Source 'rust/Cargo.toml') '(?m)^ $pending['rust/Cargo.lock'] = Replace-One (Read-Source 'rust/Cargo.lock') '(?m)(?<=name = "speedbackup-native-rs"\n)version = "[^"]+"' ('version = "' + $cargoVersion + '"') # SHA row follows the generated self-check script even on source-only version bumps. $sha = [Security.Cryptography.SHA256]::Create() -try { $selfCheckHash = ([BitConverter]::ToString($sha.ComputeHash($utf8.GetBytes($pending['dex_check.sh'])))).Replace('-','').ToLowerInvariant() } +try { $selfCheckHash = ([BitConverter]::ToString($sha.ComputeHash($utf8.GetBytes($pending[$selfCheckRelative])))).Replace('-','').ToLowerInvariant() } finally { $sha.Dispose() } -$pending['tools.sh'] = Replace-One $pending['tools.sh'] '(?m)^dex_check\.sh [0-9a-f]{64}$' ('dex_check.sh ' + $selfCheckHash) +$pending[$scriptRelative] = Replace-One $pending[$scriptRelative] '(?m)^dex_check\.sh [0-9a-f]{64}$' ('dex_check.sh ' + $selfCheckHash) foreach ($entry in $pending.GetEnumerator()) { $path = Join-Path $rootDir $entry.Key $wanted = $entry.Value.Replace("`r`n", "`n") diff --git a/tools/dex_check.sh b/tools/dex_check.sh index f7235a8..a97a65e 100644 --- a/tools/dex_check.sh +++ b/tools/dex_check.sh @@ -8,7 +8,7 @@ TOOLS_PATH="${TOOLS_PATH:-}" TEST_LOG_DIR="${TEST_LOG_DIR:-${PWD:-.}}" TEST_LOG_FILE="${TEST_LOG_FILE:-$TEST_LOG_DIR/dex_check.log}" TEST_SUMMARY_FILE="${TEST_SUMMARY_FILE:-$TEST_LOG_DIR/dex_full_test.summary}" -DEX_CHECK_VERSION="v778" +DEX_CHECK_VERSION="v780" DEX_CHECK_BUILD="v780" BACKUP_WIFI_ENABLE="${BACKUP_WIFI_ENABLE:-1}" SB_SELFTEST_LEVEL="${SB_SELFTEST_LEVEL:-quick}" diff --git a/tools/tools.sh b/tools/tools.sh index 52b3dd4..64a99c3 100644 --- a/tools/tools.sh +++ b/tools/tools.sh @@ -9493,7 +9493,7 @@ _speedbackup_tool_sha_table() { busybox 88bdc7e3d9d38ced8a51a35e811240e7f4372db61b541943f53cc5f889f1e29b classes.dex 6c1a178fa40160b8e6f529192e8556ad59bb740c3604e2f20095427dc70c7a74 cmd 08da8ac23b6e99788fd3ce6c19c7b5a083b2ad48be35963a48d01d6ee7f3bb6d -dex_check.sh f9e318188cc22aff1d870b348c2c21ca4af9e6ae25ce75a99e4d624f952788a2 +dex_check.sh de34c19c5521bc9aff354603039c91258a418edc7a4406ab0aeb9e107e48a1f6 find 7fa812e58aafa29679cf8b50fc617ecf9fec2cfb2e06ea491e0a2d6bf79b903b jq 6bc62f25981328edd3cfcfe6fe51b073f2d7e7710d7ef7fcdac28d4e384fc3d4 keycheck 50645ee0e0d2a7d64fb4a1286446df7a4445f3d11aefd49eeeb88515b314c363 diff --git a/versions.properties b/versions.properties index dfa55a2..53f99dc 100755 --- a/versions.properties +++ b/versions.properties @@ -1,7 +1,7 @@ # The only editable version source. Values are v plus three digits. # Increment build for every published build. Increment only changed components. build=v780 -script=v778 +script=v780 dex=v779 cgfreezer=v777 eventwait=v777