Generate xcarchive into a specific folder from the command line

后端 未结 7 787
南旧
南旧 2020-11-28 23:58

For the purposes of CI, I need to be able to generate an XCARCHIVE and an IPA file in our nightly build. The IPA is for our testers, to be signed with our ad-hoc keys, and t

7条回答
  •  [愿得一人]
    2020-11-29 00:30

    On Xcode 4.6 it is possible to specify a post-build action for the scheme to be compiled into an xcarchive:

    echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh
    

    A build script can be used to check if $ARCHIVE_PATH is defined after running xcodebuild and if this is the case, the output xcarchive can be moved into a designated folder.

    This method is not very maintainable if the targets in the project are a large number, as for each one it is necessary to tag the corresponding scheme as 'shared' and add the post-build action.

    To address this problem, I have created a build script that generates the archive path programmatically by extracting the last build that matches the target name on the current day. This method works reliably as long as there aren't multiple builds with the same target name running on the machine (this may be a problem in production environments where multiple concurrent builds are run).

    #!/bin/bash
    #
    # Script to archive an existing xcode project to a target location.
    # The script checks for a post-build action that defines the $ARCHIVE_PATH as follows:
    # echo "ARCHIVE_PATH=\"$ARCHIVE_PATH\"" > $PROJECT_DIR/archive_paths.sh
    # If such post-build action does not exist or sourcing it doesn't define the $ARCHIVE_PATH   
    # variable, the script tries to generate it programmatically by finding the latest build
    # in the expected archiving folder
    #
    
    post_build_script=archive_paths.sh
    build_errors_file=build_errors.log
    OUTPUT=output/
    XCODEBUILD_CMD='/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild'
    TARGET_SDK=iphoneos
    
    function archive()
    {
        echo "Archiving target '$1'"
    
        # Delete $post_build_script if it already exists as it should be generated by a 
        # post-build action
        rm -f $post_build_script
    
        # Use custom provisioning profile and code sign identity if specified, otherwise
        # default to project settings
        # Note: xcodebuild always returns 0 even if the build failed. We look for failure in
        # the stderr output instead
        if [[ ! -z "$2" ]] && [[ ! -z "$3" ]]; then 
            ${XCODEBUILD_CMD} clean archive -scheme $1 -sdk "${TARGET_SDK}" \
            "CODE_SIGN_IDENTITY=$3" "PROVISIONING_PROFILE=$2" 2>$build_errors_file
        else
            ${XCODEBUILD_CMD} clean archive -scheme $1 -sdk "${TARGET_SDK}"
            2>$build_errors_file  
        fi
    
        errors=`grep -wc "The following build commands failed" $build_errors_file`
        if [ "$errors" != "0" ]
        then
            echo "BUILD FAILED. Error Log:"
            cat $build_errors_file
            rm $build_errors_file
            exit 1
        fi
        rm $build_errors_file
    
        # Check if archive_paths.sh exists
        if [ -f "$post_build_script" ]; then
            source "$post_build_script"
            if [ -z "$ARCHIVE_PATH" ]; then
                echo "'$post_build_script' exists but ARCHIVE_PATH was not set.
                  Enabling auto-detection" 
            fi
        fi
        if [ -z "$ARCHIVE_PATH" ]; then
            # This is the format of the xcarchive path:
            # /Users/$USER/Library/Developer/Xcode/Archives/`date +%Y-%m-%d`/$1\ 
            # `date +%d-%m-%Y\ %H.%M`.xcarchive
            # In order to avoid mismatches with the hour/minute of creation of the archive and
            # the current time, we list all archives with the correct target that have been
            # built in the current day (this may fail if the build wraps around midnight) and
            # fetch the correct file with a combination of ls and grep.
            # This script can break only if there are multiple targets with exactly the same
            # name running at the same time.
            EXTRACTED_LINE=$(ls -lrt /Users/$USER/Library/Developer/Xcode/Archives/`date
              +%Y-%m-%d`/ | grep $1\ `date +%d-%m-%Y` | tail -n 1)
            if [ "$EXTRACTED_LINE" == "" ]; then
                echo "Error: couldn't fetch archive path"
                exit 1
            fi
            # ls -lrt prints lines with the following format
            # drwxr-xr-x  5 mario  1306712193  170 25 Jul 17:17 ArchiveTest 25-07-2013
            #   17.17.xcarchive
            # We can split this line with the " " separator and take the latest bit:
            #   17.17.xcarchive
            FILE_NAME_SUFFIX=$(echo $EXTRACTED_LINE | awk '{split($0,a," "); print a[11]}')
            if [ "$FILE_NAME_SUFFIX" == "" ]; then
                echo "Error: couldn't fetch archive path"
                exit 1
            fi
            # Finally, we can put everything together to generate the path to the xcarchive
            ARCHIVE_PATH="/Users/$USER/Library/Developer/Xcode/Archives/`date 
              +%Y-%m-%d`/$1 `date +%d-%m-%Y` $FILE_NAME_SUFFIX/"
        fi
    
        # Create output folder if it doesn't already exist
        mkdir -p "$OUTPUT"
    
        # Move archived xcarchive build to designated output folder
        mv -v "$ARCHIVE_PATH" "$OUTPUT"
    }
    
    
    # Check number of command line args
    if [ $# -lt 1 ]; then
        echo "Syntax: `basename $0`  [/path/to/provisioning-profile]
          [

    Full source code with an example Xcode project can be found here:

    https://github.com/bizz84/Xcode-xcarchive-command

提交回复
热议问题