Onlyoffice编译
下载编译工具
git clone https://github.com/ONLYOFFICE/build_tools.git
//如果要下载指定的历史版本添加--branch参数 如:--branch v7.3.3.60
修改源码
修改QT地址
由于qt的地址有更新,并且官网也不稳定,推荐使用镜像地址:https://www.mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.9/5.9.9/single/
vim /build_tools/tools/linux/automate.py
# 修改QT默认地址
修改连接数
默认是20个链接超过会有提示:
编辑 build_tools/server/Common/sources/contants.js,修改连接数。
1.编辑 build_tools/server/Common/sources/contants.js,修改连接数。
exports.LICENSE_CONNECTIONS = 2000; # 将此处修改你想要的连接数
开启 advancedApi
编辑 server/Common/sources/license.js,设置 advancedApi 为 true,即开启 iframe 端接口命令的能力。
exports.readLicense = async function () {
...
return [
{
...
advancedApi: true,
...
},
null
];
};
关闭源码更新
vim /build_tools/tools/linux/automate.py
build_tools_params = ["--branch", branch,
"--module", modules,
"--update", "0", #此处修改为0,1会拉取最新代码
"--qt-dir", os.getcwd() + "/qt_build/Qt-5.9.9"] + params
//也可以在执行命令时添加参数./automate server --update=0,效果一样。
增加 connnector.api 方法
1. 修改 ./sdkjs/common/plugins.js
,在 externalConnectorMessage
方法中添加相关代码,如这里添加了关闭和打开历史记录的方法
AscCommon.History.TurnOff()
和AscCommon.History.TurnOn()
是源码中自带的方法,直接引用即可。
vim /home/sdkjs/common/plugins.js
case "historyTurnOff":
AscCommon.History.TurnOff();
break;
case "historyTurnOn":
AscCommon.History.TurnOn();
break;
2. connector 添加相关代码,实现 iframe 对方法的监听
执行编译
cd /build_tools/tools/linux
./automate.py server
添加 connector 代码
编辑 build_tools/out/linux_64/onlyoffice/documentserver/web-apps/apps/api/documents/api.js
,在 DocsAPI.DocEditor
函数中添加 connetor 相关代码。
(function (m) {
function n() {
if (window.crypto && window.crypto.getRandomValues) {
var a = new Uint16Array(8);
window.crypto.getRandomValues(a);
var b = 0;
function d() {
return (65536 + a[b++]).toString(16).substring(1);
}
return (
d() + d() + "-" + d() + "-" + d() + "-" + d() + "-" + d() + d() + d()
);
}
function c() {
return Math.floor(65536 * (1 + Math.random()))
.toString(16)
.substring(1);
}
return (
c() + c() + "-" + c() + "-" + c() + "-" + c() + "-" + c() + c() + c()
);
}
function e(a) {
this.frame = a.frame;
this.guid = "asc.{" + n() + "}";
this.isConnected = !1;
this.callbacks = [];
this.events = {};
this.tasks = [];
this.editorInfo = {};
this.onMessageBound = this.onMessage.bind(this);
a.autoconnect && this.connect();
void 0 === window.Asc && (window.Asc = {});
void 0 === window.Asc.scope && (window.Asc.scope = {});
}
function g(a) {
this.connector = a;
this.id = n();
this.id = this.id.replace(/-/g, "");
this._events = {};
}
e.prototype.onMessage = function (a) {
if ("string" == typeof a.data) {
var b = {};
try {
b = JSON.parse(a.data);
} catch (f) {
b = {};
}
if (
"onExternalPluginMessageCallback" === b.type &&
((b = b.data), this.guid === b.guid)
)
switch (b.type) {
case "onMethodReturn":
0 < this.callbacks.length &&
(a = this.callbacks.shift()) &&
a(b.methodReturnData);
0 < this.tasks.length && this.sendMessage(this.tasks.shift());
break;
case "onCommandCallback":
0 < this.callbacks.length &&
(a = this.callbacks.shift()) &&
a(b.commandReturnData);
0 < this.tasks.length && this.sendMessage(this.tasks.shift());
break;
case "onEvent":
b.eventName &&
this.events[b.eventName] &&
this.events[b.eventName].call(this, b.eventData);
break;
case "onInfo":
this.editorInfo = b;
void 0 !== this.editorInfo.data && delete this.editorInfo.data;
void 0 !== this.editorInfo.type && delete this.editorInfo.type;
if (this.editorInfo.theme && this.onThemeChanged)
this.onThemeChanged(this.editorInfo.theme);
break;
case "onTheme":
this.editorInfo.theme = b.theme;
if (this.editorInfo.theme && this.onThemeChanged)
this.onThemeChanged(this.editorInfo.theme);
break;
case "onWindowEvent":
if ("private_window_method" === b.eventName) {
var c = b.windowID,
d = this;
this.executeMethod(
b.eventData.name,
b.eventData.params,
function (f) {
d._windows &&
d._windows[c] &&
d._windows[c].dispatchEvent("on_private_window_method", f);
}
);
} else
"private_window_command" === b.eventName
? ((c = b.windowID),
(d = this),
this.callCommand(
b.eventData.code,
function (f) {
d._windows &&
d._windows[c] &&
d._windows[c].dispatchEvent(
"on_private_window_command",
f
);
},
!1 === b.eventData.isCalc ? !0 : void 0
))
: this._windows[b.windowID]._oncommand(
b.eventName,
b.eventData
);
break;
case "onWindowButton":
if (this._windows && b.windowID && this._windows[b.windowID])
if (-1 === b.button) this._windows[b.windowID].close();
else if (this._windows[b.windowID].onButtonClick)
this._windows[b.windowID].onButtonClick(b.button);
}
}
};
e.prototype.sendMessage = function (a) {
var b = {
frameEditorId: "iframeEditor",
type: "onExternalPluginMessage",
subType: "connector",
};
b.data = a;
b.data.guid = this.guid;
a = this.frame;
"string" === typeof a && (a = document.getElementById(this.frame));
a && a.contentWindow.postMessage(JSON.stringify(b), "*");
};
e.prototype.connect = function () {
this.isConnected
? console.log("This connector is already connected")
: (window.addEventListener
? window.addEventListener("message", this.onMessageBound, !1)
: window.attachEvent &&
window.attachEvent("onmessage", this.onMessageBound),
(this.isConnected = !0),
this.sendMessage({
type: "register",
}));
};
e.prototype.disconnect = function () {
this.isConnected
? (window.removeEventListener
? window.removeEventListener("message", this.onMessageBound, !1)
: window.detachEvent &&
window.detachEvent("onmessage", this.onMessageBound),
(this.isConnected = !1),
this.sendMessage({
type: "unregister",
}),
this.sendMessage({
type: "unregister",
}))
: console.log("This connector is already disconnected");
};
e.prototype.callCommand = function (a, b, c) {
this.isConnected
? (this.callbacks.push(b),
(a = {
type: "command",
recalculate: !0 === c ? !1 : !0,
data:
"string" === typeof a
? a
: "var Asc = {}; Asc.scope = " +
JSON.stringify(window.Asc.scope || {}) +
"; var scope = Asc.scope; (" +
a.toString() +
")();",
}),
1 !== this.callbacks.length ? this.tasks.push(a) : this.sendMessage(a))
: console.log("Connector is not connected with editor");
};
e.prototype.executeMethod = function (a, b, c) {
this.isConnected
? (this.callbacks.push(c),
(a = {
type: "method",
methodName: a,
data: b,
}),
1 !== this.callbacks.length ? this.tasks.push(a) : this.sendMessage(a))
: console.log("Connector is not connected with editor");
};
e.prototype.attachEvent = function (a, b) {
this.isConnected
? ((this.events[a] = b),
this.sendMessage({
type: "attachEvent",
name: a,
}))
: console.log("Connector is not connected with editor");
};
e.prototype.detachEvent = function (a) {
this.events[a] &&
(delete this.events[a],
this.isConnected
? this.sendMessage({
type: "detachEvent",
name: a,
})
: console.log("Connector is not connected with editor"));
};
e.prototype.historyTurnOff = function () {
this.sendMessage({
type: "historyTurnOff",
});
};
e.prototype.historyTurnOn = function () {
this.sendMessage({
type: "historyTurnOn",
});
};
e.prototype._correctCustomMenuItems = function (a, b) {
var c = {
guid: this.guid,
};
b.tabs ? (c.tabs = []) : (c.items = []);
let d = function (p, h) {
let k = {
id: void 0 !== h.id ? h.id : n(),
};
for (prop in h)
switch (prop) {
case "id":
case "items":
case "onClick":
break;
case "url":
case "icons":
k[prop] = new URL(h[prop], location.href).href;
break;
default:
k[prop] = h[prop];
}
if (h.items) {
k.items = [];
for (var q = 0, r = h.items.length; q < r; q++)
k.items.push(d(p, h.items[q]));
} else h.onClick && ((p[a][k.id] = !0), p.attachEvent(k.id, h.onClick));
return k;
};
if (b.tabs) {
c.tabs = [];
for (var f = 0, l = b.tabs.length; f < l; f++)
c.tabs.push(d(this, b.tabs[f]));
} else
for (c.items = [], f = 0, l = b.length; f < l; f++)
c.items.push(d(this, b[f]));
return c;
};
e.prototype._addCustomMenuEvent = function (a) {
let b = "";
"contextMenuEvents" == a
? (b = "onContextMenuClick")
: "toolbarMenuEvents" == a && (b = "onToolbarMenuClick");
this.events[b] ||
this.attachEvent(
b,
function (d) {
var f = void 0,
l = d.indexOf("_oo_sep_");
-1 !== l && ((f = d.substring(l + 8)), (d = d.substring(0, l)));
this.events[d] && this.events[d].call(this, f);
}.bind(this)
);
if (this[a])
for (var c in this[a]) this[a].hasOwnProperty(c) && this.detachEvent(c);
this[a] = {};
};
e.prototype.addContextMenuItem = function (a) {
this._addCustomMenuEvent("contextMenuEvents");
this.executeMethod("AddContextMenuItem", [
this._correctCustomMenuItems("contextMenuEvents", a),
]);
};
e.prototype.updateContextMenuItem = function (a) {
this.executeMethod("UpdateContextMenuItem", [
this._correctCustomMenuItems("contextMenuEvents", a),
]);
};
e.prototype.addToolbarMenuItem = function (a) {
this._addCustomMenuEvent("toolbarMenuEvents");
this.executeMethod("AddToolbarMenuItem", [
this._correctCustomMenuItems("toolbarMenuEvents", a),
]);
};
g.prototype._register = function () {
this.connector._windows || (this.connector._windows = {});
this.connector._windows[this.id] = this;
};
g.prototype._unregister = function () {
this.connector._windows &&
this.connector._windows[this.id] &&
delete this.connector._windows[this.id];
};
g.prototype.show = function (a) {
var b = new URL(a.url, location.href).href;
b = -1 === b.indexOf(".html?") ? b + "?windowID=" : b + "&windowID=";
b += this.id + "&guid=" + encodeURIComponent(this.connector.guid);
a.url = b;
a.icons && (a.icons = new URL(a.icons, location.href).href);
this._register();
this.connector.executeMethod("ShowWindow", [this.id, a]);
};
g.prototype.activate = function () {
this.connector.executeMethod("ActivateWindow", [this.id]);
};
g.prototype.close = function () {
this._oncommand("onClose");
this.connector.executeMethod("CloseWindow", [this.id]);
this._unregister();
};
g.prototype.dispatchEvent = function (a, b) {
this.connector.executeMethod("SendToWindow", [
this.id,
a,
"object" === typeof b ? JSON.stringify(b) : b,
]);
};
g.prototype.attachEvent = function (a, b) {
this._events[a] = b;
};
g.prototype.detachEvent = function (a) {
this._events && this._events[a] && delete this._events[a];
};
g.prototype._oncommand = function (a, b) {
this._events && this._events[a] && this._events[a].call(this, b);
};
e.prototype.createWindow = function () {
return new g(this);
};
m.Asc = m.Asc ? m.Asc : {};
m.Asc.EditorConnector = e;
})(window);
function _createConnector(settings) {
return new Asc.EditorConnector({
frame: iframe,
autoconnect: settings ? settings.autoconnect : true,
});
}
同时,在 DocsAPI.DocEditor
函数的 return 中添加 createConnector
。
return {
createConnector: _createConnector,
};
编译字体
手动编译常用字体,我这里程序是放在/app/onlyoffice
下的 ,可以根据自己存放地址修改对应路径。
删除原有字体
rm -rf /usr/share/fonts/*
rm -rf /app/onlyoffice/documentserver/core-fonts/*
配置环境变量
export DIR="/app/onlyoffice/documentserver"
export LD_LIBRARY_PATH=/app/onlyoffice/documentserver/server/FileConverter/bin:$LD_LIBRARY_PATH
copy字体
将常用字体copy到/usr/share/fonts/truetype/custom
前面删除了truetype/custom
目录,这里可以手动再创建,再将字体copy到此目录。
mkdir -p /usr/share/fonts/truetype/custom
cp *.woff /usr/share/fonts/truetype/custom/
cp *.ttf /usr/share/fonts/truetype/custom/
编译字体
"$DIR/server/tools/allfontsgen"\
--input="$DIR/core-fonts"\
--allfonts-web="$DIR/sdkjs/common/AllFonts.js"\
--allfonts="$DIR/server/FileConverter/bin/AllFonts.js"\
--images="$DIR/sdkjs/common/Images"\
--selection="$DIR/server/FileConverter/bin/font_selection.bin"\
--output-web="$DIR/fonts"\
--use-system="true"\
--use-system-user-fonts="false"
编译后会再fonts目录下生成字体文件,并且在sdkjs/common
文件夹下生成AllFonts.js
文件。
问题汇总
icu无法下载
手动进行下载:
cd core/Common/3dParty/icu
wget https://github.com/unicode-org/icu/releases/download/release-58-2/icu4c-58_2-src.tgz
tar -zxvf icu4c-58_2-src.tgz
或者修改/build/build_tools/scripts/core_common/modules/icu.py
24行改为:base.cmd("svn", ["export", "https://github.com/unicode-org/icu/releases/download/release-" + icu_major + "-" + icu_minor + "/icu4c", "./icu", "--non-interactive", "--trust-server-cert"])
将tag改为releases/download/
跨平台编译
修改配置添加下列内容:nano /etc/docker/daemon.json
{
"experimental": true
}
重启docker systemctl restart docker
安装 qemu-user-static
(宿主机)
sudo apt-get install qemu-user-static binfmt-support
注册 ARM 模拟器(宿主机)
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
运行 ARM 架构的容器
docker run --platform linux/arm64 -it arm64v8/ubuntu bash
--platform linux/arm64
指定容器运行在 ARM 架构arm64v8/ubuntu
是 ARM 版本的 Ubuntu 镜像
在容器内安装编译工具并编译
升级git
apt-get install software-properties-common
add-apt-repository ppa:git-core/ppa
apt update
apt install git
错误:
python升级
(方式一)在线升级
安装依赖
sudo apt update
sudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git
安装软件
curl https://pyenv.run | bash
修改配置
echo 'export PATH="$HOME/.pyenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init --path)"' >> ~/.bashrc
echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc
source ~/.bashrc
安装新版本
pyenv install 3.12.0 # 可替换为最新版本
pyenv global 3.12.0 # 设为全局默认
(方式二)在编编译
下载wget并升级
sudo apt update
sudo apt install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev wget
下载源码并解压
wget https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tar.xz
tar -xf Python-3.12.0.tar.xz
cd Python-3.12.0
编译并配置
./configure --enable-optimizations # 启用优化
make -j $(nproc) # 使用所有CPU核心加速编译
sudo make altinstall # 避免覆盖系统默认Python V8构建失败
V8问题
构建命令中可以看到v8的输出架构。如果是x86或者64之类的说明架构自动判断有问题 ,需要在v8目录下手动执行下面命令来构建v8
gn gen out.gn/linux_arm64 --args="v8_static_library=true is_component_build=false use_jemalloc=false v8_monolithic=true v8_use_external_startup_data=false use_custom_libcxx=false treat_warnings_as_errors=false target_cpu=\"arm64\" v8_target_cpu=\"arm64\" is_debug=false is_clang=true use_sysroot=false"
修改config文件
//修改为arm架构
platform="linux_arm64"
clang++架构不对
检查发现clang++构建的时候找不到文件 ,但是文件命名存在 ,最终使用file clang++命令查看文件是x86架构的在arm架构下无法识别 。
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-aarch64-linux-gnu.tar.xz
tar xvf clang+llvm-18.1.8-aarch64-linux-gnu.tar.xz
三方地址:
https://github.com/open-onlyoffice/build_tools.git
arm环境下可以尝试其他编译版本:(暂未验证)
https://github.com/bill88t/build_tools-arm64