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
65 changes: 64 additions & 1 deletion .github/workflows/build_wheels_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,57 @@ on:
workflow_dispatch:


concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
BREW_CACHE_VERSION: v1

jobs:
FFmpeg:
# Builds LGPL FFmpeg and its dependencies that have no Homebrew bottles,
# and caches the bottles for the Build jobs.
# If it runs out of time, the cache is saved anyway: re-run the job to continue.
# The cache key has the runner image in it: update it along with runs-on.
# Outdated bottles are dropped by version. Bump BREW_CACHE_VERSION if the bottles change otherwise,
# e.g. the ffmpeg formula conversion rules in travis_config.sh.
runs-on: ${{ matrix.platform == 'arm64' && 'macos-15' || 'macos-15-intel' }}
timeout-minutes: 360
strategy:
fail-fast: false
matrix:
platform: [x86_64, arm64]
env:
BREW_LOCAL_BOTTLE_DIR: ${{ github.workspace }}/../local_bottle_metadata
BREW_TIME_LIMIT: 19200 # 320 min
BREW_TIME_HARD_LIMIT: 20400 # 340 min
steps:
- name: Checkout
uses: actions/checkout@v7
with:
submodules: false
- name: Restore Homebrew bottles
uses: actions/cache/restore@v5
with:
path: ${{ env.BREW_LOCAL_BOTTLE_DIR }}
key: brew-bottles-${{ env.BREW_CACHE_VERSION }}-macos15-${{ matrix.platform }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: brew-bottles-${{ env.BREW_CACHE_VERSION }}-macos15-${{ matrix.platform }}-
- name: Build FFmpeg
id: ffmpeg
run: |
export IS_OSX=1
source travis_config.sh
install_ffmpeg_lgpl
- name: Save Homebrew bottles
if: ${{ always() && steps.ffmpeg.outcome != 'skipped' }}
uses: actions/cache/save@v5
with:
path: ${{ env.BREW_LOCAL_BOTTLE_DIR }}
key: brew-bottles-${{ env.BREW_CACHE_VERSION }}-macos15-${{ matrix.platform }}-${{ github.run_id }}-${{ github.run_attempt }}

Build:
needs: [FFmpeg]
runs-on: ${{ matrix.platform == 'arm64' && 'macos-15' || 'macos-15-intel' }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -44,6 +93,7 @@ jobs:
ENABLE_HEADLESS: ${{ matrix.without_gui }}
ENABLE_CONTRIB: ${{ matrix.with_contrib }}
PIP_INDEX_URL: https://pypi.tuna.tsinghua.edu.cn/simple
BREW_LOCAL_BOTTLE_DIR: ${{ github.workspace }}/../local_bottle_metadata
steps:
- uses: actions/setup-python@v7
with:
Expand All @@ -61,12 +111,25 @@ jobs:
with:
submodules: false
fetch-depth: 0
- name: Restore Homebrew bottles
uses: actions/cache/restore@v5
with:
path: ${{ env.BREW_LOCAL_BOTTLE_DIR }}
key: brew-bottles-${{ env.BREW_CACHE_VERSION }}-macos15-${{ matrix.platform }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: brew-bottles-${{ env.BREW_CACHE_VERSION }}-macos15-${{ matrix.platform }}-
fail-on-cache-miss: true
- name: Install FFmpeg
run: |
export IS_OSX=1
source travis_config.sh
install_ffmpeg_lgpl
- name: Build a package
run: |
git submodule update --init multibuild
echo $ENABLE_CONTRIB > contrib.enabled
echo $ENABLE_HEADLESS > headless.enabled
export MACOSX_DEPLOYMENT_TARGET=14.0
# Homebrew bottles are built for macOS 15
export MACOSX_DEPLOYMENT_TARGET=15.0
python --version
python -m pip install delocate toml && python -c 'import toml; c = toml.load("pyproject.toml"); print("\n".join(c["build-system"]["requires"]))' | python -m pip install -r /dev/stdin
python setup.py bdist_wheel --py-limited-api=cp37 --dist-dir=wheelhouse -v
Expand Down
120 changes: 77 additions & 43 deletions travis_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,53 +48,91 @@ if [ -n "$IS_OSX" ]; then

source travis_osx_brew_cache.sh

# Projected build times in minutes on a GitHub-hosted runner
BREW_SLOW_BUILIDING_PACKAGES=$(printf '%s\n' \
"cmake 15" \
"ffmpeg 10" \
"opencv/local/ffmpeg-lgpl 15" \
"openssl@3 15" \
"python@3.15 25" \
)

FFMPEG_LGPL_FORMULA="opencv/local/ffmpeg-lgpl"

function generate_ffmpeg_formula {
local FF="ffmpeg"
local LFF="ffmpeg"
local FF_FORMULA; FF_FORMULA=$(brew formula "${FF}")
local LFF_FORMULA; LFF_FORMULA="$(dirname "$FF_FORMULA")/${LFF}.rb"

local REGENERATE
if [ -f "$LFF_FORMULA" ]; then
local UPSTREAM_VERSION VERSION
_brew_parse_package_info "$FF" " " UPSTREAM_VERSION _ _
_brew_parse_package_info "$LFF" " " VERSION _ _ || REGENERATE=1
#`rebuild` clause is ignored on `brew bottle` and deleted
# from newly-generated formula on `brew bottle --merge` for some reason
# so can't compare rebuild numbers
if [ "$UPSTREAM_VERSION" != "$VERSION" ]; then
REGENERATE=1
fi
else
REGENERATE=1
# Make an LGPL build of FFmpeg out of the upstream formula:
# no GPL parts, the same set of external libraries as in Linux builds.
local TAP="${FFMPEG_LGPL_FORMULA%/*}"
local TAP_DIR; TAP_DIR="$(brew --repository "$TAP")"
[ -d "$TAP_DIR" ] || brew tap-new --no-git "$TAP"
mkdir -p "$TAP_DIR/Formula"
# Homebrew refuses to load formulae from third-party taps otherwise
brew trust "$TAP"

local FF_FORMULA; FF_FORMULA=$(mktemp)
# Formulae from the JSON API have no source on disk, so `brew cat` doesn't work for them.
# Fetch the source for the homebrew-core revision the API data is from.
local FF_SOURCE_URL FF_SOURCE_SHA256
read -r FF_SOURCE_URL FF_SOURCE_SHA256 < <(brew info --json=v2 ffmpeg | python3 -c 'if True:
import sys, json; data=json.load(sys.stdin)["formulae"][0]
print("https://raw.githubusercontent.com/Homebrew/homebrew-core/%s/%s" % (data["tap_git_head"], data["ruby_source_path"]),
data["ruby_source_checksum"]["sha256"])
')
curl -fsSL --retry 3 -o "$FF_FORMULA" "$FF_SOURCE_URL"
if [ "$(shasum -a 256 "$FF_FORMULA" | cut -d' ' -f1)" != "$FF_SOURCE_SHA256" ]; then
echo "Checksum mismatch for $FF_SOURCE_URL" >&2
return 1
fi
if [ -n "$REGENERATE" ]; then
echo "Regenerating custom ffmpeg formula"
# Bottle block syntax: https://docs.brew.sh/Bottles#bottle-dsl-domain-specific-language
perl -wpe 'BEGIN {our ($found_blank, $bottle_block);}
if (/(^class )(Ffmpeg)(\s.*)/) {$_=$1."Opencv".$3."\n"; next;}
if (!$found_blank && /^$/) {$_.="conflicts_with \"ffmpeg\"\n\n"; $found_blank=1; next;}
if (!$bottle_block && /^\s*bottle do$/) { $bottle_block=1; next; }
if ($bottle_block) { if (/^\s*end\s*$/) { $bottle_block=0} elsif (/^\s*sha256\s/) {$_=""} next; }
if (/^\s*depends_on "(x264|x265|xvid|frei0r|rubberband|libvidstab)"$/) {$_=""; next;}
if (/^\s*--enable-(gpl|libx264|libx265|libxvid|frei0r|librubberband|libvidstab)$/) {$_=""; next;}
' <"$FF_FORMULA" >"$LFF_FORMULA"
diff -u "$FF_FORMULA" "$LFF_FORMULA" || test $? -le 1

( cd "$(dirname "$LFF_FORMULA")"
# This is the official way to add a formula
# https://docs.brew.sh/Formula-Cookbook#commit
git add "$(basename "$LFF_FORMULA")"
git commit -m "add/update custom ffmpeg ${VERSION}"
)
local LFF_FORMULA; LFF_FORMULA="$TAP_DIR/Formula/${FFMPEG_LGPL_FORMULA##*/}.rb"

echo "Generating custom ffmpeg formula"
# Bottle block syntax: https://docs.brew.sh/Bottles#bottle-dsl-domain-specific-language
perl -wpe 'BEGIN {our $bottle_block;}
if (/^class Ffmpeg < Formula$/) {$_="class FfmpegLgpl < Formula\n"; next;}
if (/^(\s*)license\s/) {$_=$1."license \"LGPL-2.1-or-later\"\n".$1."keg_only \"it conflicts with the GPL-licensed ffmpeg formula\"\n"; next;}
if (!$bottle_block && /^\s*bottle do$/) { $bottle_block=1; $_=""; next; }
if ($bottle_block) { $bottle_block=0 if /^\s*end\s*$/; $_=""; next; }
if (/^\s*depends_on "(lame|libvmaf|opus|sdl2-compat|svt-av1|x264|x265|xz)"(\s*#.*)?$/) {$_=""; next;}
if (/^\s*--enable-(gpl|version3|libsvtav1|libopus|libx264|libx265|libmp3lame|libvmaf)$/) {$_=""; next;}
s/--enable-ffplay$/--disable-ffplay/;
' <"$FF_FORMULA" >"$LFF_FORMULA"
diff -u "$FF_FORMULA" "$LFF_FORMULA" || test $? -le 1
rm -f "$FF_FORMULA"

if grep -qE -e '--enable-(gpl|nonfree|version3)$' -e 'depends_on "x26[45]"' "$LFF_FORMULA"; then
echo "The generated ffmpeg formula still has GPL parts. Update the conversion rules!" >&2
return 1
fi
}

function install_ffmpeg_lgpl {
# Install LGPL FFmpeg and its dependencies, building and caching what has no bottles

# Runner images may have an outdated Homebrew,
# e.g. 7.0.12 can't install bottles from files
brew update
brew --version

generate_ffmpeg_formula
brew_add_local_bottles
brew_install_and_cache_within_time_limit "$FFMPEG_LGPL_FORMULA" || return $?
# OpenCV finds FFmpeg with pkg-config
brew_install_and_cache_within_time_limit pkgconf || return $?

local FF_PREFIX; FF_PREFIX="$(brew --prefix "$FFMPEG_LGPL_FORMULA")"
local FF_LICENSE; FF_LICENSE="$("$FF_PREFIX/bin/ffmpeg" -hide_banner -L)"
# The license text is wrapped, the phrase can be split between lines
if ! tr -s '\n ' ' ' <<<"$FF_LICENSE" | grep -qF "GNU Lesser General Public License"; then
echo "$FF_LICENSE"
echo "The built FFmpeg is not LGPL!" >&2
return 1
fi

# The formula is keg-only, so OpenCV has to be pointed to it
local PC_PATH; PC_PATH="$FF_PREFIX/lib/pkgconfig:$(brew --prefix openssl@3)/lib/pkgconfig"
export PKG_CONFIG_PATH="$PC_PATH${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
if [ -n "$GITHUB_ENV" ]; then echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH" >>"$GITHUB_ENV"; fi
}

fi

function pre_build {
Expand All @@ -116,11 +154,7 @@ function pre_build {
echo 'Installing FFmpeg'

brew update
generate_ffmpeg_formula
brew_add_local_bottles
brew install --build-bottle ffmpeg
# It needs when we use not the latest ffmpeg formula
brew link ffmpeg
install_ffmpeg_lgpl || return $?

if [ -n "$CACHE_STAGE" ]; then
brew_go_bootstrap_mode 0
Expand Down
Loading
Loading