|
| 1 | +;;; matlab--access.el --- MATLAB access -*- lexical-binding: t -*- |
| 2 | + |
| 3 | +;; Copyright 2001-2025 Free Software Foundation, Inc. |
| 4 | +;; |
| 5 | +;; URL: https://github.com/mathworks/Emacs-MATLAB-Mode |
| 6 | +;; SPDX-License-Identifier: GPL-3.0-or-later |
| 7 | +;; |
| 8 | +;; Author: John Ciolfi <[email protected]> |
| 9 | + |
| 10 | +;; This file is free software: you can redistribute it and/or modify |
| 11 | +;; it under the terms of the GNU General Public License as published |
| 12 | +;; by the Free Software Foundation, either version 3 of the License, |
| 13 | +;; or (at your option) any later version. |
| 14 | +;; |
| 15 | +;; This file is distributed in the hope that it will be useful, |
| 16 | +;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | +;; GNU General Public License for more details. |
| 19 | +;; |
| 20 | +;; You should have received a copy of the GNU General Public License |
| 21 | +;; along with this file. If not, see <https://www.gnu.org/licenses/>. |
| 22 | + |
| 23 | +;;; Commentary: |
| 24 | +;; |
| 25 | +;; Access to the MATLAB installation: |
| 26 | +;; `matlab--platform' - "glnxa64", "maca64", "win64", etc. |
| 27 | +;; `matlab--get-abs-matlab-exe' - full path to the MATLAB executable |
| 28 | +;; `matlab--get-mlint-exe' - path to the MLint executabe, which |
| 29 | +;; should be an absolute path, but not |
| 30 | +;; guaranteed |
| 31 | +;; These should only be used by other matlab*.el files. |
| 32 | +;; |
| 33 | +;; In 2025, code which was written years back was moved from other files |
| 34 | +;; to this file. |
| 35 | + |
| 36 | +;;; Code: |
| 37 | + |
| 38 | +(require 'cl-macs) |
| 39 | + |
| 40 | +;;; matlab--platform |
| 41 | + |
| 42 | +(defvar matlab--platform |
| 43 | + ;; See |
| 44 | + ;; >> lower(computer) |
| 45 | + ;; MATLABROOT/bin/util/arch.sh (or arch.bat) |
| 46 | + (cond ((eq system-type 'darwin) |
| 47 | + (cond |
| 48 | + ((string-match "^arm" system-configuration) ;; e.g. arm-apple-darwin20.3.0 |
| 49 | + "maca64") |
| 50 | + ((string-match "^x86_64" system-configuration) |
| 51 | + "maci64") |
| 52 | + ((string-match "^i386" system-configuration) |
| 53 | + (let ((mt (getenv "MACHTYPE"))) |
| 54 | + (if (and (stringp mt) (string= "x86_32" mt)) |
| 55 | + ;; This hack is bad since an Emacs started from |
| 56 | + ;; the doc doesn't have this variable, thus by defaulting |
| 57 | + ;; to checking the 32 bit (not common anymore) version, |
| 58 | + ;; we'll get the right answer most of the time. |
| 59 | + "maci" "maci64"))) |
| 60 | + (t |
| 61 | + "mac"))) |
| 62 | + ((eq system-type 'gnu/linux) |
| 63 | + (cond ((string-match "64\\|i686" system-configuration) |
| 64 | + "glnxa64") |
| 65 | + (t "glnx86"))) |
| 66 | + ((eq system-type 'solaris) |
| 67 | + "sol2") |
| 68 | + ((eq system-type 'hpux) |
| 69 | + "hpux") |
| 70 | + ((eq system-type 'windows-nt) |
| 71 | + ;; Thought about checking the env PROCESSOR_ARCHITEW6432, |
| 72 | + ;; but this said AMD on my Intel, which seemed suspicious. |
| 73 | + (let ((proc (getenv "PROCESSOR_IDENTIFIER"))) |
| 74 | + (if (and (stringp proc) (string-match "64" proc)) |
| 75 | + "win64" |
| 76 | + "win32"))) |
| 77 | + (t "unknown")) |
| 78 | + "MATLAB platform. See >> lower(computer).") |
| 79 | + |
| 80 | +;;; MATLAB command (full path to matlab executable) |
| 81 | + |
| 82 | +(defgroup matlab-shell nil |
| 83 | + "MATLAB shell mode." |
| 84 | + :prefix "matlab-shell-" |
| 85 | + :group 'matlab) |
| 86 | + |
| 87 | +(defcustom matlab-shell-command "matlab" |
| 88 | + "The MATLAB command executable used to start MATLAB. |
| 89 | +This can be: |
| 90 | + - the name of the MATLAB command (e.g. \"matlab\") which is |
| 91 | + found on the system PATH. |
| 92 | + - an absolute path to the matlab executable. For example, |
| 93 | + \"/<path-to-MATLAB-install-dir>/bin/matlab\" |
| 94 | +If matlab-shell-command is set to \"matlab\" and \"matlab\" is not |
| 95 | +on the system PATH, `matlab-shell' will look for the matlab |
| 96 | +command executable in the default MATLAB installation locations." |
| 97 | + :type 'string |
| 98 | + :group 'matlab-shell) |
| 99 | + |
| 100 | +;;; matlab executable and matlabroot |
| 101 | + |
| 102 | +(defvar matlab--default-matlab-exe |
| 103 | + '((gnu/linux . "/usr/local/MATLAB/R*/bin/matlab") |
| 104 | + (darwin . "/Applications/MATLAB_R*.app/bin/matlab") |
| 105 | + (windows-nt . "C:/Program Files/MATLAB/R*/bin/matlab.exe")) |
| 106 | + "Standard MATLAB command installation locations, SYSTEM => GLOB.") |
| 107 | + |
| 108 | +(defun matlab--matlab-exe-not-found (no-help-window &optional default-loc) |
| 109 | + "Signal error, MATLAB command not on system PATH or in optional DEFAULT-LOC. |
| 110 | +If NO-HELP-WINDOW is t, do not show the help window" |
| 111 | + (let ((msg (format "Unable to locate \"%s\" on the system PATH%s" |
| 112 | + matlab-shell-command |
| 113 | + (if default-loc |
| 114 | + (format " or in the default installation location, %s" |
| 115 | + default-loc) |
| 116 | + "")))) |
| 117 | + (when (not no-help-window) |
| 118 | + (let ((help-buf-name "*matlab-shell-help*")) |
| 119 | + (with-current-buffer (get-buffer-create help-buf-name) |
| 120 | + (with-help-window help-buf-name |
| 121 | + (insert msg " |
| 122 | +
|
| 123 | +To fix, update your system PATH to include |
| 124 | + \"/<path-to-MATLAB-install>/bin\" |
| 125 | +To verify matlab is on your path, run \"matlab -h\" in a terminal. |
| 126 | +
|
| 127 | +Alternatively, you can provide the full path to the |
| 128 | +MATLAB command executable by customizing option |
| 129 | +`matlab-shell-command'\n"))))) |
| 130 | + |
| 131 | + (user-error "%s" msg))) |
| 132 | +(cl-defun matlab--get-abs-matlab-exe (&optional no-error) |
| 133 | + "Absolute path to the MATLAB executable. |
| 134 | +When `matlab-shell-command' is an absolute path, then this will |
| 135 | +be resolved to its true name. Otherwise, `matlab-shell-command' |
| 136 | +is found using `executable-find'. If `matlab-shell-command' is |
| 137 | +\"matlab\" and not the system PATH, this will return the latest |
| 138 | +MATLAB installed command found using |
| 139 | +`matlab--default-matlab-exe'. |
| 140 | +
|
| 141 | +If NO-ERROR is t, and matlab command is not found, nil is return, |
| 142 | +otherwise an error is signaled." |
| 143 | + (condition-case err |
| 144 | + (let (abs-matlab-exe) |
| 145 | + (cond |
| 146 | + |
| 147 | + ;;Case: the path to the matlab executable was provided, validate it exists and |
| 148 | + ;; return it. |
| 149 | + ((file-name-absolute-p matlab-shell-command) |
| 150 | + (when (not (file-exists-p matlab-shell-command)) |
| 151 | + (user-error "Invalid setting for `matlab-shell-command', %s does not exist" |
| 152 | + matlab-shell-command)) |
| 153 | + (when (not (file-executable-p matlab-shell-command)) |
| 154 | + (user-error "Invalid setting for `matlab-shell-command', %s is not executable" |
| 155 | + matlab-shell-command)) |
| 156 | + ;; Use the path provided. Consider the case where a launcher script is provided and the |
| 157 | + ;; launcher script is symlink'd. In this case, we shouldn't resolve the symlinks, i.e. |
| 158 | + ;; using file-truename would break this case. |
| 159 | + (setq abs-matlab-exe matlab-shell-command)) |
| 160 | + |
| 161 | + ;; Case: set to a relative path |
| 162 | + ;; |
| 163 | + ((when (file-name-directory matlab-shell-command) |
| 164 | + (user-error "Relative paths are not supported for `matlab-shell-command', %s" |
| 165 | + matlab-shell-command))) |
| 166 | + |
| 167 | + ;; Case: "matlab" (or something similar), locate it on the executable path |
| 168 | + ;; else locate in standard install locations. |
| 169 | + (t |
| 170 | + (let ((remote (file-remote-p default-directory))) |
| 171 | + (if remote |
| 172 | + (if (setq abs-matlab-exe (executable-find matlab-shell-command t)) |
| 173 | + (setq abs-matlab-exe (concat remote abs-matlab-exe)) |
| 174 | + (user-error "Unable to locate matlab executable on %s |
| 175 | +See https://github.com/mathworks/Emacs-MATLAB-Mode/doc/remote-matlab-emacs.org for tips" remote)) |
| 176 | + ;; else look local |
| 177 | + (setq abs-matlab-exe (executable-find matlab-shell-command)) |
| 178 | + (when (not abs-matlab-exe) |
| 179 | + (if (string= matlab-shell-command "matlab") |
| 180 | + ;; Get latest matlab command exe from the default installation location. |
| 181 | + (let* ((default-loc (cdr (assoc system-type matlab--default-matlab-exe))) |
| 182 | + (default-matlab (when default-loc |
| 183 | + (car (last (sort |
| 184 | + (file-expand-wildcards default-loc) |
| 185 | + #'string<)))))) |
| 186 | + (when (not default-matlab) |
| 187 | + (matlab--matlab-exe-not-found no-error default-loc)) |
| 188 | + (when (not (file-executable-p default-matlab)) |
| 189 | + (user-error "%s is not executable" default-matlab)) |
| 190 | + (setq abs-matlab-exe default-matlab)) |
| 191 | + ;; else unable to locate it |
| 192 | + (matlab--matlab-exe-not-found no-error))))))) |
| 193 | + |
| 194 | + ;; Return existing absolute path to the MATLAB command executable |
| 195 | + abs-matlab-exe) |
| 196 | + (error (when (not no-error) (error "%s" (error-message-string err)))))) |
| 197 | + |
| 198 | +(defun matlab--get-matlabroot () |
| 199 | + "Return the MATLABROOT from `matlab--get-abs-matlab-exe'. |
| 200 | +The returned MATLABROOT does not have a trailing slash. |
| 201 | +Returns nil if unable to determine the MATLABROOT." |
| 202 | + ;; strip "/bin/matlab" from /path/to/matlabroot/bin/matlab |
| 203 | + (let ((abs-matlab-exe (matlab--get-abs-matlab-exe 'no-error))) |
| 204 | + (when abs-matlab-exe |
| 205 | + (let ((bin-dir (directory-file-name (file-name-directory abs-matlab-exe)))) |
| 206 | + ;; matlabroot no slash |
| 207 | + (directory-file-name (file-name-directory bin-dir)))))) |
| 208 | + |
| 209 | +;;; emacsclient |
| 210 | + |
| 211 | +(defun matlab--find-emacsclient () |
| 212 | + "Locate the emacsclient corresponding for current Emacs. |
| 213 | +Emacs binary is defined by variable `invocation-name' in variable |
| 214 | +`invocation-directory'" |
| 215 | + (let ((ec "emacsclient")) |
| 216 | + (cond |
| 217 | + ;; Mac |
| 218 | + ((equal system-type 'darwin) |
| 219 | + (if (file-exists-p (concat invocation-directory "emacsclient")) ;; running the default emacs? |
| 220 | + (setq ec (concat invocation-directory "emacsclient")) |
| 221 | + ;; On Mac, one can install into |
| 222 | + ;; /Applications/Emacs.app/Contents/MacOS/Emacs |
| 223 | + ;; /Applications/Emacs.app/Contents/MacOS/bin/emacsclient |
| 224 | + (if (file-exists-p (concat invocation-directory "bin/emacsclient")) |
| 225 | + (setq ec (concat invocation-directory "bin/emacsclient"))))) |
| 226 | + ;; Windows |
| 227 | + ((equal system-type 'windows-nt) |
| 228 | + (if (file-exists-p (concat invocation-directory "emacsclientw.exe")) |
| 229 | + (setq ec (concat invocation-directory "emacsclientw.exe")) |
| 230 | + (error "Unable to locate emacsclientw.exe. It should be in %s" invocation-directory))) |
| 231 | + ;; Linux or other UNIX system |
| 232 | + (t |
| 233 | + ;; Debian 9 can be setup to have: |
| 234 | + ;; /usr/bin/emacs |
| 235 | + ;; /usr/bin/emacsclient |
| 236 | + ;; /usr/bin/emacs24 |
| 237 | + ;; /usr/bin/emacsclient.emacs24 |
| 238 | + ;; /usr/bin/emacs25 |
| 239 | + ;; /usr/bin/emacsclient.emacs25 |
| 240 | + (if (and (equal invocation-name "emacs") |
| 241 | + (file-exists-p (concat invocation-directory "emacsclient"))) |
| 242 | + (setq ec (concat invocation-directory "emacsclient")) |
| 243 | + (if (file-exists-p (concat invocation-directory "emacsclient." invocation-name)) |
| 244 | + (setq ec (concat invocation-directory "emacsclient." invocation-name)))))) |
| 245 | + |
| 246 | + ;; Return, ec, the emacsclient to use |
| 247 | + ec)) |
| 248 | + |
| 249 | +;;; mlint |
| 250 | + |
| 251 | +(defgroup mlint nil |
| 252 | + "MLint minor mode." |
| 253 | + :prefix "mlint-" |
| 254 | + :group 'matlab) |
| 255 | + |
| 256 | +(defcustom mlint-programs (list "mlint") |
| 257 | + "*List of possible mlint programs. |
| 258 | +First entry in the list that exists is used. |
| 259 | +The \"mlint\" entry means use mlint next to the |
| 260 | +matlab executable defined by `matlab-shell-command'. |
| 261 | +Other entries should be absolute paths." |
| 262 | + :group 'mlint |
| 263 | + :type '(repeat (file :tag "MLint Program: "))) |
| 264 | + |
| 265 | +(defun matlab--get-mlint-exe () |
| 266 | + "Return MLint executable or nil if not found. |
| 267 | +The returned execuable will be the full path to mlint. If we resolved |
| 268 | +the \"mlint\" entry in `mlint-programs', in which case this is the mlint |
| 269 | +next to the matlab found by `matlab--get-abs-matlab-exe'. If we |
| 270 | +resolved another entry in `mlint-programs', we'll use that and by |
| 271 | +convention that entry should be an absolute path, but that's not |
| 272 | +guaranteed." |
| 273 | + (let (mlint-exe) |
| 274 | + (cl-loop for mlint in mlint-programs do |
| 275 | + (if (string= mlint "mlint") |
| 276 | + (let ((matlab-exe (matlab--get-abs-matlab-exe 'no-error))) |
| 277 | + (when matlab-exe |
| 278 | + (setq mlint-exe |
| 279 | + (replace-regexp-in-string "matlab\\(\\.exe\\)?\\'" |
| 280 | + (concat matlab--platform "/mlint\\1") |
| 281 | + matlab-exe)) |
| 282 | + (cl-return))) |
| 283 | + (when (file-executable-p mlint) |
| 284 | + ;; We can't use file-truename on mlint because that would resolve |
| 285 | + ;; symbolic links. |
| 286 | + (setq mlint-exe mlint) |
| 287 | + (cl-return)))) |
| 288 | + mlint-exe)) |
| 289 | + |
| 290 | +(provide 'matlab--access) |
| 291 | +;;; matlab--access.el ends here |
0 commit comments