Compare commits

..

1 Commits

Author SHA1 Message Date
6736f84d54 Fixed jsdoc md generation 2024-08-02 21:26:44 +07:00
64 changed files with 669 additions and 2192 deletions

View File

@ -11,4 +11,5 @@ RUN rm /usr/bin/python && ln -s /usr/bin/python2 /usr/bin/python
ADD . /build_tools
WORKDIR /build_tools
CMD ["sh", "-c", "cd tools/linux && python3 ./automate.py"]
CMD cd tools/linux && \
python3 ./automate.py

View File

@ -27,7 +27,7 @@ parser.add_option("--db-pass", action="store", type="string", dest="db-pass", de
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("--git-protocol", action="store", type="string", dest="git-protocol", default="https", 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")

View File

@ -92,8 +92,6 @@ if config.check_option("module", "desktop"):
config.extend_option("config", "updmodule")
base.set_env("DESKTOP_URL_UPDATES_MAIN_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/onlyoffice/appcast.json")
base.set_env("DESKTOP_URL_UPDATES_DEV_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/onlyoffice/appcastdev.json")
base.set_env("DESKTOP_URL_INSTALL_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/distrib/onlyoffice/<file>")
base.set_env("DESKTOP_URL_INSTALL_DEV_CHANNEL", "https://download.onlyoffice.com/install/desktop/editors/windows/onlyoffice/onlineinstallerdev/<file>")
# build
build_sln.make()

View File

@ -10,17 +10,15 @@ import package_utils as utils
# parse
parser = argparse.ArgumentParser(description="Build packages.")
parser.add_argument("-P", "--platform", dest="platform", type=str,
action="store", help="Defines platform", required=True)
parser.add_argument("-T", "--targets", dest="targets", type=str, nargs="+",
action="store", help="Defines targets", required=True)
parser.add_argument("-V", "--version", dest="version", type=str,
action="store", help="Defines version")
parser.add_argument("-B", "--build", dest="build", type=str,
action="store", help="Defines build")
parser.add_argument("-H", "--branch", dest="branch", type=str,
action="store", help="Defines branch")
action="store", help="Defines platform", required=True)
parser.add_argument("-T", "--targets", dest="targets", type=str, nargs="+",
action="store", help="Defines targets", required=True)
parser.add_argument("-R", "--branding", dest="branding", type=str,
action="store", help="Provides branding path")
action="store", help="Provides branding path")
parser.add_argument("-V", "--version", dest="version", type=str,
action="store", help="Defines version")
parser.add_argument("-B", "--build", dest="build", type=str,
action="store", help="Defines build")
args = parser.parse_args()
# vars
@ -31,20 +29,13 @@ common.targets = args.targets
common.clean = "clean" in args.targets
common.sign = "sign" in args.targets
common.deploy = "deploy" in args.targets
if args.version: common.version = args.version
else: common.version = utils.get_env("PRODUCT_VERSION", "0.0.0")
utils.set_env("PRODUCT_VERSION", common.version)
utils.set_env("BUILD_VERSION", common.version)
if args.build: common.build = args.build
else: common.build = utils.get_env("BUILD_NUMBER", "0")
utils.set_env("BUILD_NUMBER", common.build)
if args.branch: common.branch = args.branch
else: common.branch = utils.get_env("BRANCH_NAME", "null")
utils.set_env("BRANCH_NAME", common.branch)
common.version = args.version if args.version else utils.get_env("BUILD_VERSION", "0.0.0")
common.build = args.build if args.build else utils.get_env("BUILD_NUMBER", "0")
common.branding = args.branding
common.timestamp = utils.get_timestamp()
common.workspace_dir = utils.get_abspath(utils.get_script_dir(__file__) + "/..")
common.branding_dir = utils.get_abspath(common.workspace_dir + "/" + args.branding) if args.branding else common.workspace_dir
common.deploy_data = utils.get_path(common.workspace_dir + "/deploy.txt")
common.summary = []
utils.log("os_family: " + common.os_family)
utils.log("platform: " + str(common.platform))
@ -73,14 +64,15 @@ import package_mobile
# build
utils.set_cwd(common.workspace_dir, verbose=True)
utils.delete_file(common.deploy_data)
if "core" in common.targets:
package_core.make()
if "closuremaps_sdkjs_opensource" in common.targets:
if "closuremaps_opensource" in common.targets:
package_core.deploy_closuremaps_sdkjs("opensource")
if "closuremaps_sdkjs_commercial" in common.targets:
package_core.deploy_closuremaps_sdkjs("commercial")
if "closuremaps_webapps" in common.targets:
package_core.deploy_closuremaps_webapps("opensource")
if "closuremaps_commercial" in common.targets:
package_core.deploy_closuremaps_sdkjs("commercial")
package_core.deploy_closuremaps_webapps("commercial")
if "desktop" in common.targets:
package_desktop.make()
if "builder" in common.targets:
@ -91,8 +83,6 @@ if "server_enterprise" in common.targets:
package_server.make("enterprise")
if "server_developer" in common.targets:
package_server.make("developer")
if "server_prerequisites" in common.targets:
package_server.make("prerequisites")
if "mobile" in common.targets:
package_mobile.make()

View File

@ -39,9 +39,6 @@ def is_os_arm():
return False
return True
def get_platform():
return platform.machine().lower()
def is_python_64bit():
return (struct.calcsize("P") == 8)
@ -175,13 +172,6 @@ def find_file(path, pattern):
for filename in fnmatch.filter(filenames, pattern):
return os.path.join(root, filename)
def find_files(path, pattern):
result = []
for root, dirnames, filenames in os.walk(path):
for filename in fnmatch.filter(filenames, pattern):
result.append(os.path.join(root, filename))
return result
def create_dir(path):
path2 = get_path(path)
if not os.path.exists(path2):
@ -377,7 +367,7 @@ def cmd(prog, args=[], is_no_errors=False):
else:
command = prog
for arg in args:
command += (" \"" + arg.replace('\"', '\\\"') + "\"")
command += (" \"" + arg + "\"")
ret = subprocess.call(command, stderr=subprocess.STDOUT, shell=True)
if ret != 0 and True != is_no_errors:
sys.exit("Error (" + prog + "): " + str(ret))
@ -413,7 +403,7 @@ def cmd_exe(prog, args, is_no_errors=False):
else:
command = prog
for arg in args:
command += (" \"" + arg.replace('\"', '\\\"') + "\"")
command += (" \"" + arg + "\"")
process = subprocess.Popen(command, stderr=subprocess.STDOUT, shell=True, env=env_dir)
ret = process.wait()
if ret != 0 and True != is_no_errors:
@ -428,13 +418,6 @@ def cmd_in_dir(directory, prog, args=[], is_no_errors=False):
os.chdir(cur_dir)
return ret
def cmd_in_dir_qemu(platform, directory, prog, args=[], is_no_errors=False):
if (platform == "linux_arm64"):
return cmd_in_dir(directory, "qemu-aarch64", ["-L", "/usr/aarch64-linux-gnu", prog] + args, is_no_errors)
if (platform == "linux_arm32"):
return cmd_in_dir(directory, "qemu-arm", ["-L", "/usr/arm-linux-gnueabi", prog] + args, is_no_errors)
return 0
def cmd_and_return_cwd(prog, args=[], is_no_errors=False):
cur_dir = os.getcwd()
ret = cmd(prog, args, is_no_errors)
@ -443,13 +426,12 @@ def cmd_and_return_cwd(prog, args=[], is_no_errors=False):
def run_command(sCommand):
popen = subprocess.Popen(sCommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
result = {'stdout' : '', 'stderr' : '', 'returncode' : 0}
result = {'stdout' : '', 'stderr' : ''}
try:
stdout, stderr = popen.communicate()
popen.wait()
result['stdout'] = stdout.strip().decode('utf-8', errors='ignore')
result['stderr'] = stderr.strip().decode('utf-8', errors='ignore')
result['returncode'] = popen.returncode
finally:
popen.stdout.close()
popen.stderr.close()
@ -514,37 +496,12 @@ def set_cwd(dir):
return
# git ---------------------------------------------------
def git_get_origin():
cur_dir = os.getcwd()
os.chdir(get_script_dir() + "/../")
ret = run_command("git config --get remote.origin.url")["stdout"]
os.chdir(cur_dir)
return ret
def git_is_ssh():
git_protocol = config.option("git-protocol")
if (git_protocol == "https"):
return False
if (git_protocol == "ssh"):
return True
origin = git_get_origin()
if (git_protocol == "auto") and (origin.find(":ONLYOFFICE/") != -1):
return True
return False
def get_ssh_base_url():
cur_origin = git_get_origin()
ind = cur_origin.find(":ONLYOFFICE/")
if (ind == -1):
return "git@github.com:ONLYOFFICE/"
return cur_origin[:ind+12]
def git_update(repo, is_no_errors=False, is_current_dir=False, git_owner=""):
print("[git] update: " + repo)
owner = git_owner if git_owner else "ONLYOFFICE"
url = "https://github.com/" + owner + "/" + repo + ".git"
if git_is_ssh():
url = get_ssh_base_url() + repo + ".git"
if config.option("git-protocol") == "ssh":
url = "git@github.com:ONLYOFFICE/" + repo + ".git"
folder = get_script_dir() + "/../../" + repo
if is_current_dir:
folder = repo
@ -577,12 +534,10 @@ def get_repositories():
result.update(get_sdkjs_addons())
result["onlyoffice.github.io"] = [False, False]
result["web-apps"] = [False, False]
result.update(get_web_apps_addons())
result["dictionaries"] = [False, False]
result["core-fonts"] = [False, False]
if config.check_option("module", "server"):
result.update(get_web_apps_addons())
if config.check_option("module", "builder"):
result["document-templates"] = [False, False]
@ -615,8 +570,8 @@ def get_branding_repositories(checker):
def create_pull_request(branches_to, repo, is_no_errors=False, is_current_dir=False):
print("[git] create pull request: " + repo)
url = "https://github.com/ONLYOFFICE/" + repo + ".git"
if git_is_ssh():
url = get_ssh_base_url() + repo + ".git"
if config.option("git-protocol") == "ssh":
url = "git@github.com:ONLYOFFICE/" + repo + ".git"
folder = get_script_dir() + "/../../" + repo
if is_current_dir:
folder = repo
@ -783,9 +738,8 @@ def qt_config(platform):
if (-1 != platform.find("xp")):
config_param += " build_xp"
if ("ios" == platform):
if (config.check_option("bitcode", "yes")):
set_env("BITCODE_GENERATION_MODE", "bitcode")
set_env("ENABLE_BITCODE", "YES")
set_env("BITCODE_GENERATION_MODE", "bitcode")
set_env("ENABLE_BITCODE", "YES")
config_param = config_param.replace("desktop", "")
config_param += " iphoneos device"
if (-1 == config_param_lower.find("debug")):
@ -1233,19 +1187,18 @@ def mac_correct_rpath_x2t(dir):
mac_correct_rpath_library("kernel", ["UnicodeConverter"])
mac_correct_rpath_library("kernel_network", ["UnicodeConverter", "kernel"])
mac_correct_rpath_library("graphics", ["UnicodeConverter", "kernel"])
mac_correct_rpath_library("doctrenderer", ["UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer"])
mac_correct_rpath_library("doctrenderer", ["UnicodeConverter", "kernel", "kernel_network", "graphics"])
mac_correct_rpath_library("HtmlFile2", ["UnicodeConverter", "kernel", "kernel_network", "graphics"])
mac_correct_rpath_library("EpubFile", ["UnicodeConverter", "kernel", "HtmlFile2", "graphics"])
mac_correct_rpath_library("Fb2File", ["UnicodeConverter", "kernel", "graphics"])
mac_correct_rpath_library("HtmlRenderer", ["UnicodeConverter", "kernel", "graphics"])
mac_correct_rpath_library("PdfFile", ["UnicodeConverter", "kernel", "graphics", "kernel_network"])
mac_correct_rpath_library("DjVuFile", ["UnicodeConverter", "kernel", "graphics", "PdfFile"])
mac_correct_rpath_library("XpsFile", ["UnicodeConverter", "kernel", "graphics", "PdfFile"])
mac_correct_rpath_library("DocxRenderer", ["UnicodeConverter", "kernel", "graphics"])
mac_correct_rpath_library("IWorkFile", ["UnicodeConverter", "kernel"])
mac_correct_rpath_library("HWPFile", ["UnicodeConverter", "kernel", "graphics"])
cmd("chmod", ["-v", "+x", "./x2t"])
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./x2t"], True)
mac_correct_rpath_binary("./x2t", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "doctrenderer", "DocxRenderer", "IWorkFile", "HWPFile"])
mac_correct_rpath_binary("./x2t", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "doctrenderer", "DocxRenderer"])
if is_file("./allfontsgen"):
cmd("chmod", ["-v", "+x", "./allfontsgen"])
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./allfontsgen"], True)
@ -1253,7 +1206,7 @@ def mac_correct_rpath_x2t(dir):
if is_file("./allthemesgen"):
cmd("chmod", ["-v", "+x", "./allthemesgen"])
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./allthemesgen"], True)
mac_correct_rpath_binary("./allthemesgen", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "graphics", "kernel_network", "doctrenderer", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer"])
mac_correct_rpath_binary("./allthemesgen", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "graphics", "kernel_network", "doctrenderer"])
if is_file("./pluginsmanager"):
cmd("chmod", ["-v", "+x", "./pluginsmanager"])
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./pluginsmanager"], True)
@ -1270,14 +1223,9 @@ def mac_correct_rpath_docbuilder(dir):
os.chdir(dir)
cmd("chmod", ["-v", "+x", "./docbuilder"])
cmd("install_name_tool", ["-add_rpath", "@executable_path", "./docbuilder"], True)
mac_correct_rpath_binary("./docbuilder", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "IWorkFile", "HWPFile", "doctrenderer", "DocxRenderer"])
mac_correct_rpath_library("docbuilder.c", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "doctrenderer", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer"])
def add_loader_path_to_rpath(libs):
for lib in libs:
cmd("install_name_tool", ["-add_rpath", "@loader_path", "lib" + lib + ".dylib"], True)
add_loader_path_to_rpath(["icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "doctrenderer", "PdfFile", "XpsFile", "DjVuFile", "DocxRenderer", "docbuilder.c"])
mac_correct_rpath_binary("./docbuilder", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "HtmlFile2", "Fb2File", "EpubFile", "doctrenderer", "DocxRenderer"])
mac_correct_rpath_library("docbuilder.c", ["icudata.58", "icuuc.58", "UnicodeConverter", "kernel", "kernel_network", "graphics", "doctrenderer"])
cmd("install_name_tool", ["-add_rpath", "@loader_path", "libdocbuilder.c.dylib"], True)
os.chdir(cur_dir)
return
@ -1287,9 +1235,9 @@ def mac_correct_rpath_desktop(dir):
os.chdir(dir)
mac_correct_rpath_library("hunspell", [])
mac_correct_rpath_library("ooxmlsignature", ["kernel"])
mac_correct_rpath_library("ascdocumentscore", ["UnicodeConverter", "kernel", "graphics", "kernel_network", "PdfFile", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
mac_correct_rpath_library("ascdocumentscore", ["UnicodeConverter", "kernel", "graphics", "kernel_network", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
cmd("install_name_tool", ["-change", "@executable_path/../Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework", "@rpath/Chromium Embedded Framework.framework/Chromium Embedded Framework", "libascdocumentscore.dylib"])
mac_correct_rpath_binary("./editors_helper.app/Contents/MacOS/editors_helper", ["ascdocumentscore", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
mac_correct_rpath_binary("./editors_helper.app/Contents/MacOS/editors_helper", ["ascdocumentscore", "UnicodeConverter", "kernel", "kernel_network", "graphics", "PdfFile", "HtmlRenderer", "XpsFile", "DjVuFile", "hunspell", "ooxmlsignature"])
cmd("install_name_tool", ["-add_rpath", "@executable_path/../../../../Frameworks", "./editors_helper.app/Contents/MacOS/editors_helper"], True)
cmd("install_name_tool", ["-add_rpath", "@executable_path/../../../../Resources/converter", "./editors_helper.app/Contents/MacOS/editors_helper"], True)
cmd("chmod", ["-v", "+x", "./editors_helper.app/Contents/MacOS/editors_helper"])
@ -1323,7 +1271,7 @@ def linux_set_origin_rpath_libraries(dir, libs):
return
def linux_correct_rpath_docbuilder(dir):
linux_set_origin_rpath_libraries(dir, ["docbuilder.jni.so", "docbuilder.c.so", "icuuc.so.58", "doctrenderer.so", "graphics.so", "kernel.so", "kernel_network.so", "UnicodeConverter.so", "PdfFile.so", "XpsFile.so", "DjVuFile.so", "DocxRenderer.so"])
linux_set_origin_rpath_libraries(dir, ["docbuilder.c.so", "icuuc.so.58", "doctrenderer.so", "graphics.so", "kernel.so", "kernel_network.so", "UnicodeConverter.so"])
return
def common_check_version(name, good_version, clean_func):
@ -1379,7 +1327,7 @@ def copy_marketplace_plugin(dst_dir, is_name_as_guid=False, is_desktop_local=Fal
git_dir = __file__script__path__ + "/../.."
if False:
# old version
copy_sdkjs_plugin(git_dir + "/desktop-sdk/ChromiumBasedEditors/plugins", dst_dir, "manager", is_name_as_guid, is_desktop_local)
base.copy_sdkjs_plugin(git_dir + "/desktop-sdk/ChromiumBasedEditors/plugins", dst_dir, "manager", is_name_as_guid, is_desktop_local)
return
src_dir_path = git_dir + "/onlyoffice.github.io/store/plugin"
name = "marketplace"
@ -1445,7 +1393,6 @@ def support_old_versions_plugins(out_dir):
def generate_sdkjs_plugin_list(dst):
plugins_list = config.option("sdkjs-plugin").rsplit(", ") \
+ config.option("sdkjs-plugin-server").rsplit(", ")
plugins_list = list(filter(None, plugins_list))
with open(get_path(dst), 'w') as file:
dump = json.dumps(sorted(plugins_list), indent=4)
file.write(re.sub(r"^(\s{4})", '\t', dump, 0, re.MULTILINE))
@ -1807,50 +1754,3 @@ def apply_patch(file, patch):
#file_content_new = "\n#if 0" + file_content_old + "#else" + file_content_new + "#endif\n"
replaceInFile(file, file_content_old, file_content_new)
return
def get_autobuild_version(product, platform="", branch="", build=""):
download_platform = platform
if ("" == download_platform):
osType = get_platform()
isArm = True if (-1 != osType.find("arm")) else False
is64 = True if (osType.endswith("64")) else False
if ("windows" == host_platform()):
download_platform = "win-"
elif ("linux" == host_platform()):
download_platform = "linux-"
else:
download_platform = "mac-"
download_platform += ("arm" if isArm else "")
download_platform += ("64" if is64 else "32")
else:
download_platform = download_platform.replace("_", "-")
download_build = build
if ("" == download_build):
download_build = "latest"
download_branch = branch
if ("" == download_branch):
download_branch = "develop"
download_addon = download_branch + "/" + download_build + "/" + product + "-" + download_platform + ".7z"
return "http://repo-doc-onlyoffice-com.s3.amazonaws.com/archive/" + download_addon
def create_x2t_js_cache(dir, product, platform):
if is_file(dir + "/libdoctrenderer.dylib") and (os.path.getsize(dir + "/libdoctrenderer.dylib") < 5*1024*1024):
return
if ((platform == "linux_arm64") and not is_os_arm()):
cmd_in_dir_qemu(platform, dir, "./x2t", ["-create-js-snapshots"], True)
return
cmd_in_dir(dir, "./x2t", ["-create-js-snapshots"], True)
return
def setup_local_qmake(dir_qmake):
dir_base = os.path.dirname(dir_qmake)
writeFile(dir_base + "/onlyoffice_qt.conf", "Prefix = " + dir_base)
return

View File

@ -41,7 +41,6 @@ def make():
base.create_dir(out_dir)
# builder
base.cmd_in_dir(base_dir + "/../web-apps/translation", "python", ["merge_and_check.py"])
build_interface(base_dir + "/../web-apps/build")
build_sdk_builder(base_dir + "/../sdkjs/build")
base.create_dir(out_dir + "/builder")
@ -56,14 +55,12 @@ def make():
base.copy_dir(base_dir + "/../sdkjs/deploy/sdkjs", out_dir + "/desktop/sdkjs")
correct_sdkjs_licence(out_dir + "/desktop/sdkjs")
base.copy_dir(base_dir + "/../web-apps/deploy/web-apps", out_dir + "/desktop/web-apps")
deldirs = ['ie', 'mobile', 'embed']
[base.delete_dir(root + "/" + d) for root, dirs, f in os.walk(out_dir + "/desktop/web-apps/apps") for d in dirs if d in deldirs]
# for bug 62528. remove empty folders
walklist = list(os.walk(out_dir + "/desktop/sdkjs"))
[os.remove(p) for p, _, _ in walklist[::-1] if len(os.listdir(p)) == 0]
base.delete_dir(out_dir + "/desktop/web-apps/apps/documenteditor/embed")
base.delete_dir(out_dir + "/desktop/web-apps/apps/documenteditor/mobile")
base.delete_dir(out_dir + "/desktop/web-apps/apps/presentationeditor/embed")
base.delete_dir(out_dir + "/desktop/web-apps/apps/presentationeditor/mobile")
base.delete_dir(out_dir + "/desktop/web-apps/apps/spreadsheeteditor/embed")
base.delete_dir(out_dir + "/desktop/web-apps/apps/spreadsheeteditor/mobile")
base.copy_file(base_dir + "/../web-apps/apps/api/documents/index.html.desktop", out_dir + "/desktop/web-apps/apps/api/documents/index.html")
build_interface(base_dir + "/../desktop-apps/common/loginpage/build")
@ -171,7 +168,6 @@ def build_js_develop(root_dir):
_run_npm(root_dir + external_folder + "/web-apps/build")
_run_npm_ci(root_dir + external_folder + "/web-apps/build/sprites")
_run_grunt(root_dir + external_folder + "/web-apps/build/sprites", [])
base.cmd_in_dir(root_dir + external_folder + "/web-apps/translation", "python", ["merge_and_check.py"])
old_cur = os.getcwd()
old_product_version = base.get_env("PRODUCT_VERSION")

View File

@ -68,4 +68,6 @@ def build_server_with_addons():
base.cmd_in_dir(addon_dir, "npm", ["run", "build"])
def build_server_develop():
build_server_with_addons()
server_dir = base.get_script_dir() + "/../../server"
base.cmd_in_dir(server_dir, "npm", ["ci"])
base.cmd_in_dir(server_dir, "grunt", ["develop", "-v"] + base.server_addons_param())

View File

@ -35,41 +35,15 @@ def make(solution=""):
qmake.make(platform, pro, "xcframework_platform_ios_simulator")
if config.check_option("module", "builder") and base.is_windows() and "onlyoffice" == config.branding():
# check branding libs
if (config.option("branding-name") == "onlyoffice"):
for platform in platforms:
if not platform in config.platforms:
continue
core_lib_unbranding_dir = os.getcwd() + "/../core/build/lib/" + platform + base.qt_dst_postfix()
if not base.is_dir(core_lib_unbranding_dir):
base.create_dir(core_lib_unbranding_dir)
core_lib_branding_dir = os.getcwd() + "/../core/build/onlyoffice/lib/" + platform + base.qt_dst_postfix()
base.copy_file(core_lib_branding_dir + "/doctrenderer.dll", core_lib_unbranding_dir + "/doctrenderer.dll")
base.copy_file(core_lib_branding_dir + "/doctrenderer.lib", core_lib_unbranding_dir + "/doctrenderer.lib")
# check replace
directory_builder_branding = os.getcwd() + "/../core/DesktopEditor/doctrenderer"
if base.is_dir(directory_builder_branding):
new_replace_path = base.correctPathForBuilder(directory_builder_branding + "/docbuilder.com/src/docbuilder.h")
if ("2019" == config.option("vs-version")):
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com_2019.sln")
if (True):
new_path_net = base.correctPathForBuilder(directory_builder_branding + "/docbuilder.net/src/docbuilder.net.cpp")
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.net/src", "docbuilder.net.sln")
base.restorePathForBuilder(new_path_net)
else:
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com.sln")
base.restorePathForBuilder(new_replace_path)
# build Java docbuilder wrapper
if config.check_option("module", "builder") and "onlyoffice" == config.branding():
for platform in platforms:
if not platform in config.platforms:
continue
# build JNI library
qmake.make(platform, base.get_script_dir() + "/../../core/DesktopEditor/doctrenderer/docbuilder.java/src/jni/docbuilder_jni.pro", "", True)
# build Java code to JAR
base.cmd_in_dir(base.get_script_dir() + "/../../core/DesktopEditor/doctrenderer/docbuilder.java", "python", ["make.py"])
new_replace_path = base.correctPathForBuilder(os.getcwd() + "/../core/DesktopEditor/doctrenderer/docbuilder.com/src/docbuilder.h")
if ("2019" == config.option("vs-version")):
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com_2019.sln")
if (True):
new_path_net = base.correctPathForBuilder(os.getcwd() + "/../core/DesktopEditor/doctrenderer/docbuilder.net/src/docbuilder.net.cpp")
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.net/src", "docbuilder.net.sln")
base.restorePathForBuilder(new_path_net)
else:
base.make_sln_project("../core/DesktopEditor/doctrenderer/docbuilder.com/src", "docbuilder.com.sln")
base.restorePathForBuilder(new_replace_path)
return

View File

@ -16,7 +16,6 @@ import curl
import websocket_all
import v8
import html2
import iwork
import hunspell
import glew
import harfbuzz
@ -43,7 +42,6 @@ def make():
openssl.make()
v8.make()
html2.make()
iwork.make(False)
hunspell.make(False)
harfbuzz.make()
glew.make()

View File

@ -7,27 +7,7 @@ import base
import os
import subprocess
def clear_module():
directories = ["gumbo-parser", "katana-parser"]
for dir in directories:
if base.is_dir(dir):
base.delete_dir_with_access_error(dir)
def make():
old_cur_dir = os.getcwd()
print("[fetch]: html")
base_dir = base.get_script_dir() + "/../../core/Common/3dParty/html"
os.chdir(base_dir)
base.check_module_version("2", clear_module)
os.chdir(old_cur_dir)
base.cmd_in_dir(base_dir, "python", ["fetch.py"])
return
if __name__ == '__main__':
# manual compile
make()

View File

@ -3,11 +3,6 @@ sys.path.append('../../../scripts')
import base
import os
def clean():
if base.is_dir("hunspell"):
base.delete_dir_with_access_error("hunspell")
return
def make(build_js = True):
old_cur_dir = os.getcwd()
@ -16,8 +11,6 @@ def make(build_js = True):
core_common_dir = base.get_script_dir() + "/../../core/Common"
os.chdir(core_common_dir + "/3dParty/hunspell")
base.common_check_version("hunspell", "1", clean)
base.cmd("python", ["./before.py"])
if (build_js):

View File

@ -1,38 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append('../..')
import config
import base
import os
import subprocess
def clear_module():
directories = ["glm", "libetonyek", "libodfgen", "librevenge", "mdds"]
for dir in directories:
if base.is_dir(dir):
base.delete_dir_with_access_error(dir)
def make(use_gperf = True):
old_cur_dir = os.getcwd()
print("[fetch & build]: iwork")
base_dir = base.get_script_dir() + "/../../core/Common/3dParty/apple"
os.chdir(base_dir)
base.check_module_version("3", clear_module)
os.chdir(old_cur_dir)
cmd_args = ["fetch.py"]
if use_gperf:
cmd_args.append("--gperf")
base.cmd_in_dir(base_dir, "python", cmd_args)
return
if __name__ == '__main__':
# manual compile
make(False)

View File

@ -119,7 +119,7 @@ def make():
# windows hack (delete later) ----------------------
if ("windows" == base.host_platform()):
base.delete_dir_with_access_error("v8/buildtools/win")
base.cmd("git", ["config", "--system", "core.longpaths", "true"], True)
base.cmd("git", ["config", "--system", "core.longpaths", "true"])
base.cmd("gclient", ["sync", "--force"], True)
else:
base.cmd("gclient", ["sync"], True)
@ -246,7 +246,7 @@ def make_xp():
base.cmd("./depot_tools/fetch", ["v8"], True)
base.cmd("./depot_tools/gclient", ["sync", "-r", "4.10.253"], True)
base.delete_dir_with_access_error("v8/buildtools/win")
base.cmd("git", ["config", "--system", "core.longpaths", "true"], True)
base.cmd("git", ["config", "--system", "core.longpaths", "true"])
base.cmd("gclient", ["sync", "--force"], True)
# save common py script

View File

@ -21,12 +21,6 @@ def change_bootstrap():
content += "@Subdir git\n"
content += "infra/3pp/tools/git/${platform} version:2@2.41.0.chromium.11\n"
base.replaceInFile("./depot_tools/bootstrap/bootstrap.py",
"raise subprocess.CalledProcessError(proc.returncode, argv, None)", "return")
base.replaceInFile("./depot_tools/bootstrap/bootstrap.py",
" _win_git_bootstrap_config()", " #_win_git_bootstrap_config()")
base.writeFile("./depot_tools/bootstrap/manifest.txt", content)
return
@ -111,9 +105,6 @@ def make():
if not base.is_dir(base_dir):
base.create_dir(base_dir)
if ("mac" == base.host_platform()):
base.cmd("git", ["config", "--global", "http.postBuffer", "157286400"], True)
os.chdir(base_dir)
if not base.is_dir("depot_tools"):
base.cmd("git", ["clone", "https://chromium.googlesource.com/chromium/tools/depot_tools.git"])
@ -130,7 +121,7 @@ def make():
base.copy_dir("./v8/third_party", "./v8/third_party_new")
if ("windows" == base.host_platform()):
os.chdir("v8")
base.cmd("git", ["config", "--system", "core.longpaths", "true"], True)
base.cmd("git", ["config", "--system", "core.longpaths", "true"])
os.chdir("../")
v8_branch_version = "remotes/branch-heads/8.9"
if ("mac" == base.host_platform()):
@ -146,11 +137,6 @@ def make():
else:
base.replaceInFile("depot_tools/gclient_paths.py", "@functools.lru_cache", "")
if ("mac" == base.host_platform()):
if not base.is_file("v8/build/config/compiler/BUILD.gn.bak"):
base.copy_file("v8/build/config/compiler/BUILD.gn", "v8/build/config/compiler/BUILD.gn.bak")
base.replaceInFile("v8/build/config/compiler/BUILD.gn", "\"-Wloop-analysis\",", "\"-Wloop-analysis\", \"-D_Float16=short\",")
if not base.is_file("v8/third_party/jinja2/tests.py.bak"):
base.copy_file("v8/third_party/jinja2/tests.py", "v8/third_party/jinja2/tests.py.bak")
base.replaceInFile("v8/third_party/jinja2/tests.py", "from collections import Mapping", "try:\n from collections.abc import Mapping\nexcept ImportError:\n from collections import Mapping")

View File

@ -40,10 +40,9 @@ def make():
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DjVuFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "XpsFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlFile2")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlRenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "Fb2File")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "EpubFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "IWorkFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HWPFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DocxRenderer")
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", root_dir + "/cmap.bin")
@ -77,12 +76,9 @@ def make():
if (0 == platform.find("win")):
base.copy_file(core_build_dir + "/lib/" + platform_postfix + "/doctrenderer.lib", root_dir + "/doctrenderer.lib")
base.copy_v8_files(core_dir, root_dir, platform, isWindowsXP)
# python wrapper
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "docbuilder.c")
base.copy_file(core_dir + "/DesktopEditor/doctrenderer/docbuilder.python/src/docbuilder.py", root_dir + "/docbuilder.py")
# java wrapper
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "docbuilder.jni")
base.copy_file(core_dir + "/DesktopEditor/doctrenderer/docbuilder.java/build/libs/docbuilder.jar", root_dir + "/docbuilder.jar")
# app
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, root_dir, "docbuilder")
@ -124,25 +120,6 @@ def make():
if (0 == platform.find("mac")):
base.mac_correct_rpath_x2t(root_dir)
base.mac_correct_rpath_docbuilder(root_dir)
base.create_x2t_js_cache(root_dir, "builder", platform)
# delete unnecessary builder files
def delete_files(files):
for file in files:
base.delete_file(file)
delete_files(base.find_files(root_dir, "*.wasm"))
delete_files(base.find_files(root_dir, "*_ie.js"))
base.delete_file(root_dir + "/sdkjs/pdf/src/engine/cmap.bin")
if 0 != platform.find("mac"):
delete_files(base.find_files(root_dir, "sdk-all.js"))
delete_files(base.find_files(root_dir, "sdk-all-min.js"))
base.delete_dir(root_dir + "/sdkjs/slide/themes")
base.delete_dir(root_dir + "/sdkjs/cell/css")
base.delete_file(root_dir + "/sdkjs/pdf/src/engine/viewer.js")
base.delete_file(root_dir + "/sdkjs/common/spell/spell/spell.js.mem")
base.delete_dir(root_dir + "/sdkjs/common/Images")
return

View File

@ -30,6 +30,7 @@ def make():
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "kernel_network")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "graphics")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "doctrenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "HtmlRenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "DjVuFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "XpsFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "PdfFile")
@ -37,10 +38,7 @@ def make():
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "UnicodeConverter")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "Fb2File")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "EpubFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "IWorkFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "HWPFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "DocxRenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, archive_dir, "hunspell")
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", archive_dir + "/cmap.bin")
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "x2t")
@ -63,14 +61,13 @@ def make():
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "ooxml_crypt")
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "vboxtester")
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "metafiletester")
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, archive_dir, "dictionariestester")
# js cache
base.generate_doctrenderer_config(archive_dir + "/DoctRenderer.config", "./", "builder", "", "./dictionaries")
base.create_x2t_js_cache(archive_dir, "core", platform)
base.delete_file(archive_dir + "/DoctRenderer.config")
# dictionaries
base.copy_dictionaries(git_dir + "/dictionaries", archive_dir + "/dictionaries", True, False)
if base.is_file(archive_dir + ".7z"):
base.delete_file(archive_dir + ".7z")
base.archive_folder(archive_dir + "/*", archive_dir + ".7z")
return

View File

@ -66,10 +66,9 @@ def make():
base.copy_lib(build_libraries_path, root_dir + "/converter", "DjVuFile")
base.copy_lib(build_libraries_path, root_dir + "/converter", "XpsFile")
base.copy_lib(build_libraries_path, root_dir + "/converter", "HtmlFile2")
base.copy_lib(build_libraries_path, root_dir + "/converter", "HtmlRenderer")
base.copy_lib(build_libraries_path, root_dir + "/converter", "Fb2File")
base.copy_lib(build_libraries_path, root_dir + "/converter", "EpubFile")
base.copy_lib(build_libraries_path, root_dir + "/converter", "IWorkFile")
base.copy_lib(build_libraries_path, root_dir + "/converter", "HWPFile")
base.copy_lib(build_libraries_path, root_dir + "/converter", "DocxRenderer")
if ("ios" == platform):
@ -103,7 +102,6 @@ def make():
base.generate_doctrenderer_config(root_dir + "/converter/DoctRenderer.config", "../editors/", "desktop", "", "../dictionaries")
base.copy_dir(git_dir + "/document-templates/new", root_dir + "/converter/empty")
base.copy_dir(git_dir + "/desktop-apps/common/templates", root_dir + "/converter/templates")
# dictionaries
base.copy_dictionaries(git_dir + "/dictionaries", root_dir + "/dictionaries")
@ -184,8 +182,6 @@ def make():
base.copy_file(git_dir + "/desktop-apps/win-linux/extras/projicons/" + apps_postfix + "/projicons.exe", root_dir + "/DesktopEditors.exe")
if not isWindowsXP:
base.copy_file(git_dir + "/desktop-apps/win-linux/extras/update-daemon/" + apps_postfix + "/updatesvc.exe", root_dir + "/updatesvc.exe")
else:
base.copy_file(git_dir + "/desktop-apps/win-linux/extras/online-installer/" + apps_postfix + "/online-installer.exe", root_dir + "/online-installer.exe")
base.copy_file(git_dir + "/desktop-apps/win-linux/" + apps_postfix + "/DesktopEditors.exe", root_dir + "/editors.exe")
base.copy_file(git_dir + "/desktop-apps/win-linux/res/icons/desktopeditors.ico", root_dir + "/app.ico")
elif (0 == platform.find("linux")):
@ -217,6 +213,8 @@ def make():
base.create_dir(root_dir + "/editors")
base.copy_dir(base_dir + "/js/" + branding + "/desktop/sdkjs", root_dir + "/editors/sdkjs")
if len(os.listdir(root_dir + "/editors/sdkjs")) == 0:
base.delete_dir(root_dir + "/editors/sdkjs") # delete empty folder. for bug 62528
base.copy_dir(base_dir + "/js/" + branding + "/desktop/web-apps", root_dir + "/editors/web-apps")
for file in glob.glob(root_dir + "/editors/web-apps/apps/*/*/*.js.map"):
base.delete_file(file)
@ -262,8 +260,6 @@ def make():
if isUseJSC:
base.delete_file(root_dir + "/converter/icudtl.dat")
base.create_x2t_js_cache(root_dir + "/converter", "desktop", platform)
if (0 == platform.find("win")):
base.delete_file(root_dir + "/cef_sandbox.lib")
base.delete_file(root_dir + "/libcef.lib")

View File

@ -58,11 +58,10 @@ def make():
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DjVuFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "XpsFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlFile2")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HtmlRenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "doctrenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "Fb2File")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "EpubFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "IWorkFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "HWPFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, root_dir, "DocxRenderer")
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", root_dir + "/cmap.bin")

View File

@ -78,11 +78,10 @@ def make():
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "DjVuFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "XpsFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "HtmlFile2")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "HtmlRenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "doctrenderer")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "Fb2File")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "EpubFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "IWorkFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "HWPFile")
base.copy_lib(core_build_dir + "/lib/" + platform_postfix, converter_dir, "DocxRenderer")
base.copy_file(git_dir + "/sdkjs/pdf/src/engine/cmap.bin", converter_dir + "/cmap.bin")
base.copy_exe(core_build_dir + "/bin/" + platform_postfix, converter_dir, "x2t")
@ -119,8 +118,6 @@ def make():
+ glob.glob(js_dir + "/web-apps/apps/*/mobile/dist/js/*.js.map"):
base.delete_file(file)
base.create_x2t_js_cache(converter_dir, "server", platform)
# add embed worker code
base.cmd_in_dir(git_dir + "/sdkjs/common/embed", "python", ["make.py", js_dir + "/web-apps/apps/api/documents/api.js"])

View File

@ -1,202 +0,0 @@
# Before starting, make sure that:
# 1. MVS 2022 is installed and the necessary individual components are in its installer
# <20> Windows Universal C Runtime
# <20> .NET Framework 4.x SDK (.NET Framework 5.x SDK and later are currently not supported. These don't register their information to registry, don't have csc.exe and they use dotnet command with csc.dll instead for compiling.)
# <20> C++ 20xx Redistributable MSMs (only required to build MSI installer)
# <20> C++ Clang Compiler for Windows (x.x.x)
# 2. Java JDK >= 17
# 3. Antivirus is turned off
# 4. There is enough free space on the disk (50GB Libre Office, 50Gb cygwin64)
# after completion, the files will appear:
# {LO_BUILD_PATH}/sources/libo-core/instdir/program/soffice.exe
# {LO_BUILD_PATH}/sources/libo-core/LibreOffice.sln
# debugging can be done via MVS 2022
# https://wiki.documentfoundation.org/Development/IDE#Microsoft_Visual_Studio
# or via attatch to the soffice.bin process
# https://wiki.documentfoundation.org/Development/How_to_debug#Debugging_with_gdb
import sys
sys.path.append('../../scripts')
import threading
import os
import subprocess
import shutil
import argparse
import base
CYGWIN_DOWNLOAD_URL = 'https://cygwin.com/setup-x86_64.exe'
CYGWIN_TEMP_PATH = './tmp'
CYGWIN_SETUP_FILENAME = 'setup-x86_64.exe'
CYGWIN_SETUP_PARAMS = [
"-P", "autoconf",
"-P", "automake",
"-P", "bison",
"-P", "cabextract",
"-P", "doxygen",
"-P", "flex",
"-P", "gawk=5.2.2-1",
"-P", "gcc-g++",
"-P", "gettext-devel",
"-P", "git",
"-P", "gnupg",
"-P", "gperf",
"-P", "make",
"-P", "mintty",
"-P", "nasm",
"-P", "openssh",
"-P", "openssl",
"-P", "patch",
"-P", "perl",
"-P", "python",
"-P", "python3",
"-P", "pkg-config",
"-P", "rsync",
"-P", "unzip",
"-P", "vim",
"-P", "wget",
"-P", "zip",
"-P", "perl-Archive-Zip",
"-P", "perl-Font-TTF",
"-P", "perl-IO-String",
"--no-admin",
"--quiet-mode"
]
CYGWIN_BAT_PATH = 'C:/cygwin64/Cygwin.bat'
LO_BUILD_PATH = os.path.normpath(os.path.join(os.getcwd(), '../../../LO'))
CONFIGURE_PARAMS = [f'--with-external-tar="{LO_BUILD_PATH}/sources/lo-externalsrc"',
f'--with-junit="{LO_BUILD_PATH}/sources/junit-4.10.jar"',
f'--with-ant-home="{LO_BUILD_PATH}/sources/apache-ant-1.9.5"',
"--enable-pch",
"--disable-ccache",
"--with-visual-studio=2022",
"--enable-dbgutil",
'--enable-symbols="all"']
def create_folder_safe(folder_path):
if not os.path.exists(folder_path):
try:
os.mkdir(folder_path)
print(f"Folder '{folder_path}' created successfully.")
except Exception as e:
print(f"Error creating folder: {e}")
else:
print(f"Folder '{folder_path}' already exists.")
class CygwinRunner:
@staticmethod
def process_commands(commands: list[str]):
proc = subprocess.Popen(
[CYGWIN_BAT_PATH], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
shell=True, creationflags=subprocess.CREATE_NEW_CONSOLE
)
def read_stdout():
for line in iter(proc.stdout.readline, ''):
sys.stdout.write(line)
proc.stdout.close()
def read_stderr():
for line in iter(proc.stderr.readline, ''):
sys.stderr.write(line)
proc.stderr.close()
stdout_thread = threading.Thread(target=read_stdout)
stderr_thread = threading.Thread(target=read_stderr)
stdout_thread.start()
stderr_thread.start()
for command in commands:
proc.stdin.write(command + '\n')
proc.stdin.flush()
stdout_thread.join()
stderr_thread.join()
proc.stdin.close()
proc.wait()
@staticmethod
def install_gnu_make():
base.print_info("install_gnu_make")
commands = ['mkdir -p /opt/lo/bin',
'cd /opt/lo/bin',
'wget https://dev-www.libreoffice.org/bin/cygwin/make-4.2.1-msvc.exe',
'cp make-4.2.1-msvc.exe make',
'chmod +x make',
'exit']
CygwinRunner.process_commands(commands)
@staticmethod
def install_ant_and_junit():
base.print_info("install_ant_and_junit")
commands = [f'mkdir -p {LO_BUILD_PATH}/sources',
f'cd {LO_BUILD_PATH}/sources',
'wget https://archive.apache.org/dist/ant/binaries/apache-ant-1.9.5-bin.tar.bz2',
'tar -xjvf apache-ant-1.9.5-bin.tar.bz2',
'wget http://downloads.sourceforge.net/project/junit/junit/4.10/junit-4.10.jar',
'exit']
CygwinRunner.process_commands(commands)
@staticmethod
def clone_lo():
base.print_info("clone_lo")
commands = [f'cd {LO_BUILD_PATH}/sources',
'git clone https://gerrit.libreoffice.org/core libo-core',
'exit']
CygwinRunner.process_commands(commands)
@staticmethod
def build_autogen():
base.print_info("build_autogen")
commands = [f'cd {LO_BUILD_PATH}/sources/libo-core',
f"./autogen.sh {' '.join(map(str, CONFIGURE_PARAMS))}",
'exit']
CygwinRunner.process_commands(commands)
@staticmethod
def run_make_build():
base.print_info("run_make")
commands = [f'cd {LO_BUILD_PATH}/sources/libo-core',
f'/opt/lo/bin/make gb_COLOR=1',
"exit"]
CygwinRunner.process_commands(commands)
@staticmethod
def build_vs_integration():
base.print_info("run_make")
commands = [f'cd {LO_BUILD_PATH}/sources/libo-core',
f'/opt/lo/bin/make gb_COLOR=1 vs-ide-integration',
"exit"]
CygwinRunner.process_commands(commands)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="options")
parser.add_argument("--lo_build_path", dest="build_path", default=f'../../../LO')
parser.add_argument("--disable_sln", dest="disable_sln", action=argparse.BooleanOptionalAction)
args = parser.parse_args()
LO_BUILD_PATH = args.build_path
DISABLE_SLN = args.disable_sln
create_folder_safe(f'{LO_BUILD_PATH}/sources/lo-externalsrc')
create_folder_safe(CYGWIN_TEMP_PATH)
os.chdir(CYGWIN_TEMP_PATH)
base.download(CYGWIN_DOWNLOAD_URL, CYGWIN_SETUP_FILENAME)
subprocess.run([CYGWIN_SETUP_FILENAME] + CYGWIN_SETUP_PARAMS)
os.chdir('..')
shutil.rmtree(CYGWIN_TEMP_PATH)
CygwinRunner.install_gnu_make()
CygwinRunner.install_ant_and_junit()
CygwinRunner.clone_lo()
CygwinRunner.build_autogen()
CygwinRunner.run_make_build()
if not DISABLE_SLN:
CygwinRunner.build_vs_integration()

View File

@ -5,6 +5,9 @@ import base
import os
import json
def get_core_url(arch, branch):
return "http://repo-doc-onlyoffice-com.s3.amazonaws.com/" + base.host_platform() + "/core/" + branch + "/latest/" + arch + "/core.7z"
def make():
git_dir = base.get_script_dir() + "/../.."
old_cur = os.getcwd()
@ -15,10 +18,16 @@ def make():
os.chdir(work_dir)
url = base.get_autobuild_version("core", "", config.option("branch"))
arch = "x64"
arch2 = "_64"
if ("windows" == base.host_platform()) and not base.host_platform_is64():
arch = "x86"
arch2 = "_32"
url = get_core_url(arch, config.option("branch"))
data_url = base.get_file_last_modified_url(url)
if (data_url == "" and config.option("branch") != "develop"):
url = base.get_autobuild_version("core", "", "develop")
url = get_core_url(arch, "develop")
data_url = base.get_file_last_modified_url(url)
old_data_url = base.readFile("./core.7z.data")
@ -40,6 +49,12 @@ def make():
base.extract("./core.7z", "./")
base.writeFile("./core.7z.data", data_url)
platform = ""
if ("windows" == base.host_platform()):
platform = "win" + arch2
else:
platform = base.host_platform() + arch2
base.copy_files("./core/*", "./")
else:
print("-----------------------------------------------------------")

View File

@ -539,9 +539,7 @@ def check_mysqlServer():
print(mysql_full_name + 'configuration is valid')
dependence.sqlPath = info['Location']
return dependence
print(mysql_full_name + 'configuration is not valid:' + connectionResult)
# if path exists, then further removal and installation fails(according to startup statistics). it is better to fix issue manually.
return dependence
print(mysql_full_name + 'configuration is not valid')
print('Valid MySQL Server not found')
dependence.append_uninstall('MySQL Server')

View File

@ -6,7 +6,6 @@ import base
import shutil
import optparse
import dependence
import config
arguments = sys.argv[1:]
@ -18,10 +17,6 @@ parser.add_option("--remove-path", action="append", type="string", dest="remove-
(options, args) = parser.parse_args(arguments)
configOptions = vars(options)
# parse configuration
config.parse()
config.parse_defaults()
for item in configOptions["uninstall"]:
dependence.uninstallProgram(item)
for item in configOptions["remove-path"]:

View File

@ -8,13 +8,6 @@ import dependence
import traceback
import develop
# if (sys.version_info[0] >= 3):
# unicode = str
# host_platform = base.host_platform()
# if (host_platform == 'windows'):
# import libwindows
base_dir = base.get_script_dir(__file__)
def install_module(path):
@ -28,18 +21,11 @@ def find_rabbitmqctl(base_path):
return base.find_file(os.path.join(base_path, 'RabbitMQ Server'), 'rabbitmqctl.bat')
def restart_win_rabbit():
# todo maybe restarting is not relevant after many years and versions?
base.print_info('restart RabbitMQ node to prevent "Erl.exe high CPU usage every Monday morning on Windows" https://groups.google.com/forum/#!topic/rabbitmq-users/myl74gsYyYg')
rabbitmqctl = find_rabbitmqctl(os.environ['PROGRAMW6432']) or find_rabbitmqctl(os.environ['ProgramFiles(x86)'])
if rabbitmqctl is not None:
try:
# code = libwindows.sudo(unicode(sys.executable), ['net', 'stop', 'rabbitmq'])
# code = libwindows.sudo(unicode(sys.executable), ['net', 'start', 'rabbitmq'])
base.cmd_in_dir(base.get_script_dir(rabbitmqctl), 'rabbitmqctl.bat', ['stop_app'])
base.cmd_in_dir(base.get_script_dir(rabbitmqctl), 'rabbitmqctl.bat', ['start_app'])
except SystemExit:
base.print_error('Perhaps Erlang cookies are different: Replace %userprofile%/.erlang.cookie with %WINDIR%/System32/config/systemprofile/.erlang.cookie')
raise
base.cmd_in_dir(base.get_script_dir(rabbitmqctl), 'rabbitmqctl.bat', ['stop_app'])
base.cmd_in_dir(base.get_script_dir(rabbitmqctl), 'rabbitmqctl.bat', ['start_app'])
else:
base.print_info('Missing rabbitmqctl.bat')

View File

@ -21,15 +21,14 @@
"core/DesktopEditor/raster/JBig2",
"core/DesktopEditor/raster/Jp2",
"core/DesktopEditor/xml/libxml2",
"core/DesktopEditor/xmlsec",
"core/DesktopEditor/xmlsec",
"core/DjVuFile/libdjvu",
"core/DjVuFile/wasm",
"core/EpubFile",
"core/OOXML/PPTXFormat/Limit/pri",
"core/Fb2File",
"core/HtmlFile2",
"core/Apple",
"core/HwpFile",
"core/HtmlFile2",
"core/OdfFile/Common/utf8cpp",
"core/OfficeUtils/js/emsdk",
"core/OfficeUtils/src/zlib-1.2.11",
@ -198,11 +197,6 @@
"editors-ios/Vendor/ThreadSafeMutable/ThreadSafeMutableDictionary.h",
"editors-ios/Vendor/ThreadSafeMutable/ThreadSafeMutableDictionary.m"
]
},
{
"dir": "editors-webview-ios",
"fileExtensions": [".swift", ".xcconfig"],
"licensePath": "header.license",
}
]
}

View File

@ -1,5 +1,5 @@
/*
* (c) Copyright Ascensio System SIA 2010-2025
* (c) Copyright Ascensio System SIA 2010-2024
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)

View File

@ -43,7 +43,7 @@ if utils.is_linux():
},
{
"make": "rpm",
"src": "rpm/build/RPMS/*/*.rpm",
"src": "rpm/builddir/RPMS/*/*.rpm",
"dst": "builder/linux/rhel/"
}
]

View File

@ -7,17 +7,14 @@ import package_branding as branding
def make():
utils.log_h1("BUILDER")
if not (utils.is_windows() or utils.is_macos() or utils.is_linux()):
utils.log("Unsupported host OS")
return
if common.deploy:
make_archive()
if utils.is_windows():
make_windows()
elif utils.is_macos():
make_macos()
elif utils.is_linux():
make_linux()
else:
utils.log("Unsupported host OS")
return
def s3_upload(files, dst):
@ -27,133 +24,111 @@ def s3_upload(files, dst):
key = dst + utils.get_basename(f) if dst.endswith("/") else dst
upload = utils.s3_upload(f, "s3://" + branding.s3_bucket + "/" + key)
if upload:
utils.add_deploy_data(key)
utils.log("URL: " + branding.s3_base_url + "/" + key)
ret &= upload
return ret
def make_archive():
utils.set_cwd(utils.get_path(
"build_tools/out/" + common.prefix + "/" + branding.company_name.lower()))
utils.log_h2("builder archive build")
utils.delete_file("builder.7z")
args = ["7z", "a", "-y", "builder.7z", "./documentbuilder/*"]
if utils.is_windows():
ret = utils.cmd(*args, verbose=True)
else:
ret = utils.sh(" ".join(args), verbose=True)
utils.set_summary("builder archive build", ret)
utils.log_h2("builder archive deploy")
dest = "builder-" + common.prefix.replace("_","-") + ".7z"
dest_latest = "archive/%s/latest/%s" % (common.branch, dest)
dest_version = "archive/%s/%s/%s" % (common.branch, common.build, dest)
ret = utils.s3_upload(
"builder.7z", "s3://" + branding.s3_bucket + "/" + dest_version)
utils.set_summary("builder archive deploy", ret)
if ret:
utils.log("URL: " + branding.s3_base_url + "/" + dest_version)
utils.s3_copy(
"s3://" + branding.s3_bucket + "/" + dest_version,
"s3://" + branding.s3_bucket + "/" + dest_latest)
utils.log("URL: " + branding.s3_base_url + "/" + dest_latest)
utils.set_cwd(common.workspace_dir)
return
def make_windows():
global package_version, arch
global inno_file, zip_file, suffix, key_prefix
utils.set_cwd("document-builder-package")
prefix = common.platformPrefixes[common.platform]
company = branding.company_name
product = branding.builder_product_name.replace(" ","")
source_dir = "..\\build_tools\\out\\%s\\%s\\%s" % (prefix, company, product)
package_name = company + "-" + product
package_version = common.version + "." + common.build
arch = {
suffix = {
"windows_x64": "x64",
"windows_x86": "x86"
}[common.platform]
zip_file = "%s-%s-%s-%s.zip" % (company, product, package_version, suffix)
inno_file = "%s-%s-%s-%s.exe" % (company, product, package_version, suffix)
if common.clean:
utils.log_h2("builder clean")
utils.delete_dir("build")
utils.delete_files("exe\\*.exe")
utils.delete_files("zip\\*.msi")
if make_prepare():
make_zip()
make_inno()
else:
utils.set_summary("builder zip build", False)
utils.set_summary("builder inno build", False)
utils.log_h2("copy arifacts")
utils.create_dir("build\\app")
utils.copy_dir_content(source_dir, "build\\app\\")
make_zip()
make_inno()
utils.set_cwd(common.workspace_dir)
return
def make_prepare():
args = [
"-Version", package_version,
"-Arch", arch
]
if common.sign:
args += ["-Sign"]
utils.log_h2("builder prepare")
ret = utils.ps1("make.ps1", args, verbose=True)
utils.set_summary("builder prepare", ret)
return ret
def make_zip():
args = [
"-Version", package_version,
"-Arch", arch
]
# if common.sign:
# args += ["-Sign"]
utils.log_h2("builder zip build")
ret = utils.ps1("make_zip.ps1", args, verbose=True)
utils.log_h3(zip_file)
ret = utils.cmd("7z", "a", "-y", zip_file, ".\\app\\*",
chdir="build", creates="build\\" + zip_file, verbose=True)
utils.set_summary("builder zip build", ret)
if common.deploy and ret:
utils.log_h2("builder zip deploy")
ret = s3_upload(utils.glob_path("zip/*.zip"), "builder/win/generic/")
ret = s3_upload(["build\\" + zip_file], "builder/win/generic/")
utils.set_summary("builder zip deploy", ret)
return
def make_inno():
utils.log_h2("builder inno build")
utils.log_h3(inno_file)
args = [
"-Version", package_version,
"-Arch", arch
"-Arch", suffix,
"-Version", common.version,
"-Build", common.build
]
if not branding.onlyoffice:
args += ["-Branding", common.branding]
args += [
"-Branding", "%s\\%s\\document-builder-package\\exe" % (common.workspace_dir, common.branding)
]
if common.sign:
args += ["-Sign"]
utils.log_h2("builder inno build")
ret = utils.ps1("make_inno.ps1", args, verbose=True)
args += [
"-Sign",
"-CertName", branding.cert_name
]
ret = utils.ps1(
"make_inno.ps1", args, creates="build\\" + inno_file, verbose=True
)
utils.set_summary("builder inno build", ret)
if common.deploy and ret:
utils.log_h2("builder inno deploy")
ret = s3_upload(utils.glob_path("exe/*.exe"), "builder/win/inno/")
ret = s3_upload(["build\\" + inno_file], "builder/win/inno/")
utils.set_summary("builder inno deploy", ret)
return
def make_macos():
utils.set_cwd("document-builder-package")
company = branding.company_name.lower()
product = branding.builder_product_name.replace(" ","").lower()
source_dir = "build_tools/out/%s/%s/%s" % (common.prefix, company, product)
arch_list = {
"darwin_x86_64": "x86_64",
"darwin_arm64": "arm64"
}
suffix = arch_list[common.platform]
builder_tar = "../%s-%s-%s-%s-%s.tar.xz" % \
(company, product, common.version, common.build, suffix)
utils.log_h2("builder tar build")
make_args = ["tar"]
if common.platform == "darwin_arm64":
make_args += ["-e", "UNAME_M=arm64"]
if not branding.onlyoffice:
make_args += ["-e", "BRANDING_DIR=../" + common.branding + "/document-builder-package"]
ret = utils.sh("make clean && make " + " ".join(make_args), verbose=True)
utils.set_summary("builder tar build", ret)
utils.set_cwd(source_dir)
if common.deploy:
utils.log_h2("builder tar deploy")
ret = s3_upload(utils.glob_path("tar/*.tar.xz"), "builder/mac/generic/")
utils.set_summary("builder tar deploy", ret)
if common.clean:
utils.log_h2("builder clean")
utils.delete_files("../*.tar*")
utils.log_h2("builder build")
ret = utils.sh("tar --xz -cvf %s *" % builder_tar, creates=builder_tar, verbose=True)
utils.set_summary("builder build", ret)
if common.deploy and ret:
utils.log_h2("builder deploy")
ret = s3_upload([builder_tar], "builder/mac/generic/")
utils.set_summary("builder deploy", ret)
utils.set_cwd(common.workspace_dir)
return

View File

@ -10,37 +10,47 @@ def make():
utils.log("Unsupported host OS")
return
if common.deploy:
make_archive()
make_core()
return
def make_archive():
utils.set_cwd(utils.get_path(
"build_tools/out/" + common.prefix + "/" + branding.company_name.lower()))
def make_core():
prefix = common.platformPrefixes[common.platform]
company = branding.company_name.lower()
repos = {
"windows_x64": { "repo": "windows", "arch": "x64", "version": common.version + "." + common.build },
"windows_x86": { "repo": "windows", "arch": "x86", "version": common.version + "." + common.build },
"darwin_x86_64": { "repo": "mac", "arch": "x64", "version": common.version + "-" + common.build },
"darwin_arm64": { "repo": "mac", "arch": "arm", "version": common.version + "-" + common.build },
"linux_x86_64": { "repo": "linux", "arch": "x64", "version": common.version + "-" + common.build },
}
repo = repos[common.platform]
branch = utils.get_env("BRANCH_NAME")
core_7z = utils.get_path("build_tools/out/%s/%s/core.7z" % (prefix, company))
dest_version = "%s/core/%s/%s/%s" % (repo["repo"], branch, repo["version"], repo["arch"])
dest_latest = "%s/core/%s/%s/%s" % (repo["repo"], branch, "latest", repo["arch"])
utils.log_h2("core archive build")
utils.delete_file("core.7z")
args = ["7z", "a", "-y", "core.7z", "./core/*"]
if utils.is_windows():
ret = utils.cmd(*args, verbose=True)
else:
ret = utils.sh(" ".join(args), verbose=True)
utils.set_summary("core archive build", ret)
if branch is None:
utils.log_err("BRANCH_NAME variable is undefined")
utils.set_summary("core deploy", False)
return
if not utils.is_file(core_7z):
utils.log_err("file not exist: " + core_7z)
utils.set_summary("core deploy", False)
return
utils.log_h2("core archive deploy")
dest = "core-" + common.prefix.replace("_","-") + ".7z"
dest_latest = "archive/%s/latest/%s" % (common.branch, dest)
dest_version = "archive/%s/%s/%s" % (common.branch, common.build, dest)
utils.log_h2("core deploy")
ret = utils.s3_upload(
"core.7z", "s3://" + branding.s3_bucket + "/" + dest_version)
utils.set_summary("core archive deploy", ret)
core_7z,
"s3://" + branding.s3_bucket + "/" + dest_version + "/core.7z")
if ret:
utils.log("URL: " + branding.s3_base_url + "/" + dest_version)
utils.s3_copy(
"s3://" + branding.s3_bucket + "/" + dest_version,
"s3://" + branding.s3_bucket + "/" + dest_latest)
utils.log("URL: " + branding.s3_base_url + "/" + dest_latest)
utils.set_cwd(common.workspace_dir)
utils.log("URL: " + branding.s3_base_url + "/" + dest_version + "/core.7z")
utils.add_deploy_data(dest_version + "/core.7z")
ret = utils.s3_sync(
"s3://" + branding.s3_bucket + "/" + dest_version + "/",
"s3://" + branding.s3_bucket + "/" + dest_latest + "/",
delete=True)
utils.log("URL: " + branding.s3_base_url + "/" + dest_latest + "/core.7z")
utils.set_summary("core deploy", ret)
return
def deploy_closuremaps_sdkjs(license):
@ -64,6 +74,7 @@ def deploy_closuremaps_sdkjs(license):
ret &= upload
if upload:
utils.log("URL: " + branding.s3_base_url + "/" + key)
utils.add_deploy_data(key)
utils.set_summary("sdkjs closure maps %s deploy" % license, ret)
return
@ -89,5 +100,6 @@ def deploy_closuremaps_webapps(license):
ret &= upload
if upload:
utils.log("URL: " + branding.s3_base_url + "/" + key)
utils.add_deploy_data(key)
utils.set_summary("web-apps closure maps %s deploy" % license, ret)
return

View File

@ -26,6 +26,7 @@ def s3_upload(files, dst):
key = dst + utils.get_basename(f) if dst.endswith("/") else dst
upload = utils.s3_upload(f, "s3://" + branding.s3_bucket + "/" + key)
if upload:
utils.add_deploy_data(key)
utils.log("URL: " + branding.s3_base_url + "/" + key)
ret &= upload
return ret
@ -63,7 +64,6 @@ def make_windows():
make_zip()
make_inno()
make_advinst()
make_online()
utils.set_cwd(common.workspace_dir)
return
@ -154,6 +154,17 @@ def make_inno():
else:
ret = False
utils.set_summary("desktop inno update deploy", ret)
changes_dir = common.workspace_dir + "\\" \
+ utils.get_path(branding.desktop_changes_dir) + "\\" + common.version
if common.platform == "windows_x64" and \
common.deploy and \
utils.glob_path(changes_dir + "\\*.html"):
utils.log_h2("desktop changelog deploy")
ret = s3_upload(
utils.glob_path(changes_dir + "\\*.html"),
"desktop/win/update/%s/%s/" % (common.version, common.build))
utils.set_summary("desktop changelog deploy", ret)
return
def make_advinst():
@ -177,19 +188,6 @@ def make_advinst():
utils.set_summary("desktop advinst deploy", ret)
return
def make_online():
if not common.platform in ["windows_x86_xp"]:
return
online_file = "%s-%s-%s.exe" % ("OnlineInstaller", package_version, suffix)
ret = utils.is_file(online_file)
utils.set_summary("desktop online installer build", ret)
if common.deploy and ret:
utils.log_h2("desktop online installer deploy")
ret = s3_upload([online_file], "desktop/win/online/")
utils.set_summary("desktop online installer deploy", ret)
return
#
# macOS
#
@ -296,17 +294,11 @@ def make_sparkle_updates():
macos_zip = "build/" + zip_filename + ".zip"
utils.create_dir(updates_dir)
utils.copy_file(macos_zip, updates_dir)
utils.sh(
"ls -1t " + released_updates_dir + "/*.zip" \
+ " | head -n 3" \
+ " | while read f; do cp -fv \"$f\" " + updates_dir + "/; done",
verbose=True)
utils.copy_dir_content(released_updates_dir, updates_dir, ".zip")
for ext in [".html", ".ru.html"]:
changes_src = changes_dir + "/" + common.version + "/changes" + ext
changes_dst = updates_dir + "/" + zip_filename + ext
if not utils.copy_file(changes_src, changes_dst):
utils.write_file(changes_dst, "<!DOCTYPE html>placeholder")
for file in utils.glob_path(changes_dir + "/" + common.version + "/*.html"):
filename = utils.get_basename(file).replace("changes", zip_filename)
utils.copy_file(file, updates_dir + "/" + filename)
sparkle_base_url = "%s/%s/updates/" % (branding.sparkle_base_url, suffix)
ret = utils.sh(

View File

@ -31,6 +31,7 @@ def make_mobile():
key = "mobile/android/" + zip_file
ret = utils.s3_upload(zip_file, "s3://" + branding.s3_bucket + "/" + key)
if ret:
utils.add_deploy_data(key)
utils.log("URL: " + branding.s3_base_url + "/" + key)
utils.set_summary("mobile deploy", ret)

View File

@ -21,6 +21,7 @@ def s3_upload(files, dst):
key = dst + utils.get_basename(f) if dst.endswith("/") else dst
upload = utils.s3_upload(f, "s3://" + branding.s3_bucket + "/" + key)
if upload:
utils.add_deploy_data(key)
utils.log("URL: " + branding.s3_base_url + "/" + key)
ret &= upload
return ret
@ -36,13 +37,10 @@ def make_windows(edition):
utils.log_h2("server " + edition + " build")
ret = utils.cmd("make", "clean", verbose=True)
if edition == "prerequisites":
make_args = ["exe-pr"]
else:
make_args = ["exe", "-e", "PRODUCT_NAME=" + product_name]
args = ["-e", "PRODUCT_NAME=" + product_name]
if not branding.onlyoffice:
make_args += ["-e", "BRANDING_DIR=../" + common.branding + "/document-server-package"]
ret &= utils.cmd("make", *make_args, verbose=True)
args += ["-e", "BRANDING_DIR=../" + common.branding + "/document-server-package"]
ret &= utils.cmd("make", "packages", *args, verbose=True)
utils.set_summary("server " + edition + " build", ret)
if common.deploy and ret:

View File

@ -266,6 +266,11 @@ def set_summary(target, status):
common.summary.append({target: status})
return
def add_deploy_data(key):
with open(common.deploy_data, 'a+') as f:
f.write(key + "\n")
return
def cmd(*args, **kwargs):
if kwargs.get("verbose"):
log("- cmd:")
@ -380,13 +385,15 @@ def s3_upload(src, dst, **kwargs):
ret = sh(" ".join(args), verbose=True)
return ret
def s3_copy(src, dst, **kwargs):
def s3_sync(src, dst, **kwargs):
args = ["aws"]
if kwargs.get("endpoint_url"):
args += ["--endpoint-url", kwargs["endpoint_url"]]
args += ["s3", "cp", "--no-progress"]
args += ["s3", "sync", "--no-progress"]
if kwargs.get("acl"):
args += ["--acl", kwargs["acl"]]
if kwargs.get("delete") and kwargs["delete"]:
args += ["--delete"]
args += [src, dst]
if is_windows():
ret = cmd(*args, verbose=True)

View File

@ -29,7 +29,7 @@ def check_support_platform(platform):
return False
return True
def make(platform, project, qmake_config_addon="", is_no_errors=False):
def make(platform, project, qmake_config_addon=""):
# check platform
if not check_support_platform(platform):
print("THIS PLATFORM IS NOT SUPPORTED")
@ -93,9 +93,6 @@ def make(platform, project, qmake_config_addon="", is_no_errors=False):
qmake_app = qt_dir + "/bin/qmake"
# non windows platform
if not base.is_windows():
if base.is_file(qt_dir + "/onlyoffice_qt.conf"):
build_params.append("-qtconf")
build_params.append(qt_dir + "/onlyoffice_qt.conf")
base.cmd(qmake_app, build_params)
base.correct_makefile_after_qmake(platform, makefile)
if ("1" == config.option("clean")):
@ -103,7 +100,7 @@ def make(platform, project, qmake_config_addon="", is_no_errors=False):
base.cmd_and_return_cwd("make", distclean_params, True)
base.cmd(qmake_app, build_params)
base.correct_makefile_after_qmake(platform, makefile)
base.cmd_and_return_cwd("make", ["-f", makefile] + get_j_num(), is_no_errors)
base.cmd_and_return_cwd("make", ["-f", makefile] + get_j_num())
else:
config_params_array = base.qt_config_as_param(config_param)
config_params_string = ""
@ -126,7 +123,7 @@ def make(platform, project, qmake_config_addon="", is_no_errors=False):
if ("0" != config.option("multiprocess")):
qmake_bat.append("set CL=/MP")
qmake_bat.append("call nmake -f " + makefile)
base.run_as_bat(qmake_bat, is_no_errors)
base.run_as_bat(qmake_bat)
if (base.is_file(stash_file)):
base.delete_file(stash_file)

View File

@ -1,22 +1,16 @@
# Documentation Generation Guide
This guide explains how to generate documentation for Onlyoffice Builder/Plugins API using the provided Python scripts: `generate_docs_json.py`, `generate_docs_plugins_json.py`, `generate_docs_md.py`. These scripts are used to create JSON and Markdown documentation for the `apiBuilder.js` files from the word, cell, and slide editors.
This guide explains how to generate documentation for Onlyoffice API using the provided Python scripts, `generate_docs_json.py` and `generate_docs_md.py`. These scripts are used to create JSON and Markdown documentation for the `apiBuilder.js` files from the word, cell, and slide editors.
## Requirements
## Prerequisites
```bash
Node.js v20 and above
Python v3.10 and above
```
1. **Node.js and npm**: Ensure you have Node.js and npm installed on your machine. You can download them from [Node.js official website](https://nodejs.org/).
## Installation
```bash
git clone https://github.com/ONLYOFFICE/build_tools.git
cd build_tools/scripts/sdkjs_common/jsdoc
npm install
```
2. **jsdoc**: The scripts use `jsdoc` to generate documentation. Install it using npm:
```bash
npm install
```
## Scripts Overview
@ -30,19 +24,7 @@ This script generates JSON documentation based on the `apiBuilder.js` files.
```
- **Parameters**:
- `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api`.
### `generate_docs_plugins_json.py`
This script generates JSON documentation based on the `api_plugins.js` files.
- **Usage**:
```bash
python generate_docs_plugins_json.py output_path
```
- **Parameters**:
- `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `../../../../office-js-api-declarations/office-js-api-plugins`.
- `output_path` (optional): The directory where the JSON documentation will be saved. If not specified, the default path is `Onlyoffice/sdkjs/deploy/api_builder/json`.
### `generate_docs_md.py`
@ -54,7 +36,7 @@ This script generates Markdown documentation from the `apiBuilder.js` files.
```
- **Parameters**:
- `output_path` (optional): The directory where the Markdown documentation will be saved. If not specified, the default path is `../../../../office-js-api/`.
- `output_path` (optional): The directory where the Markdown documentation will be saved. If not specified, the default path is `Onlyoffice/office-js-api`.
## Example
@ -63,11 +45,6 @@ To generate JSON documentation with the default output path:
python generate_docs_json.py /path/to/save/json
```
To generate JSON documentation with the default output path:
```bash
python generate_docs_plugins_json.py /path/to/save/json
```
To generate Markdown documentation and specify a custom output path:
```bash
python generate_docs_md.py /path/to/save/markdown

View File

@ -1,16 +0,0 @@
{
"source": {
"include": ["../../../../sdkjs/cell/api_plugins.js"]
},
"plugins": ["./correct_doclets.js"],
"opts": {
"destination": "./out",
"recurse": true,
"encoding": "utf8"
},
"templates": {
"json": {
"pretty": true
}
}
}

View File

@ -1,16 +0,0 @@
{
"source": {
"include": ["../../../../sdkjs/common/plugins/plugin_base_api.js" ,"../../../../sdkjs/common/apiBase_plugins.js"]
},
"plugins": ["./correct_doclets.js"],
"opts": {
"destination": "./out",
"recurse": true,
"encoding": "utf8"
},
"templates": {
"json": {
"pretty": true
}
}
}

View File

@ -1,85 +0,0 @@
exports.handlers = {
processingComplete: function(e) {
const filteredDoclets = [];
function checkNullProps(oDoclet) {
for (let key of Object.keys(oDoclet)) {
if (oDoclet[key] == null) {
delete oDoclet[key];
}
if (typeof(oDoclet[key]) == "object") {
checkNullProps(oDoclet[key]);
}
}
}
for (let i = 0; i < e.doclets.length; i++) {
const doclet = e.doclets[i];
if (true == doclet.undocumented || doclet.kind == 'package') {
continue;
}
const filteredDoclet = {
comment: doclet.comment,
meta: doclet.meta ? {
lineno: doclet.meta.lineno,
columnno: doclet.meta.columnno
} : doclet.meta,
kind: doclet.kind,
since: doclet.since,
name: doclet.name,
type: doclet.type ? {
names: doclet.type.names,
parsedType: doclet.type.parsedType
} : doclet.type,
description: doclet.description,
memberof: doclet.memberof,
properties: doclet.properties ? doclet.properties.map(property => ({
type: property.type ? {
names: property.type.names,
parsedType: property.type.parsedType
} : property.type,
name: property.name,
description: property.description,
optional: property.optional,
defaultvalue: property.defaultvalue
})) : doclet.properties,
longname: doclet.longname,
scope: doclet.scope,
alias: doclet.alias,
params: doclet.params ? doclet.params.map(param => ({
type: param.type ? {
names: param.type.names,
parsedType: param.type.parsedType
} : param.type,
name: param.name,
description: param.description,
optional: param.optional,
defaultvalue: param.defaultvalue
})) : doclet.params,
returns: doclet.returns ? doclet.returns.map(returnObj => ({
type: {
names: returnObj.type.names,
parsedType: returnObj.type.parsedType
}
})) : doclet.returns,
see: doclet.see
};
checkNullProps(filteredDoclet)
filteredDoclets.push(filteredDoclet);
}
e.doclets.splice(0, e.doclets.length, ...filteredDoclets);
}
};

View File

@ -1,16 +0,0 @@
{
"source": {
"include": ["../../../../sdkjs-forms/apiPlugins.js"]
},
"plugins": ["./correct_doclets.js"],
"opts": {
"destination": "./out",
"recurse": true,
"encoding": "utf8"
},
"templates": {
"json": {
"pretty": true
}
}
}

View File

@ -1,16 +0,0 @@
{
"source": {
"include": ["../../../../sdkjs/slide/api_plugins.js"]
},
"plugins": ["./correct_doclets.js"],
"opts": {
"destination": "./out",
"recurse": true,
"encoding": "utf8"
},
"templates": {
"json": {
"pretty": true
}
}
}

View File

@ -1,16 +0,0 @@
{
"source": {
"include": ["../../../../sdkjs/word/api_plugins.js", "../../../../sdkjs-forms/apiPlugins.js"]
},
"plugins": ["./correct_doclets.js"],
"opts": {
"destination": "./out",
"recurse": true,
"encoding": "utf8"
},
"templates": {
"json": {
"pretty": true
}
}
}

View File

@ -3,16 +3,13 @@ import subprocess
import json
import argparse
import re
import platform
root = '../../../..'
# Configuration files
configs = [
"./config/builder/word.json",
"./config/builder/cell.json",
"./config/builder/slide.json",
"./config/builder/forms.json"
"./config/word.json",
"./config/cell.json",
"./config/slide.json",
"./config/forms.json"
]
editors_maps = {
@ -23,17 +20,20 @@ editors_maps = {
}
def generate(output_dir, md=False):
missing_examples_file = f'{output_dir}/missing_examples.txt'
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Recreate missing_examples.txt file
with open(missing_examples_file, 'w', encoding='utf-8') as f:
f.write('')
# Generate JSON documentation
for config in configs:
editor_name = config.split('/')[-1].replace('.json', '')
output_file = os.path.join(output_dir, editor_name + ".json")
command_set_env = "export"
if (platform.system().lower() == "windows"):
command_set_env = "set"
command = f"{command_set_env} EDITOR={editors_maps[editor_name]} && npx jsdoc -c {config} -X > {output_file}"
command = f"set EDITOR={editors_maps[editor_name]} && npx jsdoc -c {config} -X > {output_file}"
print(f"Generating {editor_name}.json: {command}")
subprocess.run(command, shell=True)
@ -55,7 +55,7 @@ def generate(output_dir, md=False):
else:
doclet['see'][0] = doclet['see'][0].replace('{Editor}', editor_name.title())
file_path = f'{root}/' + doclet['see'][0]
file_path = '../../../../' + doclet['see'][0]
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as see_file:
@ -70,20 +70,22 @@ def generate(output_dir, md=False):
comment = ''
code_content = example_content
if md == True:
doclet['example'] = remove_js_comments(comment) + "```js\n" + code_content + "\n```"
# Format content for doclet['example']
doclet['example'] = remove_js_comments(comment) + "```js\n" + code_content + "\n```"
if md == False:
document_type = editor_name
if "forms" == document_type:
document_type = "pdf"
doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{document_type}"}}\n{code_content}\n```'
doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{editor_name.title()}"}}\n{code_content}\n```'
else:
# Record missing examples in missing_examples.txt
with open(missing_examples_file, 'a', encoding='utf-8') as missing_file:
missing_file.write(f"{file_path}\n")
# Write the modified JSON file back
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
print("Documentation generation for builder completed.")
print("Documentation generation completed.")
def remove_builder_lines(text):
lines = text.splitlines() # Split text into lines
@ -96,6 +98,25 @@ def remove_js_comments(text):
# Remove multi-line comments, leaving text after /*
text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL)
return text.strip()
def get_current_branch(path):
try:
# Navigate to the specified directory and get the current branch name
result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=path,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if result.returncode == 0:
return result.stdout.strip()
else:
print(f"Error: {result.stderr}")
return None
except Exception as e:
print(f"Exception: {e}")
return None
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate documentation")
@ -104,7 +125,12 @@ if __name__ == "__main__":
type=str,
help="Destination directory for the generated documentation",
nargs='?', # Indicates the argument is optional
default=f"{root}/office-js-api-declarations/office-js-api"
default="../../../../document-builder-declarations/document-builder" # Default value
)
args = parser.parse_args()
branch_name = get_current_branch("../../../../sdkjs")
if branch_name:
args.destination = f"{args.destination}/{branch_name}"
generate(args.destination)

View File

@ -13,8 +13,6 @@ editors = [
"forms"
]
missing_examples = []
def load_json(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
@ -24,26 +22,22 @@ def write_markdown_file(file_path, content):
md_file.write(content)
def remove_js_comments(text):
text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) # single-line
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
# Remove single-line comments, leaving text after //
text = re.sub(r'^\s*//\s?', '', text, flags=re.MULTILINE)
# Remove multi-line comments, leaving text after /*
text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL)
return text.strip()
def correct_description(string):
"""
Cleans up or transforms certain tags in a doclet description:
- <b> => **
- <note>...</note> => 💡 ...
- Provide a default if None.
"""
if string is None:
return 'No description provided.'
# Replace <b> tags with markdown bold
# Replace opening <b> tag with **
string = re.sub(r'<b>', '**', string)
# Replace closing </b> tag with **
string = re.sub(r'</b>', '**', string)
# Convert <note>...</note> to a little icon + text
string = re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
return string
# Note
return re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
def correct_default_value(value, enumerations, classes):
if value is None:
@ -59,126 +53,41 @@ def correct_default_value(value, enumerations, classes):
return generate_data_types_markdown([value], enumerations, classes)
def remove_line_breaks(string):
return re.sub(r'[\r\n]+', ' ', string)
# Convert Array.<T> => T[] (including nested arrays).
def convert_jsdoc_array_to_ts(type_str: str) -> str:
"""
Recursively replaces 'Array.<T>' with 'T[]',
handling nested arrays like 'Array.<Array.<string>>' => 'string[][]'.
"""
pattern = re.compile(r'Array\.<([^>]+)>')
while True:
match = pattern.search(type_str)
if not match:
break
inner_type = match.group(1).strip()
# Recursively convert inner parts
inner_type = convert_jsdoc_array_to_ts(inner_type)
# Replace the outer Array.<...> with ...[]
type_str = (
type_str[:match.start()]
+ f"{inner_type}[]"
+ type_str[match.end():]
)
return type_str
def escape_text_outside_code_blocks(markdown: str) -> str:
"""
Splits content by fenced code blocks, escapes MDX-unsafe characters
(<, >, {, }) only in the text outside those code blocks.
"""
# A regex to capture fenced code blocks with ```
parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL)
# Even indices (0, 2, 4, ...) are outside code blocks,
# odd indices (1, 3, 5, ...) are actual code blocks.
for i in range(0, len(parts), 2):
# Only escape in parts outside code blocks
parts[i] = (parts[i]
.replace('<', '&lt;')
.replace('>', '&gt;')
.replace('{', '&#123;')
.replace('}', '&#125;')
)
return "".join(parts)
def get_base_type(ts_type: str) -> str:
"""
Given a TypeScript-like type (e.g. "Drawing[][]"), return the
'base' portion by stripping trailing "[]". For "Drawing[][]",
returns "Drawing". For "Array.<Drawing>", you'd convert it first
to "Drawing[]" then return "Drawing".
"""
while ts_type.endswith('[]'):
ts_type = ts_type[:-2]
return ts_type
return re.sub(r'[\r\n]', '', string)
def generate_data_types_markdown(types, enumerations, classes, root='../../'):
"""
1) Convert each raw JSDoc type from Array.<T> to T[].
2) Split union types if needed (usually they're provided as separate
elements in 'types' already, but let's be safe).
3) For each type, extract the base type (e.g. "Drawing" from "Drawing[]").
4) If the base type matches an enumeration or class, link the entire
T[]-based string.
5) Join with " | ".
"""
param_types_md = ' &#124;'.join(types)
# Convert each raw type from JSDoc to TS
converted = [convert_jsdoc_array_to_ts(t) for t in types] # e.g. ["Drawing[]", "Foo[]", ...]
for enum in enumerations:
if enum['name'] in types:
param_types_md = param_types_md.replace(enum['name'], f"[{enum['name']}]({root}Enumeration/{enum['name']}.md)")
for cls in classes:
if cls in types:
param_types_md = param_types_md.replace(cls, f"[{cls}]({root}{cls}/{cls}.md)")
# For each converted type (like "Drawing[]"), see if the base is in enumerations or classes
def link_if_known(ts_type):
base = get_base_type(ts_type) # e.g. "Drawing" from "Drawing[]"
# Check enumerations first
for enum in enumerations:
if enum['name'] == base:
# Replace the entire token with a link
return f"[{ts_type}]({root}Enumeration/{base}.md)"
# Check classes
if base in classes:
return f"[{ts_type}]({root}{base}/{base}.md)"
# Otherwise just return as-is
return ts_type
# Build final list of possibly-linked types
linked = [link_if_known(ts_t) for ts_t in converted]
# Join them with " | "
param_types_md = r' \| '.join(linked)
# If there's still leftover angle brackets for generics, gently escape or link them
# e.g. "Object.<string, number>" => "Object.&lt;string, number&gt;"
# or do more specialized linking if you want to handle them deeper.
def replace_leftover_generics(match):
def replace_with_links(match):
element = match.group(1).strip()
return f"&lt;{element}&gt;"
base_type = element.split('.')[0] # Take only the first part before the dot, if any
if any(enum['name'] == base_type for enum in enumerations):
return f"<[{element}](../../Enumeration/{base_type}.md)>"
elif base_type in classes:
return f"<[{element}](../../{base_type}/{base_type}.md)>"
return f"<{element}>"
param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md)
return param_types_md
return re.sub(r'<([^<>]+)>', replace_with_links, param_types_md)
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
content += generate_properties_markdown(properties, enumerations, classes)
content += generate_properties_markdown(properties, enumerations, classes, '../')
content += "## Methods\n\n"
for method in methods:
method_name = method['name']
content += f"- [{method_name}](./Methods/{method_name}.md)\n"
return content
# Escape just before returning
return escape_text_outside_code_blocks(content)
def generate_method_markdown(method, enumerations, classes, example_editor_name):
def generate_method_markdown(method, enumerations, classes):
method_name = method['name']
description = method.get('description', 'No description provided.')
description = correct_description(description)
@ -189,14 +98,14 @@ def generate_method_markdown(method, enumerations, classes, example_editor_name)
content = f"# {method_name}\n\n{description}\n\n"
# Syntax
# Syntax section
param_list = ', '.join([param['name'] for param in params]) if params else ''
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
content += f"## Syntax\n\nexpression.{method_name}({param_list});\n\n"
if memberof:
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
# Parameters
content += "## Parameters\n\n"
if params:
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
@ -212,49 +121,43 @@ def generate_method_markdown(method, enumerations, classes, example_editor_name)
else:
content += "This method doesn't have any parameters.\n"
# Returns
content += "\n## Returns\n\n"
if returns:
return_type_list = returns[0].get('type', {}).get('names', [])
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
return_type = ', '.join(returns[0].get('type', {}).get('names', [])) if returns[0].get('type') else 'Unknown'
# Check for enumerations and classes in return type and add links if they exist
return_type_md = generate_data_types_markdown([return_type], enumerations, classes)
content += return_type_md
else:
content += "This method doesn't return any data."
# Example
if example:
# Separate comment and code, remove JS comments
if '```js' in example:
comment, code = example.split('```js', 1)
comment = remove_js_comments(comment)
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
else:
# If there's no triple-backtick structure, just show it as code
cleaned_example = remove_js_comments(example)
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
# Separate comment and code, and remove comment symbols
comment, code = example.split('```js', 1)
comment = remove_js_comments(comment)
content += f"\n\n## Example\n\n{comment}\n\n```javascript\n{code.strip()}\n"
return escape_text_outside_code_blocks(content)
return content
def generate_properties_markdown(properties, enumerations, classes, root='../'):
if properties is None:
def generate_properties_markdown(properties, enumerations, classes, root='../../'):
if (properties is None):
return ''
content = "## Properties\n\n"
content += "| Name | Type | Description |\n"
content += "| ---- | ---- | ----------- |\n"
for prop in properties:
prop_name = prop['name']
prop_description = prop.get('description', 'No description provided.')
prop_description = remove_line_breaks(correct_description(prop_description))
prop_types = prop['type']['names'] if prop.get('type') else []
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
param_types_md = generate_data_types_markdown(prop['type']['names'], enumerations, classes, root)
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
content += "\n"
# Escape outside code blocks
return escape_text_outside_code_blocks(content)
return content
def generate_enumeration_markdown(enumeration, enumerations, classes, example_editor_name):
def generate_enumeration_markdown(enumeration, enumerations, classes):
enum_name = enumeration['name']
description = enumeration.get('description', 'No description provided.')
description = correct_description(description)
@ -262,67 +165,41 @@ def generate_enumeration_markdown(enumeration, enumerations, classes, example_ed
content = f"# {enum_name}\n\n{description}\n\n"
ptype = enumeration['type']['parsedType']
if ptype['type'] == 'TypeUnion':
enum_empty = True # is empty enum
if 'TypeUnion' == enumeration['type']['parsedType']['type']:
content += "## Type\n\nEnumeration\n\n"
content += "## Values\n\n"
# Each top-level name in the union
for raw_t in enumeration['type']['names']:
ts_t = convert_jsdoc_array_to_ts(raw_t)
# Attempt linking: we compare the raw type to enumerations/classes
if any(enum['name'] == raw_t for enum in enumerations):
content += f"- [{ts_t}](../Enumeration/{raw_t}.md)\n"
enum_empty = False
elif raw_t in classes:
content += f"- [{ts_t}](../{raw_t}/{raw_t}.md)\n"
enum_empty = False
elif ts_t.find('Api') == -1:
content += f"- {ts_t}\n"
enum_empty = False
if enum_empty == True:
return None
elements = enumeration['type']['parsedType']['elements']
for element in elements:
element_name = element['name'] if element['type'] != 'NullLiteral' else 'null'
# Check if element is in enumerations or classes before adding link
if any(enum['name'] == element_name for enum in enumerations):
content += f"- [{element_name}](../../Enumeration/{element_name}.md)\n"
elif element_name in classes:
content += f"- [{element_name}](../../{element_name}/{element_name}.md)\n"
else:
content += f"- {element_name}\n"
elif enumeration['properties'] is not None:
content += "## Type\n\nObject\n\n"
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
else:
content += "## Type\n\n"
# If it's not a union and has no properties, simply print the type(s).
types = enumeration['type']['names']
t_md = generate_data_types_markdown(types, enumerations, classes)
content += t_md + "\n\n"
for t in types:
t = generate_data_types_markdown([t], enumerations, classes)
content += t + "\n\n"
# Example
if example:
if '```js' in example:
comment, code = example.split('```js', 1)
comment = remove_js_comments(comment)
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
else:
# If there's no triple-backtick structure
cleaned_example = remove_js_comments(example)
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
# Separate comment and code, and remove comment symbols
comment, code = example.split('```js', 1)
comment = remove_js_comments(comment)
content += f"\n\n## Example\n\n{comment}\n\n```javascript\n{code.strip()}\n"
return escape_text_outside_code_blocks(content)
return content
def process_doclets(data, output_dir, editor_name):
def process_doclets(data, output_dir):
classes = {}
classes_props = {}
enumerations = []
editor_dir = os.path.join(output_dir, editor_name)
example_editor_name = 'editor-'
if editor_name == 'Word':
example_editor_name += 'docx'
elif editor_name == 'Forms':
example_editor_name += 'pdf'
elif editor_name == 'Slide':
example_editor_name += 'pptx'
elif editor_name == 'Cell':
example_editor_name += 'xlsx'
for doclet in data:
if doclet['kind'] == 'class':
@ -340,42 +217,26 @@ def process_doclets(data, output_dir, editor_name):
# Process classes
for class_name, methods in classes.items():
class_dir = os.path.join(editor_dir, class_name)
class_dir = os.path.join(output_dir, class_name)
methods_dir = os.path.join(class_dir, 'Methods')
os.makedirs(methods_dir, exist_ok=True)
# Write class file
class_content = generate_class_markdown(
class_name,
methods,
classes_props[class_name],
enumerations,
classes
)
class_content = generate_class_markdown(class_name, methods, classes_props[class_name], enumerations, classes)
write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content)
# Write method files
for method in methods:
method_file_path = os.path.join(methods_dir, f"{method['name']}.md")
method_content = generate_method_markdown(method, enumerations, classes, example_editor_name)
write_markdown_file(method_file_path, method_content)
if not method.get('example', ''):
missing_examples.append(os.path.relpath(method_file_path, output_dir))
method_content = generate_method_markdown(method, enumerations, classes)
write_markdown_file(os.path.join(methods_dir, f"{method['name']}.md"), method_content)
# Process enumerations
enum_dir = os.path.join(editor_dir, 'Enumeration')
enum_dir = os.path.join(output_dir, 'Enumeration')
os.makedirs(enum_dir, exist_ok=True)
for enum in enumerations:
enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md")
enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name)
if enum_content is None:
continue
write_markdown_file(enum_file_path, enum_content)
if not enum.get('example', ''):
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
enum_content = generate_enumeration_markdown(enum, enumerations, classes)
write_markdown_file(os.path.join(enum_dir, f"{enum['name']}.md"), enum_content)
def generate(output_dir):
print('Generating Markdown documentation...')
@ -383,12 +244,10 @@ def generate(output_dir):
generate_docs_json.generate(output_dir + 'tmp_json', md=True)
for editor_name in editors:
input_file = os.path.join(output_dir + 'tmp_json', editor_name + ".json")
shutil.rmtree(output_dir + f'/{editor_name.title()}')
os.makedirs(output_dir + f'/{editor_name.title()}')
os.makedirs(output_dir + f'/{editor_name.title()}', exist_ok=True)
data = load_json(input_file)
process_doclets(data, output_dir, editor_name.title())
process_doclets(data, output_dir + f'/{editor_name}')
shutil.rmtree(output_dir + 'tmp_json')
print('Done')
@ -403,7 +262,5 @@ if __name__ == "__main__":
default="../../../../office-js-api/" # Default value
)
args = parser.parse_args()
generate(args.destination)
print("START_MISSING_EXAMPLES")
print(",".join(missing_examples))
print("END_MISSING_EXAMPLES")

View File

@ -1,111 +0,0 @@
import os
import subprocess
import json
import argparse
import re
# Configuration files
configs = [
"./config/plugins/common.json",
"./config/plugins/word.json",
"./config/plugins/cell.json",
"./config/plugins/slide.json",
"./config/plugins/forms.json"
]
root = '../../../..'
def generate(output_dir, md=False):
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Generate JSON documentation
for config in configs:
editor_name = config.split('/')[-1].replace('.json', '')
output_file = os.path.join(output_dir, editor_name + ".json")
command = f"npx jsdoc -c {config} -X > {output_file}"
print(f"Generating {editor_name}.json: {command}")
subprocess.run(command, shell=True)
common_doclets_file = os.path.join(output_dir, 'common.json')
with open(common_doclets_file, 'r', encoding='utf-8') as f:
common_doclets_json = json.dumps(json.load(f))
os.remove(common_doclets_file)
# Append examples to JSON documentation
for config in configs:
if (config.find('common') != -1):
continue
editor_name = config.split('/')[-1].replace('.json', '')
example_folder_name = editor_name # name of folder with examples
output_file = os.path.join(output_dir, editor_name + ".json")
# Read the JSON file
with open(output_file, 'r', encoding='utf-8') as f:
data = json.load(f)
start_common_doclet_idx = len(data)
data += json.loads(common_doclets_json)
# Modify JSON data
for idx, doclet in enumerate(data):
if idx == start_common_doclet_idx:
example_folder_name = 'common'
elif editor_name == 'forms':
example_folder_name = 'word'
if 'see' in doclet:
if doclet['see'] is not None:
doclet['see'][0] = doclet['see'][0].replace('{Editor}', example_folder_name.title())
file_path = f'{root}/' + doclet['see'][0]
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as see_file:
example_content = see_file.read()
# Extract the first line as a comment if it exists
lines = example_content.split('\n')
if lines[0].startswith('//'):
comment = lines[0] + '\n'
code_content = '\n'.join(lines[1:])
else:
comment = ''
code_content = example_content
doclet['examples'] = [remove_js_comments(comment) + code_content]
if md == False:
document_type = editor_name
if "forms" == document_type:
document_type = "pdf"
doclet['description'] = doclet['description'] + f'\n\n## Try it\n\n ```js document-builder={{"documentType": "{document_type}"}}\n{code_content}\n```'
# Write the modified JSON file back
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
print("Documentation generation for builder completed.")
def remove_builder_lines(text):
lines = text.splitlines() # Split text into lines
filtered_lines = [line for line in lines if not line.strip().startswith("builder.")]
return "\n".join(filtered_lines)
def remove_js_comments(text):
# Remove single-line comments, leaving text after //
text = re.sub(r'^\s*//\s?', '', text, flags=re.MULTILINE)
# Remove multi-line comments, leaving text after /*
text = re.sub(r'/\*\s*|\s*\*/', '', text, flags=re.DOTALL)
return text.strip()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate documentation")
parser.add_argument(
"destination",
type=str,
help="Destination directory for the generated documentation",
nargs='?', # Indicates the argument is optional
default=f"{root}/office-js-api-declarations/office-js-api-plugins"
)
args = parser.parse_args()
generate(args.destination)

View File

@ -1,467 +0,0 @@
import os
import json
import re
import shutil
import argparse
import generate_docs_plugins_json
# Configuration files
editors = [
"word",
"cell",
"slide",
"forms"
]
missing_examples = []
def load_json(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def write_markdown_file(file_path, content):
with open(file_path, 'w', encoding='utf-8') as md_file:
md_file.write(content)
def remove_js_comments(text):
text = re.sub(r'^\s*//.*$', '', text, flags=re.MULTILINE) # single-line
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
return text.strip()
def correct_description(string):
"""
Cleans up or transforms certain tags in a doclet description:
- <b> => **
- <note>...</note> => 💡 ...
- Provide a default if None.
"""
if string is None:
return 'No description provided.'
# Replace <b> tags with markdown bold
string = re.sub(r'<b>', '**', string)
string = re.sub(r'</b>', '**', string)
# Convert <note>...</note> to a little icon + text
string = re.sub(r'<note>(.*?)</note>', r'💡 \1', string, flags=re.DOTALL)
return string
def correct_default_value(value, enumerations, classes):
if value is None:
return ''
if value == True:
value = "true"
elif value == False:
value = "false"
else:
value = str(value)
return generate_data_types_markdown([value], enumerations, classes)
def remove_line_breaks(string):
return re.sub(r'[\r\n]+', ' ', string)
# Convert Array.<T> => T[] (including nested arrays).
def convert_jsdoc_array_to_ts(type_str: str) -> str:
"""
Recursively replaces 'Array.<T>' with 'T[]',
handling nested arrays like 'Array.<Array.<string>>' => 'string[][]'.
"""
pattern = re.compile(r'Array\.<([^>]+)>')
while True:
match = pattern.search(type_str)
if not match:
break
inner_type = match.group(1).strip()
# Recursively convert inner parts
inner_type = convert_jsdoc_array_to_ts(inner_type)
# Replace the outer Array.<...> with ...[]
type_str = (
type_str[:match.start()]
+ f"{inner_type}[]"
+ type_str[match.end():]
)
return type_str
def escape_text_outside_code_blocks(markdown: str) -> str:
"""
Splits content by fenced code blocks, escapes MDX-unsafe characters
(<, >, {, }) only in the text outside those code blocks.
"""
# A regex to capture fenced code blocks with ```
parts = re.split(r'(```.*?```)', markdown, flags=re.DOTALL)
# Even indices (0, 2, 4, ...) are outside code blocks,
# odd indices (1, 3, 5, ...) are actual code blocks.
for i in range(0, len(parts), 2):
# Only escape in parts outside code blocks
parts[i] = (parts[i]
.replace('<', '&lt;')
.replace('>', '&gt;')
.replace('{', '&#123;')
.replace('}', '&#125;')
)
return "".join(parts)
def get_base_type(ts_type: str) -> str:
"""
Given a TypeScript-like type (e.g. "Drawing[][]"), return the
'base' portion by stripping trailing "[]". For "Drawing[][]",
returns "Drawing". For "Array.<Drawing>", you'd convert it first
to "Drawing[]" then return "Drawing".
"""
while ts_type.endswith('[]'):
ts_type = ts_type[:-2]
return ts_type
def generate_data_types_markdown(types, enumerations, classes, root='../../'):
"""
1) Convert each raw JSDoc type from Array.<T> to T[].
2) Split union types if needed (usually they're provided as separate
elements in 'types' already, but let's be safe).
3) For each type, extract the base type (e.g. "Drawing" from "Drawing[]").
4) If the base type matches an enumeration or class, link the entire
T[]-based string.
5) Join with " | ".
"""
# Convert each raw type from JSDoc to TS
converted = [convert_jsdoc_array_to_ts(t) for t in types] # e.g. ["Drawing[]", "Foo[]", ...]
# For each converted type (like "Drawing[]"), see if the base is in enumerations or classes
def link_if_known(ts_type):
base = get_base_type(ts_type) # e.g. "Drawing" from "Drawing[]"
# Check enumerations first
for enum in enumerations:
if enum['name'] == base:
# Replace the entire token with a link
return f"[{ts_type}]({root}Enumeration/{base}.md)"
# Check classes
if base in classes:
return f"[{ts_type}]({root}{base}/{base}.md)"
# Otherwise just return as-is
return ts_type
# Build final list of possibly-linked types
linked = [link_if_known(ts_t) for ts_t in converted]
# Join them with " | "
param_types_md = r' \| '.join(linked)
# If there's still leftover angle brackets for generics, gently escape or link them
# e.g. "Object.<string, number>" => "Object.&lt;string, number&gt;"
# or do more specialized linking if you want to handle them deeper.
def replace_leftover_generics(match):
element = match.group(1).strip()
return f"&lt;{element}&gt;"
param_types_md = re.sub(r'<([^<>]+)>', replace_leftover_generics, param_types_md)
return param_types_md
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
content += generate_properties_markdown(properties, enumerations, classes)
content += "## Methods\n\n"
for method in methods:
method_name = method['name']
content += f"- [{method_name}](./Methods/{method_name}.md)\n"
# Escape just before returning
return escape_text_outside_code_blocks(content)
def generate_method_markdown(method, enumerations, classes, example_editor_name):
"""
Generates Markdown for a method doclet, relying only on `method['examples']`
(array of strings). Ignores any single `method['example']` field.
"""
method_name = method['name']
description = method.get('description', 'No description provided.')
description = correct_description(description)
params = method.get('params', [])
returns = method.get('returns', [])
memberof = method.get('memberof', '')
# Use the 'examples' array only
examples = method.get('examples', [])
content = f"# {method_name}\n\n{description}\n\n"
# Syntax
param_list = ', '.join([param['name'] for param in params]) if params else ''
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
if memberof:
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
# Parameters
content += "## Parameters\n\n"
if params:
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
for param in params:
param_name = param.get('name', 'Unnamed')
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.')))
param_required = "Required" if not param.get('optional') else "Optional"
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n"
else:
content += "This method doesn't have any parameters.\n"
# Returns
content += "\n## Returns\n\n"
if returns:
return_type_list = returns[0].get('type', {}).get('names', [])
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
content += return_type_md
else:
content += "This method doesn't return any data."
# Process examples array
if examples:
if len(examples) > 1:
content += "\n\n## Examples\n\n"
else:
content += "\n\n## Example\n\n"
for i, ex_line in enumerate(examples, start=1):
# Remove JS comments
cleaned_example = remove_js_comments(ex_line).strip()
# Attempt splitting if the user used ```js
if '```js' in cleaned_example:
comment, code = cleaned_example.split('```js', 1)
comment = comment.strip()
code = code.strip()
if len(examples) > 1:
content += f"**Example {i}:**\n\n{comment}\n\n"
content += f"```javascript {example_editor_name}\n{code}\n```\n"
else:
if len(examples) > 1:
content += f"**Example {i}:**\n\n{comment}\n\n"
# No special fences, just show as code
content += f"```javascript {example_editor_name}\n{cleaned_example}\n```\n"
return escape_text_outside_code_blocks(content)
def generate_properties_markdown(properties, enumerations, classes, root='../'):
if properties is None:
return ''
content = "## Properties\n\n"
content += "| Name | Type | Description |\n"
content += "| ---- | ---- | ----------- |\n"
for prop in properties:
prop_name = prop['name']
prop_description = prop.get('description', 'No description provided.')
prop_description = remove_line_breaks(correct_description(prop_description))
prop_types = prop['type']['names'] if prop.get('type') else []
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
# Escape outside code blocks
return escape_text_outside_code_blocks(content)
def generate_enumeration_markdown(enumeration, enumerations, classes, example_editor_name):
"""
Generates Markdown documentation for a 'typedef' doclet.
This version only works with `enumeration['examples']` (an array of strings),
ignoring any single `enumeration['examples']` field.
"""
enum_name = enumeration['name']
description = enumeration.get('description', 'No description provided.')
description = correct_description(description)
# Only use the 'examples' array
examples = enumeration.get('examples', [])
content = f"# {enum_name}\n\n{description}\n\n"
parsed_type = enumeration['type'].get('parsedType')
if not parsed_type:
# If parsedType is missing, just list 'type.names' if available
type_names = enumeration['type'].get('names', [])
if type_names:
content += "## Type\n\n"
t_md = generate_data_types_markdown(type_names, enumerations, classes)
content += t_md + "\n\n"
else:
ptype = parsed_type['type']
# 1) Handle TypeUnion
if ptype == 'TypeUnion':
content += "## Type\n\nEnumeration\n\n"
content += "## Values\n\n"
for raw_t in enumeration['type']['names']:
# Attempt linking
if any(enum['name'] == raw_t for enum in enumerations):
content += f"- [{raw_t}](../Enumeration/{raw_t}.md)\n"
elif raw_t in classes:
content += f"- [{raw_t}](../{raw_t}/{raw_t}.md)\n"
else:
content += f"- {raw_t}\n"
# 2) Handle TypeApplication (e.g. Object.<string, string>)
elif ptype == 'TypeApplication':
content += "## Type\n\nObject\n\n"
type_names = enumeration['type'].get('names', [])
if type_names:
t_md = generate_data_types_markdown(type_names, enumerations, classes)
content += f"**Type:** {t_md}\n\n"
# 3) If properties are present, treat it like an object
if enumeration.get('properties') is not None:
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
# 4) If it's neither TypeUnion nor TypeApplication, just output the type names
if ptype not in ('TypeUnion', 'TypeApplication'):
type_names = enumeration['type'].get('names', [])
if type_names:
content += "## Type\n\n"
t_md = generate_data_types_markdown(type_names, enumerations, classes)
content += t_md + "\n\n"
# Process examples array
if examples:
if len(examples) > 1:
content += "\n\n## Examples\n\n"
else:
content += "\n\n## Example\n\n"
for i, ex_line in enumerate(examples, start=1):
# Remove JS comments
cleaned_example = remove_js_comments(ex_line).strip()
# Attempt splitting if the user used ```js
if '```js' in cleaned_example:
comment, code = cleaned_example.split('```js', 1)
comment = comment.strip()
code = code.strip()
if len(examples) > 1:
content += f"**Example {i}:**\n\n{comment}\n\n"
content += f"```javascript {example_editor_name}\n{code}\n```\n"
else:
if len(examples) > 1:
content += f"**Example {i}:**\n\n{comment}\n\n"
# No special fences, just show as code
content += f"```javascript {example_editor_name}\n{cleaned_example}\n```\n"
return escape_text_outside_code_blocks(content)
def process_doclets(data, output_dir, editor_name):
classes = {}
classes_props = {}
enumerations = []
editor_dir = os.path.join(output_dir, editor_name)
example_editor_name = 'editor-'
if editor_name == 'Word':
example_editor_name += 'docx'
elif editor_name == 'Forms':
example_editor_name += 'pdf'
elif editor_name == 'Slide':
example_editor_name += 'pptx'
elif editor_name == 'Cell':
example_editor_name += 'xlsx'
for doclet in data:
if doclet['kind'] == 'class':
class_name = doclet['name']
classes[class_name] = []
classes_props[class_name] = doclet.get('properties', None)
elif doclet['kind'] == 'function':
class_name = doclet.get('memberof')
if class_name:
if class_name not in classes:
classes[class_name] = []
classes[class_name].append(doclet)
elif doclet['kind'] == 'typedef':
enumerations.append(doclet)
# Process classes
for class_name, methods in classes.items():
class_dir = os.path.join(editor_dir, class_name)
methods_dir = os.path.join(class_dir, 'Methods')
os.makedirs(methods_dir, exist_ok=True)
# Write class file
class_content = generate_class_markdown(
class_name,
methods,
classes_props[class_name],
enumerations,
classes
)
write_markdown_file(os.path.join(class_dir, f"{class_name}.md"), class_content)
# Write method files
for method in methods:
method_file_path = os.path.join(methods_dir, f"{method['name']}.md")
method_content = generate_method_markdown(method, enumerations, classes, example_editor_name)
write_markdown_file(method_file_path, method_content)
if not method.get('examples', ''):
missing_examples.append(os.path.relpath(method_file_path, output_dir))
# Process enumerations
enum_dir = os.path.join(editor_dir, 'Enumeration')
os.makedirs(enum_dir, exist_ok=True)
for enum in enumerations:
enum_file_path = os.path.join(enum_dir, f"{enum['name']}.md")
enum_content = generate_enumeration_markdown(enum, enumerations, classes, example_editor_name)
if enum_content is None:
continue
write_markdown_file(enum_file_path, enum_content)
if not enum.get('examples', ''):
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
def generate(output_dir):
print('Generating Markdown documentation...')
if output_dir[-1] == '/':
output_dir = output_dir[:-1]
generate_docs_plugins_json.generate(output_dir + '/tmp_json', md=True)
for editor_name in editors:
input_file = os.path.join(output_dir + '/tmp_json', editor_name + ".json")
shutil.rmtree(output_dir + f'/{editor_name.title()}', ignore_errors=True)
os.makedirs(output_dir + f'/{editor_name.title()}')
data = load_json(input_file)
process_doclets(data, output_dir, editor_name.title())
shutil.rmtree(output_dir + '/tmp_json')
print('Done')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate documentation")
parser.add_argument(
"destination",
type=str,
help="Destination directory for the generated documentation",
nargs='?', # Indicates the argument is optional
default="../../../../office-js-api/Plugins/" # Default value
)
args = parser.parse_args()
generate(args.destination)
print("START_MISSING_EXAMPLES")
print(",".join(missing_examples))
print("END_MISSING_EXAMPLES")

View File

@ -1,248 +0,0 @@
import os
import json
import re
import shutil
import argparse
import generate_docs_json
from datetime import datetime
# Configuration files
editors = [
"word",
"cell",
"slide",
"forms"
]
root = '../../../..'
missing_examples = []
def load_json(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def read_file_content(file_path):
try:
with open(file_path, encoding='utf-8') as f:
return f.read()
except Exception as e:
missing_examples.append(file_path)
# print(f"Failed to open file {file_path}: {e}")
return ""
def extract_js_comments_as_text(text):
# Extract single-line comments (after //)
single_line_comments = re.findall(r'^\s*//(.*)$', text, flags=re.MULTILINE)
# Extract multi-line comments (between /* and */)
multi_line_comments = re.findall(r'/\*(.*?)\*/', text, flags=re.DOTALL)
# Combine all found comments into a single list
all_comments = single_line_comments + multi_line_comments
# Join comments into a single text, separated by a space
return " ".join(comment.strip() for comment in all_comments if comment.strip())
def extract_examples_blocks(content: str):
blocks = []
current_block = {"comments": [], "code": []}
in_comment_section = True # Collect comments until code appears
current_comment_group = [] # Accumulate lines of the current comment
for line in content.splitlines():
stripped = line.strip()
if not stripped:
# Empty line
if in_comment_section and current_comment_group:
# Finish the current comment group
comment_text = " ".join(current_comment_group)
current_block["comments"].append(comment_text)
current_comment_group = []
elif not in_comment_section:
# Empty line in the code keep it as is
current_block["code"].append(line)
continue
if stripped.startswith("//"):
if in_comment_section:
# Remove comment marker and extra spaces
current_comment_group.append(extract_js_comments_as_text(stripped))
else:
# Comment after code starts finish the current block and start a new one
blocks.append({
"comments": current_block["comments"],
"code": "\n".join(current_block["code"]).rstrip()
})
current_block = {"comments": [], "code": []}
in_comment_section = True
# Start a new comment group with the current line
current_comment_group = [stripped[2:].strip()]
else:
# Code line
if in_comment_section:
if current_comment_group:
comment_text = " ".join(current_comment_group)
current_block["comments"].append(comment_text)
current_comment_group = []
in_comment_section = False
current_block["code"].append(line)
# Finalize any remaining comment group
if in_comment_section and current_comment_group:
comment_text = " ".join(current_comment_group)
current_block["comments"].append(comment_text)
# Save the last block if it's not empty
if current_block["comments"] or current_block["code"]:
blocks.append({
"comments": current_block["comments"],
"code": "\n".join(current_block["code"]).rstrip()
})
return blocks
def extract_examples_blocks_temp(content: str):
lines = content.splitlines()
comment_blocks = []
current_group = []
first_code_index = None
for i, line in enumerate(lines):
stripped = line.strip()
if not stripped:
if current_group:
comment_blocks.append(" ".join(current_group))
current_group = []
continue
if stripped.startswith("//"):
current_group.append(stripped[2:].strip())
else:
if current_group:
comment_blocks.append(" ".join(current_group))
current_group = []
first_code_index = i
break
code_part = ""
if first_code_index is not None:
code_part = "\n".join(lines[first_code_index:]).rstrip()
return [{"comments": comment_blocks, "code": code_part}]
def create_entry(system_message, user_message, assistant_message, model):
entry = {
"created_at": datetime.now().isoformat(" "),
"messages": [
{"role": "system", "content": system_message},
{"role": "user", "content": user_message},
{"role": "assistant", "content": assistant_message}
],
"recommended": False,
"upvoted": True
}
if model is not "":
entry["model"] = model
return entry
def process_doclets(doclets, output_entries, editor_name, model):
for doclet in doclets:
kind = doclet.get("kind", "").lower()
see = doclet.get("see", [])
# The "see" field must always be present
if not see:
continue
# Processing based on the "kind" value
if kind == "function":
method_name = doclet.get("name", "")
memberof = doclet.get("memberof", "")
# Functions must have both "name" (method_name) and "memberof" fields filled
if not (method_name and memberof):
continue
system_message = (
f"You act as an API expert for Onlyoffice {editor_name.title()} editor. "
f"This task is an example for the function {method_name} in the class {memberof}."
)
default_user_message = f"How do I use the method {method_name} of {memberof} class?"
elif kind == "class":
class_name = doclet.get("name", "")
system_message = (
f"You act as an API expert for Onlyoffice {editor_name.title()} editor. "
f"This task is an example for the class {class_name}."
)
default_user_message = f"How do I instantiate or work with the class {class_name}?"
elif kind == "typedef":
typedef_name = doclet.get("name", "")
system_message = (
f"You act as an API expert for Onlyoffice {editor_name.title()} editor. "
f"This task is an example for the typedef {typedef_name}"
)
default_user_message = f"How do I use the typedef {typedef_name}?"
else:
continue
# Read the content of the first file listed in the "see" field
content = read_file_content(f'{root}/{see[0]}')
if content == "":
continue
# now use only first block cause there is bad comments in examples
blocks = extract_examples_blocks_temp(content)
for block in blocks:
assistant_message = block['code']
# default entry
output_entries.append(create_entry(system_message, default_user_message, assistant_message, model))
# If the file content contains comments, create a separate entry for each one
for comment in block['comments']:
output_entries.append(create_entry(system_message, comment, assistant_message, model))
def generate(output_dir, model):
print('Generating documentation JSONL dataset...')
shutil.rmtree(output_dir, ignore_errors=True)
os.makedirs(output_dir)
generate_docs_json.generate(f'{output_dir}/tmp_json')
output_entries = []
output_filename = "dataset.jsonl"
for editor_name in editors:
input_file = os.path.join(f'{output_dir}/tmp_json', editor_name + ".json")
doclets = load_json(input_file)
process_doclets(doclets, output_entries, editor_name, model)
with open(f'{output_dir}/{output_filename}', "w", encoding="utf-8") as out_file:
for entry in output_entries:
out_file.write(json.dumps(entry, ensure_ascii=False) + "\n")
shutil.rmtree(f'{output_dir}/tmp_json')
print('Done')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate documentation JSONL dataset")
parser.add_argument(
"destination",
type=str,
help="Destination directory for the generated documentation",
nargs='?', # Indicates the argument is optional
default="../../../../office-js-api/dataset" # Default value
)
parser.add_argument(
"model",
type=str,
help="Type of model",
nargs='?', # Indicates the argument is optional
default="" # Default value
)
args = parser.parse_args()
generate(args.destination, args.model)
print("START_MISSING_EXAMPLES")
print(",".join(missing_examples))
print("END_MISSING_EXAMPLES")

View File

@ -1,39 +0,0 @@
import subprocess
def fetch_branches():
#Fetch all branches without tags from the remote.
subprocess.run(['git', 'fetch', '--no-tags', 'origin', '+refs/heads/*:refs/remotes/origin/*'], check=True)
def get_branches():
#Get list of branches in the repository."""
result = subprocess.run(['git', 'branch', '-r'], capture_output=True, text=True)
return [line.strip() for line in result.stdout.splitlines()]
def parse_version(version_str):
#Parse version string and return a tuple of integers (major, minor, patch).
try:
return tuple(map(int, version_str.lstrip('v').split('.')))
except ValueError:
return (0, 0, 0) # Default for non-parsable versions
def get_max_version_branch(branches):
#Find the branch with the highest version.
max_branch = None
max_version = (0, 0, 0)
for branch in branches:
parts = branch.split('/')
if len(parts) >= 2 and (parts[1] == 'hotfix' or parts[1] == 'release'):
version = parse_version(parts[2])
if version > max_version:
max_version = version
max_branch = parts
return max_branch
if __name__ == "__main__":
fetch_branches() # Fetch branches without tags
branches = get_branches()
max_version_branch = get_max_version_branch(branches)
if max_version_branch:
print('/'.join(max_version_branch[1:])) # Print only the branch name without origin

View File

@ -14,15 +14,6 @@ def is_exist_in_array(projects, proj):
return True
return False
def get_full_projects_list(json_data, list):
result = []
for rec in list:
if rec in json_data:
result += get_full_projects_list(json_data, json_data[rec])
else:
result.append(rec)
return result
def adjust_project_params(params):
ret_params = params
@ -95,9 +86,13 @@ def get_projects(pro_json_path, platform):
# check aliases to modules
records_src = data[module]
records = get_full_projects_list(data, records_src)
records = []
#print(records)
for rec in records_src:
if rec in data:
records += data[rec]
else:
records.append(rec)
for rec in records:
params = []
@ -164,18 +159,6 @@ def get_projects(pro_json_path, platform):
if is_append:
result.append(root_dir + record)
# delete duplicates
old_results = result
result = []
map_results = set()
for item in old_results:
proj = item.replace("\\", "/")
if proj in map_results:
continue
map_results.add(proj)
result.append(proj)
if is_log:
print(result)
return result

View File

@ -1,10 +1,6 @@
{
"root" : "../",
"spell" : [
"[win,linux,mac]core/Common/3dParty/hunspell/qt/hunspell.pro"
],
"core" : [
"core/Common/3dParty/cryptopp/project/cryptopp.pro",
@ -22,13 +18,12 @@
"core/HtmlFile2/HtmlFile2.pro",
"core/Fb2File/Fb2File.pro",
"core/EpubFile/CEpubFile.pro",
"core/HwpFile/HWPFile.pro",
"core/Apple/IWork.pro",
"core/HtmlRenderer/htmlrenderer.pro",
"core/DocxRenderer/DocxRenderer.pro",
"core/DesktopEditor/doctrenderer/doctrenderer.pro",
"core/DesktopEditor/doctrenderer/docbuilder.python/src/docbuilder_func_lib.pro",
"[!no_x2t]core/OOXML/Projects/Linux/DocxFormatLib/DocxFormatLib.pro",
"[!no_x2t]core/OOXML/Projects/Linux/PPTXFormatLib/PPTXFormatLib.pro",
@ -56,19 +51,15 @@
"[win,linux,mac,!linux_arm64]core/OfficeCryptReader/ooxml_crypt/ooxml_crypt.pro",
"spell",
"[win,linux,mac,!no_tests]core/DesktopEditor/vboxtester/vboxtester.pro",
"[win,linux,mac,!no_tests]core/Test/Applications/StandardTester/standardtester.pro",
"[win,linux,mac,!no_tests]core/Test/Applications/x2tTester/x2ttester.pro",
"[win,linux,mac,!no_tests]core/Test/Applications/MetafileTester/MetafileTester.pro",
"[win,linux,mac,!no_tests]core/Common/3dParty/hunspell/test/test.pro"
"[win,linux,mac,!no_tests]core/Test/Applications/MetafileTester/MetafileTester.pro"
],
"builder" : [
"core",
"core/DesktopEditor/doctrenderer/docbuilder.python/src/docbuilder_func_lib.pro"
"core"
],
"server" : [
@ -79,8 +70,13 @@
"[win,linux]desktop-sdk/ChromiumBasedEditors/videoplayerlib/videoplayerlib.pro"
],
"spell" : [
"[win,linux,mac]core/Common/3dParty/hunspell/qt/hunspell.pro"
],
"desktop" : [
"core",
"spell",
"multimedia",
"core/DesktopEditor/xmlsec/src/ooxmlsignature.pro",
@ -92,8 +88,7 @@
"[win,linux]desktop-apps/win-linux/ASCDocumentEditor.pro",
"[win]desktop-apps/win-linux/extras/projicons/ProjIcons.pro",
"[win,!win_xp]desktop-apps/win-linux/extras/update-daemon/UpdateDaemon.pro",
"[win_xp]desktop-apps/win-linux/extras/online-installer/OnlineInstaller.pro"
"[win,!win_xp]desktop-apps/win-linux/extras/update-daemon/UpdateDaemon.pro"
],
"mobile" : [

View File

@ -0,0 +1,4 @@
{
"browser" : "chrome",
"browserUrl" : "C:/Program Files/Google/Chrome/Application/chrome.exe"
}

View File

@ -0,0 +1,4 @@
{
"browser" : "firefox",
"browserUrl" : "C:/Program Files/Mozilla Firefox/firefox.exe"
}

View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
import sys
sys.path.append('../../scripts')
import base
import os
os.environ["PUPPETEER_SKIP_CHROMIUM_DOWNLOAD"] = "true"
base.cmd("npm", ["i", "puppeteer"])

64
tests/puppeteer/run.py Normal file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env python
import sys
sys.path.append('../../scripts')
import base
import os
import glob
import json
def get_tests_in_dir(directory):
files = []
for file in glob.glob(directory + "/*.js"):
if base.is_file(file):
files.append(file)
elif is_dir(file):
files += get_tests_in_dir(file)
return files
params = sys.argv[1:]
if (0 == len(params)):
print("use: run.py path_to_config [path_to_test]")
exit(0)
config_path = params[0]
test_file = "./tests"
if (1 < len(params)):
test_file = params[1]
tests_array = [test_file]
if base.is_dir(test_file):
tests_array = get_tests_in_dir(test_file)
config_content = "{}"
with open(config_path, "r") as config_path_loader:
config_content = config_path_loader.read()
print(config_content)
config = json.loads(config_content)
os.environ["PUPPETEER_SKIP_CHROMIUM_DOWNLOAD"] = "true"
if "browser" in config:
print("browser: " + config["browser"])
os.environ["PUPPETEER_PRODUCT"] = config["browser"]
if "browserUrl" in config:
print("browserUrl: " + config["browserUrl"])
os.environ["PUPPETEER_EXECUTABLE_PATH"] = config["browserUrl"]
if not base.is_dir("./work_directory"):
base.create_dir("./work_directory")
base.create_dir("./work_directory/cache")
base.create_dir("./work_directory/downloads")
for test in tests_array:
print("run test: " + test)
run_file = test + ".runned.js"
base.copy_file("./tester.js", run_file)
test_content = base.readFile(test)
test_content = test_content.replace("await Tester.", "Tester.")
test_content = test_content.replace("Tester.", "await Tester.")
base.replaceInFile(run_file, "\"%%CODE%%\"", test_content)
base.cmd("node", [run_file])
base.delete_file(run_file)

171
tests/puppeteer/tester.js Normal file
View File

@ -0,0 +1,171 @@
const puppeteer = require('puppeteer')
const pathfs = require('path')
const fs = require('fs');
function TesterImpl()
{
this.browser = null;
this.page = null;
this.width = 1500;
this.height = 800;
this.pixelRatio = 1;
this.cacheDir = pathfs.resolve("./work_directory/cache");
this.downloadsDir = pathfs.resolve("./work_directory/downloads");
this.downloadCounter = 0;
this.load = async function(url)
{
const head = { x: 100, y: 200 };
this.browser = await puppeteer.launch({
headless: false,
product: process.env["PUPPETEER_PRODUCT"],
args: [
"--disable-infobars",
`--window-size=${this.width+head.x},${this.height+head.y}`,
"--disk-cache-dir=" + this.cacheDir
],
defaultViewport : {width: this.width, height: this.height, deviceScaleFactor : this.pixelRatio }
});
this.page = await this.browser.newPage();
await this.page.setViewport({ width: this.width, height: this.height });
let waitObject = (process.env["PUPPETEER_PRODUCT"] === "firefox") ? { waitUntil: "networkidle0", timeout: 15000 } : {};
await this.page.goto(url + "&autotest=enabled", waitObject);
console.log("[tester] pageLoaded");
return this.page;
};
this.close = async function(nosleep)
{
if (true !== nosleep)
await this.waitAutosave();
await this.browser.close();
};
this.sleep = async function(ms)
{
return await new Promise(resolve => setTimeout(resolve, ms));
};
this.waitEditor = async function()
{
// TODO: wait first onEndRecalculate
await this.sleep(5000);
console.log("[tester] editorReady");
};
this.waitAutosave = async function()
{
await this.sleep(5000);
};
this.evaluateInMainFrame = async function(code)
{
return await this.page.evaluate(code);
};
this.evaluateInEditorFrame = async function(code)
{
const frame = await this.page.frames().find(frame => frame.name() === 'frameEditor');
if (!frame)
return;
return await frame.evaluate(code);
};
this.click = async function(id)
{
let res = await this.evaluateInEditorFrame("document.getElementById(\"" + id + "\").click(); \"[tester] clicked: " + id + "\"");
//console.log(res);
await this.sleep(200);
return res;
};
this.mouseClick = async function(x, y, options)
{
let res = await this.page.mouse.click(x, y, options);
await this.sleep(200);
return res;
};
this.eval = async function(code)
{
let res = await this.evaluateInEditorFrame(code);
await this.sleep(200);
return res;
};
this.keyDown = async function(key)
{
// https://pptr.dev/api/puppeteer.keyinput
let res = await this.page.keyboard.down(key);
await this.sleep(200);
return res;
};
this.keyUp = async function(key)
{
// https://pptr.dev/api/puppeteer.keyinput
let res = await this.page.keyboard.up(key);
await this.sleep(200);
return res;
};
this.keyClick = async function(key)
{
// https://pptr.dev/api/puppeteer.keyinput
let res = await this.page.keyboard.down(key);
res = await this.page.keyboard.up(key);
await this.sleep(200);
return res;
};
this.keyPress = async function(key)
{
// https://pptr.dev/api/puppeteer.keyinput
let res = await this.page.keyboard.press(key);
await this.sleep(200);
return res;
};
this.input = async function(text)
{
let res = await this.page.keyboard.type(text);
await this.sleep(200);
return res;
};
this.downloadFile = async function(format, path)
{
const tmpDir = pathfs.resolve(this.downloadsDir, "./tmp" + this.downloadCounter++);
fs.mkdirSync(tmpDir);
// emulate download
const client = await this.page.target().createCDPSession();
await client.send("Page.setDownloadBehavior", {
behavior: "allow",
downloadPath: tmpDir
});
await this.evaluateInEditorFrame("document.querySelectorAll('[data-layout-name=\"toolbar-file\"]')[0].click();");
await this.sleep(200);
await this.evaluateInEditorFrame("document.getElementsByClassName(\"svg-format-" + format + "\")[0].click();");
await this.sleep(200);
await this.evaluateInEditorFrame("document.getElementById(\"fm-btn-return\").click();");
await this.sleep(2000);
const files = fs.readdirSync(tmpDir);
fs.copyFileSync(pathfs.resolve(tmpDir, "./" + files[0]), pathfs.resolve(path));
fs.rmSync(tmpDir, { recursive: true, force: true });
};
}
const Tester = new TesterImpl;
try {
(async () => {
"%%CODE%%"
})();
} catch (err) {
console.error(err);
}

View File

@ -0,0 +1,27 @@
Tester.load("path_to_file");
Tester.waitEditor();
// down Enter
Tester.keyClick("Enter");
// type text
Tester.input("Hello World!");
Tester.keyPress("ArrowLeft");
Tester.keyDown("Shift");
for (let i = 0; i < 5; i++)
Tester.keyPress("ArrowLeft");
Tester.keyUp("Shift");
// bold
Tester.click("id-toolbar-btn-bold");
// italic
Tester.mouseClick(115, 105);
// if needed
Tester.waitAutosave();
Tester.downloadFile("docx", "./work_directory/new.docx")
Tester.downloadFile("odt", "./work_directory/new.odt")
Tester.close(true);

View File

@ -1,114 +0,0 @@
#!/usr/bin/env python
import sys
sys.path.append('../../scripts')
import base
import os
import glob
import base64
sys.stdin.reconfigure(encoding='utf-8')
sys.stdout.reconfigure(encoding='utf-8')
base.configure_common_apps()
def change_property(data_src, name, value):
data = data_src
creator_open = "<dc:" + name + ">"
creator_close = "</dc:" + name + ">"
open_tag_pos = data.find(creator_open)
if open_tag_pos == -1:
creator_close_to_find = "<dc:" + name + "/>"
else:
creator_close_to_find = "</dc:" + name + ">"
close_tag_pos = data.find(creator_close_to_find)
last_tag_pos = data.find("</cp:coreProperties>")
if open_tag_pos != -1 and close_tag_pos != - 1:
data = data[:open_tag_pos + len(creator_open)] + value + data[close_tag_pos:]
elif close_tag_pos != - 1:
data = data[:close_tag_pos] + creator_open + value + creator_close + data[close_tag_pos + len(creator_close_to_find):]
else:
data = data[:last_tag_pos] + creator_open + value + creator_close + data[last_tag_pos:]
return data
def change_author_name(file_input):
temp_dir = os.getcwd().replace("\\", "/") + "/temp"
base.create_dir(temp_dir)
app = "7za" if ("mac" == base.host_platform()) else "7z"
base.cmd_exe(app, ["x", "-y", file_input, "-o" + temp_dir, "docProps/core.xml", "-r"])
with open(temp_dir + "/docProps/core.xml", 'r', encoding='utf-8') as file:
data = file.read()
data = change_property(data, "creator", "")
data = change_property(data, "lastModifiedBy", "")
with open(temp_dir + "/docProps/core.xml", 'w', encoding='utf-8') as file:
file.write(data)
base.cmd_exe(app, ["a", "-r", file_input, temp_dir + "/docProps"])
base.delete_dir(temp_dir)
def get_files(dir):
arr_files = []
for file in glob.glob(dir + "/*"):
if base.is_file(file):
arr_files.append(file)
elif base.is_dir(file):
arr_files += get_files(file)
return arr_files
def get_local_path(base, src_dir):
test1 = base.replace("\\", "/")
test2 = src_dir.replace("\\", "/")
return test2[len(test1)+1:]
params = sys.argv[1:]
if (3 > len(params)):
print("use: convert.py path_to_x2t_directory path_to_input_directory path_to_output_directory")
exit(0)
base.configure_common_apps()
x2t_directory = params[0]
src_directory = params[1]
dst_directory = params[2]
if base.is_dir(dst_directory):
base.delete_dir(dst_directory)
base.create_dir(dst_directory)
src_files = get_files(src_directory)
for file in src_files:
directory = os.path.dirname(file)
name = os.path.basename(file)
directory_out_file = dst_directory + "/" + get_local_path(src_directory, directory)
if not base.is_dir(directory_out_file):
os.makedirs(directory_out_file, exist_ok=True)
name_without_ext = os.path.splitext(name)[0]
name_ext = os.path.splitext(name)[1][1:]
dst_ext = name_ext
if ("docx" == name_ext) or ("dotx" == name_ext):
dst_ext = "dotx"
elif ("pptx" == name_ext) or ("potx" == name_ext):
dst_ext = "potx"
elif ("xlsx" == name_ext) or ("xltx" == name_ext):
dst_ext = "xltx"
dst_name = name_without_ext
if (len(dst_name) < 4) or (dst_name[0:4] != "[32]"):
dst_name = "[32]" + base64.b32encode(name_without_ext.encode("utf-8")).decode("utf-8")
dst_file = directory_out_file + "/" + dst_name + "." + dst_ext
os.makedirs(directory_out_file, exist_ok=True)
base.cmd_in_dir(x2t_directory, "x2t", [file, dst_file])
change_author_name(dst_file)
print(name_without_ext + " => " + dst_name)

View File

@ -55,23 +55,14 @@ def install_qt():
base.cmd_in_dir("./qt-everywhere-opensource-src-5.9.9", "make", ["-j", "4"])
base.cmd_in_dir("./qt-everywhere-opensource-src-5.9.9", "make", ["install"])
return
def install_qt_prebuild():
url_amd64 = "https://s3.eu-west-1.amazonaws.com/static-doc.teamlab.eu.com/qt/5.9.9/linux_amd64/qt_binary.7z"
base.download(url_amd64, "./qt_amd64.7z")
base.extract("./qt_amd64.7z", "./qt_build")
base.create_dir("./qt_build/Qt-5.9.9")
base.cmd("mv", ["./qt_build/qt_amd64", "./qt_build/Qt-5.9.9/gcc_64"])
base.setup_local_qmake("./qt_build/Qt-5.9.9/gcc_64/bin")
return
if not base.is_file("./node_js_setup_14.x"):
print("install dependencies...")
deps.install_deps()
if not base.is_dir("./qt_build"):
if not base.is_dir("./qt_build"):
print("install qt...")
install_qt_prebuild()
install_qt()
branch = get_branch_name("../..")
@ -112,3 +103,6 @@ build_tools_params = ["--branch", branch,
base.cmd_in_dir("../..", "./configure.py", build_tools_params)
base.cmd_in_dir("../..", "./make.py")

View File

@ -39,14 +39,9 @@ def install_deps():
"libxi-dev",
"libxrender-dev",
"libxss1",
"libncurses5",
"libncurses6",
"curl",
"libxkbcommon-dev",
"libxkbcommon-x11-dev"]
"libncurses5"]
for package in packages:
base.cmd("sudo", ["apt-get", "install", "-y", package], True)
base.cmd("sudo", ["apt-get", "install", "-y"] + packages)
# nodejs
base.cmd("sudo", ["apt-get", "install", "-y", "nodejs"])
@ -84,3 +79,4 @@ def install_deps():
if __name__ == "__main__":
install_deps()

View File

@ -1 +1 @@
8.3.2
8.1.1