= Elixir 交叉引用工具 :doctype: book :pp: {plus}{plus} :toc: :toc-placement!:
Elixir 是一个受 https://en.wikipedia.org/wiki/LXR_Cross_Referencer[LXR] 启发的源代码交叉引用工具。它用 Python 编写,主要目的是为了以最小的占用空间索引 C 或 C{pp} 项目(如 Linux 内核)的每个发布版本。
它使用 Git 作为源代码文件存储,使用 Berkeley DB 存储交叉引用数据。在内部,它索引 Git blobs 而不是文件树,以避免重复工作和数据。它具有简单直观的数据结构(类似于早期的 LXR 版本),以保持查询简单快速。
您可以在 https://elixir.bootlin.com/ 上看到它的运行效果
注意:本文档适用于 Elixir 2.0 版本。
toc::[]
= 要求
- Python >= 3.6
- Git >= 1.9
- Jinja2 和 Pygments (>= 2.7) Python 库
- Berkeley DB (及其 Python 绑定)
- Universal Ctags
- Perl (用于非贪婪正则表达式和自动化测试)
- Falcon 和
mod_wsgi
(用于 REST API)
= 架构
Elixir 具有以下架构:
.---------------.----------------. | CGI 接口 | REST 接口 | |---------------|----------------.
查询命令 | 更新命令 |
---|---|
Shell 脚本 | |
'--------------------------------' |
Shell 脚本 (script.sh
) 是底层,提供与 Git 和其他 Unix 工具交互的命令。Python 命令使用 shell 脚本的服务来提供对带注释的源代码和标识符列表的访问 (query.py
),或创建和更新数据库 (update.py
)。最后,CGI 接口 (web.py
) 和 REST 接口 (api.py
) 使用查询接口来生成 HTML 页面和响应 REST 查询。
在安装系统时,您应该手动测试每一层,确保它正常工作后再继续下一层。
== 数据库设计
./update.py
存储 git 对象哈希("blobs")和顺序键之间的双向映射。
索引这些哈希的目的是减少它们的存储占用(SHA-1 哈希为 20 字节,而 32 位整数为 4 字节)。
将提供详细的数据库图表。在此之前,请直接查看源代码。
= 手动安装
== 安装依赖
对于 RedHat/Fedora/AlmaLinux
sudo dnf install python36-pip python36-pytest python36-jinja2 python36-bsddb3 python36-falcon python3-pygments git httpd perl perl-autodie jansson libyaml rh-python36-mod_wsgi
对于 Debian
sudo apt install python3-jinja2 python3-bsddb3 python3-falcon python3-pytest python3-pygments universal-ctags perl git apache2 libapache2-mod-wsgi-py3 libjansson4
要启用 REST API,请按照 https://github.com/GrahamDumpleton/mod_wsgi[`mod_wsgi`] 的安装说明进行操作,并按照 https://github.com/GrahamDumpleton/mod_wsgi#connecting-into-apache-installation 中的详细说明将其连接到 apache 安装。
要了解需要安装哪些软件包,您还可以阅读 docker/
目录中的 Docker 文件,以了解 Elixir 在您喜欢的发行版中需要哪些软件包。
== 下载 Elixir 项目
git clone https://github.com/bootlin/elixir.git /usr/local/elixir/
== 创建目录
mkdir -p /path/elixir-data/linux/repo mkdir -p /path/elixir-data/linux/data
== 设置环境变量
使用两个环境变量来告诉 Elixir 在哪里找到项目的本地 git 仓库和数据库:
LXR_REPO_DIR
(项目的 git 仓库目录)LXR_DATA_DIR
(项目的数据库目录)
现在打开 /etc/profile
并追加以下内容。
export LXR_REPO_DIR=/path/elixir-data/linux/repo export LXR_DATA_DIR=/path/elixir-data/linux/data
然后运行 source /etc/profile
。
== 克隆内核源代码
首先克隆 Linus Torvalds 发布的主分支:
cd /path/elixir-data/linux git clone --bare https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git repo
然后,您还应该声明一个对应于 stable
树的 stable
远程分支,以获取所有发布更新:
cd repo git remote add stable git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git git fetch stable
然后,您还可以声明一个对应于其他仓库中不存在的旧 Linux 版本的 history
远程分支,以获取所有仍然可用的旧版本:
cd repo git remote add history https://github.com/bootlin/linux-history.git git fetch history --tags
请随意以这种方式添加更多远程分支,因为 Elixir 将考虑所有远程分支的标签。
== 第一次测试
cd /usr/local/elixir/ ./script.sh list-tags
== 创建数据库
./update.py <线程数>
生成完整数据库可能需要很长时间:在 Xeon E3-1245 v5 上索引 Linux 内核的 1800 个标签大约需要 15 小时。因此,您可能想要调整脚本(例如,通过使用 "head" 限制标签数量)以测试更新和查询命令。您甚至可以创建一个新的 Git 仓库,只创建一个标签,而不使用非常大的官方内核仓库。
== 第二次测试
验证查询是否有效:
$ ./query.py v4.10 ident raw_spin_unlock_irq C $ ./query.py v4.10 file /kernel/sched/clock.c
注意: v4.10
可以替换为任何其他标签。
== 配置 httpd
CGI 接口 (web.py
) 旨在由您的 Web 服务器调用。由于它包含对多个项目进行索引的支持,它需要一个不同的变量 (LXR_PROJ_DIR
),该变量指向具有特定结构的目录:
<LXR_PROJ_DIR>
**<项目 1>
***data
***repo
**<项目 2>
***data
***repo
**<项目 3>
***data
***repo
然后它将在调用查询命令时生成其他两个变量。
现在打开 /etc/httpd/conf.d/elixir.conf
并写入以下内容。
注意:如果使用 apache2 (Ubuntu/Debian) 而不是 httpd (RedHat/Centos),
默认配置文件为: /etc/apache2/sites-enabled/000-default.conf
HTTP 所需
<Directory /usr/local/elixir/http/> Options +ExecCGI AllowOverride None Require all granted SetEnv PYTHONIOENCODING utf-8 SetEnv LXR_PROJ_DIR /path/elixir-data
REST API 所需
<Directory /usr/local/elixir/api/> SetHandler wsgi-script Require all granted SetEnv PYTHONIOENCODING utf-8 SetEnv LXR_PROJ_DIR /path/elixir-data
AddHandler cgi-script .py #Listen 80 <VirtualHost *:80> ServerName xxx DocumentRoot /usr/local/elixir/http
# 要在安装 mod_wsgi 后启用 REST api:填写路径并取消注释:
#WSGIScriptAlias /api /usr/local/elixir/api/api.py
AllowEncodedSlashes On
RewriteEngine on RewriteRule "^/$" "/linux/latest/source" [R] RewriteRule "^/(?!api|acp).*/(source|ident|search)" "/web.py" [PT] RewriteRule "^/acp" "/autocomplete.py" [PT]
RHEL/CentOS 默认已启用 cgi 和 rewrite 支持,但如果你使用的是 Debian/Ubuntu 发行版,则需要手动启用。
a2enmod cgi rewrite
最后,启动 httpd 服务器。
systemctl start httpd
== 配置 SELinux 策略
当使用启用了 SELinux 的 systemd 时,httpd 服务器只能访问有限的目录。 如果你的 /path/elixir-data/ 不在这些允许的目录中,你将收到 500 状态码的响应。
要允许 httpd 服务器访问 /path/elixir-data/,请运行以下命令:
chcon -R -t httpd_sys_rw_content_t /path/elixir-data/
要检查是否生效,请运行以下命令:
ls -Z /path/elixir-data/
如果你想查看与 httpd 相关的 SELinux 日志,请运行以下命令:
audit2why -a | grep httpd | less
== 配置 systemd 日志目录
默认情况下,elixir 的错误日志会放在 /tmp/elixir-errors。 然而,systemd 默认启用 PrivateTmp。 因此,最终的错误目录将类似于 /tmp/systemd-private-xxxxx-httpd.service-xxxx/tmp/elixir-errors。 如果你想禁用它,请使用以下属性配置 httpd.service:
PrivateTmp=false
== 配置 lighttpd
以下是 lighttpd 的示例配置:
server.document-root = server_root + "/elixir/http" url.redirect = ( "^/$" => "/linux/latest/source" ) url.rewrite = ( "^/(?!api|acp).*/(source|ident|search)" => "/web.py/$1") url.rewrite = ( "^/acp" => "/autocomplete.py") setenv.add-environment = ( "PYTHONIOENCODING" => "utf-8", "LXR_PROJ_DIR" => "/path/to/elixir-data" )
= REST API 使用
配置 httpd 后,你可以测试 API 的使用:
== ident 查询
向 /api/ident/<Project>/<Ident>?version=<version>&family=<family>
发送 GET 请求。
例如:
curl http://127.0.0.1/api/ident/barebox/cdev?version=latest&family=C
响应体的结构如下:
{ "definitions": [{"path": "commands/loadb.c", "line": 71, "type": "variable"}, ...], "references": [{"path": "arch/arm/boards/cm-fx6/board.c", "line": "64,64,71,72,75", "type": null}, ...] }
= 维护和增强
== 使用缓存提高性能
在 Bootlin,我们使用 https://varnish-cache.org/[Varnish http 缓存] 作为前端,以减少运行 Elixir 代码的服务器负载。
.-------------. .---------------. .-----------------------. | Http 客户端 | --------> | Varnish 缓存 | --------> | 运行 Elixir 的 Apache | '-------------' '---------------' '-----------------------'
== 保持 Elixir 数据库更新
为了保持 Elixir 数据库的更新并索引新发布的版本,
我们建议使用类似 utils/update-elixir-data
的脚本,通过每日 cron 任务调用。
你可以设置 $ELIXIR_THREADS
来更改 update.py 用于索引的线程数(默认为系统的 CPU 数量)。
== 控制 git 仓库的磁盘使用
随着不断更新 git 仓库,你可能会注意到某些仓库比原来大得多。这似乎发生在大型仓库中出现 gc.log
文件时,
导致 git 的垃圾收集器(git gc
)失败,因此每次获取新对象时都会快速消耗磁盘空间。
当这种情况发生时,你可以通过打包 git 目录来节省磁盘空间,方法如下:
cd
git prune
rm gc.log
git gc --aggressive
实际上,再次运行上述命令可以节省更多空间。
要在循环中处理多个 git 仓库,你可以使用我们提供的 utils/pack-repositories
脚本,
在所有仓库所在的目录中运行。
= 构建 Docker 镜像
Dockerfile 文件位于 docker/
目录中。要构建镜像,请运行以下命令:
git clone https://github.com/bootlin/elixir.git ./elixir
docker build -t elixir -f ./elixir/docker/Dockerfile ./elixir
然后你可以使用 docker run
运行镜像。
建议将卷或主机目录挂载到 Elixir 数据目录。
这样可以轻松替换容器为新版本,而不会丢失索引数据。
mkdir ./elixir-data
docker run -v ./elixir-data/:/srv/elixir-data -d --name elixir-container elixir
Docker 镜像不包含任何仓库。要索引仓库,你可以使用实用脚本 index-repository。 例如,要添加 musl 仓库,请运行:
docker exec -it -e PYTHONUNBUFFERED=1 elixir-container /usr/local/elixir/utils/index-repository musl https://git.musl-libc.org/git/musl
没有 PYTHONUNBUFFERED 环境变量,更新日志可能会延迟显示。
或者,在单独的容器中运行索引:
docker run -e PYTHONUNBUFFERED=1 -v ./elixir-data/:/srv/elixir-data --entrypoint /usr/local/elixir/utils/index-repository elixir musl https://git.musl-libc.org/git/musl
你也可以使用 utils/index-all-repositories 开始索引所有官方支持的仓库。
索引完成后,Elixir 应该可以在主机上通过以下 URL 访问: http://172.17.0.2/musl/latest/source
如果 172.17.0.2 无响应,你可以通过运行以下命令检查容器的 IP 地址:
docker inspect elixir-container | grep IPAddress
== 自动仓库更新
Docker 镜像本身不会自动更新仓库。
你可以,例如,在容器中启动 utils/update-elixir-data
(或在单独的容器中,挂载 Elixir 数据卷/目录)
通过主机上的 cron 定期更新仓库。
== 将 Docker 镜像用作开发服务器
你可以通过按照上述步骤轻松将 Docker 镜像用作开发服务器,但在运行 docker run elixir
时,
将主机上的 Elixir 源目录挂载到容器中的 /usr/local/elixir/
。
在主机上对代码所做的更改应该自动反映在容器中。
你可以使用 apache2ctl
重启 Apache。
错误日志可在容器内的 /var/log/apache2/error.log
和 /tmp/elixir-errors
中找到。
= 硬件要求
性能要求主要取决于 Elixir 服务的流量。但是,快速的服务器对于项目的初始索引也有帮助。
强烈推荐使用 SSD 存储,因为需要频繁访问 git 仓库。
在 Bootlin,以下是我们使用的服务器的一些详细信息:
- 截至 2019 年 7 月,我们的 Elixir 服务消耗 17 GB 数据(支持所有项目), 仅对于 Linux 内核(最新版本为 5.2),索引数据占用 12 GB, git 仓库占用 2 GB。
- 我们在一个云服务器上使用 8 GB RAM 的 LXD 实例,该服务器有 8 个 CPU 核心, 运行频率为 3.1 GHz。
= 为 Elixir 做贡献
== 支持新项目 Elixir具有非常简单的模块化架构,只需在Elixir源码中添加一个新文件即可支持新的源代码项目。
Elixir的假设:
- 项目源码必须存在于git仓库中
- 所有项目发布都与特定的git标签关联。Elixir只考虑这些标签。
首先按照上述说明安装Elixir。查看projects
子目录了解已支持的项目。
当Elixir至少能运行一个项目后,就可以克隆你想支持的项目的git仓库:
cd /srv/git git clone --bare https://github.com/zephyrproject-rtos/zephyr
完成后,你也可以引用和获取该项目的远程分支,例如对应Linux内核的stable
树(参见本文档前面关于Linux的说明)。
现在,在你的LXR_PROJ_DIR
目录中,为新项目创建一个新目录:
cd $LXR_PROJ_DIR mkdir -p zephyr/data ln -s /srv/git/zephyr.git repo export LXR_DATA_DIR=$LXR_PROJ_DIR/data export LXR_REPO_DIR=$LXR_PROJ_DIR/repo
然后,返回Elixir源码并测试标签是否正确提取:
./script.sh list-tags
根据你想在Elixir页面上展示可用版本的方式,你可能需要对每个标签字符串进行替换,例如添加缺失的v
前缀,以与其他项目版本的显示方式保持一致。你也可以决定忽略特定标签。这些都可以通过在新的projects/<projectname>.sh
文件中重新定义默认的list_tags()
函数来实现。下面是一个例子(projects/zephyr.sh
文件):
list_tags() { echo "$tags" | grep -v '^zephyr-v' }
注意<project_name>
必须与你在LXR_PROJ_DIR
下创建的目录名称匹配。
下一步是确保版本在版本菜单中按照你的意愿分类。这个分类工作是通过list_tags_h()
函数完成的,该函数生成./scripts.sh list-tags -h
命令的输出。以下是Linux项目的输出示例:
v4 v4.16 v4.16 v4 v4.16 v4.16-rc7 v4 v4.16 v4.16-rc6 v4 v4.16 v4.16-rc5 v4 v4.16 v4.16-rc4 v4 v4.16 v4.16-rc3 v4 v4.16 v4.16-rc2 v4 v4.16 v4.16-rc1 ...
第一列是版本的顶级菜单项。 第二列是下一级菜单项, 第三列是可以在菜单中选择的实际版本。 注意,这第三项必须与git中标签的确切名称相对应。
如果默认行为不符合你的需求,你需要自定义list_tags_h
函数。
你还应确保Elixir正确识别最新版本:
./script.sh get-latest
如有必要,自定义get_latest()
函数。
如果你想在设备树文件中启用对compatible
属性的支持,在projects/<projectname>.sh
的开头添加dts_comp_support=1
。
现在你可以为新项目生成Elixir的数据库:
./update.py <线程数>
然后你可以通过HTTP服务器检查Elixir是否正常工作。
== 编码风格
如果你希望为Elixir的Python代码做出贡献,请遵循Python的官方编码风格。
== 如何发送补丁
与我们分享贡献的最佳方式是在GitHub上提交拉取请求。
= 自动化测试
Elixir在t/
目录中包含一个简单的测试套件。要运行它,
在Elixir的顶级目录中执行:
prove
测试套件使用t/tree
中提取的Linux v5.4代码。
== t/tree
中代码的许可
复制的代码按照Linux附带的COPYING文件中描述的方式许可。所有复制的文件都带有GPL-2.0+
或GPL-2.0-or-later
的SPDX许可标识符。根据GNU的兼容性表,GPL 2.0+代码可以在GPLv3下使用,前提是组合在GPLv3下。此外,GNU对AGPLv3的概述表明,其条款"实际上包含GPLv3的条款"加上网络使用段落。因此,开发者有理由相信将这些文件在AGPLv3下许可是授权的。(另见此问题评论,其中有类似情况的另一个例子。)
= 许可
Elixir版权所有 (c) 2017--2020 其贡献者。它基于AGPLv3许可。
详情请参阅Elixir附带的COPYING
文件。