;;; 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))) (defmacro arch-ros2-with-modify-global-env (&rest body) "Execute BODY, modifying the default toplevel `process-environment'." `(let ((process-environment (default-toplevel-value 'process-environment))) (unwind-protect (progn ,@body) (set-default-toplevel-value 'process-environment process-environment)))) (defun arch-ros2-activate () "Activate a ROS2 development environment." (interactive) (arch-ros2-with-modify-global-env (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) (arch-ros2-with-modify-global-env (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