前言
LibreOffice 是一套开源且免费的办公软件,类似于 Microsoft Office 和 Apache OpenOffice。它包括文字处理、电子表格、演示文稿、绘图、数据库等功能模块。 LibreOffice可执行于各种系统平台,包括Microsoft Windows、MacOS及GNU/Linux,LibreOffice 也提供了 Android 平台的版本,支持Office文档的浏览和少量的编辑功能。本文介绍在Ubuntu下编译LibreOffice 的Android 版本,生成apk及so文件。
本文所用编译环境:
- Ubuntu 22.04.3 LTS, 64Bit
- gcc/g++ 12.3.0,LibreOffice 要求版本12或以上
- jdk 17.0.10,根据Gradle版本而定
- ndk 25.2.9519653,LibreOffice 要求版本23~25
1 下载
1.1 克隆源代码
在LibreOffice中文社区源码介绍页面可以获取到仓库地址,除了官方仓库外,也提供了国内镜像。 使用git clone
命令将源码下载下来,源码非常大,大概有4G,如果下载不来下,参考下一节的常见错误解决
。
bash
# 克隆LibreOffice源码,包括子模块;只克隆最新提交以减少下载内容
git clone --recurse-submodules --depth=1 https://git.libreoffice.org/core
1.2 安装jdk
bash
# 查看jdk 版本
java --version
# 安装jdk
sudo apt install openjdk-17-jdk
# 查看jdk安装路径
update-alternatives --display java
1.3 安装Android sdk及NDK
官方建议通过下载Android studio 进行安装,打开Ubuntu应用商店通过图形界面安装并打开Android studio,然后选择SDKManager下载Android sdk和ndk。
1.4 安装c++环境
c/c++的编译环境可以通过build-essential软件包安装,它包含了g++, gcc, make, dpkg-dev,libc6-dev等 构建编译 C/C++ 项目必要的编译器、构建工具和常用的库文件。 不过由于这里build-essential默认安装的编译器版本只有11,而LibreOffice要求最低12,因此这里选择手动安装。sudo apt install build-essential
bash
# 手动安装gcc 12,g++ 12, make
sudo apt install gcc-12 g++-12 make
# 使用 update-alternatives 命令将gcc12,g++12设置为默认版本
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 --slave /usr/bin/g++ g++ /usr/bin/g++-12
1.5 安装其他依赖
输入以下命令,安装编译LibreOffice需要用到的一些工具和库。执行编译环境检查时报错缺什么工具就补上。
bash
sudo apt install autoconf pkg-config libfontconfig1-dev gperf python3-dev bison flex ant gettext nasm
这些工具的主要功能:
- autoconf:自动配置工具,用于生成软件包的配置脚本。
- pkg-config:用于检查系统中安装的库的版本和路径等信息。
- libfontconfig1-dev:Fontconfig库的开发文件,用于构建依赖于Fontconfig的软件。
- gperf:GNU gperf工具,用于生成完美哈希函数。
- python3-dev:Python 3的开发文件,包括头文件和静态库,用于编译依赖Python的软件。
- bison:用于生成解析器的工具。
- flex:用于生成词法分析器的工具。
- ant:Java项目构建工具。
- gettext:用于国际化的工具,包括翻译字符串的工具和库。
2 编译
2.1 配置编译选项
编译过程可以在make
命令加上编译参数,也可以通过autogen.input
文件来设置编译选项,如不指定则按照默认的编译选项进行。autogen.input
文件仅在不存在任何命令行参数时才会生效,完整的编译选项及含义见源代码根目录下的configure文件。
在项目根目录(源码根目录)使用touch
命令新建一个配置文件,
bash
touch autogen.input
添加以下内容,路径相关选项请根据实际情况修改。
ini
# 指定构建的发行版,本例是构建Android平台(armeabi-v7a),因此选择LibreOfficeAndroid
# 更多预设配置参考源码目录/distro-configs文件夹
--with-distro=LibreOfficeAndroid
# 指定 Android SDK 的路径
--with-android-sdk=/home/zhg/Android/Sdk
# 指定 Android NDK 的路径
--with-android-ndk=/home/zhg/Android/Sdk/ndk/25.2.9519653
# 指定 JDK 的安装路径,可通过`update-alternatives --display java`命令查看
--with-jdk-home=/usr/lib/jvm/java-17-openjdk-amd64
# 启用Android上的实验性编辑功能
--enable-android-editing
# 启用构建办公开发工具包(Office Development Kit),用于扩展和定制功能
--enable-odk
# 设置构建平台的 configure 选项,禁用系统 libxml,使用 LibreOffice 提供的 libxml 库
--with-build-platform-configure-options=--without-system-libxml
# 指定LibreOffice构建过程中需要使用的外部依赖项或源代码的路径
--with-external-tar=/home/zhg/Workplace/LibreOffice/external_tar
# 启用简体及繁体中文用户界面
--with-lang=zh-CN zh-TW
2.2 检查编译环境
在项目根目录下运行autogen.sh脚本,这将会执行检查构建环境、读取autogen.input选项并生成配置脚本。
bash
./autogen.sh
当输出以下内容表示autogen.sh运行无错误,可以进行下一步。
bash
To show information on various make targets and make flags, run:
/usr/bin/make help
To just build, run:
/usr/bin/make
2.3 开始构建
输入make命令开始构建,编译耗时取决于下载软件包的速度及电脑配置。
bash
make
当输出BUILD SUCCESSFUL
表明编译成功。
bash
BUILD SUCCESSFUL in 39m 3s
46 actionable tasks: 46 executed
[CUS] android/loandroid3
[BIN] android
[MOD] android
[MOD] libreoffice
[BIN] top level modules: libreoffice
[ALL] top level modules: build-non-l10n-only build-l10n-only
构建完成的Android so文件在项目目录/android/jniLibs/armeabi-v7a
目录下。
css
├── libc++_shared.so
├── libfreebl3.so
├── liblo-native-code.so
├── libnspr4.so
├── libnss3.so
├── libnssckbi.so
├── libnssdbm3.so
├── libnssutil3.so
├── libplc4.so
├── libplds4.so
├── libsmime3.so
├── libsoftokn3.so
├── libsqlite3.so
└── libssl3.so
2.4 打包apk
LibreOffice 提供了 Android 的示例项目,其主要源码和gradle配置文件位于/android/source
,可以导入到Android studio中进行开发和编译,也可以直接使用gradle命令打包出apk。
定位到android/source
文件夹,输入以下命令:
bash
# 编译并打Debug包
./gradlew assembleDebug
当输出以下字样表示构建完成,apk位于source/build/outputs/apk/<xxFlavor>/debug
下。
bash
BUILD SUCCESSFUL in 1m 53s
101 actionable tasks: 64 executed, 37 up-to-date
2.5 导入Android Studio
由于/android/source
下的Android项目还依赖的LibreOffic其他目录的源码,这些都是由build.gradle
中的任务来控制的,主要是生成Android端的配置和复制资源文件,在Ubuntu下使用./gradlew build
构建完生成相应的文件以后,并修改 gradle sourceSets
相关路径,即可去掉这些任务,从而将Android 项目从LibreOffice 源码中独立出来,导入到Android Studio。这里还修改了一下目录位置使其更符合Android项目的默认组织结构,源码已上传Github。
groovy
//以下是这些任务的说明:
//从${liboInstdir}/${liboEtcFolder}/types、${liboInstdir}/share/fonts/truetype目录中
// 复制程序文件和字体文件到assets/unpack目录
task copyUnpackAssets(type: Copy) {
description "copies assets that need to be extracted on the device"
into 'assets/unpack'
into('program') {
from("${liboInstdir}/${liboEtcFolder}/types") {
includes = [
"offapi.rdb",
"oovbaapi.rdb"
]
}
from("${liboInstdir}/${liboUreMiscFolder}") {
includes = ["types.rdb"]
rename 'types.rdb', 'udkapi.rdb'
}
}
into('user/fonts') {
from "${liboInstdir}/share/fonts/truetype"
// Note: restrict list of fonts due to size considerations - no technical reason anymore
// ToDo: fonts would be good candidate for using Expansion Files instead
includes = [
"Liberation*.ttf",
"Caladea-*.ttf",
"Carlito-*.ttf",
"Gen*.ttf",
"opens___.ttf"
]
}
into('etc/fonts') {
from "./"
includes = ['fonts.conf']
filter {
String line ->
line.replaceAll(
'@@APPLICATION_ID@@', new String("${android.defaultConfig.applicationId}")
)
}
}
}
//将${liboInstdir}/share/config、${liboInstdir}/program、${liboInstdir}/share目录中的
// 各种资源文件复制到应用的 assets 目录中,以便在安装后可以访问这些资源
task copyAssets(type: Copy) {
description "copies assets that can be accessed within the installed apk"
into 'assets'
// include icons, Impress styles and required .ui files
into ('share') {
into ('config') {
from ("${liboInstdir}/share/config")
includes = ['images_**.zip',
'**/simpress/**.xml',
'**/annotation.ui',
'**/hfmenubutton.ui',
'**/inforeadonlydialog.ui',
'**/pbmenubutton.ui',
'**/scrollbars.ui',
'**/tabbuttons.ui',
'**/tabviewbar.ui'
]
}
}
into('program') {
from "${liboInstdir}/program"
includes = ['services.rdb', 'services/services.rdb']
into('resource') {
from "${liboInstdir}/${liboSharedResFolder}"
includes = ['*en-US.res']
}
}
into('share') {
from("${liboInstdir}/share") {
// Filter data is needed by e.g. the drawingML preset shape import.
includes = ['registry/**', 'filter/**']
// those two get processed by mobile-config.py
excludes = ['registry/main.xcd', 'registry/res/registry_en-US.xcd']
}
// separate data files for Chinese and Japanese
from("${liboWorkdir}/CustomTarget/i18npool/breakiterator/") {
include '*.data'
}
}
}
//将 LICENSE 和 NOTICE 文件从 ${liboInstdir} 复制到 res_generated/raw 目录,并将它们重命名为 license.txt 和 notice.txt
task copyAppResources(type: Copy) {
description "copies documents to make them available as app resources"
into 'res_generated/raw'
from("${liboInstdir}") {
includes = ["LICENSE", "NOTICE"]
rename "LICENSE", "license.txt"
rename "NOTICE", "notice.txt"
}
}
//将 soffice.cfg 文件从 ${liboInstdir}/share/config/ 复制到 assets_fullUI/share/config/ 目录下
task createFullConfig(type: Copy) {
// grab dir to clear whole hierarchy on clean target
outputs.dir "assets_fullUI"
into 'assets_fullUI/share/config/soffice.cfg'
from "${liboInstdir}/share/config/soffice.cfg"
}
//创建输出目录和文件,以便将处理后的文件和配置保存到正确的位置
task createStrippedConfig {
def preserveDir = file("assets_strippedUI/share/config/soffice.cfg/empty")
outputs.dir "assets_strippedUI"
outputs.dir "assets_strippedUI/share/registry/res"
outputs.file preserveDir
doLast {
file('assets_strippedUI/share/registry/res').mkdirs()
file("assets_strippedUI/share/config/soffice.cfg").mkdirs()
// just empty file
preserveDir.text = ""
}
}
//执行外部的 Python 脚本 mobile-config.py 来处理main.xcd 配置文件,
// 从而生成一个精简版本的配置文件,输出到assets_strippedUI/share/registry/main.xcd
task createStrippedConfigMain(type: Exec) {
dependsOn 'createStrippedConfig'
inputs.files "${liboInstdir}/share/registry/main.xcd", "${liboSrcRoot}/android/mobile-config.py"
outputs.file "assets_strippedUI/share/registry/main.xcd"
executable "${liboSrcRoot}/android/mobile-config.py"
args = ["${liboInstdir}/share/registry/main.xcd", "assets_strippedUI/share/registry/main.xcd"]
}
//执行外部的 Python 脚本 mobile-config.py 来处理registry_en-US.xcd 配置文件,
// 从而生成一个精简版本的配置文件,输出到assets_strippedUI/share/registry/res/registry_en-US.xcd
task createStrippedConfigRegistry(type: Exec) {
dependsOn 'createStrippedConfig'
inputs.files "${liboInstdir}/share/registry/res/registry_en-US.xcd", "${liboSrcRoot}/android/mobile-config.py"
outputs.file "assets_strippedUI/share/registry/res/registry_en-US.xcd"
executable "${liboSrcRoot}/android/mobile-config.py"
args = ["${liboInstdir}/share/registry/res/registry_en-US.xcd", "assets_strippedUI/share/registry/res/registry_en-US.xcd"]
doFirst {
file('assets_strippedUI/share/registry/res').mkdirs()
}
}
//根据 liboSettings.gradle 文件,生成sofficerc, fundamentalrc, unorc, bootstraprc, versionrc等配置文件
task createRCfiles {
inputs.file "liboSettings.gradle"
dependsOn copyUnpackAssets, copyAssets
def sofficerc = file('assets/unpack/program/sofficerc')
def fundamentalrc = file('assets/program/fundamentalrc')
def bootstraprc = file('assets/program/bootstraprc')
def unorc = file('assets/program/unorc')
def versionrc = file('assets/program/versionrc')
outputs.files sofficerc, fundamentalrc, unorc, bootstraprc, versionrc
doLast {
sofficerc.text = '''\
[Bootstrap]
Logo=1
NativeProgress=1
URE_BOOTSTRAP=file:///assets/program/fundamentalrc
HOME=$APP_DATA_DIR/cache
OSL_SOCKET_PATH=$APP_DATA_DIR/cache
'''.stripIndent()
fundamentalrc.text = '''\
[Bootstrap]
LO_LIB_DIR=file://$APP_DATA_DIR/lib/
BRAND_BASE_DIR=file:///assets
BRAND_SHARE_SUBDIR=share
CONFIGURATION_LAYERS=xcsxcu:${BRAND_BASE_DIR}/share/registry res:${BRAND_BASE_DIR}/share/registry
URE_BIN_DIR=file:///assets/ure/bin/dir/nothing-here/we-can/exec-anyway
'''.stripIndent()
bootstraprc.text = '''\
[Bootstrap]
InstallMode=<installmode>
ProductKey=LibreOffice '''+ "${liboVersionMajor}.${liboVersionMinor}" + '''
UserInstallation=file://$APP_DATA_DIR
'''.stripIndent()
unorc.text = '''\
[Bootstrap]
URE_INTERNAL_LIB_DIR=file://$APP_DATA_DIR/lib/
UNO_TYPES=file://$APP_DATA_DIR/program/udkapi.rdb file://$APP_DATA_DIR/program/offapi.rdb file://$APP_DATA_DIR/program/oovbaapi.rdb
UNO_SERVICES=file:///assets/program/services.rdb file:///assets/program/services/services.rdb
'''.stripIndent()
versionrc.text = '''\
[Version]
AllLanguages=en-US
buildid=''' + "${liboGitFullCommit}" + '''
ReferenceOOoMajorMinor=4.1
'''.stripIndent()
}
}
3 常见错误解决
3.1 Git clone错误
bash
error: RPC 失败。curl 56 GnuTLS recv error (-9): Error decoding the received TLS packet.
error: 预期仍然需要 7923 个字节的正文
fetch-pack: unexpected disconnect while reading sideband packet
fatal: 过早的文件结束符(EOF)
fatal: fetch-pack:无效的 index-pack 输出
解决方法:切换到 SSH 仓库地址或者增加git发送数据的缓冲区大小
bash
# 设置 Git 全局配置中的 http.postBuffer 参数
git config --global http.postBuffer 1024M
3.2 文件权限相关
bash
mkdir: cannot create directory '/LibreOffice': Permission denied
configure: error: Failed to resolve absolute path.
or
`/bin/sh: 1: cannot create /LibreOffice/lo-externalsrc-core/fetch.log: Permission denied`
解决方法:使用chmod -R 777 <root_path>
命令赋予读写权限。注意如果该路径如果是定义在autogen.input
,检查路径是否在系统根目录下(/
),如果是则改到其他目录。普通用户对系统根目录是没有读写权限的。
也不建议使用root用户进行编译(Building LibreOffice as root is a very bad idea, use a regular user.
)。日常操作也应该遵循最小权限原则,少用root账号直接操作。
3.3 c++环境错误
bash
configure: error: C++ preprocessor "/lib/cpp" fails sanity check
解决方法:缺少c++
编译器,安装gcc
及g++
3.4 c++版本太低
bash
configure: error: GCC 11.4.0 is too old, must be at least GCC 12
解决方法:
bash
# 查看gcc g++版本
gcc --version
g++ --version
# 手动安装gcc12 g++12
sudo apt-get install gcc-12 g++-12
# 使用 update-alternatives 命令将gcc12,g++12设置为默认版本
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 --slave /usr/bin/g++ g++ /usr/bin/g++-12
3.5 ndk版本不匹配
bash
* WARNING : Untested Android NDK version 26.2.11394342, only versions 23.* to 25.* have been used successfully. Proceed at your own risk.
解决:重新安装版本为23~25的ndk。
3.6 jdk版本太低
bash
> Failed to apply plugin 'com.android.internal.application'.
> Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
解决:Gradle与jdk版本不匹配,按照提示安装对应的jdk版本。
3.7 缺少某个工具或者库
bash
configure: error: gperf not found but needed. Install it.
# 找不到gperf,安装gperf
sudo apt install gperf
bash
configure: error: Package requirements (fontconfig >= 2.12.0) were not met:
No package 'fontconfig' found
# 需要fontconfig,安装libfontconfig1-dev
sudo apt install libfontconfig1-dev
bash
configure: error: Python headers not found. You probably want to set both the PYTHON_CFLAGS and PYTHON_LIBS environment variables.
# 缺少 Python 头文件,安装python3-dev
sudo apt install python3-dev
bash
configure: error: Could not find CUPS. Install libcups2-dev or cups-devel.
Error running configure at ./autogen.sh line 321.
# 缺少 CUPS 相关的开发包,安装 libcups2-dev
sudo apt install libcups2-dev
bash
configure: error: msgfmt not found. Install GNU gettext, or re-run without languages.
# 缺少 msgfmt 工具,安装gettext
sudo apt install gettext
3.8 OpenSSL被禁用
bash
* WARNING : OpenSSL has been disabled. No code compiled here will make use of it but system libraries may create indirect dependencies
项目根目录打开configure.ac文件,将openssl、nss设置为enable。OpenSSL 是默认的 TLS/SSL 实现,但仍然可能存在一些依赖于 NSS 的代码,因此把nss也打开。
css
sub_conf_defaults=" \
--build="$build_alias" \
--disable-cairo-canvas \
--disable-cups \
--disable-customtarget-components \
--disable-firebird-sdbc \
--disable-gpgmepp \
--disable-gstreamer-1-0 \
--disable-gtk3 \
--disable-gtk4 \
--disable-libcmis \
--disable-mariadb-sdbc \
--enable-nss \
--disable-online-update \
--disable-opencl \
--enable-openssl \
--disable-pdfimport \
--disable-postgresql-sdbc \
--disable-skia \
--disable-xmlhelp \
--enable-dynamic-loading \
--enable-icecream="$enable_icecream" \
--without-doxygen \
--without-tls \
--without-webdav \
--without-x \
如果提示No package 'nss' 'nss'found
,则先通过以下命令安装它们。
bash
sudo apt install libnss3-dev libnspr4-dev
3.9 Gradle Build 报错
bash
/home/zhg/zhg/core/android/source/src/java/org/libreoffice/SettingsActivity.java:33: 错误: 找不到符号
if(!BuildConfig.ALLOW_EDITING) {
^
符号: 变量 ALLOW_EDITING
位置: 类 BuildConfig
/home/zhg/zhg/core/android/source/src/java/org/mozilla/gecko/gfx/SubdocumentScrollHelper.java:38: 警告: [deprecation] Handler中的Handler()已过时
mUiHandler = new Handler();
^
...
3 个错误
78 个警告
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileFullUIDebugJavaWithJavac'.
> Compilation failed; see the compiler error output for details.
解决:根据具体报错修复,最好导入Android studio 方便查定和定位,这里报错BuildConfig.ALLOW_EDITING
找不到, 打开build.gradle
文件,可以看到项目配置了三个flavor:
gradle
productFlavors {
strippedUI {
dimension "default"
buildConfigField 'boolean', 'ALLOW_EDITING', 'false'
}
strippedUIEditing {
dimension "default"
buildConfigField 'boolean', 'ALLOW_EDITING', 'true'
}
fullUI.dimension "default"
}
可以选择其中一个变体,例如strippedUI
。重新使用./gradlew assembleStrippedUIDebug
命令打包apk,当看到BUILD SUCCESSFUL
说明打包成功,apk输出文件在/build/outputs/apk/strippedUI/debug
目录下。
或者将fullUI变体增加ALLOW_EDITING
字段:
gradle
productFlavors {
strippedUI {
dimension "default"
buildConfigField 'boolean', 'ALLOW_EDITING', 'false'
}
strippedUIEditing {
dimension "default"
buildConfigField 'boolean', 'ALLOW_EDITING', 'true'
}
fullUI{
dimension "default"
buildConfigField 'boolean', 'ALLOW_EDITING', 'true'
}
}
重新使用./gradlew assembleDebug
编译和打包所有变体。
参考链接
1\] [如何编译 LibreOffice - LibreOffice中文社区](https://link.juejin.cn?target=https%3A%2F%2Fwww.libreofficechina.org%2Fhow-to-build-libreoffice-zh-cn%2F "https://www.libreofficechina.org/how-to-build-libreoffice-zh-cn/") \[2\] [Development/BuildingForAndroid - The Document Foundation Wiki](https://link.juejin.cn?target=https%3A%2F%2Fwiki.documentfoundation.org%2FDevelopment%2FBuildingForAndroid "https://wiki.documentfoundation.org/Development/BuildingForAndroid")