| <colbgcolor=#FFF,#1F2023><colcolor=#4280B1> Nuitka | |
| | |
| 종류 | 컴파일러 |
| 라이선스 | 아파치 라이선스 |
| 언어 | Python |
| | |
1. 개요
Python 코드를 C 언어 코드로 변환한 후, 이를 다시 기계어로 컴파일하는 AOT 컴파일러이다.단순히 Python 인터프리터와 스크립트, 라이브러리 등을 한꺼번에 패키징할 뿐인 PyInstaller와 달리, Nuitka는 실제 컴파일 과정을 거친다는 특징이 있다.
이름은 러시아어 Нютка(Njutka)에서 온 것으로 한글로 표기하면 "뉴트카" 정도가 된다. 개발자의 아내의 애칭인 아뉴트카(Анютка)에서 따 온 것이라고.
2. 상세
파이썬은 예로부터 일반 사용자에게 프로그램을 배포하는 것이 매우 까다로운 것으로 악명높았다. 아무리 PyPI를 통해 배포를 받는 것이 타 언어에 비해 쉽다고 해도, 터미널을 열고 명령어를 작성하는 것 그 자체가 최종 사용자들에게는 크나큰 벽이다. 그러나 파이썬 커뮤니티에서는 이에 대해 별 반응이 없고, PSF에서도 이를 진지하게 해결해주려는 모습을 보이지 않아 개발자들이 각자도생을 해야만 했다.그 결과물로 나왔던 Py2exe, PyInstaller, cx_Freeze 등의 기존 패키징 도구는 Python 스크립트와 CPython 런타임을 하나의 실행 프로그램으로 단순히 묶어버리는 단순무식한 방법인데다, 그 와중에도 서드파티 프로그램이라 동적으로 로드되는 모듈이나 일부 패키지와 제대로 호환되지 않는 잠재적 문제가 존재한다.[1] 이 탓에 진지하게 배포를 생각해야 할 정도로 프로그램이 커지면, 정작 배포의 시도가 거의 불가능해지는 모순까지 발생하였다.
Nuitka는 이런 기존 패키징 도구들의 문제를 해결하기 위해 나타난 패키징 도구 중 하나로, 비슷한 시기 주목받는 GraalPy 등과 함께 아예 네이티브 실행을 추구하기 위해 Python을 다른 언어로 컴파일하는 특징이 있다. 비록 사용하기 복잡하다는 단점이 있지만, 잘만 사용하면 깔끔한 배포가 가능하다는 점에서 큰 주목을 받고 있다.
3. 사용법
3.1. 설치
Nuitka는 PyPI에 등록되어 있으므로, 다음 명령어로 간단히 설치할 수 있다.#!syntax sh
pip install nuitka
그러나 Nuitka 자신만을 설치하는 것으로는 제대로 실행하기 어렵다. Nuitka는 Python에서 C로, C에서 기계어로 번역하나 정작 실제로 번역을 하는 C 컴파일러를 내장하지는 않았기 때문이다. 따라서 C 컴파일러를 따로 설치해주는게 좋다.
Nuitka를 위한 C 컴파일러로는, 주로 MSVC, GCC, Clang 등을 채택하는 경우가 많다. 설치 및 PATH 지정만 제대로 하면 경로 지정이 없어도 알아서 인식해주는 편이라는 점이 다행이다.
3.2. 기본적인 사용법
우선, 간단한 파이썬 프로그램을 만들어보자.#!syntax python
print("Hello, world!")
input("\nPress any key.")
이를 hello.py로 저장한 다음, 이렇게 불러보자.
#!syntax sh
nuitka hello.py
워낙 간단한 프로그램이니 아마 빌드가 순식간에 끝나고 hello.exe라는 파일이 하나 생성될 것이다. build라는 폴더가 생겼을텐데, 보통은 그저 빌드 작업의 잔여물일 뿐이니 제거해도 된다.
정상적으로 빌드된다면,
Hello, world!
Press any Key.
Press any Key.
라는 문구가 터미널에 나타날 것이다. 성공한 것이니 아무 키나 입력하고 창을 닫자.
3.3. 명령어
모든 명령어는 기본 명령어 뒤에 붙이는 형태로 사용할 수 있다.우선, 배포를 원한다면 이런 명령을 알아두는 것이 좋다.
- --standalone
생성된 실행 파일이 Python이 설치되지 않은 환경에서도 독립적으로 실행될 수 있도록 필요한 모든 라이브러리와 의존성을 폴더 안에 함께 패키징한다. 이는 프로그램으로서 배포하기 위해 사실상 필수이다. - --onefile
단 한 개의 파일로 실행할 수 있도록 만든다. 소위 '무설치 파일'과 비슷한 원리. 커다란 프로그램의 배포 방식으로는 오히려 어울리지 않지만, 굳이 '설치'라는 거창한 작업이 필요하지 않은 작은 프로그램이라면 standalone보다 훨씬 좋은 선택지다. - --output-dir=DIR_NAME
DIR_NAME에 결과물을 생성한다.
그러나 복잡한 프로젝트는, 보통 nuitka hello.py와 같은 짧은 명령으로는 제대로 생성되지 않을 것이다. 이는 아무래도 Nuitka가 자동으로 의존성과 데이터 파일을 인식하는 능력이 좀 떨어지기 때문이다. 따라서, 명령어를 이용해 수동으로 지정해주는 것이 필요하다.
그래도, 실제 Python이나 클론한 디렉토리를 통째로 인용해야 하는 Pyinstaller보다는 지정하기 쉽다는게 다행이다.
- --include-package=PACKAGE_NAME
Nuitka에서 PACKAGE_NAME 전체를 강제로 포함하여 빌드한다. - --include-module=MODULE_NAME
Nuitka에서 MODULE_NAME이라는 단일 모듈을 강제로 포함하여 빌드한다. - --enable-plugin=PLUGIN_NAME
Nuitka에는 C로 작성된 유명한 라이브러리들을 처리하기 위한 플러그인이 많다. 이 명령어는 그런 플러그인들을 강제로 활성화시킨다. 단, Numpy는 예외적으로 plugin 지원을 진작에 중단하고 내장했으니 넣지 않는게 좋다. - --include-data-dir=SOURCE_PATH=DESTINATION
Nuitka에서 SOURCE_PATH 폴더 또는 파일들을 강제로 포함해 프로그램 루트 폴더 아래의 DESTINATION 경로로 집어넣는다. 프로그램 사용에 꼭 필요한데 Nuitka가 자동으로 넣어주지 않는 데이터 파일이 있을 때 사용한다.
이 외에도
3.4. 빌드 자동화
3.4.1. 간편한 로컬 빌드
우선, 다음과 같은 파일을 텍스트 에디터 등으로 작성해 build.bat으로 저장한다.#!syntax sh
@echo off
chcp 65001 > nul
echo === %PROJECT_NAME% 빌드 도구 ===
echo.
REM Python 설치 확인
python --version >nul 2>&1
if errorlevel 1 (
echo [오류] Python이 설치되어 있지 않습니다.
echo Python 3.9 이상을 설치해주세요.
pause
exit /b 1
)
echo [1/3] 필수 패키지 설치 중...
python install_requirements.py
if errorlevel 1 (
echo [오류] 패키지 설치에 실패했습니다.
pause
exit /b 1
)
echo.
echo [2/3] Nuitka 빌드 시작...
python build_nuitka.py
if errorlevel 1 (
echo [오류] 빌드에 실패했습니다.
pause
exit /b 1
)
echo.
echo [3/3] 빌드 완료!
echo.
echo 실행 파일 위치: %PROJECT_NAME%_Distribution 폴더
echo.
pause
여기서 %PROJECT_NAME%은 프로젝트의 이름을 뜻한다. 이는 자신이 작성하고 있는 프로젝트의 이름으로 지정해서 넣어야 한다.
다음으로, 이 파일을 작성해 build_nuitka.py로 저장한다.
#!syntax python
"""
Nuitka를 사용한 빌드 스크립트 (일반화 버전)
배포 방식(standalone/onefile)을 선택할 수 있습니다.
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
# === 프로젝트별 설정 ===
PROJECT_NAME = "MyProject"
COMPANY_NAME = "MyCompany"
VERSION = "1.0.0"
DESCRIPTION = "프로젝트 설명"
ENTRY_POINT = "main.py"
# gui 파일이 따로 있다면 그 파일을 빌드해야 한다.
# 예를 들어, 커맨드 형식으로 실행하는 main.py가 존재한다면,
# gui를 구현하는 파일 예를 들어 gui.py 등을 ENTRY_POINT로 설정하는게 좋다.
OUTPUT_NAME = "MyApp"
ICON_FILE = "icon.ico" # 없으면 "" 형태로, 따옴표 안의 내용을 비워둔다.
# 포함할 모듈들
INCLUDE_MODULES = [
"tkinter", "numpy", "matplotlib",
# 프로젝트별로 추가/수정
]
# 포함할 데이터 디렉토리들
INCLUDE_DATA_DIRS = [
"data=data", "assets=assets",
# 프로젝트별로 추가/수정 (없으면 빈 리스트)
#=의 경우, pyproject.toml에서 특정 디렉토리를 변수로 지정할 수 있음을 이용한 문법이다.
]
# Nuitka 플러그인들
ENABLE_PLUGINS = [
"tk-inter", "matplotlib",
# 필요한 플러그인 추가/수정
# numpy는 플러그인이 지원중단 되었으므로 제외하는 것을 권장한다.
]
def choose_build_mode():
"""빌드 방식에 대한 선택"""
global BUILD_MODE
print("\n=== 배포 방식 선택 ===")
print("1. standalone - 폴더 형태")
print("2. onefile - 단일 파일")
while True:
choice = input("\n어떤 방식으로 빌드하시겠습니까? ").strip()
if choice == "1":
BUILD_MODE = "standalone"
print("특정 폴더에 빌드합니다.")
break
elif choice == "2":
BUILD_MODE = "onefile"
print("단일 파일로 빌드합니다.")
break
else:
print("1 또는 2를 입력하십시오.")
def clean_build_folders():
"""이전 빌드 폴더 정리"""
folders_to_clean = [
'dist', 'build',
f'{OUTPUT_NAME.lower()}.build',
f'{OUTPUT_NAME.lower()}.dist',
f'{OUTPUT_NAME.lower()}.onefile-build'
]
for folder in folders_to_clean:
if os.path.exists(folder):
print(f"이전 빌드 폴더 삭제 중: {folder}")
shutil.rmtree(folder)
def build_project():
"""Nuitka로 프로젝트 빌드"""
# 기본 Nuitka 명령어
nuitka_cmd_args = [
sys.executable, "-m", "nuitka",
"--windows-console-mode=disable", # Windows에서 콘솔 창 숨기기
f"--company-name={COMPANY_NAME}",
f"--product-name={PROJECT_NAME}",
f"--file-version={VERSION}",
f"--product-version={VERSION}",
f"--file-description={DESCRIPTION}",
f"--output-filename={OUTPUT_NAME}",
"--assume-yes-for-downloads", # 필요한 파일 자동 다운로드
"--show-progress", # 진행 상황 표시
"--show-memory", # 메모리 사용량 표시
"--output-dir=dist", # 출력 디렉토리
]
# 배포 방식에 따른 옵션 추가
if BUILD_MODE == "standalone":
nuitka_cmd_args.append("--standalone")
elif BUILD_MODE == "onefile":
nuitka_cmd_args.append("--onefile")
# 아이콘 추가 (있는 경우)
if ICON_FILE and os.path.exists(ICON_FILE):
nuitka_cmd_args.append(f"--windows-icon-from-ico={ICON_FILE}")
print(f"아이콘 설정: {ICON_FILE}")
# 플러그인 추가
for plugin in ENABLE_PLUGINS:
nuitka_cmd_args.append(f"--enable-plugin={plugin}")
# 모듈 추가
for module in INCLUDE_MODULES:
nuitka_cmd_args.append(f"--include-module={module}")
# 데이터 디렉토리 추가
for data_dir in INCLUDE_DATA_DIRS:
if "=" in data_dir: # 유효한 형식인지 확인
nuitka_cmd_args.append(f"--include-data-dir={data_dir}")
# 엔트리 포인트 추가
nuitka_cmd_args.append(ENTRY_POINT)
print(f"\n{BUILD_MODE} 모드로 빌드를 시작합니다.")
print("시간이 좀 걸릴 수 있습니다...")
try:
result = subprocess.run(nuitka_cmd_args, check=True)
print("\n빌드가 성공적으로 완료되었습니다!")
return True
except subprocess.CalledProcessError as e:
print(f"\n빌드 중 오류가 발생했습니다: {e}")
return False
def create_distribution():
"""배포용 폴더 생성"""
dist_folder = f"{PROJECT_NAME}_Distribution"
if os.path.exists(dist_folder):
shutil.rmtree(dist_folder)
os.makedirs(dist_folder)
# 빌드 결과물 복사
success = False
if BUILD_MODE == "standalone":
# standalone 모드: 전체 폴더 복사
standalone_folder = os.path.join("dist", f"{OUTPUT_NAME}.dist")
if os.path.exists(standalone_folder):
shutil.copytree(standalone_folder, os.path.join(dist_folder, "app"))
print(f"standalone 폴더를 {dist_folder}/app로 복사했습니다.")
success = True
elif BUILD_MODE == "onefile":
# onefile 모드: 실행 파일만 복사
exe_file = f"{OUTPUT_NAME}.exe"
if os.path.exists(exe_file):
shutil.copy(exe_file, dist_folder)
print(f"{exe_file}를 배포 폴더로 복사했습니다.")
success = True
if not success:
print("빌드 결과물을 찾을 수 없습니다.")
return False
print(f"\n배포 폴더가 생성되었습니다: {dist_folder}")
return True
def main():
"""메인 빌드 프로세스"""
print(f"=== {PROJECT_NAME} Nuitka 빌드 스크립트 ===\n")
# 엔트리 포인트 파일 확인
if not os.path.exists(ENTRY_POINT):
print(f"엔트리 포인트 파일을 찾을 수 없습니다: {ENTRY_POINT}")
print("build_config.py의 ENTRY_POINT를 확인하세요.")
sys.exit(1)
# Nuitka 확인
if not check_nuitka():
sys.exit(1)
# 빌드 모드 선택
choose_build_mode()
# 이전 빌드 정리
clean_build_folders()
# 빌드 실행
if build_project():
# 배포 폴더 생성
if create_distribution():
print(f"\n빌드가 완료되었습니다.")
print(f"{PROJECT_NAME}_Distribution 폴더에서 결과물을 확인하세요.")
if BUILD_MODE == "standalone":
print("폴더 형태로 빌드되었습니다. 이제 인스톨러를 제작하여 배포하십시오.")
else:
print("단일 파일로 빌드되었습니다. exe 파일만 배포하면 됩니다.")
else:
print("\n배포 폴더 생성에 실패했습니다.")
sys.exit(1)
else:
print("\n빌드에 실패했습니다.")
sys.exit(1)
if __name__ == "__main__":
main()
마지막으로, build.bat이 참조하는 또 다른 파일 install_requirements.py는 다음과 같이 생성한다.
#!syntax python
#패키지를 감지하고 설치하는 스크립트이다.
import subprocess
import sys
import os
# 프로젝트별 설정
REQUIRED_PACKAGES = [
"nuitka", # Nuitka 자체를 설치하는 것이므로 필수다.
"numpy",
"matplotlib",
"scipy",
"pandas",
# 프로젝트에 따라 추가/제거
]
def install_package(package_name):
"""개별 패키지 설치"""
try:
print(f"{package_name} 설치 중...")
result = subprocess.run(
[sys.executable, "-m", "pip", "install", package_name],
capture_output=True,
text=True,
check=True
)
print(f"{package_name} 설치 완료")
return True
except subprocess.CalledProcessError as e:
print(f"{package_name} 설치 실패:")
print(f" {e.stderr}")
return False
def check_package_installed(package_name):
"""패키지가 이미 설치되어 있는지 확인"""
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "show", package_name],
capture_output=True,
text=True,
check=True
)
return True
except subprocess.CalledProcessError:
return False
def install_requirements():
"""필요한 모든 패키지 설치"""
print("=== 필수 패키지 설치 시작 ===\n")
# Python 버전 확인
if not check_python_version():
return False
# pip 업그레이드
print("\npip 업그레이드 중...")
try:
subprocess.run(
[sys.executable, "-m", "pip", "install", "--upgrade", "pip"],
capture_output=True,
check=True
)
print("pip 업그레이드 완료")
except subprocess.CalledProcessError:
print("pip 업그레이드에 실패하였으나, 설치는 계속 진행합니다.")
# requirements.txt 파일이 있으면 먼저 설치
if os.path.exists("requirements.txt"):
print("\nrequirements.txt 파일 발견, 설치 중...")
try:
subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
check=True
)
print("requirements.txt 설치 완료")
except subprocess.CalledProcessError:
print("requirements.txt 설치 실패")
return False
# 개별 패키지 설치
print(f"\n필수 패키지 {len(REQUIRED_PACKAGES)}개 확인 중...")
failed_packages = []
for package in REQUIRED_PACKAGES:
if check_package_installed(package):
print(f"{package} 이미 설치됨")
else:
if not install_package(package):
failed_packages.append(package)
# 결과 출력
if failed_packages:
print(f"\n설치 실패한 패키지: {', '.join(failed_packages)}")
print("수동으로 설치를 시도해보세요:")
for pkg in failed_packages:
print(f" pip install {pkg}")
return False
else:
print(f"\n모든 패키지 설치 완료!")
return True
def main():
"""메인 실행 함수"""
success = install_requirements()
if success:
print("\n패키지 설치가 완료되었습니다.")
else:
print("\n패키지 설치 중 오류가 발생했습니다.")
sys.exit(1)
if __name__ == "__main__":
main()
이렇게 만들어두면, build.bat을 더블클릭하는 것만으로도 build.bat -> install_requirements.py -> build_nuitka.py 순으로 실행되어, 파이썬 프로젝트를 Nuitka로 편리하게 빌드할 수 있게 된다.
문제는, Nuitka가 굴리는 C 컴파일러의 특성상 빌드를 할 때마다 CPU 부하가 상당하고, 프로그램 설치를 원할 경우 설치 파일을 별도로 만드는 제법 귀찮은 작업이 남는다는 점이다. Inno Setup 같은, 스크립트를 저장할 수 있는 좋은 인스톨러 생성기 덕분에 난이도가 낮아졌지만 그래도 짜증나는게 사실이다. 큰 프로그램이면 용량의 압박도 상당하다.
다행히도 이 모든 불편함을 서버에 떠넘길 방법이 존재한다.
3.4.2. CI/CD 워크플로우
GitHub Action을 이용하면 앞서 설명한 빌드 과정을 깃허브 서버에서 자동으로 수행할 수 있다. 로컬 PC의 CPU를 혹사시키지 않아도 되고, 코드를 push할 때마다 자동으로 빌드와 배포가 이루어진다.프로젝트에 '.github/workflows/' 폴더를 만들고[2], 다음 파일을 nuitka_distribution.yml이라는 이름으로 저장한다.
name: Nuitka Distribution
on:
push:
branches:
- master
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build-windows:
name: Build Windows Distribution
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Cache Nuitka compilation
uses: actions/cache@v4
with:
path: ~\AppData\Local\Nuitka\Nuitka\Cache
key: ${{ runner.os }}-nuitka-${{ hashFiles('**/*.py') }}
restore-keys: |
${{ runner.os }}-nuitka-
- name: Install dependencies
run: |
pip install nuitka
pip install -r requirements.txt
shell: bash
- name: Run Nuitka build
run: |
python build_nuitka.py
echo "NUITKA_OUTPUT_DIR=${{ github.workspace }}\dist\MyProject_Distribution" >> $GITHUB_ENV
echo "PROJECT_ROOT=${{ github.workspace }}" >> $GITHUB_ENV
shell: bash
env:
PYTHONIOENCODING: UTF-8
- name: Set up Inno Setup
uses: Minionguyjpro/[email protected]
with:
path: .github/workflows/installer.iss
options: /DNUITKA_BUILD_DIR="${{ env.NUITKA_OUTPUT_DIR }}" /DPROJECT_ROOT="${{ env.PROJECT_ROOT }}"
- name: Upload installer
uses: actions/upload-artifact@v4
with:
name: windows-installer
path: .github/workflows/Output/*.exe
retention-days: 30
3.4.2.1. 세부 요소
3.4.2.1.1. 트리거 설정
on:
push:
branches:
- master
tags:
- 'v*'
workflow_dispatch:
master 브랜치에 push하거나, v1.0.0 같은 형태의 태그를 발행하면 워크플로우가 실행된다. workflow_dispatch는 깃허브 웹 인터페이스에서 수동으로 실행할 수 있게 해준다.
3.4.2.1.2. Nuitka 캐시
- name: Cache Nuitka compilation
uses: actions/cache@v4
with:
path: ~\AppData\Local\Nuitka\Nuitka\Cache
key: ${{ runner.os }}-nuitka-${{ hashFiles('**/*.py') }}
restore-keys: |
${{ runner.os }}-nuitka-
Nuitka는 C 컴파일을 수행하므로 빌드 시간이 상당히 길다. 캐시를 활용하면 변경되지 않은 부분의 재컴파일을 건너뛰어 빌드 시간을 크게 단축할 수 있다. 파이썬 파일이 변경되면 캐시 키가 바뀌어 새로 빌드한다.
3.4.2.1.3. 빌드 스크립트 호출
- name: Run Nuitka build
run: |
python build_nuitka.py
shell: bash
env:
PYTHONIOENCODING: UTF-8
로컬에서 사용하던 build_nuitka.py를 그대로 호출한다. 빌드 로직을 Python 스크립트로 분리해두면 로컬과 CI 환경에서 동일한 빌드 과정을 재사용할 수 있다.
이 때 install_requirements.py는 build_nuitka.py에 통합하는 것을 전제로 하고 있음에 주의해야 한다.[3]
3.4.2.1.4. Inno Setup 인스톨러 생성
- name: Set up Inno Setup
uses: Minionguyjpro/[email protected]
with:
path: .github/workflows/installer.iss
Nuitka가 생성한 standalone 폴더를 Inno Setup으로 패키징하여 설치 파일을 만든다. ISS 스크립트는 '.github/workflows/' 폴더에 함께 저장해두면 관리하기 편하다.
Inno Setup은 로컬 환경에서는 GUI로 구성된 마법사만 따라가도 쓸만한 스크립트를 만들어주지만, GitHub Action에서는 명령어 단위로 실행해야 하므로 반드시 스크립트 문법을 직접 수정해야 한다.
이 때, ISS 스크립트에서 빌드 결과물의 경로를 하드코딩하면 문제가 생긴다. 로컬 환경과 GitHub Action 환경의 디렉토리 구조가 다르고, 워크플로우 실행마다 workspace 경로가 달라질 수 있기 때문이다. 따라서 경로를 스크립트에 직접 쓰는 대신, 실행 시점에 환경변수로 주입하는 방식이 안전하다.
먼저 워크플로우에서 빌드 후 경로를 환경변수로 저장한다.
- name: Run Nuitka build
run: |
python build_nuitka.py
echo "NUITKA_OUTPUT_DIR=${{ github.workspace }}\dist\MyProject_Distribution" >> $GITHUB_ENV
echo "PROJECT_ROOT=${{ github.workspace }}" >> $GITHUB_ENV
shell: bash
그 다음 Inno Setup 실행 시 /D 옵션으로 값을 전달한다.
- name: Set up Inno Setup
uses: Minionguyjpro/[email protected]
with:
path: .github/workflows/installer.iss
options: /DNUITKA_BUILD_DIR="${{ env.NUITKA_OUTPUT_DIR }}" /DPROJECT_ROOT="${{ env.PROJECT_ROOT }}"
이제 installer.iss 파일에서는, #ifndef 전처리기를 사용하여 외부에서 값이 주입되면 그 값을 쓰고, 주입되지 않으면 기본값을 사용하도록 구성한다.
NUITKA_BUILD_DIR
이렇게 구성하면 로컬에서 Inno Setup GUI로 직접 컴파일할 때는 기본값이 적용되고, GitHub Action에서는 워크플로우가 인식한 실제 경로가 주입된다. 환경에 따라 스크립트를 수정할 필요가 없어진다.
3.4.2.1.5. 릴리스 자동 생성
빌드된 인스톨러를 GitHub Releases에 자동으로 업로드하려면 다음 코드를 추가한다. create-release:
name: Create Release
needs: build-windows
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: windows-installer
path: artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: artifacts/*.exe
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
needs: build-windows로 빌드가 완료된 후 실행되도록 설정하고, if: startsWith(github.ref, 'refs/tags/')로 태그 발행 시에만 릴리스를 생성한다. 일반적인 push 시에는 아티팩트만 생성되고 릴리스는 만들어지지 않는다.
4. PyInstaller 와의 차이점
PyInstaller는 파이썬 파일과 개발 시 사용한 모든 의존관계를 단순히 exe 파일로 압축 해놓은것이다. 그래서 exe 파일을 실행할때는 약간의 시간이 걸린다. 게다가 모든 것을 전혀 스마트하지 않게 쑤셔넣기 때문에 빌드 후 배포하기 위한 도구로 사용하기에는 용량이 지나치게 많고, 심지어 다 집어넣었음에도 일부 파일은 아예 인식하지 못해 Module not found 에러를 내곤 한다.반면 Nuitka는 빌드 과정에서 필요없는 의존관계를 아예 포함하지 않기에 상대적으로 가볍고, 단일한 exe 파일이 아닌 폴더로 빼낼 수도 있다. Inno Setup 등을 같이 사용하면 인스톨러, 언인스톨러를 포함시켜 배포용 프로그램을 완성할 수 있다.
하지만 Nuitka는 트렌스 컴파일러[4]이므로 별도의 C / C++ 컴파일러가 있어야 빌드를 시작할 수 있다. 또한 PyInstaller와 달리 컴파일 과정이 포함되어 있어 시간이 오래 걸리고 그 시간동안 컴퓨터를 무지막지하게 갈군다. GitHub Action을 사용하는 등의 방법으로 이 과정을 우회할 수 있다.