Generate xcarchive into a specific folder from the command line

后端 未结 7 771
南旧
南旧 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:15

    Similar to the others, but perhaps a little simpler since I try to record the .xcarchive file's location. (I also don't move the archives folder, so this will work better if you're doing multiple builds at the same time.)

    My caller build script generates a new tempfile and sets its path to an environment variable named XCARCHIVE_PATH_TMPFILE. This environment variable is available in my scheme's Archive post-action shell script, which then that writes the .xcarchive's path to that file. The build script that can then read that file after it calls xcodebuild archive.

    post-action shell script

    echo $ARCHIVE_PATH > "$XCARCHIVE_PATH_TMPFILE"
    
    0 讨论(0)
  • 2020-11-29 00:17

    Xcode 5 now supports an -archivePath option:

    xcodebuild -scheme myscheme archive -archivePath /path/to/AppName.xcarchive
    

    You can also now export a signed IPA from the archive you just built:

    xcodebuild -exportArchive -exportFormat IPA -exportProvisioningProfile my_profile_name -archivePath /path/to/AppName.xcarchive -exportPath /path/to/AppName.ipa
    
    0 讨论(0)
  • 2020-11-29 00:30

    My current solution is to rename the user's existing archives folder, run the build, and do a 'find' to copy the archives where i want, then delete the archives folder and rename the old folder back as it was, with code like this in my ruby build script:

    # Move the existing archives out of the way
    system('mv ~/Library/Developer/Xcode/Archives ~/Library/Developer/Xcode/OldArchivesTemp')
    # Build the .app, the .DSYM, and the .xcarchive
    system("xcodebuild -scheme \"#{scheme}\" clean build archive CONFIGURATION_BUILD_DIR=\"#{build_destination_folder}\"")
    # Find the xcarchive wherever it was placed and copy it where i want it
    system("find ~/Library/Developer/Xcode/Archives -name *.xcarchive -exec cp -r {} \"#{build_destination_folder}\" \";\"")
    # Delete the new archives folder with this new xcarchive
    system('rm -rf ~/Library/Developer/Xcode/Archives')
    # Put the old archives back
    system('mv ~/Library/Developer/Xcode/OldArchivesTemp ~/Library/Developer/Xcode/Archives')
    

    Its a bit hacky but i don't see a better solution currently. At least it preserves the user's 'archives' folder and all their pre-existing archives.

    --Important note!--

    I since found out that the line of code where i find the archive and cp it to the folder i want doesn't copy the symlinks inside the archive correctly, thus breaking the code signing in the app. You'll want to replace that with a 'mv' or something that maintains symlinks. Cheers!

    0 讨论(0)
  • 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` <target name> [/path/to/provisioning-profile]
          [<code sign identity]"
        exit 1
    fi
    
    if [ ! -z "$2" ]; then
        PROVISIONING_PROFILE="$2"
    fi
    
    if [ ! -z "$3" ]; then
        SIGN_PROVISIONING_PROFILE="$3"
    else
        if [ ! -z "$PROVISIONING_PROFILE" ]; then
            SIGN_PROVISIONING_PROFILE=$(cat "$PROVISIONING_PROFILE" | egrep -a -o
              '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}')
        fi
    fi
    
    
    archive "$1" "$PROVISIONING_PROFILE" "$SIGN_PROVISIONING_PROFILE"
    

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

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

    0 讨论(0)
  • 2020-11-29 00:31

    I have just solved this one - just add the argument -archivePath to your xcode build command line, given the initial question that would mean:

    xcodebuild -scheme myscheme archive
    

    becomes ...

    xcodebuild -scheme myscheme archive -archivePath Build/Archive
    

    (Note: paths are relative, I output my build to $PWD/Build)

    This will then place your .app folder in:

    Build/Archive.xarchive/Products/Application
    

    If your build target already has your signing certificate and provisioning profile in it you can then create your IPA file without re-signing using the following command:

    xcrun -v -sdk iphoneos PackageApplication -v `pwd`'/Build/Archive.xarchive/Products/Application/my.app' -o `pwd`'/myapp.ipa'
    

    (Note: xcrun doesn't like relative paths hence the pwd)

    The -v args dump lots of useful information - this command can fail to sign properly and still exit with code 0, sigh!

    If you are finding that you can't run the built .ipa it's probably a signing issue that you can do a double check on using:

    codesign --verify -vvvv myapp.app
    

    If it's signed correctly and un-tampered with the output will have this in:

    myapp.app: valid on disk
    myapp.app: satisfies its Designated Requirement
    

    If not you will see something similar to this:

    Codesign check fails : /blahpath/myapp.app: a sealed resource is missing or invalid
    file modified: /blahpath/ls-ios-develop.app/Assets.car
    

    ... which generally means you are trying to use an intermediate output directory rather than the proper archive.

    0 讨论(0)
  • 2020-11-29 00:34

    Here's a bit of bash that I've come up with for our Jenkins CI system. These commands should be run in a script immediately after the xcodebuild archive command finishes.

    BUILD_DIR="${WORKSPACE}/build"
    XCODE_SCHEME="myscheme"
    
    # Common path and partial filename
    ARCHIVE_BASEPATH="${HOME}/Library/Developer/Xcode/Archives/$(date +%Y-%m-%d)/${XCODE_SCHEME}"
    
    # Find the latest .xcarchive for the given scheme
    NEW_ARCHIVE=$(ls -td "${ARCHIVE_BASEPATH}"* | head -n 1)
    
    # Zip it up so non-Apple systems won't treat it as a dir
    pushd "${NEW_ARCHIVE%/*}"
    zip -r "${BUILD_DIR}/${NEW_ARCHIVE##*/}.zip" "${NEW_ARCHIVE##*/}"
    popd
    
    # Optional, disk cleanup
    rm -rf "${NEW_ARCHIVE}"
    

    The BUILD_DIR is used to collect artifacts so that it's easy to archive them from Jenkins with a glob such as build/*.ipa,build/*.zip

    0 讨论(0)
提交回复
热议问题