Skip to content

Đóng gói & Phân phối

Bài học này hướng dẫn cách đóng gói ứng dụng thành file EXE di động để phân phối cho người dùng cuối.

1. Tổng quan

1.1. Mục tiêu

Tạo Gói di động mà người dùng chỉ cần:

  1. Giải nén
  2. Tạo file .env với khóa API
  3. Chạy GuideAI.exe

1.2. Thành phần gói

dist/GuideAI/
├── GuideAI.exe          # File thực thi chính
├── browsers/            # Chromium browser (~300MB)
│   ├── chromium-xxxx/
│   └── ffmpeg-xxxx/
├── .env.example         # Template cấu hình
├── README.txt           # Hướng dẫn cho người dùng
└── [các file hỗ trợ]

2. File cấu hình PyInstaller

2.1. Cấu trúc guide_ai.spec

python
# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller spec file for Guide AI Tool
Build command: pyinstaller guide_ai.spec
"""

import sys
import os
from pathlib import Path

block_cipher = None

# Tìm đường dẫn Playwright browsers
def get_playwright_browsers_path():
    """Tìm thư mục chứa Playwright browsers"""
    possible_paths = [
        os.path.join(os.environ.get('LOCALAPPDATA', ''), 'ms-playwright'),
        os.path.join(os.environ.get('USERPROFILE', ''), '.cache', 'ms-playwright'),
    ]
    for path in possible_paths:
        if os.path.exists(path):
            return path
    return None

2.2. Các file dữ liệu

python
# Collect data files
datas = [
    ('src', 'src'),           # Source modules
    ('.env.example', '.'),    # Config template
]

# Thêm Playwright driver (QUAN TRỌNG cho recording)
playwright_pkg = get_playwright_package_path()
if playwright_pkg:
    driver_path = os.path.join(playwright_pkg, 'driver')
    if os.path.exists(driver_path):
        datas.append((driver_path, 'playwright/driver'))
        print(f"[INFO] Bundling Playwright driver from: {driver_path}")

2.3. Import ẩn

python
hiddenimports=[
    # Playwright
    'playwright',
    'playwright.sync_api',
    'playwright._impl',
    'playwright._impl._driver',
    
    # AI Libraries
    'google.generativeai',
    'google.ai',
    'openai',
    
    # Utilities
    'PIL', 'PIL.Image',
    'jinja2',
    'dotenv',
    'requests',
    
    # Internal modules
    'src.analyzer',
    'src.config_manager',
    'src.doc_generator',
    'src.models',
    'src.orchestrator',
    'src.parser',
    'src.recorder',
    'src.replayer',
    'src.uploader',
    'src.auth_manager',
]

2.4. Loại trừ (Giảm kích thước)

python
excludes=[
    'pytest',
    'pytest_mock',
    'hypothesis',
    'tests',
    'tkinter',
    'matplotlib',
    'numpy',
    'pandas',
]

2.5. Cấu hình EXE

python
exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='GuideAI',
    debug=False,
    strip=False,
    upx=True,           # Nén với UPX
    console=True,       # Console app (không phải GUI)
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    name='GuideAI',
)

3. Hook thời gian chạy

3.1. runtime_hook.py

File này chạy trước khi ứng dụng khởi động:

python
import os
import sys

def setup_environment():
    """Setup environment cho bundled EXE."""
    
    if getattr(sys, 'frozen', False):
        # Đang chạy từ EXE
        exe_dir = os.path.dirname(sys.executable)
        
        # Set PLAYWRIGHT_BROWSERS_PATH
        browsers_path = os.path.join(exe_dir, 'browsers')
        if os.path.exists(browsers_path):
            os.environ['PLAYWRIGHT_BROWSERS_PATH'] = browsers_path
        
        # Set working directory
        os.chdir(exe_dir)

setup_environment()

4. Script đóng gói

4.1. build_exe.bat (Windows)

batch
@echo off
echo ========================================
echo Building GuideAI Portable Package
echo ========================================

REM Activate venv
call .venv\Scripts\activate

REM Clean previous build
rmdir /s /q dist 2>nul
rmdir /s /q build 2>nul

REM Build with PyInstaller
pyinstaller guide_ai.spec --clean

REM Copy browsers
echo Copying Playwright browsers...
xcopy /E /I "%LOCALAPPDATA%\ms-playwright" "dist\GuideAI\browsers"

REM Copy config files
copy .env.example dist\GuideAI\
copy README.txt dist\GuideAI\ 2>nul

echo ========================================
echo Build complete! Output: dist\GuideAI\
echo ========================================

4.2. build_exe.ps1 (PowerShell)

powershell
Write-Host "Building GuideAI Portable Package" -ForegroundColor Cyan

# Activate venv
& .\.venv\Scripts\Activate.ps1

# Clean
Remove-Item -Recurse -Force dist, build -ErrorAction SilentlyContinue

# Build
pyinstaller guide_ai.spec --clean

# Copy browsers
$browsersPath = "$env:LOCALAPPDATA\ms-playwright"
if (Test-Path $browsersPath) {
    Copy-Item -Recurse $browsersPath "dist\GuideAI\browsers"
}

# Copy configs
Copy-Item .env.example "dist\GuideAI\"

Write-Host "Build complete!" -ForegroundColor Green

5. Xử lý Playwright trong EXE

5.1. Phát hiện chế độ đóng gói

python
def is_bundled_exe() -> bool:
    """Check if running as bundled EXE."""
    return getattr(sys, 'frozen', False)

5.2. Lấy driver Playwright

python
def get_playwright_driver() -> tuple:
    """Get Playwright driver paths."""
    
    if is_bundled_exe():
        base_path = getattr(sys, '_MEIPASS', os.path.dirname(sys.executable))
        exe_dir = os.path.dirname(sys.executable)
        
        search_paths = [
            os.path.join(base_path, 'playwright', 'driver'),
            os.path.join(exe_dir, 'playwright', 'driver'),
        ]
        
        for driver_path in search_paths:
            node_exe = os.path.join(driver_path, 'node.exe')
            cli_js = os.path.join(driver_path, 'package', 'cli.js')
            
            if os.path.exists(node_exe) and os.path.exists(cli_js):
                return (node_exe, cli_js)
        
        raise FileNotFoundError("Playwright driver not found")
    
    # Normal Python
    from playwright._impl._driver import compute_driver_executable
    return compute_driver_executable()

5.3. Bắt đầu ghi từ EXE

python
def start_recording(url: str, output_path: Path) -> bool:
    if is_bundled_exe():
        node_exe, cli_js = get_playwright_driver()
        cmd = [node_exe, cli_js, "codegen",
               "--target", "python", "-o", str(output_path), url]
    else:
        cmd = [sys.executable, "-m", "playwright", "codegen",
               "--target", "python", "-o", str(output_path), url]
    
    subprocess.run(cmd, check=True)

6. Kích thước & Tối ưu

6.1. Phân tích kích thước

ComponentSizeGhi chú
GuideAI.exe~50MBPython + dependencies
Chromium browser~300MBPlaywright browser
Node.js driver~50MBPlaywright driver
Total~400-500MBSau nén: ~200MB

6.2. Tối ưu kích thước

python
# Trong spec file
excludes=[
    'pytest', 'hypothesis',  # Testing
    'tkinter',               # GUI (không dùng)
    'matplotlib', 'numpy',   # Data science
]

7. Phân phối

7.1. Đóng gói

batch
REM Nén thành ZIP
powershell Compress-Archive -Path dist\GuideAI -DestinationPath GuideAI-v1.0.zip

7.2. Hướng dẫn cho người dùng

README.txt
==========

HƯỚNG DẪN SỬ DỤNG GUIDEAI

1. Giải nén file GuideAI-v1.0.zip

2. Tạo file .env trong thư mục GuideAI với nội dung:
   GOOGLE_API_KEY=your_api_key_here
   
   (Lấy API key miễn phí tại: https://aistudio.google.com/apikey)

3. Chạy GuideAI.exe

4. Làm theo hướng dẫn trên màn hình

8. Xử lý sự cố

8.1. "Không tìm thấy trình duyệt"

Nguyên nhân: Thư mục browsers/ không được sao chép
Giải pháp: 
  1. Kiểm tra dist/GuideAI/browsers/ tồn tại
  2. Nếu không, sao chép thủ công từ %LOCALAPPDATA%\ms-playwright

8.2. Phần mềm diệt virus cảnh báo

Nguyên nhân: EXE từ PyInstaller thường bị cảnh báo nhầm
Giải pháp:
  1. Thêm ngoại lệ trong phần mềm diệt virus
  2. Hoặc ký EXE với chứng chỉ ký mã

8.3. "Không tìm thấy GOOGLE_API_KEY"

Nguyên nhân: Chưa tạo file .env
Giải pháp:
  1. Sao chép .env.example thành .env
  2. Điền khóa API vào file .env

9. Ma trận tương thích

WindowsHỗ trợGhi chú
Windows 11Đầy đủ
Windows 10Đầy đủ
Windows 8.1⚠️Có thể hoạt động
Windows 7Không hỗ trợ

Yêu cầu:

  • Chỉ hỗ trợ 64-bit (x64)
  • ~500MB dung lượng đĩa
  • Kết nối internet (cho API AI)

Tổng kết

Quy trình đóng gói EXE di động:

  1. File spec PyInstaller: Cấu hình chi tiết các phụ thuộc
  2. Hook thời gian chạy: Thiết lập môi trường khi khởi động
  3. Đóng gói Playwright: Sao chép driver + trình duyệt
  4. Phân phối: Gói ZIP kèm hướng dẫn

Bài học tiếp theo

Phân tích công nghệ & Tình huống thực tế

Internal documentation for iNET Portal