From b459b95950c8557a8f3d3f2d67f0a4420759f503 Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sat, 2 May 2026 03:28:01 +0200 Subject: [PATCH 1/8] - The comma-separated return creates a 7-element tuple instead of comparing two tuples, every equality check returns True - master -> main branch fix - prepare_trash now calls self.move_to_trash instead of self.delete, so plugin subclasses won't permanently delete files when the user expects trashing - NotImplementedError - unrecognized platforms fail fast with a clear message instead of a cryptic NameError - removed shadowing basename import - get_column_widths uses range so plugin-added columns get their widths saved/restored --- build.py | 8 ++++---- src/main/python/fman/__init__.py | 2 ++ src/main/python/fman/fs.py | 2 +- src/main/python/fman/impl/model/worker.py | 6 +++--- src/main/python/fman/impl/widgets.py | 2 +- .../resources/base/Plugins/Core/core/commands/__init__.py | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/build.py b/build.py index 60255c1a..f9988e72 100644 --- a/build.py +++ b/build.py @@ -96,7 +96,7 @@ def release(): settings_path, next_version + snapshot_suffix, 'Bump version for next development iteration' ) - git('push', '-u', 'origin', 'master') + git('push', '-u', 'origin', 'main') try: git('push', 'origin', release_tag) try: @@ -106,7 +106,7 @@ def release(): ' git pull\n' ' git checkout %s\n' ' python build.py release\n' - ' git checkout master\n\n' + ' git checkout main\n\n' 'on the other OSs now, then come back here and do:' '\n\n' ' python build.py post_release\n' @@ -117,7 +117,7 @@ def release(): raise except: git('revert', '--no-edit', revision_before + '..HEAD' ) - git('push', '-u', 'origin', 'master') + git('push', '-u', 'origin', 'main') revision_before = git('rev-parse', 'HEAD').rstrip() raise except: @@ -149,7 +149,7 @@ def post_release(): create_cloudfront_invalidation(cloudfront_items_to_invalidate) record_release_on_server() upload_core_to_github() - git('checkout', 'master') + git('checkout', 'main') def _prompt_for_next_version(release_version): next_version = _get_suggested_next_version(release_version) diff --git a/src/main/python/fman/__init__.py b/src/main/python/fman/__init__.py index ab1f58d1..eefe7c12 100644 --- a/src/main/python/fman/__init__.py +++ b/src/main/python/fman/__init__.py @@ -41,6 +41,8 @@ DATA_DIRECTORY = expanduser('~/Library/Application Support/fman') elif PLATFORM == 'Linux': DATA_DIRECTORY = expanduser('~/.config/fman') +else: + raise NotImplementedError('Unsupported platform: %s' % PLATFORM) class ApplicationCommand: def __init__(self, window): diff --git a/src/main/python/fman/fs.py b/src/main/python/fman/fs.py index 2cf8f00f..d020a0a7 100644 --- a/src/main/python/fman/fs.py +++ b/src/main/python/fman/fs.py @@ -146,7 +146,7 @@ def prepare_trash(self, path): raise self._operation_not_implemented() return [Task( 'Deleting ' + path.rsplit('/', 1)[-1], - fn=self.delete, args=(path,), size=1 + fn=self.move_to_trash, args=(path,), size=1 )] def touch(self, path): raise self._operation_not_implemented() diff --git a/src/main/python/fman/impl/model/worker.py b/src/main/python/fman/impl/model/worker.py index 51066233..df74de27 100644 --- a/src/main/python/fman/impl/model/worker.py +++ b/src/main/python/fman/impl/model/worker.py @@ -21,7 +21,7 @@ def submit(self, priority, fn, *args, **kwargs): with self._shutdown_lock: if self._shutdown: return - self._queue.put(WorkItem(priority, fn, *args, *kwargs)) + self._queue.put(WorkItem(priority, fn, *args, **kwargs)) def shutdown(self): with self._shutdown_lock: self._shutdown = True @@ -56,7 +56,7 @@ def __lt__(self, other): return NotImplemented def __eq__(self, other): try: - return self._fn, self._args, self._kwargs, self._priority == \ - other._fn, other._args, other._kwargs, other._priority + return (self._fn, self._args, self._kwargs, self._priority) == \ + (other._fn, other._args, other._kwargs, other._priority) except AttributeError: return NotImplemented \ No newline at end of file diff --git a/src/main/python/fman/impl/widgets.py b/src/main/python/fman/impl/widgets.py index c92c08fc..12a8fe24 100644 --- a/src/main/python/fman/impl/widgets.py +++ b/src/main/python/fman/impl/widgets.py @@ -155,7 +155,7 @@ def get_sort_column(self): return column, ascending @run_in_main_thread def get_column_widths(self): - return [self._file_view.columnWidth(i) for i in (0, 1)] + return [self._file_view.columnWidth(i) for i in range(self._model.columnCount())] @run_in_main_thread def set_column_widths(self, column_widths): num_columns = self._model.columnCount() diff --git a/src/main/resources/base/Plugins/Core/core/commands/__init__.py b/src/main/resources/base/Plugins/Core/core/commands/__init__.py index 319553d4..644397e8 100644 --- a/src/main/resources/base/Plugins/Core/core/commands/__init__.py +++ b/src/main/resources/base/Plugins/Core/core/commands/__init__.py @@ -17,7 +17,7 @@ from io import UnsupportedOperation from itertools import chain from os import strerror -from os.path import basename, pardir +from os.path import pardir from pathlib import PurePath from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices From 98a8c0eb433e69f52321d8639016feeb0c3ac76f Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sat, 2 May 2026 03:34:28 +0200 Subject: [PATCH 2/8] fix failing tests --- src/main/resources/base/Plugins/Core/core/tests/fs/test_zip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/base/Plugins/Core/core/tests/fs/test_zip.py b/src/main/resources/base/Plugins/Core/core/tests/fs/test_zip.py index 837f2bc2..a68a38d6 100644 --- a/src/main/resources/base/Plugins/Core/core/tests/fs/test_zip.py +++ b/src/main/resources/base/Plugins/Core/core/tests/fs/test_zip.py @@ -351,7 +351,7 @@ def _read_directory(self, dir_path): child_contents = self._read_directory(child) else: child_contents = child.read_text() - result[child.name] = child_contents + result[normalize('NFC', child.name)] = child_contents return result def _expect_zip_contents(self, contents, zip_file_path): with TemporaryDirectory() as tmp_dir: From 8cf09613cc230d23046428fc31f161b238ac9b3c Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sat, 2 May 2026 03:43:47 +0200 Subject: [PATCH 3/8] all pending plugin errors are now shown at startup instead of just the first one --- src/main/python/fman/impl/plugins/error.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/python/fman/impl/plugins/error.py b/src/main/python/fman/impl/plugins/error.py index 8b19d965..c6797555 100644 --- a/src/main/python/fman/impl/plugins/error.py +++ b/src/main/python/fman/impl/plugins/error.py @@ -46,8 +46,8 @@ def handle_system_exit(self, code=0): self._app.exit(code) def on_main_window_shown(self, main_window): self._main_window = main_window - if self._pending_error_messages: - self._main_window.show_alert(self._pending_error_messages[0]) + for message in self._pending_error_messages: + self._main_window.show_alert(message) def _get_plugin_traceback(self, exc): if isinstance(exc, ThemeError): return exc.description From 064b5f0eb887c97202e3f80b7973715a33d7aa7d Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sat, 2 May 2026 03:49:46 +0200 Subject: [PATCH 4/8] =?UTF-8?q?=20=20-=20command=5Fregistry.py=20=E2=80=94?= =?UTF-8?q?=20=5Fset=5Fcontext=20now=20uses=20try/finally=20so=20cm.=5F=5F?= =?UTF-8?q?exit=5F=5F=20always=20runs,=20even=20on=20exception.=20=20=20-?= =?UTF-8?q?=20util/qt/=5F=5Finit=5F=5F.py=20=E2=80=94=20Added=20missing=20?= =?UTF-8?q?c=5Fvoid=5Fp=20import=20from=20ctypes,=20fixing=20a=20macOS=20r?= =?UTF-8?q?untime=20crash.=20=20=20-=20table.py=20=E2=80=94=20Fixed=20off-?= =?UTF-8?q?by-one:=20bounds=20check=20now=20rejects=20len=20+=201=20correc?= =?UTF-8?q?tly.=20=20=20-=20widgets.py=20=E2=80=94=20Added=20null=20guard?= =?UTF-8?q?=20on=20=5Fmain=5Fwindow=20before=20accessing=20it=20in=20state?= =?UTF-8?q?=20change=20handler.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/python/fman/impl/model/table.py | 2 +- src/main/python/fman/impl/plugins/command_registry.py | 8 +++++--- src/main/python/fman/impl/util/qt/__init__.py | 3 ++- src/main/python/fman/impl/widgets.py | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/python/fman/impl/model/table.py b/src/main/python/fman/impl/model/table.py index 33fe1c80..c1d17ff6 100644 --- a/src/main/python/fman/impl/model/table.py +++ b/src/main/python/fman/impl/model/table.py @@ -151,7 +151,7 @@ def insert(self, rows, first_rownum): new_keys = {row.key: first_rownum + i for i, row in enumerate(rows)} with self._lock: # Perform this check here, once we have the lock: - if first_rownum < 0 or first_rownum > len(self._rows) + 1: + if first_rownum < 0 or first_rownum > len(self._rows): raise ValueError('Invalid first_rownum: %d' % first_rownum) num_rows = len(rows) for row in self._rows[first_rownum:]: diff --git a/src/main/python/fman/impl/plugins/command_registry.py b/src/main/python/fman/impl/plugins/command_registry.py index e7204763..7bb1a0e6 100644 --- a/src/main/python/fman/impl/plugins/command_registry.py +++ b/src/main/python/fman/impl/plugins/command_registry.py @@ -153,9 +153,11 @@ def _set_context(self, pane, file_under_cursor=_DEFAULT): if file_under_cursor is not self._DEFAULT: cm = pane._override_file_under_cursor(file_under_cursor) cm.__enter__() - yield - if file_under_cursor is not self._DEFAULT: - cm.__exit__(None, None, None) + try: + yield + finally: + if file_under_cursor is not self._DEFAULT: + cm.__exit__(None, None, None) def _get_default_aliases(cmd_class): return re.sub(r'([a-z])([A-Z])', r'\1 \2', cmd_class.__name__)\ diff --git a/src/main/python/fman/impl/util/qt/__init__.py b/src/main/python/fman/impl/util/qt/__init__.py index 9d77ee96..8baddbad 100644 --- a/src/main/python/fman/impl/util/qt/__init__.py +++ b/src/main/python/fman/impl/util/qt/__init__.py @@ -16,8 +16,9 @@ def disable_window_animations_mac(window): # penalties and leads to subtle changes in behaviour. We therefore wait for # the Show event: def eventFilter(target, event): + from ctypes import c_void_p from objc import objc_object - view = objc_object(c_void_p=int(target.winId())) + view = objc_object(c_void_p=c_void_p(int(target.winId()))) NSWindowAnimationBehaviorNone = 2 view.window().setAnimationBehavior_(NSWindowAnimationBehaviorNone) FilterEventOnce(window, QEvent.Show, eventFilter) diff --git a/src/main/python/fman/impl/widgets.py b/src/main/python/fman/impl/widgets.py index 12a8fe24..5a591d98 100644 --- a/src/main/python/fman/impl/widgets.py +++ b/src/main/python/fman/impl/widgets.py @@ -37,7 +37,7 @@ def exit(self, returnCode=0): def set_style_sheet(self, stylesheet): self.setStyleSheet(stylesheet) def _on_state_changed(self, new_state): - if new_state == Qt.ApplicationActive: + if new_state == Qt.ApplicationActive and self._main_window is not None: for pane in self._main_window.get_panes(): pane.reload() From 93f580e40940ccf1b539a398466bd1b05b1eae11 Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sat, 2 May 2026 03:50:56 +0200 Subject: [PATCH 5/8] =?UTF-8?q?=20=20-=20util/path.py=20=E2=80=94=20normal?= =?UTF-8?q?ize=20now=20loops=20until=20all=20..=20segments=20are=20resolve?= =?UTF-8?q?d,=20so=20a/b/c/../../d=20correctly=20becomes=20a/d.=20=20=20-?= =?UTF-8?q?=20session.py=20=E2=80=94=20Removed=20dead=20=5Fget=5Fstartup?= =?UTF-8?q?=5Fmessage=20method=20(duplicated=20by=20=5Fshow=5Fstartup=5Fme?= =?UTF-8?q?ssages)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/python/fman/impl/session.py | 7 ------- src/main/python/fman/impl/util/path.py | 5 ++++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/python/fman/impl/session.py b/src/main/python/fman/impl/session.py index 58278245..18144e70 100644 --- a/src/main/python/fman/impl/session.py +++ b/src/main/python/fman/impl/session.py @@ -68,13 +68,6 @@ def _show_startup_messages(self, main_window): 'Updated to v%s. ' \ 'Changelog' % self._fman_version main_window.show_status_message(status_message, timeout_secs=5) - def _get_startup_message(self): - previous_version = self._settings.get('fman_version', None) - if not previous_version or previous_version == self._fman_version: - return 'v%s ready.' % self._fman_version - return 'Updated to v%s. ' \ - 'Changelog' \ - % self._fman_version def _init_panes(self, panes, pane_infos, paths_on_cmdline): with ThreadPoolExecutor(max_workers=len(panes)) as executor: futures = [ diff --git a/src/main/python/fman/impl/util/path.py b/src/main/python/fman/impl/util/path.py index b5ffff8a..7105975f 100644 --- a/src/main/python/fman/impl/util/path.py +++ b/src/main/python/fman/impl/util/path.py @@ -34,5 +34,8 @@ def normalize(path_): if path_ == '.': path_ = '' # Resolve a/../b - path_ = re.subn(r'(^|/)([^/]+)/\.\.(?:$|/)', r'\1', path_)[0] + while True: + path_, count = re.subn(r'(^|/)([^/]+)/\.\.(?:$|/)', r'\1', path_) + if not count: + break return path_.rstrip('/') \ No newline at end of file From 74f1ba7cee0e48c29eaab9b999811bc2df97124e Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sun, 3 May 2026 06:30:22 +0200 Subject: [PATCH 6/8] bugfix --- src/main/resources/base/Plugins/Core/core/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/base/Plugins/Core/core/commands/__init__.py b/src/main/resources/base/Plugins/Core/core/commands/__init__.py index 644397e8..b6c88f53 100644 --- a/src/main/resources/base/Plugins/Core/core/commands/__init__.py +++ b/src/main/resources/base/Plugins/Core/core/commands/__init__.py @@ -1730,7 +1730,7 @@ def on_command(self, command_name, args): except (KeyError, ValueError): return None if scheme == 'file://': - new_scheme = _get_handler_for_archive(basename(path)) + new_scheme = _get_handler_for_archive(basename(url)) if new_scheme: try: if is_dir(url): From b3543b2e6b2e532d32d05d790ffd7867817862a6 Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Sat, 9 May 2026 04:36:24 +0200 Subject: [PATCH 7/8] =?UTF-8?q?-=20venv=E2=86=92.venv=20=E2=80=94=20.gitig?= =?UTF-8?q?nore,=20desk.sh,=20desk.cmd,=20all=203=20Dockerfiles=20-=20desk?= =?UTF-8?q?.cmd=20=E2=80=94=20auto-detect=20Windows=20SDK,=20relative=20pa?= =?UTF-8?q?ths,=20add=20sign/publish=20aliases=20-=20build.py=20=E2=80=94?= =?UTF-8?q?=20remove=20unused=20import,=20Debian=20support=20in=20publish,?= =?UTF-8?q?=20bare=20except=E2=86=92except=20Exception,=20assert=E2=86=92V?= =?UTF-8?q?alueError=20-=20build=5Fimpl/init.py=20=E2=80=94=20hasattr(=5F?= =?UTF-8?q?=5Fpath=5F=5F)=20for=20package=20detection,=20dest=5Fpath=20bug?= =?UTF-8?q?fix,=20utf-8=20fallback=20encoding,=20assert=E2=86=92RuntimeErr?= =?UTF-8?q?or,=20chmod=20check=3DTrue,=20skip=20.git=20in=20cleanup,=20mas?= =?UTF-8?q?ter=E2=86=92main,=20HTTPS=20validation=20+=20timeout=20-=20aws.?= =?UTF-8?q?py=20=E2=80=94=20unique=20CallerReference=20-=20mac.py=20?= =?UTF-8?q?=E2=80=94=20altool=E2=86=92notarytool=20(modern=20Apple=20notar?= =?UTF-8?q?ization=20API),=20removed=20unused=20imports=20-=20ubuntu.py=20?= =?UTF-8?q?=E2=80=94=20dynamic=20GTK=20path=20via=20ctypes,=20removed=20st?= =?UTF-8?q?ale=20libpng12=20exception=20-=20Settings=20=E2=80=94=20fedora?= =?UTF-8?q?=20hidden=5Fimports,=20mac=20team=5Fid,=20windows=20timestamp?= =?UTF-8?q?=20server=20update=20-=20fedora.repo=20=E2=80=94=20gpgcheck=3D1?= =?UTF-8?q?=20+=20gpgkey=20-=20post-commit=20=E2=80=94=20FMAN=5FAPI=5FSECR?= =?UTF-8?q?ET=20env=20var=20(removes=20hardcoded=20secret),=20--fail=20--s?= =?UTF-8?q?ilent=20on=20curl=20-=20install.txt=20=E2=80=94=20updated=20URL?= =?UTF-8?q?s,=20NSIS=20version,=20placeholder=20for=20cert=20path=20-=20re?= =?UTF-8?q?quirements=20=E2=80=94=20http=E2=86=92https,=20remove=20duplica?= =?UTF-8?q?te=20PyQt5=20in=20windows.txt,=20arch.txt=20comment=20-=20CHANG?= =?UTF-8?q?ELOG.md=20=E2=80=94=20QtQmlModels=20added=20to=20strip=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + CHANGELOG.md | 2 +- bin/desk.cmd | 13 +++-- bin/desk.sh | 2 +- bin/post-commit | 6 ++- build.py | 14 +++--- install.txt | 8 ++-- requirements/arch.txt | 2 + requirements/base.txt | 2 +- requirements/windows.txt | 2 - src/build/docker/arch/Dockerfile | 6 +-- src/build/docker/fedora/Dockerfile | 6 +-- src/build/docker/ubuntu/Dockerfile | 8 ++-- src/build/python/build_impl/__init__.py | 22 +++++---- src/build/python/build_impl/aws.py | 2 +- src/build/python/build_impl/mac.py | 63 ++++++++----------------- src/build/python/build_impl/ubuntu.py | 13 +++-- src/build/settings/fedora.json | 1 + src/build/settings/mac.json | 3 +- src/build/settings/windows.json | 2 +- src/repo/fedora/fman.repo | 3 +- 21 files changed, 88 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index 52d50e73..972a1069 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ __pycache__/ *.py[cod] cache/ venv/ +.venv/ target/ .DS_Store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a76d15b9..1c3f5bff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,5 +36,5 @@ `try/except TypeError` around `Path.resolve(strict=True)` and updated version-specific comments. - **Reduced macOS app bundle size** from ~110MB to ~77MB by stripping unused - Qt frameworks (QtQml, QtQuick, QtWebSockets), unused Qt plugins, and + Qt frameworks (QtQml, QtQmlModels, QtQuick, QtWebSockets), unused Qt plugins, and build-only dependencies (boto3/botocore) from the frozen bundle. diff --git a/bin/desk.cmd b/bin/desk.cmd index 162a8d1f..cb81117f 100644 --- a/bin/desk.cmd +++ b/bin/desk.cmd @@ -1,13 +1,20 @@ -set PATH=C:\Windows\SysWOW64\downlevel;C:\Program Files (x86)\NSIS;C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x86;%PATH% +FOR /F "delims=" %%i IN ('dir /b /ad /o-n "C:\Program Files (x86)\Windows Kits\10\bin\10.*"') DO ( + SET "WIN_SDK_VER=%%i" + GOTO :sdk_found +) +:sdk_found +set PATH=C:\Windows\SysWOW64\downlevel;C:\Program Files (x86)\NSIS;C:\Program Files (x86)\Windows Kits\10\bin\%WIN_SDK_VER%\x86;%PATH% -SET ROOT=c:\Users\micha\dev\fman +SET ROOT=%~dp0.. CD %ROOT% -CALL venv\scripts\activate.bat +CALL .venv\scripts\activate.bat DOSKEY run=python build.py run $* DOSKEY clean=python build.py clean DOSKEY freeze=python build.py freeze $* DOSKEY installer=python build.py installer $* +DOSKEY sign=python build.py sign $* DOSKEY sign_installer=python build.py sign_installer $* +DOSKEY publish=python build.py publish $* DOSKEY tests=python build.py test $* DOSKEY release=python build.py release $* \ No newline at end of file diff --git a/bin/desk.sh b/bin/desk.sh index ba9f803e..ada8f884 100755 --- a/bin/desk.sh +++ b/bin/desk.sh @@ -26,7 +26,7 @@ THIS_SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" PROJECT_DIR="$THIS_SCRIPT_DIR/.." cd "$PROJECT_DIR" -source venv/bin/activate +source .venv/bin/activate PS1="(fman) \h:\W \u\$ " diff --git a/bin/post-commit b/bin/post-commit index 8e8e7da7..88f309f4 100755 --- a/bin/post-commit +++ b/bin/post-commit @@ -5,11 +5,15 @@ set -e URL=https://fman.io/api/record-commit/ +if [ -z "${FMAN_API_SECRET:-}" ]; then + exit 0 +fi + message_tmp_file=`mktemp` git log --pretty=format:%B -n1 > ${message_tmp_file} sha=`git log --pretty=format:%H -n1` date=`git log --pretty=format:%cd --date=iso-strict -n1` -curl --data-urlencode secret=k92XhhhmOf8rD7QJ --data-urlencode sha=${sha} \ +curl --fail --silent --data-urlencode secret=${FMAN_API_SECRET} --data-urlencode sha=${sha} \ --data-urlencode date=${date} --data-urlencode message@${message_tmp_file} \ ${URL} rm ${message_tmp_file} \ No newline at end of file diff --git a/build.py b/build.py index f9988e72..5e708cf8 100644 --- a/build.py +++ b/build.py @@ -10,7 +10,6 @@ from fbs.cmdline import command from fbs_runtime.platform import is_windows, is_mac, is_linux, is_ubuntu, \ is_fedora, is_arch_linux, linux_distribution -from os.path import dirname import fbs.cmdline import re @@ -49,7 +48,7 @@ def publish(): sign_installer() upload() elif is_linux(): - if is_ubuntu(): + if is_ubuntu() or linux_distribution() == 'Debian GNU/Linux': freeze() installer() upload() @@ -112,18 +111,18 @@ def release(): ' python build.py post_release\n' % release_tag ) - except: + except Exception: git('push', '--delete', 'origin', release_tag) raise - except: + except Exception: git('revert', '--no-edit', revision_before + '..HEAD' ) git('push', '-u', 'origin', 'main') revision_before = git('rev-parse', 'HEAD').rstrip() raise - except: + except Exception: git('tag', '-d', release_tag) raise - except: + except Exception: git('reset', revision_before) _replace_in_json(settings_path, 'version', version) raise @@ -136,7 +135,8 @@ def release(): def post_release(): activate_profile('release') version = SETTINGS['version'] - assert not version.endswith(snapshot_suffix) + if version.endswith(snapshot_suffix): + raise ValueError('Cannot post_release a SNAPSHOT version: %s' % version) cloudfront_items_to_invalidate = [] for item in ('fman.dmg', 'fman.deb', 'fman.pkg.tar.xz', 'fman.rpm'): cloudfront_items_to_invalidate.append(item) diff --git a/install.txt b/install.txt index e834c762..a50c5de5 100644 --- a/install.txt +++ b/install.txt @@ -7,14 +7,14 @@ Import developer certificate: security import "~/dev/fman/conf/mac/Private key.p12" -k ~/Library/Keychains/login.keychain -P "" -T /usr/bin/codesign Install a version of XCode that is compatible with your macOS version from -https://developer.apple.com/download/more/?q=xcode. +https://developer.apple.com/download/all/?q=xcode. Code signing usually does not work when invoked via SSH. It prompts for a pw with the GUI. NSIS: ===== -Install version 2.51 on Windows. +Install NSIS 3.x on Windows. Add to PATH. Linux (Ubuntu) extra @@ -30,10 +30,10 @@ fmanSetup.exe (Google Omaha installer) ====================================== 1) hammer -c -set TIMESTAMP_SERVER=http://sha256timestamp.ws.symantec.com/sha256/timestamp +set TIMESTAMP_SERVER=http://timestamp.digicert.com set SHA1_TIMESTAMP_SERVER=%TIMESTAMP_SERVER% set SHA2_TIMESTAMP_SERVER=%TIMESTAMP_SERVER% -set AUTHENTICODE_FILE="c:\Users\Michael\dev\fman\conf\windows\michaelherrmann.pfx" +set AUTHENTICODE_FILE="" set AUTHENTICODE_PASSWORD="..." hammer MODE=opt-win --authenticode_file=%AUTHENTICODE_FILE% --authenticode_password=%AUTHENTICODE_PASSWORD% --sha1_authenticode_file=%AUTHENTICODE_FILE% --sha2_authenticode_file=%AUTHENTICODE_FILE% --sha1_authenticode_password=%AUTHENTICODE_PASSWORD% --sha2_authenticode_password=%AUTHENTICODE_PASSWORD% diff --git a/requirements/arch.txt b/requirements/arch.txt index bcd90b10..880912a1 100644 --- a/requirements/arch.txt +++ b/requirements/arch.txt @@ -1 +1,3 @@ +# pgi intentionally excluded — Gnome icon integration unavailable on Arch. +# Qt's default QFileIconProvider is used instead. -r linux.txt \ No newline at end of file diff --git a/requirements/base.txt b/requirements/base.txt index a2bddc2e..513111af 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -fbs[sentry] @ http://build-system.fman.io/pro/b5aab865-bd29-4f23-992c-0eb4f3a24f33/0.9.4 +fbs[sentry] @ https://build-system.fman.io/pro/b5aab865-bd29-4f23-992c-0eb4f3a24f33/0.9.4 PyQt5==5.15.11 PyInstaller==6.19.0 rsa==4.9 diff --git a/requirements/windows.txt b/requirements/windows.txt index e6dfd14d..819d7473 100644 --- a/requirements/windows.txt +++ b/requirements/windows.txt @@ -1,6 +1,4 @@ -r base.txt -PyQt5==5.15.11 -# Note: Send2Trash 1.5.0 has no effect on Windows! Send2Trash==1.8.3 adodbapi==2.6.0.7 pywinpty==2.0.14 diff --git a/src/build/docker/arch/Dockerfile b/src/build/docker/arch/Dockerfile index 605be314..2e555296 100644 --- a/src/build/docker/arch/Dockerfile +++ b/src/build/docker/arch/Dockerfile @@ -14,9 +14,9 @@ WORKDIR /root/${app_name} # Set up virtual environment: ADD *.txt /tmp/requirements/ -RUN python -m venv venv && \ - venv/bin/python -m pip install -U pip && \ - venv/bin/python -m pip install -r "/tmp/requirements/${requirements}" +RUN python -m venv .venv && \ + .venv/bin/python -m pip install -U pip && \ + .venv/bin/python -m pip install -r "/tmp/requirements/${requirements}" RUN rm -rf /tmp/requirements/ ADD .bashrc /root/.bashrc diff --git a/src/build/docker/fedora/Dockerfile b/src/build/docker/fedora/Dockerfile index 994e4b38..97331652 100644 --- a/src/build/docker/fedora/Dockerfile +++ b/src/build/docker/fedora/Dockerfile @@ -20,9 +20,9 @@ RUN dnf install -y gobject-introspection # Set up virtual environment: ADD *.txt /tmp/requirements/ -RUN python3.9 -m venv venv && \ - venv/bin/python -m pip install -U pip && \ - venv/bin/python -m pip install -r "/tmp/requirements/${requirements}" +RUN python3.9 -m venv .venv && \ + .venv/bin/python -m pip install -U pip && \ + .venv/bin/python -m pip install -r "/tmp/requirements/${requirements}" RUN rm -rf /tmp/requirements/ ADD .bashrc /root diff --git a/src/build/docker/ubuntu/Dockerfile b/src/build/docker/ubuntu/Dockerfile index 88ff01b9..351c3d6b 100644 --- a/src/build/docker/ubuntu/Dockerfile +++ b/src/build/docker/ubuntu/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get install libgl1-mesa-glx -y # fpm: RUN apt-get install ruby ruby-dev build-essential -y && \ - gem install --no-ri --no-rdoc fpm + gem install --no-document fpm WORKDIR /root/${app_name} @@ -25,9 +25,9 @@ RUN apt-get install gobject-introspection libgtk-3-0 -y # Set up virtual environment: ADD *.txt /tmp/requirements/ -RUN python3.9 -m venv venv && \ - venv/bin/python -m pip install -U pip && \ - venv/bin/python -m pip install -r "/tmp/requirements/${requirements}" +RUN python3.9 -m venv .venv && \ + .venv/bin/python -m pip install -U pip && \ + .venv/bin/python -m pip install -r "/tmp/requirements/${requirements}" RUN rm -rf /tmp/requirements/ ADD .bashrc /root/.bashrc diff --git a/src/build/python/build_impl/__init__.py b/src/build/python/build_impl/__init__.py index 086f20aa..798dffba 100644 --- a/src/build/python/build_impl/__init__.py +++ b/src/build/python/build_impl/__init__.py @@ -25,7 +25,7 @@ def remove_if_exists(file_path): def copy_python_library(name, dest_dir): library = import_module(name) - is_package = re.match(r'^__init__\.pyc?$', basename(library.__file__)) + is_package = hasattr(library, '__path__') if is_package: package_dir = dirname(library.__file__) copytree(package_dir, join(dest_dir, basename(package_dir))) @@ -54,7 +54,7 @@ def upload_file(f, dest_dir, dest_name=None): run(args, check=True) else: if isdir(f): - copytree(f, join(dest_dir, dest_name or basename(f))) + copytree(f, join(dest_path, dest_name or basename(f))) else: copy(f, dest_path) @@ -79,10 +79,11 @@ def run_on_server(command): return check_output_decode(command, shell=True) def check_output_decode(*args, **kwargs): - return check_output(*args, **kwargs).decode(sys.stdout.encoding) + return check_output(*args, **kwargs).decode(sys.stdout.encoding or 'utf-8') def upload_installer_to_aws(installer_name): - assert SETTINGS['release'] + if not SETTINGS['release']: + raise RuntimeError('upload_installer_to_aws called in non-release mode') src_path = path('target/' + installer_name) upload_to_s3(src_path, installer_name) version_dest_path = '%s/%s' % (SETTINGS['version'], installer_name) @@ -105,7 +106,7 @@ def upload_core_to_github(): ssh_key = path('${core_plugin_ssh_key}') if not is_windows(): # Prevent clone failing due to lacking access restrictions: - run(['chmod', '600', ssh_key]) + run(['chmod', '600', ssh_key], check=True) with TemporaryDirectory() as tmp_dir: cwd_before = getcwd() chdir(tmp_dir) @@ -118,7 +119,7 @@ def upload_core_to_github(): if not line.startswith('#') and line.rstrip() } for name in listdir(tmp_dir): - if name not in extra_files: + if name not in extra_files and name != '.git': if isdir(name): rmtree(name) else: @@ -137,7 +138,7 @@ def upload_core_to_github(): 'commit', '-m', 'Source code of the Core plugin in fman ' + version ) - git('push', '-u', 'origin', 'master', ssh_key=ssh_key) + git('push', '-u', 'origin', 'main', ssh_key=ssh_key) tag = 'v' + version git('tag', tag) git('push', 'origin', tag, ssh_key=ssh_key) @@ -146,8 +147,11 @@ def upload_core_to_github(): def record_release_on_server(): import requests - response = requests.post(SETTINGS['record_release_url'], { + url = SETTINGS['record_release_url'] + if not url.startswith('https://'): + raise ValueError('record_release_url must use HTTPS') + response = requests.post(url, { 'secret': SETTINGS['server_api_secret'], 'version': SETTINGS['version'] - }) + }, timeout=30) response.raise_for_status() \ No newline at end of file diff --git a/src/build/python/build_impl/aws.py b/src/build/python/build_impl/aws.py index d0b5ddf5..12fb423e 100644 --- a/src/build/python/build_impl/aws.py +++ b/src/build/python/build_impl/aws.py @@ -44,7 +44,7 @@ def create_cloudfront_invalidation(items): 'Quantity': len(items), 'Items': ['/' + item for item in items] }, - 'CallerReference': str(int(time())) + 'CallerReference': '%s-%s' % (int(time()), id(items)) } ) diff --git a/src/build/python/build_impl/mac.py b/src/build/python/build_impl/mac.py index d8925b23..81fc9f9d 100644 --- a/src/build/python/build_impl/mac.py +++ b/src/build/python/build_impl/mac.py @@ -7,13 +7,9 @@ from os import remove from os.path import basename, join from shutil import rmtree, move -from subprocess import run, PIPE, CalledProcessError, SubprocessError -from time import sleep +from subprocess import run, PIPE -import json import os -import plistlib -import requests _UPDATES_DIR = 'updates/mac' @@ -114,44 +110,25 @@ def _run_codesign(*args): def _staple(file_path): run(['xcrun', 'stapler', 'staple', file_path], check=True) -def _notarize(file_path, query_interval_secs=10): - response = _run_altool([ - '--notarize-app', '-t', 'osx', '-f', file_path, - '--primary-bundle-id', SETTINGS['mac_bundle_identifier'] - ]) - request_uuid = response['notarization-upload']['RequestUUID'] - while True: - sleep(query_interval_secs) - try: - response = _run_altool(['--notarization-info', request_uuid]) - except CalledProcessError as e: - stdout = e.stdout.decode('utf-8') - if 'Could not find the RequestUUID' not in stdout: - raise - else: - status = response['notarization-info']['Status'] - if status != 'in progress': - break - print('Waiting for notarization to complete...') - log_url = response['notarization-info']['LogFileURL'] - log_response = requests.get(log_url) - log_response.raise_for_status() - log_json = log_response.json() - issues = log_json.get('issues', []) - if issues: - print('Notarization encountered some issues:') - print(json.dumps(issues, indent=4, sort_keys=True)) - if status != 'success': - raise RuntimeError('Unexpected notarization status: %r' % status) - -def _run_altool(args): - all_args = [ - 'xcrun', 'altool', '--output-format', 'xml', - '-u', SETTINGS['apple_developer_user'], - '-p', SETTINGS['apple_developer_app_pw'] - ] + args - process = run(all_args, stdout=PIPE, stderr=PIPE, check=True) - return plistlib.loads(process.stdout) +def _notarize(file_path): + result = run( + [ + 'xcrun', 'notarytool', 'submit', file_path, '--wait', + '--apple-id', SETTINGS['apple_developer_user'], + '--password', SETTINGS['apple_developer_app_pw'], + '--team-id', SETTINGS['apple_developer_team_id'] + ], + stdout=PIPE, stderr=PIPE, check=False + ) + output = result.stdout.decode('utf-8') + print(output) + if result.returncode != 0: + stderr = result.stderr.decode('utf-8') + raise RuntimeError( + 'Notarization failed (exit %d):\n%s' % (result.returncode, stderr) + ) + if 'status: Accepted' not in output: + raise RuntimeError('Unexpected notarization status:\n%s' % output) @command def sign_installer(): diff --git a/src/build/python/build_impl/ubuntu.py b/src/build/python/build_impl/ubuntu.py index 91290625..8aadd767 100644 --- a/src/build/python/build_impl/ubuntu.py +++ b/src/build/python/build_impl/ubuntu.py @@ -26,8 +26,12 @@ def freeze(): _remove_gtk_dependencies() def _remove_gtk_dependencies(): + import ctypes.util + gtk_path = ctypes.util.find_library('gtk-3') + if gtk_path is None: + return output = check_output_decode( - 'ldd /usr/lib/x86_64-linux-gnu/libgtk-3.so.0', shell=True + 'ldd ' + gtk_path, shell=True ) assert output.endswith('\n') for line in output.split('\n')[:-1]: @@ -38,12 +42,7 @@ def _remove_gtk_dependencies(): raise ValueError(repr(line)) so_name, so_path = match.groups() if so_name and so_path: - # libQt5Widgets.so.0 depends on libpng12.so.0. This file is present - # on Ubuntu versions < 17.04. On 17.04 (and above?), libpng16.so.0 - # is used instead. We therefore need to keep libpng12.so.0 so fman - # can run on Ubuntu 17.04+: - if so_name != 'libpng12.so.0': - remove_if_exists(path('${freeze_dir}/' + so_name)) + remove_if_exists(path('${freeze_dir}/' + so_name)) @command def upload(): diff --git a/src/build/settings/fedora.json b/src/build/settings/fedora.json index b3ec1597..f5e5090e 100644 --- a/src/build/settings/fedora.json +++ b/src/build/settings/fedora.json @@ -1,3 +1,4 @@ { + "hidden_imports": ["pgi.overrides.GObject", "pgi.overrides.GLib"], "gpg_preset_passphrase": "/usr/libexec/gpg-preset-passphrase" } \ No newline at end of file diff --git a/src/build/settings/mac.json b/src/build/settings/mac.json index 3263fd41..dc3842ff 100644 --- a/src/build/settings/mac.json +++ b/src/build/settings/mac.json @@ -9,5 +9,6 @@ "src/freeze/mac/Contents/SharedSupport/bin/fman" ], "apple_developer_user": "michael@herrmann.io", - "apple_developer_app_pw": "apsy-xzlg-dszl-kttf" + "apple_developer_app_pw": "apsy-xzlg-dszl-kttf", + "apple_developer_team_id": "" } \ No newline at end of file diff --git a/src/build/settings/windows.json b/src/build/settings/windows.json index 267f4713..bb66039d 100644 --- a/src/build/settings/windows.json +++ b/src/build/settings/windows.json @@ -4,5 +4,5 @@ "win32com.shell.shellcon", "win32gui", "winpty", "win32wnet" ], "windows_sign_pass": "Tu4suttmdpn", - "windows_sign_server": "http://sha256timestamp.ws.symantec.com/sha256/timestamp" + "windows_sign_server": "http://timestamp.digicert.com" } \ No newline at end of file diff --git a/src/repo/fedora/fman.repo b/src/repo/fedora/fman.repo index 06cbb5fc..1354f2b9 100644 --- a/src/repo/fedora/fman.repo +++ b/src/repo/fedora/fman.repo @@ -2,4 +2,5 @@ name=fman baseurl=https://download.fman.io/rpm enabled=1 -gpgcheck=0 \ No newline at end of file +gpgcheck=1 +gpgkey=https://download.fman.io/rpm/public.gpg \ No newline at end of file From 433acfd5adbd9aedaa21d183ae74c97dd4bcd020 Mon Sep 17 00:00:00 2001 From: Srecko Skocilic Date: Tue, 12 May 2026 12:18:37 +0200 Subject: [PATCH 8/8] Fix macOS build: add TCC usage descriptions and handle missing Sparkle --- src/build/settings/mac.json | 2 +- src/main/python/fman/impl/application_context.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/build/settings/mac.json b/src/build/settings/mac.json index dc3842ff..28ad76d2 100644 --- a/src/build/settings/mac.json +++ b/src/build/settings/mac.json @@ -4,7 +4,7 @@ "hidden_imports": ["pty"], "mac_bundle_identifier": "io.fman.fman", "appcast_url": "${server_url}/updates/Appcast.xml", - "info_plist_extra": "NSAppTransportSecurity\n\t\n\t\tNSAllowsArbitraryLoads\n\t\t\n\t", + "info_plist_extra": "NSAppTransportSecurity\n\t\n\t\tNSAllowsArbitraryLoads\n\t\t\n\t\n\tNSDesktopFolderUsageDescription\n\tfman needs access to your Desktop folder to display and manage files.\n\tNSDownloadsFolderUsageDescription\n\tfman needs access to your Downloads folder to display and manage files.\n\tNSDocumentsFolderUsageDescription\n\tfman needs access to your Documents folder to display and manage files.\n\tNSRemovableVolumesUsageDescription\n\tfman needs access to removable volumes to display and manage files.\n\tNSNetworkVolumesUsageDescription\n\tfman needs access to network volumes to display and manage files.", "files_to_filter": [ "src/freeze/mac/Contents/SharedSupport/bin/fman" ], diff --git a/src/main/python/fman/impl/application_context.py b/src/main/python/fman/impl/application_context.py index 8744eaad..21bda04e 100644 --- a/src/main/python/fman/impl/application_context.py +++ b/src/main/python/fman/impl/application_context.py @@ -92,7 +92,10 @@ def on_main_window_shown(self): if is_mac(): self._preload_core_services() if self.updater: - self.updater.start() + try: + self.updater.start() + except ImportError: + pass if self.is_licensed: if not self.session_manager.was_licensed_on_last_run: self.metrics.track('InstalledLicenseKey')