ci: add Linux build image workflow

This commit is contained in:
dijunkun
2026-07-21 21:54:03 +08:00
parent 59f77c2820
commit f5c4e2ba4a
23 changed files with 812 additions and 58 deletions
+20
View File
@@ -0,0 +1,20 @@
**
!.dockerignore
!docker/
!docker/linux-build/
!docker/linux-build/**
# Dependency manifests and local xmake package recipes are the only project
# files needed while pre-warming the build image.
!xmake.lua
!xmake/
!xmake/**
!thirdparty/
!thirdparty/**
!submodules/
!submodules/xmake.lua
!submodules/minirtc/
!submodules/minirtc/xmake.lua
!submodules/minirtc/thirdparty/
!submodules/minirtc/thirdparty/**
+155
View File
@@ -0,0 +1,155 @@
name: Build Linux Build Image
on:
push:
branches:
- ci/linux-build-image
paths:
- .dockerignore
- .gitmodules
- .github/workflows/build-linux-image.yml
- docker/linux-build/**
- xmake.lua
- xmake/**
- thirdparty/**
- submodules/xmake.lua
- submodules/minirtc
- submodules/minirtc/xmake.lua
- submodules/minirtc/thirdparty/**
workflow_dispatch:
permissions:
contents: read
concurrency:
group: linux-build-image-${{ github.ref }}
cancel-in-progress: true
env:
IMAGE_NAME: crossdesk/ubuntu22.04
STACK_TAG: buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
XMAKE_VERSION: 3.0.9
RUST_VERSION: 1.92.0
RUSTUP_VERSION: 1.28.2
jobs:
build:
name: Build candidate (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
base_image: nvidia/cuda:12.6.3-devel-ubuntu22.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
base_image: ubuntu:22.04
steps:
- name: Set candidate tag
id: image
run: |
CANDIDATE_TAG="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-${{ matrix.arch }}"
echo "CANDIDATE_TAG=${CANDIDATE_TAG}" >> "${GITHUB_ENV}"
echo "candidate=${CANDIDATE_TAG}" >> "${GITHUB_OUTPUT}"
- name: Checkout source
uses: actions/checkout@v5
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push candidate
uses: docker/build-push-action@v7
with:
context: .
file: docker/linux-build/Dockerfile
platforms: ${{ matrix.platform }}
push: true
pull: true
tags: ${{ steps.image.outputs.candidate }}
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:${{ env.STACK_TAG }}
cache-to: type=inline
provenance: false
sbom: false
build-args: |
BASE_IMAGE=${{ matrix.base_image }}
XMAKE_VERSION=${{ env.XMAKE_VERSION }}
RUST_VERSION=${{ env.RUST_VERSION }}
RUSTUP_VERSION=${{ env.RUSTUP_VERSION }}
verify:
name: Verify candidate (${{ matrix.arch }})
needs: build
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-24.04
- arch: arm64
runner: ubuntu-24.04-arm
steps:
- name: Set candidate tag
run: |
CANDIDATE_TAG="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-${{ matrix.arch }}"
echo "CANDIDATE_TAG=${CANDIDATE_TAG}" >> "${GITHUB_ENV}"
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Verify tools, architecture, and Ubuntu baseline
run: |
docker pull "${CANDIDATE_TAG}"
docker run --rm \
--env EXPECTED_ARCH="${{ matrix.arch }}" \
"${CANDIDATE_TAG}" \
verify-crossdesk-build-image
publish:
name: Publish multi-arch image
needs: verify
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Promote verified candidates
run: |
AMD64_IMAGE="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-amd64"
ARM64_IMAGE="${IMAGE_NAME}:${STACK_TAG}-${GITHUB_SHA}-arm64"
docker buildx imagetools create \
--tag "${IMAGE_NAME}:${STACK_TAG}" \
--tag "${IMAGE_NAME}:sha-${GITHUB_SHA}" \
--tag "${IMAGE_NAME}:latest" \
"${AMD64_IMAGE}" \
"${ARM64_IMAGE}"
- name: Inspect published manifest
run: |
docker buildx imagetools inspect "${IMAGE_NAME}:${STACK_TAG}"
+43 -8
View File
@@ -4,6 +4,7 @@ on:
push:
branches:
- "**"
- "!ci/linux-build-image"
tags:
- "*"
workflow_dispatch:
@@ -27,13 +28,13 @@ jobs:
matrix:
include:
- arch: amd64
runner: ubuntu-22.04
image: crossdesk/ubuntu20.04:latest
runner: ubuntu-24.04
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
package_script: ./scripts/linux/pkg_amd64.sh
- arch: arm64
runner: ubuntu-22.04-arm
image: crossdesk/ubuntu20.04-arm64v8:latest
runner: ubuntu-24.04-arm
image: crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
package_script: ./scripts/linux/pkg_arm64.sh
container:
@@ -99,12 +100,14 @@ jobs:
with:
submodules: recursive
- name: Verify Linux build image
run: verify-crossdesk-build-image
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
apt install -y libxft-dev
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y
xmake b -vy --root crossdesk
@@ -113,6 +116,14 @@ jobs:
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${LEGAL_VERSION}
- name: Verify packaged Ubuntu 22.04 glibc baseline
run: |
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-linux-${{ matrix.arch }}-${LEGAL_VERSION}.deb"
VERIFY_DIR="${RUNNER_TEMP}/crossdesk-glibc-${{ matrix.arch }}"
install -d "${VERIFY_DIR}"
dpkg-deb --extract "${PACKAGE_FILE}" "${VERIFY_DIR}"
verify-crossdesk-glibc-baseline "${VERIFY_DIR}" 2.35
- name: Upload artifact
uses: actions/upload-artifact@v6
with:
@@ -123,17 +134,21 @@ jobs:
build-macos:
name: Build on macOS
runs-on: ${{ matrix.runner }}
env:
MACOSX_DEPLOYMENT_TARGET: "14.0"
strategy:
matrix:
include:
- arch: x64
binary_arch: x86_64
runner: macos-15-intel
cache-key: intel
cache-key: intel-macos14
out-dir: ./build/macosx/x86_64/release/crossdesk
package_script: ./scripts/macosx/pkg_x64.sh
- arch: arm64
binary_arch: arm64
runner: macos-14
cache-key: arm
cache-key: arm-macos14
out-dir: ./build/macosx/arm64/release/crossdesk
package_script: ./scripts/macosx/pkg_arm64.sh
@@ -189,6 +204,9 @@ jobs:
- name: Install xmake
run: brew install xmake
- name: Update xmake repositories
run: xmake repo -u
- name: Checkout code
uses: actions/checkout@v5
@@ -197,7 +215,7 @@ jobs:
- name: Build CrossDesk
run: |
xmake f --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y
xmake f --target_minver=${MACOSX_DEPLOYMENT_TARGET} --CROSSDESK_VERSION=${VERSION_NUM} --USE_CUDA=true -y
xmake b -vy crossdesk
- name: Package CrossDesk app
@@ -205,6 +223,22 @@ jobs:
chmod +x ${{ matrix.package_script }}
${{ matrix.package_script }} ${VERSION_NUM}
- name: Verify packaged app
shell: bash
run: |
PACKAGE_FILE="${GITHUB_WORKSPACE}/crossdesk-macos-${{ matrix.arch }}-${VERSION_NUM}.pkg"
VERIFY_DIR="$(mktemp -d "${RUNNER_TEMP}/crossdesk-macos-pkg.XXXXXX")"
trap 'rm -rf "${VERIFY_DIR}"' EXIT
pkgutil --expand-full "${PACKAGE_FILE}" "${VERIFY_DIR}/expanded"
APP_PATH="$(find "${VERIFY_DIR}/expanded" -type d -name 'CrossDesk.app' -print -quit)"
if [[ -z "${APP_PATH}" ]]; then
echo "CrossDesk.app is missing from ${PACKAGE_FILE}" >&2
exit 1
fi
./scripts/macosx/verify_app.sh "${APP_PATH}" "${{ matrix.binary_arch }}"
- name: Upload build artifacts
uses: actions/upload-artifact@v6
with:
@@ -275,6 +309,7 @@ jobs:
run: |
Invoke-Expression (Invoke-Webrequest 'https://raw.githubusercontent.com/tboox/xmake/master/scripts/get.ps1' -UseBasicParsing).Content
echo "C:\Users\runneradmin\xmake" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
xmake repo -u
xmake create cuda
Set-Location cuda
xmake g --theme=plain
+153
View File
@@ -0,0 +1,153 @@
# syntax=docker/dockerfile:1.7
ARG BASE_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu22.04
FROM ${BASE_IMAGE}
ARG TARGETARCH
ARG XMAKE_VERSION=3.0.9
ARG RUST_VERSION=1.92.0
ARG RUSTUP_VERSION=1.28.2
LABEL org.opencontainers.image.title="CrossDesk Linux build environment" \
org.opencontainers.image.description="Ubuntu 22.04 build environment for CrossDesk" \
org.opencontainers.image.source="https://github.com/kunkundi/crossdesk" \
org.opencontainers.image.licenses="GPL-3.0-only"
ENV DEBIAN_FRONTEND=noninteractive \
TZ=Etc/UTC \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8 \
RUSTUP_HOME=/opt/rustup \
CARGO_HOME=/opt/cargo \
XMAKE_GLOBALDIR=/data \
XMAKE_ROOT=y \
CUDA_PATH=/usr/local/cuda \
PATH=/root/.local/bin:/opt/cargo/bin:/usr/local/bin:${PATH}
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
autoconf \
automake \
binutils \
bison \
build-essential \
ca-certificates \
ccache \
cmake \
curl \
dbus \
dpkg-dev \
file \
flex \
gettext \
git \
libasound2-dev \
libdbus-1-dev \
libdrm-dev \
libegl1-mesa-dev \
libffi-dev \
libfontconfig1-dev \
libfreetype6-dev \
libgl1-mesa-dev \
libmount-dev \
libpulse-dev \
libreadline-dev \
libsndio-dev \
libssl-dev \
libtool \
libx11-dev \
libxcb-xfixes0-dev \
libxcb-randr0-dev \
libxcb-shape0-dev \
libxcb-shm0-dev \
libxcb-xinerama0-dev \
libxcb-xkb-dev \
libxcb-xtest0-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxft-dev \
libxi-dev \
libxinerama-dev \
libxkbcommon-dev \
libxrandr-dev \
libxrender-dev \
libxtst-dev \
libxv-dev \
make \
nasm \
ninja-build \
patch \
patchelf \
perl \
pkg-config \
python3 \
python3-pip \
unzip \
xutils-dev \
xz-utils \
yasm \
zip \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
RUN export GITHUB_ACTIONS=true \
&& curl -fsSL https://xmake.io/shget.text \
| bash -s -- "v${XMAKE_VERSION}" \
&& source ~/.xmake/profile \
&& xmake --version | grep -F "v${XMAKE_VERSION}"
RUN case "${TARGETARCH}" in \
amd64) \
rustup_arch="x86_64-unknown-linux-gnu"; \
rustup_sha256="20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c" \
;; \
arm64) \
rustup_arch="aarch64-unknown-linux-gnu"; \
rustup_sha256="e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c" \
;; \
*) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac \
&& curl --proto '=https' --tlsv1.2 --fail --show-error --location \
"https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustup_arch}/rustup-init" \
-o /tmp/rustup-init \
&& echo "${rustup_sha256} /tmp/rustup-init" | sha256sum --check - \
&& chmod 0755 /tmp/rustup-init \
&& /tmp/rustup-init -y --no-modify-path --profile minimal \
--default-toolchain "${RUST_VERSION}" \
&& rm -f /tmp/rustup-init \
&& rustc --version \
&& cargo --version
WORKDIR /tmp/crossdesk-dependencies
COPY xmake.lua ./xmake.lua
COPY xmake ./xmake
COPY thirdparty ./thirdparty
COPY submodules/xmake.lua ./submodules/xmake.lua
COPY submodules/minirtc/xmake.lua ./submodules/minirtc/xmake.lua
COPY submodules/minirtc/thirdparty ./submodules/minirtc/thirdparty
# Configure the real project manifests so the image cache stays aligned with
# CrossDesk's exact dependency graph. Source files are intentionally excluded
# from the Docker context; source-only changes therefore reuse this layer.
RUN xmake repo -u --root \
&& xmake f -m release --USE_CUDA=true --root -y \
&& rm -rf /data/.xmake/cache /tmp/crossdesk-dependencies \
&& mkdir -p /workspace
WORKDIR /workspace
COPY docker/linux-build/verify-build-image.sh \
/usr/local/bin/verify-crossdesk-build-image
COPY docker/linux-build/verify-glibc-baseline.sh \
/usr/local/bin/verify-crossdesk-glibc-baseline
RUN chmod 0755 \
/usr/local/bin/verify-crossdesk-build-image \
/usr/local/bin/verify-crossdesk-glibc-baseline \
&& verify-crossdesk-build-image
CMD ["bash"]
+41
View File
@@ -0,0 +1,41 @@
# CrossDesk Linux 构建镜像
这个目录维护 CrossDesk 专用的 Ubuntu 22.04 构建环境。镜像包含 xmake、
Rust、Linux 开发库以及根据项目 `xmake.lua` 提前编译好的依赖。
## 发布方式
推送影响镜像的文件到 `ci/linux-build-image` 分支时,
`build-linux-image.yml` 会在 GitHub 原生 amd64/arm64 runner 上分别构建和验证,
全部通过后再合并为同一个 multi-arch 镜像:
```text
crossdesk/ubuntu22.04:buildenv-cuda12.6.3-xmake3.0.9-rust1.92.0
```
Docker 会根据运行机器自动选择正确架构。amd64 变体以
`nvidia/cuda:12.6.3-devel-ubuntu22.04` 为基础;arm64 变体不包含当前构建
用不到的 CUDA SDK。两个变体都使用 Ubuntu 22.04/glibc 2.35。
因此该镜像生成的 Linux 安装包最低支持 Ubuntu 22.04,不再保证兼容
Ubuntu 20.04。
每次成功发布还会生成 `sha-<完整提交 SHA>`,用于精确回滚;`latest` 和上面的
工具链版本 tag 只会在两个架构都验证成功后更新。
## GitHub 配置
在仓库的 Actions secrets 中配置:
- `DOCKERHUB_USERNAME`:有权推送 `crossdesk/ubuntu22.04` 的 Docker Hub 用户名
- `DOCKERHUB_TOKEN`:该用户的 Docker Hub access token
工作流文件进入默认分支后,也可以从 Actions 页面手动运行。为了避免首次发布
新 tag 时应用构建先于镜像完成,常规 `build.yml` 不响应
`ci/linux-build-image` 分支的 push。第一次启用时,应先推送该分支并等待镜像
发布成功,再把应用构建工作流的修改合并到 `main`
## 更新依赖
修改 Dockerfile、xmake 清单、本地包配方或子模块中的包配方后,推送该分支即可。
升级工具链时应同时修改 Dockerfile 的版本参数、工作流中的 `STACK_TAG`,以及
常规构建工作流引用的镜像 tag。
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -euo pipefail
EXPECTED_ARCH="${EXPECTED_ARCH:-}"
EXPECTED_XMAKE_VERSION="${EXPECTED_XMAKE_VERSION:-3.0.9}"
EXPECTED_RUST_VERSION="${EXPECTED_RUST_VERSION:-1.92.0}"
# shellcheck disable=SC1091
source /etc/os-release
if [[ "${ID}" != "ubuntu" || "${VERSION_ID}" != "22.04" ]]; then
echo "Expected Ubuntu 22.04, found ${ID:-unknown} ${VERSION_ID:-unknown}" >&2
exit 1
fi
GLIBC_VERSION="$(getconf GNU_LIBC_VERSION | awk '{print $2}')"
if [[ "${GLIBC_VERSION}" != "2.35" ]]; then
echo "Expected glibc 2.35, found ${GLIBC_VERSION}" >&2
exit 1
fi
ACTUAL_ARCH="$(dpkg --print-architecture)"
if [[ -n "${EXPECTED_ARCH}" && "${ACTUAL_ARCH}" != "${EXPECTED_ARCH}" ]]; then
echo "Expected architecture ${EXPECTED_ARCH}, found ${ACTUAL_ARCH}" >&2
exit 1
fi
XMAKE_VERSION_OUTPUT="$(xmake --version 2>&1)"
grep -F "v${EXPECTED_XMAKE_VERSION}" <<<"${XMAKE_VERSION_OUTPUT}" >/dev/null
rustc --version | grep -F "rustc ${EXPECTED_RUST_VERSION}" >/dev/null
cargo --version >/dev/null
cmake --version >/dev/null
g++ --version >/dev/null
pkg-config --exists xft
for package_path in \
"s/slint" \
"l/libdatachannel" \
"o/openssl3" \
"s/spdlog"; do
if [[ ! -d "${XMAKE_GLOBALDIR}/.xmake/packages/${package_path}" ]]; then
echo "Prebuilt xmake package is missing: ${package_path}" >&2
exit 1
fi
done
if ! find "${XMAKE_GLOBALDIR}/.xmake/packages/s/slint" \
-type f -name 'slint-compiler*' -print -quit | grep -q .; then
echo "The prebuilt Slint compiler is missing" >&2
exit 1
fi
if [[ "${ACTUAL_ARCH}" == "amd64" && ! -f "${CUDA_PATH}/include/cuda.h" ]]; then
echo "The amd64 image is missing CUDA headers under ${CUDA_PATH}" >&2
exit 1
fi
echo "Verified CrossDesk build image: Ubuntu ${VERSION_ID}, ${ACTUAL_ARCH}, glibc ${GLIBC_VERSION}"
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 1 || $# -gt 2 ]]; then
echo "Usage: $0 <ELF-file-or-directory> [maximum-glibc-version]" >&2
exit 2
fi
INPUT_PATH="$1"
MAXIMUM_VERSION="${2:-2.35}"
verify_elf() {
local elf_file="$1"
local version=""
local highest_version="none"
local -a required_versions=()
mapfile -t required_versions < <(
readelf --version-info "${elf_file}" \
| grep -oE 'GLIBC_[0-9]+(\.[0-9]+)+' \
| sed 's/^GLIBC_//' \
| sort -Vu \
|| true
)
for version in "${required_versions[@]}"; do
if dpkg --compare-versions "${version}" gt "${MAXIMUM_VERSION}"; then
echo "${elf_file} requires GLIBC_${version}; maximum allowed is GLIBC_${MAXIMUM_VERSION}" >&2
return 1
fi
done
if [[ ${#required_versions[@]} -gt 0 ]]; then
highest_version="${required_versions[-1]}"
fi
echo "Verified ${elf_file}: highest required glibc version is ${highest_version}"
}
if [[ -f "${INPUT_PATH}" ]]; then
if ! readelf -h "${INPUT_PATH}" >/dev/null 2>&1; then
echo "Not an ELF file: ${INPUT_PATH}" >&2
exit 1
fi
verify_elf "${INPUT_PATH}"
exit 0
fi
if [[ ! -d "${INPUT_PATH}" ]]; then
echo "File or directory does not exist: ${INPUT_PATH}" >&2
exit 1
fi
ELF_COUNT=0
while IFS= read -r -d '' candidate; do
if readelf -h "${candidate}" >/dev/null 2>&1; then
verify_elf "${candidate}"
ELF_COUNT=$((ELF_COUNT + 1))
fi
done < <(find "${INPUT_PATH}" -type f -print0)
if [[ ${ELF_COUNT} -eq 0 ]]; then
echo "No ELF files found under ${INPUT_PATH}" >&2
exit 1
fi
+1 -1
View File
@@ -147,7 +147,7 @@ Version: $DEB_VERSION
Architecture: $DEBIAN_ARCH
Maintainer: $MAINTAINER
Description: $DESCRIPTION
Depends: libc6 (>= 2.29), libstdc++6 (>= 9), libx11-6, libxext6,
Depends: libc6 (>= 2.35), libstdc++6 (>= 9), libx11-6, libxext6,
libxrender1, libxft2, libxrandr2, libxfixes3, libxcb1, libxcb-randr0,
libxcb-xtest0, libxcb-xinerama0, libxcb-shape0, libxcb-xkb1,
libxcb-xfixes0, libxv1, libxtst6, $ALSA_RUNTIME_DEP, libsndio7.0,
+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <app-bundle> <expected-architecture>" >&2
exit 2
fi
APP_BUNDLE="$1"
EXPECTED_ARCH="$2"
APP_EXECUTABLE="$APP_BUNDLE/Contents/MacOS/CrossDesk"
FRAMEWORKS_DIR="$APP_BUNDLE/Contents/Frameworks"
for command_name in codesign install_name_tool lipo otool; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "Required bundling command is missing: $command_name" >&2
exit 1
fi
done
if [[ ! -x "$APP_EXECUTABLE" ]]; then
echo "Missing app executable: $APP_EXECUTABLE" >&2
exit 1
fi
if ! lipo "$APP_EXECUTABLE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
echo "App executable does not contain the expected architecture: $EXPECTED_ARCH" >&2
lipo -archs "$APP_EXECUTABLE" >&2 || true
exit 1
fi
SLINT_REFERENCE="$(otool -L "$APP_EXECUTABLE" | awk \
'$1 ~ /(^|\/)libslint_cpp[^\/[:space:]]*\.dylib$/ { print $1; exit }')"
if [[ -z "$SLINT_REFERENCE" ]]; then
echo "The app executable does not reference the Slint runtime" >&2
exit 1
fi
SLINT_NAME="${SLINT_REFERENCE##*/}"
SLINT_CURRENT_VERSION="$(otool -L "$APP_EXECUTABLE" | awk -v dependency="$SLINT_REFERENCE" '
$1 == dependency {
for (i = 1; i <= NF; ++i) {
if ($i == "current") {
version = $(i + 2)
gsub(/[),]/, "", version)
print version
exit
}
}
}')"
slint_runtime_version() {
local library="$1"
otool -L "$library" | awk -v name="$SLINT_NAME" '
index($1, "/" name) || $1 == "@rpath/" name {
for (i = 1; i <= NF; ++i) {
if ($i == "current") {
version = $(i + 2)
gsub(/[),]/, "", version)
print version
exit
}
}
}'
}
find_slint_runtime() {
local candidate=""
local candidate_version=""
local package_root=""
local package_roots=()
if [[ "$SLINT_REFERENCE" == /* && -f "$SLINT_REFERENCE" ]]; then
if lipo "$SLINT_REFERENCE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
echo "$SLINT_REFERENCE"
return 0
fi
fi
if [[ -n "${XMAKE_GLOBALDIR:-}" ]]; then
package_roots+=("$XMAKE_GLOBALDIR/.xmake/packages")
fi
package_roots+=("$HOME/.xmake/packages" "/data/.xmake/packages")
for package_root in "${package_roots[@]}"; do
[[ -d "$package_root/s/slint" ]] || continue
while IFS= read -r candidate; do
if ! lipo "$candidate" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
continue
fi
candidate_version="$(slint_runtime_version "$candidate")"
if [[ -n "$SLINT_CURRENT_VERSION" && "$candidate_version" != "$SLINT_CURRENT_VERSION" ]]; then
continue
fi
echo "$candidate"
return 0
done < <(find "$package_root/s/slint" -type f -path "*/lib/$SLINT_NAME" -print 2>/dev/null)
done
return 1
}
SLINT_LIBRARY="$(find_slint_runtime || true)"
if [[ -z "$SLINT_LIBRARY" ]]; then
echo "Unable to locate $SLINT_NAME for $EXPECTED_ARCH (current version $SLINT_CURRENT_VERSION)" >&2
exit 1
fi
install -d "$FRAMEWORKS_DIR"
install -m 0755 "$SLINT_LIBRARY" "$FRAMEWORKS_DIR/$SLINT_NAME"
install_name_tool -id "@rpath/$SLINT_NAME" "$FRAMEWORKS_DIR/$SLINT_NAME"
if [[ "$SLINT_REFERENCE" != "@rpath/$SLINT_NAME" ]]; then
install_name_tool -change "$SLINT_REFERENCE" "@rpath/$SLINT_NAME" "$APP_EXECUTABLE"
fi
if ! otool -l "$APP_EXECUTABLE" | awk '
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
in_rpath && $1 == "path" && $2 == "@executable_path/../Frameworks" { found = 1 }
END { exit(found ? 0 : 1) }'; then
install_name_tool -add_rpath "@executable_path/../Frameworks" "$APP_EXECUTABLE"
fi
# install_name_tool invalidates the linker's ad-hoc signature. Sign nested code
# first, then seal the complete app bundle so local packages remain launchable.
codesign --force --sign - "$FRAMEWORKS_DIR/$SLINT_NAME"
codesign --force --deep --sign - "$APP_BUNDLE"
echo "Bundled Slint runtime: $SLINT_LIBRARY"
+8 -3
View File
@@ -1,14 +1,16 @@
#!/bin/bash
set -e
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="crossdesk"
APP_NAME_UPPER="CrossDesk"
EXECUTABLE_PATH="./build/macosx/arm64/release/crossdesk"
PLATFORM="macos"
ARCH="arm64"
BINARY_ARCH="arm64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/macos/crossdesk.icns"
MACOS_MIN_VERSION="10.12"
MACOS_MIN_VERSION="14.0"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
normalize_app_version() {
local input="$1"
@@ -95,6 +97,9 @@ EOF
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
find "${APP_BUNDLE}" -name '._*' -delete
"${SCRIPT_DIR}/bundle_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
"${SCRIPT_DIR}/verify_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
echo ".app created successfully."
mkdir -p build_pkg_scripts
+8 -3
View File
@@ -1,14 +1,16 @@
#!/bin/bash
set -e
#!/usr/bin/env bash
set -euo pipefail
APP_NAME="crossdesk"
APP_NAME_UPPER="CrossDesk"
EXECUTABLE_PATH="build/macosx/x86_64/release/crossdesk"
PLATFORM="macos"
ARCH="x64"
BINARY_ARCH="x86_64"
IDENTIFIER="cn.crossdesk.app"
ICON_PATH="icons/macos/crossdesk.icns"
MACOS_MIN_VERSION="10.12"
MACOS_MIN_VERSION="14.0"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
normalize_app_version() {
local input="$1"
@@ -95,6 +97,9 @@ EOF
xattr -cr "${APP_BUNDLE}" 2>/dev/null || true
find "${APP_BUNDLE}" -name '._*' -delete
"${SCRIPT_DIR}/bundle_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
"${SCRIPT_DIR}/verify_app.sh" "${APP_BUNDLE}" "${BINARY_ARCH}"
echo ".app created successfully."
mkdir -p build_pkg_scripts
+99
View File
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <app-bundle> <expected-architecture>" >&2
exit 2
fi
APP_BUNDLE="$1"
EXPECTED_ARCH="$2"
APP_EXECUTABLE="$APP_BUNDLE/Contents/MacOS/CrossDesk"
FRAMEWORKS_DIR="$APP_BUNDLE/Contents/Frameworks"
INFO_PLIST="$APP_BUNDLE/Contents/Info.plist"
if [[ ! -x "$APP_EXECUTABLE" ]]; then
echo "Missing app executable: $APP_EXECUTABLE" >&2
exit 1
fi
if ! lipo "$APP_EXECUTABLE" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
echo "Unexpected app architecture; expected $EXPECTED_ARCH" >&2
lipo -archs "$APP_EXECUTABLE" >&2 || true
exit 1
fi
if ! otool -l "$APP_EXECUTABLE" | awk '
$1 == "cmd" { in_rpath = ($2 == "LC_RPATH"); next }
in_rpath && $1 == "path" && $2 == "@executable_path/../Frameworks" { found = 1 }
END { exit(found ? 0 : 1) }'; then
echo "Missing app rpath: @executable_path/../Frameworks" >&2
exit 1
fi
verify_dependencies() {
local macho="$1"
local dependency=""
local relative_path=""
while IFS= read -r dependency; do
case "$dependency" in
/System/Library/*|/usr/lib/*)
;;
@rpath/*)
relative_path="${dependency#@rpath/}"
if [[ ! -f "$FRAMEWORKS_DIR/$relative_path" ]]; then
echo "Unbundled dependency in $macho: $dependency" >&2
return 1
fi
;;
@executable_path/../Frameworks/*)
relative_path="${dependency#@executable_path/../Frameworks/}"
if [[ ! -f "$FRAMEWORKS_DIR/$relative_path" ]]; then
echo "Unbundled dependency in $macho: $dependency" >&2
return 1
fi
;;
@loader_path/*)
relative_path="${dependency#@loader_path/}"
if [[ ! -f "$(dirname "$macho")/$relative_path" ]]; then
echo "Unbundled dependency in $macho: $dependency" >&2
return 1
fi
;;
*)
echo "External non-system dependency in $macho: $dependency" >&2
return 1
;;
esac
done < <(otool -L "$macho" | tail -n +2 | awk '{ print $1 }')
}
verify_dependencies "$APP_EXECUTABLE"
SLINT_LIBRARY="$(find "$FRAMEWORKS_DIR" -maxdepth 1 -type f -name 'libslint_cpp*.dylib' -print -quit 2>/dev/null || true)"
if [[ -z "$SLINT_LIBRARY" ]]; then
echo "The packaged app does not contain libslint_cpp.dylib" >&2
exit 1
fi
if ! lipo "$SLINT_LIBRARY" -verify_arch "$EXPECTED_ARCH" >/dev/null 2>&1; then
echo "Unexpected Slint runtime architecture; expected $EXPECTED_ARCH" >&2
lipo -archs "$SLINT_LIBRARY" >&2 || true
exit 1
fi
verify_dependencies "$SLINT_LIBRARY"
PLIST_MIN_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$INFO_PLIST")"
BINARY_MIN_VERSION="$(otool -l "$APP_EXECUTABLE" | awk '
$1 == "cmd" { build_version = ($2 == "LC_BUILD_VERSION"); version_min = ($2 == "LC_VERSION_MIN_MACOSX"); next }
build_version && $1 == "minos" { print $2; exit }
version_min && $1 == "version" { print $2; exit }')"
if [[ -z "$BINARY_MIN_VERSION" || "$PLIST_MIN_VERSION" != "$BINARY_MIN_VERSION" ]]; then
echo "Minimum macOS version mismatch: plist=$PLIST_MIN_VERSION binary=${BINARY_MIN_VERSION:-unknown}" >&2
exit 1
fi
codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE"
echo "Verified $APP_BUNDLE ($EXPECTED_ARCH, macOS $BINARY_MIN_VERSION+)"
@@ -1,5 +1,9 @@
#include "keyboard_capturer.h"
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <errno.h>
#include <poll.h>
@@ -7,10 +7,6 @@
#ifndef _KEYBOARD_CAPTURER_H_
#define _KEYBOARD_CAPTURER_H_
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <atomic>
#include <cstdint>
#include <functional>
@@ -21,6 +17,7 @@
struct DBusConnection;
struct DBusMessageIter;
struct _XDisplay;
namespace crossdesk {
@@ -48,8 +45,8 @@ class KeyboardCapturer : public DeviceController {
append_args);
private:
Display* display_;
Window root_;
_XDisplay* display_;
unsigned long root_;
std::atomic<bool> running_;
std::thread event_thread_;
bool use_wayland_portal_ = false;
@@ -1,6 +1,9 @@
#include "mouse_controller.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XTest.h>
#include <unistd.h>
#include "platform.h"
#include "rd_log.h"
@@ -7,10 +7,6 @@
#ifndef _MOUSE_CONTROLLER_H_
#define _MOUSE_CONTROLLER_H_
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <unistd.h>
#include <cstdint>
#include <functional>
#include <string>
@@ -20,6 +16,7 @@
struct DBusConnection;
struct DBusMessageIter;
struct _XDisplay;
namespace crossdesk {
@@ -53,8 +50,8 @@ class MouseController : public DeviceController {
enum class WaylandAbsoluteMode { kUnknown, kPixels, kNormalized, kDisabled };
Display* display_ = nullptr;
Window root_ = 0;
_XDisplay* display_ = nullptr;
unsigned long root_ = 0;
std::vector<DisplayInfo> display_info_list_;
int screen_width_ = 0;
int screen_height_ = 0;
+6 -5
View File
@@ -57,7 +57,7 @@ void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
v_fit.data(), (fit_w + 1) / 2, abgr.data(), fit_w * 4,
fit_w, fit_h);
memset(dst_rgba, 0, dst_w * dst_h * 4);
std::memset(dst_rgba, 0, dst_w * dst_h * 4);
for (int i = 0; i < dst_w * dst_h; ++i) {
dst_rgba[i * 4 + 3] = static_cast<char>(0xFF);
}
@@ -65,7 +65,8 @@ void ScaleNv12ToABGR(char* src, int src_w, int src_h, int dst_w, int dst_h,
for (int row = 0; row < fit_h; ++row) {
int dst_offset =
((row + (dst_h - fit_h) / 2) * dst_w + (dst_w - fit_w) / 2) * 4;
memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4, fit_w * 4);
std::memcpy(dst_rgba + dst_offset, abgr.data() + row * fit_w * 4,
fit_w * 4);
}
}
@@ -85,8 +86,8 @@ Thumbnail::Thumbnail(std::string save_path, unsigned char* aes128_key,
save_path_ = save_path;
}
memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
std::memcpy(aes128_key_, aes128_key, sizeof(aes128_key_));
std::memcpy(aes128_iv_, aes128_iv, sizeof(aes128_iv_));
std::filesystem::create_directories(save_path_);
}
@@ -116,7 +117,7 @@ int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
thumbnail_height_, rgba_buffer_);
} else {
// If yuv420p is null, fill the buffer with black pixels
memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
std::memset(rgba_buffer_, 0x00, thumbnail_width_ * thumbnail_height_ * 4);
for (int i = 0; i < thumbnail_width_ * thumbnail_height_; ++i) {
// Set alpha channel to opaque
rgba_buffer_[i * 4 + 3] = static_cast<char>(0xFF);
+5 -4
View File
@@ -7,6 +7,7 @@
#ifndef _THUMBNAIL_H_
#define _THUMBNAIL_H_
#include <cstring>
#include <filesystem>
#include <map>
#include <unordered_map>
@@ -52,18 +53,18 @@ class Thumbnail {
int DeleteAllFilesInDirectory();
int GetKey(unsigned char* aes128_key) {
memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
std::memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
return sizeof(aes128_key_);
}
int GetIv(unsigned char* aes128_iv) {
memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
std::memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
return sizeof(aes128_iv_);
}
int GetKeyAndIv(unsigned char* aes128_key, unsigned char* aes128_iv) {
memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
std::memcpy(aes128_key, aes128_key_, sizeof(aes128_key_));
std::memcpy(aes128_iv, aes128_iv_, sizeof(aes128_iv_));
return 0;
}
@@ -13,14 +13,17 @@ package("slint")
on_load(function(package)
package:add("includedirs", "include/slint")
package:add("links", "slint_cpp")
if package:is_plat("windows") then
package:add("links", "slint_cpp.dll")
else
package:add("links", "slint_cpp")
end
end)
on_install("windows", "linux", "macosx", function(package)
local configs = {
"-DSLINT_BUILD_TESTING=OFF",
"-DSLINT_BUILD_EXAMPLES=OFF",
"-DSLINT_COMPILER=download",
"-DSLINT_FEATURE_INTERPRETER=OFF",
"-DSLINT_FEATURE_LIVE_PREVIEW=OFF",
"-DSLINT_FEATURE_TESTING=OFF",
@@ -36,23 +39,6 @@ package("slint")
"-DBUILD_SHARED_LIBS=ON"
}
import("package.tools.cmake").install(package, configs)
-- SLINT_COMPILER=download only configures downstream CMake projects.
-- xmake's native Slint rule needs the matching host executable here.
import("net.http")
import("utils.archive")
local host_system = is_host("windows") and "windows" or (is_host("macosx") and "Darwin" or "Linux")
local host_arch = os.arch()
if host_arch == "x64" then
host_arch = "x86_64"
end
local suffix = host_system .. "-" .. host_arch
local archive_name = "slint-compiler-" .. suffix .. ".tar.gz"
local archive_file = path.join(package:builddir(), archive_name)
local download_url = "https://github.com/slint-ui/slint/releases/download/v" .. package:version_str() .. "/" .. archive_name
http.download(download_url, archive_file)
os.mkdir(path.join(package:installdir(), "bin"))
archive.extract(archive_file, package:installdir())
end)
on_test(function(package)
+1 -1
View File
@@ -1 +1 @@
includes("cpp-httplib")
includes("cpp-httplib", "slint")
-2
View File
@@ -1,8 +1,6 @@
set_project("crossdesk")
set_license("GPL-3.0-only")
add_repositories("crossdesk-packages " .. path.join(os.scriptdir(), "xmake", "repository"))
includes("xmake/options.lua")
includes("xmake/platform.lua")
includes("xmake/rules/slint.lua")
+1
View File
@@ -26,6 +26,7 @@ end
function setup_platform_settings()
if is_os("windows") then
add_requires("libyuv", "miniaudio 0.11.21")
add_defines("NOMINMAX")
add_links("Shell32", "dwmapi", "User32", "kernel32",
"gdi32", "winmm", "setupapi", "version",
"Imm32", "iphlpapi", "d3d11", "dxgi")
+1
View File
@@ -240,6 +240,7 @@ function setup_targets()
"src/gui/features/settings/*.cpp", "src/gui/ui/crossdesk_ui.slint")
add_includedirs("src/gui", {public = true})
if is_os("windows") then
add_cxxflags("/bigobj")
add_files("src/gui/platform/tray/win_tray.cpp")
add_includedirs("src/service/windows", {public = true})
elseif is_os("macosx") then