Medium - Build it, Test it, Deliver it! Complete iOS Guide on Continuous Delivery with fastlane and Jenkins

원문 - S.T.Huang

Trend 파악을 Medium 기고문 요약 포스팅 - 빌드하고 테스트하고 넘기세요! iOS 환경에서 fastlane과 젠킨스로 하는 Continuous Delivery 가이드

iOS/macOS 개발은 정말 신나는 일입니다. 여러분은 여러분야의 도메인 지식들을 얻을 수 있습니다. 여러분은 Bezier나 3D 변환같은 그래픽 기술을 배웠을 수도 있습니다. 그리고 여러분은 데이터베이스로 일하는 법을 이해할 필요가 있거나 효율적인 스키마를 설계할 필요가 있죠. 게다가 굉장한 MRC 시대에 있었던 분이라면 임베디드 시스템에서 메모리 관리를 할 줄 알아야 합니다. 이런 모든 것들이 iOS/macOS 개발을 다양하고 도전적인 분야로 만들어 줍니다.

이 포스트에서는 여러분이 알아야 할 또 다른 것에 대해 배우게 되실겁니다. Continuous Deliver(CD)와 Continuous Integration(CI) 입니다. CD는 언제라도 신뢰성있게 제품을 릴리즈하는 데 도움이 되는 소프트웨어 접근법 입니다. CD는 CI라는 단어와 함께 쓰입니다. CI 또한 소프트웨어 엔지니어링 기술입니다. CI의 의미는 시스템이 지속적으로 개발자의 작업을 항상 mainline에 합치는 것을 의미합니다. CI와 CD는 큰 팀은 물론 1인 개발팀에도 매우 유용합니다. 만약 여러분이 혼자 개발을 하고 있다면 CD는 더욱 큰 의미를 가질 것입니다. 모든 응용프로그램 개발자들에게 전달은 피할 수 없는 것이거든요. 그래서 이 포스트에서는 여러분의 어플리케이션을 위해서 어떻게 CD 시스템을 구축하는지에 초점을 맞출 것입니다. 다행스럽게도 모든 기법들은 CI 시스템 구축에도 적용될 수 있습니다.

우리가 Brewer 이라는 이름의 iOS 앱을 개발한다고 칩시다. 그러면 우리의 작업과정은 꽤나 간단해보일 겁니다.

먼저 우리는 개발합니다. 그리고 QA팀이 수동적으로 테스트를 합니다. QA팀이 테스트빌드를 승인하면 우리가 릴리즈합니다.(앱스토어에 리뷰를 신청하는 것) 여러 단계에서 우리는 다른 환경을 가지고 있습니다. 개발을 하는 동안에는 staging환경에서 낮이고 밤이고 앱을 빌드합니다. QA팀이 테스트를 할 때는 Production 환경에서 앱을 빌드할 준비를 해야합니다. QA팀을 위한 이 작업은 몇주가 걸릴 수도 있습니다. 마지막에 우리는 Production 환경에서 앱을 제출합니다. 이런 마지막 빌드는 언제 이뤄질 지 전혀 예측할 수 없죠.

전달 부분을 좀 더 깊게 살펴봅시다. 여러분은 우리가 테스트 앱 빌드 시기에 많은 중복작업을 한다는 것을 알 수 있으실 겁니다. 여기서 CD 시스템이 우리를 도와주는 것입니다. 특히나 CD시스템은 다음과 같은 작업에 도움이 됩니다.

  1. 다른 환경에서 앱을 빌드하는 것 (staging/production)
  2. 우리가 선책한 환경에 코드가 서명되는 것
  3. Crashlytics나 TestFlight같은 분산 플랫폼으로 앱이 추출되어 보내지는 것
  4. 특정 일정에 맞춰 앱이 빌드되게 하는 것

Outline

다음은 우리가 이 포스트에서 수행할 것들입니다.

  • 프로젝트 설정: 다양한 환경에 맞게 프로젝트가 바뀌도록 설정하는 법
  • 수동 코드 서명: 인증서와 프로파일 프로비저닝을 수동으로 처리하는 법
  • 독립 환경: 독립된 환경에서 번들러를 사용하는 법
  • fastlane으로 빌드: fastlane을 사용하여 앱을 빌드하고 추출하는 법
  • 젠킨스가 오늘 밤 도와드립니다: 젠킨스로 여러분의 예약작업을 수행하는 법

시작하기 전에 여러분이 확인하고 싶으실 수도 있겠네요

여러분이 바쁘신 분이라도 걱정마세요 제가 Brewer app 저장소를 만들어서 스크립트 예제를 올려놨습니다. Koromiko/Brewer

자 그럼 시작해봅시다!

Setup your project

우리는 개발자 테스트 단계에서 주로 개발 서버나 staging 서버에 연결합니다. 우리는 또한 QA팀이나 Appstore에 앱을 릴리즈하기 위해 production 서버에 연결해야 할 때도 있죠. 코드를 수정해서 서버를 바꾸는 것은 좋은 생각이 아닙니다. 다음은 우리가 Xcode에서 빌드 설정과 컴파일러 플래그를 사용하는 예시 입니다. 설정을 깊게 살펴볼 것은 아니기 때문에 설정에 관심이 있다면 다음의 기사를 참고하세요.

우리의 Brewer 프로젝트에서는 세가지 빌드 설정이 있습니다.

  • Staging
  • Production
  • Release 각 단계는 특정 번들러 구분자와 매칭됩니다.

우리는 플래그를 이용해 코드에 사용될 서버 환경을 설정하도록 했습니다.

그래서 우리는 다음과 같이 수행할 수 있죠.

#if PROD
  print(“We brew beer in the Production”)
#elseif STG
  print(“We brew beer in the Staging”)
#endif

이제 우리는 코드 수정 없이 빌드 설정만 바꾸면 staging/production 환경을 변경 할 수 있게 되었습니다!🎉

Sign the code manually

이 버튼은 모든 iOS/macOS 개발자에게는 잘 알려진 레드 버튼입니다. 우리는 모든 프로젝트를 이 체크박스를 없애면서 시작하죠. 그렇지만 왜 이렇게 악명이 자자할까요? 여러분은 아마 저 버튼이 여러분의 프로젝트와 시스템을 위한 인증서와 프로파일 프로비저닝을 다운로드 한다는 것을 알 수 있을 수도 있겠네요. 만약 파일이 하나라도 없으면 여러분을 위한 새로운 것을 만들어냅니다. 일인 개발자로서 여기에는 문제가 없죠. 그러나 여러분이 큰 팀이라면 원본 인증서를 갱신하다가 유효하지 않은 인증서 때문에 빌드 시스템이 멈출 수도 있습니다. 우리에게는 너무 많은 정보를 숨기고 있는 블랙박스와 같죠.

그래서 우리의 Brewer 프로젝트에서는 다음과 같은 것을 손으로 수행합니다. 우리의 설정에서는 3개의 앱 아이디가 있습니다.

  • works.sth.brewer.staging
  • works.sth.brewer.production
  • works.sth.brewer

이 포스트에서는 첫 번째와 두 번째 설정에만 초점을 맞출 것입니다. 이제 우리가 준비해야 할 것은

  • Certificate: .p12 포맷으로 AD Hoc/Appstore에 분배되는 인증서
  • 프로비저닝 프로파일: AD Hoc 분산 프로비저닝 프로파일로 두 개의 앱 구분자 들입니다. works.sth.brewer.staging works.sth.brewer.production 입니다.

우리가 p12 포맷의 인증서가 필요하다는 것을 기억하세요. 해당 형식은 다른 기계로 변환될 수 있고 인증서의 개인 키가 포함되어 있습니다.

이제 우리의 폴더 속에 다음과 같이 서명 파일이 생겼습니다.

해당 파일들은 CD 시스템에서 사용 될 것입니다. 그러니까 해당 폴더를 CD 머신에 넣어주세요. 제발 해당 파일을 프로젝트에 넣거나 프로젝트 저장소에 커밋하지 마세요. 다른 비공개 저장소에서 서명 파일을 호스트한다면 괜찮습니다.

Build with fastlane 🚀

fastlane은 개발과 배포를 자동화 해주는 도구입니다. 예를 들어 fastlane은 앱을 빌드 하고 단위 테스트를 수행하고 바이너르 파일을 Crashlytic에 업로드 하는 작업을 하나의 스크립트를 통해 수행할 수 잇습니다. 여러분은 해당 작업들을 매 단계마다 수동으로 할 필요가 없습니다.

이 프로젝트에서 우리는 다음 두 가지 목적을 달성하기 위해 fastlane을 사용할 것입니다.

  • staging 환경에서 앱을 빌드하고 릴리즈 하는 것
  • production 환경에서 앱을 빌드하고 릴리즈 하는 것

두 작업의 차이점은 오적 설정이 다른 것입니다. 공유하는 작업은 다음과 같습니다

  • 프로비저닝 파일과 인증서에 서명하는 것
  • 앱을 빌드하고 추출하는 것
  • 앱을 Crashlytics나 젠킨스에 업로드 하는 것

우리가 해야할 일을 아셨다면 바로 fastlane 스크립트를 작성할 수 있습니다. 우리의 프로젝트에서는 스위프트를 위한 fastlane 스크립트를 사용할 것입니다. 해당 스크립트는 베타버전이기 때문에 모든게 잘 동작하지만 플러그인과 예외처리가 되지 않습니다.

그러나 개발자들에게 스위프트에서 스크립트를 작성하는 것은 더욱 가독성을 높여주고 유지보수를 쉽게 할 수 있도록 해줍니다. 그리고 스위프트 스크립트를 루비 스크립트로 쉽게 변환할 수도 있죠. 그럼 시작해 봅시다.

먼저 우리의 프로젝트를 시작합니다.

bundler exec fastlane init swift

이제 fastlane/Fastfile.swift 파일을 찾으실 수 있을겁니다. 해당 스크립트에 fastfie 클래스가 있습니다. 그게 우리 메인 프로그램이죠. 해당 클래스에는 모든 메소드에는 “Lane”이 접두사로 붙습니다. 우리는 lane에 사전 정의된 동작을 추가할 수 있고 다음과 같은 명령어로 lane을 실행할 수 있습니다.

bundle exec fastlane <lane name>.

코드를 좀 채워봅시다.

class Fastfile: LaneFile {
    func developerReleaseLane() {
        desc("Create a developer release")
	package(config: Staging())
	crashlytics
    }

    func qaReleaseLane() {
        desc("Create a qa release")
        package(config: Production())
        crashlytics
    }
}

우리는 두개의 레인을 만들었습니다. 개발자릴리즈와 QA릴리즈 입니다. 해당 작업은 같은 것을 수행합니다. 특정 설정으로 패키지를 빌드하고 추출된 앱을 Crashlytics에 업로드 하는 것입니다.

양 lane에는 패키지 메소드가 있습니다. 해당 메소드의 인터페이스는 다음과 같습니다.

func package(config: Configuration) {
}

매개인자는 프로토콜 설정을 준수하는 객체입니다. 설정은 다음과 같습니다.


protocol Configuration {
    /// file name of the certificate
    var certificate: String { get }

    /// file name of the provisioning profile
    var provisioningProfile: String { get }

    /// configuration name in xcode project
    var buildConfiguration: String { get }

    /// the app id for this configuration
    var appIdentifier: String { get }

    /// export methods, such as "ad-doc" or "appstore"
    var exportMethod: String { get }
}

그리고 우리는 프로토콜을 준수하는 두개의 구조체를 만듭니다.

struct Staging: Configuration {
    var certificate = "ios_distribution"
    var provisioningProfile = "Brewer_Staging"
    var buildConfiguration = "Staging"
    var appIdentifier = "works.sth.brewer.staging"
    var exportMethod = "ad-hoc"
}

struct Production: Configuration {
    var certificate = "ios_distribution"
    var provisioningProfile = "Brewer_Production"
    var buildConfiguration = "Production"
    var appIdentifier = "works.sth.brewer.production"
    var exportMethod = "ad-hoc"
}

프로토콜을 사용해서 우리는 모든 설정이 요구된 세팅을 수행하도록 할 수 있습니다. 그리고 새로운 설정이 필요해도 패키지의 세세한 부분을 작성하지 않아도 됩니다.

그럼 package(config:)는 어떻게 생겼을까요? 먼저 파일시스템으로 부터 인증서를 가져옵니다. 우리의 코드 서명 폴더를 기억하시나요? 우리는 목적을 달성하기 위해서 importCertificate를 사용할 것입니다.

importCertificate(
    keychainName: environmentVariable(get: "KEYCHAIN_NAME"),
    keychainPassword: environmentVariable(get: "KEYCHAIN_PASSWORD"),
    certificatePath: "\(ProjectSetting.codeSigningPath)/\(config.certificate).p12",
    certificatePassword: ProjectSetting.certificatePassword
)

키체인이름은 여러분의 Keychain을 위한 이름입니다. 기본 값중 하나는 login이죠. keychainPassword는 패스워드 키체인을 위한 것이고 해당 키체인을 이용해 패스트레인이 키체인을 해제합니다. 우리가 Fastfile.swift를 저장소에 커밋할 것이므로 전달되는 코드는 해당 머신과 일치하는지 확실히 하세요. Fastfile.swift에 패스워드를 문자 그대로 넣는 것은 좋은 생각이 아닙니다. 그래서 우리는 문자를 환경변수로 대체합니다. 우리의 시스템에서 환경변수롤 다음과 같이 저장합니다.

export KEYCHAIN_NAME=”KEYCHAIN_NAME”;
export KEYCHAIN_PASSWORD=”YOUR_PASSWORD”;

Fastfile에서는 environmentVariable(get:)을 통해 환경변수를 얻어올 수 있습니다. 환경변수를 이용해서 코드속에 패스워드가 노출되지 않도록 할 수 있고 이를 통해 보안을 향상 시킬 수 있죠.

enum ProjectSetting {
    static let codeSigningPath = environmentVariable(get: "CODESIGNING_PATH")
    static let certificatePassword = environmentVariable(get: "CERTIFICATE_PASSWORD")
}

인증서를 임포팅하고 나서 프로비저닝 프로파일을 설정할 차례입니다. 우리는 updateProjectProvisioning을 사용할 것입니다.

updateProjectProvisioning(
    xcodeproj: ProjectSetting.project,
    profile: "\(ProjectSetting.codeSigningPath)/\(config.provisioningProfile).mobileprovision",
    targetFilter: "^\(ProjectSetting.target)$",
    buildConfiguration: config.buildConfiguration
)

위의 작업은 프로비저닝 프로파일을 가져와서 특정 설정으로 프로젝트 세팅을 수정할 것입니다. 프로파일 매개변수는 프로비저닝 프로파일의 위치 입니다. 여러분이 수정하기 원하는 타겟을 찾기위한 정규표현식으로 목표 필터를 사용할 수 있습니다. updateProjectProvisioning은 여러분의 프로젝트 파일을 수정합니다, 그러니까 로컬에서 수행할 때는 주의하세요. CD시스템에서 CD작업을 할 때는 저장소에 어떤 커밋도 하지 않으니 상관없습니다.

그럼 코드 서명 부분이 끝났습니다. 다음 부분은 수월하니까 조금만 더 힘내주세요!

앱을 빌드해 봅시다.

buildApp(
    workspace: ProjectSetting.workspace,
    scheme: ProjectSetting.scheme,
    clean: true,
    outputDirectory: "./",
    outputName: "\(ProjectSetting.productName).ipa",
    configuration: config.buildConfiguration,
    silent: true,
    exportMethod: config.exportMethod,
    exportOptions: [
        "signingStyle": "manual",
        "provisioningProfiles": [config.appIdentifier: config.provisioningProfile] ],
    sdk: ProjectSetting.sdk
)

buildApp은 프로젝트를 빌드하고 익스포트 해줍니다. xcodebuild를 호출하며 익스포트 옵션외에는 모든 변수가 직관적입니다. 한번 자세히 봅시다.

exportOptions: [
    "signingStyle": "manual",
    "provisioningProfiles": [config.appIdentifier: config.provisioningProfile] ]

다른 매개변수와 달리 딕셔너리로 되어 있습니다. signingStyle은 여러분이 어떻게 코드에 서명하는지 나타내는 것이며 우리는 수동으로 설정했습니다. provisioningProfiles 또한 딕셔너리 입니다. 그것은 app id와 일치하는 프로비저닝 프로파일을 매핑합니다. 이제 진짜 fastlane 설정을 끝냈습니다. 이제 여러분은 다음을 수행할 수 있습니다.

bundle exec fastlane qaRelease

아니면 이렇게 하면

bundle exec fastlane developerRelease

적절한 설정과 함께 테스트 빌드가 릴리즈 되었습니다!

Jenkins’ll be your server for tonight

젠킨스는 CI/CD 작업을 수행하는 데 도움을 주는 자동화 서버입니다. Web GUI 인터페이스로 동작하며 꽤 쉽게 커스텀 할 수 있기 때문에 애자일팀에게 좋은 선택지가 됩니다. 우리 프로젝트에서 젠킨스의 역할은 다음의 그림과 같습니다.

젠킨스는 프로젝트의 최신 코드를 가져와서 주기적으로 작업을 수행합니다. 쉘 영역에서는 우리가 이전 섹션에서 했던 작업들을 젠킨스가 실제로 수행하는 것을 볼 수 있습니다. 그러나 우리 스스로 할 필요는 없죠 젠킨스가 매끄럽게 수행해 줄겁니다.

매일 밤마다 빌드가 되도록 젠킨스의 작업을 생성해 봅시다. 먼저 우리는 “freestyle project”를 만들고 설정 페이지로 들어갑니다. 먼저 우리가 해야할 것은 Source Code Management(SCM) 설정 영역입니다.

저장소 URL은 프로젝트의 소스코드가 있는 url입니다. 만약 비공개 저장소라면 저장소에 접속하기 위한 인증서를 추가해야 합니다. 여러분은 빌드 브랜치들을 추가할 수도 있지만 보통 기본 브랜치에서 수행됩니다.

그리고 아래는 빌드 트리거 영역입니다. 이 영역에서 우리는 빌드 잡에서 어떤 트리거가 실행될 지 결정할 수 있습니다. 우리의 작업 방식에서는 평일 저녁마다 실행되길 원합니다.

그래서 우리는 Poll SCM을 확인할 겁니다. 이것은 젠킨스가 주기적으로 지정된 저장소를 체크한다는 것입니다. 스케줄 영역은 다음과 같습니다.

H 0 * * 0–4

무슨 의미일까요? 공식적인 명령어를 알아봅시다.

MINUTE HOUR DOM MONTH DOW

MINUTE Minutes within the hour (0–59)

HOUR The hour of the day (0–23)

DOM The day of the month (1–31)

MONTH The month (1–12)

DOW The day of the week (0–7) where 0 and 7 are Sunday.

해당 문법은 다섯개의 필드로 구성됩니다.

  • 시간
  • 평일

해당 필드는 숫자가 될 수 있습니다. 우리는 모든 숫자를 표현하기 위해 ‘*‘를 사용할 수 있습니다. 그리고 자동으로 숫자를 선택하는 hash를 표현하기 위해 ‘H’를 사용할 수 있습니다.

H 0 * * 0–4

따라서 우리의 스케줄은 일요일에서 목요일까지 매일 밤 자정시각에서 오전 1시 사이에 특정 분에 작업을 수행합니다.

마지막으로 중요한 것은 아래의 Build 영역입니다. 여기에는 우리가 젠킨스를 통해 실행하고자 하는 작업이 있습니다.

export LC_ALL=en_US.UTF-8;
export LANG=en_US.UTF-8;
export CODESIGNING_PATH=”/path/to/cert”;
export CERTIFICATE_PASSWORD=”xxx”;
export KEYCHAIN_NAME=”XXXXXXXX”;
export KEYCHAIN_PASSWORD=”xxxxxxxxxxxxxx”
bundle install — path vendor/bundler
bundle exec fastlane developerRelease

처음 여섯줄은 환경 변수를 설정하는 것이고 일곱번째 라인은 fastline을 포함하여 의존성을 설치하는 것입니다. 그리고 나서 마지막 라인은 developerRelease라는 lane을 실행합니다. 요약하자면 이 작업은 developerRelease를 평일 밤마다 빌드하고 업로드 하는 것입니다. 우리의 첫 빌드가 완성되었네요!🚀

여러분은 빌드 상태를 젠킨스 프로젝트 페이지의 빌드 숫자를 클릭해서 확인할 수 있습니다.

Summary

  • 개발 환경 설정 -> 빌드 -> 배포의 과정 자동화 도구 사용례
  • 인증서와 프로비저닝 프로파일, 개발 환경등만 세팅해주면 fastlane이 동작할 부분을 스크립트로 구성해둬서 젠킨스로 매일 밤 자동 빌드&배포를 수행할 수 있다.

© 2019. All rights reserved.

Powered by Hydejack v8.1.1