Some checks failed
ZaStoGram source guards / guards (push) Successful in 23s
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Failing after 3m42s
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Failing after 12m39s
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Failing after 15m5s
299 lines
14 KiB
Python
299 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
CONTROLLER = ROOT / "TMessagesProj_AppStandalone/src/main/java/org/telegram/messenger/ForgejoUpdaterController.java"
|
|
HTTP_GET_FILE_TASK = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/web/HttpGetFileTask.java"
|
|
LOADER = ROOT / "TMessagesProj_AppStandalone/src/main/java/org/telegram/messenger/ApplicationLoaderImpl.java"
|
|
BASE_LOADER = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java"
|
|
LAYOUT = ROOT / "TMessagesProj_AppStandalone/src/main/java/org/telegram/ui/Components/ForgejoUpdateLayout.java"
|
|
ALERT = ROOT / "TMessagesProj_AppStandalone/src/main/java/org/telegram/ui/Components/ForgejoUpdateAlertDialog.java"
|
|
LAUNCH = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java"
|
|
BETA_UPDATE = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/BetaUpdate.java"
|
|
ANDROID_UTILITIES = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java"
|
|
SETTINGS_ACTIVITY = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java"
|
|
LIB_GRADLE = ROOT / "TMessagesProj/build.gradle"
|
|
APP_GRADLE = ROOT / "TMessagesProj_AppStandalone/build.gradle"
|
|
ROOT_GRADLE = ROOT / "build.gradle"
|
|
STANDALONE_MANIFEST = ROOT / "TMessagesProj/config/release/AndroidManifest_standalone.xml"
|
|
GOOGLE_SERVICES = ROOT / "TMessagesProj_AppStandalone/google-services.json"
|
|
WORKFLOW = ROOT / ".forgejo/workflows/build-apk.yml"
|
|
PROVIDER_PATHS = ROOT / "TMessagesProj/src/main/res/xml/provider_paths.xml"
|
|
MAIN_STRINGS = ROOT / "TMessagesProj/src/main/res/values/strings.xml"
|
|
RU_STRINGS = ROOT / "TMessagesProj/src/main/res/values-ru/strings.xml"
|
|
|
|
|
|
def read(path: Path) -> str:
|
|
try:
|
|
return path.read_text(encoding="utf-8")
|
|
except FileNotFoundError:
|
|
print(f"FAIL: missing {path.relative_to(ROOT)}", file=sys.stderr)
|
|
raise SystemExit(1)
|
|
|
|
|
|
def require(text: str, literal: str, description: str, failures: list[str]) -> None:
|
|
if literal not in text:
|
|
failures.append(f"{description}: missing {literal!r}")
|
|
|
|
|
|
def main() -> int:
|
|
controller = read(CONTROLLER)
|
|
http_get_file_task = read(HTTP_GET_FILE_TASK)
|
|
loader = read(LOADER)
|
|
base_loader = read(BASE_LOADER)
|
|
layout = read(LAYOUT)
|
|
alert = read(ALERT)
|
|
launch = read(LAUNCH)
|
|
beta_update = read(BETA_UPDATE)
|
|
android_utilities = read(ANDROID_UTILITIES)
|
|
settings_activity = read(SETTINGS_ACTIVITY)
|
|
lib_gradle = read(LIB_GRADLE)
|
|
app_gradle = read(APP_GRADLE)
|
|
root_gradle = read(ROOT_GRADLE)
|
|
standalone_manifest = read(STANDALONE_MANIFEST)
|
|
google_services = read(GOOGLE_SERVICES)
|
|
workflow = read(WORKFLOW)
|
|
provider_paths = read(PROVIDER_PATHS)
|
|
main_strings = read(MAIN_STRINGS)
|
|
ru_strings = read(RU_STRINGS)
|
|
failures: list[str] = []
|
|
|
|
for literal in (
|
|
"public boolean isCustomUpdate()",
|
|
"return true;",
|
|
"ForgejoUpdaterController.getInstance().checkForUpdate(force, whenDone)",
|
|
"ForgejoUpdaterController.getInstance().downloadUpdate()",
|
|
"new ForgejoUpdateLayout(activity, sideMenuContainer)",
|
|
"ForgejoUpdateAlertDialog.show(context, update)",
|
|
"ForgejoUpdaterController.getInstance().shouldShowUpdatePopup(force)",
|
|
"ForgejoUpdaterController.getInstance().markUpdatePopupShown()",
|
|
"public String getCustomBuildVersionInfo()",
|
|
):
|
|
require(loader, literal, "Standalone loader must fully own app updates", failures)
|
|
|
|
for literal in (
|
|
'org.telegram.messenger.web.R.string.ZastoForgejoRepository',
|
|
'String base = "https://git.zapret.moe/api/v1/repos/" + repository + "/releases";',
|
|
'return isDevChannel() ? base + "?limit=100" : base + "/latest";',
|
|
'release.optBoolean("draft", false)',
|
|
'release.optBoolean("prerelease", false) != expectPrerelease',
|
|
'asset.optString("browser_download_url", "")',
|
|
'for (String abi : Build.SUPPORTED_ABIS)',
|
|
'"ZaStoGram-standalone-" + abi + ".apk"',
|
|
'candidate.releaseTag.equals(getInstalledReleaseTag())',
|
|
'ApplicationLoader.getApplicationId().equals(packageInfo.packageName)',
|
|
'file.length() != assetSize',
|
|
'asset.optLong("id", 0L)',
|
|
'"zastogram-update-" + releaseId + "-" + assetId + ".apk.part"',
|
|
'new File(ApplicationLoader.applicationContext.getFilesDir(), "cache")',
|
|
'"zastogram-update-" + releaseId + "-" + assetId + ".apk"',
|
|
'partialFile.renameTo(completedFile)',
|
|
'REMIND_LATER_INTERVAL = 24L * 60L * 60L * 1000L',
|
|
'prefs.getString("snoozedReleaseTag", null)',
|
|
'prefs.getString("skippedReleaseTag", null)',
|
|
'public boolean shouldShowUpdatePopup(boolean force)',
|
|
'public void markUpdatePopupShown()',
|
|
'public void remindAboutCurrentUpdateLater()',
|
|
'public void skipCurrentUpdate()',
|
|
'candidate.releaseTag.equals(skippedReleaseTag)',
|
|
'.setDestFile(partialFile)',
|
|
'.setResumeExistingFile(true)',
|
|
'.setKeepPartialFileOnCancel(true)',
|
|
'setHeader("Accept", "application/json")',
|
|
'setHeader("User-Agent", "ZaStoGram-Android-Updater")',
|
|
):
|
|
require(controller, literal, "Forgejo updater channel/asset safety contract", failures)
|
|
|
|
for literal in (
|
|
'urlConnection.setRequestProperty("Range", "bytes=" + downloadedSize + "-")',
|
|
'status == HttpURLConnection.HTTP_PARTIAL',
|
|
'parseContentRangeStart(urlConnection.getHeaderField("Content-Range"))',
|
|
'new FileOutputStream(file, resuming)',
|
|
'downloadedSize != totalSize',
|
|
'keepPartialFileOnCancel',
|
|
):
|
|
require(http_get_file_task, literal, "HTTP file resume contract", failures)
|
|
|
|
require(
|
|
provider_paths,
|
|
'<cache-path name="update_cache" path="."/>',
|
|
"Downloaded update APKs must be shareable with the Android package installer",
|
|
failures,
|
|
)
|
|
require(
|
|
provider_paths,
|
|
'<files-path name="cache" path="/cache/"/>',
|
|
"Updater APKs must remain shareable with FileProvider rules from older releases",
|
|
failures,
|
|
)
|
|
|
|
if "authorization" in controller.lower():
|
|
failures.append("Android updater must use the public Forgejo Releases API without embedding credentials")
|
|
|
|
custom_branch = launch.find("if (ApplicationLoader.applicationLoaderInstance.isCustomUpdate())")
|
|
telegram_request = launch.find("new TLRPC.TL_help_getAppUpdate()", custom_branch)
|
|
custom_return = launch.find("return;", custom_branch)
|
|
if custom_branch < 0 or custom_return < 0 or telegram_request < 0 or not custom_return < telegram_request:
|
|
failures.append("LaunchActivity must return from the custom updater before Telegram TL_help_getAppUpdate")
|
|
for literal in (
|
|
"public boolean allowCustomUpdateAppPopup(boolean force, boolean updateChanged)",
|
|
"return force || updateChanged;",
|
|
):
|
|
require(base_loader, literal, "Non-Forgejo custom updaters must retain their existing popup policy", failures)
|
|
require(
|
|
launch,
|
|
"ApplicationLoader.applicationLoaderInstance.allowCustomUpdateAppPopup(force, updateChanged)",
|
|
"Every automatic custom-update popup must respect its persisted snooze",
|
|
failures,
|
|
)
|
|
|
|
for literal in (
|
|
'System.getenv("ZASTO_UPDATE_CHANNEL") ?: "stable"',
|
|
"ext.zastoReleaseIdentity",
|
|
"updateChannel: zastoUpdateChannelValue",
|
|
):
|
|
require(root_gradle, literal, "Build-wide release identity", failures)
|
|
|
|
# A stable release must derive its versionCode from its own tag. Passing the
|
|
# number by hand once shipped 1.1.13 with a versionCode BELOW 1.1.12, and
|
|
# Android then refuses the install with nothing but "App not installed".
|
|
for literal in (
|
|
"zastoSemanticTag = (zastoReleaseTagValue =~",
|
|
"zastoResolvedBuildNumber = major * 1000 + minor * 100 + patch",
|
|
"buildNumber: zastoResolvedBuildNumber,",
|
|
):
|
|
require(
|
|
root_gradle,
|
|
literal,
|
|
"Semantic release tag must derive the versionCode",
|
|
failures,
|
|
)
|
|
|
|
for literal in (
|
|
'def zastoApplicationId = zastoUpdateChannel == "dev" ? "${APP_PACKAGE}.dev" : APP_PACKAGE',
|
|
'def zastoApplicationName = zastoUpdateChannel == "dev" ? "ZaStoGram Dev" : "ZaStoGram"',
|
|
"defaultConfig.applicationId = zastoApplicationId",
|
|
'resValue "string", "ZastoApplicationName", zastoApplicationName',
|
|
'resValue "string", "ZastoUpdateChannel", zastoUpdateChannel',
|
|
'resValue "string", "ZastoReleaseTag", zastoReleaseIdentity.releaseTag',
|
|
'resValue "string", "ZastoForgejoRepository", zastoReleaseIdentity.forgejoRepository',
|
|
'resValue "integer", "ZastoBuildNumber", zastoBuildNumber.toString()',
|
|
"buildConfig = false",
|
|
):
|
|
require(app_gradle, literal, "Standalone package/update identity", failures)
|
|
|
|
for literal in (
|
|
"getEmbeddedReleaseTag()",
|
|
"org.telegram.messenger.web.R.string.ZastoReleaseTag",
|
|
"getEmbeddedBuildNumber()",
|
|
"org.telegram.messenger.web.R.integer.ZastoBuildNumber",
|
|
):
|
|
require(controller, literal, "Cacheable per-release resource identity", failures)
|
|
|
|
release_identity_sources = "\n".join((app_gradle, controller, loader, layout, alert))
|
|
for forbidden in (
|
|
'buildConfigField "String", "ZASTO_RELEASE_TAG"',
|
|
'buildConfigField "int", "ZASTO_BUILD_NUMBER"',
|
|
'buildConfigField "String", "ZASTO_UPDATE_CHANNEL"',
|
|
'buildConfigField "String", "ZASTO_FORGEJO_REPOSITORY"',
|
|
"BuildConfig.ZASTO_RELEASE_TAG",
|
|
"BuildConfig.ZASTO_BUILD_NUMBER",
|
|
"BuildConfig.ZASTO_UPDATE_CHANNEL",
|
|
"BuildConfig.ZASTO_FORGEJO_REPOSITORY",
|
|
):
|
|
if forbidden in release_identity_sources:
|
|
failures.append(f"Per-release value must not invalidate R8 through BuildConfig: {forbidden!r}")
|
|
|
|
if "org.telegram.messenger.web.BuildConfig" in release_identity_sources:
|
|
failures.append("Standalone release identity must not import the app BuildConfig")
|
|
|
|
if "ZASTO_UPDATE_CHANNEL" in lib_gradle:
|
|
failures.append("Update-channel identity belongs to standalone application resources, not the shared library")
|
|
for literal in (
|
|
'def zastoStandaloneDebugVersion = rootProject.ext.zastoReleaseIdentity.updateChannel == "dev"',
|
|
'buildConfigField "boolean", "DEBUG_VERSION", zastoStandaloneDebugVersion.toString()',
|
|
):
|
|
require(lib_gradle, literal, "Standalone dev diagnostic identity", failures)
|
|
require(standalone_manifest, 'android:label="@string/ZastoApplicationName"', "Channel-specific Android app label", failures)
|
|
require(google_services, '"package_name": "org.zastogram.messenger.dev"', "Dev Google services package mapping", failures)
|
|
|
|
require(loader, "return getPackageName();", "Standalone package id must not depend on BuildConfig", failures)
|
|
|
|
require(
|
|
app_gradle,
|
|
"output.versionCodeOverride = defaultConfig.versionCode * 100000 + zastoBuildNumber * 10 + abiVersionDigit",
|
|
"Published APKs need monotonically increasing workflow version codes",
|
|
failures,
|
|
)
|
|
|
|
for literal in (
|
|
"python3 Tools/check_forgejo_update_contract.py",
|
|
"ZASTO_UPDATE_CHANNEL: dev",
|
|
"ZASTO_RELEASE_TAG: forgejo-build-${{ forgejo.run_number }}-${{ forgejo.run_attempt }}",
|
|
"ZASTO_BUILD_NUMBER: ${{ forgejo.run_number }}",
|
|
"ZASTO_FORGEJO_REPOSITORY: ${{ forgejo.repository }}",
|
|
"https://data.forgejo.org/forgejo/upload-artifact@v4",
|
|
):
|
|
require(workflow, literal, "Forgejo Actions background build identity", failures)
|
|
|
|
if "${{ github." in workflow or "GITHUB_" in workflow:
|
|
failures.append("Forgejo workflow must not use GitHub compatibility aliases")
|
|
|
|
for text, description in (
|
|
(layout, "Forgejo update drawer UI"),
|
|
(alert, "Forgejo update alert UI"),
|
|
):
|
|
require(text, "ForgejoUpdaterController.isDevChannel()", description, failures)
|
|
require(text, '"ZaStoGram.apk"', description, failures)
|
|
|
|
for literal in (
|
|
"updater.remindAboutCurrentUpdateLater()",
|
|
"updater.skipCurrentUpdate()",
|
|
"R.string.AppUpdateSkipVersion",
|
|
".forceVerticalButtons()",
|
|
"public static boolean show(Context context, BetaUpdate update)",
|
|
):
|
|
require(alert, literal, "Forgejo update prompt choices", failures)
|
|
|
|
require(main_strings, '<string name="AppUpdateSkipVersion">Skip this version</string>', "Skip-version label", failures)
|
|
require(ru_strings, '<string name="AppUpdateSkipVersion">Пропустить эту версию</string>', "Russian skip-version label", failures)
|
|
require(main_strings, 'name="ZaStoGramVersion"', "ZaStoGram build-version label", failures)
|
|
require(ru_strings, 'name="ZaStoGramVersion"', "Russian ZaStoGram build-version label", failures)
|
|
|
|
for literal in (
|
|
"pInfo.versionCode / 100000",
|
|
"getCustomBuildVersionInfo()",
|
|
'telegramVersion + "\\n" + customVersion',
|
|
):
|
|
require(android_utilities, literal, "Version footer must show Telegram and ZaStoGram identities", failures)
|
|
require(
|
|
settings_activity,
|
|
"return AndroidUtilities.getBuildVersionInfo();",
|
|
"Settings footer must use the combined build identity",
|
|
failures,
|
|
)
|
|
|
|
for literal in (
|
|
"public final long updateOrder;",
|
|
"updateOrder > update.updateOrder",
|
|
"new BetaUpdate(version, displayVersionCode, changelog, updateOrder)",
|
|
):
|
|
target = controller if literal.startswith("new BetaUpdate") else beta_update
|
|
require(target, literal, "Release ordering must not depend on Telegram APP_VERSION_CODE", failures)
|
|
|
|
if failures:
|
|
print("Forgejo updater contract failed:", file=sys.stderr)
|
|
for failure in failures:
|
|
print(f"- {failure}", file=sys.stderr)
|
|
return 1
|
|
|
|
print("Forgejo updater contract passed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|