1070 lines
30 KiB
YAML
1070 lines
30 KiB
YAML
name: Build RustDesk Android
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- ".gitea/workflows/android.yml"
|
|
|
|
env:
|
|
# Change only this value to build another RustDesk tag/release.
|
|
RUSTDESK_VERSION: "1.4.9"
|
|
|
|
# Target constants, not software versions.
|
|
ANDROID_ABI: arm64-v8a
|
|
RUST_TARGET: aarch64-linux-android
|
|
FLUTTER_TARGET: android-arm64
|
|
|
|
jobs:
|
|
build-android:
|
|
name: Build RustDesk Android ARM64
|
|
runs-on: rustdesk-android
|
|
|
|
steps:
|
|
# -----------------------------------------------------------------------
|
|
# RustDesk source
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Clone RustDesk
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
rm -rf rustdesk
|
|
|
|
git clone \
|
|
--branch "${RUSTDESK_VERSION}" \
|
|
--depth 1 \
|
|
--recurse-submodules \
|
|
--shallow-submodules \
|
|
https://github.com/rustdesk/rustdesk.git \
|
|
rustdesk
|
|
|
|
cd rustdesk
|
|
|
|
echo "RustDesk commit:"
|
|
git rev-parse HEAD
|
|
|
|
echo
|
|
echo "RustDesk tag:"
|
|
git describe --tags --always
|
|
|
|
echo
|
|
echo "Submodules:"
|
|
git submodule status
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Resolve versions from the selected upstream RustDesk tag.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Resolve upstream build configuration
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
python3 <<'PY' > build-config.env
|
|
import re
|
|
from pathlib import Path
|
|
|
|
repo = Path("rustdesk")
|
|
|
|
flutter = (
|
|
repo / ".github/workflows/flutter-build.yml"
|
|
).read_text(encoding="utf-8")
|
|
|
|
bridge = (
|
|
repo / ".github/workflows/bridge.yml"
|
|
).read_text(encoding="utf-8")
|
|
|
|
def env_value(text, name, required=True):
|
|
match = re.search(
|
|
rf'(?m)^\s*{re.escape(name)}:\s*["\']?([^"\'#\s]+)',
|
|
text,
|
|
)
|
|
|
|
if match:
|
|
return match.group(1).strip()
|
|
|
|
if required:
|
|
raise SystemExit(
|
|
f"Unable to resolve upstream variable: {name}"
|
|
)
|
|
|
|
return ""
|
|
|
|
android_flutter = (
|
|
env_value(
|
|
flutter,
|
|
"ANDROID_FLUTTER_VERSION",
|
|
required=False,
|
|
)
|
|
or env_value(flutter, "FLUTTER_VERSION")
|
|
)
|
|
|
|
# Select the default bridge artifact used by Android.
|
|
bridge_flutter = ""
|
|
|
|
for block in re.findall(r"\{(.*?)\}", bridge, flags=re.S):
|
|
artifact = re.search(
|
|
r'artifact-name:\s*["\']([^"\']+)["\']',
|
|
block,
|
|
)
|
|
version = re.search(
|
|
r'flutter-version:\s*["\']([^"\']+)["\']',
|
|
block,
|
|
)
|
|
|
|
if (
|
|
artifact
|
|
and artifact.group(1) == "bridge-artifact"
|
|
and version
|
|
):
|
|
bridge_flutter = version.group(1)
|
|
break
|
|
|
|
if not bridge_flutter:
|
|
raise SystemExit(
|
|
"Unable to resolve default bridge Flutter version"
|
|
)
|
|
|
|
values = {
|
|
"RUST_VERSION":
|
|
env_value(flutter, "RUST_VERSION"),
|
|
|
|
"CARGO_NDK_VERSION":
|
|
env_value(flutter, "CARGO_NDK_VERSION"),
|
|
|
|
"ANDROID_FLUTTER_VERSION":
|
|
android_flutter,
|
|
|
|
"NDK_VERSION":
|
|
env_value(flutter, "NDK_VERSION"),
|
|
|
|
"VCPKG_COMMIT_ID":
|
|
env_value(flutter, "VCPKG_COMMIT_ID"),
|
|
|
|
"BRIDGE_RUST_VERSION":
|
|
env_value(bridge, "RUST_VERSION"),
|
|
|
|
"BRIDGE_FLUTTER_VERSION":
|
|
bridge_flutter,
|
|
|
|
"CARGO_EXPAND_VERSION":
|
|
env_value(bridge, "CARGO_EXPAND_VERSION"),
|
|
|
|
"FLUTTER_RUST_BRIDGE_VERSION":
|
|
env_value(
|
|
bridge,
|
|
"FLUTTER_RUST_BRIDGE_VERSION",
|
|
),
|
|
}
|
|
|
|
for key, value in values.items():
|
|
print(f"{key}={value}")
|
|
PY
|
|
|
|
echo "Resolved upstream configuration:"
|
|
cat build-config.env
|
|
|
|
while IFS='=' read -r key value; do
|
|
[ -z "${key}" ] && continue
|
|
echo "${key}=${value}" >> "${GITHUB_ENV}"
|
|
done < build-config.env
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Prepare exact toolchain requested by the selected tag.
|
|
# Prefer the prebuilt builder image where it already matches.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Prepare builder environment
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
echo "=== Rust ==="
|
|
|
|
rustup toolchain install \
|
|
"${RUST_VERSION}" \
|
|
--profile minimal \
|
|
--component rustfmt
|
|
|
|
rustup target add \
|
|
--toolchain "${RUST_VERSION}" \
|
|
"${RUST_TARGET}"
|
|
|
|
export RUSTUP_TOOLCHAIN="${RUST_VERSION}"
|
|
|
|
current_cargo_ndk="$(
|
|
cargo ndk --version 2>/dev/null |
|
|
awk '{print $2}' ||
|
|
true
|
|
)"
|
|
|
|
if [ "${current_cargo_ndk}" != "${CARGO_NDK_VERSION}" ]; then
|
|
cargo install \
|
|
cargo-ndk \
|
|
--version "${CARGO_NDK_VERSION}" \
|
|
--locked \
|
|
--force
|
|
fi
|
|
|
|
echo "RUSTUP_TOOLCHAIN=${RUST_VERSION}" \
|
|
>> "${GITHUB_ENV}"
|
|
|
|
rustc --version
|
|
cargo --version
|
|
cargo ndk --version
|
|
|
|
echo
|
|
echo "=== Flutter SDKs ==="
|
|
|
|
flutter_version() {
|
|
local root="$1"
|
|
|
|
if [ ! -x "${root}/bin/flutter" ]; then
|
|
return 1
|
|
fi
|
|
|
|
git config --global \
|
|
--add safe.directory "${root}" \
|
|
>/dev/null 2>&1 \
|
|
|| true
|
|
|
|
git \
|
|
-C "${root}" \
|
|
describe \
|
|
--tags \
|
|
--exact-match \
|
|
HEAD \
|
|
2>/dev/null \
|
|
|| true
|
|
}
|
|
|
|
prepare_flutter() {
|
|
local requested="$1"
|
|
local preferred="$2"
|
|
local fallback="$3"
|
|
local root=""
|
|
|
|
if (
|
|
[ -n "${preferred}" ] &&
|
|
[ "$(flutter_version "${preferred}")" = "${requested}" ]
|
|
); then
|
|
root="${preferred}"
|
|
else
|
|
root="${fallback}"
|
|
|
|
if [ "$(flutter_version "${root}")" != "${requested}" ]; then
|
|
rm -rf "${root}"
|
|
mkdir -p "$(dirname "${root}")"
|
|
|
|
git clone \
|
|
--branch "${requested}" \
|
|
--depth 1 \
|
|
https://github.com/flutter/flutter.git \
|
|
"${root}"
|
|
fi
|
|
fi
|
|
|
|
git config --global \
|
|
--add safe.directory "${root}" \
|
|
>/dev/null 2>&1 \
|
|
|| true
|
|
|
|
printf '%s\n' "${root}"
|
|
}
|
|
|
|
BRIDGE_FLUTTER_ROOT="$(
|
|
prepare_flutter \
|
|
"${BRIDGE_FLUTTER_VERSION}" \
|
|
"${FLUTTER_BRIDGE_HOME:-}" \
|
|
"${HOME}/.cache/rustdesk/flutter-${BRIDGE_FLUTTER_VERSION}"
|
|
)"
|
|
|
|
ANDROID_FLUTTER_ROOT="$(
|
|
prepare_flutter \
|
|
"${ANDROID_FLUTTER_VERSION}" \
|
|
"${FLUTTER_ANDROID_HOME:-}" \
|
|
"${HOME}/.cache/rustdesk/flutter-${ANDROID_FLUTTER_VERSION}"
|
|
)"
|
|
|
|
echo "BRIDGE_FLUTTER_ROOT=${BRIDGE_FLUTTER_ROOT}" \
|
|
>> "${GITHUB_ENV}"
|
|
|
|
echo "ANDROID_FLUTTER_ROOT=${ANDROID_FLUTTER_ROOT}" \
|
|
>> "${GITHUB_ENV}"
|
|
|
|
"${BRIDGE_FLUTTER_ROOT}/bin/flutter" \
|
|
config \
|
|
--no-analytics \
|
|
>/dev/null
|
|
|
|
"${ANDROID_FLUTTER_ROOT}/bin/flutter" \
|
|
config \
|
|
--no-analytics \
|
|
>/dev/null
|
|
|
|
echo "Bridge Flutter:"
|
|
"${BRIDGE_FLUTTER_ROOT}/bin/flutter" --version
|
|
|
|
echo
|
|
echo "Android Flutter:"
|
|
"${ANDROID_FLUTTER_ROOT}/bin/flutter" --version
|
|
|
|
echo
|
|
echo "=== Android NDK ==="
|
|
|
|
# Convert r28c -> expected Pkg.Revision prefix 28.2.
|
|
NDK_EXPECTED_PREFIX="$(
|
|
python3 <<'PY'
|
|
import os
|
|
import re
|
|
|
|
version = os.environ["NDK_VERSION"]
|
|
match = re.fullmatch(r"r(\d+)([a-z]?)", version)
|
|
|
|
if not match:
|
|
raise SystemExit(
|
|
f"Unsupported NDK version format: {version}"
|
|
)
|
|
|
|
major = int(match.group(1))
|
|
letter = match.group(2)
|
|
|
|
if letter:
|
|
minor = ord(letter) - ord("a")
|
|
print(f"{major}.{minor}.")
|
|
else:
|
|
print(f"{major}.")
|
|
PY
|
|
)"
|
|
|
|
current_ndk_ok=0
|
|
|
|
if (
|
|
[ -n "${ANDROID_NDK_HOME:-}" ] &&
|
|
[ -f "${ANDROID_NDK_HOME}/source.properties" ]
|
|
); then
|
|
CURRENT_NDK_REVISION="$(
|
|
sed -n \
|
|
's/^Pkg\.Revision[[:space:]]*=[[:space:]]*//p' \
|
|
"${ANDROID_NDK_HOME}/source.properties"
|
|
)"
|
|
|
|
if [[ "${CURRENT_NDK_REVISION}" == "${NDK_EXPECTED_PREFIX}"* ]]; then
|
|
current_ndk_ok=1
|
|
fi
|
|
fi
|
|
|
|
if [ "${current_ndk_ok}" -eq 0 ]; then
|
|
ANDROID_NDK_HOME="${HOME}/.cache/rustdesk/android-ndk-${NDK_VERSION}"
|
|
|
|
if [ ! -f "${ANDROID_NDK_HOME}/source.properties" ]; then
|
|
rm -rf "${ANDROID_NDK_HOME}"
|
|
|
|
tmp_zip="${HOME}/.cache/rustdesk/android-ndk-${NDK_VERSION}.zip"
|
|
tmp_extract="${HOME}/.cache/rustdesk/ndk-extract-${NDK_VERSION}"
|
|
|
|
mkdir -p "${HOME}/.cache/rustdesk"
|
|
rm -rf "${tmp_extract}"
|
|
|
|
curl -fL \
|
|
"https://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux.zip" \
|
|
-o "${tmp_zip}"
|
|
|
|
python3 -m zipfile \
|
|
-e \
|
|
"${tmp_zip}" \
|
|
"${tmp_extract}"
|
|
|
|
mv \
|
|
"${tmp_extract}/android-ndk-${NDK_VERSION}" \
|
|
"${ANDROID_NDK_HOME}"
|
|
|
|
rm -rf "${tmp_extract}"
|
|
fi
|
|
fi
|
|
|
|
test -f "${ANDROID_NDK_HOME}/source.properties"
|
|
|
|
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}" \
|
|
>> "${GITHUB_ENV}"
|
|
|
|
echo "ANDROID_NDK_ROOT=${ANDROID_NDK_HOME}" \
|
|
>> "${GITHUB_ENV}"
|
|
|
|
cat "${ANDROID_NDK_HOME}/source.properties"
|
|
|
|
echo
|
|
echo "=== vcpkg ==="
|
|
|
|
test -n "${VCPKG_ROOT:-}"
|
|
test -d "${VCPKG_ROOT}/.git"
|
|
|
|
current_vcpkg="$(
|
|
git -C "${VCPKG_ROOT}" rev-parse HEAD
|
|
)"
|
|
|
|
if [ "${current_vcpkg}" != "${VCPKG_COMMIT_ID}" ]; then
|
|
git -C "${VCPKG_ROOT}" fetch \
|
|
origin \
|
|
"${VCPKG_COMMIT_ID}"
|
|
|
|
git -C "${VCPKG_ROOT}" checkout \
|
|
--force \
|
|
"${VCPKG_COMMIT_ID}"
|
|
|
|
rm -rf \
|
|
"${VCPKG_ROOT}/installed/arm64-android" \
|
|
"${VCPKG_ROOT}/buildtrees" \
|
|
"${VCPKG_ROOT}/packages"
|
|
fi
|
|
|
|
"${VCPKG_ROOT}/bootstrap-vcpkg.sh" \
|
|
-disableMetrics
|
|
|
|
"${VCPKG_ROOT}/vcpkg" version
|
|
|
|
echo
|
|
echo "Builder environment prepared"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Generate Flutter/Rust bridge in THE SAME checkout, like the original
|
|
# working workflow. Use version-specific preparation commands from the
|
|
# selected tag's upstream bridge.yml.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Generate Flutter Rust bridge
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
export RUSTUP_TOOLCHAIN="${BRIDGE_RUST_VERSION}"
|
|
export PATH="${BRIDGE_FLUTTER_ROOT}/bin:${CARGO_HOME}/bin:${PATH}"
|
|
|
|
rustup toolchain install \
|
|
"${BRIDGE_RUST_VERSION}" \
|
|
--profile minimal \
|
|
--component rustfmt
|
|
|
|
cd rustdesk
|
|
|
|
python3 <<'PY'
|
|
import re
|
|
from pathlib import Path
|
|
|
|
text = Path(
|
|
".github/workflows/bridge.yml"
|
|
).read_text(encoding="utf-8")
|
|
|
|
def run_blocks(text):
|
|
lines = text.splitlines()
|
|
blocks = []
|
|
|
|
for index, line in enumerate(lines):
|
|
match = re.match(r"^(\s*)run:\s*\|\s*$", line)
|
|
|
|
if not match:
|
|
continue
|
|
|
|
base_indent = len(match.group(1))
|
|
body = []
|
|
|
|
for child in lines[index + 1:]:
|
|
if child.strip():
|
|
indent = len(child) - len(child.lstrip())
|
|
|
|
if indent <= base_indent:
|
|
break
|
|
|
|
cut = min(
|
|
len(child),
|
|
base_indent + 2,
|
|
)
|
|
|
|
body.append(child[cut:])
|
|
|
|
blocks.append("\n".join(body) + "\n")
|
|
|
|
return blocks
|
|
|
|
blocks = run_blocks(text)
|
|
|
|
prepare = next(
|
|
(
|
|
block
|
|
for block in blocks
|
|
if "cargo install cargo-expand" in block
|
|
and "flutter_rust_bridge_codegen" in block
|
|
),
|
|
None,
|
|
)
|
|
|
|
generate = next(
|
|
(
|
|
block
|
|
for block in blocks
|
|
if "flutter_rust_bridge_codegen --rust-input" in block
|
|
),
|
|
None,
|
|
)
|
|
|
|
if not prepare:
|
|
raise SystemExit(
|
|
"Unable to find upstream bridge preparation block"
|
|
)
|
|
|
|
if not generate:
|
|
raise SystemExit(
|
|
"Unable to find upstream bridge generation block"
|
|
)
|
|
|
|
def replace_expression(
|
|
body,
|
|
expression,
|
|
replacement,
|
|
):
|
|
pattern = (
|
|
r"\$\{\{\s*"
|
|
+ re.escape(expression)
|
|
+ r"\s*\}\}"
|
|
)
|
|
|
|
return re.sub(
|
|
pattern,
|
|
lambda _match: replacement,
|
|
body,
|
|
)
|
|
|
|
replacements = (
|
|
(
|
|
"env.CARGO_EXPAND_VERSION",
|
|
"${CARGO_EXPAND_VERSION}",
|
|
),
|
|
(
|
|
"env.FLUTTER_RUST_BRIDGE_VERSION",
|
|
"${FLUTTER_RUST_BRIDGE_VERSION}",
|
|
),
|
|
(
|
|
"matrix.job.flutter-version",
|
|
"${BRIDGE_FLUTTER_VERSION}",
|
|
),
|
|
)
|
|
|
|
for expression, replacement in replacements:
|
|
prepare = replace_expression(
|
|
prepare,
|
|
expression,
|
|
replacement,
|
|
)
|
|
|
|
generate = replace_expression(
|
|
generate,
|
|
expression,
|
|
replacement,
|
|
)
|
|
|
|
# Upstream bridge.yml may call the codegen binary through
|
|
# ~/.cargo/bin/... . Our builder intentionally uses a custom
|
|
# CARGO_HOME (for example /opt/rust/cargo), so a hard-coded HOME
|
|
# cargo path is not portable. The selected tool version has already
|
|
# been installed above; resolve it through PATH instead.
|
|
cargo_bin_patterns = (
|
|
r"~/.cargo/bin/flutter_rust_bridge_codegen",
|
|
r"\$HOME/.cargo/bin/flutter_rust_bridge_codegen",
|
|
r"\$\{HOME\}/.cargo/bin/flutter_rust_bridge_codegen",
|
|
r"/root/.cargo/bin/flutter_rust_bridge_codegen",
|
|
)
|
|
|
|
for pattern in cargo_bin_patterns:
|
|
generate = re.sub(
|
|
pattern,
|
|
"flutter_rust_bridge_codegen",
|
|
generate,
|
|
)
|
|
|
|
if re.search(r"\$\{\{", prepare + generate):
|
|
raise SystemExit(
|
|
"Unsupported upstream expression remains "
|
|
"in bridge commands"
|
|
)
|
|
|
|
Path("bridge-prepare.sh").write_text(
|
|
"#!/usr/bin/env bash\n"
|
|
"set -euxo pipefail\n"
|
|
+ prepare,
|
|
encoding="utf-8",
|
|
)
|
|
|
|
Path("bridge-generate.sh").write_text(
|
|
"#!/usr/bin/env bash\n"
|
|
"set -euxo pipefail\n"
|
|
+ generate,
|
|
encoding="utf-8",
|
|
)
|
|
PY
|
|
|
|
chmod +x \
|
|
bridge-prepare.sh \
|
|
bridge-generate.sh
|
|
|
|
bash -n bridge-prepare.sh
|
|
bash -n bridge-generate.sh
|
|
|
|
echo
|
|
echo "Bridge codegen binary:"
|
|
command -v flutter_rust_bridge_codegen
|
|
flutter_rust_bridge_codegen --version
|
|
|
|
echo
|
|
echo "Generated bridge command:"
|
|
grep -n "flutter_rust_bridge_codegen" \
|
|
bridge-generate.sh
|
|
|
|
./bridge-prepare.sh
|
|
./bridge-generate.sh
|
|
|
|
test -f src/bridge_generated.rs
|
|
test -f src/bridge_generated.io.rs
|
|
test -f flutter/lib/generated_bridge.dart
|
|
test -f flutter/lib/generated_bridge.freezed.dart
|
|
|
|
echo
|
|
echo "Generated bridge:"
|
|
ls -lh \
|
|
src/bridge_generated.rs \
|
|
src/bridge_generated.io.rs \
|
|
flutter/lib/generated_bridge.dart \
|
|
flutter/lib/generated_bridge.freezed.dart
|
|
|
|
# Restore only tracked changes made by bridge preparation.
|
|
# Generated bridge files are untracked and remain in place.
|
|
rm -f \
|
|
bridge-prepare.sh \
|
|
bridge-generate.sh
|
|
|
|
git restore \
|
|
--worktree \
|
|
--source=HEAD \
|
|
-- \
|
|
flutter/pubspec.yaml \
|
|
flutter/pubspec.lock \
|
|
2>/dev/null \
|
|
|| true
|
|
|
|
rm -rf flutter/.dart_tool
|
|
|
|
test -f src/bridge_generated.rs
|
|
test -f src/bridge_generated.io.rs
|
|
|
|
echo
|
|
echo "Flutter Rust bridge generated successfully"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Custom RustDesk ID server + public key.
|
|
# Relay is NOT modified.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Configure custom ID server and key
|
|
env:
|
|
CUSTOM_ID_SERVER: ${{ secrets.RUSTDESK_ID_SERVER }}
|
|
CUSTOM_SERVER_KEY: ${{ secrets.RUSTDESK_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ -z "${CUSTOM_ID_SERVER}" ]; then
|
|
echo "ERROR: RUSTDESK_ID_SERVER secret is empty"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${CUSTOM_SERVER_KEY}" ]; then
|
|
echo "ERROR: RUSTDESK_KEY secret is empty"
|
|
exit 1
|
|
fi
|
|
|
|
cd rustdesk
|
|
|
|
python3 <<'PY'
|
|
import json
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
path = Path("libs/hbb_common/src/config.rs")
|
|
|
|
if not path.exists():
|
|
raise SystemExit(
|
|
"libs/hbb_common/src/config.rs does not exist. "
|
|
"Check that submodules were cloned correctly."
|
|
)
|
|
|
|
text = path.read_text(encoding="utf-8")
|
|
|
|
id_server = os.environ["CUSTOM_ID_SERVER"].strip()
|
|
server_key = os.environ["CUSTOM_SERVER_KEY"].strip()
|
|
|
|
if not id_server:
|
|
raise SystemExit("RUSTDESK_ID_SERVER is empty")
|
|
|
|
if not server_key:
|
|
raise SystemExit("RUSTDESK_KEY is empty")
|
|
|
|
text, id_count = re.subn(
|
|
r"pub const RENDEZVOUS_SERVERS\s*:\s*[^=]+?="
|
|
r"\s*&\[(?:.|\n)*?\];",
|
|
"pub const RENDEZVOUS_SERVERS: &[&str] = &["
|
|
+ json.dumps(id_server)
|
|
+ "];",
|
|
text,
|
|
count=1,
|
|
)
|
|
|
|
text, key_count = re.subn(
|
|
r"pub const RS_PUB_KEY\s*:\s*[^=]+?="
|
|
r"\s*.*?;",
|
|
"pub const RS_PUB_KEY: &str = "
|
|
+ json.dumps(server_key)
|
|
+ ";",
|
|
text,
|
|
count=1,
|
|
flags=re.S,
|
|
)
|
|
|
|
if id_count != 1:
|
|
raise SystemExit(
|
|
"Failed to patch RENDEZVOUS_SERVERS. "
|
|
"RustDesk source layout may have changed."
|
|
)
|
|
|
|
if key_count != 1:
|
|
raise SystemExit(
|
|
"Failed to patch RS_PUB_KEY. "
|
|
"RustDesk source layout may have changed."
|
|
)
|
|
|
|
path.write_text(text, encoding="utf-8")
|
|
|
|
print(f"ID server configured: {id_server}")
|
|
print("Server public key configured successfully")
|
|
PY
|
|
|
|
grep -n \
|
|
"RENDEZVOUS_SERVERS" \
|
|
libs/hbb_common/src/config.rs
|
|
|
|
echo "Relay configuration untouched: yes"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Apply Flutter compatibility patches supplied by the selected tag.
|
|
# Only patches that cleanly apply to the selected Android Flutter SDK
|
|
# are applied.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Patch Android Flutter SDK
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
if [ ! -d "rustdesk/.github/patches" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
shopt -s nullglob
|
|
|
|
for patch in rustdesk/.github/patches/flutter_*.diff; do
|
|
if git \
|
|
-C "${ANDROID_FLUTTER_ROOT}" \
|
|
apply \
|
|
--check \
|
|
"${PWD}/${patch}" \
|
|
>/dev/null 2>&1; then
|
|
|
|
echo "Applying Flutter patch: ${patch}"
|
|
|
|
git \
|
|
-C "${ANDROID_FLUTTER_ROOT}" \
|
|
apply \
|
|
"${PWD}/${patch}"
|
|
fi
|
|
done
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Android native dependencies.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Verify Android native dependencies
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
cd rustdesk
|
|
|
|
echo "VCPKG_ROOT=${VCPKG_ROOT}"
|
|
echo "ANDROID_NDK_HOME=${ANDROID_NDK_HOME}"
|
|
|
|
if ! ./flutter/build_android_deps.sh "${ANDROID_ABI}"; then
|
|
echo
|
|
echo "Android dependency verification/build failed."
|
|
echo "Dumping vcpkg logs:"
|
|
echo
|
|
|
|
find "${VCPKG_ROOT}" \
|
|
-name "*.log" \
|
|
-type f \
|
|
| while read -r logfile; do
|
|
echo "===== ${logfile} ====="
|
|
cat "${logfile}" || true
|
|
echo
|
|
done
|
|
|
|
exit 1
|
|
fi
|
|
|
|
test -d \
|
|
"${VCPKG_ROOT}/installed/arm64-android"
|
|
|
|
echo
|
|
echo "Android native dependencies OK"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Build Rust native Android library.
|
|
#
|
|
# Important: copy the .so into jniLibs in THIS SAME step, matching
|
|
# upstream RustDesk Android CI. The next Flutter step no longer depends
|
|
# on target/ surviving as a build-output directory.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Build RustDesk native library
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
export RUSTUP_TOOLCHAIN="${RUST_VERSION}"
|
|
|
|
cd rustdesk
|
|
|
|
rustc --version
|
|
cargo --version
|
|
cargo ndk --version
|
|
|
|
test -f src/bridge_generated.rs
|
|
test -f src/bridge_generated.io.rs
|
|
|
|
if ! rustup \
|
|
target list \
|
|
--installed \
|
|
--toolchain "${RUST_VERSION}" \
|
|
| grep -qx "${RUST_TARGET}"; then
|
|
|
|
echo "ERROR: ${RUST_TARGET} Rust target is missing"
|
|
exit 1
|
|
fi
|
|
|
|
./flutter/ndk_arm64.sh
|
|
|
|
NATIVE_LIB="target/${RUST_TARGET}/release/liblibrustdesk.so"
|
|
|
|
test -f "${NATIVE_LIB}"
|
|
|
|
mkdir -p \
|
|
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"
|
|
|
|
cp \
|
|
"${NATIVE_LIB}" \
|
|
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
|
|
|
|
test -f \
|
|
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
|
|
|
|
echo
|
|
echo "Native library:"
|
|
ls -lh \
|
|
"${NATIVE_LIB}" \
|
|
"flutter/android/app/src/main/jniLibs/${ANDROID_ABI}/librustdesk.so"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Permanent Android release signing.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Configure Android release signing
|
|
env:
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ -z "${ANDROID_KEYSTORE_BASE64}" ]; then
|
|
echo "ERROR: ANDROID_KEYSTORE_BASE64 secret is empty"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${ANDROID_KEYSTORE_PASSWORD}" ]; then
|
|
echo "ERROR: ANDROID_KEYSTORE_PASSWORD secret is empty"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${ANDROID_KEY_ALIAS}" ]; then
|
|
echo "ERROR: ANDROID_KEY_ALIAS secret is empty"
|
|
exit 1
|
|
fi
|
|
|
|
cd rustdesk/flutter/android
|
|
|
|
printf '%s' "${ANDROID_KEYSTORE_BASE64}" \
|
|
| base64 --decode \
|
|
> app/rustdesk-release.jks
|
|
|
|
chmod 600 app/rustdesk-release.jks
|
|
|
|
cat > key.properties <<EOF2
|
|
storePassword=${ANDROID_KEYSTORE_PASSWORD}
|
|
keyPassword=${ANDROID_KEYSTORE_PASSWORD}
|
|
keyAlias=${ANDROID_KEY_ALIAS}
|
|
storeFile=rustdesk-release.jks
|
|
EOF2
|
|
|
|
chmod 600 key.properties
|
|
|
|
if [ ! -s app/rustdesk-release.jks ]; then
|
|
echo "ERROR: Restored keystore is empty"
|
|
exit 1
|
|
fi
|
|
|
|
keytool \
|
|
-list \
|
|
-keystore app/rustdesk-release.jks \
|
|
-storepass "${ANDROID_KEYSTORE_PASSWORD}" \
|
|
-alias "${ANDROID_KEY_ALIAS}" \
|
|
>/dev/null
|
|
|
|
echo "Android release signing configured successfully."
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Build signed ARM64 APK.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Build signed APK
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
export RUSTUP_TOOLCHAIN="${RUST_VERSION}"
|
|
export PATH="${ANDROID_FLUTTER_ROOT}/bin:${CARGO_HOME}/bin:${PATH}"
|
|
|
|
flutter --version
|
|
|
|
cd rustdesk
|
|
|
|
JNI_DIR="flutter/android/app/src/main/jniLibs/${ANDROID_ABI}"
|
|
|
|
test -f "${JNI_DIR}/librustdesk.so"
|
|
|
|
cp \
|
|
"${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so" \
|
|
"${JNI_DIR}/libc++_shared.so"
|
|
|
|
sed -i \
|
|
's/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g' \
|
|
flutter/android/gradle.properties
|
|
|
|
echo
|
|
echo "JNI libraries:"
|
|
ls -lh "${JNI_DIR}/"
|
|
|
|
echo
|
|
echo "Signing files:"
|
|
ls -lh \
|
|
flutter/android/key.properties \
|
|
flutter/android/app/rustdesk-release.jks
|
|
|
|
pushd flutter
|
|
|
|
flutter clean
|
|
flutter pub get
|
|
|
|
flutter build apk \
|
|
--release \
|
|
--target-platform "${FLUTTER_TARGET}" \
|
|
--split-per-abi
|
|
|
|
popd
|
|
|
|
APK_SOURCE="flutter/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk"
|
|
|
|
test -f "${APK_SOURCE}"
|
|
|
|
mkdir -p output
|
|
|
|
cp \
|
|
"${APK_SOURCE}" \
|
|
"output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
|
|
|
|
echo
|
|
echo "Built APK:"
|
|
ls -lh \
|
|
"output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Verify APK release signature.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Verify APK signature
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
APK="rustdesk/output/rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk"
|
|
|
|
APKSIGNER="$(
|
|
find "${ANDROID_SDK_ROOT}/build-tools" \
|
|
-type f \
|
|
-name apksigner \
|
|
-print \
|
|
| sort -V \
|
|
| tail -n 1
|
|
)"
|
|
|
|
if [ -z "${APKSIGNER}" ]; then
|
|
echo "ERROR: apksigner was not found"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${APK}" ]; then
|
|
echo "ERROR: APK not found: ${APK}"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Using apksigner: ${APKSIGNER}"
|
|
|
|
"${APKSIGNER}" verify \
|
|
--verbose \
|
|
--print-certs \
|
|
"${APK}"
|
|
|
|
echo "APK signature verification successful."
|
|
|
|
# -----------------------------------------------------------------------
|
|
# APK checksum.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Create checksum
|
|
run: |
|
|
set -euxo pipefail
|
|
|
|
cd rustdesk/output
|
|
|
|
sha256sum \
|
|
"rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk" \
|
|
> "rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk.sha256"
|
|
|
|
cat \
|
|
"rustdesk-${RUSTDESK_VERSION}-custom-arm64.apk.sha256"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Upload Gitea Actions artifact.
|
|
# Keep the same action family as the original known-working workflow.
|
|
# -----------------------------------------------------------------------
|
|
|
|
- name: Upload APK
|
|
uses: actions/upload-artifact@v4.6.2
|
|
with:
|
|
name: rustdesk-${{ env.RUSTDESK_VERSION }}-custom-arm64
|
|
path: |
|
|
rustdesk/output/rustdesk-${{ env.RUSTDESK_VERSION }}-custom-arm64.apk
|
|
rustdesk/output/rustdesk-${{ env.RUSTDESK_VERSION }}-custom-arm64.apk.sha256
|
|
if-no-files-found: error
|