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 84421196..488a7a68 100644 --- a/packaging/build_exe.py +++ b/packaging/build_exe.py @@ -101,20 +101,25 @@ def build_exe(): # Copy resources into installer directory 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: + 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: 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: @@ -122,9 +127,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 83c7d4ac..f89dc0f4 100644 --- a/rascal2/config.py +++ b/rascal2/config.py @@ -14,6 +14,9 @@ # we are running in a bundle SOURCE_PATH = pathlib.Path(sys.executable).parent.parent 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 @@ -273,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"]), ], )