Skip to content

Commit 82beef8

Browse files
authored
Add macOS code signing and build improvements (#156)
* add .env files to ignore * use VIRTUAL_ENV if set, otherwise fallback to `./venv/` * update workflow for codesigning * allow manual run * cleaup workflows * update pyinstaller.spec for macOS codesigning and add os import; update requirements-dev.txt to ensure pyinstaller is included * add mango.entitlements for macOS codesigning requirements * don't upload artifacts from test builds * update actions versions and improve dependency caching in test workflow
1 parent 697b774 commit 82beef8

File tree

8 files changed

+119
-48
lines changed

8 files changed

+119
-48
lines changed

.github/workflows/build_exe.yml

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ on:
44
workflow_call:
55
inputs:
66
is_release:
7-
required: true
7+
default: false
8+
description: 'Is this a release?'
89
type: boolean
9-
10+
required: false
1011
jobs:
1112
build:
1213
strategy:
@@ -20,49 +21,70 @@ jobs:
2021
sha_command: pwsh -c "Get-FileHash -Algorithm SHA1 dist\mangotango_windows.exe | Format-Table Hash -HideTableHeaders > dist\mangotango_windows.exe.sha1"
2122
list_command: dir dist
2223
check_command: dist\mangotango_windows.exe --noop
23-
- platform_name: MacOS 14
24-
artifact_name: macos-14
25-
os: macos-14
26-
move_command: mv dist/mangotango dist/mangotango_macos_14
27-
sha_command: shasum -a 1 dist/mangotango_macos_14 > dist/mangotango_macos_14.sha1
24+
- platform_name: MacOS (x86)
25+
artifact_name: macos-x86
26+
os: macos-13
27+
move_command: mv dist/mangotango dist/mangotango_macos-x86
28+
sha_command: shasum -a 1 dist/mangotango_macos-x86 > dist/mangotango_macos-x86.sha1
29+
sha_command_pkg: shasum -a 1 dist/mangotango_macos-x86.pkg > dist/mangotango_macos-x86.pkg.sha1
2830
list_command: ls -ll dist
29-
check_command: dist/mangotango_macos_14 --noop
30-
- platform_name: MacOS 15
31-
artifact_name: macos-15
31+
check_command: dist/mangotango_macos-x86 --noop
32+
- platform_name: MacOS (arm64)
33+
artifact_name: macos-arm64
3234
os: macos-15
33-
move_command: mv dist/mangotango dist/mangotango_macos_15
34-
sha_command: shasum -a 1 dist/mangotango_macos_15 > dist/mangotango_macos_15.sha1
35+
move_command: mv dist/mangotango dist/mangotango_macos-arm64
36+
sha_command: shasum -a 1 dist/mangotango_macos-arm64 > dist/mangotango_macos-arm64.sha1
37+
sha_command_pkg: shasum -a 1 dist/mangotango_macos-arm64.pkg > dist/mangotango_macos-arm64.pkg.sha1
3538
list_command: ls -ll dist
36-
check_command: dist/mangotango_macos_15 --noop
39+
check_command: dist/mangotango_macos-arm64 --noop
3740

3841
name: Build ${{ matrix.platform_name }}
3942
runs-on: ${{ matrix.os }}
4043
steps:
4144
- name: Checkout code
42-
uses: actions/checkout@v2
45+
uses: actions/checkout@v4
4346

4447
- name: Set up Python
4548
uses: actions/setup-python@v4
4649
with:
4750
python-version: 3.12
48-
49-
- name: Cache dependencies
50-
uses: actions/cache@v3
51-
with:
52-
path: |
53-
~/.cache/pip
54-
key: ${{ matrix.os }}-pip-${{ hashFiles('requirements.txt') }}
55-
restore-keys: |
56-
${{ matrix.os }}-pip-
51+
cache: 'pip'
52+
cache-dependency-path: '**/requirements*.txt'
5753

5854
- name: Install dependencies
5955
run: |
6056
python -m pip install --upgrade pip
6157
pip install -r requirements.txt
6258
6359
- name: Install PyInstaller
64-
run: pip install pyinstaller
65-
60+
run: |
61+
pip install pyinstaller
62+
echo "PYINST_BIN=\"$(which pyinstaller)\"" >> "$GITHUB_ENV"
63+
- name: Create macOS keychain
64+
id: keychain
65+
if: runner.os == 'macOS'
66+
env:
67+
APPLE_DEV_EMAIL: ${{secrets.APPLE_DEV_EMAIL}}
68+
APP_SPEC_PASS: ${{secrets.APP_SPEC_PASS}}
69+
APPLE_APP_CERTIFICATE: ${{secrets.DEV_APP_CERT}}
70+
APPLE_APP_CERT_PASSWORD: ${{secrets.DEV_APP_CERT_PASS}}
71+
APPLE_INST_CERTIFICATE: ${{secrets.DEV_INST_CERT}}
72+
APPLE_INST_CERT_PASSWORD: ${{secrets.DEV_INST_CERT_PASS}}
73+
APPLE_KEYCHAIN_PASS: ${{secrets.APPLE_KEY_PASS}}
74+
run: |
75+
echo "$APPLE_APP_CERTIFICATE" | base64 --decode > app_certificate.p12
76+
echo "$APPLE_INST_CERTIFICATE" | base64 --decode > inst_certificate.p12
77+
security create-keychain -p $APPLE_KEYCHAIN_PASS build.keychain
78+
security default-keychain -s build.keychain
79+
security set-keychain-settings -lut 21600 build.keychain
80+
security unlock-keychain -p $APPLE_KEYCHAIN_PASS build.keychain
81+
security import app_certificate.p12 -k build.keychain -P $APPLE_APP_CERT_PASSWORD -A
82+
security import inst_certificate.p12 \
83+
-k build.keychain \
84+
-P "$APPLE_INST_CERT_PASSWORD" \
85+
-A
86+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $APPLE_KEYCHAIN_PASS build.keychain
87+
security find-identity -v -p codesigning -p macappstore
6688
- name: Print version string (for tag)
6789
id: get_version_tag
6890
if: ${{ github.ref_type == 'tag' }}
@@ -80,25 +102,60 @@ jobs:
80102
run: ${{ matrix.version_command }}
81103

82104
- name: Build the executable
83-
run: |
84-
pyinstaller pyinstaller.spec
105+
env:
106+
APPLE_APP_CERT_ID: ${{secrets.APPLE_APP_CERT_ID}}
107+
run: pyinstaller pyinstaller.spec
85108

86109
- name: Rename the executable to include platform suffix
87110
run: ${{ matrix.move_command }}
88111

89112
- name: Compute the SHA1 hashsum
90113
run: ${{ matrix.sha_command }}
91114

115+
- name: Create and sign mac package
116+
if: runner.os == 'macOS'
117+
env:
118+
APPLE_INST_CERT_ID: ${{secrets.APPLE_INST_CERT_ID}}
119+
APPLE_KEYCHAIN_PASS: ${{secrets.APPLE_KEY_PASS}}
120+
run: |
121+
mkdir -p /tmp/mangotango/
122+
ditto dist/mangotango_${{matrix.artifact_name}} /tmp/mangotango/mangotango
123+
chmod +x /tmp/mangotango/mangotango
124+
security unlock-keychain -p $APPLE_KEYCHAIN_PASS build.keychain
125+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$APPLE_KEYCHAIN_PASS" build.keychain
126+
security find-identity -v -p codesigning build.keychain
127+
pkgbuild --identifier "org.mangotango.cli" --timestamp --root /tmp/mangotango --install-location /Applications "./dist/mangotango_${{matrix.artifact_name}}_signed.pkg" --sign "$APPLE_INST_CERT_ID"
128+
129+
- name: Notarize Mac package
130+
if: runner.os == 'macOS'
131+
env:
132+
APPLE_DEV_EMAIL: ${{secrets.APPLE_DEV_EMAIL}}
133+
APPLE_TEAM_ID: ${{secrets.TEAM_ID}}
134+
APP_SPEC_PASS: ${{secrets.APP_SPEC_PASS}}
135+
run: xcrun notarytool submit dist/mangotango_${{matrix.artifact_name}}_signed.pkg --apple-id $APPLE_DEV_EMAIL --team-id $APPLE_TEAM_ID --password $APP_SPEC_PASS --wait > notarization_output.txt
136+
137+
- name: Staple the notarization ticket
138+
if: runner.os == 'macOS'
139+
run: xcrun stapler staple dist/mangotango_${{matrix.artifact_name}}_signed.pkg
140+
141+
- name: Clean up macOS Artifacts
142+
if: runner.os == 'macOS'
143+
run: |
144+
rm -rf /tmp/mangotango
145+
rm -rf dist/mangotango_${{matrix.artifact_name}}
146+
rm -rf dist/mangotango_${{matrix.artifact_name}}.pkg
147+
mv dist/mangotango_${{matrix.artifact_name}}_signed.pkg dist/mangotango_${{matrix.artifact_name}}.pkg
148+
149+
- name: Compute the SHA1 hashsum for macOS .pkg
150+
if: runner.os == 'macOS'
151+
run: ${{ matrix.sha_command_pkg }}
152+
92153
- name: Inspect the dist/ directory before uploading artifacts
93154
run: ${{ matrix.list_command }}
94155

95-
- name: Check that the executable runs
96-
if: inputs.is_release == false
97-
run: ${{ matrix.check_command}}
98-
99156
- name: Upload artifacts
100-
if: inputs.is_release
101157
uses: actions/upload-artifact@v4
158+
if: inputs.is_release
102159
with:
103160
name: ${{ matrix.artifact_name }}
104161
path: |

.github/workflows/release.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@ name: New Release
33
on:
44
push:
55
tags:
6-
- "v[0-9]+.[0-9]+.[0-9]+-?*"
6+
- "v*.*.*"
77
workflow_dispatch:
88

99
jobs:
1010
call-build_exe:
1111
uses: ./.github/workflows/build_exe.yml
12-
with:
13-
is_release: true
1412
release:
1513
needs: call-build_exe
1614
runs-on: ubuntu-latest
@@ -21,7 +19,8 @@ jobs:
2119
path: artifacts
2220

2321
- name: Create Release
24-
uses: softprops/action-gh-release@v1
22+
uses: softprops/action-gh-release@v2
23+
if: github.ref_type == 'tag'
2524
with:
2625
files: |
2726
artifacts/**/*

.github/workflows/test.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,14 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- name: Checkout repository
11-
uses: actions/checkout@v2
11+
uses: actions/checkout@v4
1212

1313
- name: Set up Python
1414
uses: actions/setup-python@v4
1515
with:
1616
python-version: 3.12
17-
18-
- name: Cache dependencies
19-
uses: actions/cache@v3
20-
with:
21-
path: |
22-
~/.cache/pip
23-
key: linux-pip-dev-${{ hashFiles('requirements-dev.txt') }}
17+
cache: 'pip'
18+
cache-dependency-path: '**/requirements*.txt'
2419

2520
- name: Install dependencies
2621
run: |
@@ -32,4 +27,4 @@ jobs:
3227
test_build:
3328
uses: ./.github/workflows/build_exe.yml
3429
with:
35-
is_release: false
30+
is_release: false

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ __private__
66
/analysis_outputs
77
VERSION
88
*.DS_Store
9+
.env*

bootstrap.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Define the virtual environment and requirements file paths
44
REPO_ROOT=$(pwd)
5-
VENV_PATH="$REPO_ROOT/venv"
5+
VENV_PATH="${VIRTUAL_ENV:=$REPO_ROOT/venv}"
66
REQUIREMENTS_FILE="$REPO_ROOT/requirements-dev.txt"
77

88
# Check if virtual environment exists

mango.entitlements

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.cs.disable-library-validation</key>
6+
<true/>
7+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
8+
<true/>
9+
<key>com.apple.security.cs.allow-jit</key>
10+
<true/>
11+
</dict>
12+
</plist>

pyinstaller.spec

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
# code: language=python
12
# main.spec
23
# This file tells PyInstaller how to bundle your application
3-
44
from PyInstaller.utils.hooks import copy_metadata
5+
from PyInstaller.building.api import EXE,PYZ
6+
from PyInstaller.building.build_main import Analysis
57
import sys
8+
import os
69

710
block_cipher = None
811

@@ -35,6 +38,7 @@ a = Analysis(
3538
)
3639

3740
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
41+
3842
if sys.platform == "darwin":
3943
exe = EXE(
4044
pyz,
@@ -46,7 +50,9 @@ if sys.platform == "darwin":
4650
debug=False,
4751
strip=True,
4852
upx=True, # You can set this to False if you don’t want UPX compression
49-
console=True # Set to False if you don't want a console window
53+
console=True, # Set to False if you don't want a console window
54+
entitlements_file="./mango.entitlements",
55+
codesign_identity=os.getenv('APPLE_APP_CERT_ID'),
5056
)
5157
else:
5258
exe = EXE(

requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pyarrow-stubs==17.13
44
black==24.10.0
55
isort==5.13.2
66
pytest==8.3.4
7+
pyinstaller==6.14.1

0 commit comments

Comments
 (0)