-
Notifications
You must be signed in to change notification settings - Fork 120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
macOS build configuration for pyinstaller with associated helper assets #111
base: master
Are you sure you want to change the base?
Changes from all commits
97bb949
9d56f17
85b69a0
3931008
649222f
b2bb2de
d168b5f
01cc9f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
## macOS CQ-Editor Build Files | ||
|
||
This directory contains build scripts and resources to build a standalone macOS application bundle of the CQ-Editor. | ||
|
||
## Requirements | ||
|
||
- XCode - Apple Developer IDE, compilers, and build utilities. Available from the macOS App Store. | ||
- [brew](https://brew.sh) - a popular macOS package manager | ||
- conda - virtual python environments with packaging. The [miniconda](https://docs.conda.io/en/latest/miniconda.html) variant of conda is recommended for a minimal installation without unnecessary libraries and packages. | ||
|
||
## conda environment | ||
|
||
The `macos_env.yml` file specifies a conda environment which can be used to build and/or run the CQ-Editor. This command will create a new conda environment named `cqgui` containing the necessary tools and libraries: | ||
|
||
```shell | ||
$ conda env create -f macos_env.yml -n cqgui | ||
$ conda activate cqgui | ||
``` | ||
|
||
## pyinstaller | ||
|
||
The `pyinstaller.spec` file contains the build specification to build all platform variants of the CQ-Editor. The macOS specific section of this file is as follows: | ||
|
||
```python | ||
app = BUNDLE( | ||
coll, | ||
name="CQ-Editor.app", | ||
icon="icons/cadquery_logo_dark.icns", | ||
bundle_identifier="org.cadquery.cqeditor", | ||
info_plist={ | ||
"CFBundleName": "CQ-Editor", | ||
"CFBundleShortVersionString": "0.1.0", | ||
"NSHighResolutionCapable": True, | ||
"NSPrincipalClass": "NSApplication", | ||
"NSAppleScriptEnabled": False, | ||
"LSBackgroundOnly": False, | ||
}, | ||
) | ||
``` | ||
|
||
The `CFBundleShortVersionString` key in the `info_plist` dictionary can be changed to the desired version number of the build. | ||
|
||
## Building the Application Bundle | ||
|
||
To build the application bundle using pyinstaller, a convenient build script is contained in this folder and can be executed as follows: | ||
|
||
```shell | ||
$ ./makeapp.sh | ||
``` | ||
|
||
Alternatively, pyinstaller can be run directly from the repository root directory as follows: | ||
|
||
```shell | ||
$ pyinstaller --onedir --windowed --clean -y pyinstaller.spec | ||
``` | ||
|
||
The resulting application bundle `CQ-Editor.app` will be found in the `dist` directory. Verify that it works by either launching the `CQ-Editor.app` file in the Finder or double-clicking the standalone executable `dist/CQ-Editor/CQ-Editor`. | ||
|
||
## Building a DMG Installer | ||
|
||
To distribute an application bundle, a disk image file (.DMG) is typically used as a convenient single file container format. A DMG file also allows the application bundle to be compressed for efficiency. A DMG file can be created from the application bundle using the build script in this folder: | ||
|
||
```shell | ||
$ ./makedmg.sh | ||
``` | ||
|
||
The `makedmg.sh` script file has a variable called `version` which can be changed to match the `CFBundleShortVersionString` key in the `pyinstaller.spec` file. | ||
|
||
This script requires the following helper components: | ||
|
||
- [dmgbuild](https://github.com/al45tair/dmgbuild/blob/master/doc/index.rst) : python utility which creates `dmg` files with a great deal of customization (install using pip) | ||
- [fileicon](https://github.com/mklement0/fileicon) : a small utility which can assign custom icons to macOS files and/or folders (install using brew) | ||
|
||
## Resource Files | ||
|
||
| Resource | File | Description | | ||
| --- | --- | --- | | ||
| ![alt text](../icons/cadquery_logo_dark.svg) | `icons/cadquery_logo_dark.icns` | `CQ-Editor.app` application icon | | ||
| ![alt text](./CQDiskImageIcon.png) | `CQDiskImageIcon.png` | DMG file icon | | ||
| ![alt text](./CQInstallerBackground.png) | `./CQInstallerBackground.png` | DMG folder background image | | ||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# Make sure we’re using the latest Homebrew. | ||
brew update | ||
# Upgrade any already-installed formulae. | ||
brew upgrade | ||
|
||
brew install fileicon |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
|
||
import biplist | ||
import os.path | ||
|
||
# | ||
# Example settings file for dmgbuild | ||
# | ||
# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg | ||
|
||
# You can actually use this file for your own application (not just TextEdit) | ||
# by doing e.g. | ||
# | ||
# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg | ||
|
||
# .. Useful stuff .............................................................. | ||
|
||
application = defines.get("app", "../dist/CQ-Editor.app") | ||
appname = os.path.basename(application) | ||
|
||
# Volume format (see hdiutil create -help) | ||
format = defines.get("format", "UDBZ") | ||
|
||
# Volume size | ||
size = defines.get("size", "1.0g") | ||
|
||
# Files to include | ||
files = [application] | ||
|
||
# Symlinks to create | ||
symlinks = {"Applications": "/Applications"} | ||
|
||
# Volume icon | ||
# | ||
# You can either define icon, in which case that icon file will be copied to the | ||
# image, *or* you can define badge_icon, in which case the icon file you specify | ||
# will be used to badge the system's Removable Disk icon | ||
# | ||
badge_icon = "../icons/cadquery_logo_dark.icns" | ||
|
||
# Where to put the icons | ||
icon_locations = {appname: (130, 190), "Applications": (470, 185)} | ||
|
||
# .. Window configuration ...................................................... | ||
|
||
background = "CQInstallerBackground.png" | ||
show_status_bar = False | ||
show_tab_view = False | ||
show_toolbar = False | ||
show_pathbar = False | ||
show_sidebar = False | ||
sidebar_width = 180 | ||
|
||
# Window position in ((x, y), (w, h)) format | ||
window_rect = ((200, 120), (600, 400)) | ||
|
||
# Select the default view; must be one of | ||
# | ||
# 'icon-view' | ||
# 'list-view' | ||
# 'column-view' | ||
# 'coverflow' | ||
# | ||
default_view = "icon-view" | ||
|
||
# General view configuration | ||
show_icon_preview = False | ||
|
||
# Set these to True to force inclusion of icon/list view settings (otherwise | ||
# we only include settings for the default view) | ||
include_icon_view_settings = "auto" | ||
include_list_view_settings = "auto" | ||
|
||
# .. Icon view configuration ................................................... | ||
|
||
arrange_by = None | ||
grid_offset = (0, 0) | ||
grid_spacing = 100 | ||
scroll_position = (0, 0) | ||
label_pos = "bottom" # or 'right' | ||
text_size = 16 | ||
icon_size = 88 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: cqgui | ||
channels: | ||
- CadQuery | ||
- defaults | ||
- conda-forge | ||
dependencies: | ||
- pyqt=5.9.2 | ||
- pyparsing | ||
- pyqtgraph=0.10.0 | ||
- python=3.7 | ||
- spyder=3.3.4 | ||
- pythonocc-core=0.18.2 | ||
- path.py | ||
- logbook | ||
- qtconsole=4.4.4 | ||
- requests | ||
- pyinstaller | ||
- pip | ||
- pip: | ||
- "git+https://github.com/CadQuery/cadquery" | ||
- PyQt5 | ||
- spyder==3.3.4 | ||
- pyobjc-framework-Quartz | ||
- dmgbuild |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/bin/sh | ||
cd .. | ||
pyinstaller --onedir --windowed --clean -y pyinstaller.spec | ||
cd macos |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/sh | ||
version="0.1" | ||
appname="CQ-Editor" | ||
appbundle="../dist/CQ-Editor.app" | ||
dmgfile="${appname} v${version}.dmg" | ||
test -f "$dmgfile" && rm "$dmgfile" | ||
# xattr -cr $appbundle | ||
dmgbuild -s cq_dmg_settings.py "${appname} App v.${version}" "$dmgfile" | ||
fileicon set "$dmgfile" CQDiskImageIcon.png |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,52 +4,64 @@ import sys, site, os | |
from path import Path | ||
|
||
block_cipher = None | ||
spyder_data = Path(site.getsitepackages()[-1]) / "spyder" | ||
parso_grammar = Path(site.getsitepackages()[-1]) / "parso/python/grammar36.txt" | ||
|
||
spyder_data = Path(site.getsitepackages()[-1]) / 'spyder' | ||
parso_grammar = Path(site.getsitepackages()[-1]) / 'parso/python/grammar36.txt' | ||
|
||
if sys.platform == 'linux': | ||
oce_dir = Path(sys.prefix) / 'share' / 'oce-0.18' | ||
if sys.platform == "linux" or sys.platform == "darwin": | ||
oce_dir = Path(sys.prefix) / "share" / "oce-0.18" | ||
else: | ||
oce_dir = Path(sys.prefix) / 'Library' / 'share' / 'oce' | ||
|
||
a = Analysis(['run.py'], | ||
pathex=['/home/adam/cq/CQ-editor'], | ||
binaries=[], | ||
datas=[(spyder_data ,'spyder'), | ||
(parso_grammar, 'parso/python'), | ||
(oce_dir , 'oce')], | ||
hiddenimports=['ipykernel.datapub'], | ||
hookspath=[], | ||
runtime_hooks=['pyinstaller/pyi_rth_occ.py', | ||
'pyinstaller/pyi_rth_fontconfig.py'], | ||
excludes=['_tkinter',], | ||
win_no_prefer_redirects=False, | ||
win_private_assemblies=False, | ||
cipher=block_cipher, | ||
noarchive=False) | ||
|
||
pyz = PYZ(a.pure, a.zipped_data, | ||
cipher=block_cipher) | ||
exe = EXE(pyz, | ||
a.scripts, | ||
[], | ||
exclude_binaries=True, | ||
name='CQ-editor', | ||
debug=False, | ||
bootloader_ignore_signals=False, | ||
strip=False, | ||
upx=True, | ||
console=True, | ||
icon='icons/cadquery_logo_dark.ico') | ||
|
||
exclude = ('libGL','libEGL','libbsd') | ||
oce_dir = Path(sys.prefix) / "Library" / "share" / "oce" | ||
|
||
|
||
a = Analysis( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's with the formatting change? I think only one line is actually edited. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The entire pyinstaller.spec file was accidentally reformatted with black. Therefore, spacing and single-quote substitution was applied globally. Since it had no semantic impact, I left it. In the line above, I simply added the check for sys.platform=="darwin" |
||
["run.py"], | ||
pathex=[], | ||
binaries=[], | ||
datas=[(spyder_data, "spyder"), (parso_grammar, "parso/python"), (oce_dir, "oce")], | ||
hiddenimports=["ipykernel.datapub"], | ||
hookspath=[], | ||
runtime_hooks=["pyinstaller/pyi_rth_occ.py", "pyinstaller/pyi_rth_fontconfig.py"], | ||
excludes=["_tkinter",], | ||
win_no_prefer_redirects=False, | ||
win_private_assemblies=False, | ||
cipher=block_cipher, | ||
noarchive=False, | ||
) | ||
|
||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) | ||
exe = EXE( | ||
pyz, | ||
a.scripts, | ||
[], | ||
exclude_binaries=True, | ||
name="CQ-Editor", | ||
debug=False, | ||
bootloader_ignore_signals=False, | ||
strip=False, | ||
upx=True, | ||
console=True, | ||
icon="icons/cadquery_logo_dark.ico", | ||
) | ||
|
||
exclude = ("libGL", "libEGL", "libbsd") | ||
a.binaries = TOC([x for x in a.binaries if not x[0].startswith(exclude)]) | ||
|
||
coll = COLLECT(exe, | ||
a.binaries, | ||
a.zipfiles, | ||
a.datas, | ||
strip=False, | ||
upx=True, | ||
name='CQ-editor') | ||
coll = COLLECT( | ||
exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name="CQ-Editor" | ||
) | ||
|
||
app = BUNDLE( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did build on Win and Linux without this section. Shouldn't it be behind an if? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I understand, the "BUNDLE" component will not be processed on linux/windows anyway. Guarding with an "if" is likely not necessary, but wouldn't hurt--everything still builds the same in macOS with or without the if guard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, but still it will be more readable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No problem, I'll put a platform 'if' guard for macOS to enhance readability. |
||
coll, | ||
name="CQ-Editor.app", | ||
icon="icons/cadquery_logo_dark.icns", | ||
bundle_identifier="org.cadquery.cqeditor", | ||
info_plist={ | ||
"CFBundleName": "CQ-Editor", | ||
"CFBundleShortVersionString": "0.1.0", | ||
"NSHighResolutionCapable": True, | ||
"NSPrincipalClass": "NSApplication", | ||
"NSAppleScriptEnabled": False, | ||
"LSBackgroundOnly": False, | ||
}, | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The application (without bundling) did work on Mac (according to some users). So I'd rather not remove this functionality but rather fix the graphics initialization process
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I leave this statement, the resulting app binary is guaranteed to crash (verified in macOS 10.14 and 10.15). We can either defer this pull request until a root cause is found or leave the 'if platform' guard in place until a root cause is found for future releases. Unless there is some urgency to make a new release including macOS, we can always defer the PR and wait until the root cause / remedy to this race condition crash at startup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually if you are willing to help, I have some ideas regarding debugging this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I did spend a few hours yesterday trying to get more clues. One issue is the timing when “InitDriver” is called relative to MainWindow.show(). The rest of the issue is related to abi trap from AIS interactive viewer. I think it tries to throw an exception and it doesn’t get handled properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adam-urbanczyk let me know if you want to proceed with some macOS debugging of this issue--I would like to help if I can. BTW, good luck with the FOSDEM talk--I just checked out your slide deck!