Pages

October 21, 2013

[Unity] Jenkins로 유니티 자동 빌드 세팅하기 (3) - ios 앱 빌드 준비 과정

이번에는 유니티에서 생성된 iOS를 프로젝트를 빌드하고 코드 사이닝을 하는 과정을 Jenkins로 자동화하는 과정을 살펴보자. 몇일간 iOS 프로젝트 빌드를 위해 상당한 시간을 소비했는데 일단 코드 사이닝과정이 안드로이드 버전의 그것에 비해 상당한 iOS앱 배포에 대한 이해를 요구하여서 골치가 좀 아팠지만 어쨌든 자동 빌드 세팅에 결국 성공했다.

차근 차근 과정을 짚어가면서 정리해보도록 하겠다. 그리고 이번 튜토리얼을 쓰면서 유니티에 구현한 빌드 스크립트를 공개하도록 하겠다.

일단 유니티에서 iOS버전으로 빌드를 하면 안드로이드와는 달리 빌드된 바이너리가 생성되지 않고 Xcode 프로젝트와 소스코드가 프로젝트 디렉토리에 생성된다. 이것을 다시 Xcode로 빌드하고 코드 사인하여 최종 앱 바이너리를 만들어 내게 된다.

따라서 Jenkins에서도 iOS 앱 빌드를 위해서는 유니티에서 iOS 타겟으로 빌드하고 다시 생성된 Xcode 프로젝트를 Xcode 커맨드 라인 빌드 툴을 통해 빌드하고 사이닝하는 절차를 진행해주어야 한다.

우선 Xcode 5와 Unity3D 4.2.1은 궁합이 맞질않는다. Xcode 5를 사용해서 빌드를 진행하고자 한다면 Untity3D를 4.2.2로 업데이트를 해야 한다. 

  •  이 튜토리얼은 Xcode 5와 Unity3D 4.2.2버전을 기준으로 진행하도록 한다.
  •  Xcode를 사용하여 iOS앱 빌드를 하는과정은 준비되어 있다고 가정하고 설명을 진행하도록 한다. (Xcode및 관련 Library 설치, 키 파일 생성 및 인증서, Provisioning Profile 등에 대한 과정은 다른 블로그를 참조하도록 하자.)

유니티에서 생성된 Xcode 프로젝트를 Jenkins에서 빌드하도록 하는 방법은 크게 두가지가 있는데 Build step에서  Jenkins xcode plugin을 이용하는 방법과 쉘 스크립트를 작성하여 직접 모든 빌드 명령을 구현하는 방법 두가지가 있다.

내가 사용한 방법은 Jenkins xcode 플러그인  이용하여 빌드에 성공했으므로 이 방법 위주로 설명하고 시간이 나면 쉘스크립트를 통해 빌드하는 방법도 시도해보고 성공하면 결과를 다시 포스팅하도록 하겠다.

Add Unity build script for iOS


이전 글에서는 Android 빌드 과정을 중심으로 설명했는데 이번에는 iOS버전으로 빌드가 되어야 하므로 유니티 빌드 스크립트에 iOS타겟의 빌드 과정을 추가하도록 하자.  이전 글에서 유니티 빌드 스크립트를 유니티 프로젝트 디렉토리의 Assets/Editor/ProjectBuilder.cs라는 파일로 생성했는데 이 스크립트를 iOS 빌드를 지원할 수 있도록 수정하였다.

using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;


class ProjectBuilder {
    static string[] SCENES = FindEnabledEditorScenes();
    static string TARGET_DIR = "build";

    [MenuItem ("Custom/CI/Build iOS Debug")]
    static void PerformiOSDebugBuild ()
    {
         BuildOptions opt = BuildOptions.SymlinkLibraries |
                            BuildOptions.Development |
                            BuildOptions.ConnectWithProfiler |
                            BuildOptions.AllowDebugging |
                            BuildOptions.Development;         

         PlayerSettings.iOS.sdkVersion = iOSSdkVersion.DeviceSDK; 
         PlayerSettings.iOS.targetOSVersion = iOSTargetOSVersion.iOS_4_3;
         PlayerSettings.statusBarHidden = true;

         char sep = Path.DirectorySeparatorChar;
         string buildDirectory = Path.GetFullPath(".") + sep + TARGET_DIR;
         Directory.CreateDirectory(buildDirectory);

         string BUILD_TARGET_PATH = buildDirectory + "/ios";
         Directory.CreateDirectory(BUILD_TARGET_PATH);

         GenericBuild(SCENES, BUILD_TARGET_PATH, BuildTarget.iPhone, opt);
    }

    private static string[] FindEnabledEditorScenes() {
        List<string> EditorScenes = new List<string>();
        foreach(EditorBuildSettingsScene scene in EditorBuildSettings.scenes) {
           if (!scene.enabled) continue;
           EditorScenes.Add(scene.path);
        }

        return EditorScenes.ToArray();
    }

    static void GenericBuild(string[] scenes, string target_path, BuildTarget build_target, BuildOptions build_options)
    {
        EditorUserBuildSettings.SwitchActiveBuildTarget(build_target);
        string res = BuildPipeline.BuildPlayer(scenes, target_path, build_target, build_options);
        if (res.Length > 0) {
            throw new Exception("BuildPlayer failure: " + res);
        }
    }
}




소스 코드를 수정 후 에는 유니티 UI에 생성되는 상단 메뉴(Custom/CI/Build iOS Debug)를 통해 빌드가 문제 없이 일어나는지 확인하도록 하자.




빌드가 성공하였다면 아래 스크린샷과 같이 유니티 프로젝트 폴더에 build/ios 폴더가 생성되고 그 안에 유니티에서 생성한 Xcode 프로젝트 파일들이 있음을 확인할 수 있다.



Install Xcode Plugin 


이제 Jenkins에 Xcode build job을 설정하기 위해  Jenkins에 xcode plugin을 설치하자.

Jenkins 첫 화면 -> Manage Jenkins -> Manage Plugin 페이지로 이동 후 Available 탭에서 Xcode integration 플러그인을 찾아서 인스톨한다.



Xcode plugin을 설치후 Manage Jenkins -> Configure System 페이지에서 Xcode Builder 항목을 찾아 현재 시스템에 설치된 Xcode의 경로가 정확한지 확인한다.  Keychains 부분은 수정할 필요가 없다.



Configure Build Project for xcode plugin

플러그인이 설치되었으면 빌드 프로젝트에 Xcode 빌드 과정을 추가하고 설정하는 과정을 알아보자.

유니티에서 빌드 스크립트를 수정하여 iOS버전 빌드 함수를 추가하였으므로 다음과 같이 Jenkins 빌드 프로젝트의 Unity3d 빌드 스텝의 커맨드 라인 옵션을 수정하여 새로 추가한 iOS 빌드 함수가 수행되도록 한다.



이제 Xcode 빌드 스텝을 추가한다. 아래와 스샷과 같이 Add build step을 클릭하면 Xcode 항목이 보인다.



Xcode 빌드 스텝을 추가하고 아래의 스크린샷과 같이 세부 항목을 설정하도록 한다.
유니티에서 프로젝트가 생성되는 폴더 등에 대한 설정은 앞에서 공개한 유니티 빌드 스크립트  소스코드를 기준으로 작성되었다.



이 설정에서 가장 중요한 것은 Unlock keychain 에 관련한 설정과 Xcode Project Directory/Build Output Directory에 관련한 설정이다.

Xcode 빌드 과정에서 코드 사이닝을 동시에 수행하기 때문에 플러그인이 시스템 키 체인에 반드시 접근할 수 있어야 한다. (코드 사이닝을 위한 키 파일 생성과 인증서 관련한 부분은 이미 처리 되었다고 가정한다.)

Xcode Project Directory/Build Output Directory 관련 부분은 빌드 스크립트에서 통상적으로 유니티가 빌드 프로젝트를 내뱉는 경로와 다른 경로를 사용하도록 했기 때문에 수정이 필요하다.

이렇게 설정을 하였으면 이제 준비는 완료되었다.

Build iOS Project


이제 Jenkins에서 빌드 Job을 실행 해 보자.  위의 과정을 잘 따라왔다면 빌드 Job을 실행 후에는 배포를 위한 ipa파일과 크래쉬 덤프 분석을 위한 dSym 파일이 생성되게 된다.

빌드 프로세스가 실행 중에는 왼쪽의 Build Executor Status에  현재 빌드 진행중 인 Job이 표시되고 이것을 클릭하면 다시 Build History를 볼 수 있고 진행 중인 Build History의 항목을 클릭하면 현재 진행 중인 프로세스의 콘솔 아웃풋을 실시간으로 확인할 수 있다.









콘솔 아웃풋 화면에서 맨 마지막에 "Finished: Success"가 찍히면 문제없이 빌드가 완료된 것이다.





위의 스크린샷은 해당 빌드 Job이 마지막으로 빌드된 결과 파일을 Testflight으로 업로드하게 설정해두어서 업로드가 완료된 후 Finished: Success가 찍힌것을 확인할 수 있다.

빌드 결과 파일들은 콘솔 로그에 보이는 것 처럼 각각


'/Users/내계정/Jenkins/Home/jobs/unity_test_build/workspace/build/ios/build/Unity-iPhone.build/demo-1.0-1.0.ipa' 

'/Users/내계정/Jenkins/Home/jobs/unity_test_build/workspace/build/ios/build/Unity-iPhone.build/demo-1.0-1.0-dSYM.zip'

경로에서 찾을 수 있다.



No end yet for iOS distribution!


이렇게 빌드 작업이 마무리 되었고 내가 원하는 결과파일을 얻었는데 이것으로 자동화 과정이 완료가 된것이라고 볼 수 있을까?

테스터를 위해 내가 만든 앱을 iOS기기에 배포를 하기 위해서는 Ad hoc provisioning profile 혹은 애플 엔터프라이즈 개발자 계정이 있다면 In House provisioning profile을 만들고 앱을 해당 profile에 맞게 code signing 과정을 거쳐야 하는데 지금까지 진행한 과정으로는 내가 지정한 provisioning profile을 지정할 수가 없다. 더욱이 Unity에서도 해당 부분에 대한 설정은 가능하지 않아 보인다. Android 앱의 경우에는 배포를 위해서 필요한  Keypair를 지정할 수 있는 것과는 큰 차이가 있다.

아직도 갈길이 더 남았다..

다음 포스트를 통해 원하는 코드 사이닝 과정을 추가하는 법과 Testflight에 빌드된 앱을 업로드 하는 방법을 살펴보도록 하자.







4 comments:

  1. 질문이 있는데요..
    해당 자료에서

    UnauthorizedAccessException: Access to the path "/User" is denied.
    at System.IO.Directory.CreateDirectoriesInternal (System.String path) [0x00064] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:131
    at System.IO.Directory.CreateDirectory (System.String path) [0x0009a] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:96
    at System.IO.DirectoryInfo.Create () [0x00000] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/DirectoryInfo.cs:141
    at (wrapper remoting-invoke-with-check) System.IO.DirectoryInfo:Create ()
    at System.IO.Directory.CreateDirectoriesInternal (System.String path) [0x00039] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:116
    at System.IO.Directory.CreateDirectory (System.String path) [0x0009a] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:96
    at System.IO.DirectoryInfo.Create () [0x00000] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/DirectoryInfo.cs:141
    at (wrapper remoting-invoke-with-check) System.IO.DirectoryInfo:Create ()
    at System.IO.Directory.CreateDirectoriesInternal (System.String path) [0x00039] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:116
    at System.IO.Directory.CreateDirectory (System.String path) [0x0009a] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:96
    at System.IO.DirectoryInfo.Create () [0x00000] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/DirectoryInfo.cs:141
    at (wrapper remoting-invoke-with-check) System.IO.DirectoryInfo:Create ()
    at System.IO.Directory.CreateDirectoriesInternal (System.String path) [0x00039] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:116
    at System.IO.Directory.CreateDirectory (System.String path) [0x0009a] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/corlib/System.IO/Directory.cs:96
    at ProjectBuilder.PerformIOSBuild () [0x0004e] in /Jenkins/slave/workspace/xcode/Assets/Editor/test.cs:32

    (Filename: Assets/Editor/test.cs Line: 32)

    executeMethod method ProjectBuilder.PerformIOSBuild threw exception.

    (Filename: /Applications/buildAgent/work/d3d49558e4d408f4/Runtime/Utilities/Argv.cpp Line: 117)


    Aborting batchmode due to failure:
    executeMethod method ProjectBuilder.PerformIOSBuild threw exception.

    ***Thread 'UnityLookForNewInputDevices' is still running!***

    (Filename: /Applications/buildAgent/work/d3d49558e4d408f4/Runtime/Threads/Thread.cpp Line: 68)

    ***Thread was not cleaned up!***

    (Filename: /Applications/buildAgent/work/d3d49558e4d408f4/Runtime/Threads/Posix/PlatformThread.cpp Line: 47)

    FATAL: Unity3d command line execution failed with status 1
    Build step 'Invoke Unity3d Editor' marked build as failure
    [OS X] restore keychains as defined in global configuration
    [xcode] $ /usr/bin/security list-keychains -s
    Finished: FAILURE

    이러한 에러가 나옵니다.
    과정은 똑같이 진행하였고 xcode빌드하는 부분까진 잘되었으나

    젠킨스연동부분설정을 다하고
    빌드시에 다음과 같은 결과가나오는데
    혹시 이부분에 해결방법좀 답변 또는 메일(angelstar21c@gmail.com)부탁드립니다.

    감사합니다.

    ReplyDelete
    Replies
    1. 빌드 결과물이 나오는 패스를 잘 못 설정하신 것 같습니다. "/User" 폴더 대신 현재 로그인한 사용자의 홈디렉토리를 타겟 패스로 잡고 진행해보시길 바랍니다. ^^

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. 안녕하세요. 좋은 정보 감사드립니다.
    계속 jenkins 공부하다가. Xcode와서 빌드하는 단계에 이르렀는데요.

    xcodebuild: error: The project 'Unity-iPhone.xcodeproj' does not contain a target named ''.

    이런 에러를 내면서 Fail 뜨네요.
    제가 Xcode를 처음 만져서 이해가 잘 안가는 부분이 많습니다. 혹시 어떤 조언을 해줄 수 있으신가요?

    ReplyDelete