본문 바로가기

💻 내 소개 안녕하세요 엄청짱 프로그래머 손다빈 입니다.
  • 나이 : 96년생
  • 특이사항 : 조금씩 늙어가고 있음
  • 좋아하는 음식 : 햄부기, 치킨, 고기

🥷기술
Unity
Godot
Cpp
Javascript
D3
Vue

🐱 우리집 고양이 소개
츄르 먹은 후 츄르 먹기 전
  • 이름 : 콜라
  • 나이 : 14년생
  • 종 : Nado moreum

📱 개인 프로젝트
🏢 참여한 프로젝트
🌱 내 잔디밭

Unity Jenkins를 이용한 빌드 자동화 구축 과정 정리 본문

글 묶음/내 밥줄 Unity, C#

Unity Jenkins를 이용한 빌드 자동화 구축 과정 정리

초긍정 개발자 다빈맨 2025. 2. 16. 00:33

 

회사에서 Jenkins를 쓰기는 하는데 아예 처음 부터 구축해 본 경험은 없어서 한번 해볼까 싶은 마음에 집에 남는 맥으로 첨부터 구축해보면서 과정을 정리해보았습니다.

 

| 젠킨스 설치 및 세팅

더보기
  1. https://www.jenkins.io/download/ 젠킨스 다운로드 홈페이지 접속합니다.
  2. 홈페이지 하단에 MacOS 설치하기 선택하면 가이드 링크로 넘어가집니다. 가이드에 있는 그대로 따라합니다.
  3. 가이드에 따라 터미널에서 다음 명령어를 입력해 설치합니다.
    ※ homebrew가 설치되어 있지 않으면 아래 명령어 동작 안할 수 있으므로 선행 설치 필요합니다.
brew install jenkins-lts

터미널에서 설치 끝나면 위랑 비슷하게 나올겁니다.

brew services start jenkins-lts

 

명령어로 실행이 완료되면 Jenkins는 http://localhost:8080/ 에 접속해서 액세스할 수 있습니다.

접속하면 위 화면 처럼 비밀번호를 요구하는 화면이 나타난 경로의 파일(initialAdminPassword)을 열어 해당 비밀번호를 바꾸지 말고 복사해 입력 후 Continue를 클릭해 진행 합니다. (비번은 바꾸고 싶으면 나중에)

 

(해당 경로 폴더가 없다면 숨김 폴더로 감춰져있을 수 있는데  Shift+Command+. 으로 숨김 폴더를 볼 수 있습니다)

Install suggested plugins 선택하면 대충 추천 하는 플러그인을 같이 깔아줍니다. 

 

설치 완료되면 초기 관리자 이름과 비밀번호 설정 화면에서 원하는 비밀번호로 설정 후 진행하면 됩니다.

위 화면이 나왔다면 젠킨스가 구동되었습니다.

 

| 젠킨스에 유니티 플러그인 및 버전 등록, GitHub 세팅하기

더보기

1. 유니티 플러그인 설치 + 유니티 버전 등록

젠킨스 관리 메뉴에서 Plugins 선택.

Available plugins 에서 Unity 검색 후 체크 한 다음에 설치합니다.

설치가 끝난 후 젠킨스 관리의 Tools 메뉴에 들어가서...

하단에 쭉 내려보면 유니티 버전 정보를 넣는 곳이 있어서 사용하는 버전과 경로를 넣으면 됩니다.


 

2. 유니티 GitHub 계정 자격 증명 추가

 

GitHub 저장소에 빌드할 프로젝트가 있는 경우 GitHub 자격 인증이 필요합니다.

 

젠킨스 상단에 계정 정보에서 Credentials 메뉴를 선택합니다.

Stores from parent는 글로벌하게 설정할 수 있습니다. (특정 계정만 권한을 추가하려면 위에 Stores scoped.. 에서 가능합니다)

알아보기 좋은 자격 증명 이름 설정 후 Create 합니다.

Add credentails를 선택합니다.

  • ID : 하고 싶은 자격 증명 식별자로 원하는 이름으로 설정
  • username : 깃 허브 계정 닉네임
  • password : Access Token 키

전부 기입 후 Create 버튼을 누르면 자격 증명 등록이 끝납니다! 

 

※ 참고 : Github Access Token은 다음 과정으로 얻을 수 있습니다 ▽

Github 로그인 후 설정 최하단의 Developer Settings 메뉴에 들어갑니다.

Personal access tokens > tokens(classic) 으로 들어가서..

 [Generate new token]를 클릭합니다.

scopes: repo, repo_hook 전체 권한을 체크하고 토큰을 생성하면 됩니다.

이 과정으로 AccessToken을 발급 받게 되고 복사할 수 있습니다.

 

 

| Android 빌드용 빌드 자동화 스크립트 

더보기

1. Unity 에서 빌드시 호출될 빌드 프로세스 스크립트 작성

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

public class BuildProcessor
{
    private const string KeystorePath = "KeystorePath/keystore.keystore";
    private const string keystorePass = "keystorePass1234";
    private const string KeyaliasName = "keyAliasName";
    private const string KeyaliasPass = "keyAliasPass";
    

    private const string ArgName_BuildNum = "buildNum";
    private const string ArgName_OutputPath = "outputPath";
    private const string ArgName_BuildType = "buildType";
    private const string ArgName_BuildVersion = "buildVersion";
    private const string ArgName_EnableDev = "enableDev";
    private const string ArgName_EnableDeepProfiling = "enableDeepProfiling";
    private const string ArgName_OutputFileName = "outputFileName";
    
    private static string GetCommandLineArgument(string name)
    {
        var args = System.Environment.GetCommandLineArgs();
        for (var i = 0; i < args.Length; i++)
        {
            if (args[i] == $"-{name}" && i + 1 < args.Length)
                return args[i + 1];
        }
        return null;
    }

    public static void BuildAndroid()
    { 
#if UNITY_ANDROID
        // Jenkins 에서 세팅한 Arguments들
        var buildNum = int.Parse(GetCommandLineArgument(ArgName_BuildNum));
        var outputPath = GetCommandLineArgument(ArgName_OutputPath);
        var version = GetCommandLineArgument(ArgName_BuildVersion);
        var extension = GetCommandLineArgument(ArgName_BuildType);
        var enableAab = extension == "aab"; // AAB 빌드 여부
        var enableDev = GetCommandLineArgument(ArgName_EnableDev) == "true"; // Dev Build 여부
        var enableDeepProfiling = GetCommandLineArgument(ArgName_EnableDeepProfiling) == "true"; // Dev Build 여부
        var outputFileName = GetCommandLineArgument(ArgName_OutputFileName);
       
        // BuildPlayerOptions 설정하는 부분. 빌드 세팅에서 추가한 Scene들
        var buildPlayerOptions = new BuildPlayerOptions
        {
            scenes = FindEnabledEditorScenes(),
            locationPathName = outputPath,
            target = BuildTarget.Android
        };

        EditorUserBuildSettings.buildAppBundle = enableAab; //.aab로 추출한것인지
        EditorUserBuildSettings.development = enableDev;
        EditorUserBuildSettings.buildWithDeepProfilingSupport = enableDeepProfiling;

        // PlayerSettings
        PlayerSettings.bundleVersion = version;
        PlayerSettings.Android.bundleVersionCode = buildNum;
        PlayerSettings.Android.useCustomKeystore = true;
        PlayerSettings.Android.keystoreName = KeystorePath;
        PlayerSettings.Android.keystorePass = keystorePass;
        PlayerSettings.Android.keyaliasName = KeyaliasName;
        PlayerSettings.Android.keyaliasPass = KeyaliasPass;  
       
        // 여기서 백그라운드를 통해 batchmode로 실제로 빌드가 실행된다. 
        // 자세한 로그는 fastlane cmd 나 Jenkins log에서 확인이 가능하다. (에러 터지면 얘네들로 찾아야함)
        var report = BuildPipeline.BuildPlayer(buildPlayerOptions);

        switch (report.summary.result)
        {
            case BuildResult.Succeeded:
            case BuildResult.Failed:
            case BuildResult.Unknown:
            case BuildResult.Cancelled:
                Debug.Log($"빌드 결과 : {report.summary.result}");
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
#endif
    }
    
    // Enable 처리된 Scene들 가져오는 부분
    private static string[] FindEnabledEditorScenes()
    {
        var editorScenes = new List<string>();

        foreach (var scene in EditorBuildSettings.scenes)
        {
            if (!scene.enabled) continue;
            editorScenes.Add(scene.path);
        }

        return editorScenes.ToArray();
    }
}

 

이제 위 스크립트를 Jenkins에서 빌드 파이프라인에서 호출해주어야 합니다.

 

 

2. Jenkins에 빌드 자동화 스크립트 작성하기

Jenkins 대시보드에서 폴더 선택 후 New Item을 선택합니다. (폴더 없으면 새로운 Item 선택 후 폴더 추가하면 됩니다.)

item 이름은 유니티 프로젝트의 이름으로 합니다. Pipeline 선택 후 OK

 파이프 라인 스크립트 추가 ▼

pipeline {
    agent any
    environment {
        UNITY_VERSION = "${params.UnityVersion ?: '2022.3.23f1'}"
    }
    parameters {
        booleanParam(name: 'EnableDev', defaultValue: true, description: 'Dev 빌드로 할까요?')
        booleanParam(name: 'EnableAab', defaultValue: false, description: 'aab로 빌드할까요?')
        string(name: 'BuildVersion', defaultValue: '0.0.1', description: '설명')
        string(name: 'UnityVersion', defaultValue: '2022.3.23f1', description: '2022.3.23f1')
        string(name: 'ProjectPath', defaultValue: 'YourUnityProject', description: 'Git 저장소 안쪽 실제 유니티 프로젝트 폴더 경로')
    }

    stages {
        stage('Checkout') {
            steps {
                script {                    
                    git url: 'https://github.com/YourGitRepo/YourProject.git', branch: 'main', credentialsId: 'GitHub-Credential-ID'
                }
            }
        }
        stage('Build') {
            steps {
                script {
                    def buildType = params.EnableAab ? "aab" : "apk"
                 
                    sh """
                    /Applications/Unity/Hub/Editor/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \\
                    -batchmode \\
                    -quit \\
                    -projectPath "${WORKSPACE}/${params.ProjectPath}" \\
                    -executeMethod "BuildProcessor.BuildAndroid" \\
                    -buildTarget Android \\
                    -buildVersion ${params.BuildVersion} \\
                    -buildNum ${env.BUILD_NUMBER} \\
                    -enableDev ${params.EnableDev} \\
                    -buildType ${buildType} \\
                    -outputPath ${WORKSPACE}/BuildOutput/${params.ProjectPath}.{buildType} \\
                    -logFile -
                	"""
                }
            }
        }
    }
}

내용은 최대한 간단하게 구성했습니다. 

| iOS 빌드용 빌드 자동화 스크립트 

더보기

1. Fastlane 설치 및 파일 생성 (※  Fastlane : ios용 빌드 자동화 도구)

brew install fastlane

 iOS로 빌드된 (Unity-iPhone.xcworkspace가 존재하는 디렉토리)로 이동한 후 다음 명령어 실행합니다. 

fastlane init

위 명령어를 하면 몇가지 질의를 거쳐 Fastfile을 생성할 수 있습니다.

첫번째 질문에서, 테스트 플라이트 배포가 가능하도록 할 것 이기 때문에 2번을 선택. 

그 다음 Apple Developer ID와 비밀번호를 입력합니다.

Please enter the 6 digit code:

그러면 6개의 digit code를 입력을 요구하는데 이 때 웹으로 6개의 코드를 보여주기 때문에 그걸 입력해서 넘어가면 됩니다.

fastlane 폴더가 생성되었는지 확인합니다. 이 폴더는 유니티 프로젝트에 배치해둡니다. 

 

2. 빌드할 때 인증을 위한 App Store Connect API 팀 Key 발급

 

위에서 생성한 Fastfile로 빌드를 할 때 테스트 플라이트에 업로드하기 위해선 편의상 AppStoreConnect API 팀 키가 필요합니다.

 

 AppStoreConnect 페이지에서 사용자 및 액세스 > 통합 탭 > App Store Connect API > 팀 키 [API 키 생성] 클릭 합니다.

역할은 앱 관리로 선택하고 [생성]을 눌러줍니다.

만들어진 키를 [다운로드] 합니다. (팀 키 메뉴에서 Issuer ID와 키 ID도 복사해두세요! )

 

이제 위에서 만들어둔 Fastfile을 편집해야 합니다.

default_platform(:ios)

platform :ios do
  desc "Push a new beta build to TestFlight"
  lane :beta do
    api_key = app_store_connect_api_key(
      key_id: "위에서 복사한 Key ID 값",
      issuer_id: "위에서 복사한 Issuer ID 값",
      key_content: "-----BEGIN PRIVATE KEY-----\nBlabababal\nBalsdasdas\n....\n-----END PRIVATE KEY-----",
    )
    build_app(workspace: "Unity-iPhone.xcworkspace", scheme: "Unity-iPhone")
    upload_to_testflight(api_key: api_key)
  end
end

위에서 얻어낸 정보로 Fastfile을 위와 같은 형태로 수정합니다.

 

※ key_content는 다운로드 받은 파일을 열어보시면 "-----BEGIN PRIVATE KEY-----" 로 시작하는 텍스트를 복사해서 넣으면 되는데 주의할 건 개행되는 부분을 "\n"으로 반드시 대체해서 넣어야 합니다.

 

3. 젠킨스 iOS용 파이프라인 스크립트 작성

pipeline {
    agent any
    environment {
        UNITY_VERSION = "${params.UnityVersion ?: '2022.3.23f1'}"
        LANG = 'en_US.UTF-8'
        LC_ALL = 'en_US.UTF-8'
    }
    parameters {
        booleanParam(name: 'IsAndroid', defaultValue: true, description: '안드로이드 빌드인가요?')
        booleanParam(name: 'EnableDev', defaultValue: true, description: 'Dev 빌드로 할까요?')
        booleanParam(name: 'EnableAab', defaultValue: false, description: 'aab로 빌드할까요?')
        string(name: 'BuildVersion', defaultValue: '0.0.1', description: '설명')
        string(name: 'UnityVersion', defaultValue: '2022.3.23f1', description: '2022.3.23f1')
        string(name: 'ProjectPath', defaultValue: 'YourUnityProject', description: 'Git 저장소 안쪽 실제 유니티 프로젝트 폴더 경로')
    }

    stages {
        stage('Checkout') {
            steps {
                script {                    
                    git url: 'https://github.com/YourGitRepo/YourProject.git', branch: 'main', credentialsId: 'GitHub-Credential-ID'
                }
            }
        }
        stage('Build') {
            steps {
                script {
                    def buildType = params.EnableAab ? "aab" : "apk"
                 
                    if (params.IsAndroid) {
                        outputFilePath = "${WORKSPACE}/BuildOutput/${params.ProjectPath}.${buildType}"
                    }
                    else {
                        outputFilePath = "${WORKSPACE}/BuildOutput/${params.ProjectPath}"
                    }

                    projectPath = "${WORKSPACE}/${params.ProjectPath}"

                    sh """
                    /Applications/Unity/Hub/Editor/${UNITY_VERSION}/Unity.app/Contents/MacOS/Unity \\
                    -batchmode \\
                    -quit \\
                    -projectPath "${projectPath}" \\
                    -executeMethod "BuildProcessor.BuildAndroid" \\
                    -buildTarget Android \\
                    -buildVersion ${params.BuildVersion} \\
                    -buildNum ${env.BUILD_NUMBER} \\
                    -enableDev ${params.EnableDev} \\
                    -buildType ${buildType} \\
                    -outputPath ${outputFilePath} \\
                    -logFile -
                	"""
                }
            }
        }
        stage('Fastlane') {
            when {
                expression {
                    return params.IsAndroid == false
                }
            }
            steps {
                script {
                    sh """
                    mkdir -p "${outputFilePath}/fastlane"
                    cp -R "${projectPath}/fastlane/"* "${outputFilePath}/fastlane"
                    """
                    dir(outputFilePath) {
                        sh "fastlane beta"
                    }
                }
            }
        }
    }
}

전체적인 내용은 Android용에서 'Fastlane' stage 블록만 추가해서 조금 확장했습니다. 프로젝트 안에 배치해둔 fastlane 폴더를 빌드시 빌드된 xcode 안쪽으로 복사한 후, fastlane 명령어를 구동시켜주는 코드입니다.

 

Unity 에서 호출되는 빌드 스크립트 iOS용은 사실 Android랑 동일하게 사용하되 BuildTarget을 설정하는 부분만 iOS로 바꾸면 되기 때문에 여기서 자세히 작성하지 않았습니다.

| 젠킨스 PlayGameService에 업로드 시키기

더보기

Google Cloud Services 에서 서비스 계정을 발급 받습니다. (GCS 에 대충 아무 프로젝트 계정 준비 합니다)

GCS 프로젝트에 Google Play Custom App Publisher API 검색해서 [사용] 설정

서비스 계정 만들고 [새 키 만들기] -> JSON 선택 해서 만들면 비공개 키 파일 다운로드 받아 놓습니다.

 

플레이 스토어에 사용자 및 권한에 권한을 부여할 서비스 계정을 추가. (구글 플레이 권한은 전부 다 주거나 업로드 관련된 권한 체크하면 됩니다.)

젠킨스 플러그인에 Google Play Android Publisher를 설치합니다.

위 설정으로 등록합니다. JSON key 는 Google Service Cloud Console에서 발급받은 json 업로드하면 됩니다.

androidApkUpload (
  googleCredentialsId: 'GooglePlayConsole',
    trackName: 'internal',
    rolloutPercentage: '100',
    filesPattern: "BuildOutput/YourAppName.apk" // or *.aab
  )

마지막으로 위 스크립트를 빌드 스크립트에 넣으면 되는데 filesPattern의 경우 workspace 기준으로 빌드 되어 생성된 apk 혹은 aab의 경로가 됩니다.

 

| 젠킨스 디스코드 알림 보내게 하기

더보기

슬랙으로 하려다가 슬랙은 뭔가 개인이 설치하기에 적당한 무료 요금제가 없어 보여서 디스코드로 해봤습니다.

 

젠킨스 관리 > 플러그인에서 Discord notifier 를 검색해서 설치합니다.

디코에서 공유를 원하는 서버 프로필 우클릭 서버 설정 > 연동을 선택합니다.

생성한 웹 후크 URL 복사합니다. 

복사한 내용을 젠킨스 관리 > Credentials에 추가합니다. Kind는 SecretText,  Secret에 복사 내용을 붙여넣기 합니다. 

discordSend description: "Jenkins Pipeline Build", footer: "Footer Text", link: env.BUILD_URL, result: currentBuild.currentResult, title: env.JOB_NAME, webhookURL: "Webhook URL"

젠킨스 빌드 파이프라인 스크립트 내에서 대충 위 플러그인 함수를 호출해주면 디스코드가 잘 보내집니다.

(가이드 : https://plugins.jenkins.io/discord-notifier/)

 

| 젠킨스 외부 접속 허용

더보기

젠킨스를 구축해 놓은 빌드 머신이 아닌 외부에서 접속하게 하기 위해서는 추가적인 설정이 필요합니다.

sondabin-ui-MacBookPro:/ dabin$ cd /usr/local/opt/jenkins-lts/
vi homebrew.mxcl.jenkins-lts.plist

 jenkins 설정 파일(plist) 경로로 이동 후 vi로 편집합니다.

httpListenAddress=127.0.0.1

내용에 위와 같이 명시되어 있는데, 이 값을 젠킨스 구동중인 컴퓨터의 ip로 바꿔주면 됩니다.