Motivation
pyqt6๋ฅผ ํ์ฉํ์ฌ ๊ฐ๋ฐํ python ๋ฐ์คํฌํ ์ฑ์ ๋ฐฐํฌํ๊ธฐ๊น์ง ์ฑ๊ณตํ๋ค. pyinstaller๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด ํ๋์ ์คํ ํ์ผ๋ก ์์ถํ์ฌ ๋ฐฐํฌํ์ง๋ง, ํด๋ผ์ด์ธํธ ์ธก์์ ์์์๋ ์ค๋ฅ๋ค์ด ๋ฐ์ํ๊ณ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ์ํ ๋ฐฉ๋ฒ์ ๋ชจ์ํ๋ค.
์ฐ์ , pyinstaller๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ์ ํจํค์งํ๋ ๊ณผ์ ์ ๋๋ฆ์ ๋ถํธํจ์ด ๋๋ฐํ๋ค. window์ ๊ฒฝ์ฐ exe ํ์ผ, macOs์ ๊ฒฝ์ฐ app ๋ฒ๋ค๋ก ์ด๋ฃจ์ด์ง๋ค. windows์ฉ ์ฑ์ ๋น๋ํ๋ ค๋ฉด windows์์ pyintstaller๋ฅผ ์คํํด์ผํ๋ฉฐ, mac์ฉ ์ฑ์ ๋น๋ํ๋ ค๋ฉด mac์์ pyinstaller๋ฅผ ์คํํด์ผํ๋ค.
์ฆ, ๊ฐ os์ ์คํ ํ๊ฒฝ์ด ์๋๋ฉด ๋น๋ํ ์ ์๋ค. (ํ ์คํธ๋ ๋ง์ฐฌ๊ฐ์ง๋ค) ๋๋ mac m2๋ฅผ ๋ฉ์ธ ์ปดํจํฐ๋ก ์ฐ๊ณ ์๊ธฐ์ ์๋์ฐ ํจํค์ง์ด ์ ๋งคํ๊ณ ๊ฒฐ๊ตญ, ์์ ์ ์ฐ๋ gram 14์ธ์น์ง๋ฆฌ๋ฅผ ๋ค๊ณ ๋ค๋๋ฉฐ ์ฑ์ ๋น๋ํ๊ณ ๋ค๋ ๋ค.
ํ ์คํธ์ ํ๊ณ๋ฅผ ๋์ด mac์ ๋ค์ํ ํ๊ฒฝ(Intel ๊ธฐ๋ฐ ๋ฑ)์์ ๋ฐ์ํ๋ ์ค๋ฅ๋ฅผ ์ก์ง ๋ชปํ๊ฒ ๋์, CI/CD(์ง์์ ํตํฉ/๋ฐฐํฌ) ๋ฐฉ๋ฒ์ ๋์ ํด์ผ๊ฒ ๋ค๊ณ ์๊ฐํ๋ค.
GitHub Action์ ์ ํ
ํด๋ผ์ฐ๋ ๊ตฌ์ถ์๋ ์ฌ๋ฌ ์ต์ ์ด ์์ง๋ง, github๋ฅผ ์ง์์ ์ผ๋ก ํ์ฉํ๊ณ ๋ฐฐํฌํ๊ณ ์๊ธฐ์ GitHub Action์ ์ ํํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค. GitHub Action์ CI/CD ๋๊ตฌ๋ก public ๋ ํ์งํ ๋ฆฌ๋ผ๋ฉด ๋ฌด๋ฃ๋ก ์ฌ์ฉ๊ฐ๋ฅํ๋ค. Linux, Windows, MacOs๋ฅผ ์คํํ๋ ํด๋ผ์ฐ๋ ์์คํ ์์ ์คํ๋ ์ํฌํ๋ก๋ฅผ ์ ์ํ ์์๊ณ ,์ด๋ ๋ค์ํ ํ๋ ํผ์์ ๊ตฌ์ถํด์ผํ๋ ์ฌ๋ก์ ์ ํฉํ๋ค. (์ด๊ฒ ๋น์ต ๋ฌด์จ๋ง์ด๋ ํ๋ฉด, github ์์ ๊ทธ๋ฅ ๊ฐ๊ฐ์ os ์ปดํจํฐ๋ฅผ ๋น๋ ค์ฃผ์ด์ ์ด๊ฑธ ๋ฐํ์ผ๋ก ์ฑ์ ๋น๋ํ ์์๋ค๋ ๊ฒ์ด๋ค.)
GitHub Action ์์ํ๊ธฐ
GItHub Actions๋ฅผ ์์ํ๋ ค๋ฉด ์ฑ ์ ์ฅ์์ ๋ค์๊ณผ ๊ฐ์ ํด๋๋ฅผ ๋ง๋ ๋ค
mkdir -p .github/workflows/๊ทธ๋ฆฌ๊ณ build.yml ์ ์์ ์ ์ ์ํ๋ ํ์ผ์ ์ถ๊ฐํ๋ค. github action ์ด build.yml์ ์ฝ์ด์ค๋ฉด์ ๋น๋ํด์ค๋ค.
์ฌ์ฉ์๊ฐ ์ํ๋ ์์๋๋ก ๋น๋ ๋ช ๋ น์ด๋ฅผ ์๋ง๊ฒ ์ค์ ํด์ฃผ๋ฉด ๋๋๋ฐ, ๋์ ๊ฒฝ์ฐ pyinstaller๋ก ๊ฐ๊ฐ์ os ์์ ํ์ผ์ ํจํค์งํด์ฃผ๋๋ก ์ค์ ํด์ฃผ์๋ค.
action ์ ์ํด์ ์ค์ ํด๋ณด์๋ฉด,
- push : push ์ด๋ฒคํธ์์ ๋ค์ ์์ ์ด ์คํ๋๋๋ก ์ ์ํ๋ค. ์ปค๋ฐ์ 'v'๋ฅผ ์ ๋์ฌ๋ก ์ฌ์ฉํ์ฌ ํ๊ทธ๊ฐ ์ง์ ๋ ๊ฒฝ์ฐ์๋ง ํด๋น๋๊ฒ ํด์ฃผ์๋ค.
name: Build
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
- createrrelease : github์์ ์ release๋ฅผ ์์ฑํ๋ ์์ ์ ๋ด๋นํ๋ค.
jobs:
createrelease:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Output Release URL File
run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
- name: Save Release URL File for publish
uses: actions/upload-artifact@v1
with:
name: release_url
path: release_url.txt
- build : createrelease ์์
์ด ์๋ฃ๋ ํ์ ์คํ๋๋ค. strategyํํธ์์ ๋๊ฐ์ง ๋ฒ์ ์ ์ ์ํ ์์ด์ด๋ฐ MacOs์ WIndows ๋ฒ์ ์ด๋ค. ์ด ๊ณผ์ ๋ค์ ์กฐ๊ธ๋ ์ธ๋ถ์ ์ผ๋ก ๋ณผ์์๋๋ฐ,
- ๊ฐ๊ฐ์ ์์คํ ์ ๋์์ ์ ์ํด์ค๋ค.
- actions/checkout@v1 ์ ์ฅ์๊ฐ ํด๋ผ์ฐ๋ ์์คํ ์ ๋ณต์ ๋๋ค.
- ์ ์ฅ์์์ requirements.txt ๋ฅผ ํตํด ์ข ์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ค๋ค.
- ํจํค์ง๋ pyinstaller๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑ๋๋ค.
- createrelease์์ ์์ ํ realase url์ด ํํจ๋ ํ์ผ์ ๋ค์ด๋ฐ๋๋ค
- ํ์ผ์์ release url ์ ์ถ์ถํด ๋ณ์์ ์ ์ฅํ๋ค
- ๊ทธ release url ์ ํจํค์ง ์ฑ๋ค์ ์ ๋ก๋ํ๋ค.
build:
name: Build packages
needs: createrelease
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: macos-latest
TARGET: macos
CMD_BUILD: |
pyinstaller app.spec
cd dist/
zip -r9 DICOM_Anonymizer.zip DICOM_Anonymizer
cd ../
OUT_FILE_NAME: DICOM_Anonymizer.zip
ASSET_MIME: application/zip
- os: windows-latest
TARGET: windows
CMD_BUILD: pyinstaller app.spec
OUT_FILE_NAME: DICOM_Anonymizer.exe
ASSET_MIME: application/x-msdownload
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.12
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build with pyinstaller for ${{ matrix.TARGET }}
run: ${{ matrix.CMD_BUILD }}
- name: Download Release URL File
uses: actions/download-artifact@v1
with:
name: release_url
- name: Get Release File Name & Upload URL
id: get_release_info
shell: bash
run: echo ::set-output name=upload_url::$(<release_url/release_url.txt)
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.get_release_info.outputs.upload_url }}
asset_path: ./dist/${{ matrix.OUT_FILE_NAME }}
asset_name: ${{ matrix.OUT_FILE_NAME }}
asset_content_type: ${{ matrix.ASSET_MIME }}
์ด๋ ๊ฒ ์ค์ ํ์ฌ ์์ฑ๋ build.yml ์ ๋ค์๊ณผ ๊ฐ๋ค.
name: Build
on:
push:
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs:
createrelease:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Save Release URL File
run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt
- name: Upload Release URL File
uses: actions/upload-artifact@v1
with:
name: release_url
path: release_url.txt
build:
name: Build packages
needs: createrelease
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: macos-latest
TARGET: macos
CMD_BUILD: |
pyinstaller app.spec
cd dist/
zip -r9 DICOM_Anonymizer.zip DICOM_Anonymizer
cd ../
OUT_FILE_NAME: DICOM_Anonymizer.zip
ASSET_MIME: application/zip
- os: windows-latest
TARGET: windows
CMD_BUILD: pyinstaller app.spec
OUT_FILE_NAME: DICOM_Anonymizer.exe
ASSET_MIME: application/x-msdownload
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.12
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build with pyinstaller for ${{ matrix.TARGET }}
run: ${{ matrix.CMD_BUILD }}
- name: Download Release URL File
uses: actions/download-artifact@v1
with:
name: release_url
- name: Get Release File Name & Upload URL
id: get_release_info
shell: bash
run: echo ::set-output name=upload_url::$(<release_url/release_url.txt)
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.get_release_info.outputs.upload_url }}
asset_path: ./dist/${{ matrix.OUT_FILE_NAME }}
asset_name: ${{ matrix.OUT_FILE_NAME }}
asset_content_type: ${{ matrix.ASSET_MIME }}
GitHub Action ์คํํ๊ธฐ
์์์๋ permission ์ค๋ฅ๊ฐ ๋ฐ์ํ ์์๋๋ฐ, action ์ permission ๊ด๋ จ ํ์ฌ ์ค์ ์ ๋ง์ ธ์ค์ผํ๋ค.

์ด์ ๊ฐ์ด ์ฐ๊ธฐ ์ฝ๊ธฐ ๊ถํ์ ๋ถ์ฌํด์ผ ์ ๋๋ก ์คํ์ด ๋๋ค.
๋ํ, GITHUB_TOKEN ์ ๋ฐ๋ก ์ค์ ํด์ค์ผํ๋ค๊ณ ์๊ฐํ๋๋ฐ, ์๋์ผ๋ก ์์ฑ๋๋ค๊ณ ํ๋ค. (์ด์ธ์ ํ ํฐ์ ์ถ๊ฐ์ ์ผ๋ก ํธ๋ค๋งํ๋ ค๋ฉด, action ์ ํ ํฐ ๊ด๋ จํ์ฌ ์ค์ ์ ํด์ค์ผํ๋ค. )
์ด์ ๋น๋ ์ํฌํ๋ก์ฐ๋ฅผ ์คํ์์ผ๋ณด์. ๋ค์์ ๋ช ๋ น์ด๋ฅผ ์ณ๋ณด๋ฉด์ ํ ์คํธํ ์์๋ค.
git commit -a -m "add autostart enable and disable scripts to pypi"
git tag v0.4.5
git push
git push origin --tags
์ค์ action ํญ์ ๋ค์ด๊ฐ๋ณด๋ฉด build๊ฐ ์๋ฃ๋์๋ค๊ณ ์๋ ค์ค๋ค.

์ค์ ํ๋๋ก, release์๊น์ง ์ ์ ๋ก๋ ๋์๋ค.

๋์ ํ๊ฒฝ์ธ m2 ์์ ์คํํด๋ณด๋, ๋ง์ง์๋ exec ํ์์ด๋ผ๊ณ ์๋ฌ๊ฐ ๋๋๊ฒ์ ํ์ธํ๋ค. mac m2 ๊ธฐ์ค์ผ๋ก ๋์ arch ๋ arm64์ด๊ณ , m1๊ณผ m2 ์ ํ์ฌ๋๋ apple silicon์ ์ง์ํ๋ ๋ชจ๋ธ์ด ์ต๊ทผ 2023๋ 10์์ ์ ์ ์ถ์๋์๋ค๊ณ ํ๋ค. ์ฐธ๊ณ : (Introducing the new, Apple silicon powered M1 macOS larger runner for GitHub Actions - The GitHub Blog)
๊ทธ๋ฌ๋ฏ๋ก m1, m2 ์ ์ง์๊ฐ๋ฅํ ํํ๋ก ๋ชจ๋ ์์ ํด์ค์ผํ ๊ฒ๊ฐ๋ค.
Conclusion
๊ฒฐ๋ก ์ ์ผ๋ก ํตํฉ๋ ๊ฐ๋ฐ ํ๊ฒฝ๊ณผ ์ ์ง๋ณด์๋ฅผ ์ํด์๋ CI/CD๋ฅผ ๋์ ํ๋๊ฒ์ด ๋์์ด ๋๋ค. ๋ค์ํ os์ ๋ง์ถ์ด ๋น๋๋ฅผ ํ ์์๊ณ , ์ค์ ๋์ ๋ก์ปฌ ๋๋ ํ ๋ฆฌ๋ฅผ ๊ฑฐ์น์ง ์๊ณ ๋ ๋น๋๊ฐ ๊ฐ๋ฅํ๋ค๋ ์ ์ด ํธ๋ฆฌํ๋ค. ๋ํ, ํตํฉ๋๊ณ ์์ ๋ ํ๊ฒฝ์์ ๋น๋๋ฅผ ์งํํ๊ธฐ์ ์ค๋ฅ๊ฐ ๋ ํ๋ฅ ๋ ์ ์ด์ง๋ค๋ ์ด์ ๋ ์๋๊ฒ๊ฐ๋ค. ์ถํ ํ ์คํธ๋ฅผ ๊ฑฐ์น๋ฉฐ, ๋ค์ํ ๋ฒ์ ์ ์ถ๊ฐํด์ผํ ๊ฒ๊ฐ๋ค.
** cc sorce Build a multi OS Python app in the cloud: PyInstaller on GitHub Actions - Data-Dive GitHub ํธ์คํ ์คํ๊ธฐ ์ ๋ณด - GitHub Docs