diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fefc95..c51af3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,31 +165,46 @@ jobs: if: startsWith(matrix.os, 'macos') shell: bash run: | + # GUI dependencies are installed by the earlier `uv sync --group dev` step which prepares .venv + # Use the environment prepared by uv to run PyInstaller via the uvx wrapper. + + # Human readable display name used both for Info.plist and the installer title + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + DISPLAY_NAME="MkPFS ${VERSION}" + VERSION_STRING="${VERSION}" + else + DISPLAY_NAME="MkPFS ${VERSION_SHORT} (${COMMIT_SHORT})" + VERSION_STRING="${VERSION_SHORT}-${COMMIT_SHORT}" + fi + + # Ensure PyInstaller can see packages installed into the repo .venv + SITE_PACKAGES="$PWD/.venv/lib/python3.11/site-packages" + uvx pyinstaller \ --noconfirm \ --clean \ --windowed \ --onedir \ + --additional-hooks-dir pyinstaller-hooks \ + --paths "$SITE_PACKAGES" \ --name MkPFS \ --icon dist/MkPFS.icns \ --collect-all customtkinter \ + --collect-submodules customtkinter \ --collect-all PIL \ --add-data "assets:assets" \ --hidden-import cryptography \ + --hidden-import customtkinter \ mkpfs/gui.py + # Post-process app bundle Info.plist to set user-friendly display name and version PLIST="dist/MkPFS.app/Contents/Info.plist" if [ -f "$PLIST" ]; then - # human readable display name - if [[ "${GITHUB_REF}" == refs/tags/* ]]; then - DISPLAY_NAME="MkPFS ${VERSION}" - else - DISPLAY_NAME="MkPFS ${VERSION_SHORT} (${COMMIT_SHORT})" - fi /usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName \"${DISPLAY_NAME}\"" "$PLIST" || /usr/libexec/PlistBuddy -c "Add :CFBundleDisplayName string \"${DISPLAY_NAME}\"" "$PLIST" || true /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString \"${VERSION_SHORT}\"" "$PLIST" || /usr/libexec/PlistBuddy -c "Add :CFBundleShortVersionString string \"${VERSION_SHORT}\"" "$PLIST" || true /usr/libexec/PlistBuddy -c "Set :CFBundleVersion \"${ART_VERSION}\"" "$PLIST" || /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string \"${ART_VERSION}\"" "$PLIST" || true fi + # Diagnostic: list app bundle and check executable linkage echo "--- Info.plist ---" /usr/libexec/PlistBuddy -c "Print" "$PLIST" || true @@ -201,6 +216,7 @@ jobs: echo "--- otool -L $exe ---"; otool -L "$exe" || true; done + # Ensure CFBundleExecutable matches actual executable and make it executable if [ -d dist/MkPFS.app/Contents/MacOS ]; then exe_name=$(basename "$(ls dist/MkPFS.app/Contents/MacOS | head -n1)" ) || true @@ -209,6 +225,7 @@ jobs: chmod +x "dist/MkPFS.app/Contents/MacOS/$exe_name" || true fi fi + # create component package then product package so installer shows proper title/version # sanitize ART_VERSION for filename use ART_FILE_SAFE=$(echo "$ART_VERSION" | tr ' ' '_' | tr -d '()') @@ -218,7 +235,8 @@ jobs: --version "${ART_VERSION}" \ --identifier "com.psbrew.mkpfs" \ dist/mkpfs-component.pkg - productbuild --identifier com.psbrew.mkpfs --version "${ART_VERSION}" --package dist/mkpfs-component.pkg "dist/mkpfs-${ART_FILE_SAFE}-macos-${ARCH}.pkg" || cp dist/mkpfs-component.pkg "dist/mkpfs-${ART_FILE_SAFE}-macos-${ARCH}.pkg" + # Use a human-friendly installer title so macOS Installer GUI shows product name/version + productbuild --identifier com.psbrew.mkpfs --version "${ART_VERSION}" --title "MkPFS" --package dist/mkpfs-component.pkg "dist/mkpfs-${ART_FILE_SAFE}-macos-${ARCH}.pkg" || cp dist/mkpfs-component.pkg "dist/mkpfs-${ART_FILE_SAFE}-macos-${ARCH}.pkg" rm -f dist/mkpfs-component.pkg || true - name: Build Linux onefile GUI diff --git a/assets/images/icon.png b/assets/images/icon.png index 314feab..fd1b405 100644 Binary files a/assets/images/icon.png and b/assets/images/icon.png differ diff --git a/scripts/pyinstaller-hook-customtkinter.py b/scripts/pyinstaller-hook-customtkinter.py new file mode 100644 index 0000000..5a6fc96 --- /dev/null +++ b/scripts/pyinstaller-hook-customtkinter.py @@ -0,0 +1,7 @@ +# PyInstaller hook for customtkinter +# Collect submodules and package data to ensure PyInstaller bundles the package correctly. +from PyInstaller.utils.hooks import collect_data_files, collect_submodules + +hiddenimports = collect_submodules("customtkinter") + +datas = collect_data_files("customtkinter")