diff --git a/elisp/arch-ros2.el b/elisp/arch-ros2.el new file mode 100644 index 0000000..48d70fc --- /dev/null +++ b/elisp/arch-ros2.el @@ -0,0 +1,133 @@ +;;; arch-ros2.el --- Activate and deactivate ROS2 dev environment on ArchLinux -*- lexical-binding: t -*- +;;; Commentary: +;;; Code: +(require 'cl-lib) + +(defcustom arch-ros2-root "/opt/ros/humble/" + "Root directory of the ROS2 install." + :type 'directory + :group 'arch-ros2) + +(defcustom arch-ros2-distro "humble" + "Version name of ROS2." + :type 'string + :group 'arch-ros2) + +(defcustom arch-ros2-version 2 + "Version number of ROS2 (probably 2)." + :type 'integer + :group 'arch-ros2) + +(defcustom arch-ros2-python-version "3.13" + "Python version of ROS2." + :type 'string + :group 'arch-ros2) + +(defvar arch-ros2-active nil + "Weather of not the ROS2 development environment is active.") + +(defconst arch-ros2-mode-line-format `(arch-ros2-active + ,(propertize "[ROS2]" + 'face 'mode-line-emphasis)) + "Mode line element for ROS2.") + +(defvar arch-ros2--saved-env-vars (make-hash-table :test 'equal) + "Hash table of saved environment variables. +The key of each entry is the variable name. The value is a cons. The car is +either the symbol \\='value or \\='files. If it is \\='value, the cons is a +list of the old value and the value we installed. If the cdr is \\='files, the +value is a list of files to be removed from the variable.") + +(defun arch-ros2--set-env-var (var value) + "Set the environment variable VAR to VALUE, saving its old value." + (puthash var (list 'value (getenv var) value) arch-ros2--saved-env-vars) + (setenv var value)) + +(defun arch-ros2--add-file-to-var (var &rest values) + "Add each of VALUES to the file list environment variable VAR. +This will prepend the values to VAR." + (let* ((cur-val (split-string (or (getenv var) "") ":" t)) + (to-set)) + (dolist (value values) + (unless (cl-find value cur-val :test 'equal) + (push value to-set))) + (let ((cache (gethash var arch-ros2--saved-env-vars))) + (puthash var (cons 'files (seq-uniq (append to-set (cdr cache)))) + arch-ros2--saved-env-vars)) + (setenv var (string-join (append to-set cur-val) ":")))) + +(defun arch-ros2--add-to-path (&rest values) + "Add each of VALUES to the variable `exec-path'." + (let ((to-check (butlast exec-path)) + (did-add nil)) + (dolist (value values) + (unless (cl-find value to-check :test 'equal) + (push value exec-path) + (push value did-add))) + (puthash 'exec-path (append did-add + (gethash 'exec-path arch-ros2--saved-env-vars)) + arch-ros2--saved-env-vars))) + +(defun arch-ros2--restore-env-var (var) + "Restore the value of VAR set with `arch-ros2--set-env-var'." + (let ((entry (gethash var arch-ros2--saved-env-vars))) + (cl-case (car entry) + (value + (cl-destructuring-bind (&optional old-val our-val) (cdr entry) + ;; don't restore values that have been changed + (when (equal our-val (getenv var)) + (setenv var old-val)))) + (files + (when-let ((cur-val (getenv var)) + (parts (split-string cur-val ":" t))) + (setenv var (string-join (seq-difference parts (cdr entry)) ":"))))) + (remhash var arch-ros2--saved-env-vars))) + +(defun arch-ros2-activate () + "Activate a ROS2 development environment." + (interactive) + (setq arch-ros2-active t) + (add-to-list 'mode-line-misc-info arch-ros2-mode-line-format) + (arch-ros2--add-to-path "/opt/ros/humble/bin/") + (arch-ros2--set-env-var "AMENT_PREFIX_PATH" arch-ros2-root) + (arch-ros2--set-env-var "CMAKE_PREFIX_PATH" arch-ros2-root) + (arch-ros2--set-env-var "COLCON_PREFIX_PATH" arch-ros2-root) + (arch-ros2--set-env-var "ROS_DISTRO" arch-ros2-distro) + (arch-ros2--set-env-var "ROS_LOCALHOST_ONLY" "0") + (arch-ros2--set-env-var "ROS_PYTHON_VERSION" + (car (split-string arch-ros2-python-version "\\."))) + (arch-ros2--set-env-var "ROS_VERSION" + (number-to-string arch-ros2-version)) + (arch-ros2--add-file-to-var + "LD_LIBRARY_PATH" + (expand-file-name "opt/rviz_ogre_vendor/lib" + arch-ros2-root) + (expand-file-name "lib" + arch-ros2-root)) + (arch-ros2--add-file-to-var + "PKG_CONFIG_PATH" (expand-file-name "lib/pkgconfig" arch-ros2-root)) + (let ((python-dir (expand-file-name + (concat "lib/python" arch-ros2-python-version) + arch-ros2-root))) + (arch-ros2--add-file-to-var "PYTHONPATH" + (expand-file-name "dist-packages" python-dir) + (expand-file-name "site-packages" python-dir)))) + +(defun arch-ros2-deactivate () + "Deactivate the ROS2 development environment." + (interactive) + (setq arch-ros2-active nil + mode-line-misc-info (cl-remove arch-ros2-mode-line-format + mode-line-misc-info + :test 'equal)) + (maphash (lambda (k v) + (cond + ((stringp k) + (arch-ros2--restore-env-var k)) + ((eq k 'exec-path) + (setq exec-path (seq-difference exec-path v)) + (remhash 'exec-path arch-ros2--saved-env-vars)))) + arch-ros2--saved-env-vars)) + +(provide 'arch-ros2) +;;; arch-ros2.el ends here