diff --git a/RELEASE.md b/RELEASE.md index 4dcd1770..19fc40dd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -112,25 +112,23 @@ This will create `desktop/windows/OnionShare-$VERSION.msi`, signed. ## macOS -Set up the development environment described in `README.md`. And install `create-dmg`: +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 -brew install create-dmg +poetry run python setup-freeze.py bdist_dmg ``` -Run the macOS build script: - -```sh -poetry run ./package/macos/build.py --with-codesign -``` +The will create `build/OnionShare.dmg`. Now, notarize the release. You must have an app-specific Apple ID password saved in the login keychain called `onionshare-notarize`. -- Notarize it: `xcrun altool --notarize-app --primary-bundle-id "com.micahflee.onionshare" -u "micah@micahflee.com" -p "@keychain:onionshare-notarize" --file macOS/OnionShare.dmg` +- Notarize it: `xcrun altool --notarize-app --primary-bundle-id "com.micahflee.onionshare" -u "micah@micahflee.com" -p "@keychain:onionshare-notarize" --file build/OnionShare.dmg` - Wait for it to get approved, check status with: `xcrun altool --notarization-history 0 -u "micah@micahflee.com" -p "@keychain:onionshare-notarize"` -- After it's approved, staple the ticket: `xcrun stapler staple macOS/OnionShare.dmg` +- After it's approved, staple the ticket: `xcrun stapler staple build/OnionShare.dmg` -This will create `desktop/macOS/OnionShare.dmg`, signed and notarized. +This will create `desktop/build/OnionShare.dmg`, signed and notarized. ## Source package diff --git a/desktop/package/macos/Entitlements.plist b/desktop/package/Entitlements.plist similarity index 100% rename from desktop/package/macos/Entitlements.plist rename to desktop/package/Entitlements.plist diff --git a/desktop/package/macos/build.py b/desktop/package/macos/build.py deleted file mode 100755 index 2bfea3ad..00000000 --- a/desktop/package/macos/build.py +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env python3 -import os -import inspect -import subprocess -import argparse -import shutil -import glob -import itertools - -root = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) - ) - ) -) - - -def codesign(path, entitlements, identity): - run( - [ - "codesign", - "--sign", - identity, - "--entitlements", - str(entitlements), - "--timestamp", - "--deep", - str(path), - "--force", - "--options", - "runtime", - ] - ) - - -def run(cmd, cwd=None): - subprocess.run(cmd, cwd=cwd, check=True) - - -def main(): - # Parse arguments - parser = argparse.ArgumentParser() - parser.add_argument( - "--with-codesign", - action="store_true", - dest="with_codesign", - help="Codesign the app bundle", - ) - args = parser.parse_args() - - cli_dir = os.path.join(root, "cli") - desktop_dir = os.path.join(root, "desktop") - - print("○ Clean up from last build") - if os.path.exists(os.path.join(cli_dir, "dist")): - shutil.rmtree(os.path.join(cli_dir, "dist")) - if os.path.exists(os.path.join(desktop_dir, "macOS")): - shutil.rmtree(os.path.join(desktop_dir, "macOS")) - - print("○ Build onionshare-cli") - run(["poetry", "install"], cli_dir) - run(["poetry", "build"], cli_dir) - whl_filename = glob.glob(os.path.join(cli_dir, "dist", "*.whl"))[0] - whl_basename = os.path.basename(whl_filename) - shutil.copyfile(whl_filename, os.path.join(desktop_dir, whl_basename)) - - print("○ Create app bundle") - run(["briefcase", "create"], desktop_dir) - app_path = os.path.join(desktop_dir, "macOS", "app", "OnionShare", "OnionShare.app") - - print("○ Delete unused Qt5 frameworks from app bundle") - 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", - "QtNfc", - "QtOpenGL", - "QtPdf", - "QtPdfWidgets", - "QtPositioning", - "QtPositioningQuick", - "QtPurchasing", - "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( - os.path.join( - app_path, - "Contents", - "Resources", - "app_packages", - "PySide2", - "Qt", - "lib", - f"{framework}.framework", - ) - ) - try: - os.remove( - os.path.join( - app_path, - "Contents", - "Resources", - "app_packages", - "PySide2", - f"{framework}.abi3.so", - ) - ) - os.remove( - os.path.join( - app_path, - "Contents", - "Resources", - "app_packages", - "PySide2", - f"{framework}.pyi", - ) - ) - except FileNotFoundError: - pass - shutil.rmtree( - os.path.join( - app_path, - "Contents", - "Resources", - "app_packages", - "PySide2", - "Designer.app", - ) - ) - - print(f"○ Unsigned app bundle: {app_path}") - - if args.with_codesign: - identity_name_application = "Developer ID Application: Micah Lee (N9B95FDWH4)" - entitlements_plist_path = os.path.join( - desktop_dir, "package", "macos", "Entitlements.plist" - ) - - print("○ Code sign app bundle") - for path in itertools.chain( - glob.glob( - f"{app_path}/Contents/Resources/app_packages/**/*.dylib", recursive=True - ), - glob.glob( - f"{app_path}/Contents/Resources/app_packages/**/*.so", recursive=True - ), - glob.glob( - f"{app_path}/Contents/Resources/Support/**/*.dylib", recursive=True - ), - glob.glob(f"{app_path}/Contents/Resources/Support/**/*.so", recursive=True), - glob.glob( - f"{app_path}/Contents/Resources/app_packages/PySide2/Qt/lib/**/Versions/5/*", - recursive=True, - ), - [ - f"{app_path}/Contents/Resources/app_packages/PySide2/pyside2-lupdate", - f"{app_path}/Contents/Resources/app_packages/PySide2/rcc", - f"{app_path}/Contents/Resources/app_packages/PySide2/uic", - app_path, - ], - ): - codesign(path, entitlements_plist_path, identity_name_application) - codesign(app_path, entitlements_plist_path, identity_name_application) - print(f"○ Signed app bundle: {app_path}") - - if not os.path.exists("/usr/local/bin/create-dmg"): - print("○ Error: create-dmg is not installed") - return - - print("○ Create DMG") - dmg_path = os.path.join(desktop_dir, "macOS", "OnionShare.dmg") - run( - [ - "create-dmg", - "--volname", - "OnionShare", - "--volicon", - os.path.join( - desktop_dir, "src", "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/build.py b/desktop/package/windows/build.py deleted file mode 100644 index bbccbe46..00000000 --- a/desktop/package/windows/build.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/usr/bin/env python3 -import os -import inspect -import subprocess -import shutil -import glob - -root = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) - ) - ) -) - - -def run(cmd, cwd=None): - subprocess.run(cmd, cwd=cwd, check=True) - - -def main(): - cli_dir = os.path.join(root, "cli") - desktop_dir = os.path.join(root, "desktop") - - print("○ Clean up from last build") - if os.path.exists(os.path.join(cli_dir, "dist")): - shutil.rmtree(os.path.join(cli_dir, "dist")) - if os.path.exists(os.path.join(desktop_dir, "windows")): - shutil.rmtree(os.path.join(desktop_dir, "windows")) - - print("○ Building onionshare-cli") - run(["poetry", "install"], cli_dir) - run(["poetry", "build"], cli_dir) - whl_filename = glob.glob(os.path.join(cli_dir, "dist", "*.whl"))[0] - whl_basename = os.path.basename(whl_filename) - shutil.copyfile(whl_filename, os.path.join(desktop_dir, whl_basename)) - - print("○ Create the binary") - run(["briefcase", "create"], desktop_dir) - - print("○ Delete unused Qt5 DLLs to save space") - for filename in [ - "plugins/assetimporters/assimp.dll", - "plugins/assetimporters/uip.dll", - "plugins/audio/qtaudio_wasapi.dll", - "plugins/audio/qtaudio_windows.dll", - "plugins/bearer/qgenericbearer.dll", - "plugins/canbus/qtpassthrucanbus.dll", - "plugins/canbus/qtpeakcanbus.dll", - "plugins/canbus/qtsysteccanbus.dll", - "plugins/canbus/qttinycanbus.dll", - "plugins/canbus/qtvectorcanbus.dll", - "plugins/canbus/qtvirtualcanbus.dll", - "plugins/gamepads/xinputgamepad.dll", - "plugins/generic/qtuiotouchplugin.dll", - "plugins/geometryloaders/defaultgeometryloader.dll", - "plugins/geometryloaders/gltfgeometryloader.dll", - "plugins/geoservices/qtgeoservices_esri.dll", - "plugins/geoservices/qtgeoservices_itemsoverlay.dll", - "plugins/geoservices/qtgeoservices_mapbox.dll", - "plugins/geoservices/qtgeoservices_nokia.dll", - "plugins/geoservices/qtgeoservices_osm.dll", - "plugins/mediaservice/dsengine.dll", - "plugins/mediaservice/qtmedia_audioengine.dll", - "plugins/mediaservice/wmfengine.dll", - "plugins/platforminputcontexts/qtvirtualkeyboardplugin.dll", - "plugins/platformthemes/qxdgdesktopportal.dll", - "plugins/playlistformats/qtmultimedia_m3u.dll", - "plugins/position/qtposition_positionpoll.dll", - "plugins/position/qtposition_serialnmea.dll", - "plugins/position/qtposition_winrt.dll", - "plugins/printsupport/windowsprintersupport.dll", - "plugins/qmltooling/qmldbg_debugger.dll", - "plugins/qmltooling/qmldbg_inspector.dll", - "plugins/qmltooling/qmldbg_local.dll", - "plugins/qmltooling/qmldbg_messages.dll", - "plugins/qmltooling/qmldbg_native.dll", - "plugins/qmltooling/qmldbg_nativedebugger.dll", - "plugins/qmltooling/qmldbg_preview.dll", - "plugins/qmltooling/qmldbg_profiler.dll", - "plugins/qmltooling/qmldbg_quickprofiler.dll", - "plugins/qmltooling/qmldbg_server.dll", - "plugins/qmltooling/qmldbg_tcp.dll", - "plugins/renderers/openglrenderer.dll", - "plugins/renderplugins/scene2d.dll", - "plugins/scenegraph/qsgd3d12backend.dll", - "plugins/sceneparsers/assimpsceneimport.dll", - "plugins/sceneparsers/gltfsceneexport.dll", - "plugins/sceneparsers/gltfsceneimport.dll", - "plugins/sensorgestures/qtsensorgestures_plugin.dll", - "plugins/sensorgestures/qtsensorgestures_shakeplugin.dll", - "plugins/sensors/qtsensors_generic.dll", - "plugins/sqldrivers/qsqlite.dll", - "plugins/sqldrivers/qsqlodbc.dll", - "plugins/sqldrivers/qsqlpsql.dll", - "plugins/texttospeech/qtexttospeech_sapi.dll", - "plugins/virtualkeyboard/qtvirtualkeyboard_hangul.dll", - "plugins/virtualkeyboard/qtvirtualkeyboard_openwnn.dll", - "plugins/virtualkeyboard/qtvirtualkeyboard_pinyin.dll", - "plugins/virtualkeyboard/qtvirtualkeyboard_tcime.dll", - "plugins/virtualkeyboard/qtvirtualkeyboard_thai.dll", - "plugins/webview/qtwebview_webengine.dll", - "qml/Qt/labs/animation/labsanimationplugin.dll", - "qml/Qt/labs/calendar/qtlabscalendarplugin.dll", - "qml/Qt/labs/folderlistmodel/qmlfolderlistmodelplugin.dll", - "qml/Qt/labs/location/locationlabsplugin.dll", - "qml/Qt/labs/lottieqt/lottieqtplugin.dll", - "qml/Qt/labs/platform/qtlabsplatformplugin.dll", - "qml/Qt/labs/qmlmodels/labsmodelsplugin.dll", - "qml/Qt/labs/settings/qmlsettingsplugin.dll", - "qml/Qt/labs/sharedimage/sharedimageplugin.dll", - "qml/Qt/labs/wavefrontmesh/qmlwavefrontmeshplugin.dll", - "qml/Qt3D/Animation/quick3danimationplugin.dll", - "qml/Qt3D/Core/quick3dcoreplugin.dll", - "qml/Qt3D/Extras/quick3dextrasplugin.dll", - "qml/Qt3D/Input/quick3dinputplugin.dll", - "qml/Qt3D/Logic/quick3dlogicplugin.dll", - "qml/Qt3D/Render/quick3drenderplugin.dll", - "qml/QtBluetooth/declarative_bluetooth.dll", - "qml/QtCharts/qtchartsqml2.dll", - "qml/QtDataVisualization/datavisualizationqml2.dll", - "qml/QtGamepad/declarative_gamepad.dll", - "qml/QtGraphicalEffects/private/qtgraphicaleffectsprivate.dll", - "qml/QtGraphicalEffects/qtgraphicaleffectsplugin.dll", - "qml/QtLocation/declarative_location.dll", - "qml/QtMultimedia/declarative_multimedia.dll", - "qml/QtNfc/declarative_nfc.dll", - "qml/QtPositioning/declarative_positioning.dll", - "qml/QtPurchasing/declarative_purchasing.dll", - "qml/QtQml/Models.2/modelsplugin.dll", - "qml/QtQml/qmlplugin.dll", - "qml/QtQml/RemoteObjects/qtqmlremoteobjects.dll", - "qml/QtQml/StateMachine/qtqmlstatemachine.dll", - "qml/QtQml/WorkerScript.2/workerscriptplugin.dll", - "qml/QtQuick/Controls/qtquickcontrolsplugin.dll", - "qml/QtQuick/Controls/Styles/Flat/qtquickextrasflatplugin.dll", - "qml/QtQuick/Controls.2/Fusion/qtquickcontrols2fusionstyleplugin.dll", - "qml/QtQuick/Controls.2/Imagine/qtquickcontrols2imaginestyleplugin.dll", - "qml/QtQuick/Controls.2/Material/qtquickcontrols2materialstyleplugin.dll", - "qml/QtQuick/Controls.2/qtquickcontrols2plugin.dll", - "qml/QtQuick/Controls.2/Universal/qtquickcontrols2universalstyleplugin.dll", - "qml/QtQuick/Dialogs/dialogplugin.dll", - "qml/QtQuick/Dialogs/Private/dialogsprivateplugin.dll", - "qml/QtQuick/Extras/qtquickextrasplugin.dll", - "qml/QtQuick/Layouts/qquicklayoutsplugin.dll", - "qml/QtQuick/LocalStorage/qmllocalstorageplugin.dll", - "qml/QtQuick/Particles.2/particlesplugin.dll", - "qml/QtQuick/Pdf/pdfplugin.dll", - "qml/QtQuick/PrivateWidgets/widgetsplugin.dll", - "qml/QtQuick/Scene2D/qtquickscene2dplugin.dll", - "qml/QtQuick/Scene3D/qtquickscene3dplugin.dll", - "qml/QtQuick/Shapes/qmlshapesplugin.dll", - "qml/QtQuick/Templates.2/qtquicktemplates2plugin.dll", - "qml/QtQuick/Timeline/qtquicktimelineplugin.dll", - "qml/QtQuick/VirtualKeyboard/qtquickvirtualkeyboardplugin.dll", - "qml/QtQuick/VirtualKeyboard/Settings/qtquickvirtualkeyboardsettingsplugin.dll", - "qml/QtQuick/VirtualKeyboard/Styles/qtquickvirtualkeyboardstylesplugin.dll", - "qml/QtQuick/Window.2/windowplugin.dll", - "qml/QtQuick/XmlListModel/qmlxmllistmodelplugin.dll", - "qml/QtQuick.2/qtquick2plugin.dll", - "qml/QtQuick3D/Effects/qtquick3deffectplugin.dll", - "qml/QtQuick3D/Helpers/qtquick3dhelpersplugin.dll", - "qml/QtQuick3D/Materials/qtquick3dmaterialplugin.dll", - "qml/QtQuick3D/qquick3dplugin.dll", - "qml/QtRemoteObjects/qtremoteobjects.dll", - "qml/QtScxml/declarative_scxml.dll", - "qml/QtSensors/declarative_sensors.dll", - "qml/QtTest/qmltestplugin.dll", - "qml/QtWebChannel/declarative_webchannel.dll", - "qml/QtWebEngine/qtwebengineplugin.dll", - "qml/QtWebSockets/declarative_qmlwebsockets.dll", - "qml/QtWebView/declarative_webview.dll", - "Qt5DBus.dll", - "Qt5PrintSupport.dll", - "Qt5Script.dll", - "Qt5ScriptTools.dll", - "Qt5Scxml.dll", - "Qt5Sensors.dll", - "Qt5SerialBus.dll", - "Qt5SerialPort.dll", - "Qt5Sql.dll", - "Qt5Svg.dll", - "Qt5Test.dll", - "Qt5TextToSpeech.dll", - "Qt5VirtualKeyboard.dll", - "Qt5WebChannel.dll", - "Qt5WebEngine.dll", - "Qt5WebEngineCore.dll", - "Qt5WebEngineWidgets.dll", - "Qt5WebSockets.dll", - "Qt5WebView.dll", - "Qt5Xml.dll", - "Qt5XmlPatterns.dll", - ]: - os.remove( - os.path.join( - desktop_dir, - "windows", - "msi", - "OnionShare", - "src", - "app_packages", - "PySide2", - filename.replace("/", "\\"), - ) - ) - - print("○ Create the installer") - run(["briefcase", "package"], desktop_dir) - msi_filename = glob.glob(os.path.join(desktop_dir, "windows", "OnionShare-*.msi"))[ - 0 - ] - print(f"○ Created unsigned installer: {msi_filename}") - - print("○ Signing installer") - run( - [ - "signtool.exe", - "sign", - "/v", - "/d", - "OnionShare", - "/a", - "/tr", - "http://time.certum.pl/", - msi_filename, - ], - desktop_dir, - ) - print(f"○ Signed installer: {msi_filename}") - - -if __name__ == "__main__": - main() diff --git a/desktop/setup-freeze.py b/desktop/setup-freeze.py index e4e31389..0a26620b 100644 --- a/desktop/setup-freeze.py +++ b/desktop/setup-freeze.py @@ -114,6 +114,7 @@ include_files = [(os.path.join("..", "LICENSE"), "LICENSE")] if platform.system() == "Windows": include_msvcr = True gui_base = "Win32GUI" + exec_icon = os.path.join("onionshare", "resources", "onionshare.ico") elif platform.system() == "Darwin": import PySide2 @@ -121,6 +122,7 @@ elif platform.system() == "Darwin": include_msvcr = False gui_base = None + exec_icon = None include_files += [ ( os.path.join(PySide2.__path__[0], "libpyside2.abi3.5.15.dylib"), @@ -137,6 +139,7 @@ setup( version=version, description="Securely and anonymously share files, host websites, and chat with friends using the Tor network", options={ + # build_exe, for Windows and macOS "build_exe": { "packages": [ "cffi", @@ -197,18 +200,31 @@ setup( ], "include_files": include_files, "include_msvcr": include_msvcr, - } + }, + # bdist_mac, making the macOS app bundle + "bdist_mac": { + "iconfile": os.path.join("onionshare", "resources", "onionshare.icns"), + "bundle_name": "OnionShare", + "codesign_identity": "Developer ID Application: Micah Lee (N9B95FDWH4)", + "codesign_entitlements": os.path.join("package", "Entitlements.plist"), + "codesign_deep": True, + }, + # bdist_dmg, packaging the macOS app bundle in a dmg + "bdist_dmg": { + "volume_label": f"OnionShare-{version}", + "applications_shortcut": True, + }, }, executables=[ Executable( "package/onionshare.py", base=gui_base, - icon=os.path.join("onionshare", "resources", "onionshare.ico"), + icon=exec_icon, ), Executable( "package/onionshare-cli.py", base=None, - icon=os.path.join("onionshare", "resources", "onionshare.ico"), + icon=exec_icon, ), ], )