Skip to content

Commit 75d5668

Browse files
committed
Refactor macOS build workflows to use PyInstaller directly
- Updated the macOS build workflows for both ARM64 and Intel architectures to utilize PyInstaller directly instead of creating a spec file. This change simplifies the build process and addresses import issues. - Enhanced the app bundle creation steps, including the generation of the Info.plist file and ensuring the main executable is executable. - Improved logging and verification steps to confirm successful builds and artifact integrity.
1 parent 8310b52 commit 75d5668

File tree

2 files changed

+176
-172
lines changed

2 files changed

+176
-172
lines changed

.github/workflows/macos-build-arm64.yml

Lines changed: 88 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -49,89 +49,90 @@ jobs:
4949
pip install markdown==3.4.3
5050
pip install pyyaml==6.0
5151
52-
- name: Create PyInstaller spec
52+
- name: Build macOS executable
5353
run: |
54-
cat > Huntarr-macos-arm64.spec << 'EOF'
55-
# -*- mode: python ; coding: utf-8 -*-
56-
from PyInstaller.building.api import PYZ, EXE, COLLECT, BUNDLE
57-
from PyInstaller.building.build_main import Analysis
58-
import os
59-
60-
block_cipher = None
61-
62-
# Data files to include
63-
datas = [
64-
('frontend', 'frontend'),
65-
('version.txt', '.'),
66-
('README.md', '.'),
67-
('LICENSE', '.'),
68-
('src', 'src'),
69-
]
70-
71-
# Add apprise data files
72-
try:
73-
import apprise
74-
apprise_path = os.path.dirname(apprise.__file__)
75-
apprise_attachment_path = os.path.join(apprise_path, 'attachment')
76-
apprise_plugins_path = os.path.join(apprise_path, 'plugins')
77-
apprise_config_path = os.path.join(apprise_path, 'config')
78-
79-
if os.path.exists(apprise_attachment_path):
80-
datas.append((apprise_attachment_path, 'apprise/attachment'))
81-
if os.path.exists(apprise_plugins_path):
82-
datas.append((apprise_plugins_path, 'apprise/plugins'))
83-
if os.path.exists(apprise_config_path):
84-
datas.append((apprise_config_path, 'apprise/config'))
85-
except ImportError:
86-
print("Warning: apprise not found, skipping apprise data files")
87-
88-
a = Analysis(
89-
['main.py'],
90-
pathex=['.'],
91-
binaries=[],
92-
datas=datas,
93-
hiddenimports=[
94-
'flask', 'flask.json', 'requests', 'waitress', 'bcrypt', 'qrcode', 'PIL',
95-
'pyotp', 'qrcode.image.pil', 'routes', 'main', 'apprise', 'apprise.common',
96-
'apprise.conversion', 'apprise.decorators', 'apprise.locale', 'apprise.logger',
97-
'apprise.manager', 'apprise.utils', 'apprise.URLBase', 'apprise.AppriseAsset',
98-
'apprise.AppriseAttachment', 'apprise.AppriseConfig', 'apprise.cli',
99-
'apprise.config', 'apprise.attachment', 'apprise.plugins', 'markdown', 'yaml', 'cryptography',
100-
],
101-
hookspath=[],
102-
hooksconfig={},
103-
runtime_hooks=[],
104-
excludes=[],
105-
cipher=block_cipher,
106-
noarchive=False,
107-
)
108-
109-
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
110-
111-
exe = EXE(
112-
pyz, a.scripts, [], exclude_binaries=True, name='Huntarr',
113-
debug=False, bootloader_ignore_signals=False, strip=False, upx=True,
114-
console=False, disable_windowed_traceback=False, argv_emulation=True,
115-
target_arch='arm64', codesign_identity=None, entitlements_file=None,
116-
)
117-
118-
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name='Huntarr')
119-
120-
app = BUNDLE(
121-
coll, name='Huntarr.app', bundle_identifier='io.huntarr.app',
122-
info_plist={
123-
'CFBundleShortVersionString': '${{ steps.meta.outputs.VERSION }}',
124-
'CFBundleVersion': '${{ steps.meta.outputs.VERSION }}',
125-
'NSHighResolutionCapable': True,
126-
'NSRequiresAquaSystemAppearance': False,
127-
'LSUIElement': False,
128-
'CFBundleDocumentTypes': [],
129-
'NSPrincipalClass': 'NSApplication',
130-
'CFBundleExecutable': 'Huntarr',
131-
'CFBundleIconFile': 'icon.icns',
132-
},
133-
)
134-
EOF
54+
# Use PyInstaller directly without spec file to avoid import issues
55+
python -m PyInstaller \
56+
--name="Huntarr" \
57+
--onedir \
58+
--windowed \
59+
--target-arch=arm64 \
60+
--add-data="frontend:frontend" \
61+
--add-data="version.txt:." \
62+
--add-data="README.md:." \
63+
--add-data="LICENSE:." \
64+
--add-data="src:src" \
65+
--hidden-import="flask" \
66+
--hidden-import="flask.json" \
67+
--hidden-import="requests" \
68+
--hidden-import="waitress" \
69+
--hidden-import="bcrypt" \
70+
--hidden-import="qrcode" \
71+
--hidden-import="PIL" \
72+
--hidden-import="pyotp" \
73+
--hidden-import="qrcode.image.pil" \
74+
--hidden-import="routes" \
75+
--hidden-import="main" \
76+
--hidden-import="apprise" \
77+
--hidden-import="apprise.common" \
78+
--hidden-import="apprise.conversion" \
79+
--hidden-import="apprise.decorators" \
80+
--hidden-import="apprise.locale" \
81+
--hidden-import="apprise.logger" \
82+
--hidden-import="apprise.manager" \
83+
--hidden-import="apprise.utils" \
84+
--hidden-import="apprise.URLBase" \
85+
--hidden-import="apprise.AppriseAsset" \
86+
--hidden-import="apprise.AppriseAttachment" \
87+
--hidden-import="apprise.AppriseConfig" \
88+
--hidden-import="apprise.cli" \
89+
--hidden-import="apprise.config" \
90+
--hidden-import="apprise.attachment" \
91+
--hidden-import="apprise.plugins" \
92+
--hidden-import="markdown" \
93+
--hidden-import="yaml" \
94+
--hidden-import="cryptography" \
95+
--clean \
96+
--noconfirm \
97+
main.py
98+
99+
# Create a simple app bundle structure manually
100+
echo "Creating app bundle structure..."
101+
mkdir -p "dist/Huntarr.app/Contents/MacOS"
102+
mkdir -p "dist/Huntarr.app/Contents/Resources"
103+
104+
# Copy the executable
105+
cp -r "dist/Huntarr/"* "dist/Huntarr.app/Contents/MacOS/"
106+
107+
# Create Info.plist
108+
cat > "dist/Huntarr.app/Contents/Info.plist" << 'PLIST_EOF'
109+
<?xml version="1.0" encoding="UTF-8"?>
110+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
111+
<plist version="1.0">
112+
<dict>
113+
<key>CFBundleExecutable</key>
114+
<string>Huntarr</string>
115+
<key>CFBundleIdentifier</key>
116+
<string>io.huntarr.app</string>
117+
<key>CFBundleName</key>
118+
<string>Huntarr</string>
119+
<key>CFBundleVersion</key>
120+
<string>${{ steps.meta.outputs.VERSION }}</string>
121+
<key>CFBundleShortVersionString</key>
122+
<string>${{ steps.meta.outputs.VERSION }}</string>
123+
<key>CFBundleInfoDictionaryVersion</key>
124+
<string>6.0</string>
125+
<key>CFBundlePackageType</key>
126+
<string>APPL</string>
127+
<key>NSHighResolutionCapable</key>
128+
<true/>
129+
<key>NSRequiresAquaSystemAppearance</key>
130+
<false/>
131+
<key>LSUIElement</key>
132+
<false/>
133+
</dict>
134+
</plist>
135+
PLIST_EOF
135136
136137
- name: Create app icon
137138
run: |
@@ -186,19 +187,20 @@ jobs:
186187
cp /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns icon.icns 2>/dev/null || touch icon.icns
187188
fi
188189
189-
- name: Build macOS app
190+
- name: Finalize app bundle
190191
run: |
191-
# Build the app
192-
python -m PyInstaller Huntarr-macos-arm64.spec --clean --noconfirm
193-
194192
# Copy icon into the app bundle
195193
if [ -f "icon.icns" ]; then
196194
cp icon.icns dist/Huntarr.app/Contents/Resources/icon.icns || true
197195
fi
198196
197+
# Make sure the main executable is executable
198+
chmod +x dist/Huntarr.app/Contents/MacOS/Huntarr
199+
199200
# Verify the build
200201
echo "Checking built app:"
201202
ls -la dist/
203+
ls -la dist/Huntarr.app/Contents/MacOS/
202204
file dist/Huntarr.app/Contents/MacOS/Huntarr
203205
204206
# Test that the app can at least start (basic smoke test)

.github/workflows/macos-build-intel.yml

Lines changed: 88 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -49,89 +49,90 @@ jobs:
4949
pip install markdown==3.4.3
5050
pip install pyyaml==6.0
5151
52-
- name: Create PyInstaller spec
52+
- name: Build macOS executable
5353
run: |
54-
cat > Huntarr-macos-intel.spec << 'EOF'
55-
# -*- mode: python ; coding: utf-8 -*-
56-
from PyInstaller.building.api import PYZ, EXE, COLLECT, BUNDLE
57-
from PyInstaller.building.build_main import Analysis
58-
import os
59-
60-
block_cipher = None
61-
62-
# Data files to include
63-
datas = [
64-
('frontend', 'frontend'),
65-
('version.txt', '.'),
66-
('README.md', '.'),
67-
('LICENSE', '.'),
68-
('src', 'src'),
69-
]
70-
71-
# Add apprise data files
72-
try:
73-
import apprise
74-
apprise_path = os.path.dirname(apprise.__file__)
75-
apprise_attachment_path = os.path.join(apprise_path, 'attachment')
76-
apprise_plugins_path = os.path.join(apprise_path, 'plugins')
77-
apprise_config_path = os.path.join(apprise_path, 'config')
78-
79-
if os.path.exists(apprise_attachment_path):
80-
datas.append((apprise_attachment_path, 'apprise/attachment'))
81-
if os.path.exists(apprise_plugins_path):
82-
datas.append((apprise_plugins_path, 'apprise/plugins'))
83-
if os.path.exists(apprise_config_path):
84-
datas.append((apprise_config_path, 'apprise/config'))
85-
except ImportError:
86-
print("Warning: apprise not found, skipping apprise data files")
87-
88-
a = Analysis(
89-
['main.py'],
90-
pathex=['.'],
91-
binaries=[],
92-
datas=datas,
93-
hiddenimports=[
94-
'flask', 'flask.json', 'requests', 'waitress', 'bcrypt', 'qrcode', 'PIL',
95-
'pyotp', 'qrcode.image.pil', 'routes', 'main', 'apprise', 'apprise.common',
96-
'apprise.conversion', 'apprise.decorators', 'apprise.locale', 'apprise.logger',
97-
'apprise.manager', 'apprise.utils', 'apprise.URLBase', 'apprise.AppriseAsset',
98-
'apprise.AppriseAttachment', 'apprise.AppriseConfig', 'apprise.cli',
99-
'apprise.config', 'apprise.attachment', 'apprise.plugins', 'markdown', 'yaml', 'cryptography',
100-
],
101-
hookspath=[],
102-
hooksconfig={},
103-
runtime_hooks=[],
104-
excludes=[],
105-
cipher=block_cipher,
106-
noarchive=False,
107-
)
108-
109-
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
110-
111-
exe = EXE(
112-
pyz, a.scripts, [], exclude_binaries=True, name='Huntarr',
113-
debug=False, bootloader_ignore_signals=False, strip=False, upx=True,
114-
console=False, disable_windowed_traceback=False, argv_emulation=True,
115-
target_arch='x86_64', codesign_identity=None, entitlements_file=None,
116-
)
117-
118-
coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name='Huntarr')
119-
120-
app = BUNDLE(
121-
coll, name='Huntarr.app', bundle_identifier='io.huntarr.app',
122-
info_plist={
123-
'CFBundleShortVersionString': '${{ steps.meta.outputs.VERSION }}',
124-
'CFBundleVersion': '${{ steps.meta.outputs.VERSION }}',
125-
'NSHighResolutionCapable': True,
126-
'NSRequiresAquaSystemAppearance': False,
127-
'LSUIElement': False,
128-
'CFBundleDocumentTypes': [],
129-
'NSPrincipalClass': 'NSApplication',
130-
'CFBundleExecutable': 'Huntarr',
131-
'CFBundleIconFile': 'icon.icns',
132-
},
133-
)
134-
EOF
54+
# Use PyInstaller directly without spec file to avoid import issues
55+
python -m PyInstaller \
56+
--name="Huntarr" \
57+
--onedir \
58+
--windowed \
59+
--target-arch=x86_64 \
60+
--add-data="frontend:frontend" \
61+
--add-data="version.txt:." \
62+
--add-data="README.md:." \
63+
--add-data="LICENSE:." \
64+
--add-data="src:src" \
65+
--hidden-import="flask" \
66+
--hidden-import="flask.json" \
67+
--hidden-import="requests" \
68+
--hidden-import="waitress" \
69+
--hidden-import="bcrypt" \
70+
--hidden-import="qrcode" \
71+
--hidden-import="PIL" \
72+
--hidden-import="pyotp" \
73+
--hidden-import="qrcode.image.pil" \
74+
--hidden-import="routes" \
75+
--hidden-import="main" \
76+
--hidden-import="apprise" \
77+
--hidden-import="apprise.common" \
78+
--hidden-import="apprise.conversion" \
79+
--hidden-import="apprise.decorators" \
80+
--hidden-import="apprise.locale" \
81+
--hidden-import="apprise.logger" \
82+
--hidden-import="apprise.manager" \
83+
--hidden-import="apprise.utils" \
84+
--hidden-import="apprise.URLBase" \
85+
--hidden-import="apprise.AppriseAsset" \
86+
--hidden-import="apprise.AppriseAttachment" \
87+
--hidden-import="apprise.AppriseConfig" \
88+
--hidden-import="apprise.cli" \
89+
--hidden-import="apprise.config" \
90+
--hidden-import="apprise.attachment" \
91+
--hidden-import="apprise.plugins" \
92+
--hidden-import="markdown" \
93+
--hidden-import="yaml" \
94+
--hidden-import="cryptography" \
95+
--clean \
96+
--noconfirm \
97+
main.py
98+
99+
# Create a simple app bundle structure manually
100+
echo "Creating app bundle structure..."
101+
mkdir -p "dist/Huntarr.app/Contents/MacOS"
102+
mkdir -p "dist/Huntarr.app/Contents/Resources"
103+
104+
# Copy the executable
105+
cp -r "dist/Huntarr/"* "dist/Huntarr.app/Contents/MacOS/"
106+
107+
# Create Info.plist
108+
cat > "dist/Huntarr.app/Contents/Info.plist" << 'PLIST_EOF'
109+
<?xml version="1.0" encoding="UTF-8"?>
110+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
111+
<plist version="1.0">
112+
<dict>
113+
<key>CFBundleExecutable</key>
114+
<string>Huntarr</string>
115+
<key>CFBundleIdentifier</key>
116+
<string>io.huntarr.app</string>
117+
<key>CFBundleName</key>
118+
<string>Huntarr</string>
119+
<key>CFBundleVersion</key>
120+
<string>${{ steps.meta.outputs.VERSION }}</string>
121+
<key>CFBundleShortVersionString</key>
122+
<string>${{ steps.meta.outputs.VERSION }}</string>
123+
<key>CFBundleInfoDictionaryVersion</key>
124+
<string>6.0</string>
125+
<key>CFBundlePackageType</key>
126+
<string>APPL</string>
127+
<key>NSHighResolutionCapable</key>
128+
<true/>
129+
<key>NSRequiresAquaSystemAppearance</key>
130+
<false/>
131+
<key>LSUIElement</key>
132+
<false/>
133+
</dict>
134+
</plist>
135+
PLIST_EOF
135136
136137
- name: Create app icon
137138
run: |
@@ -186,19 +187,20 @@ jobs:
186187
cp /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns icon.icns 2>/dev/null || touch icon.icns
187188
fi
188189
189-
- name: Build macOS app
190+
- name: Finalize app bundle
190191
run: |
191-
# Build the app
192-
python -m PyInstaller Huntarr-macos-intel.spec --clean --noconfirm
193-
194192
# Copy icon into the app bundle
195193
if [ -f "icon.icns" ]; then
196194
cp icon.icns dist/Huntarr.app/Contents/Resources/icon.icns || true
197195
fi
198196
197+
# Make sure the main executable is executable
198+
chmod +x dist/Huntarr.app/Contents/MacOS/Huntarr
199+
199200
# Verify the build
200201
echo "Checking built app:"
201202
ls -la dist/
203+
ls -la dist/Huntarr.app/Contents/MacOS/
202204
file dist/Huntarr.app/Contents/MacOS/Huntarr
203205
204206
# Test that the app can at least start (basic smoke test)

0 commit comments

Comments
 (0)