aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMicah Lee <micah@micahflee.com>2022-03-31 19:35:26 -0700
committerMicah Lee <micah@micahflee.com>2022-03-31 19:35:26 -0700
commit4e5ad4d0ca996f0dd651e8e2e52107927107d01d (patch)
tree84a7ad0b23bdb4f8b9e45bf1aeeb39a8cfd7ce57
parentf4a50b573d78888dffac77769c5513252d993f5a (diff)
downloadonionshare-4e5ad4d0ca996f0dd651e8e2e52107927107d01d.tar.gz
onionshare-4e5ad4d0ca996f0dd651e8e2e52107927107d01d.zip
Work on macOS build
-rw-r--r--.circleci/config.yml41
-rw-r--r--RELEASE.md9
-rw-r--r--desktop/package/macos.py311
-rw-r--r--desktop/package/windows.py4
4 files changed, 359 insertions, 6 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 91944c6c..1f7d763a 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -166,6 +166,47 @@ jobs:
xcode: 12.5.1
steps:
- checkout
+ - run:
+ name: Install Homebrew dependencies
+ command: |
+ brew install wget
+ brew install go
+ - run:
+ name: Install Python 3.9.12
+ command: |
+ wget https://www.python.org/ftp/python/3.9.12/python-3.9.12-macos11.pkg -O ~/Downloads/python.pkg
+ sudo installer -pkg ~/Downloads/python.pkg -target /
+ - run:
+ name: Install poetry
+ command: |
+ pip3 install poetry
+ ln -s /Library/Frameworks/Python.framework/Versions/3.9/bin/poetry /usr/local/bin
+ - run:
+ name: Install poetry dependencies
+ command: |
+ cd ~/project/desktop
+ poetry install
+ - run:
+ name: Get tor
+ command: |
+ cd ~/project/desktop
+ poetry run ./scripts/get-tor-osx.py
+ - run:
+ name: Build meek
+ command: |
+ cd ~/project/desktop
+ ./scripts/build-meek-client.py
+ - run:
+ name: Build OnionShare
+ command: |
+ cd ~/project/desktop
+ poetry run python ./setup-freeze.py bdist_mac
+ poetry run python ./package/macos.py cleanup-build
+ - run:
+ name: Compress
+ command: zip -r ~/onionshare-macos.zip ~/project/desktop/build/OnionShare.app
+ - store_artifacts:
+ path: ~/onionshare-macos.zip
build-snapcraft:
machine:
diff --git a/RELEASE.md b/RELEASE.md
index 958275cc..cab2909b 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -111,8 +111,8 @@ Build the Windows binaries, delete extra files, codesign, and create an MSI pack
```
poetry run python .\setup-freeze.py build
poetry run python .\package\windows.py cleanup-build
-poetry run python .\package\windows.py codesign
-poetry run python .\package\windows.py package
+poetry run python .\package\windows.py codesign [build_dir]
+poetry run python .\package\windows.py package [build_dir]
```
This will create `desktop/dist/OnionShare-$VERSION.msi`, signed.
@@ -124,7 +124,10 @@ Set up the development environment described in `README.md`.
Then build an executable, make it a macOS app bundle, and package it in a dmg:
```sh
-poetry run ./package/build-mac.py
+poetry run python ./setup-freeze.py bdist_mac
+poetry run python ./package/macos.py cleanup-build
+poetry run python ./package/macos.py codesign [app_path]
+poetry run python ./package/macos.py package [app_path]
```
The will create `dist/OnionShare-$VERSION.dmg`.
diff --git a/desktop/package/macos.py b/desktop/package/macos.py
new file mode 100644
index 00000000..307e8ddc
--- /dev/null
+++ b/desktop/package/macos.py
@@ -0,0 +1,311 @@
+#!/usr/bin/env python3
+import os
+import inspect
+import click
+import subprocess
+import shutil
+import glob
+import itertools
+
+root = os.path.dirname(
+ os.path.dirname(
+ os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+ )
+)
+desktop_dir = os.path.join(root, "desktop")
+
+identity_name_application = "Developer ID Application: Micah Lee (N9B95FDWH4)"
+entitlements_plist_path = f"{desktop_dir}/package/Entitlements.plist"
+
+
+def get_app_path():
+ return os.path.join(desktop_dir, "build", "OnionShare.app")
+
+
+def run(cmd, cwd=None, error_ok=False):
+ print(f"{cmd} # cwd={cwd}")
+ subprocess.run(cmd, cwd=cwd, check=True)
+
+
+def get_size(dir):
+ size = 0
+ for path, dirs, files in os.walk(dir):
+ for f in files:
+ fp = os.path.join(path, f)
+ size += os.path.getsize(fp)
+ return size
+
+
+def sign(path, entitlements, identity):
+ run(
+ [
+ "codesign",
+ "--sign",
+ identity,
+ "--entitlements",
+ str(entitlements),
+ "--timestamp",
+ "--deep",
+ "--force",
+ "--options",
+ "runtime,library",
+ str(path),
+ ]
+ )
+
+
+@click.group()
+def main():
+ """
+ macOS build tasks
+ """
+
+
+@main.command()
+def cleanup_build():
+ """Delete unused PySide2 stuff to save space"""
+ app_path = get_app_path()
+ before_size = get_size(app_path)
+
+ print("> Delete unused Qt Frameworks")
+ for framework in [
+ "Qt3DAnimation",
+ "Qt3DCore",
+ "Qt3DExtras",
+ "Qt3DInput",
+ "Qt3DLogic",
+ "Qt3DQuick",
+ "Qt3DQuickAnimation",
+ "Qt3DQuickExtras",
+ "Qt3DQuickInput",
+ "Qt3DQuickRender",
+ "Qt3DQuickScene2D",
+ "Qt3DRender",
+ "QtBluetooth",
+ "QtBodymovin",
+ "QtCharts",
+ "QtConcurrent",
+ "QtDataVisualization",
+ "QtDesigner",
+ "QtDesignerComponents",
+ "QtGamepad",
+ "QtHelp",
+ "QtLocation",
+ "QtMultimedia",
+ "QtMultimediaQuick",
+ "QtMultimediaWidgets",
+ "QtNetwork",
+ "QtNetworkAuth",
+ "QtNfc",
+ "QtOpenGL",
+ "QtPdf",
+ "QtPdfWidgets",
+ "QtPositioning",
+ "QtPositioningQuick",
+ "QtPrintSupport",
+ "QtPurchasing",
+ "QtQml",
+ "QtQmlModels",
+ "QtQmlWorkerScript",
+ "QtQuick",
+ "QtQuick3D",
+ "QtQuick3DAssetImport",
+ "QtQuick3DRender",
+ "QtQuick3DRuntimeRender",
+ "QtQuick3DUtils",
+ "QtQuickControls2",
+ "QtQuickParticles",
+ "QtQuickShapes",
+ "QtQuickTemplates2",
+ "QtQuickTest",
+ "QtQuickWidgets",
+ "QtRemoteObjects",
+ "QtRepParser",
+ "QtScript",
+ "QtScriptTools",
+ "QtScxml",
+ "QtSensors",
+ "QtSerialBus",
+ "QtSerialPort",
+ "QtSql",
+ "QtSvg",
+ "QtTest",
+ "QtTextToSpeech",
+ "QtUiPlugin",
+ "QtVirtualKeyboard",
+ "QtWebChannel",
+ "QtWebEngine",
+ "QtWebEngineCore",
+ "QtWebEngineWidgets",
+ "QtWebSockets",
+ "QtWebView",
+ "QtXml",
+ "QtXmlPatterns",
+ ]:
+ shutil.rmtree(
+ f"{app_path}/Contents/MacOS/lib/PySide2/Qt/lib/{framework}.framework"
+ )
+ try:
+ os.remove(f"{app_path}/Contents/MacOS/lib/PySide2/{framework}.abi3.so")
+ os.remove(f"{app_path}/Contents/MacOS/lib/PySide2/{framework}.pyi")
+ except FileNotFoundError:
+ pass
+
+ print("> Move files around so Apple will notarize")
+ # https://github.com/marcelotduarte/cx_Freeze/issues/594
+ # https://gist.github.com/TechnicalPirate/259a9c24878fcad948452cb148af2a2c#file-custom_bdist_mac-py-L415
+
+ # Move lib from MacOS into Resources
+ os.rename(
+ f"{app_path}/Contents/MacOS/lib",
+ f"{app_path}/Contents/Resources/lib",
+ )
+ run(
+ ["ln", "-s", "../Resources/lib"],
+ cwd=f"{app_path}/Contents/MacOS",
+ )
+
+ # Move frameworks from Resources/lib into Frameworks
+ os.makedirs(f"{app_path}/Contents/Frameworks", exist_ok=True)
+ for framework_filename in glob.glob(
+ f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib/Qt*.framework"
+ ):
+ basename = os.path.basename(framework_filename)
+
+ os.rename(framework_filename, f"{app_path}/Contents/Frameworks/{basename}")
+ run(
+ ["ln", "-s", f"../../../../../Frameworks/{basename}"],
+ cwd=f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib",
+ )
+ if os.path.exists(f"{app_path}/Contents/Frameworks/{basename}/Resources"):
+ os.rename(
+ f"{app_path}/Contents/Frameworks/{basename}/Resources",
+ f"{app_path}/Contents/Frameworks/{basename}/Versions/5/Resources",
+ )
+ run(
+ ["ln", "-s", "Versions/5/Resources"],
+ cwd=f"{app_path}/Contents/Frameworks/{basename}",
+ )
+
+ run(
+ ["ln", "-s", "5", "Current"],
+ cwd=f"{app_path}/Contents/Frameworks/{basename}/Versions",
+ )
+
+ # Move Qt plugins
+ os.rename(
+ f"{app_path}/Contents/Resources/lib/PySide2/Qt/plugins",
+ f"{app_path}/Contents/Frameworks/plugins",
+ )
+ run(
+ ["ln", "-s", "../../../../Frameworks/plugins"],
+ cwd=f"{app_path}/Contents/Resources/lib/PySide2/Qt",
+ )
+
+ print("> Delete more unused PySide2 stuff to save space")
+ for filename in [
+ f"{app_path}/Contents/Resources/lib/PySide2/Designer.app",
+ f"{app_path}/Contents/Resources/lib/PySide2/examples",
+ f"{app_path}/Contents/Resources/lib/PySide2/glue",
+ f"{app_path}/Contents/Resources/lib/PySide2/include",
+ f"{app_path}/Contents/Resources/lib/PySide2/pyside2-lupdate",
+ f"{app_path}/Contents/Resources/lib/PySide2/Qt/qml",
+ f"{app_path}/Contents/Resources/lib/PySide2/libpyside2.abi3.5.15.dylib",
+ f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib/QtRepParser.framework",
+ f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib/QtUiPlugin.framework",
+ f"{app_path}/Contents/Resources/lib/PySide2/Qt/lib/QtWebEngineCore.framework/Helpers",
+ f"{app_path}/Contents/Resources/lib/shiboken2/libshiboken2.abi3.5.15.dylib",
+ f"{app_path}/Contents/Resources/lib/shiboken2/docs",
+ f"{app_path}/Contents/Resources/lib/PySide2/rcc",
+ f"{app_path}/Contents/Resources/lib/PySide2/uic",
+ ]:
+ if os.path.isdir(filename):
+ shutil.rmtree(filename)
+ elif os.path.isfile(filename):
+ os.remove(filename)
+ else:
+ print(f"Cannot delete, filename not found: {filename}")
+
+ after_size = get_size(f"{app_path}")
+ freed_bytes = before_size - after_size
+ freed_mb = int(freed_bytes / 1024 / 1024)
+ print(f"> Freed {freed_mb} mb")
+
+
+@main.command()
+@click.argument("app_path")
+def codesign(app_path):
+ """Sign macOS binaries before packaging"""
+ for path in itertools.chain(
+ glob.glob(f"{app_path}/Contents/Resources/lib/**/*.so", recursive=True),
+ glob.glob(f"{app_path}/Contents/Resources/lib/**/*.dylib", recursive=True),
+ [
+ f"{app_path}/Contents/Frameworks/QtCore.framework/Versions/5/QtCore",
+ f"{app_path}/Contents/Frameworks/QtDBus.framework/Versions/5/QtDBus",
+ f"{app_path}/Contents/Frameworks/QtGui.framework/Versions/5/QtGui",
+ f"{app_path}/Contents/Frameworks/QtMacExtras.framework/Versions/5/QtMacExtras",
+ f"{app_path}/Contents/Frameworks/QtWidgets.framework/Versions/5/QtWidgets",
+ f"{app_path}/Contents/Resources/lib/Python",
+ f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/meek-client",
+ f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/obfs4proxy",
+ f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/snowflake-client",
+ f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/tor",
+ f"{app_path}/Contents/Resources/lib/onionshare/resources/tor/libevent-2.1.7.dylib",
+ f"{app_path}/Contents/MacOS/onionshare",
+ f"{app_path}/Contents/MacOS/onionshare-cli",
+ f"{app_path}",
+ ],
+ ):
+ codesign(path, entitlements_plist_path, identity_name_application)
+
+ print(f"> Signed app bundle: {app_path}")
+
+
+@main.command()
+@click.argument("app_path")
+def package(app_path):
+ """Build the DMG package"""
+ if not os.path.exists("/usr/local/bin/create-dmg"):
+ print("> Error: create-dmg is not installed")
+ return
+
+ print("> Create DMG")
+ version_filename = f"{root}/cli/onionshare_cli/resources/version.txt"
+ with open(version_filename) as f:
+ version = f.read().strip()
+
+ os.makedirs(f"{desktop_dir}/dist", exist_ok=True)
+ dmg_path = f"{desktop_dir}/dist/OnionShare-{version}.dmg"
+ run(
+ [
+ "create-dmg",
+ "--volname",
+ "OnionShare",
+ "--volicon",
+ f"{desktop_dir}/onionshare/resources/onionshare.icns",
+ "--window-size",
+ "400",
+ "200",
+ "--icon-size",
+ "100",
+ "--icon",
+ "OnionShare.app",
+ "100",
+ "70",
+ "--hide-extension",
+ "OnionShare.app",
+ "--app-drop-link",
+ "300",
+ "70",
+ dmg_path,
+ app_path,
+ "--identity",
+ identity_name_application,
+ ]
+ )
+
+ print(f"> Finished building DMG: {dmg_path}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/desktop/package/windows.py b/desktop/package/windows.py
index a0bb3db9..215496bd 100644
--- a/desktop/package/windows.py
+++ b/desktop/package/windows.py
@@ -22,9 +22,7 @@ def get_build_path():
python_arch = "win-amd64"
else:
python_arch = "win32"
-
- build_path = os.path.join(desktop_dir, "build", f"exe.{python_arch}-3.9")
- return build_path
+ return os.path.join(desktop_dir, "build", f"exe.{python_arch}-3.9")
def get_size(dir):