이 경고 메시지는 uv가 패키지를 설치할 때 고속 처리를 위해 사용하는 하드링크(Hardlink) 방식을 사용할 수 없어, 데이터를 통째로 복사하는 전체 복사(Full Copy) 방식으로 전환했다는 것을 의미한다.

성능이 조금 떨어질 뿐 설치 자체는 정상적으로 완료되지만, 원인을 해결하거나 설정을 변경하여 경고가 뜨지 않게 조치할 수 있다.

발생 원인

uv는 기본적으로 글로벌 캐시 폴더에 패키지를 다운로드한 뒤, 가상환경(.venv) 폴더로 파일을 연결할 때 하드링크를 사용하여 디스크 공간을 아끼고 속도를 극적으로 높인다. 하지만 다음과 같은 상황에서는 하드링크를 만들 수 없다.

  1. 서로 다른 파일 시스템(드라이브): uv 캐시 폴더는 C 드라이브에 있는데, 현재 작업 중인 프로젝트와 가상환경(.venv)은 E 드라이브나 USB 메모리 등에 위치한 경우이다. 하드링크는 물리적으로 동일한 드라이브 안에서만 생성할 수 있다.
  2. 네트워크 드라이브 또는 WSL 환경: 네트워크 공유 폴더나 Windows와 WSL(리눅스) 간의 경계를 넘어 파일을 조작할 때 파일 시스템 특성상 하드링크가 제한될 수 있다.

해결 방법

상황에 맞춰 아래 방법 중 하나를 선택하여 해결할 수 있다.

방법 1. 경고 메시지 끄기 (가장 간편함)

서로 다른 드라이브를 사용하는 것이 의도된 환경(예: 프로젝트를 다른 드라이브에서 관리하는 경우)이라면, uv에게 복사 방식을 사용하겠다고 명시하여 경고를 숨길 수 있다.

  • PowerShell (Windows) 환경인 경우 터미널에 아래 명령어를 입력한다.유지되게 하려면 PowerShell 프로필 파일($PROFILE)에 위 줄을 추가한다.
    $env:UV_LINK_MODE="copy"
    
  • Bash (Linux / WSL / macOS) 환경인 경우 터미널에 아래 명령어를 입력한다.유지되게 하려면 ~/.bashrc 또는 ~/.zshrc 파일 맨 아래에 추가한다.
    export UV_LINK_MODE=copy
    
  • 일회성 명령어 사용: 실행할 때마다 옵션을 붙여준다.
    uv sync --link-mode=copy
    

방법 2. 캐시 디렉토리 위치 변경하기 (성능 유지, 추천)

하드링크 고속 기능을 그대로 유지하면서 경고를 없애고 싶다면, uv 캐시 폴더의 위치를 프로젝트가 있는 드라이브(예: E 드라이브) 내부로 강제 지정하면 된다.

  • PowerShell (Windows):
    $env:UV_CACHE_DIR="E:\.uv_cache"
    
  • Bash (Linux / WSL):
    export UV_CACHE_DIR="/path/to/your/drive/.uv_cache"
    

환경 변수를 설정한 뒤 다시 uv sync를 실행하면 경고 없이 깔끔하게 작동할 것이다.

윈도우즈에선 시스펨 환경변수에 새로운 폴더를 등록하고 리눅스에선 .bashrc 에도 추가한다.

PyInstaller는 파이썬 스크립트(.py 파일)를 윈도우(.exe), 맥(.app), 리눅스 등에서 파이썬 설치 없이도 바로 실행할 수 있는 독립적인 실행 파일로 만들어주는 아주 유용한 도구다.

PyInstaller의 기본적인 사용법과 알아두면 좋은 주요 옵션들을 정리해 보았다.

1. PyInstaller 설치

먼저 터미널(또는 명령 프롬프트)을 열고 pip를 이용해 PyInstaller를 설치해야 한다.

 

Bash

pip install pyinstaller

2. 가장 기본적인 사용법

실행 파일로 만들고자 하는 파이썬 파일이 있는 폴더로 이동한 뒤, 터미널에 아래와 같이 입력한다. (예시 파일명이 main.py인 경우)

 

Bash

pyinstaller main.py

이렇게 하면 폴더 안에 build와 dist라는 폴더, 그리고 main.spec이라는 파일이 생성된다.

우리가 원하는 최종 실행 파일은 dist 폴더 안에 만들어진다.

3. 알아두면 매우 유용한 주요 옵션들

그냥 기본 명령어로 만들면 관련 라이브러리 파일들이 주렁주렁 달리거나 쓸데없이 까만 콘솔창이 뜨는 등 불편한 점이 많다. 그래서 보통 아래의 옵션들을 조합해서 사용한다.

  • -F 또는 --onefile (하나의 파일로 만들기):
    이 옵션이 가장 많이 쓰인다. 여러 개의 파일과 라이브러리를 하나로 묶어서 단일 .exe (또는 실행) 파일 하나만 딱 만들어준다. 배포하기에 훨씬 깔끔하다.
    Bash
    pyinstaller -F main.py

*   **`-w` 또는 `--noconsole` (콘솔 창 숨기기):**
    GUI 프로그램(PyQt, Tkinter, PySide 등)을 만들었을 때 사용한다. 이 옵션을 넣지 않으면 프로그램을 실행할 때 까만 도스(cmd) 창이 뒤에 같이 뜨는데, 이를 보이지 않게 숨겨준다.
    ```bash
    pyinstaller -w main.py
    ```

*   **`-n 이름` 또는 `--name 이름` (출력 파일 이름 지정):**
    만들어질 실행 파일의 이름을 파이썬 파일명과 다르게 지정하고 싶을 때 사용한다.
   
```bash
    pyinstaller -F -n "MySuperApp" main.py
    ```

*   **`-i 아이콘경로` 또는 `--icon 아이콘경로` (아이콘 설정):**
    실행 파일의 아이콘(보통 `.ico` 파일)을 지정한다.
    ```bash
    pyinstaller -F -i "my_icon.ico" main.py
    ```

### 4. 실전 종합 예시

GUI로 만든 파이썬 프로그램을, 아이콘을 넣어서, 단일 파일 하나로, 콘솔 창 없이 깔끔하게 만들고 싶다면 아래처럼 한 줄로 조합해서 사용하면 된다.
```bash
pyinstaller -F -w -i "app_icon.ico" -n "MyBestApp" main.py

4. 주의 및 참고 사항

  1. 용량 문제: -F (onefile) 옵션을 사용하면 파일 하나로 깔끔해지지만, 실행할 때 임시 폴더에 압축을 풀고 실행하는 과정을 거치기 때문에 초기 로딩 속도가 조금 느려질 수 있다.
  2. OS 종속성: 윈도우에서 PyInstaller를 실행하면 .exe 파일이 나오고, 맥에서 실행하면 맥용 실행 파일이 나온다. 윈도우에서 맥용 앱을 만들 수는 없으므로, 타겟 OS 환경에서 직접 빌드해야 한다.
  3. 외부 파일 포함: 프로그램 내부에서 이미지, 텍스트 파일, json 등 외부 데이터 파일을 읽어오는 경우, 기본적으로는 실행 파일 안에 포함되지 않는다. 이런 데이터 파일까지 실행 파일 안에 우겨넣으려면 .spec 파일을 직접 수정해야 하는 조금 더 복잡한 과정이 필요하다. (단순히 같은 폴더에 이미지 파일을 같이 두고 배포하는 방식을 먼저 권장한다.)

 

PyInstaller를 사용할 때 이미지, 텍스트, 설정 파일 등 외부 데이터 파일을 단일 실행 파일(.exe) 안에 함께 포함하려면 --add-data 옵션을 사용하거나 .spec 파일을 직접 수정해야 한다.

이 과정은 생각보다 까다로울 수 있는데, 가장 확실하고 대중적인 방법을 순서대로 설명해 주겠다.


5. 터미널(명령어)에서 --add-data 옵션 사용하기

간단하게 파일 한두 개를 포함할 때 사용하기 좋은 방법이다.

  • 명령어 구조: --add-data "원본_경로;실행_파일_내부_경로"
  • 주의사항: 윈도우에서는 경로 구분자로 세미콜론(;)을 쓰고, 맥이나 리눅스에서는 콜론(:)을 사용한다.

사용 예시 (윈도우 기준):

my_image.png라는 파일을 실행 파일과 같은 위치(.)에 포함시키고 싶다면 아래와 같이 입력한다.

 

Bash

pyinstaller -F -w --add-data "my_image.png;." main.py

만약 data라는 폴더 전체를 포함하고 싶다면 이렇게 한다.

 

Bash

pyinstaller -F -w --add-data "data/*;data" main.py

6. .spec 파일 수정하기 (권장)

포함해야 할 파일이나 폴더가 많아지면 명령어가 너무 길어져서 관리가 힘들다. 이때는 .spec 파일을 수정하는 것이 훨씬 깔끔하다.

  1. 먼저 기본 명령어로 빌드를 한 번 실행하여 main.spec 파일을 생성한다. (pyinstaller -F main.py)
  2. 생성된 main.spec 파일을 메모장이나 코드 에디터로 연다.
  3. Analysis 부분 안에 있는 datas=[] 항목을 찾아서 아래처럼 수정한다. (형식: ('원본 경로', '내부 경로'))

 

Python

 

a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    # 이 부분을 수정한다. 여러 개면 콤마로 구분한다.
    datas=[
        ('my_image.png', '.'),
        ('config.json', '.'),
        ('data_folder', 'data_folder')
    ],
    # ... 생략 ...
)

  1. 수정을 저장하고, 터미널에서 .py 파일 대신 .spec 파일로 다시 빌드한다.

 

Bash



pyinstaller main.spec


🚨 [가장 중요] 파이썬 소스코드 수정 (sys._MEIPASS)

많은 사람들이 파일 포함 옵션을 주고도 "파일을 찾을 수 없다"는 에러를 겪는다.

-F (단일 파일) 옵션으로 만든 프로그램은 실행될 때 내부에 포함된 파일들을 컴퓨터의 임시 폴더(Temp) 에 압축을 풀고 실행한다. 따라서 파이썬 코드 안에서 파일 경로를 읽을 때, 현재 폴더가 아니라 그 '임시 폴더'의 경로를 찾도록 코드를 수정해 주어야 한다.

파이썬 코드 최상단에 아래의 함수를 추가하고, 외부 파일을 불러올 때마다 이 함수를 거치도록 수정해야 한다.

 

Python



import os
import sys

def resource_path(relative_path):
    """ 실행 파일 내부의 임시 폴더 경로를 반환하는 함수 """
    try:
        # PyInstaller에 의해 실행될 때 (임시 폴더 경로)
        base_path = sys._MEIPASS
    except Exception:
        # 파이썬 원본 스크립트로 실행될 때 (현재 경로)
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

# --- 실제 사용 예시 ---
# 기존 코드: image_path = "my_image.png"
# 변경 코드: image_path = resource_path("my_image.png")

# 예: PySide6에서 이미지를 불러올 때
# pixmap = QPixmap(resource_path("my_image.png"))

이렇게 코드를 구성하면, 평소에 에디터에서 파이썬 코드를 테스트할 때도 정상적으로 작동하고, PyInstaller로 빌드한 후 .exe 파일로 실행할 때도 임시 폴더에서 파일을 정확하게 찾아오게 된다.

 

환경은 우분투 26.04 라고 가정한다.

 

1. 시스템 필수 패키지 및 자바(JDK 17) 설치

1-1) 설치

 

sudo apt update

sudo apt install -y openjdk-17-jdk git curl unzip cmake ninja-build clang pkg-config

 

설치가 끝난 후 java -version 을 입력하여 17 버전이 정상적으로 잡히는지 확인한다.

만약 다른 버전이 잡힌다면

 

1-2) 자바 버전 교체

 

sudo update-alternatives --config java

 

이 명령어를 치면 화면에 현재 설치된 자바 버전들의 목록과 번호가 뜬다. 그중에서 java-17-openjdk 가 포함된 줄을 찾은 뒤, 해당 줄의 맨 앞에 있는 번호를 키보드로 입력하고 엔터를 누른다.

 

1-3) 자바 컴파일러 버전 교체

앱 빌드 시 내부적으로 사용하는 컴파일러의 버전도 똑같이 17로 맞춰주는 것이 안전하다.

 

sudo update-alternatives --config javac

 

마찬가지로 17 버전에 해당하는 번호를 누르고 엔터를 친다.

 

1-4) JAVA_HOME 환경 변수

주입 안드로이드 컴파일러(Gradle)는 버전을 찾을 때 환경 변수를 우선적으로 참조하는 경향이 있다. 현재 터미널 창에 17 버전의 경로를 확실하게 못 박아준다.

 

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

export PATH=$JAVA_HOME/bin:$PATH

 

또는 영구적으로 효과를 나타내게 하려면 .bashrc 파일에 같은 내용을 입력해준다. 그리고

source ~/.bashrc

명령을 내린다.

 

2. 안드로이드 스튜디오 설치

 

2-1) 설치

https://developer.android.com/studio?hl=ko 에 방문하여 안드로이드 스튜디오를 다운 받고 설치한다.

 

2-2) 설치 후 세팅 1

설치 후 {안드로이드 스튜디오 설치 폴더}/bin 을 PATH 에 넣어준다.

export PATH= {안드로이드 스튜디오 설치 폴더}/bin :$PATH

와 같이 1-3) 과 같은 방법으로 추가한다.

 

2-3) 설치 후 세팅 2

안드로이드 스튜디오에서 Settings - Languages & Frameworks - Android SDK - SDK Tools 에서

 

Android SDK Build-Tools

NDK (Side by side)

Android SDK Command-line Tools

CMake

Android Emulator

Android SDK Platform-Tools

 

등 항목이 체크되어 함께 설치되었는지 확인하고 안되어 잇으면 체크하고 적용한다.

 

3. uv 패키지 매니저 설치

curl -LsSf https://astral.sh/uv/install.sh | sh

 

4. 프로젝트 초기화 및 flet 설치

 

mkdir flet_linux_app

cd flet_linux_app

uv init uv add flet

 

5. 안드로이드 빌드 실행

5-1) 빌드 명령

 

uv run flet build apk --yes -v

 

5-2) apk 확인

build/apk 폴더에서 정상적으로 apk 파일이 만들어졌는지 확인한다.

 

6. 안드로이드에 설치

다음 둘 중 하나로 설치하여 실행해 본다.

 

6-1) 파일 전송 및 스마트폰 직접 설치

  1. 파일 전송: 카카오톡 '내게 쓰기', 이메일, 구글 드라이브 등을 활용하여 컴퓨터에 있는 .apk 파일을 스마트폰으로 전송하고 다운로드한다. (USB 케이블로 폰을 연결해 직접 복사해 넣어도 된다.)
  2. 앱 설치: 스마트폰의 '내 파일(파일 관리자)' 앱을 열고 다운로드한 .apk 파일을 터치한다.
  3. 보안 권한 허용: 구글 플레이 스토어를 거치지 않고 직접 만든 파일이므로 설치가 차단된다.
    • "출처를 알 수 없는 앱 설치" 경고가 뜨면 '설정'을 눌러 권한을 허용해 준다.
    • "Play 프로텍트에 의해 차단됨" 경고가 뜨면 '세부정보 보기'를 누른 뒤 '그래도 설치(안전하지 않음)'를 눌러 진행한다. 본인이 직접 짠 코드이므로 안심해도 된다.

6-2) 터미널 명령어로 바로 설치

현재 안드로이드 SDK가 모두 세팅되어 있으므로, 폰을 컴퓨터에 연결한 상태라면 명령어로 아주 우아하게 설치할 수 있다.

  1. 스마트폰의 설정에서 개발자 옵션을 켜고 USB 디버깅을 활성화한다.
  2. 스마트폰을 컴퓨터에 USB 케이블로 연결한다. (폰 화면에 디버깅 허용 팝업이 뜨면 '허용'을 누른다.)
  3. 다음 명령을 입력한다.   adb install build/apk/app-release.apk

연도의 범위를 주로 1900년부터 2100년까지로 설정하는 이유는 프로그램의 실용성과 시스템의 안정성을 고려한 프로그래밍의 가장 일반적인 관행이기 때문이다. 구체적인 이유는 다음과 같다.

1. 대한민국의 현대 공휴일 체계
우리가 코드에 적용한 holidays 라이브러리는 대한민국의 현대적인 공휴일(광복절, 삼일절, 개천절 등)을 계산해 준다. 이러한 현대식 법정 공휴일 개념은 1900년대 중반 대한민국 정부 수립 이후부터 확립되었다. 따라서 1800년이나 1500년 같은 과거 연도를 입력해 보았자 우리가 기대하는 현대의 공휴일 데이터가 제대로 나오지 않으므로 실용성이 없다.

2. 일반적인 달력의 사용 목적
사용자가 달력 프로그램을 통해 과거의 기록(생일, 기념일 등)을 찾아보거나 미래의 일정을 계획하는 범위는 대개 현재를 기준으로 앞뒤 100년 안팎이다. 2100년 이후나 1900년 이전으로 스크롤을 넘길 일이 현실적으로 거의 없기 때문에, 범위를 무한정 늘려놓으면 오히려 숫자를 조작할 때 불편함만 커지게 된다.

3. 파이썬 및 시스템의 날짜 계산 안정성
컴퓨터 시스템과 프로그래밍 언어에서 날짜를 다룰 때 극단적으로 오래된 과거 연도나 너무 먼 미래 연도를 입력하면, 달력 알고리즘(윤년 계산 등)이나 시스템의 시간 처리 규격에서 예상치 못한 에러가 발생할 가능성이 있다. 1900년에서 2100년 사이는 파이썬의 datetime 모듈과 윈도우 등 모든 운영체제에서 가장 완벽하게 검증된 안전한 구간이다.

4. 파이썬 내장 라이브러리의 한계
우리가 코드에서 사용한 파이썬의 표준 날짜 모듈(datetime)과 달력 모듈(calendar)은 내부적으로 서기 1년(MINYEAR)부터 서기 9999년(MAXYEAR)까지만 정상적으로 처리하도록 엄격하게 설계되어 있다. 기원전을 의미하는 음수(예: -3000)나 0년이라는 숫자를 해당 라이브러리에 집어넣는 순간, 허용 범위를 벗어났다며 즉각 에러(ValueError)를 뿜어내고 프로그램이 강제로 종료된다.

5. 역사적 달력 체계의 모순
현재 우리가 사용하는 날짜 체계인 그레고리력(1년 365일, 4년마다 2월 29일 추가 등)은 1582년에 처음 도입되었다. 수학적인 공식을 사용해 1582년 이전의 과거로 거슬러 올라가 기원전 3000년의 요일을 억지로 끼워 맞추는 것은 가능하지만, 당시 고대 수메르나 이집트 사람들이 쓰던 실제 역법이나 계절과는 전혀 들어맞지 않는 무의미한 숫자판이 되어버린다.

6. 그레고리력 (실제 역사)
현재 우리가 쓰는 달력이다. 1582년 10월 15일에 교황 그레고리오 13세가 제정했다. 따라서 역사적 사실에 부합하는 진짜 그레고리력은 1582년부터 유효한 것이 맞다. 이 달력이 만들어지기 전 유럽 사람들은 율리우스력이라는 다른 규칙의 달력을 사용했다.

7. 선그레고리력 (소급 적용된 가상의 달력)
이름에 붙은 '선(Proleptic)'이라는 단어는 소급 적용을 의미한다. 즉, 1582년에 만들어진 달력의 수학적 규칙(4년에 한 번 윤년 등)을 1582년 이전의 과거로 똑같이 밀어붙여서 계산한 것이다.

결론적으로 파이썬으로 만년달력을 만들 때는 년도의 범위를 1 - 9999 년으로 설정해야하며 실제적은 용도로는 1900 - 2100 정도로 세팅하는 것이 바람직하다.

 

일반적인 사실은 다음 글을 참고하기 바란다.

2026.04.09 - [IT/Python] - Tesseract 와 EasyOCR을 비교 : 일반적으로 알려져 있는 사항

 

Tesseract 와 EasyOCR을 비교 : 일반적으로 알려져 있는 사항

테서랙트(Tesseract)와 이지OCR(EasyOCR)은 파이썬 환경에서 가장 널리 쓰이는 두 가지 오프라인 OCR 엔진이다. 두 엔진은 개발 방식과 장단점이 아주 명확하게 대비된다. 표1. 두 엔진의 핵심적인 차이

mmemories.tistory.com

 

다양하게 경험하진 못했고, 파이썬 코드를 만들어 책과 웹사이트 캡쳐한 것을 가지고 간단하게 테스트해 보았다.

 

결과적으로

  1. 스크린 캡쳐나 아니면 깨끗하게 잘 스캔된 책과 같은 페이지들을 대상으로한 인식은 2가지가 크게 다르지 않았다.
  2. 대신 EasyOCR은 로딩시에 시간이 아주 많이 걸린다. 5-10초 정도?
  3. 일반적으로 알려진 사실은 지저분하게 스캔된 책은 EasyOCR이 더 잘된다고 되어 있으나 확인하지 못했다. 지저분한 것을 구하지 못했기 때문에..
  4. 둘 다 영어는 아주 잘 되지만 한글은 아주 잘되진 않고 대충된다. 한글인식률을 높이려면 아마도 유료엔진을 사용하거나 Paddle OCR을 사용해야할 것 같다. 패들은 현재 파이썬 3.14를 지원하지 않는다.

+ Recent posts