187 lines
9.0 KiB
Groovy
187 lines
9.0 KiB
Groovy
pipeline {
|
|
agent {
|
|
node {
|
|
label '构建机1'
|
|
customWorkspace '/home/nanxunai/goujian/workspace/jizhang-android-release'
|
|
}
|
|
}
|
|
|
|
parameters {
|
|
string(name: 'API_BASE_URL', defaultValue: 'http://192.168.5.8:5000', description: '内测后端 HTTP(S) 地址;Webhook 自动构建使用默认局域网地址')
|
|
}
|
|
|
|
options {
|
|
timestamps()
|
|
disableConcurrentBuilds()
|
|
skipDefaultCheckout(true)
|
|
timeout(time: 180, unit: 'MINUTES')
|
|
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '20'))
|
|
}
|
|
|
|
environment {
|
|
PATH = '/home/nanxunai/.local/bin:/home/nanxunai/flutter/bin:/home/nanxunai/Android/Sdk/platform-tools:/home/nanxunai/Android/Sdk/cmdline-tools/latest/bin:/usr/local/bin:/usr/bin:/bin'
|
|
JAVA_HOME = '/home/nanxunai/.local/lib/jvm/zulu-21'
|
|
ANDROID_HOME = '/home/nanxunai/Android/Sdk'
|
|
ANDROID_SDK_ROOT = '/home/nanxunai/Android/Sdk'
|
|
FLUTTER_ROOT = '/home/nanxunai/flutter'
|
|
GIT_EXEC_PATH = '/home/nanxunai/.local/usr/lib/git-core'
|
|
LD_LIBRARY_PATH = '/home/nanxunai/.local/usr/lib/x86_64-linux-gnu'
|
|
PUB_HOSTED_URL = 'https://pub.flutter-io.cn'
|
|
FLUTTER_STORAGE_BASE_URL = 'https://storage.flutter-io.cn'
|
|
HTTP_PROXY = 'http://192.168.5.200:7890'
|
|
HTTPS_PROXY = 'http://192.168.5.200:7890'
|
|
http_proxy = 'http://192.168.5.200:7890'
|
|
https_proxy = 'http://192.168.5.200:7890'
|
|
NO_PROXY = '127.0.0.1,localhost,192.168.5.8,.nxsir.cn,pub.flutter-io.cn,storage.flutter-io.cn'
|
|
no_proxy = '127.0.0.1,localhost,192.168.5.8,.nxsir.cn,pub.flutter-io.cn,storage.flutter-io.cn'
|
|
GRADLE_USER_HOME = "${WORKSPACE}/.ci/gradle"
|
|
PUB_CACHE = "${WORKSPACE}/.ci/pub-cache"
|
|
OPENLIST_CREDENTIALS = 'openlist_key'
|
|
OPENLIST_BASE_URL = 'https://openlist.nxsir.cn'
|
|
OPENLIST_REMOTE_DIR = '/yidongpan/构建产物/jizhang'
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout') {
|
|
steps {
|
|
deleteDir()
|
|
checkout scm
|
|
}
|
|
}
|
|
|
|
stage('Metadata And Preflight') {
|
|
steps {
|
|
script {
|
|
env.PUBSPEC_VERSION = sh(script: "sed -n 's/^version:[[:space:]]*//p' frontend/pubspec.yaml | head -n 1", returnStdout: true).trim()
|
|
def versionParts = env.PUBSPEC_VERSION.tokenize('+')
|
|
if (versionParts.size() != 2) { error("Invalid pubspec version: ${env.PUBSPEC_VERSION}") }
|
|
env.APP_VERSION = versionParts[0]
|
|
env.BASE_VERSION_CODE = versionParts[1]
|
|
env.ANDROID_VERSION_CODE = "${versionParts[1].toInteger() + env.BUILD_NUMBER.toInteger()}"
|
|
env.SHORT_SHA = sh(script: 'git rev-parse --short=8 HEAD', returnStdout: true).trim()
|
|
env.IMMUTABLE_TAG = "${env.APP_VERSION}-internal-b${env.BUILD_NUMBER}-${env.SHORT_SHA}"
|
|
env.APK_BASENAME = "JiZhi-${env.IMMUTABLE_TAG}-vc${env.ANDROID_VERSION_CODE}-arm64-v8a.apk"
|
|
env.APK_PATH = "${env.WORKSPACE}/artifacts/${env.APK_BASENAME}"
|
|
env.MANIFEST_PATH = "${env.WORKSPACE}/artifacts/JiZhi-${env.IMMUTABLE_TAG}-build-manifest.json"
|
|
currentBuild.displayName = "#${env.BUILD_NUMBER} ${env.IMMUTABLE_TAG}"
|
|
currentBuild.description = "versionCode ${env.ANDROID_VERSION_CODE} · ${params.API_BASE_URL}"
|
|
}
|
|
sh '''
|
|
set -euo pipefail
|
|
test "$(uname -m)" = x86_64
|
|
for command_name in git java curl python3 sha256sum; do command -v "$command_name" >/dev/null; done
|
|
test -x "$FLUTTER_ROOT/bin/flutter"
|
|
test -x "$JAVA_HOME/bin/jar"
|
|
test -x "$ANDROID_HOME/build-tools/36.1.0/apksigner"
|
|
test -x "$ANDROID_HOME/build-tools/36.1.0/aapt2"
|
|
python3 - "$API_BASE_URL" <<'PY'
|
|
import sys, urllib.parse
|
|
value = sys.argv[1].strip()
|
|
parsed = urllib.parse.urlparse(value)
|
|
if parsed.scheme not in {'http','https'} or not parsed.hostname or parsed.username or parsed.password:
|
|
raise SystemExit(f'invalid API_BASE_URL: {value}')
|
|
PY
|
|
mkdir -p "$GRADLE_USER_HOME" "$PUB_CACHE" artifacts
|
|
available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }')
|
|
test "$available_kb" -ge 4194304
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Analyze And Test') {
|
|
steps {
|
|
dir('frontend') {
|
|
sh '''
|
|
set -euo pipefail
|
|
"$FLUTTER_ROOT/bin/flutter" config --no-analytics
|
|
"$FLUTTER_ROOT/bin/flutter" pub get
|
|
# Keep warnings/errors fatal while allowing the repository's existing
|
|
# informational style lints to be cleaned up independently.
|
|
"$FLUTTER_ROOT/bin/flutter" analyze --no-pub --no-fatal-infos
|
|
'''
|
|
retry(3) {
|
|
sh '"$FLUTTER_ROOT/bin/flutter" test --no-pub'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Build arm64 Internal Release') {
|
|
steps {
|
|
dir('frontend') {
|
|
retry(3) {
|
|
sh '''
|
|
set -euo pipefail
|
|
"$FLUTTER_ROOT/bin/flutter" build apk \
|
|
--flavor internal --release --target-platform android-arm64 \
|
|
--build-name "$APP_VERSION" --build-number "$ANDROID_VERSION_CODE" \
|
|
--dart-define=INTERNAL_BUILD=true \
|
|
--dart-define="API_BASE_URL=$API_BASE_URL" \
|
|
--dart-define="APP_VERSION=$IMMUTABLE_TAG"
|
|
source_apk=build/app/outputs/flutter-apk/app-internal-release.apk
|
|
test -s "$source_apk"
|
|
cp "$source_apk" "$APK_PATH"
|
|
'''
|
|
}
|
|
}
|
|
sh '''
|
|
set -euo pipefail
|
|
"$ANDROID_HOME/build-tools/36.1.0/apksigner" verify --verbose "$APK_PATH"
|
|
badging=$("$ANDROID_HOME/build-tools/36.1.0/aapt2" dump badging "$APK_PATH")
|
|
printf '%s\n' "$badging" | grep -q "package: name='com.nx.miaoji.internal'"
|
|
printf '%s\n' "$badging" | grep -q "versionCode='$ANDROID_VERSION_CODE'"
|
|
"$JAVA_HOME/bin/jar" tf "$APK_PATH" >artifacts/apk-files.txt
|
|
grep -q 'lib/arm64-v8a/' artifacts/apk-files.txt
|
|
if grep -Eq 'lib/(x86|x86_64|armeabi-v7a)/' artifacts/apk-files.txt; then
|
|
echo 'APK contains a non-arm64 native ABI' >&2
|
|
exit 1
|
|
fi
|
|
sha256sum "$APK_PATH" >"$APK_PATH.sha256"
|
|
apk_sha=$(awk '{print $1}' "$APK_PATH.sha256"); export apk_sha
|
|
python3 - <<'PY'
|
|
import json, os, pathlib, subprocess
|
|
path = pathlib.Path(os.environ['MANIFEST_PATH'])
|
|
payload = {
|
|
'project':'jizhang','buildNumber':os.environ['BUILD_NUMBER'],
|
|
'commit':subprocess.check_output(['git','rev-parse','HEAD'],text=True).strip(),
|
|
'productVersion':os.environ['APP_VERSION'],'versionName':os.environ['APP_VERSION']+'-internal',
|
|
'versionCode':int(os.environ['ANDROID_VERSION_CODE']),'displayVersion':os.environ['IMMUTABLE_TAG'],
|
|
'apiBaseUrl':os.environ['API_BASE_URL'],'abi':'arm64-v8a',
|
|
'apk':pathlib.Path(os.environ['APK_PATH']).name,'apkSha256':os.environ['apk_sha']
|
|
}
|
|
path.write_text(json.dumps(payload,ensure_ascii=False,indent=2)+chr(10),encoding='utf-8')
|
|
PY
|
|
sha256sum "$MANIFEST_PATH" >"$MANIFEST_PATH.sha256"
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Upload Artifacts') {
|
|
steps {
|
|
withCredentials([usernamePassword(credentialsId: "${OPENLIST_CREDENTIALS}", usernameVariable: 'OPENLIST_USERNAME', passwordVariable: 'OPENLIST_PASSWORD')]) {
|
|
sh '''
|
|
set -euo pipefail
|
|
for artifact in "$APK_PATH" "$APK_PATH.sha256" "$MANIFEST_PATH" "$MANIFEST_PATH.sha256"; do
|
|
./scripts/upload-openlist-artifact.sh "$artifact" "$OPENLIST_REMOTE_DIR"
|
|
done
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
success { archiveArtifacts artifacts: 'artifacts/*.apk,artifacts/*.sha256,artifacts/*.json', fingerprint: true }
|
|
always {
|
|
script {
|
|
// A build can be aborted while still waiting for an executor. In that
|
|
// case Jenkins has no FilePath context and cleanWs would mask the
|
|
// original result with MissingContextVariableException.
|
|
if (env.NODE_NAME?.trim()) {
|
|
cleanWs(deleteDirs: true, notFailBuild: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|