生产力 · 21 4 月, 2024 0

如何部署自己的 Bootlin Elixir 并索引自己的代码库

Elixir Cross Referencer 是一个非常好用的 C/C++ 代码索引工具。它提供了一个 Web UI,可以选择不同的项目代码进行阅读,并支持搜索和进行符号跳转。本文讲述如何搭建自己的 Elixir Cross Referencer,并以 FreeBSD 为例,说明如何适配自己的代码仓库。

本教程的成品展示:https://elixir.catboy.me

目录

  1. 为什么需要适配自己的代码仓库?
  2. 安装 Elixir Cross Referencer
  3. 下载 FreeBSD 源码
  4. 为 FreeBSD 适配 Elixir
  5. 索引 FreeBSD 源码
  6. 配置 Apache2 网页服务器
  7. Elixir 的日常维护

为什么需要适配自己的代码仓库?

在 Elixir Cross Referencer 的 Web UI 左侧边栏,包含一个版本列表,可以让用户快速切换同一代码库的不同版本。这一功能,是通过列出 Git 代码仓库的所有 Tag 实现的。Elixir 会要求用户实现几个 Shell 函数,来列出 Git 仓库的所有标签,以及将所有标签的字符串,按照主次版本号分割。如下图中的版本号 “v6 / v6.5 / v6.5.5”。


默认情况下,Elixir 使用 “vX.X.XXX” 字符串匹配模式来显示版本层次,如 Linux,U-Boot,Zephyr 等。如果某个仓库的 Git 标签不遵循这种模式,那么你需要支持这个仓库。否则,版本列表中会显示 “FIXME” 字样,表示版本替换功能不可用。

安装 Elixir Cross Referencer

本教程基于 Ubuntu 22.04 LXC 容器,并采用直接安装,而非使用 Docker 进行部署。

Elixir 的安装过程,和官方 GitHub 所描述的过程一致:

# 依赖项目
apt install python3-jinja2 python3-bsddb3 python3-falcon python3-pytest python3-pygments universal-ctags perl git apache2 libapache2-mod-wsgi-py3 libjansson4

# Elixir 会被安装在 /usr/local/elixir/
git clone https://github.com/bootlin/elixir.git /usr/local/elixir/

下载 FreeBSD 源码

本文使用 /opt/elixir-data 作为 Elixir 项目目录。

# 创建 FreeBSD 的 Elixir 索引缓存目录
mkdir -p /opt/elixir-data/freebsd/data

# 拉取 FreeBSD 源码
git clone https://github.com/freebsd/freebsd-src.git /opt/elixir-data/freebsd/repo

# (!!关键!!) 更改所有者为 www-data,否则会报错 "This file does not exist"
chown -R www-data /opt/elixir-data/freebsd/repo

为 FreeBSD 适配 Elixir

适配一个代码仓库,需要编写一个 Shell 脚本,并重新实现一些 Shell 函数。这个 Shell 脚本需要放在 “/usr/local/elixir/projects” 目录中,并应命名为 “<项目名称>.sh”。当访问 Elixir Web UI 时,会调用此 Shell 脚本中实现的函数,来获取最新版本,以及版本层次。

要适配 FreeBSD,我们需要在 “/usr/local/elixir/projects/” 目录内创建名为 “freebsd.sh” 的文件。文件的内容如下:

# Elixir definitions for FreeBSD

# Enable DT bindings compatible strings support
dts_comp_support=1

list_tags()
{
        echo "$tags"
}

list_tags_h()
{
        echo "$tags" | tac | sed -E 's/(release\/[0-9]+)(\.[0-9]+)?(\.[0-9a-zA-Z_]+)?/\1  /'
}

get_latest()
{
        git tag | sort -V | tail -n 1
}

详细说明一下 freebsd.sh 文件中各个部分的作用:

  1. 变量 dts_comp_support=1
    这个变量启用了 Elixir 对设备树(Device Tree)的支持。设备树是用来描述硬件设备信息的数据结构,在嵌入式系统中非常常见。
  2. 函数 list_tags()
    该函数负责过滤 Git 标签。函数被调用时,有一个预定义好的变量 $tags,其值是一个字符串,包含了所有 Git 标签。你需要过滤这些标签,去除那些你不想在网页界面上显示的标签,并将剩下的标签打印到标准输出(stdout)。
  3. 函数 list_tags_h()
    该函数用于将 Git 标签拆分成三部分的字符串,以显示版本层次结构。例如,如果 FreeBSD 有一个版本标签 release/13.2.0,你需要将其拆分为以下字符串,并将其打印至标准输出:
    “release/13 release/13.2 release/13.2.0”
  4. 函数 get_latest()
    该函数用于获取仓库的最新版本。当请求 URL https://<elixir>/<repository>/latest/source 时,会调用 get_latest() 函数来获取最新版本。如果该函数没有实现,你会收到一个 404 错误。

索引 FreeBSD 源码

要在 Elixir 的 Web UI 上进行符号搜索和跳转,需要首先对代码进行索引。Elixir 会对代码进行分析,并将代码符号索引存入数据库。

在索引之前,首先要使用 Elixir 自带的测试脚本,来验证先前的适配是否正常。首先来测试能否列出 FreeBSD 所有的 Git 标签:

root@elixir:~# cd /usr/local/elixir
root@elixir:/usr/local/elixir# LXR_REPO_DIR=/opt/elixir-data/freebsd/repo/ LXR_DATA_DIR=/opt/elixir-data/freebsd/data/ ./script.sh list-tags
release/1.0.0_cvs
release/1.1.0_cvs
release/1.1.5.1_cvs
release/2.0
release/2.0.5
release/2.0.5_cvs
release/2.0_cvs
release/2.1.0
...

看起来并没有毛病。再试试版本的字符串分割是否有效:

root@elixir:/usr/local/elixir# LXR_REPO_DIR=/opt/elixir-data/freebsd/repo/ LXR_DATA_DIR=/opt/elixir-data/freebsd/data/ ./script.sh list-tags -h
release/13 release/13.2 release/13.2.0
release/13 release/13.1 release/13.1.0
release/13 release/13.0 release/13.0.0
release/12 release/12.4 release/12.4.0
release/12 release/12.3 release/12.3.0
release/12 release/12.2 release/12.2.0
release/12 release/12.1 release/12.1.0
release/12 release/12.0 release/12.0.0
...

也没有问题。接下来就可以开始建立索引了。我的机器性能很强,我开了 16 个 Job 来建立索引:

root@elixir:/usr/local/elixir# LXR_REPO_DIR=/opt/elixir-data/freebsd/repo/ LXR_DATA_DIR=/opt/elixir-data/freebsd/data/ ./update.py 16

对于 FreeBSD 这种超大型项目来说,建立索引需要很长的时间。在我的机器上,建立索引用了 20 个小时。当索引建立完成后,可以使用 Elixir 的测试脚本来尝试搜索一个符号,来验证先前的工作是否都成功完成:

root@elixir:/usr/local/elixir# LXR_REPO_DIR=/opt/elixir-data/freebsd/repo/ LXR_DATA_DIR=/opt/elixir-data/freebsd/data/ ./query.py release/13.2.0 ident fuse_filehandle_open C
Symbol Definitions:
Symbol in path: sys/fs/fuse/fuse_file.h, line: 216 , type: prototype
Symbol in path: sys/fs/fuse/fuse_file.c, line: 124 , type: function

Symbol References:
Symbol in path: sys/fs/fuse/fuse_vnops.c, line: 1763,1860,1942,2478

Documented in:
...

看起来都没有问题。

配置 Apache2 网页服务器

Apache2 的安装以及配置方法,不在本文的讨论范围内。这里我直接提供我的 Apache2 的配置文件:

HttpProtocolOptions Unsafe

# Required for HTTP
<Directory /usr/local/elixir/http/>
    Options +ExecCGI
    AllowOverride None
    Require all granted
    SetEnv PYTHONIOENCODING utf-8
    SetEnv LXR_PROJ_DIR /opt/elixir-data
</Directory>

# Required for the REST API
<Directory /usr/local/elixir/api/>
    SetHandler wsgi-script
    Require all granted
    SetEnv PYTHONIOENCODING utf-8
    SetEnv LXR_PROJ_DIR /opt/elixir-data
</Directory>

AddHandler cgi-script .py

<IfModule mod_ssl.c>
<VirtualHost *:443>
        ServerAdmin mail@example.com
        DocumentRoot /usr/local/elixir/http

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        AllowEncodedSlashes On

        RewriteEngine on
        RewriteRule "^/$" "/freebsd/latest/source" [R]
        RewriteRule "^/(?!api|acp).*/(source|ident|search)" "/web.py" [PT]
        RewriteRule "^/acp" "/autocomplete.py" [PT]

        ServerName elixir.example.com
        SSLCertificateFile /etc/letsencrypt/live/elixir.example.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/elixir.example.com/privkey.pem
        Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

记得更换文件中的域名哦。

Elixir 的日常维护

Elixir 没有很大的维护成本。通常来讲只有代码仓库更新,需要重新建立索引时,才需要进行操作。

如果你需要删掉 Elixir Web UI 顶部的 Bootlin LOGO,清空这个文件即可:

root@elixir:/usr/local/elixir# :> /usr/local/elixir/templates/header.html

完成的成品如下图所示:

我的 GitHub 上有两个脚本可以一键完成添加新代码仓库,以及重新索引代码仓库。可以进行参考。