大家的 Emacs 配置是如何适配 多系统/多设备 的呢?

抄代码的时候看到 yfzhe/.emacs.d 这样的语法糖。 大受启发,也写了一个类似的。

;; for other use
(defconst *is-mac* (string-equal system-type "darwin"))
(defconst *is-linux* (string-equal system-type "gnu/linux"))
(defconst *is-win* (string-equal system-type "windows-nt"))

;; /os, multi-os config tool
(require 'cl-macs)

;; 值 1 对 1
(cl-defmacro get-value/os (&key macos linux windows default)
  "Value depended on `system-type',
  each clause using a keyword, `:windows', `:macos', or `:linux',
  and an optional `:default' clause."
  `(cond (*is-mac* ,macos)
	     (*is-linux* ,linux)
	     (*is-win* ,windows)
	     (t ,default)))

(defmacro defconst/os (id &rest rhs)
  "Define constant based on `system-type', see `value/os'."
  `(defconst ,id (get-value/os ,@rhs)))

这样,配置不同平台的不同路径就变得如此直观简洁

(defconst/os my-org-directory
  :windows "C:/Dropbox"
  :macos "~/Documents/Docs/org")

不知道大家感觉怎么样呢?还是有更好的办法?

5 个赞

个人比较喜欢 radian/radian.el at develop · radian-software/radian · GitHub 这个的写法

1 个赞

选择不用多系统, 就算 emacs 可以适配, 也不是所有的都可以适配.

这个通过配置不同机器都放置在同一个路径下就可以了。 比如我的 org-directory 全部在到 ~/org 这个路径下。

啊,这个好棒,用起来像内置函数一样自然,毫无违和感。不过我用 &key 是因为主要是解决一些平时不同工作环境下的路径参数,功能加载什么,主要是为了复用同一个变量名。

这个大佬写的,文档都好详细,越看越喜欢 :cowboy_hat_face:

是的,相对路径保持一致就好了。但我有时候会需要加载的路径或者文件有区分度,比如notdeft 用到 notdeft-xapian 需要自己编译,要加上后缀区分一下系统 :sweat_smile:

我是做映射吧,访问相同路径。除了原生Window,没有在原生Windows中使用,用的是WSL下。

我发现其实这是两个问题,故修改题目成 多系统/多设备,目前的办法如下

为了区分后缀 分别是 /os/device

多系统的 defmacro 尽量写成内置函数风格,参照 2楼 brsvh

;; /os, multi-os config tool
;; https://github.com/raxod502/radian/blob/develop/emacs/radian.el#L169-L188
(defmacro operating-system-p (os)
  "Return non-nil if OS corresponds to the current operating system.
Allowable values for OS (not quoted) are `macOS', `osx',
`windows', `linux', `unix'."
  (pcase os
    (`unix `(not (memq system-type '(ms-dos windows-nt cygwin))))
    ((or `macOS `osx) `(eq system-type 'darwin))
    (`linux `(not (memq system-type
                        '(darwin ms-dos windows-nt cygwin))))
    (`windows `(memq system-type '(ms-dos windows-nt cygwin)))))

(defmacro eval-with/os (os &rest body)
  "If OS corresponds to the current operating system, eval and return BODY.
If not, return nil.
Allowable values for OS (not quoted) are `macOS', `osx',
`windows', `linux', `unix'."
  (declare (indent 1))
  `(when (operating-system-p ,os)
     ,@body))

多设备则依赖载入的常量,目前是

;; /device, multi-device config tool
(require 'cl-macs)

;; please name your device other place before
;; such as mac1~100 (hopefully)
(defvar *current-device* 'unknow
  "Not `unknow' if you name it" )

(cl-defmacro value/device (&key mac-a pc-a linux-a server-a default)
  "Return Value depended on `*current-device*', each clause using a keyword"
  (pcase *current-device*
    ;; maintain device list here
    (`mac-a mac-a)
    (`pc-a pc-a)
    (`linux-a inux-a)
    (`server-a server-a)
    ((or `default `unknow) default)))

(defmacro defconst/device (id &rest rhs)
  "Define constant based on device name"
  `(defconst ,id (value/device ,@rhs)))

(defmacro defvar/device (id &rest rhs)
  "Define variable based on device name"
  `(defvar ,id (value/device ,@rhs)))

;;; test
*current-device*
;; => "unknow"

(value/device :mac-a "test")
;; => nil
;; because :default nil

(defconst/device test :default "default value")
(message test)
;; => "default value"

后续可以考虑扩展成 key list 方便批量添加维护(不过自己大概率不会有那么多设备 :rofl:

1 个赞

多设备有可能,一般不用多系统,Windows下开启wsl以便使用magit

其实你想表达 predicate 的命名?可以阅读 Coding Conventions (GNU Emacs Lisp Reference Manual)

经过充分的深思熟虑 :rofl: ,发现我前面把问题复杂化了(菜鸟新手)。

其实就是搭建一个脚手架,基于两个变量 system-typesystem-name 的值,对给定的参数列表(symbol)进行选择,最后再 eval 选择到的 symbol,最终 return 想要的值回去。

故我先确认最后我想要这么一个配置的样式(用 alist 传参数)

(defconst font-size-int
  (select-default 13
    (macOS . 15)
    (windows . 17)))

;; xapian for notdeft
(defconst notdeft-xapian
  (select-default nil
    (macOS . (expand-file-name
              "etc/site-lisp/notdeft/xapian/mac-nt-xa"
              user-emacs-directory))
    (windows . (expand-file-name
           "etc/site-lisp/notdeft/xapian/win-nt-xa"
           user-emacs-directory))
    ))

那么我需要两层(同一系统不同设备多一层)

最外层对 cell 处理成 alist 传给内层,对内层的返回结果判断是不是要传 第一个值(作为 default)

(defmacro select-default (defatult &rest cells)
  "Select Value base on `os-name'"
  (declare (indent 1))
  `(let ((re (read/os '(,@cells))))
     (if re re ,defatult)))

(defmacro select-specific (default &rest cells)
  "Select Value base on `device-name'(priority) and `os-name'"
  (declare (indent 1))
  `(let ((re (read/device '(,@cells))))
     (if re re (select-default ,@default ,@cells))))

最内层,处理 alist (得到想要的 symbol),手动 eval 之后返回。

(defmacro read/os (alist)
  "Get value from alist base on `os-name' "
  (declare (indent 1))
  (eval `(cdr (assq os-name ,alist))))

(defmacro read/device (alist)
  "Get value from alist base on `os-name' "
  (declare (indent 1))
  (eval `(cdr (assq device-name ,alist))))

最后就剩下额外的工作,把不那么直观的 system-typesystem-name 翻译成自己的 os-name device-name 就可以了。

(defconst os-name
  (cond
   ;; Translate system-type to os-name
   ((eq system-type 'darwin) 'macOS)
   ((memq system-type '(ms-dos windows-nt cygwin)) 'windows)
   (t 'linux))
  "Value base on `system-type'. 
Three candidates `macOS', `windows' and `linux'.")

(defconst device-name
  ;; Translate system-name to device-name
  (cdr (assq (intern system-name) *device-alist*))
  "Value base on `system-name' and `device-alist'")

(defconst *device-alist*
  '(;; name your device here
    (ingtshans-MacBook-Pro.local . 19mbp)
    ;; end
    )
  "Naming your device base on `system-name'.
You can't use any candidate of `os-name'")

反向整理就得到一个还可用的脚手架了。

同时也符合,配置信息灵活按需添加的原则。比如

;; version 1
(defconst font-size-int 13)

;; version 2
;; macOS need adapt
(defconst font-size-int
  (select-default 13
    (macOS . 15)))

;; version 3
;; win need adapt
(defconst font-size-int
  (select-default 13
    (macOS . 15)
    (windows . 17)))

感觉还是用if语句来判断系统、设备更直观一些,或者说与其它的语言更像。

我是把windows或linux单独有的配置提取出来到了一个单独文件,在init.el里判断

(if (eq system-type 'windows-nt)
  (load "~/.emacs.d/init/os-win.el" nil t)
  (load "~/.emacs.d/init/os-linux.el" nil t))
5 个赞

好办法,直面问题简单明了。

故,我前面那个精确到单个变量或者语句的 “多设备” 可以做为过渡阶段(比较灵活),稳定了之后,再分析代码从中剥离出跨平台配置放到单独文件里(这个过程好像也可以用代码自动化)。

或许我可以按照你的思路,简单的多设备适配是两类(变量和语句),只要保证 os-xxx.el 最先 load,然后充分利用 defvardefconst, defun, fboundp, with-eval-after-load的特性,其中变量在 os-xxx.el 中用 defconst 保证是最新的,默认的就用defvar,而需要额外执行的语句用函数装起来,并在需要的位置执行或在原来文件中with-eval-after-load择机执行(执行前 fboundp 检查,早于这个位置的defun 也要boundp检查 ) 再多一步就是 init.el 的最后用 fmakunbound 删除这些函数(可选,容易导致某些延迟加载问题)。 那么其实也达到我前面说的 “灵活问题”。

1 个赞

借宝地问个问题,如何区分 ARM 和 x86 架构?

你可以看下tumashu的emacs配置

没找到,求一个配置地址

1 个赞

直接用 hostname 也就是 (system-name) 不行吗? 不同系统或者不同设备设置不同的 hostname

;; Load different machine config
(cond
 ((string-equal (downcase (system-name)) "work")
  (progn (message "On machine work")
         (load! "+work.el")))
 ((string-equal (downcase (system-name)) "inspiron5510")
  (progn (message "On machine inspiron5510")
         (load! "inspiron5510.el")))
 ((string-equal (downcase (system-name)) "macbookpro")
  (progn (message "On machine MacBookPro")
         (load! "macbookpro.el")))
 ;; else 写法
 (t (progn (warn "Illegal Machine!"))))
4 个赞