Some checks failed
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Has been cancelled
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Has been cancelled
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Has been cancelled
ZaStoGram source guards / guards (push) Has been cancelled
В проекте два хранилища ключей: настоящий релизный лежит вне репозитория и подключается переменной ZASTO_RELEASE_KEYSTORE, а в самом репозитории есть запасной с другим сертификатом. Если переменную не передать, сборка молча брала запасной — так вышли 1.1.13 и 1.1.14, подписанные ключом a08d7dc3 вместо 84315d38. Android запрещает обновление приложения со сменившейся подписью и сообщает об этом лишь фразой «Приложение не установлено», а Play Protect вдобавок помечает неизвестного подписанта как угрозу — обе жалобы пользователя объясняются этим. Теперь стабильная сборка без релизного ключа падает с внятной ошибкой, а не подменяет ключ. Параметры подписи дополнительно принимаются как свойства Gradle: System.getenv в скрипте сборки читает окружение демона, а не команды, поэтому экспортированная переменная может не примениться — та же ловушка, что была с номером сборки. Добавлена проверка отпечатка сертификата собранного APK и её вызов из конвейера, чтобы чужой ключ нельзя было выпустить незамеченным. Заодно исправлен спам proxy_warmup в логах: дедупликация не действовала на решения allow, а её кэш состоял из одной ячейки, поэтому чередующиеся записи от разных аккаунтов вытесняли друг друга. Теперь окно у каждого ключа своё, а число свёрнутых повторов печатается как repeated=N, чтобы записи не пропадали молча. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
332 lines
13 KiB
Groovy
332 lines
13 KiB
Groovy
apply plugin: 'com.android.application'
|
|
|
|
def zastoReleaseIdentity = rootProject.ext.zastoReleaseIdentity
|
|
def zastoUpdateChannel = zastoReleaseIdentity.updateChannel
|
|
def zastoBuildNumber = zastoReleaseIdentity.buildNumber
|
|
def zastoApplicationId = zastoUpdateChannel == "dev" ? "${APP_PACKAGE}.dev" : APP_PACKAGE
|
|
def zastoApplicationName = zastoUpdateChannel == "dev" ? "ZaStoGram Dev" : "ZaStoGram"
|
|
|
|
// Signing inputs accept a Gradle property as well as the environment variable.
|
|
// System.getenv() inside a build script reports the DAEMON's environment, not
|
|
// the one of the command that started the build, so an exported variable can
|
|
// silently fail to apply while a -P property always reaches this code.
|
|
def zastoSigningInput = { String propertyName, String envName ->
|
|
return (findProperty(propertyName) ?: System.getenv(envName))?.toString()?.trim() ?: null
|
|
}
|
|
|
|
def zastoReadSigningSecret = { String propertyName, String envName ->
|
|
def secretPath = zastoSigningInput(propertyName, envName)
|
|
if (!secretPath) {
|
|
return null
|
|
}
|
|
def secretFile = file(secretPath)
|
|
if (!secretFile.isFile()) {
|
|
throw new GradleException("ZaStoGram signing secret file does not exist: ${secretPath}")
|
|
}
|
|
return secretFile.getText("UTF-8").trim()
|
|
}
|
|
|
|
def zastoReleaseKeystorePath = zastoSigningInput("zastoReleaseKeystore", "ZASTO_RELEASE_KEYSTORE")
|
|
def zastoReleaseStorePassword = zastoReadSigningSecret("zastoReleaseStorePasswordFile", "ZASTO_RELEASE_STORE_PASSWORD_FILE")
|
|
def zastoReleaseKeyPassword = zastoReadSigningSecret("zastoReleaseKeyPasswordFile", "ZASTO_RELEASE_KEY_PASSWORD_FILE")
|
|
def zastoReleaseKeyAlias = zastoReadSigningSecret("zastoReleaseKeyAliasFile", "ZASTO_RELEASE_KEY_ALIAS_FILE")
|
|
def zastoExternalSigningValues = [
|
|
zastoReleaseKeystorePath,
|
|
zastoReleaseStorePassword,
|
|
zastoReleaseKeyPassword,
|
|
zastoReleaseKeyAlias,
|
|
]
|
|
if (zastoExternalSigningValues.any { it } && !zastoExternalSigningValues.every { it }) {
|
|
throw new GradleException("ZaStoGram external signing requires the keystore and all three credential files")
|
|
}
|
|
// A stable build MUST carry the real release identity. The in-repo keystore is
|
|
// a different certificate, and Android refuses to update an installed app whose
|
|
// signature changed - it only says "App not installed", and Play Protect flags
|
|
// the unknown signer on top. Releases 1.1.13 and 1.1.14 shipped that way
|
|
// because the fallback applied silently; refusing to build is the only way this
|
|
// cannot happen again.
|
|
if (zastoUpdateChannel == "stable" && !zastoExternalSigningValues.every { it }) {
|
|
throw new GradleException(
|
|
"Refusing a stable ZaStoGram build without its release signing key. " +
|
|
"Pass -PzastoReleaseKeystore, -PzastoReleaseStorePasswordFile, " +
|
|
"-PzastoReleaseKeyPasswordFile and -PzastoReleaseKeyAliasFile " +
|
|
"(or the matching ZASTO_RELEASE_* environment variables).")
|
|
}
|
|
def zastoReleaseKeystoreFile = zastoReleaseKeystorePath
|
|
? file(zastoReleaseKeystorePath)
|
|
: file("../TMessagesProj/config/release.keystore")
|
|
if (zastoReleaseKeystorePath && !zastoReleaseKeystoreFile.isFile()) {
|
|
throw new GradleException("ZaStoGram release keystore does not exist: ${zastoReleaseKeystorePath}")
|
|
}
|
|
|
|
repositories {
|
|
mavenCentral()
|
|
google()
|
|
}
|
|
|
|
configurations {
|
|
compile.exclude module: 'support-v4'
|
|
}
|
|
|
|
configurations.all {
|
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
|
exclude group: 'androidx.recyclerview', module: 'recyclerview'
|
|
}
|
|
|
|
dependencies {
|
|
implementation project(':TMessagesProj')
|
|
implementation 'androidx.fragment:fragment:1.8.9'
|
|
implementation 'androidx.core:core:1.16.0'
|
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
|
implementation files('../TMessagesProj/libs/libgsaverification-client.aar')
|
|
}
|
|
|
|
android {
|
|
compileSdkVersion 35
|
|
buildToolsVersion '35.0.0'
|
|
|
|
defaultConfig.applicationId = zastoApplicationId
|
|
|
|
sourceSets.main.jniLibs.srcDirs = ['../TMessagesProj/jni/']
|
|
|
|
lintOptions {
|
|
disable 'MissingTranslation'
|
|
disable 'ExtraTranslation'
|
|
disable 'BlockedPrivateApi'
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility JavaVersion.VERSION_1_8
|
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
|
|
coreLibraryDesugaringEnabled true
|
|
}
|
|
|
|
signingConfigs {
|
|
debug {
|
|
storeFile zastoReleaseKeystoreFile
|
|
storePassword zastoReleaseStorePassword ?: RELEASE_STORE_PASSWORD
|
|
keyAlias zastoReleaseKeyAlias ?: RELEASE_KEY_ALIAS
|
|
keyPassword zastoReleaseKeyPassword ?: RELEASE_KEY_PASSWORD
|
|
}
|
|
|
|
release {
|
|
storeFile zastoReleaseKeystoreFile
|
|
storePassword zastoReleaseStorePassword ?: RELEASE_STORE_PASSWORD
|
|
keyAlias zastoReleaseKeyAlias ?: RELEASE_KEY_ALIAS
|
|
keyPassword zastoReleaseKeyPassword ?: RELEASE_KEY_PASSWORD
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
debug {
|
|
debuggable true
|
|
jniDebuggable true
|
|
signingConfig signingConfigs.debug
|
|
applicationIdSuffix ".web"
|
|
minifyEnabled false
|
|
multiDexEnabled true
|
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), '../TMessagesProj/proguard-rules.pro', '../TMessagesProj/proguard-rules-beta.pro'
|
|
ndk.debugSymbolLevel = 'FULL'
|
|
}
|
|
standalone {
|
|
debuggable false
|
|
jniDebuggable false
|
|
signingConfig signingConfigs.release
|
|
minifyEnabled true
|
|
multiDexEnabled true
|
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), '../TMessagesProj/proguard-rules.pro'
|
|
ndk.debugSymbolLevel = 'FULL'
|
|
}
|
|
}
|
|
|
|
sourceSets.debug {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
sourceSets.standalone {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
|
|
flavorDimensions "minApi"
|
|
|
|
productFlavors {
|
|
afat {
|
|
ndk {
|
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
|
}
|
|
ext {
|
|
abiVersionCode = 9
|
|
}
|
|
sourceSets.standalone {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
}
|
|
arm64 {
|
|
ndk {
|
|
abiFilters "arm64-v8a"
|
|
}
|
|
ext {
|
|
abiVersionCode = 11
|
|
}
|
|
sourceSets.standalone {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
}
|
|
armv7 {
|
|
ndk {
|
|
abiFilters "armeabi-v7a"
|
|
}
|
|
ext {
|
|
abiVersionCode = 12
|
|
}
|
|
sourceSets.standalone {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
}
|
|
x86 {
|
|
ndk {
|
|
abiFilters "x86"
|
|
}
|
|
ext {
|
|
abiVersionCode = 13
|
|
}
|
|
sourceSets.standalone {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
}
|
|
x64 {
|
|
ndk {
|
|
abiFilters "x86_64"
|
|
}
|
|
ext {
|
|
abiVersionCode = 14
|
|
}
|
|
sourceSets.standalone {
|
|
manifest.srcFile '../TMessagesProj/config/release/AndroidManifest_standalone.xml'
|
|
}
|
|
}
|
|
}
|
|
|
|
defaultConfig.versionCode = Integer.parseInt(APP_VERSION_CODE)
|
|
|
|
applicationVariants.all { variant ->
|
|
variant.outputs.all { output ->
|
|
outputFileName = "app.apk"
|
|
// Reserve five decimal digits per upstream Telegram version: four for the
|
|
// Forgejo Actions run and one for the ABI. A newer dev prerelease can then
|
|
// be installed over the previous prerelease even when APP_VERSION_CODE did
|
|
// not change between workflow runs.
|
|
def abiVersionDigit = variant.productFlavors.get(0).abiVersionCode % 10
|
|
output.versionCodeOverride = defaultConfig.versionCode * 100000 + zastoBuildNumber * 10 + abiVersionDigit
|
|
}
|
|
}
|
|
|
|
def standaloneBuildFlavors = ["afat", "arm64", "armv7", "x86", "x64"]
|
|
variantFilter { variant ->
|
|
def names = variant.flavors*.name
|
|
if (variant.buildType.name != "release" && !names.any { standaloneBuildFlavors.contains(it) }) {
|
|
setIgnore(true)
|
|
}
|
|
}
|
|
|
|
defaultConfig {
|
|
minSdkVersion 24 // ZaStoGram: raised for Chaquopy (plugin engine); must match library
|
|
targetSdkVersion 35
|
|
versionName APP_VERSION_NAME
|
|
ndkVersion "27.2.12479018"
|
|
|
|
// Stable and dev are independent installed applications. Keep their
|
|
// package, visible label and updater channel derived from one identity.
|
|
resValue "string", "ZastoApplicationName", zastoApplicationName
|
|
// Keep identity in resources rather than BuildConfig. BuildConfig always embeds the
|
|
// changing VERSION_CODE, which would invalidate the expensive R8 output each release.
|
|
resValue "string", "ZastoUpdateChannel", zastoUpdateChannel
|
|
resValue "string", "ZastoReleaseTag", zastoReleaseIdentity.releaseTag
|
|
resValue "string", "ZastoForgejoRepository", zastoReleaseIdentity.forgejoRepository
|
|
resValue "integer", "ZastoBuildNumber", zastoBuildNumber.toString()
|
|
|
|
multiDexEnabled true
|
|
|
|
vectorDrawables.generatedDensities = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi']
|
|
|
|
externalNativeBuild {
|
|
cmake {
|
|
version '3.10.2'
|
|
arguments '-DANDROID_STL=c++_static', '-DANDROID_PLATFORM=android-21' //, '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON'
|
|
}
|
|
}
|
|
}
|
|
|
|
buildFeatures {
|
|
buildConfig = false
|
|
}
|
|
namespace 'org.telegram.messenger.web'
|
|
|
|
lintOptions {
|
|
checkReleaseBuilds false
|
|
}
|
|
}
|
|
|
|
// A second line of defense after packaging: even a custom or abbreviated Gradle
|
|
// invocation must never leave behind an ABI-specific APK containing Chaquopy
|
|
// runtime assets for another architecture.
|
|
def zastoStandaloneAbiApks = [
|
|
Arm64: [outputDir: "arm64", abi: "arm64-v8a"],
|
|
Armv7: [outputDir: "armv7", abi: "armeabi-v7a"],
|
|
X86: [outputDir: "x86", abi: "x86"],
|
|
X64: [outputDir: "x64", abi: "x86_64"],
|
|
]
|
|
def zastoValidNativeAbis = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
|
|
zastoStandaloneAbiApks.each { flavor, spec ->
|
|
def assembleTaskName = "assemble${flavor}Standalone"
|
|
def verifyTask = tasks.register("verify${flavor}StandaloneAbiIsolation") {
|
|
group = "verification"
|
|
description = "Rejects foreign native and Chaquopy ABI payload in ${flavor} standalone APK"
|
|
dependsOn assembleTaskName
|
|
doLast {
|
|
File apkFile = file("$buildDir/outputs/apk/${spec.outputDir}/standalone/app.apk")
|
|
if (!apkFile.isFile()) {
|
|
throw new GradleException("Standalone APK is missing: ${apkFile}")
|
|
}
|
|
|
|
def foreignEntries = []
|
|
def hasExpectedNative = false
|
|
def hasExpectedChaquopy = false
|
|
def archive = new java.util.zip.ZipFile(apkFile)
|
|
try {
|
|
def entries = archive.entries()
|
|
while (entries.hasMoreElements()) {
|
|
def name = entries.nextElement().name
|
|
zastoValidNativeAbis.each { abi ->
|
|
def nativeEntry = name.startsWith("lib/${abi}/")
|
|
def chaquopyBootstrap = name.startsWith("assets/chaquopy/bootstrap-native/${abi}/")
|
|
def chaquopyArchive = name == "assets/chaquopy/requirements-${abi}.imy" ||
|
|
name == "assets/chaquopy/stdlib-${abi}.imy"
|
|
if (nativeEntry || chaquopyBootstrap || chaquopyArchive) {
|
|
if (abi != spec.abi) {
|
|
foreignEntries.add(name)
|
|
} else {
|
|
hasExpectedNative |= nativeEntry
|
|
hasExpectedChaquopy |= chaquopyBootstrap
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
archive.close()
|
|
}
|
|
|
|
if (!foreignEntries.isEmpty() || !hasExpectedNative || !hasExpectedChaquopy) {
|
|
throw new GradleException(
|
|
"APK ABI isolation failed for ${apkFile}: foreign ABI payload=${foreignEntries.take(12)}, " +
|
|
"expected native=${hasExpectedNative}, expected Chaquopy=${hasExpectedChaquopy}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
tasks.matching { it.name == assembleTaskName }.configureEach {
|
|
finalizedBy verifyTask
|
|
}
|
|
}
|
|
|
|
apply plugin: 'com.google.gms.google-services'
|