ONLYOFFICE源码编译不完全指南

前言

ONLYOFFICE 是一个可以用于在线编辑和查看 office 文档的工具,有免费的社区版和付费版的,哪怕是免费的社区版其功能也比较完善了,且可以自行部署。

由于社区版的源码进行了开源,因此如果需要进行私有化的定制改动则可以基于源码进行编译。不过官方的社区版编译文档十分简陋,根本没办法按照其说明的编译步骤正常进行编译,很多坑

基于 Docker 容器进行编译

根据官方的 编译工具项目 (build_tools)文档可知,目前编译只能在 Linux 环境进行(官方配置使用的是 Ubuntu 操作系统)。所以理论上有以下途径可以进行编译:

  • 原生 Linux 环境
  • WSL
  • Docker 容器

我最后选择在 Windows 系统使用 Docker 容器进行编译,原因是官方提供了一个现成的编译环境的 Docker 配置(其实当时把 WSL 给忘了😅)。官方的编译环境 Docker 配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
FROM ubuntu:20.04

ENV TZ=Etc/UTC
ENV DEBIAN_FRONTEND=noninteractive

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN echo 'keyboard-configuration keyboard-configuration/layoutcode string us' | debconf-set-selections && \
echo 'keyboard-configuration keyboard-configuration/modelcode string pc105' | debconf-set-selections

RUN apt-get -y update && \
apt-get -y install tar \
sudo \
wget

RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.0/cmake-3.30.0-linux-x86_64.tar.gz && \
tar -xzf cmake-3.30.0-linux-x86_64.tar.gz -C /opt && \
ln -s /opt/cmake-3.30.0-linux-x86_64/bin/cmake /usr/local/bin/cmake && \
ln -s /opt/cmake-3.30.0-linux-x86_64/bin/ctest /usr/local/bin/ctest && \
rm cmake-3.30.0-linux-x86_64.tar.gz

ADD . /build_tools
WORKDIR /build_tools

RUN mkdir -p /opt/python3 && \
wget -P /opt/python3/ https://github.com/ONLYOFFICE-data/build_tools_data/raw/refs/heads/master/python/python3.tar.gz && \
tar -xzf /opt/python3/python3.tar.gz -C /opt/python3 --strip-components=1

ENV PATH="/opt/python3/bin:${PATH}"

RUN ln -s /opt/python3/bin/python3.10 /usr/bin/python

CMD ["sh", "-c", "cd tools/linux && python3 ./automate.py"]

理论上可以基于这份 Docker 配置构建一个专门的编译环境容器,然后在该容器内进行编译。如果这份 Docker 配置可以直接用也就不会有这篇文章了😂

nodejs 版本的兼容性

不出意外的话,如果你按照官方文档写的步骤执行容器内编译时,首先会碰到下面这个报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
/usr/bin/env: ‘python\r’: No such file or directory
Error (./configure.py): 127
install dependencies...
Installed Node.js version: 10.19
Node.js version cannot be less 16
Reinstall
install qt...
---------------------------------------------
build branch: master
---------------------------------------------
---------------------------------------------
build modules: desktop builder server
---------------------------------------------

因为 Ubuntu20.04 内置的 nodejs 版本是 10.x,这个版本很低,所以需要在 Dockerfile 中手动安装≥16版本的 nodejs:

1
2
3
4
5
# 安装 Node.js 20.x
RUN curl -fsSL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh && \
bash /tmp/nodesource_setup.sh && \
apt-get install -y nodejs && \
rm /tmp/nodesource_setup.sh

这里我安装了 20.x 这个 LTS 版本。

Ubuntu 版本的兼容性

一开始为了解决上面提到的 nodejs 版本过低问题,我想到了提升 Ubuntu 版本来解决这个问题,后面发现提升到 24.04 版本时会出现 C/C++ 编译相关的报错,因此可以认为当前 onlyoffice 源码编译并不兼容 Ubuntu24.04 系统。

源码的加载

由于 onlyoffice 实际上是由多个子项目组成的,而这些子项目每个都是单独的 git 项目,因此编译前需要先准备好这些源码。理论上可以把所有 git 项目先 clone 到本地,然后修改相应的源码进行编译,也可以直接使用编译脚本自带的更新功能进行实时更新源码进行编译。

局部截取_20260107_145630.png

本地源码挂载

一开始我想的是先把 onlyoffice 项目全部 clone 到本地,然后再把这些源码挂载到编译容器中相应的位置,避免修改容器配置重新构建时丢失所有数据然后重新下载源码。但我的开发环境(宿主机)是 Windows,把源码挂载到 Ubuntu 容器中会出现因为换行符不兼容而导致的编译失败问题(因为 Windows 系统默认换行符为 CRLF,而 Linux 系统则是 LF)。

同理,基于本地 build_tools 项目源码构建编译容器时,build_tools 中的源码也会出现因为宿主机和 Linux 容器换行符不一致而出现不能正常运行的问题,解决办法就是在 Dockerfile 中增加一个换行符转换的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# dos2unix就是进行换行符转换所需要的工具
RUN apt-get -y update && \
apt-get -y install tar \
sudo \
wget \
dos2unix \
curl \
gnupg \
ca-certificates \
lsb-release \
git


# 修复所有脚本文件的换行符(关键步骤!)
RUN find /onlyoffice/build_tools -type f \( -name "*.py" -o -name "*.sh" -o -name "configure.py" -o -name "make.py" \) -exec dos2unix {} \; 2>/dev/null || true

加快编译速度

官方基于容器进行编译的做法是,启动容器后执行一次编译命令(通过 CMD 指令)。如果你正处理频繁调试容器配置和编译参数的阶段时,使用这个编译流程就会变得十分浪费时间。因为每次调整配置后容器就会被重新创建,然后又得重新编译 C/C++ 相关依赖包(这个过程可能需要几十分钟)。

所以我构建了一个可以持久运行的编译容器,然后进入容器内部执行编译命令(跟 WSL 类似),这样频繁调整编译参数时就不需要重新构建编译容器了,而是可以在容器内反复编译。由于 C/C++ 相关依赖包在相同的容器实例内只需要编译一次,因此后续反复编译只会编译 nodejs 运行时二进制包和一些前端代码(这种编译就只需要几分钟到十几分钟),比起全量编译就会快很多。

1
2
# 容器启动时执行阻塞命令(保持运行)
CMD ["tail", "-f", "/dev/null"]

把容器启动命令改成上述命令就可以得到一个持久运行的编译容器了,之后通过 docker exec 命令进入容器执行相应命令即可。

编译镜像的完整 Docker 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/ubuntu:20.04

ENV TZ=Etc/UTC
ENV DEBIAN_FRONTEND=noninteractive

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN echo 'keyboard-configuration keyboard-configuration/layoutcode string us' | debconf-set-selections && \
echo 'keyboard-configuration keyboard-configuration/modelcode string pc105' | debconf-set-selections

# 替换为国内软件源加速 apt 安装
RUN sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list && \
sed -i 's@//.*security.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list

RUN apt-get -y update && \
apt-get -y install tar \
sudo \
wget \
dos2unix \
curl \
gnupg \
ca-certificates \
lsb-release \
git

RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.0/cmake-3.30.0-linux-x86_64.tar.gz && \
tar -xzf cmake-3.30.0-linux-x86_64.tar.gz -C /opt && \
ln -s /opt/cmake-3.30.0-linux-x86_64/bin/cmake /usr/local/bin/cmake && \
ln -s /opt/cmake-3.30.0-linux-x86_64/bin/ctest /usr/local/bin/ctest && \
rm cmake-3.30.0-linux-x86_64.tar.gz

# 先复制文件
ADD . /onlyoffice/build_tools
WORKDIR /onlyoffice/build_tools

# 修复所有脚本文件的换行符(关键步骤!)
RUN find /onlyoffice/build_tools -type f \( -name "*.py" -o -name "*.sh" -o -name "configure.py" -o -name "make.py" \) -exec dos2unix {} \; 2>/dev/null || true

# 将 build_tools 的 git origin 改为 HTTPS,避免 SSH 密钥问题
RUN cd /onlyoffice/build_tools && \
git remote set-url origin https://github.com/ONLYOFFICE/build_tools.git

RUN mkdir -p /opt/python3 && \
wget -P /opt/python3/ https://github.com/ONLYOFFICE-data/build_tools_data/raw/refs/heads/master/python/python3.tar.gz && \
tar -xzf /opt/python3/python3.tar.gz -C /opt/python3 --strip-components=1

ENV PATH="/opt/python3/bin:${PATH}"

RUN ln -s /opt/python3/bin/python3.10 /usr/bin/python

# 安装 Node.js 20.x
RUN curl -fsSL https://deb.nodesource.com/setup_20.x -o /tmp/nodesource_setup.sh && \
bash /tmp/nodesource_setup.sh && \
apt-get install -y nodejs && \
rm /tmp/nodesource_setup.sh

# 配置 npm 使用国内镜像并安装全局包
RUN npm config set registry https://registry.npmmirror.com && \
npm install -g grunt-cli

# CMD ["sh", "-c", "cd tools/linux && python3 ./automate.py server --update=0 --branch=release/v9.1.0 --git-protocol=https"]
# 容器启动时执行阻塞命令(保持运行)
CMD ["tail", "-f", "/dev/null"]

这个 Docker 配置除了对上面提及的问题进行了修复,也对基础镜像和包的安装加了国内代理(避免国内构建镜像很慢),如果代理不能用了可以自行换成能用的代理地址。不过需要注意的是,这个编译容器的环境我只在 onlyoffice 9.1.0 这个版本成功构建过源码,不保证其它版本的源码也可以正常编译😂。

构建产物的部分替换与全量替换

理论上我们完成 onlyoffice 项目的完整编译后,可以参考官方的 Docker应用构建配置 构建完整的 onlyoffice Docker 应用。但是我发现编译后的产物与官方 Docker 实例内部的文件不完全一致,有些文件缺失,而有些文件则是容器内不存在的文件的;具体文件的 diff 到底有多少我也没有进行详细对比,因为文件实在是太多了😂。

为了避免自行编译的 Docker 应用无法正常启动(或引入其它未知运行时错误),我想到了一种办法——即直接把 onlyoffice Docker 镜像作为基础镜像,然后在镜像中替换修改过的源码的编译产物。这样就可以最大限度保证整个 Docker 应用的可靠性,因为只会替换官方镜像中极少的文件,不至于影响范围很广,后续出现问题也可以很快定位到是哪部分出现问题。

对我而言,我只会修改 server 这个子项目中的源码,该子项目编译出的产物如下图红框所示:

局部截取_20260105_174059.png

server 文件夹中核心的几个文件就是基于 pkg 编译得到的包含 nodejs 运行时的二进制模块文件,而我修改的源码正好就是要修改这几个二进制文件的一些逻辑,且经测试编译后这几个二进制文件可以正常进行替换运行;下面就是我进行部分文件替换构建 Docker 应用的配置:

1
2
3
4
5
6
7
FROM onlyoffice-documentserver:9.1.0

# 复制DocService
COPY ./DocService/ /var/www/onlyoffice/documentserver/server/DocService/

# 复制FileConverter
COPY ./FileConverter/converter /var/www/onlyoffice/documentserver/server/FileConverter/converter

总的来说,部分替换官方镜像的文件是更为可靠的做法,因为从 0 构建一个完整的 onlyoffice 应用镜像所需要进行的测试和校验工作实在太大了。

编译参数

编译是通过 build_tools/tools/linux/automate.py 这个脚本文件进行启动的:

局部截取_20260107_144828.png

命令格式如下:

1
./automate.py <appname> --option1=v1 ... --optionN=vN

其中 appname 就是要编译的应用:

常用的编译参数有:

  • update:是否在编译时自动更新所有项目的 git 源码(0 为不自动更新,1 为自动更新);我只会在第一次编译使用自动更新来拉取所有子项目源码,后续都设置为 0 来固定源码,便于本地修改
  • branch:指定编译时所有项目的 git 分支或者tag,如果某个项目不存在该分支则会退回到主分支,所以最好确保这个分支在所有项目中都存在
  • git-protocol:指定拉取 git 代码时,使用的网络协议;默认会自动使用 build_tools 项目使用的协议,建议使用 https 协议(因为使用 ssh 协议还要加上秘钥)
  • clean:是否重新编译所有的部分(开启后还会重新编译 C/C++ 相关包,即彻底重新编译,花费的时间会更长)

完整可用的编译参数可以查看 build_tools 项目的 configure.py文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import os
import sys
import optparse

arguments = sys.argv[1:]

parser = optparse.OptionParser()
parser.add_option("--update", action="store", type="string", dest="update", default="1", help="defines whether it's necessary to update/clone repos. If it's set to true (1 === true), build_tools automatically get the necessary subrepos. If it's set to false (0 === false), you should define which ones to use")
parser.add_option("--update-light", action="store", type="string", dest="update-light", default="", help="performs pull/clone without switching branches, can be used only if update is true.")
parser.add_option("--branch", action="store", type="string", dest="branch", default="master", help="branch/tag name, used only if update is true and update_light is not used. Updates/clones all the repos and switches the branch to the proper one deleting all the local changes")
parser.add_option("--clean", action="store", type="string", dest="clean", default="1", help="defines whether to build everything anew")
parser.add_option("--module", action="store", type="string", dest="module", default="builder", help="defines what modules to build. You can specify several of them, e.g. --module 'core desktop builder server mobile'")
parser.add_option("--develop", action="store", type="string", dest="develop", default="0", help="defines develop mode")
parser.add_option("--beta", action="store", type="string", dest="beta", default="0", help="defines beta mode")
parser.add_option("--platform", action="store", type="string", dest="platform", default="native", help="defines the destination platform for your build ['win_64', 'win_32', 'win_64_xp', 'win_32_xp', 'win_arm64', 'linux_64', 'linux_32', 'mac_64', 'ios', 'android_arm64_v8a', 'android_armv7', 'android_x86', 'android_x86_64'; combinations: 'native': your current system (windows/linux/mac only); 'all': all available systems; 'windows': win_64 win_32 win_64_xp win_32_xp; 'linux': linux_64 linux_32; 'mac': mac_64; 'android': android_arm64_v8a android_armv7 android_x86 android_x86_64]")
parser.add_option("--config", action="store", type="string", dest="config", default="", help="provides ability to specify additional parameters for qmake")
parser.add_option("--qt-dir", action="store", type="string", dest="qt-dir", default="", help="defines qmake directory path. qmake can be found in qt-dir/compiler/bin directory")
parser.add_option("--qt-dir-xp", action="store", type="string", dest="qt-dir-xp", default="", help="defines qmake directory path for Windows XP. qmake can be found in 'qt-dir/compiler/bin directory")
parser.add_option("--external-folder", action="store", type="string", dest="external-folder", default="", help="defines a directory with external folder")
parser.add_option("--sql-type", action="store", type="string", dest="sql-type", default="postgres", help="defines the sql type wich will be used")
parser.add_option("--db-port", action="store", type="string", dest="db-port", default="5432", help="defines the sql db-port wich will be used")
parser.add_option("--db-name", action="store", type="string", dest="db-name", default="onlyoffice", help="defines the sql db-name wich will be used")
parser.add_option("--db-user", action="store", type="string", dest="db-user", default="onlyoffice", help="defines the sql db-user wich will be used")
parser.add_option("--db-pass", action="store", type="string", dest="db-pass", default="onlyoffice", help="defines the sql db-pass wich will be used")
parser.add_option("--compiler", action="store", type="string", dest="compiler", default="", help="defines compiler name. It is not recommended to use it as it's defined automatically (msvc2015, msvc2015_64, gcc, gcc_64, clang, clang_64, etc)")
parser.add_option("--no-apps", action="store", type="string", dest="no-apps", default="0", help="disables building desktop apps that use qt")
parser.add_option("--themesparams", action="store", type="string", dest="themesparams", default="", help="provides settings for generating presentation themes thumbnails")
parser.add_option("--git-protocol", action="store", type="string", dest="git-protocol", default="auto", help="can be used only if update is set to true - 'https', 'ssh'")
parser.add_option("--branding", action="store", type="string", dest="branding", default="", help="provides branding path")
parser.add_option("--branding-name", action="store", type="string", dest="branding-name", default="", help="provides branding name")
parser.add_option("--branding-url", action="store", type="string", dest="branding-url", default="", help="provides branding url")
parser.add_option("--sdkjs-addon", action="append", type="string", dest="sdkjs-addons", default=[], help="provides sdkjs addons")
parser.add_option("--sdkjs-addon-desktop", action="append", type="string", dest="sdkjs-addons-desktop", default=[], help="provides sdkjs addons for desktop")
parser.add_option("--server-addon", action="append", type="string", dest="server-addons", default=[], help="provides server addons")
parser.add_option("--web-apps-addon", action="append", type="string", dest="web-apps-addons", default=[], help="provides web-apps addons")
parser.add_option("--sdkjs-plugin", action="append", type="string", dest="sdkjs-plugin", default=["default"], help="provides plugins for server-based and desktop versions of the editors")
parser.add_option("--sdkjs-plugin-server", action="append", type="string", dest="sdkjs-plugin-server", default=["default"], help="provides plugins for server-based version of the editors")
parser.add_option("--features", action="store", type="string", dest="features", default="", help="native features (config addon)")
parser.add_option("--vs-version", action="store", type="string", dest="vs-version", default="2015", help="version of visual studio")
parser.add_option("--vs-path", action="store", type="string", dest="vs-path", default="", help="path to vcvarsall")
parser.add_option("--siteUrl", action="store", type="string", dest="siteUrl", default="127.0.0.1", help="site url")
parser.add_option("--multiprocess", action="store", type="string", dest="multiprocess", default="1", help="provides ability to specify single process for make")
parser.add_option("--sysroot", action="store", type="string", dest="sysroot", default="0", help="provides ability to use sysroot (ubuntu 16.04) to build c++ code. If value is \"1\", then the sysroot from tools/linux/sysroot will be used, and if it is not there, it will download it and unpack it. You can also set value as the path to the your own sysroot (rarely used). Only for linux")