Files
rustdesk/build.py
Mariano Abad 3b6914b1b2 feat(drm): phase-2 split, pass the dma-buf fd instead of the converted frame
move the egl detile and rgba pack out of the root --service and into the
unprivileged --server. the root now calls only drmtap_open + drmtap_grab_desc
and exports a raw dma-buf fd; the fd rides the _drm channel over SCM_RIGHTS with
a small descriptor (geometry, per-plane offsets/pitches, modifier, hdr) instead
of the full rgba frame, dropping the per-frame copy. the --server imports the fd
with drmtap_open_render + drmtap_convert_dmabuf, keyed by the import-once egl
cache, and the render context is created and dropped on the recv thread.

the _drm transport moves off Framed<BytesCodec> (which cannot carry a fd) to a
bespoke sendmsg/recvmsg framing (DrmConn) that attaches one SCM_RIGHTS cmsg only
when a fd is present and rejects a truncated ancillary message. the split
symbols are bound optionally so an older libdrmtap still loads the cpu path, and
the whole thing degrades to the cpu BGRA path or PipeWire when no render node is
available. pins libdrmtap-sys to =0.4.13 with the Cargo.lock checksum. folds in
the DP-MST, ldconfig-restart and per-display PipeWire-fallback review fixes and a
udev hotplug refresh.
2026-07-24 08:50:16 -03:00

786 lines
35 KiB
Python
Executable File

#!/usr/bin/env python3
import os
import glob
import pathlib
import platform
import zipfile
import urllib.request
import shutil
import hashlib
import argparse
import sys
from pathlib import Path
windows = platform.platform().startswith('Windows')
osx = platform.platform().startswith(
'Darwin') or platform.platform().startswith("macOS")
hbb_name = 'rustdesk' + ('.exe' if windows else '')
exe_path = 'target/release/' + hbb_name
if windows:
win_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64'
flutter_build_dir = f'build/windows/{win_arch}/runner/Release/'
elif osx:
flutter_build_dir = 'build/macos/Build/Products/Release/'
else:
flutter_build_dir = 'build/linux/x64/release/bundle/'
flutter_build_dir_2 = f'flutter/{flutter_build_dir}'
skip_cargo = False
def get_deb_arch() -> str:
custom_arch = os.environ.get("DEB_ARCH")
if custom_arch is None:
return "amd64"
return custom_arch
def get_deb_extra_depends() -> str:
custom_arch = os.environ.get("DEB_ARCH")
if custom_arch == "armhf": # for arm32v7 libsciter-gtk.so
return ", libatomic1"
return ""
def system2(cmd):
exit_code = os.system(cmd)
if exit_code != 0:
sys.stderr.write(f"Error occurred when executing: `{cmd}`. Exiting.\n")
sys.exit(-1)
def get_version():
with open("Cargo.toml", encoding="utf-8") as fh:
for line in fh:
if line.startswith("version"):
return line.replace("version", "").replace("=", "").replace('"', '').strip()
return ''
def parse_rc_features(feature):
available_features = {}
apply_features = {}
if not feature:
feature = []
def platform_check(platforms):
if windows:
return 'windows' in platforms
elif osx:
return 'osx' in platforms
else:
return 'linux' in platforms
def get_all_features():
features = []
for (feat, feat_info) in available_features.items():
if platform_check(feat_info['platform']):
features.append(feat)
return features
if isinstance(feature, str) and feature.upper() == 'ALL':
return get_all_features()
elif isinstance(feature, list):
if windows:
# download third party is deprecated, we use github ci instead.
# feature.append('PrivacyMode')
pass
for feat in feature:
if isinstance(feat, str) and feat.upper() == 'ALL':
return get_all_features()
if feat in available_features:
if platform_check(available_features[feat]['platform']):
apply_features[feat] = available_features[feat]
else:
print(f'Unrecognized feature {feat}')
return apply_features
else:
raise Exception(f'Unsupported features param {feature}')
def make_parser():
parser = argparse.ArgumentParser(description='Build script.')
parser.add_argument(
'-f',
'--feature',
dest='feature',
metavar='N',
type=str,
nargs='+',
default='',
help='Integrate features, windows only.'
'Available: [Not used for now]. Special value is "ALL" and empty "". Default is empty.')
parser.add_argument('--flutter', action='store_true',
help='Build flutter package', default=False)
parser.add_argument(
'--hwcodec',
action='store_true',
help='Enable feature hwcodec' + (
'' if windows or osx else ', need libva-dev.')
)
parser.add_argument(
'--vram',
action='store_true',
help='Enable feature vram, only available on windows now.'
)
parser.add_argument(
'--portable',
action='store_true',
help='Build windows portable'
)
parser.add_argument(
'--unix-file-copy-paste',
action='store_true',
help='Build with unix file copy paste feature'
)
parser.add_argument(
'--drm',
action='store_true',
help='Linux only: build the DRM/KMS capture backend (bundles libdrmtap.so, '
'dlopen-ed in-process by the root service). Off by default.'
)
parser.add_argument(
'--skip-cargo',
action='store_true',
help='Skip cargo build process, only flutter version + Linux supported currently'
)
if windows:
parser.add_argument(
'--skip-portable-pack',
action='store_true',
help='Skip packing, only flutter version + Windows supported'
)
parser.add_argument(
"--package",
type=str
)
if osx:
parser.add_argument(
'--screencapturekit',
action='store_true',
help='Enable feature screencapturekit'
)
return parser
# Generate build script for docker
#
# it assumes all build dependencies are installed in environments
# Note: do not use it in bare metal, or may break build environments
def generate_build_script_for_docker():
with open("/tmp/build.sh", "w") as f:
f.write('''
#!/bin/bash
# environment
export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation: " | cut -d' ' -f4-)/include"
# flutter
pushd /opt
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.0.5-stable.tar.xz
tar -xvf flutter_linux_3.0.5-stable.tar.xz
export PATH=`pwd`/flutter/bin:$PATH
popd
# flutter_rust_bridge
dart pub global activate ffigen --version 5.0.1
pushd /tmp && git clone https://github.com/SoLongAndThanksForAllThePizza/flutter_rust_bridge --depth=1 && popd
pushd /tmp/flutter_rust_bridge/frb_codegen && cargo install --path . --locked && popd
pushd flutter && flutter pub get && popd
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
# install vcpkg
pushd /opt
export VCPKG_ROOT=`pwd`/vcpkg
git clone https://github.com/microsoft/vcpkg
vcpkg/bootstrap-vcpkg.sh
popd
$VCPKG_ROOT/vcpkg install --x-install-root="$VCPKG_ROOT/installed"
# build rustdesk
./build.py --flutter --hwcodec
''')
system2("chmod +x /tmp/build.sh")
system2("bash /tmp/build.sh")
# Downloading third party resources is deprecated.
# We can use this function in an offline build environment.
# Even in an online environment, we recommend building third-party resources yourself.
def download_extract_features(features, res_dir):
import re
proxy = ''
def req(url):
if not proxy:
return url
else:
r = urllib.request.Request(url)
r.set_proxy(proxy, 'http')
r.set_proxy(proxy, 'https')
return r
for (feat, feat_info) in features.items():
includes = feat_info['include'] if 'include' in feat_info and feat_info['include'] else []
includes = [re.compile(p) for p in includes]
excludes = feat_info['exclude'] if 'exclude' in feat_info and feat_info['exclude'] else []
excludes = [re.compile(p) for p in excludes]
print(f'{feat} download begin')
download_filename = feat_info['zip_url'].split('/')[-1]
checksum_md5_response = urllib.request.urlopen(
req(feat_info['checksum_url']))
for line in checksum_md5_response.read().decode('utf-8').splitlines():
if line.split()[1] == download_filename:
checksum_md5 = line.split()[0]
filename, _headers = urllib.request.urlretrieve(feat_info['zip_url'],
download_filename)
md5 = hashlib.md5(open(filename, 'rb').read()).hexdigest()
if checksum_md5 != md5:
raise Exception(f'{feat} download failed')
print(f'{feat} download end. extract bein')
zip_file = zipfile.ZipFile(filename)
zip_list = zip_file.namelist()
for f in zip_list:
file_exclude = False
for p in excludes:
if p.match(f) is not None:
file_exclude = True
break
if file_exclude:
continue
file_include = False if includes else True
for p in includes:
if p.match(f) is not None:
file_include = True
break
if file_include:
print(f'extract file {f}')
zip_file.extract(f, res_dir)
zip_file.close()
os.remove(download_filename)
print(f'{feat} extract end')
def external_resources(flutter, args, res_dir):
features = parse_rc_features(args.feature)
if not features:
return
print(f'Build with features {list(features.keys())}')
if os.path.isdir(res_dir) and not os.path.islink(res_dir):
shutil.rmtree(res_dir)
elif os.path.exists(res_dir):
raise Exception(f'Find file {res_dir}, not a directory')
os.makedirs(res_dir, exist_ok=True)
download_extract_features(features, res_dir)
if flutter:
os.makedirs(flutter_build_dir_2, exist_ok=True)
for f in pathlib.Path(res_dir).iterdir():
print(f'{f}')
if f.is_file():
shutil.copy2(f, flutter_build_dir_2)
else:
shutil.copytree(f, f'{flutter_build_dir_2}{f.stem}')
def get_features(args):
features = ['inline'] if not args.flutter else []
if args.hwcodec:
features.append('hwcodec')
if args.vram:
features.append('vram')
if args.flutter:
features.append('flutter')
if args.unix_file_copy_paste:
features.append('unix-file-copy-paste')
if not windows and not osx and args.drm:
features.append('drm')
if osx:
if args.screencapturekit:
features.append('screencapturekit')
print("features:", features)
return features
def generate_control_file(version, extra_depends="", package_name="rustdesk"):
control_file_path = "../res/DEBIAN/control"
system2('/bin/rm -rf %s' % control_file_path)
# An alternative-build package (e.g. the opt-in unattended-wayland / DRM
# variant) installs the same files as the stock `rustdesk` package, so it
# must conflict with / replace it: you install one OR the other, not both.
variant_control = ""
if package_name != "rustdesk":
variant_control = "Conflicts: rustdesk\nReplaces: rustdesk\nProvides: rustdesk\n"
content = """Package: %s
Section: net
Priority: optional
Version: %s
Architecture: %s
Maintainer: rustdesk <info@rustdesk.com>
Homepage: https://rustdesk.com
%sDepends: libgtk-3-0t64 | libgtk-3-0, libxcb-randr0, libxdo3 | libxdo4, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2t64 | libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s%s
Recommends: libayatana-appindicator3-1
Description: A remote control software.
""" % (package_name, version, get_deb_arch(), variant_control, get_deb_extra_depends(), extra_depends)
file = open(control_file_path, "w")
file.write(content)
file.close()
def ffi_bindgen_function_refactor():
# workaround ffigen
system2(
'sed -i "s/ffi.NativeFunction<ffi.Bool Function(DartPort/ffi.NativeFunction<ffi.Uint8 Function(DartPort/g" flutter/lib/generated_bridge.dart')
# libdrmtap is fetched at build time by cloning the rustdesk-org fork at a pinned
# ref — the same way rustdesk sources its other native build deps (vcpkg,
# flutter_rust_bridge, ...), rather than carrying a git submodule. The ref is an
# EXACT release tag (vX.Y.Z), NOT a moving branch: the bundled runtime .so version must
# match the `libdrmtap-sys = "=0.4.13"` crate pinned in libs/scrap/Cargo.toml (the dlopen
# loader only checks ABI-major, so a minor skew between the two artifacts would go undetected).
# Bump this tag and the crate pin together. Override the repo/ref via env (DRMTAP_REPO /
# DRMTAP_REF) for local testing or another fork.
LIBDRMTAP_REPO = os.environ.get('DRMTAP_REPO', 'https://github.com/rustdesk-org/libdrmtap')
LIBDRMTAP_REF = os.environ.get('DRMTAP_REF', 'v0.4.13')
def _single_real_so(paths, where):
# Return the one real libdrmtap.so.0.* object among `paths`, failing if there are zero or several.
# glob order is arbitrary, so silently taking [0] could ship a stale or wrong-arch object left
# over from an earlier build; a mismatch should fail the build loudly instead.
real = sorted(p for p in paths if os.path.isfile(p) and not os.path.islink(p))
if len(real) != 1:
raise Exception(
f'expected exactly one real libdrmtap.so.0.* in {where}, found {len(real)}: {real}')
return real[0]
def build_libdrmtap_so():
# Build libdrmtap.so from the rustdesk-org fork cloned at LIBDRMTAP_REF. The
# pivot dlopen-s this .so in-process in the root service (which already holds
# CAP_SYS_ADMIN) — no setcap helper, no privileged child. Only the shared
# library target is built (the source also carries a helper binary we do not
# ship). Returns the path to the built versioned .so (e.g. libdrmtap.so.0.4.x).
repo_root = os.path.dirname(os.path.abspath(__file__))
# Allow a caller (e.g. CI) to build the .so ahead of time and hand it in via
# DRMTAP_PREBUILT_DIR (must contain the real libdrmtap.so.0.* object).
prebuilt_dir = os.environ.get('DRMTAP_PREBUILT_DIR')
if prebuilt_dir:
# DRMTAP_PREBUILT_DIR explicitly names the artifact source, so honor it strictly: fail
# (rather than silently falling back to a source build) if it holds no single real .so.
prebuilt = glob.glob(os.path.join(prebuilt_dir, 'libdrmtap.so.0.*'))
return _single_real_so(prebuilt, f'DRMTAP_PREBUILT_DIR={prebuilt_dir}')
# Clone the pinned source if it is not already present (a shallow clone at the
# ref). third_party/libdrmtap is not a submodule anymore; it is git-ignored.
src = os.path.join(repo_root, 'third_party', 'libdrmtap')
if not os.path.exists(os.path.join(src, 'meson.build')):
if os.path.isdir(src):
shutil.rmtree(src)
os.makedirs(os.path.dirname(src), exist_ok=True)
system2(f'git clone --depth 1 --branch {LIBDRMTAP_REF} {LIBDRMTAP_REPO} {src}')
build_dir = os.path.join(src, 'build-pkg')
if not os.path.exists(os.path.join(build_dir, 'build.ninja')):
system2(f'meson setup {build_dir} {src} --buildtype=release')
# Build only the shared library target ('drmtap'), not the bundled helper binary.
system2(f'meson compile -C {build_dir} drmtap')
sos = glob.glob(os.path.join(build_dir, 'libdrmtap.so.0.*'))
# keep the real object (libdrmtap.so.0.4.x), not the .so/.so.0 symlinks or meson's .p dir, and
# require exactly one so a stale object from an earlier build is never silently picked.
return _single_real_so(sos, f'the libdrmtap meson build dir {build_dir}')
def append_drm_ldconfig_postinst():
# The DRM package installs libdrmtap.so under a private dir; register it with the
# dynamic linker so the in-process dlopen("libdrmtap.so.0") resolves. Only the DRM
# package calls this, so the stock package's postinst stays byte-identical to upstream.
#
# This block is appended AFTER the stock postinst, which has already run
# `systemctl start rustdesk`. On a FRESH install that ordering is a trap: the root
# service's DRM pre-warm can dlopen("libdrmtap.so.0") BEFORE this ldconfig has
# populated the linker cache, the dlopen fails, and that failure is cached in the
# DRMTAP_LIB OnceLock for the life of the process — so DRM stays disabled until a
# manual restart. So immediately after ldconfig we `try-restart` the unit: it re-runs
# the pre-warm against the now-resolvable soname. `try-restart` is a no-op when the
# unit is not running, so it never spuriously starts the service.
with open('tmpdeb/DEBIAN/postinst', 'a') as f:
f.write(
'\n'
'if [ "$1" = configure ] && [ -d /usr/lib/rustdesk ]; then\n'
'\tldconfig /usr/lib/rustdesk 2>/dev/null || ldconfig 2>/dev/null || true\n'
'\tif command -v systemctl >/dev/null 2>&1; then\n'
'\t\tsystemctl try-restart rustdesk 2>/dev/null || true\n'
'\tfi\n'
'fi\n'
)
def finalize_deb(version, ships_so, so_basename=None):
# Shared deb finalization for build_flutter_deb / build_deb_from_folder. Any DRM .so is assumed
# already staged at tmpdeb/usr/lib/rustdesk/. For a DRM build this adds the soname symlink + the
# ld.so.conf.d drop-in, names the package rustdesk-unattended-wayland with libdrmtap's runtime
# deps (libdrm / EGL / GLESv2), and appends the ldconfig postinst; otherwise it builds the stock
# rustdesk package. Then it writes the control, checksums, builds, and renames the .deb.
if ships_so:
system2(f'ln -sf {so_basename} tmpdeb/usr/lib/rustdesk/libdrmtap.so.0')
system2('mkdir -p tmpdeb/etc/ld.so.conf.d')
with open('tmpdeb/etc/ld.so.conf.d/rustdesk-unattended-wayland.conf', 'w') as f:
f.write('/usr/lib/rustdesk\n')
package_name = 'rustdesk-unattended-wayland' if ships_so else 'rustdesk'
drm_depends = ", libdrm2, libegl1, libgles2" if ships_so else ""
system2('mkdir -p tmpdeb/DEBIAN')
generate_control_file(version, drm_depends, package_name)
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
if ships_so:
append_drm_ldconfig_postinst()
md5_file_folder("tmpdeb/")
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
system2('/bin/rm -rf tmpdeb/')
system2('/bin/rm -rf ../res/DEBIAN/control')
os.rename('rustdesk.deb', f'../{package_name}-{version}.deb')
def build_flutter_deb(version, features):
if not skip_cargo:
system2(f'cargo build --locked --features {features} --lib --release')
ffi_bindgen_function_refactor()
os.chdir('flutter')
system2('flutter build linux --release')
system2('mkdir -p tmpdeb/usr/bin/')
system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mkdir -p tmpdeb/etc/rustdesk/')
system2('mkdir -p tmpdeb/etc/pam.d/')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
system2('mkdir -p tmpdeb/usr/share/applications/')
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
system2('rm tmpdeb/usr/bin/rustdesk || true')
system2(
f'cp -r {flutter_build_dir}/* tmpdeb/usr/share/rustdesk/')
system2(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2(
'cp ../res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png')
system2(
'cp ../res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg')
system2(
'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
system2(
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
system2(
'cp ../res/startwm.sh tmpdeb/etc/rustdesk/')
system2(
'cp ../res/xorg.conf tmpdeb/etc/rustdesk/')
system2(
'cp ../res/pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
system2(
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
# Bundle libdrmtap.so for the DRM/KMS capture path — but ONLY when this build
# actually enabled the `drm` feature, so normal packages stay opt-out. The root
# service dlopen-s it in-process (no setcap helper); it lives in a private dir
# that postinst registers with ldconfig so dlopen("libdrmtap.so.0") resolves.
# Bundle libdrmtap.so for a DRM build (opt-in), then finalize the deb. A DRM build ships as a
# separately-named rustdesk-unattended-wayland package (finalize_deb marks it
# Conflicts/Replaces/Provides rustdesk), so installing it is an explicit choice.
ships_so = 'drm' in features
so_basename = None
if ships_so:
so_path = build_libdrmtap_so()
so_basename = os.path.basename(so_path)
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2(f'cp {so_path} tmpdeb/usr/lib/rustdesk/')
finalize_deb(version, ships_so, so_basename)
os.chdir("..")
def build_deb_from_folder(version, binary_folder):
os.chdir('flutter')
system2('mkdir -p tmpdeb/usr/bin/')
system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
system2('mkdir -p tmpdeb/usr/share/applications/')
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
system2('rm tmpdeb/usr/bin/rustdesk || true')
system2(
f'cp -r ../{binary_folder}/* tmpdeb/usr/share/rustdesk/')
system2(
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2(
'cp ../res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png')
system2(
'cp ../res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg')
system2(
'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
system2(
'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
system2(
"echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit")
# A staged bundle (binary_folder) carries its own libdrmtap.so.0* for a --drm build, so we do
# not rebuild it here; the `cp -r` above placed it under usr/share/rustdesk/. Move it to the
# private lib dir, then finalize the deb the same way build_flutter_deb does.
bundled_glob = glob.glob('tmpdeb/usr/share/rustdesk/libdrmtap.so.0.*')
ships_so = any(os.path.isfile(p) and not os.path.islink(p) for p in bundled_glob)
so_basename = None
if ships_so:
so = _single_real_so(bundled_glob, 'the staged --drm bundle')
so_basename = os.path.basename(so)
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
system2(f'mv {so} tmpdeb/usr/lib/rustdesk/')
system2('rm -f tmpdeb/usr/share/rustdesk/libdrmtap.so tmpdeb/usr/share/rustdesk/libdrmtap.so.0')
finalize_deb(version, ships_so, so_basename)
os.chdir("..")
def build_flutter_dmg(version, features):
if not skip_cargo:
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
system2(
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --locked --features {features} --release')
# copy dylib
system2(
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
os.chdir('flutter')
# cargo builds a single-arch dylib for the host; restrict Xcode to the same arch
# so the universal-by-default ARCHS_STANDARD doesn't try to link a missing slice.
# FLUTTER_XCODE_* env vars are forwarded to xcodebuild as build settings.
mac_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x86_64'
system2(
f'FLUTTER_XCODE_ARCHS={mac_arch} FLUTTER_XCODE_ONLY_ACTIVE_ARCH=YES flutter build macos --release')
system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
'''
system2(
"create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
os.rename("rustdesk.dmg", f"../rustdesk-{version}.dmg")
'''
os.chdir("..")
def build_flutter_arch_manjaro(version, features):
if not skip_cargo:
system2(f'cargo build --locked --features {features} --lib --release')
ffi_bindgen_function_refactor()
os.chdir('flutter')
system2('flutter build linux --release')
system2(f'strip {flutter_build_dir}/lib/librustdesk.so')
os.chdir('../res')
system2('HBB=`pwd`/.. FLUTTER=1 makepkg -f')
def build_flutter_windows(version, features, skip_portable_pack):
if not skip_cargo:
system2(f'cargo build --locked --features {features} --lib --release')
if not os.path.exists("target/release/librustdesk.dll"):
print("cargo build failed, please check rust source code.")
exit(-1)
os.chdir('flutter')
system2('flutter build windows --release')
os.chdir('..')
shutil.copy2('target/release/deps/dylib_virtual_display.dll',
flutter_build_dir_2)
if skip_portable_pack:
return
os.chdir('libs/portable')
system2('pip3 install -r requirements.txt')
system2(
f'python3 ./generate.py -f ../../{flutter_build_dir_2} -o . -e ../../{flutter_build_dir_2}/rustdesk.exe')
os.chdir('../..')
if os.path.exists('./rustdesk_portable.exe'):
os.replace('./target/release/rustdesk-portable-packer.exe',
'./rustdesk_portable.exe')
else:
os.rename('./target/release/rustdesk-portable-packer.exe',
'./rustdesk_portable.exe')
print(
f'output location: {os.path.abspath(os.curdir)}/rustdesk_portable.exe')
os.rename('./rustdesk_portable.exe', f'./rustdesk-{version}-install.exe')
print(
f'output location: {os.path.abspath(os.curdir)}/rustdesk-{version}-install.exe')
def main():
global skip_cargo
parser = make_parser()
args = parser.parse_args()
if os.path.exists(exe_path):
os.unlink(exe_path)
if os.path.isfile('/usr/bin/pacman'):
system2('git checkout src/ui/common.tis')
version = get_version()
features = ','.join(get_features(args))
flutter = args.flutter
if not flutter:
system2('python3 res/inline-sciter.py')
print(args.skip_cargo)
if args.skip_cargo:
skip_cargo = True
portable = args.portable
package = args.package
if package:
build_deb_from_folder(version, package)
return
res_dir = 'resources'
external_resources(flutter, args, res_dir)
if windows:
# build virtual display dynamic library
os.chdir('libs/virtual_display/dylib')
system2('cargo build --locked --release')
os.chdir('../../..')
if flutter:
build_flutter_windows(version, features, args.skip_portable_pack)
return
system2('cargo build --locked --release --features ' + features)
# system2('upx.exe target/release/rustdesk.exe')
system2('mv target/release/rustdesk.exe target/release/RustDesk.exe')
pa = os.environ.get('P')
if pa:
# https://certera.com/kb/tutorial-guide-for-safenet-authentication-client-for-code-signing/
system2(
f'signtool sign /a /v /p {pa} /debug /f .\\cert.pfx /t http://timestamp.digicert.com '
'target\\release\\rustdesk.exe')
else:
print('Not signed')
os.makedirs(res_dir, exist_ok=True)
system2(
f'cp -rf target/release/RustDesk.exe {res_dir}')
os.chdir('libs/portable')
system2('pip3 install -r requirements.txt')
system2(
f'python3 ./generate.py -f ../../{res_dir} -o . -e ../../{res_dir}/rustdesk-{version}-win7-install.exe')
system2(f'mv ../../{res_dir}/rustdesk-{version}-win7-install.exe ../..')
elif os.path.isfile('/usr/bin/pacman'):
# pacman -S -needed base-devel
system2("sed -i 's/pkgver=.*/pkgver=%s/g' res/PKGBUILD" % version)
if flutter:
build_flutter_arch_manjaro(version, features)
else:
system2('cargo build --locked --release --features ' + features)
system2('git checkout src/ui/common.tis')
system2('strip target/release/rustdesk')
system2('ln -s res/pacman_install && ln -s res/PKGBUILD')
system2('HBB=`pwd` makepkg -f')
system2('mv rustdesk-%s-0-x86_64.pkg.tar.zst rustdesk-%s-manjaro-arch.pkg.tar.zst' % (
version, version))
# pacman -U ./rustdesk.pkg.tar.zst
elif os.path.isfile('/usr/bin/yum'):
system2('cargo build --locked --release --features ' + features)
system2('strip target/release/rustdesk')
system2(
"sed -i 's/Version: .*/Version: %s/g' res/rpm.spec" % version)
system2('HBB=`pwd` rpmbuild -ba res/rpm.spec')
system2(
'mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-fedora28-centos8.rpm' % (
version, version))
# yum localinstall rustdesk.rpm
elif os.path.isfile('/usr/bin/zypper'):
system2('cargo build --locked --release --features ' + features)
system2('strip target/release/rustdesk')
system2(
"sed -i 's/Version: .*/Version: %s/g' res/rpm-suse.spec" % version)
system2('HBB=`pwd` rpmbuild -ba res/rpm-suse.spec')
system2(
'mv $HOME/rpmbuild/RPMS/x86_64/rustdesk-%s-0.x86_64.rpm ./rustdesk-%s-suse.rpm' % (
version, version))
# yum localinstall rustdesk.rpm
else:
if flutter:
if osx:
build_flutter_dmg(version, features)
pass
else:
# system2(
# 'mv target/release/bundle/deb/rustdesk*.deb ./flutter/rustdesk.deb')
build_flutter_deb(version, features)
else:
system2('cargo --locked bundle --release --features ' + features)
if osx:
system2(
'strip target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk')
system2(
'cp libsciter.dylib target/release/bundle/osx/RustDesk.app/Contents/MacOS/')
# https://github.com/sindresorhus/create-dmg
system2('/bin/rm -rf *.dmg')
pa = os.environ.get('P')
if pa:
system2('''
# buggy: rcodesign sign ... path/*, have to sign one by one
# install rcodesign via cargo install apple-codesign
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/rustdesk
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/libsciter.dylib
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./target/release/bundle/osx/RustDesk.app
# goto "Keychain Access" -> "My Certificates" for below id which starts with "Developer ID Application:"
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app/Contents/MacOS/*
codesign -s "Developer ID Application: {0}" --force --options runtime ./target/release/bundle/osx/RustDesk.app
'''.format(pa))
system2(
'create-dmg "RustDesk %s.dmg" "target/release/bundle/osx/RustDesk.app"' % version)
os.rename('RustDesk %s.dmg' %
version, 'rustdesk-%s.dmg' % version)
if pa:
system2('''
# https://pyoxidizer.readthedocs.io/en/apple-codesign-0.14.0/apple_codesign.html
# https://pyoxidizer.readthedocs.io/en/stable/tugger_code_signing.html
# https://developer.apple.com/developer-id/
# goto xcode and login with apple id, manager certificates (Developer ID Application and/or Developer ID Installer) online there (only download and double click (install) cer file can not export p12 because no private key)
#rcodesign sign --p12-file ~/.p12/rustdesk-developer-id.p12 --p12-password-file ~/.p12/.cert-pass --code-signature-flags runtime ./rustdesk-{1}.dmg
codesign -s "Developer ID Application: {0}" --force --options runtime ./rustdesk-{1}.dmg
# https://appstoreconnect.apple.com/access/api
# https://gregoryszorc.com/docs/apple-codesign/stable/apple_codesign_getting_started.html#apple-codesign-app-store-connect-api-key
# p8 file is generated when you generate api key (can download only once)
rcodesign notary-submit --api-key-path ../.p12/api-key.json --staple rustdesk-{1}.dmg
# verify: spctl -a -t exec -v /Applications/RustDesk.app
'''.format(pa, version))
else:
print('Not signed')
else:
# build deb package
system2(
'mv target/release/bundle/deb/rustdesk*.deb ./rustdesk.deb')
system2('dpkg-deb -R rustdesk.deb tmpdeb')
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
system2(
'cp res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
system2(
'cp res/128x128@2x.png tmpdeb/usr/share/icons/hicolor/256x256/apps/rustdesk.png')
system2(
'cp res/scalable.svg tmpdeb/usr/share/icons/hicolor/scalable/apps/rustdesk.svg')
system2(
'cp res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop')
system2(
'cp res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop')
os.system('mkdir -p tmpdeb/etc/rustdesk/')
os.system('cp -a res/startwm.sh tmpdeb/etc/rustdesk/')
os.system('mkdir -p tmpdeb/etc/X11/rustdesk/')
os.system('cp res/xorg.conf tmpdeb/etc/X11/rustdesk/')
os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/')
os.system('mkdir -p tmpdeb/etc/pam.d/')
os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
system2('strip tmpdeb/usr/bin/rustdesk')
system2('mkdir -p tmpdeb/usr/share/rustdesk')
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/share/rustdesk/')
system2('cp libsciter-gtk.so tmpdeb/usr/share/rustdesk/')
md5_file_folder("tmpdeb/")
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
def md5_file(fn):
md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest()
system2('echo "%s /%s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
def md5_file_folder(base_dir):
base_path = Path(base_dir)
for file in base_path.rglob('*'):
if file.is_file() and 'DEBIAN' not in file.parts:
relative_path = file.relative_to(base_path)
md5_file(str(relative_path))
if __name__ == "__main__":
main()