上传apk jenins测试

This commit is contained in:
zhang hongbo 2026-05-09 10:17:04 +08:00
parent 0d0c1caf09
commit aa31bab121
3 changed files with 180 additions and 0 deletions

View File

@ -9,4 +9,6 @@
Pipeline_dev(
winbakeLabel: "unity_2022_3_dev", // optional node labels. Valid entries: macbakeLabel, linuxbakeLabel, winbakeLabel, syncLabel
execUnityMethod:"MyBuildTool.StartBuildAndroid_dev",
uploadEnabled: true,
uploadTokenCredentialsId: "apk-upload-token",
)

View File

@ -68,6 +68,7 @@ def call(Map config)
booleanParam(name: 'DebugBuild', defaultValue: false, description: 'To Create Debug Build for Profiling')
booleanParam(name: 'CleanCache', defaultValue: false, description: 'Force Clean Script and Package Cache')
booleanParam(name: 'CleanLibrary', defaultValue: false, description: 'Force Clean the Whole Library Folder')
booleanParam(name: 'BuildNonTuyoo', defaultValue: false, description: 'Build the without_sdk APK package')
}
options
{
@ -139,6 +140,53 @@ def call(Map config)
archiveArtifact("Android_dev")
}
}
stage("Upload APK")
{
when
{
expression { config.uploadEnabled == true }
}
options
{
timeout(10)
}
steps
{
catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE')
{
script
{
env.APK_UPLOAD_STATUS = 'FAILED'
env.APK_UPLOAD_MESSAGE = ''
env.APK_UPLOAD_FILE = ''
env.APK_UPLOAD_VERSION = ''
env.APK_UPLOAD_PACKAGE_TYPE = ''
def packageType = config.uploadPackageType ?: (params.BuildNonTuyoo ? 'without_sdk' : 'with_sdk')
try {
def uploadResult = uploadApk([
publishSubFolder: 'Android_dev',
uploadEnv: 'dev',
packageType: packageType,
uploadUrl: config.uploadUrl,
uploadTokenCredentialsId: config.uploadTokenCredentialsId,
uploadVersion: config.uploadVersion
])
env.APK_UPLOAD_STATUS = 'SUCCESS'
env.APK_UPLOAD_MESSAGE = 'APK uploaded successfully.'
env.APK_UPLOAD_FILE = uploadResult.apkName
env.APK_UPLOAD_VERSION = uploadResult.version
env.APK_UPLOAD_PACKAGE_TYPE = uploadResult.packageType
} catch (Exception ex) {
env.APK_UPLOAD_MESSAGE = ex.message ?: 'APK upload failed.'
env.APK_UPLOAD_PACKAGE_TYPE = packageType
throw ex
}
}
}
}
}
}
post
{
@ -154,6 +202,17 @@ def call(Map config)
// prod / stable
def changeString = getMainRepoChangeString(currentBuild.changeSets)
def uploadStatus = config.uploadEnabled == true ? '未执行' : '已跳过'
if (env.APK_UPLOAD_STATUS == 'SUCCESS') {
uploadStatus = env.APK_UPLOAD_VERSION ?
"成功: ${env.APK_UPLOAD_FILE} (${env.APK_UPLOAD_PACKAGE_TYPE}, version ${env.APK_UPLOAD_VERSION})" :
"成功: ${env.APK_UPLOAD_FILE} (${env.APK_UPLOAD_PACKAGE_TYPE})"
} else if (env.APK_UPLOAD_STATUS == 'FAILED') {
uploadStatus = env.APK_UPLOAD_PACKAGE_TYPE ?
"失败: ${env.APK_UPLOAD_PACKAGE_TYPE}, ${env.APK_UPLOAD_MESSAGE}" :
"失败: ${env.APK_UPLOAD_MESSAGE}"
}
//
def submoduleChangeString = ""
def submoduleStateFile = ".jenkins_submodule_state_dev.json"
@ -238,6 +297,7 @@ def call(Map config)
"- 分支: main",
"- 触发者: ${triggerUser}",
"- 耗时: ${currentBuild.durationString}${failedStage}",
"- APK 上传: ${uploadStatus}",
"\n链接:",
"- [下载链接](${env.BUILD_URL})",
changeString

118
vars/uploadApk.groovy Normal file
View File

@ -0,0 +1,118 @@
//=================================================================
// Byway Studios Inc. Confidential Information.
// Copyright Byway Studios Inc. All rights reserved
//=================================================================
def call(Map config)
{
def publishSubFolder = config.publishSubFolder
def uploadEnv = config.uploadEnv
def uploadUrl = config.uploadUrl ?: 'https://gadmin.bywaystudios.com/api/open/apk/upload/'
def uploadTokenCredentialsId = config.uploadTokenCredentialsId ?: 'apk-upload-token'
def packageType = config.packageType ?: 'with_sdk'
if (!publishSubFolder) {
error('publishSubFolder is required for APK upload.')
}
if (!uploadEnv) {
error('uploadEnv is required for APK upload.')
}
if (!(packageType in ['with_sdk', 'without_sdk'])) {
error("packageType must be with_sdk or without_sdk, got: ${packageType}")
}
def apkListOutput = bat(
returnStdout: true,
script: "@echo off\r\ndir /b /a-d /o-d \"apk\\${publishSubFolder}\\*.apk\" 2>nul"
).trim()
if (!apkListOutput) {
error("No APK found under apk/${publishSubFolder}.")
}
def apkNames = apkListOutput.readLines().collect { it.trim() }.findAll { it }
def apkName = apkNames[0]
def apkPath = "${env.WORKSPACE}\\apk\\${publishSubFolder}\\${apkName}".replace('\\', '/')
def version = config.uploadVersion ?: extractVersionFromFileName(apkName)
if (apkNames.size() > 1) {
echo "Multiple APKs found, uploading newest file: ${apkName}"
}
def curlArgs = [
"curl.exe -sS -w \"\\nHTTP_STATUS:%%{http_code}\" -X POST \"${uploadUrl}\"",
" -H \"X-Apk-Upload-Token: %APK_UPLOAD_TOKEN%\"",
" -F \"env=${uploadEnv}\"",
" -F \"packageType=${packageType}\"",
" -F \"file=@${apkPath};type=application/vnd.android.package-archive\""
]
if (version) {
curlArgs << " -F \"version=${version}\""
}
withCredentials([string(credentialsId: uploadTokenCredentialsId, variable: 'APK_UPLOAD_TOKEN')]) {
def curlOutput = bat(
returnStdout: true,
label: 'Upload APK to server',
script: "@echo off\r\n" + curlArgs.join(" ^\r\n")
).trim()
def response = parseCurlResponse(curlOutput)
if (!(response.statusCode ==~ /2\\d\\d/)) {
error("APK upload failed with HTTP ${response.statusCode}: ${truncateResponse(response.body)}")
}
echo "APK upload succeeded: HTTP ${response.statusCode}"
if (response.body) {
echo "APK upload response: ${truncateResponse(response.body)}"
}
}
return [
apkName: apkName,
version: version ?: '',
packageType: packageType,
uploadEnv: uploadEnv,
uploadUrl: uploadUrl
]
}
def extractVersionFromFileName(String fileName)
{
def matcher = fileName =~ /(?i)(?:^|[^0-9])v?(\d+(?:\.\d+){1,3})(?:[^0-9]|$)/
if (matcher.find()) {
return matcher.group(1)
}
return ''
}
def parseCurlResponse(String curlOutput)
{
def statusMarker = 'HTTP_STATUS:'
def statusIndex = curlOutput.lastIndexOf(statusMarker)
if (statusIndex < 0) {
error('APK upload returned an unexpected response without HTTP status marker.')
}
return [
body: curlOutput.substring(0, statusIndex).trim(),
statusCode: curlOutput.substring(statusIndex + statusMarker.length()).trim()
]
}
def truncateResponse(String responseBody)
{
if (!responseBody) {
return ''
}
if (responseBody.length() <= 400) {
return responseBody
}
return responseBody.take(400) + '...'
}