Jenkins实现iOS自动化打包

一、前言:为什么需要自动化打包?

在iOS项目开发中,随着项目规模增大、版本迭代频繁,手动打包会面临以下问题:

  1. 效率低下:每次打包需要等待15-30分钟,占用开发时间
  2. 环境不一致:不同开发者打包环境配置差异导致包体不一致
  3. 流程复杂:证书管理、描述文件更新、多环境配置等操作繁琐
  4. 资源浪费:开发者机器持续高负载运行影响其他工作

二、环境准备与Jenkins安装

2.1 系统环境要求

组件 版本要求 说明
macOS 10.15+ 建议使用较新版本
Java JDK 8+ Jenkins运行依赖
Xcode 最新稳定版 需包含命令行工具
Homebrew 最新版 可选,用于安装管理

2.2 Jenkins安装(两种方式)

方式一:使用Homebrew安装(推荐)

bash 复制代码
# 安装Homebrew(如未安装)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 安装Jenkins LTS版本
brew install jenkins-lts

# 启动Jenkins服务
brew services start jenkins-lts

# 查看服务状态
brew services list | grep jenkins

方式二:使用安装包安装

bash 复制代码
# 下载官方安装包
# 访问:https://jenkins.io/download/

# 或使用命令行下载
curl -L -o jenkins.pkg https://get.jenkins.io/osx/latest

# 安装
sudo installer -pkg jenkins.pkg -target /

2.3 初始配置

  1. 访问Jenkins :浏览器打开 http://localhost:8080
  2. 获取初始密码
bash 复制代码
# 密码存储位置
sudo cat /Users/Shared/Jenkins/Home/secrets/initialAdminPassword
# 或
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
  1. 安装推荐插件:选择"Install suggested plugins"
  2. 创建管理员账户:设置用户名、密码、邮箱等信息

三、iOS专用插件配置

3.1 必要插件安装

在Jenkins的"系统管理" → "插件管理"中安装以下插件:

插件名称 作用 必需性
Keychains and Provisioning Profiles Management 管理iOS证书和描述文件 必需
Xcode integration Xcode项目集成 必需
Git plugin Git源代码管理 必需
Pipeline 流水线任务支持 推荐

3.2 证书与描述文件配置

步骤一:准备Keychain文件

bash 复制代码
# 找到系统钥匙串文件
cp ~/Library/Keychains/login.keychain-db ~/Desktop/login.keychain

# 注意:需要复制并去除-db后缀才能上传到Jenkins

步骤二:准备描述文件

bash 复制代码
# 描述文件默认路径
~/Library/MobileDevice/Provisioning\ Profiles/

# 可以查看所有描述文件
ls -la ~/Library/MobileDevice/Provisioning\ Profiles/

步骤三:Jenkins插件配置

  1. 进入 Manage JenkinsKeychains and Provisioning Profiles Management
  2. 上传Keychain文件:
    • 点击"Upload Keychain"
    • 选择准备好的login.keychain文件
    • 在"Code Signing Identity"中添加证书名称(可在钥匙串访问中查看)
  3. 上传描述文件:
    • 点击"Upload Provisioning Profile"
    • 选择描述文件
    • 设置目标路径:/Users/共享用户名/Library/MobileDevice/Provisioning Profiles

四、完整自动化打包脚本

以下是一个功能完整的自动化打包脚本,支持多环境配置:

bash 复制代码
#!/bin/bash
# iOS自动化打包脚本
# 文件名:ios_auto_build.sh
# 使用方法:./ios_auto_build.sh [环境] [版本号]

# 设置语言环境,避免编码问题
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

# ============== 参数配置 ==============
# 默认配置
CONFIGURATION="Release"
EXPORT_METHOD="development"  # development, ad-hoc, app-store, enterprise
BUILD_SCHEME="YourApp"
PROJECT_NAME="YourApp"
UPLOAD_TO_PGYER=false
VERSION_OVERRIDE=""

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    case $1 in
        -e|--environment)
            case $2 in
                dev) EXPORT_METHOD="development" ;;
                adhoc) EXPORT_METHOD="ad-hoc" ;;
                appstore) EXPORT_METHOD="app-store" ;;
                enterprise) EXPORT_METHOD="enterprise" ;;
                *) echo "未知环境: $2"; exit 1 ;;
            esac
            shift 2
            ;;
        -s|--scheme)
            BUILD_SCHEME="$2"
            shift 2
            ;;
        -v|--version)
            VERSION_OVERRIDE="$2"
            shift 2
            ;;
        -u|--upload)
            UPLOAD_TO_PGYER=true
            shift
            ;;
        *)
            echo "未知参数: $1"
            echo "用法: $0 [-e dev|adhoc|appstore|enterprise] [-s scheme] [-v version] [-u]"
            exit 1
            ;;
    esac
done

# ============== 路径配置 ==============
# Jenkins工作空间路径(Jenkins环境变量)
if [ -z "$WORKSPACE" ]; then
    WORKSPACE=$(pwd)
fi

# 项目相关路径
PROJECT_PATH="$WORKSPACE"
XCODEPROJ_PATH="$PROJECT_PATH/$PROJECT_NAME.xcodeproj"
WORKSPACE_PATH="$PROJECT_PATH/$PROJECT_NAME.xcworkspace"
INFOPLIST_PATH="$PROJECT_PATH/$PROJECT_NAME/Info.plist"

# 输出路径
BUILD_DATE=$(date +%Y%m%d_%H%M%S)
ARCHIVE_NAME="${BUILD_SCHEME}_${EXPORT_METHOD}_${BUILD_DATE}.xcarchive"
IPA_NAME="${BUILD_SCHEME}_${EXPORT_METHOD}_${BUILD_DATE}.ipa"

ARCHIVE_DIR="$WORKSPACE/build/archive"
IPA_DIR="$WORKSPACE/build/ipa"
LOG_DIR="$WORKSPACE/build/logs"

# 创建目录
mkdir -p "$ARCHIVE_DIR" "$IPA_DIR" "$LOG_DIR"

# ExportOptions.plist路径
EXPORT_OPTIONS_DIR="$PROJECT_PATH/ExportOptions"
EXPORT_OPTIONS_PLIST="$EXPORT_OPTIONS_DIR/${EXPORT_METHOD}.plist"

# ============== 日志函数 ==============
log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_DIR/build.log"
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_DIR/error.log"
}

log_success() {
    echo "[SUCCESS] $(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_DIR/build.log"
}

# ============== 版本号管理 ==============
update_version_if_needed() {
    if [ ! -z "$VERSION_OVERRIDE" ]; then
        log_info "更新版本号为: $VERSION_OVERRIDE"
        
        # 更新CFBundleShortVersionString (市场版本号)
        /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION_OVERRIDE" "$INFOPLIST_PATH"
        
        # 更新CFBundleVersion (构建版本号)
        BUILD_NUMBER=$(date +%Y%m%d%H%M)
        /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$INFOPLIST_PATH"
        
        log_success "版本号更新完成: $VERSION_OVERRIDE (Build: $BUILD_NUMBER)"
    fi
}

# ============== 依赖安装 ==============
install_dependencies() {
    log_info "开始安装依赖..."
    
    # 检查是否使用CocoaPods
    if [ -f "$PROJECT_PATH/Podfile" ]; then
        log_info "检测到Podfile,执行pod install..."
        
        # 更新CocoaPods仓库(可选,首次或需要更新时)
        # pod repo update
        
        # 安装依赖
        cd "$PROJECT_PATH"
        pod install --repo-update
        
        if [ $? -eq 0 ]; then
            log_success "依赖安装成功"
        else
            log_error "依赖安装失败"
            exit 1
        fi
    else
        log_info "未检测到Podfile,跳过依赖安装"
    fi
}

# ============== 清理项目 ==============
clean_project() {
    log_info "开始清理项目..."
    
    # 清理DerivedData
    DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData"
    if [ -d "$DERIVED_DATA_PATH" ]; then
        find "$DERIVED_DATA_PATH" -name "${PROJECT_NAME}*" -type d -exec rm -rf {} + 2>/dev/null || true
        log_info "已清理DerivedData"
    fi
    
    # Xcode清理
    xcodebuild clean \
        -workspace "$WORKSPACE_PATH" \
        -scheme "$BUILD_SCHEME" \
        -configuration "$CONFIGURATION" \
        -quiet \
        2>&1 | tee -a "$LOG_DIR/clean.log"
    
    if [ ${PIPESTATUS[0]} -eq 0 ]; then
        log_success "项目清理完成"
    else
        log_error "项目清理失败"
        exit 1
    fi
}

# ============== 构建归档 ==============
build_archive() {
    log_info "开始构建归档..."
    
    # 构建归档
    xcodebuild archive \
        -workspace "$WORKSPACE_PATH" \
        -scheme "$BUILD_SCHEME" \
        -configuration "$CONFIGURATION" \
        -archivePath "$ARCHIVE_DIR/$ARCHIVE_NAME" \
        -destination "generic/platform=iOS" \
        -allowProvisioningUpdates \
        -quiet \
        2>&1 | tee -a "$LOG_DIR/archive.log"
    
    if [ ${PIPESTATUS[0]} -eq 0 ]; then
        log_success "归档构建完成: $ARCHIVE_NAME"
    else
        log_error "归档构建失败"
        exit 1
    fi
}

# ============== 导出IPA ==============
export_ipa() {
    log_info "开始导出IPA..."
    
    # 检查ExportOptions.plist是否存在
    if [ ! -f "$EXPORT_OPTIONS_PLIST" ]; then
        log_error "ExportOptions.plist文件不存在: $EXPORT_OPTIONS_PLIST"
        log_info "请先通过Xcode手动导出一次获取正确的plist文件"
        exit 1
    fi
    
    # 导出IPA
    xcodebuild -exportArchive \
        -archivePath "$ARCHIVE_DIR/$ARCHIVE_NAME" \
        -exportPath "$IPA_DIR" \
        -exportOptionsPlist "$EXPORT_OPTIONS_PLIST" \
        -allowProvisioningUpdates \
        2>&1 | tee -a "$LOG_DIR/export.log"
    
    if [ ${PIPESTATUS[0]} -eq 0 ]; then
        # 重命名IPA文件
        mv "$IPA_DIR/$BUILD_SCHEME.ipa" "$IPA_DIR/$IPA_NAME"
        log_success "IPA导出完成: $IPA_NAME"
        echo "IPA路径: $IPA_DIR/$IPA_NAME"
    else
        log_error "IPA导出失败"
        exit 1
    fi
}

# ============== 上传到分发平台 ==============
upload_to_pgyer() {
    if [ "$UPLOAD_TO_PGYER" = false ]; then
        return 0
    fi
    
    log_info "开始上传到蒲公英..."
    
    # 蒲公英API配置(请替换为自己的账号信息)
    PGYER_U_KEY="your_u_key_here"
    PGYER_API_KEY="your_api_key_here"
    IPA_PATH="$IPA_DIR/$IPA_NAME"
    
    # 检查文件是否存在
    if [ ! -f "$IPA_PATH" ]; then
        log_error "IPA文件不存在: $IPA_PATH"
        return 1
    fi
    
    # 上传到蒲公英
    curl -F "file=@$IPA_PATH" \
         -F "uKey=$PGYER_U_KEY" \
         -F "_api_key=$PGYER_API_KEY" \
         -F "installType=2" \
         -F "password=123456" \
         https://upload.pgyer.com/apiv1/app/upload \
         2>&1 | tee -a "$LOG_DIR/upload.log"
    
    if [ ${PIPESTATUS[0]} -eq 0 ]; then
        log_success "上传到蒲公英成功"
    else
        log_error "上传到蒲公英失败"
    fi
}

# ============== 生成构建报告 ==============
generate_build_report() {
    log_info "生成构建报告..."
    
    REPORT_FILE="$LOG_DIR/build_report_${BUILD_DATE}.md"
    
    cat > "$REPORT_FILE" << EOF
# iOS构建报告

## 基本信息
- 项目名称: $PROJECT_NAME
- 构建方案: $BUILD_SCHEME
- 构建环境: $EXPORT_METHOD
- 构建配置: $CONFIGURATION
- 构建时间: $(date '+%Y-%m-%d %H:%M:%S')

## 版本信息
$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFOPLIST_PATH" 2>/dev/null | awk '{print "市场版本: "$0}')
$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFOPLIST_PATH" 2>/dev/null | awk '{print "构建版本: "$0}')

## 产物信息
- 归档文件: $ARCHIVE_NAME
- IPA文件: $IPA_NAME
- IPA大小: $(du -h "$IPA_DIR/$IPA_NAME" | cut -f1)

## 构建状态
- 依赖安装: ✅ 成功
- 项目清理: ✅ 成功  
- 归档构建: ✅ 成功
- IPA导出: ✅ 成功
$(if [ "$UPLOAD_TO_PGYER" = true ]; then echo "- 蒲公英上传: ✅ 成功"; fi)

## 文件路径
- 工作空间: $WORKSPACE
- IPA目录: $IPA_DIR
- 日志目录: $LOG_DIR

## 构建日志
\`\`\`
$(tail -20 "$LOG_DIR/build.log")
\`\`\`

---
*此报告由自动化构建系统生成*
EOF
    
    log_success "构建报告已生成: $REPORT_FILE"
}

# ============== 主执行流程 ==============
main() {
    log_info "========== 开始iOS自动化打包 =========="
    log_info "项目: $PROJECT_NAME"
    log_info "方案: $BUILD_SCHEME"
    log_info "环境: $EXPORT_METHOD"
    log_info "配置: $CONFIGURATION"
    
    # 执行构建步骤
    update_version_if_needed
    install_dependencies
    clean_project
    build_archive
    export_ipa
    upload_to_pgyer
    generate_build_report
    
    log_success "========== iOS自动化打包完成 =========="
    log_success "总耗时: $SECONDS 秒"
    log_success "IPA文件: $IPA_DIR/$IPA_NAME"
    
    # 打开输出目录(仅本地运行)
    if [ -z "$JENKINS_HOME" ]; then
        open "$IPA_DIR"
    fi
    
    return 0
}

# 异常处理
trap 'log_error "构建过程被中断"; exit 1' INT TERM

# 执行主函数
main "$@"

4.1 ExportOptions.plist文件模板

创建ExportOptions目录,根据不同的导出方式创建对应的plist文件:

DevelopmentExportOptions.plist

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>compileBitcode</key>
    <false/>
    <key>destination</key>
    <string>export</string>
    <key>method</key>
    <string>development</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>com.yourcompany.yourapp</key>
        <string>Your App Development Profile</string>
    </dict>
    <key>signingCertificate</key>
    <string>iPhone Developer</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>YOUR_TEAM_ID</string>
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>

AdHocExportOptions.plist

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>compileBitcode</key>
    <false/>
    <key>destination</key>
    <string>export</string>
    <key>method</key>
    <string>ad-hoc</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>com.yourcompany.yourapp</key>
        <string>Your App AdHoc Profile</string>
    </dict>
    <key>signingCertificate</key>
    <string>iPhone Distribution</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>YOUR_TEAM_ID</string>
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>

五、Jenkins任务配置

5.1 创建自由风格任务

  1. 新建任务:选择"新建Item" → 输入任务名称 → 选择"Freestyle project"

  2. 源码管理:配置Git仓库信息

    • Repository URL: git@github.com:yourname/yourapp.git
    • Credentials: 添加SSH密钥
    • Branches to build: */main (根据实际情况)
  3. 构建触发器(可选):

    • 定时构建:H */2 * * * (每2小时构建一次)
    • GitHub hook触发

5.2 构建环境配置

在"构建环境"中勾选:

  • Keychains and Code Signing Identities
  • Mobile Provisioning Profiles

选择对应的证书和描述文件。

5.3 构建步骤配置

添加构建步骤:

  1. Execute shell
bash 复制代码
# 设置执行权限
chmod +x ./scripts/ios_auto_build.sh

# 执行打包脚本(开发环境)
./scripts/ios_auto_build.sh -e dev -s YourApp -u

# 或者使用参数化构建
./scripts/ios_auto_build.sh -e ${BUILD_ENV} -s ${SCHEME} ${UPLOAD}
  1. 构建后操作
    • Archive the artifacts: build/ipa/*.ipa
    • 邮件通知(配置SMTP服务器)

六、性能优化方案

6.1 构建速度优化策略

bash 复制代码
#!/bin/bash
# 文件名:optimized_build.sh
# 构建优化脚本

# 1. 使用DerivedData缓存
DERIVED_DATA_PATH="$HOME/Library/Developer/Xcode/DerivedData/Cache"
mkdir -p "$DERIVED_DATA_PATH"

# 2. 仅清理必要部分,避免完全清理
clean_partial() {
    # 只清理当前项目的DerivedData
    find "$DERIVED_DATA_PATH" -name "${PROJECT_NAME}*" -mtime +7 -type d -exec rm -rf {} \;
}

# 3. 并行构建(如果项目支持)
xcodebuild archive \
    -workspace "$WORKSPACE_PATH" \
    -scheme "$BUILD_SCHEME" \
    -configuration "$CONFIGURATION" \
    -archivePath "$ARCHIVE_PATH" \
    -parallelizeTargets \
    -jobs 4 \  # 根据CPU核心数调整
    -quiet

# 4. 使用ccache加速编译
export CCACHE_DIR="$HOME/.ccache"
export CCACHE_MAXSIZE=5G
export CCACHE_CPP2=true

# 检查ccache是否安装
if command -v ccache &> /dev/null; then
    export CC="ccache clang"
    export CXX="ccache clang++"
fi

6.2 Jenkins节点配置优化

  1. 增加构建节点:将构建任务分发到专用构建机
  2. 配置SSD存储:确保DerivedData在SSD上
  3. 内存优化:为Jenkins分配足够内存(4GB+)

七、常见问题解决方案

7.1 证书与签名问题

问题1:Code Signing Identity找不到

复制代码
解决:确保Keychain文件正确上传,且在Jenkins构建环境中正确选择

问题2:描述文件过期

bash 复制代码
# 自动检查描述文件过期脚本
#!/bin/bash
EXPIRY_DATE=$(security cms -D -i "profile.mobileprovision" | plutil -extract ExpirationDate xml1 -o - - | xmllint --xpath '/plist/date/text()' -)
TODAY=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

if [[ "$EXPIRY_DATE" < "$TODAY" ]]; then
    echo "描述文件已过期,请更新!"
    # 发送通知邮件
    echo "描述文件过期" | mail -s "证书警报" admin@example.com
fi

7.2 构建失败问题

问题3:xcodebuild: error: Scheme is not currently configured for the build action.

markdown 复制代码
解决:
1. 在Xcode中打开项目
2. Product → Scheme → Manage Schemes
3. 勾选对应Scheme的"Shared"复选框
4. 提交.xcscheme文件到Git仓库

问题4:Pod install失败

bash 复制代码
# 在构建前添加环境变量
export COCOAPODS_DISABLE_STATS=true  # 禁用数据统计,加速
export GIT_SSL_NO_VERIFY=true  # 如果遇到SSL问题

7.3 性能问题

问题5:构建时间过长(>10分钟)

markdown 复制代码
解决方案:
1. 启用增量编译:避免每次clean
2. 使用ccache:缓存编译结果
3. 分离资源:将图片等资源放在服务器动态加载
4. 模块化:将大项目拆分成多个模块

八、监控与维护

8.1 构建监控脚本

bash 复制代码
#!/bin/bash
# 构建监控脚本
# monitor_builds.sh

JENKINS_URL="http://localhost:8080"
API_TOKEN="your_api_token"
JOB_NAME="iOS-Build"

# 获取最近构建状态
check_build_status() {
    local response=$(curl -s -u "admin:$API_TOKEN" \
        "$JENKINS_URL/job/$JOB_NAME/lastBuild/api/json")
    
    local result=$(echo "$response" | grep -o '"result":"[^"]*"' | cut -d'"' -f4)
    local number=$(echo "$response" | grep -o '"number":[0-9]*' | cut -d':' -f2)
    local duration=$(echo "$response" | grep -o '"duration":[0-9]*' | cut -d':' -f2)
    
    echo "构建 #$number: 状态=$result, 耗时=$((duration/1000))秒"
    
    if [ "$result" = "FAILURE" ]; then
        send_alert "构建失败" "构建 #$number 失败,请检查!"
    fi
}

# 检查磁盘空间
check_disk_space() {
    local usage=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
    if [ "$usage" -gt 90 ]; then
        send_alert "磁盘空间不足" "磁盘使用率: ${usage}%"
    fi
}

# 发送警报
send_alert() {
    local subject=$1
    local message=$2
    
    # 发送邮件
    echo "$message" | mail -s "$subject" admin@example.com
    
    # 发送Slack通知(可选)
    curl -X POST -H 'Content-type: application/json' \
        --data "{\"text\":\"$subject: $message\"}" \
        https://hooks.slack.com/services/your/webhook/url
}

# 主监控循环
while true; do
    check_build_status
    check_disk_space
    sleep 300  # 每5分钟检查一次
done

8.2 定期维护任务

  1. 每周清理
bash 复制代码
# 清理旧的构建产物
find ~/.jenkins/workspace -name "*.ipa" -mtime +30 -delete
find ~/.jenkins/workspace -name "*.xcarchive" -mtime +7 -delete

# 清理DerivedData
find ~/Library/Developer/Xcode/DerivedData -name "*" -type d -mtime +30 -exec rm -rf {} +
  1. 证书更新提醒:设置日历提醒,在证书到期前30天更新

九、高级功能扩展

9.1 Jenkins Pipeline配置

创建Jenkinsfile实现声明式流水线:

groovy 复制代码
// Jenkinsfile
pipeline {
    agent any
    
    parameters {
        choice(
            name: 'BUILD_ENV',
            choices: ['dev', 'adhoc', 'appstore'],
            description: '选择构建环境'
        )
        string(
            name: 'VERSION',
            defaultValue: '',
            description: '版本号(留空使用当前)'
        )
    }
    
    environment {
        PROJECT_NAME = 'YourApp'
        SCHEME = 'YourApp'
    }
    
    stages {
        stage('代码拉取') {
            steps {
                checkout scm
            }
        }
        
        stage('依赖安装') {
            steps {
                sh 'pod install --repo-update'
            }
        }
        
        stage('构建IPA') {
            steps {
                script {
                    sh """
                    chmod +x ./scripts/ios_auto_build.sh
                    ./scripts/ios_auto_build.sh \
                        -e ${params.BUILD_ENV} \
                        -s ${SCHEME} \
                        ${params.VERSION ? "-v ${params.VERSION}" : ""} \
                        -u
                    """
                }
            }
        }
        
        stage('质量检查') {
            steps {
                // 运行单元测试
                sh 'xcodebuild test -workspace ${PROJECT_NAME}.xcworkspace -scheme ${SCHEME} -destination "platform=iOS Simulator,name=iPhone 14"'
                
                // 代码静态分析
                sh 'xcodebuild analyze -workspace ${PROJECT_NAME}.xcworkspace -scheme ${SCHEME}'
            }
        }
        
        stage('部署') {
            when {
                expression { params.BUILD_ENV == 'adhoc' || params.BUILD_ENV == 'appstore' }
            }
            steps {
                // 上传到TestFlight或App Store
                script {
                    if (params.BUILD_ENV == 'appstore') {
                        sh 'xcrun altool --upload-app -f build/ipa/*.ipa -u apple@example.com -p @keychain:App_Password'
                    }
                }
            }
        }
    }
    
    post {
        success {
            emailext (
                subject: "构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "构建成功!\n\n详情: ${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
        failure {
            emailext (
                subject: "构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "构建失败,请检查!\n\n日志: ${env.BUILD_URL}console",
                to: 'devops@example.com'
            )
        }
    }
}

9.2 多环境配置管理

创建配置文件管理不同环境:

bash 复制代码
# configs/environments/dev.config
export APP_NAME="MyApp Dev"
export BUNDLE_ID="com.company.app.dev"
export API_BASE="https://dev.api.example.com"
export UPLOAD_TO_PGYER=true

# configs/environments/prod.config  
export APP_NAME="MyApp"
export BUNDLE_ID="com.company.app"
export API_BASE="https://api.example.com"
export UPLOAD_TO_PGYER=false

在构建脚本中加载配置:

bash 复制代码
# 加载环境配置
load_environment_config() {
    local env=$1
    local config_file="$PROJECT_PATH/configs/environments/${env}.config"
    
    if [ -f "$config_file" ]; then
        source "$config_file"
        log_info "已加载环境配置: $env"
    else
        log_error "环境配置文件不存在: $config_file"
        exit 1
    fi
}

十、总结

通过本文的完整指南,你可以建立一套专业的iOS自动化打包系统,实现以下目标:

  1. 一键打包:支持多环境、多配置的自动化构建
  2. 持续集成:代码提交后自动构建、测试、部署
  3. 质量保障:集成代码检查、测试覆盖率分析
  4. 性能优化:通过缓存、并行构建等手段提升效率
  5. 监控告警:实时监控构建状态,及时发现问题

这套方案不仅适用于初创团队快速搭建CI/CD流程,也适合中大型团队优化现有构建系统。根据团队实际情况,可以灵活调整配置,逐步完善自动化流程。

后续优化建议:

  1. 容器化构建:使用Docker确保构建环境一致性
  2. 二进制缓存:将编译产物缓存到云端,加速CI/CD
  3. 自动化测试:集成UI自动化测试和性能测试
  4. 安全扫描:集成代码安全扫描工具
  5. 数据分析:收集构建数据,分析优化方向
相关推荐
2501_915909061 天前
手机崩溃日志导出的工程化体系,从系统级诊断到应用行为分析的多工具协同方法
android·ios·智能手机·小程序·uni-app·iphone·webview
2501_915106321 天前
App HTTPS 抓包实战解析,从代理调试到真实网络流量观察的完整抓包思路
网络协议·http·ios·小程序·https·uni-app·iphone
要站在顶端1 天前
iOS自动化测试全流程教程(基于WebDriverAgent+go-ios)
开发语言·ios·golang
2501_916008891 天前
深入理解 iPhone 文件管理,从沙盒结构到开发调试的多工具协同实践
android·ios·小程序·https·uni-app·iphone·webview
腾讯云qcloud07552 天前
腾讯位置商业授权iOS 轨迹SDK
macos·ios·cocoa
2501_916007472 天前
没有 Mac,如何在 Windows 上架 iOS 应用?一套可落地的工程方案
android·macos·ios·小程序·uni-app·iphone·webview
2501_915106322 天前
uni-app 上架 iOS 的完整实践,从跨端开发到稳定提交的工程路径
android·ios·小程序·uni-app·cocoa·iphone·webview
2501_916007472 天前
HTTPS工作原理与重要性:全面安全指南
网络协议·安全·ios·小程序·https·uni-app·iphone
函数的彼端2 天前
iOS Model Generator - 让 JSON 转模型变得简单高效
ios·json·cocoa