From b998a47e3e226c2a1c74cb16a2cc599a3006df64 Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Wed, 8 Oct 2025 12:31:19 +0100 Subject: [PATCH 1/3] Fix resource folder --- packaging/build_exe.py | 2 +- rascal2/config.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packaging/build_exe.py b/packaging/build_exe.py index 84421196..2ef4acce 100644 --- a/packaging/build_exe.py +++ b/packaging/build_exe.py @@ -114,7 +114,7 @@ def build_exe(): shutil.copytree(src_path, dest_path, ignore=shutil.ignore_patterns("__pycache__")) if IS_MAC: - shutil.rmtree(PACKAGING_PATH / "bundle" / "app" / "rascal") + shutil.rmtree(PACKAGING_PATH / "bundle" / "rascal") if IS_WINDOWS: with open(PACKAGING_PATH / "windows" / "version.nsh", "w") as ver_file: diff --git a/rascal2/config.py b/rascal2/config.py index 83c7d4ac..7403dce2 100644 --- a/rascal2/config.py +++ b/rascal2/config.py @@ -13,6 +13,8 @@ if getattr(sys, "frozen", False): # we are running in a bundle SOURCE_PATH = pathlib.Path(sys.executable).parent.parent + if pathlib.Path(SOURCE_PATH / 'MacOS').is_dir(): + SOURCE_PATH = SOURCE_PATH / 'Resources' SITE_PATH = SOURCE_PATH / "bin/_internal" EXAMPLES_PATH = SOURCE_PATH / "examples" else: From 3c1b66d9cbf448fccb0a98e1a79131bd70abc12c Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Tue, 4 Nov 2025 11:18:04 +0000 Subject: [PATCH 2/3] Add menu role for macos, updates to macos installer --- .github/workflows/build_installer.yaml | 48 ++++++++++++++++++++++++-- .github/workflows/unit_tests.yaml | 1 - packaging/build_exe.py | 14 ++++++-- packaging/macos/distribution.xml.in | 13 +++++++ packaging/macos/make.sh | 15 ++++++++ rascal2/config.py | 7 ++-- rascal2/dialogs/settings_dialog.py | 13 +++++-- rascal2/ui/view.py | 18 ++++------ tests/ui/test_view.py | 2 +- 9 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 packaging/macos/distribution.xml.in create mode 100644 packaging/macos/make.sh diff --git a/.github/workflows/build_installer.yaml b/.github/workflows/build_installer.yaml index 1f567cef..541937b1 100644 --- a/.github/workflows/build_installer.yaml +++ b/.github/workflows/build_installer.yaml @@ -13,7 +13,7 @@ jobs: windows-installer: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: conda-incubator/setup-miniconda@v3 with: activate-environment: rascal2 @@ -29,6 +29,7 @@ jobs: conda init powershell conda activate rascal2 conda install -c nsis nsis=3.* accesscontrol + pip install matlabengine==9.14.* python packaging/build_exe.py makensis packaging/windows/build_installer.nsi - name: Upload installer @@ -40,7 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up MATLAB uses: matlab-actions/setup-matlab@v2 with: @@ -64,10 +65,51 @@ jobs: with: name: linux installer path: packaging/linux/*.run + macos-installer: + strategy: + fail-fast: false + matrix: + platform: [ macos-15-intel, macos-14 ] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v5 + - uses: conda-incubator/setup-miniconda@v3 + with: + activate-environment: rascal2 + environment-file: environment.yaml + auto-activate-base: false + - name: Set up MATLAB + uses: matlab-actions/setup-matlab@v2 + with: + release: R2023b + - name: Make installer + shell: bash -el {0} + run: | + conda init bash + conda activate rascal2 + if [ ${{ matrix.platform }} == "macos-14" ]; then + ARCH="arm64" + export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/Users/runner/hostedtoolcache/MATLAB/2023.2.999/arm64/MATLAB.app/bin/maca64 + python -m pip install --no-build-isolation /Users/runner/hostedtoolcache/MATLAB/2023.2.999/arm64/MATLAB.app/extern/engines/python + else + ARCH="x64" + export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:/Users/runner/hostedtoolcache/MATLAB/2023.2.999/x64/MATLAB.app/bin/maci64 + python -m pip install --no-build-isolation /Users/runner/hostedtoolcache/MATLAB/2023.2.999/x64/MATLAB.app/extern/engines/python + fi + python packaging/build_exe.py + chmod 777 packaging/bundle/rascal.app/Contents/Resources/matlab/engine/_arch.txt + cd packaging/macos/ + chmod 777 make.sh + ./make.sh $GITHUB_REF_NAME $ARCH + - name: Upload installer + uses: actions/upload-artifact@v4 + with: + name: macOS installer-${{ strategy.job-index }} + path: packaging/macos/rascal2-*.pkg deploy-nightly: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - needs: [windows-installer, linux-installer] + needs: [windows-installer, linux-installer, macos-installer] permissions: contents: write steps: diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index 2189c6d3..0e7886d9 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -42,4 +42,3 @@ jobs: - name: Install and run tests (MacOS/Windows) if: runner.os != 'linux' uses: ./.github/actions/windows-mac - diff --git a/packaging/build_exe.py b/packaging/build_exe.py index 2ef4acce..6192a3b6 100644 --- a/packaging/build_exe.py +++ b/packaging/build_exe.py @@ -100,9 +100,13 @@ def build_exe(): shutil.rmtree(work_path) # Copy resources into installer directory - resources = ["static/images", "static/style.css"] + resources = ["static/images", "static/style.css", "../examples"] shutil.copy(PROJECT_PATH / "LICENSE", dist_path / "LICENSE") for resource in resources: + if resource == "../examples" and not IS_MAC: + # on macOS, examples have to go into resources + continue + if IS_MAC: dest_path = dist_path / "rascal.app" / "Contents" / "Resources" / resource else: @@ -122,9 +126,13 @@ def build_exe(): ver_file.write(f'!define VERSION "{RASCAL2_VERSION}"') - arch_path = dist_path / "bin" / "_internal" / "matlab" / "engine" / "_arch.txt" + if IS_MAC: + arch_path = dist_path / "rascal.app" / "Contents" / "Resources" / "matlab" / "engine" / "_arch.txt" + else: + arch_path = dist_path / "bin" / "_internal" / "matlab" / "engine" / "_arch.txt" + if arch_path.exists(): - open(dist_path / "bin" / "_internal" / "matlab" / "engine" / "_arch.txt", "w").close() + open(arch_path, "w").close() else: warnings.warn( f"MATLAB engine arch file ({arch_path}) was not found. Ignore if you don't plan to use MATLAB", diff --git a/packaging/macos/distribution.xml.in b/packaging/macos/distribution.xml.in new file mode 100644 index 00000000..533f3761 --- /dev/null +++ b/packaging/macos/distribution.xml.in @@ -0,0 +1,13 @@ + + + Rascal-2 @VERSION_NAME@ + + + + + + + + rascal.pkg + + diff --git a/packaging/macos/make.sh b/packaging/macos/make.sh new file mode 100644 index 00000000..809dd985 --- /dev/null +++ b/packaging/macos/make.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +RASCAL_PATH="../bundle/rascal.app" +VER_NAME=$1 +ARCH_NAME=$2 +VER=$VER_NAME + +if [[ ${VER_NAME:0:1} == 'v' ]]; then + VER=${VER:1} +fi + +# Build Pkg +sed -e "s/@VERSION_NAME@/${VER_NAME}/g" -e "s/@VERSION@/${VER}/g" distribution.xml.in > distribution.xml +pkgbuild --root ${RASCAL_PATH} --identifier com.rascal2.rascal.pkg --version ${VER} --install-location "/Applications/rascal.app" rascal.pkg +productbuild --distribution distribution.xml --resources . "rascal2-${VER}-${ARCH_NAME}.pkg" diff --git a/rascal2/config.py b/rascal2/config.py index 7403dce2..f89dc0f4 100644 --- a/rascal2/config.py +++ b/rascal2/config.py @@ -13,9 +13,10 @@ if getattr(sys, "frozen", False): # we are running in a bundle SOURCE_PATH = pathlib.Path(sys.executable).parent.parent - if pathlib.Path(SOURCE_PATH / 'MacOS').is_dir(): - SOURCE_PATH = SOURCE_PATH / 'Resources' SITE_PATH = SOURCE_PATH / "bin/_internal" + if pathlib.Path(SOURCE_PATH / "MacOS").is_dir(): + SOURCE_PATH = SOURCE_PATH / "Resources" + SITE_PATH = SOURCE_PATH EXAMPLES_PATH = SOURCE_PATH / "examples" else: SOURCE_PATH = pathlib.Path(__file__).parent @@ -275,7 +276,7 @@ def get_matlab_path(self): if len(lines) == 4: install_dir = pathlib.Path(lines[1]).parent.parent else: - error = "Matlab not found, use 'Tools > Setup Matlab' to specify MATLAB location " + error = "Matlab not found, specify MATLAB location in settings i.e. 'File > Settings' menu" except FileNotFoundError: error = "Matlab engine could not be found, ensure it is installed properly" if error: diff --git a/rascal2/dialogs/settings_dialog.py b/rascal2/dialogs/settings_dialog.py index 573eb666..0626be26 100644 --- a/rascal2/dialogs/settings_dialog.py +++ b/rascal2/dialogs/settings_dialog.py @@ -165,7 +165,10 @@ def __init__(self): def open_folder_selector(self) -> None: """Open folder selector.""" - folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, "Select MATLAB Directory", ".") + if platform.system() == "Darwin": + folder_name = QtWidgets.QFileDialog.getOpenFileName(self, "Select MATLAB Application", filter="(*.app)")[0] + else: + folder_name = QtWidgets.QFileDialog.getExistingDirectory(self, "Select MATLAB Directory", ".") if folder_name: self.matlab_path.setText(folder_name) self.changed = True @@ -186,7 +189,13 @@ def set_matlab_paths(self): path_file.truncate(0) - arch = "win64" if platform.system() == "Windows" else "glnxa64" + if platform.system() == "Windows": + arch = "win64" + elif platform.system() == "Darwin": + arch = "maca64" if platform.mac_ver()[-1] == "arm64" else "maci64" + else: + arch = "glnxa64" + path_file.writelines( [ f"{arch}\n", diff --git a/rascal2/ui/view.py b/rascal2/ui/view.py index a129c84d..a14be0ac 100644 --- a/rascal2/ui/view.py +++ b/rascal2/ui/view.py @@ -153,9 +153,8 @@ def create_actions(self): self.settings_action = QtGui.QAction("Settings", self) self.settings_action.setStatusTip("Settings") self.settings_action.setIcon(QtGui.QIcon(path_for("settings.png"))) + self.settings_action.setMenuRole(QtGui.QAction.MenuRole.PreferencesRole) self.settings_action.triggered.connect(lambda: self.show_settings_dialog()) - self.settings_action.setEnabled(False) - self.disabled_elements.append(self.settings_action) self.open_help_action = QtGui.QAction("&Help", self) self.open_help_action.setStatusTip("Open Documentation") @@ -170,13 +169,16 @@ def create_actions(self): self.toggle_slider_action.setEnabled(False) self.disabled_elements.append(self.toggle_slider_action) - self.open_about_action = QtGui.QAction("&About", self) - self.open_about_action.setStatusTip("Report RAT version&info") - self.open_about_action.triggered.connect(self.open_about_info) + open_about_action = QtGui.QAction("&About", self) + open_about_action.setStatusTip(f"About {MAIN_WINDOW_TITLE}") + open_about_action.triggered.connect(self.open_about_info) + open_about_action.setMenuRole(QtGui.QAction.MenuRole.AboutQtRole) + self.open_about_action = open_about_action self.exit_action = QtGui.QAction("E&xit", self) self.exit_action.setStatusTip(f"Quit {MAIN_WINDOW_TITLE}") self.exit_action.setShortcut(QtGui.QKeySequence.StandardKey.Quit) + self.exit_action.setMenuRole(QtGui.QAction.MenuRole.QuitRole) self.exit_action.triggered.connect(self.close) # Window menu actions @@ -204,10 +206,6 @@ def create_actions(self): self.clear_terminal_action.setStatusTip("Clear text in the terminal") self.clear_terminal_action.triggered.connect(self.terminal_widget.clear) - self.setup_matlab_action = QtGui.QAction("Setup MATLAB", self) - self.setup_matlab_action.setStatusTip("Set the path of the MATLAB executable") - self.setup_matlab_action.triggered.connect(lambda: self.show_settings_dialog(tab_name="Matlab")) - def create_menus(self): """Add sub menus to the main menu bar""" main_menu = self.menuBar() @@ -244,8 +242,6 @@ def create_menus(self): tools_menu.addAction(self.toggle_slider_action) tools_menu.addSeparator() tools_menu.addAction(self.clear_terminal_action) - tools_menu.addSeparator() - tools_menu.addAction(self.setup_matlab_action) help_menu = main_menu.addMenu("&Help") help_menu.addAction(self.open_about_action) diff --git a/tests/ui/test_view.py b/tests/ui/test_view.py index 70a5d128..69cc655e 100644 --- a/tests/ui/test_view.py +++ b/tests/ui/test_view.py @@ -179,7 +179,7 @@ def test_menu_element_present(test_view, submenu_name): ), ("&Edit", ["&Undo", "&Redo", "Undo &History"]), ("&Windows", ["Tile Windows", "Reset to Default", "Save Current Window Positions"]), - ("&Tools", ["Show &Sliders", "", "Clear Terminal", "", "Setup MATLAB"]), + ("&Tools", ["Show &Sliders", "", "Clear Terminal"]), ("&Help", ["&About", "&Help"]), ], ) From d5338d9362cb74ae1780af7adb66005a00f59cf0 Mon Sep 17 00:00:00 2001 From: Stephen Nneji Date: Thu, 27 Nov 2025 16:18:23 +0000 Subject: [PATCH 3/3] fix example not being copied --- packaging/build_exe.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packaging/build_exe.py b/packaging/build_exe.py index 6192a3b6..488a7a68 100644 --- a/packaging/build_exe.py +++ b/packaging/build_exe.py @@ -100,18 +100,19 @@ def build_exe(): shutil.rmtree(work_path) # Copy resources into installer directory - resources = ["static/images", "static/style.css", "../examples"] + resources = ["static/images", "static/style.css"] + if IS_MAC: + # on macOS, examples have to go into resources + resources.append("../examples") shutil.copy(PROJECT_PATH / "LICENSE", dist_path / "LICENSE") for resource in resources: - if resource == "../examples" and not IS_MAC: - # on macOS, examples have to go into resources - continue - + src_path = PROJECT_PATH / "rascal2" / resource + dest_path = dist_path / resource if IS_MAC: + if resource == "../examples": + resource = resource[3:] dest_path = dist_path / "rascal.app" / "Contents" / "Resources" / resource - else: - dest_path = dist_path / resource - src_path = PROJECT_PATH / "rascal2" / resource + if src_path.is_file(): shutil.copy(src_path, dest_path) else: