Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Recorder native SDK

AIScan uses a two-stage build so ordinary full builds do not compile FFmpeg and x264.

1. Maintainers run the `recorder-native-sdk` workflow after changing `versions.env` or the native build configuration. It builds the pinned sources, creates relocatable static SDK archives, writes SHA-256 sidecars, and publishes the assets to the versioned GitHub release.
2. Users, CI, and product release jobs run `fetch.sh`. It downloads the matching platform archive once, verifies it, and installs it below `.cache/record-native` before the Go/CGO link step.

Supported bundles are `linux-amd64`, `linux-arm64`, and `windows-amd64`. FFmpeg and x264 are static; operating-system libraries remain external dependencies. The source builders use an explicit component allowlist (capture input, H.264 encoder, MP4 muxer, and file output only), and packaging rejects static-library sets larger than 16 MiB by default.

Commands:

```bash
# Consumer path (default for make full)
bash .github/native/fetch.sh linux amd64

# Maintainer/source path
bash .github/native/build-linux.sh
bash .github/native/package.sh linux amd64 dist/native
```

Environment overrides:

- `AISCAN_RECORD_PREFIX`: SDK install/cache directory.
- `AISCAN_RECORD_NATIVE_URL`: release or mirror base URL containing the archive and `.sha256` sidecar.
- `AISCAN_RECORD_OFFLINE=1`: forbid downloads and require an already cached matching SDK.
- `AISCAN_RECORD_BUILD_FROM_SOURCE=1`: make `make full` or `build.sh -p full` use the pinned source builders instead of downloading an SDK.

When native inputs or flags change, increment `RECORD_NATIVE_VERSION` and `RECORD_NATIVE_RELEASE` together before publishing. Do not replace an existing SDK version with incompatible contents.
85 changes: 85 additions & 0 deletions .github/native/build-linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "${ROOT}/.github/native/versions.env"

ARCH="$(go env GOARCH)"
PREFIX="${AISCAN_RECORD_PREFIX:-${ROOT}/.cache/record-native/linux-${ARCH}}"
SOURCE_ROOT="${AISCAN_RECORD_SOURCE:-${ROOT}/.cache/record-native/src}"
STAMP="${PREFIX}/.versions"
EXPECTED="source_bundle=${RECORD_NATIVE_VERSION} ffmpeg=${FFMPEG_COMMIT} x264=${X264_COMMIT}"

emit_env() {
if [[ -n "${GITHUB_ENV:-}" ]]; then
echo "PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig" >> "${GITHUB_ENV}"
echo "PKG_CONFIG=${ROOT}/.github/native/pkg-config-static.sh" >> "${GITHUB_ENV}"
echo "CGO_CFLAGS=-I${PREFIX}/include" >> "${GITHUB_ENV}"
echo "CGO_LDFLAGS=-L${PREFIX}/lib" >> "${GITHUB_ENV}"
fi
}

if [[ -f "${STAMP}" ]] && [[ "$(cat "${STAMP}")" == "${EXPECTED}" ]]; then
echo "record native dependencies already built at ${PREFIX}"
emit_env
exit 0
fi

mkdir -p "${SOURCE_ROOT}" "${PREFIX}"

if [[ ! -d "${SOURCE_ROOT}/x264/.git" ]]; then
git clone https://code.videolan.org/videolan/x264.git "${SOURCE_ROOT}/x264"
fi
git -C "${SOURCE_ROOT}/x264" fetch --depth 1 origin "${X264_COMMIT}"
git -C "${SOURCE_ROOT}/x264" checkout --detach "${X264_COMMIT}"
(
cd "${SOURCE_ROOT}/x264"
make distclean >/dev/null 2>&1 || true
./configure \
--prefix="${PREFIX}" \
--enable-static --enable-pic --disable-cli \
--bit-depth=8 --chroma-format=420 \
--disable-opencl --disable-interlaced
make -j"$(nproc)"
make install
)

if [[ ! -d "${SOURCE_ROOT}/ffmpeg/.git" ]]; then
git clone https://github.com/FFmpeg/FFmpeg.git "${SOURCE_ROOT}/ffmpeg"
fi
git -C "${SOURCE_ROOT}/ffmpeg" fetch --depth 1 origin "${FFMPEG_COMMIT}"
git -C "${SOURCE_ROOT}/ffmpeg" checkout --detach "${FFMPEG_COMMIT}"
(
cd "${SOURCE_ROOT}/ffmpeg"
make distclean >/dev/null 2>&1 || true
PKG_CONFIG_PATH="${PREFIX}/lib/pkgconfig" ./configure \
--prefix="${PREFIX}" \
--disable-shared --enable-static --enable-pic \
--disable-programs --disable-doc --disable-debug --disable-network \
--disable-autodetect --disable-everything \
--enable-gpl --enable-libx264 \
--enable-indev=xcbgrab --enable-decoder=rawvideo \
--enable-encoder=libx264 --enable-muxer=mp4 \
--enable-protocol=file --enable-swscale \
--enable-libxcb --enable-libxcb-shm --enable-libxcb-shape --enable-libxcb-xfixes \
--extra-cflags="-I${PREFIX}/include" \
--extra-ldflags="-L${PREFIX}/lib"
bash "${ROOT}/.github/native/verify-ffmpeg-config.sh" linux config_components.h
make -j"$(nproc)"
make install
)

mkdir -p "${PREFIX}/share/licenses/ffmpeg" "${PREFIX}/share/licenses/x264"
for license in COPYING.GPLv2 COPYING.GPLv3 LICENSE.md; do
if [[ -f "${SOURCE_ROOT}/ffmpeg/${license}" ]]; then
cp "${SOURCE_ROOT}/ffmpeg/${license}" "${PREFIX}/share/licenses/ffmpeg/"
fi
done
if [[ -f "${SOURCE_ROOT}/x264/COPYING" ]]; then
cp "${SOURCE_ROOT}/x264/COPYING" "${PREFIX}/share/licenses/x264/"
fi

printf '%s' "${EXPECTED}" > "${STAMP}"
echo "record native dependencies built at ${PREFIX}"

emit_env
109 changes: 109 additions & 0 deletions .github/native/build-windows.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "${ROOT}/.github/native/versions.env"

# Calling usr/bin/bash directly does not enter the MINGW64 environment. Make
# the toolchain selection explicit so local builds and GitHub Actions agree.
export MSYSTEM=MINGW64
export PATH="/mingw64/bin:/usr/bin:${PATH}"
if ! gcc -dumpmachine | grep -q 'mingw32$'; then
echo "a MinGW-w64 GCC toolchain is required" >&2
exit 1
fi

ARCH="${GOARCH:-}"
if [[ -z "${ARCH}" ]] && command -v go >/dev/null 2>&1; then
ARCH="$(go env GOARCH)"
fi
if [[ -z "${ARCH}" ]]; then
case "$(uname -m)" in
x86_64|amd64) ARCH=amd64 ;;
*) echo "cannot determine recorder target architecture" >&2; exit 1 ;;
esac
fi
if [[ "${ARCH}" != "amd64" ]]; then
echo "record native Windows build only supports amd64" >&2
exit 1
fi

PREFIX="${AISCAN_RECORD_PREFIX:-${ROOT}/.cache/record-native/windows-${ARCH}}"
SOURCE_ROOT="${AISCAN_RECORD_SOURCE:-${ROOT}/.cache/record-native/src}"
STAMP="${PREFIX}/.versions"
EXPECTED="source_bundle=${RECORD_NATIVE_VERSION} ffmpeg=${FFMPEG_COMMIT} x264=${X264_COMMIT}"

emit_env() {
if [[ -n "${GITHUB_ENV:-}" ]]; then
PREFIX_WIN="$(cygpath -w "${PREFIX}")"
ROOT_WIN="$(cygpath -w "${ROOT}")"
echo "PKG_CONFIG_PATH=${PREFIX_WIN}\\lib\\pkgconfig" >> "${GITHUB_ENV}"
echo "PKG_CONFIG=${ROOT_WIN}\\.github\\native\\pkg-config-static.cmd" >> "${GITHUB_ENV}"
echo "CGO_CFLAGS=-I${PREFIX_WIN}\\include" >> "${GITHUB_ENV}"
echo "CGO_LDFLAGS=-L${PREFIX_WIN}\\lib -static -static-libgcc" >> "${GITHUB_ENV}"
fi
}

if [[ -f "${STAMP}" ]] && [[ "$(cat "${STAMP}")" == "${EXPECTED}" ]]; then
echo "record native dependencies already built at ${PREFIX}"
emit_env
exit 0
fi

mkdir -p "${SOURCE_ROOT}" "${PREFIX}"

if [[ ! -d "${SOURCE_ROOT}/x264/.git" ]]; then
git clone https://code.videolan.org/videolan/x264.git "${SOURCE_ROOT}/x264"
fi
git -C "${SOURCE_ROOT}/x264" fetch --depth 1 origin "${X264_COMMIT}"
git -C "${SOURCE_ROOT}/x264" checkout --detach "${X264_COMMIT}"
(
cd "${SOURCE_ROOT}/x264"
make distclean >/dev/null 2>&1 || true
./configure \
--prefix="${PREFIX}" \
--host=x86_64-w64-mingw32 \
--enable-static --disable-cli \
--bit-depth=8 --chroma-format=420 \
--disable-opencl --disable-interlaced
make -j"$(nproc)"
make install
)

if [[ ! -d "${SOURCE_ROOT}/ffmpeg/.git" ]]; then
git clone https://github.com/FFmpeg/FFmpeg.git "${SOURCE_ROOT}/ffmpeg"
fi
git -C "${SOURCE_ROOT}/ffmpeg" fetch --depth 1 origin "${FFMPEG_COMMIT}"
git -C "${SOURCE_ROOT}/ffmpeg" checkout --detach "${FFMPEG_COMMIT}"
(
cd "${SOURCE_ROOT}/ffmpeg"
make distclean >/dev/null 2>&1 || true
PKG_CONFIG_PATH="${PREFIX}/lib/pkgconfig" ./configure \
--prefix="${PREFIX}" \
--disable-shared --enable-static \
--disable-programs --disable-doc --disable-debug --disable-network \
--disable-autodetect --disable-everything \
--enable-gpl --enable-libx264 \
--enable-indev=gdigrab --enable-encoder=libx264 \
--enable-muxer=mp4 --enable-protocol=file --enable-swscale \
--extra-cflags="-I${PREFIX}/include" \
--extra-ldflags="-L${PREFIX}/lib"
bash "${ROOT}/.github/native/verify-ffmpeg-config.sh" windows config_components.h
make -j"$(nproc)"
make install
)

mkdir -p "${PREFIX}/share/licenses/ffmpeg" "${PREFIX}/share/licenses/x264"
for license in COPYING.GPLv2 COPYING.GPLv3 LICENSE.md; do
if [[ -f "${SOURCE_ROOT}/ffmpeg/${license}" ]]; then
cp "${SOURCE_ROOT}/ffmpeg/${license}" "${PREFIX}/share/licenses/ffmpeg/"
fi
done
if [[ -f "${SOURCE_ROOT}/x264/COPYING" ]]; then
cp "${SOURCE_ROOT}/x264/COPYING" "${PREFIX}/share/licenses/x264/"
fi

printf '%s' "${EXPECTED}" > "${STAMP}"
echo "record native dependencies built at ${PREFIX}"

emit_env
130 changes: 130 additions & 0 deletions .github/native/fetch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "${ROOT}/.github/native/versions.env"

detect_platform() {
case "$(uname -s)" in
Linux*) echo linux ;;
MINGW*|MSYS*|CYGWIN*) echo windows ;;
*) echo unsupported ;;
esac
}

PLATFORM="${1:-$(detect_platform)}"
ARCH="${2:-$(go env GOARCH)}"
case "${PLATFORM}/${ARCH}" in
linux/amd64|linux/arm64|windows/amd64) ;;
*)
echo "no prebuilt recorder SDK for ${PLATFORM}/${ARCH}" >&2
exit 1
;;
esac

PREFIX="${AISCAN_RECORD_PREFIX:-${ROOT}/.cache/record-native/${PLATFORM}-${ARCH}}"
ARCHIVE="aiscan-record-native-${RECORD_NATIVE_VERSION}-${PLATFORM}-${ARCH}.tar.gz"
BASE_URL="${AISCAN_RECORD_NATIVE_URL:-https://github.com/${RECORD_NATIVE_REPOSITORY}/releases/download/${RECORD_NATIVE_RELEASE}}"
EXPECTED="bundle=${RECORD_NATIVE_VERSION} platform=${PLATFORM} arch=${ARCH} ffmpeg=${FFMPEG_COMMIT} x264=${X264_COMMIT}"
STAMP="${PREFIX}/.versions"

emit_env() {
if [[ -z "${GITHUB_ENV:-}" ]]; then
return
fi
if [[ "${PLATFORM}" == "windows" ]] && command -v cygpath >/dev/null 2>&1; then
local prefix_native root_native
prefix_native="$(cygpath -w "${PREFIX}")"
root_native="$(cygpath -w "${ROOT}")"
echo "PKG_CONFIG_PATH=${prefix_native}\\lib\\pkgconfig" >> "${GITHUB_ENV}"
echo "PKG_CONFIG=${root_native}\\.github\\native\\pkg-config-static.cmd" >> "${GITHUB_ENV}"
echo "CGO_CFLAGS=-I${prefix_native}\\include" >> "${GITHUB_ENV}"
echo "CGO_LDFLAGS=-L${prefix_native}\\lib -static -static-libgcc" >> "${GITHUB_ENV}"
else
echo "PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig" >> "${GITHUB_ENV}"
echo "PKG_CONFIG=${ROOT}/.github/native/pkg-config-static.sh" >> "${GITHUB_ENV}"
echo "CGO_CFLAGS=-I${PREFIX}/include" >> "${GITHUB_ENV}"
echo "CGO_LDFLAGS=-L${PREFIX}/lib" >> "${GITHUB_ENV}"
fi
}

if [[ -f "${STAMP}" ]] && [[ "$(cat "${STAMP}")" == "${EXPECTED}" ]]; then
echo "record native SDK already available at ${PREFIX}"
emit_env
exit 0
fi

if [[ "${AISCAN_RECORD_OFFLINE:-0}" == "1" ]]; then
echo "record native SDK is not cached at ${PREFIX} and offline mode is enabled" >&2
exit 1
fi

for command_name in curl tar; do
if ! command -v "${command_name}" >/dev/null 2>&1; then
echo "${command_name} is required to download the recorder SDK" >&2
exit 1
fi
done

case "${PREFIX}" in
""|/|"${HOME:-__missing__}"|"${ROOT}")
echo "refusing unsafe recorder SDK prefix: ${PREFIX}" >&2
exit 1
;;
esac

TMP="$(mktemp -d)"
STAGE="${PREFIX}.tmp.$$"
BACKUP="${PREFIX}.old.$$"
cleanup() {
rm -rf "${TMP}" "${STAGE}"
}
trap cleanup EXIT

echo "downloading recorder SDK ${RECORD_NATIVE_VERSION} for ${PLATFORM}/${ARCH}"
curl --fail --location --retry 3 --retry-delay 2 \
"${BASE_URL}/${ARCHIVE}" -o "${TMP}/${ARCHIVE}"
curl --fail --location --retry 3 --retry-delay 2 \
"${BASE_URL}/${ARCHIVE}.sha256" -o "${TMP}/${ARCHIVE}.sha256"

if command -v sha256sum >/dev/null 2>&1; then
(cd "${TMP}" && sha256sum --check "${ARCHIVE}.sha256")
elif command -v shasum >/dev/null 2>&1; then
(cd "${TMP}" && shasum -a 256 --check "${ARCHIVE}.sha256")
else
echo "sha256sum or shasum is required to verify the recorder SDK" >&2
exit 1
fi

mkdir -p "$(dirname "${PREFIX}")"
rm -rf "${STAGE}" "${BACKUP}"
mkdir -p "${STAGE}"
tar -xzf "${TMP}/${ARCHIVE}" -C "${STAGE}"

if [[ ! -f "${STAGE}/.versions" ]] || [[ "$(cat "${STAGE}/.versions")" != "${EXPECTED}" ]]; then
echo "recorder SDK manifest does not match the requested version" >&2
exit 1
fi
for library in avcodec avdevice avfilter avformat avutil swresample swscale x264; do
if [[ ! -f "${STAGE}/lib/lib${library}.a" ]]; then
echo "recorder SDK archive is missing lib${library}.a" >&2
exit 1
fi
done
if [[ ! -d "${STAGE}/include/libavcodec" ]]; then
echo "recorder SDK archive is missing FFmpeg headers" >&2
exit 1
fi

if [[ -e "${PREFIX}" ]]; then
mv "${PREFIX}" "${BACKUP}"
fi
if ! mv "${STAGE}" "${PREFIX}"; then
if [[ -e "${BACKUP}" ]]; then
mv "${BACKUP}" "${PREFIX}"
fi
exit 1
fi
rm -rf "${BACKUP}"
echo "record native SDK installed at ${PREFIX}"
emit_env
Loading
Loading