Git, CocoaPods, Xcode, Shell

[iOS - swift] 2. GitHub Action 사용 방법 (CI/CD)

jake-kim 2021. 12. 5. 23:08

1. GitHub Action 개념, 기능 (CI/CD)

2. GitHub Action 사용 방법 (CI/CD)

GitHub Action으로 할 수 있는 것

  • Push 발생되면 아래 단계들이 자동으로 실행
    ( 1. GitHub Action 개념, 기능 (CI/CD) 에서는 UnitTest까지 진행)
    • Build
    • Unit Test
    • Archive
    • ipa 생성, Git에 Artifacts 형식으로 업로드 or 앱스토어에 ipa파일 업로드

GitHub Action을 이용한 Archive

  • Archive를 하기 위해서는 Certfiicate, Provisioning Profile이 필요한데, 이 정보를 GitHub에도 업로드해주어야 Archive가능
  • Edit Scheme 클릭

  • Build Configuration 확인 -> 대상이 Release이므로 Distribution Certificate와 Distribution Provisioning Profile 필요
    • Distribution Certificate: 애플의 하드웨어에는 신뢰받은 소프트웨어만이 설치가 가능한데, 이 신뢰인증서를 의미
    • Distribution Provisioning Profile: 애플에서 앱을 구분하는 App ID정보 + 개발자 신뢰 Certificate + Device 신뢰정보
    • 구체적인 개념은 이곳 참고: https://ios-development.tistory.com/246

  • 이곳을 참고하여 Distribution 용도의 Certificate, Provisioning Profile 생성
    • (아래처럼 기존에 만들어 둔 인증서가 없는 경우)Certificate 생성하기
    • App ID 등록하기
    • Provisioning Profile 생성
  • 만약, Apple Developer에 Distribution인증서가 이미 존재한다면 해당 부분 다운로드

  • Provisioning Profile을 만들때는 App Store 클릭

  • 생성 완료 

  • .cer 파일을 두번 클릭하여 활성화 -> Keychain Access 앱 -> 인증서 탭에 활성화된 것 확인

  • 활성화된 인증서 -> 오른쪽 마우스 클릭 -> 내보내기 클릭
    • .p12파일 내보내기

  • 내보내기 하면, 암호입력이 나오는데 이 부분은 나중에 사용되므로 따록 기록

cf) .p12는 1년마다 갱신되는 인증서, .p8은 영구적 사용이 가능

  • Xcode에 Provisioning Profile 적용
    • Rarget -> Signing & Capabilities -> Release
    • Automatically manage signing 체크를 해제

  • import profiles -> 다운받은 Profiles을 선택

Profile 선택 완료

  • Target -> Build Settins에서 Code Signing Identity의 Release에도 적용

깃허브에 업로드할 파일들 Certificate, Profiles을 암호화

  • GnuGP 보안 솔루션 이용
    • M1 CPU 맥북인 경우 설치
      arch -arm64 brew install gnupg2​
    • intel CPU 맥북인 경우 설치
      brew install gnupg2

cf) M1 CPU 맥북에서 brew install gnupg2로 설치 시, "Cannot install under Rosetta 2 in ARM default prefix" 오류 발생

  • certificate와 provisioning profile을 암호화하여 깃허브에 업로드

암호화할 파일들

  • 암호화
gpg -c certs.p12
  • passphrase도 역시, .p12를 내보낼때 패스워드를 기록했듯이 나중에 사용되므로 따로 기록

  • provisioning profile도 동일하게 암호화 (위와 같이 암호 따로 기록해 놓을 것)

  • 암호화된 파일 생성 완료



암호화된 certificate, profiles을 깃허브에 업로드하기 위해 필요한 환경변수 설정

  • Github -> Settings 탭

  • Secrets -> New repository secret 클릭

  • 위에서 기록했었던 것들 추가
    • CERTS_EXPORT_PWD: .cer파일을 .p12로 내보내기할때 사용된 패스워드
    • CERTS_ENCRYPTO_PWD: gpg로 .p12파일을 암호화할때 사용된 패스워드
    • PROFILES_ENCRYPTO_PWD: gpg로 provisioning profiles 파일을 암호화할때 사용된 패스워드

  • 생성한 3개 secrets 변수

  • 암호화된 certificate, profiles을 깃허브에 업로드
    • .github하위에 secrets폴더를 만든 후 .gpg파일 저장

ExportOptions.plist 파일 생성

  • 원래 xcode에서 Archive하면 생성되는 파일이므로, Archive 시도
    • Any iOS Device로 선택
    • Xcode > project > Arhive
    • Distribution App
    • Export 체크 후 Next 연속
    • 완료 시 ExportOptions.plist 파일 생성
  • ExportOptions.plist파일을 생성하여, 프로젝트가 존재하는 곳에 복사
    • 루트가 아닌 다른곳에 복사할 경우 오류 발생하므로 주의

GitHub Action에서 정의한 .yml파일 수정

  • .yml 파일에 환경변수를 이용하여 unit 테스트까지 되게끔 수정
  • GitHub Action으로 기존에 만들어놓은 .yml파일 오픈
     
  • unit 테스트까지 되게끔 .yml파일 수정
    • 만약 cocoapods을 사용하여, .xcworkspace파일이 존재하는 경우, -project부분을 -workspace로 수정 필요
      (이부분은 아래 전체 .yml코드에서 주석으로 된 부분 확인)
name: Swift

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: macos-latest
    env: 
      XC_VERSION: ${{ '13.1' }}
      XC_PROJECT: ${{ 'ExGithubAction.xcodeproj' }}
      XC_SCHEME: ${{ 'ExGithubAction' }}
    steps:
    - name: Select latest Xcode
      run: "sudo xcode-select -s /Applications/Xcode_$XC_VERSION.app"
    - uses: actions/checkout@v2
    - name: Build
      run: echo Hello, world!
    - name: Run tests
      run: | 
        xcodebuild test -project "$XC_PROJECT" -scheme "$XC_SCHEME" -destination 'platform=iOS Simulator,name=iPhone 13'

GitHub에서 certificate, provisioning profile 사용되게끔하는 방법

1. GitHub 가상머신에 keychain 생성하는 스텝 추가

  • env 추가
    	KEYCHAIN: ${{ 'test.keychain' }}​
  • step추가 - .yml파일에 security라는 명령어 사용
    - name: Configure Keychain 
      run: | 
        security create-keychain -p "" "$KEYCHAIN" 
        security list-keychains -s "$KEYCHAIN" 
        security default-keychain -s "$KEYCHAIN" 
        security unlock-keychain -p "" "$KEYCHAIN"

2. 암호화한 certificate, provisioning profile을 decrypt

  • env 추가 (이전에 암호화된 파일 certificate와 profiles와 이 파일들이 복호화 했을때 어디에 저장할지 path 입력)
    ENCRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certs.p12.gpg' }}
    DECRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certs.p12' }} # 어디에 복호화 할 것인지 명시
    
    ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/ExGithubAction.mobileprovision.gpg' }}
    DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/ExGithubAction.mobileprovision' }} # 어디에 복호화 할 것인지 명시
  • env 추가 입력 (위 .pgp파일들의 패스워드와 .cet 인증서를 .p12로 내보내기할 때 사용한 패스워드 값)
  • github에 입력했던 Setting -> secrets에 입력하면, secrets.{입력한 변수}로 접근
    .yml파일에서 secrets.CERTS_ENCRYPTO_PWD로 접근 가능
    # 주의할점: 다른 env 변수이름은 작은 따옴표를 붙이지만, github의 secrets에 접근할 땐 작은 따옴표 붙이지 않음
    
    CERTS_EXPORT_PWD: ${{ secrets.CERTS_EXPORT_PWD }}
    CERTS_ENCRYPTION_PWD: ${{ secrets.CERTS_ENCRYPTO_PWD }}
    PROFILES_ENCRYPTO_PWD: ${{ secrets.PROVISION_ENCRYPTO_PWD }}​
     
  •  gpg파일을 복호화하여 keychain에 certificates, provisioning profile 저장 스크립트 작성
    • gpg의 -d 옵션: dectypt
    • gpg의 -o 옵션: output
    • 기타 gpg 옵션 참고: https://gnupg.org/documentation/manpage.html
      - name : Configure Code Signing
        run: | 
          gpg -d -o "$DECRYPTED_CERTS_FILE_PATH" --pinentry-mode=loopback --passphrase "$CERTS_ENCRYPTION_PWD" "$ENCRYPTED_CERTS_FILE_PATH"
          gpg -d -o "$DECRYPTED_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$PROFILES_ENCRYPTO_PWD" "$ENCRYPTED_PROVISION_FILE_PATH"
          security import "$DECRYPTED_CERTS_FILE_PATH" -k "$KEYCHAIN" -P "$CERTS_EXPORT_PWD" -A
          security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN"
          mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
      
          echo `ls .github/secrets/*.mobileprovision`
          # 프로파일들을 rename하고 새로만든 디렉토리에 복사
          for PROVISION in `ls .github/secrets/*.mobileprovision`
            do
              UUID=`/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i ./$PROVISION)`
            cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision"
            done

3. 앱 빌드 스크립트 작성

  • env
    XC_ARCHIVE_PATH: ${{ 'ExGithubAction.xcarchive' }}​
  • 빌드
        - name: Archive
          run: | 
            mkdir artifacts
            xcodebuild archive -project "$XC_PROJECT" -scheme "$XC_SCHEME" -configuration release -archivePath "$XC_ARCHIVE_PATH"

4. Archive와 Export

  • env
    XC_EXPORT_PATH: ${{ './artifacts' }}​
  •  Archive
    • 주의: ExportOptions.plist 파일은 "-archivePath" 없이 그냥 선언해주지 않으면 오류 발생
      - name: Export for App Store
        run: | 
          xcodebuild -exportArchive -archivePath "$XC_ARCHIVE_PATH" -exportOptionsPlist ExportOptions.plist -exportPath "$XC_EXPORT_PATH"

5. Artifact 업로드

env: PROJECT_ROOT_PATH: ${{ 'ExGithubAction' }}

...

- name: Upload Artifact
  uses: actions/upload-artifact@v2
  with:
    name: Artifacts
    path: ./artifacts

.yml 전체 코드

name: Swift

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: macos-latest
    env: 
      XC_VERSION: ${{ '13.1' }}
      XC_PROJECT: ${{ 'ExGithubAction.xcodeproj' }}
      XC_SCHEME: ${{ 'ExGithubAction' }}
      KEYCHAIN: ${{ 'test.keychain' }} # GitHub가상 머신에 키체인 생성할때의 이름

      PROJECT_ROOT_PATH: ${{ 'ExGithubAction' }}

      ENCRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certs.p12.gpg' }}
      DECRYPTED_CERTS_FILE_PATH: ${{ '.github/secrets/certs.p12' }} # 어디에 복호화 할 것인지 명시

      ENCRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/ExGithubAction.mobileprovision.gpg' }}
      DECRYPTED_PROVISION_FILE_PATH: ${{ '.github/secrets/ExGithubAction.mobileprovision' }} # 어디에 복호화 할 것인지 명시

      CERTS_EXPORT_PWD: ${{ secrets.CERTS_EXPORT_PWD }}
      CERTS_ENCRYPTION_PWD: ${{ secrets.CERTS_ENCRYPTO_PWD }}
      PROFILES_ENCRYPTO_PWD: ${{ secrets.PROFILES_ENCRYPTO_PWD }}

      XC_ARCHIVE_PATH: ${{ 'ExGithubAction.xcarchive' }}
      XC_EXPORT_PATH: ${{ './artifacts' }}

    steps:
    - name: Select latest Xcode
      run: "sudo xcode-select -s /Applications/Xcode_$XC_VERSION.app"

    - uses: actions/checkout@v2

    - name: Build
      run: echo Hello, world!

    - name: Run tests
      run: | 
        xcodebuild test -project "$XC_PROJECT" -scheme "$XC_SCHEME" -destination 'platform=iOS Simulator,name=iPhone 13'

    - name: Configure Keychain 
      run: | 
        security create-keychain -p "" "$KEYCHAIN" 
        security list-keychains -s "$KEYCHAIN" 
        security default-keychain -s "$KEYCHAIN" 
        security unlock-keychain -p "" "$KEYCHAIN"

    - name : Configure Code Signing
      run: | 
        gpg -d -o "$DECRYPTED_CERTS_FILE_PATH" --pinentry-mode=loopback --passphrase "$CERTS_ENCRYPTION_PWD" "$ENCRYPTED_CERTS_FILE_PATH"
        gpg -d -o "$DECRYPTED_PROVISION_FILE_PATH" --pinentry-mode=loopback --passphrase "$PROFILES_ENCRYPTO_PWD" "$ENCRYPTED_PROVISION_FILE_PATH"
        security import "$DECRYPTED_CERTS_FILE_PATH" -k "$KEYCHAIN" -P "$CERTS_EXPORT_PWD" -A
        security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN"
        mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"

        echo `ls .github/secrets/*.mobileprovision`
        # 프로파일들을 rename하고 새로만든 디렉토리에 복사
        for PROVISION in `ls .github/secrets/*.mobileprovision`
          do
            UUID=`/usr/libexec/PlistBuddy -c 'Print :UUID' /dev/stdin <<< $(security cms -D -i ./$PROVISION)`
          cp "./$PROVISION" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision"
          done

    - name: Archive
      run: | 
        mkdir artifacts
        xcodebuild archive -project "$XC_PROJECT" -scheme "$XC_SCHEME" -configuration release -archivePath "$XC_ARCHIVE_PATH"

    # Cocoapods을 사용 할 경우
    # - name: Archive
    #   run: |
    #     pod install --repo-update --clean-install --project-directory="$PROJECT_ROOT_PATH"/
    #     xcodebuild clean archive -workspace "$XC_WORKSPACE" -scheme "$XC_SCHEME" -configuration release -archivePath "$XC_ARCHIVE"

    - name: Export for App Store
      run: | 
        xcodebuild -exportArchive -archivePath "$XC_ARCHIVE_PATH" -exportOptionsPlist ExportOptions.plist -exportPath "$XC_EXPORT_PATH"

    - name: Upload Artifact
      uses: actions/upload-artifact@v2
      with:
        name: Artifacts
        path: ./artifacts

작성 후 Push하면 Artifacts까지 업로드 완료

  • Build
  • Unit Test
  • Archive
  • ipa 생성, Git에 Artifacts 형식으로 업로드 or 앱스토어에 ipa파일 업로드

성공

  • artifacts 업로드도 성공

 

* 전체 소스 코드: https://github.com/JK0369/ExGithubAction

 

* 참고

- https://medium.com/@karaiskc/archive-and-export-ios-app-with-github-actions-b44f676e4bf9

- https://gnupg.org/documentation/manpage.html

- https://stackoverflow.com/questions/45748140/xcode-9-distribution-build-fails-because-format-of-exportoptions-plist-has-chang

- https://blog.dalso.org/article/m1-mac-brew-error-cannot-install-in-homebrew-on-arm-processor-in-intel-default-prefix

- https://zeddios.tistory.com/1033

- https://blog.dalso.org/article/m1-mac-brew-error-cannot-install-in-homebrew-on-arm-processor-in-intel-default-prefix