diff --git a/build/mac/Readme.md b/build/mac/Readme.md new file mode 100644 index 000000000..0082e0ecb --- /dev/null +++ b/build/mac/Readme.md @@ -0,0 +1,38 @@ +# MacOS Build and Signing Procedure + +## Introduction +This README outlines the procedures for building and signing the MacOS application and DMG installer for the Tribler project. We have updated the build system to streamline the signing process and centralize the build environment. + +## Build System Overview + +### Script Separation +To improve maintainability and clarity, the signing process has been separated from the main build script: +- **Application Signing**: `sign_app.sh` is used to sign the `.app` file. +- **DMG Signing**: `sign_dmg.sh` is used to sign the `.dmg` file. + +### Environment Configuration +Environment variables are isolated in the `env.sh` file under `./build/mac/`, allowing for easier management of build settings. + +### Jenkins Integration +The build process is now performed on a dedicated `mac_mini` hosted on Jenkins, removing the reliance on externally dependent machines. + +## Build and Signing Process +Follow these steps to build and sign the Tribler application for MacOS: + +1. **Set Environment Variables**: Configure necessary variables in `./build/mac/env.sh`. +2. **Initialize Virtual Environment**: Prepare the virtual environment for build operations. +3. **Build the Binary**: Use Python packaging tools like PyInstaller or CxFreeze to compile the application. +4. **Sign the App**: Execute `./build/mac/sign_app.sh` to sign the `.app` file. +5. **Create DMG Installer**: Assemble the DMG file that will contain the application. +6. **Sign the DMG File**: Run `./build/mac/sign_dmg.sh` to sign the DMG and submit it to the Apple Notary service for notarization. + +## Conditions for Signing +The signing scripts will only execute if the following conditions are met, ensuring security and compliance: +- `CODE_SIGN_ENABLED` is set to enable signing. +- `APPLE_DEV_ID` is provided to specify the developer ID used for signing. + +## Repository Links +- **Build Script**: `./build/mac/makedist_macos.sh` +- **Environment Settings**: `./build/mac/env.sh` +- **App Signing Script**: `./build/mac/sign_app.sh` +- **DMG Signing Script**: `./build/mac/sign_dmg.sh` diff --git a/build/mac/env.sh b/build/mac/env.sh new file mode 100644 index 000000000..fdacb237c --- /dev/null +++ b/build/mac/env.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -x # print all commands +set -e # exit when any command fails + +export APPNAME=Tribler +export LOG_LEVEL=${LOG_LEVEL:-"DEBUG"} +export BUILD_ENV=${BUILD_ENV:-"venv"} + +PRE_BUILD_INSTRUCTIONS=$(cat <<-END + git describe --tags | python -c "import sys; print(next(sys.stdin).lstrip('v'))" > .TriblerVersion + git rev-parse HEAD > .TriblerCommit + + export TRIBLER_VERSION=\$(head -n 1 .TriblerVersion) + python3 ./build/update_version.py -r . +END +) + +if [ ! -f .TriblerVersion ]; then + echo "No .TriblerVersion file found, run the following commands:" + echo "$PRE_BUILD_INSTRUCTIONS" + exit 1 +fi + +if [ -e .TriblerVersion ]; then + export DMGNAME="Tribler-$(cat .TriblerVersion)" +fi + +# Directories +export DIST_DIR=dist +export INSTALL_DIR=$DIST_DIR/installdir +export TEMP_DIR=$DIST_DIR/temp +export RESOURCES_DIR=build/mac/resources + +# Environment variables related to signing +export CODE_SIGN_ENABLED=${CODE_SIGN_ENABLED:-""} +export APPLE_DEV_ID=${APPLE_DEV_ID:-""} diff --git a/build/mac/makedist_macos.sh b/build/mac/makedist_macos.sh index 85083fb10..6d13af940 100755 --- a/build/mac/makedist_macos.sh +++ b/build/mac/makedist_macos.sh @@ -5,18 +5,10 @@ set -e # exit when any command fails # Script to build Tribler 64-bit on Mac # Initial author(s): Riccardo Petrocco, Arno Bakker -APPNAME=Tribler -LOG_LEVEL=${LOG_LEVEL:-"DEBUG"} -BUILD_ENV=${BUILD_ENV:-"venv"} - -if [ -e .TriblerVersion ]; then - DMGNAME="Tribler-$(cat .TriblerVersion)" -fi - -export RESOURCES=build/mac/resources +source ./build/mac/env.sh # ----- Clean up -/bin/rm -rf dist +/bin/rm -rf $DIST_DIR # ----- Prepare venv & install dependencies before the build @@ -29,39 +21,35 @@ python3 -m pip install --upgrade -r requirements-build.txt pyinstaller tribler.spec --log-level="${LOG_LEVEL}" -mkdir -p dist/installdir -mv dist/$APPNAME.app dist/installdir +mkdir -p $INSTALL_DIR +mv $DIST_DIR/$APPNAME.app $INSTALL_DIR # From original Makefile # Background -mkdir -p dist/installdir/.background -cp $RESOURCES/background.png dist/installdir/.background +mkdir -p $INSTALL_DIR/.background +cp $RESOURCES_DIR/background.png $INSTALL_DIR/.background # Volume Icon -cp $RESOURCES/VolumeIcon.icns dist/installdir/.VolumeIcon.icns +cp $RESOURCES_DIR/VolumeIcon.icns $INSTALL_DIR/.VolumeIcon.icns # Shortcut to /Applications -ln -s /Applications dist/installdir/Applications +ln -s /Applications $INSTALL_DIR/Applications -touch dist/installdir +touch $INSTALL_DIR -mkdir -p dist/temp +mkdir -p $TEMP_DIR # Sign the app if environment variables are set -if [ -n "$CODE_SIGN_ENABLED" ] && [ -n "$APPLE_DEV_ID" ]; then - echo "Signing $APPNAME.app with ID: $APPLE_DEV_ID" - SIGN_MSG="Developer ID Application: $APPLE_DEV_ID" - codesign --deep --force --verbose --sign "$SIGN_MSG" --options runtime dist/installdir/$APPNAME.app -fi +./build/mac/sign_app.sh # create image -hdiutil create -fs HFS+ -srcfolder dist/installdir -format UDRW -scrub -volname ${APPNAME} dist/$APPNAME.dmg +hdiutil create -fs HFS+ -srcfolder $INSTALL_DIR -format UDRW -scrub -volname ${APPNAME} $DIST_DIR/$APPNAME.dmg # open it -hdiutil attach -readwrite -noverify -noautoopen dist/$APPNAME.dmg -mountpoint dist/temp/mnt +hdiutil attach -readwrite -noverify -noautoopen $DIST_DIR/$APPNAME.dmg -mountpoint $TEMP_DIR/mnt # make sure root folder is opened when image is -bless --folder dist/temp/mnt --openfolder dist/temp/mnt +bless --folder $TEMP_DIR/mnt --openfolder $TEMP_DIR/mnt # hack: wait for completion sleep 1 @@ -100,23 +88,19 @@ osascript -e "tell application \"Finder\"" \ -e "end tell" || true # turn on custom volume icon -SetFile -a C dist/temp/mnt || true +SetFile -a C $TEMP_DIR/mnt || true # close -hdiutil detach dist/temp/mnt || true +hdiutil detach $TEMP_DIR/mnt || true # make read-only -mv dist/$APPNAME.dmg dist/temp/rw.dmg -hdiutil convert dist/temp/rw.dmg -format UDZO -imagekey zlib-level=9 -o dist/$APPNAME.dmg -rm -f dist/temp/rw.dmg +mv $DIST_DIR/$APPNAME.dmg $TEMP_DIR/rw.dmg +hdiutil convert $TEMP_DIR/rw.dmg -format UDZO -imagekey zlib-level=9 -o $DIST_DIR/$APPNAME.dmg +rm -f $TEMP_DIR/rw.dmg if [ ! -z "$DMGNAME" ]; then - mv dist/$APPNAME.dmg dist/$DMGNAME.dmg + mv $DIST_DIR/$APPNAME.dmg $DIST_DIR/$DMGNAME.dmg fi # Sign the dmg package and verify it -if [ -n "$CODE_SIGN_ENABLED" ] && [ -n "$APPLE_DEV_ID" ]; then - codesign --force --verify --verbose --sign "$SIGN_MSG" dist/$DMGNAME.dmg - codesign --verify --verbose=4 dist/$DMGNAME.dmg - spctl --assess --type open --context context:primary-signature -v dist/$DMGNAME.dmg -fi +./build/mac/sign_dmg.sh diff --git a/build/mac/sign_app.sh b/build/mac/sign_app.sh new file mode 100644 index 000000000..f6a6ffc7e --- /dev/null +++ b/build/mac/sign_app.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -x # print all commands +set -e # exit when any command fails + +source ./build/mac/env.sh + +# App file to sign +APP_FILE=$INSTALL_DIR/$APPNAME.app +if [ -z "$APP_FILE" ]; then + echo "$APP_FILE file not found" + exit 1 +fi + +if [ -z "$CODE_SIGN_ENABLED" ]; then + echo "Code sign is not enabled. Skipping code signing the app $APP_FILE." + exit 0 +fi + +if [ -z "$APPLE_DEV_ID" ]; then + echo "Code sign is enabled but Apple Dev ID is not set. Exiting with failure" + exit 1 +fi + +echo "Signing $APP_FILE with Apple Dev ID: $APPLE_DEV_ID" +SIGN_MSG="Developer ID Application: $APPLE_DEV_ID" +codesign --deep --force --verbose --sign "$SIGN_MSG" --options runtime $APP_FILE diff --git a/build/mac/sign_dmg.sh b/build/mac/sign_dmg.sh new file mode 100644 index 000000000..156e71d98 --- /dev/null +++ b/build/mac/sign_dmg.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -x # print all commands +set -e # exit when any command fails + +source ./build/mac/env.sh + +DMG_FILE=$DIST_DIR/$DMGNAME.dmg +if [ -z "$DMG_FILE" ]; then + echo "$DMG_FILE file not found" + exit 1 +fi + +if [ -z "$CODE_SIGN_ENABLED" ]; then + echo "Code sign is not enabled. Skipping code signing the installer $DMG_FILE." + exit 0 +fi + +if [ -z "$APPLE_DEV_ID" ]; then + echo "Code sign is enabled but Apple Dev ID is not set. Exiting with failure" + exit 1 +fi + +# Sign the dmg package and verify it +SIGN_MSG="Developer ID Application: $APPLE_DEV_ID" +codesign --force --verify --verbose --sign "$SIGN_MSG" $DMG_FILE +codesign --verify --verbose=4 $DMG_FILE + +# Assuming the keychain profile with the signing key is created and named as "tribler-codesign-profile". +# If not create the keychain profile with the following command: +# xcrun notarytool store-credentials "tribler-codesign-profile" --apple-id "" --team-id "" +KEYCHAIN_PROFILE=${KEYCHAIN_PROFILE:-"tribler-codesign-profile"} +# Submit the DMG for notarization and staple afterwards +xcrun notarytool submit $DMG_FILE --keychain-profile "$KEYCHAIN_PROFILE" --wait +xcrun stapler staple $DMG_FILE +# Verify the notarization +spctl --assess --type open --context context:primary-signature -v $DMG_FILE diff --git a/src/tribler/gui/tribler_window.py b/src/tribler/gui/tribler_window.py index 43d9ff226..e088f3a78 100644 --- a/src/tribler/gui/tribler_window.py +++ b/src/tribler/gui/tribler_window.py @@ -163,7 +163,6 @@ def __init__(self, app_manager: AppManager, root_state_dir: Path, api_port: int, self.tribler_core_test_exception_shortcut = QShortcut(QKeySequence("Ctrl+Alt+Shift+C"), self) connect(self.tribler_core_test_exception_shortcut.activated, self.on_test_tribler_core_exception) - connect(self.top_search_bar.clicked, self.clicked_search_bar) connect(self.top_search_bar.returnPressed, self.on_top_search_bar_return_pressed) # Remove the focus rect on OS X @@ -770,12 +769,6 @@ def deselect_all_menu_buttons(self, except_select=None): button.setEnabled(True) button.setChecked(False) - def clicked_search_bar(self, checked=False): - query = self.top_search_bar.text() - if query and self.search_results_page.has_results: - self.deselect_all_menu_buttons() - self.stackedWidget.setCurrentIndex(PAGE_SEARCH_RESULTS) - def on_top_search_bar_return_pressed(self): query_text = self.top_search_bar.text() if not query_text: diff --git a/src/tribler/gui/utilities.py b/src/tribler/gui/utilities.py index 061da69d3..8bc0c4124 100644 --- a/src/tribler/gui/utilities.py +++ b/src/tribler/gui/utilities.py @@ -406,8 +406,6 @@ def html_label(text, background="#e4e4e4", color="#222222", bold=True): def votes_count(votes=0.0): votes = float(votes) - # FIXME: this is a temp fix to cap the normalized value to 1. - # The votes should already be normalized before formatting it. votes = max(0.0, min(votes, 1.0)) # We add sqrt to flatten the votes curve a bit votes = math.sqrt(votes) diff --git a/src/tribler/gui/widgets/popular/popular_torrents_model.py b/src/tribler/gui/widgets/popular/popular_torrents_model.py index 1bd43eb92..055c36229 100644 --- a/src/tribler/gui/widgets/popular/popular_torrents_model.py +++ b/src/tribler/gui/widgets/popular/popular_torrents_model.py @@ -2,7 +2,7 @@ class PopularTorrentsModel(ChannelContentModel): - columns_shown = (Column.CATEGORY, Column.NAME, Column.SIZE, Column.CREATED) + columns_shown = (Column.CATEGORY, Column.NAME, Column.SIZE, Column.HEALTH, Column.CREATED) def __init__(self, *args, **kwargs): super().__init__(*args, endpoint_url="metadata/torrents/popular", **kwargs) diff --git a/src/tribler/gui/widgets/tablecontentdelegate.py b/src/tribler/gui/widgets/tablecontentdelegate.py index 3672214ea..5a1afbf59 100644 --- a/src/tribler/gui/widgets/tablecontentdelegate.py +++ b/src/tribler/gui/widgets/tablecontentdelegate.py @@ -139,7 +139,6 @@ def __init__(self, parent=None): self.hovering_over_tag_edit_button: bool = False self.hovering_over_download_popular_torrent_button: int = -1 - # TODO: restore this behavior, so there is really some tolerance zone! # We have to control if mouse is in the buttons box to add some tolerance for vertical mouse # misplacement around the buttons. The button box effectively overlaps upper and lower rows. # row 0 @@ -565,7 +564,6 @@ class TriblerContentDelegate( SnippetCreatedColumnMixin, ): def __init__(self, table_view, parent=None): - # TODO: refactor this not to rely on inheritance order, but instead use interface method pattern TriblerButtonsDelegate.__init__(self, parent) self.download_button = DownloadIconButton()