Skip to content

Commit b048695

Browse files
authored
Fix tests to run on Cairo 1.18.0 (#3416)
* Add a script to build and install cairo * Update gui tests for cairo 1.18.0 * update script to set env vars * Make the script run with plain python * Prefer the recently built one in pkg-config * Skip the built if it's windows * CI: build and install latest cairo * CI: only run when cache is missed * Disable compiling tests while building cairo * update poetry lock file * Display the cairo version when running pytest * fixup * tests: skip graphical test when cairo is old * fix the path to find the pkgconfig files on linux * set the LD_LIBRARY_PATH too only then it'll work on linux * fixup * small fixup * Move the script inside `.github/scripts` folder * Make the minimum cairo version a constant * Seperate setting env vars to a sperate step this seem to have broken when cache is hit
1 parent 8320cdd commit b048695

File tree

227 files changed

+272
-27
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

227 files changed

+272
-27
lines changed

.github/scripts/ci_build_cairo.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# Logic is as follows:
2+
# 1. Download cairo source code: https://cairographics.org/releases/cairo-<version>.tar.xz
3+
# 2. Verify the downloaded file using the sha256sums file: https://cairographics.org/releases/cairo-<version>.tar.xz.sha256sum
4+
# 3. Extract the downloaded file.
5+
# 4. Create a virtual environment and install meson and ninja.
6+
# 5. Run meson build in the extracted directory. Also, set required prefix.
7+
# 6. Run meson compile -C build.
8+
# 7. Run meson install -C build.
9+
10+
import hashlib
11+
import logging
12+
import os
13+
import subprocess
14+
import sys
15+
import tarfile
16+
import tempfile
17+
import typing
18+
import urllib.request
19+
from contextlib import contextmanager
20+
from pathlib import Path
21+
from sys import stdout
22+
23+
CAIRO_VERSION = "1.18.0"
24+
CAIRO_URL = f"https://cairographics.org/releases/cairo-{CAIRO_VERSION}.tar.xz"
25+
CAIRO_SHA256_URL = f"{CAIRO_URL}.sha256sum"
26+
27+
VENV_NAME = "meson-venv"
28+
BUILD_DIR = "build"
29+
INSTALL_PREFIX = Path(__file__).parent.parent / "third_party" / "cairo"
30+
31+
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
32+
logger = logging.getLogger(__name__)
33+
34+
35+
def is_ci():
36+
return os.getenv("CI", None) is not None
37+
38+
39+
def download_file(url, path):
40+
logger.info(f"Downloading {url} to {path}")
41+
block_size = 1024 * 1024
42+
with urllib.request.urlopen(url) as response, open(path, "wb") as file:
43+
while True:
44+
data = response.read(block_size)
45+
if not data:
46+
break
47+
file.write(data)
48+
49+
50+
def verify_sha256sum(path, sha256sum):
51+
with open(path, "rb") as file:
52+
file_hash = hashlib.sha256(file.read()).hexdigest()
53+
if file_hash != sha256sum:
54+
raise Exception("SHA256SUM does not match")
55+
56+
57+
def extract_tar_xz(path, directory):
58+
with tarfile.open(path) as file:
59+
file.extractall(directory)
60+
61+
62+
def run_command(command, cwd=None, env=None):
63+
process = subprocess.Popen(command, cwd=cwd, env=env)
64+
process.communicate()
65+
if process.returncode != 0:
66+
raise Exception("Command failed")
67+
68+
69+
@contextmanager
70+
def gha_group(title: str) -> typing.Generator:
71+
if not is_ci():
72+
yield
73+
return
74+
print(f"\n::group::{title}")
75+
stdout.flush()
76+
try:
77+
yield
78+
finally:
79+
print("::endgroup::")
80+
stdout.flush()
81+
82+
83+
def set_env_var_gha(name: str, value: str) -> None:
84+
if not is_ci():
85+
return
86+
env_file = os.getenv("GITHUB_ENV", None)
87+
if env_file is None:
88+
return
89+
with open(env_file, "a") as file:
90+
file.write(f"{name}={value}\n")
91+
stdout.flush()
92+
93+
94+
def get_ld_library_path(prefix: Path) -> str:
95+
# given a prefix, the ld library path can be found at
96+
# <prefix>/lib/* or sometimes just <prefix>/lib
97+
# this function returns the path to the ld library path
98+
99+
# first, check if the ld library path exists at <prefix>/lib/*
100+
ld_library_paths = list(prefix.glob("lib/*"))
101+
if len(ld_library_paths) == 1:
102+
return ld_library_paths[0].absolute().as_posix()
103+
104+
# if the ld library path does not exist at <prefix>/lib/*,
105+
# return <prefix>/lib
106+
ld_library_path = prefix / "lib"
107+
if ld_library_path.exists():
108+
return ld_library_path.absolute().as_posix()
109+
return ""
110+
111+
112+
def main():
113+
if sys.platform == "win32":
114+
logger.info("Skipping build on windows")
115+
return
116+
117+
with tempfile.TemporaryDirectory() as tmpdir:
118+
with gha_group("Downloading and Extracting Cairo"):
119+
logger.info(f"Downloading cairo version {CAIRO_VERSION}")
120+
download_file(CAIRO_URL, os.path.join(tmpdir, "cairo.tar.xz"))
121+
122+
logger.info("Downloading cairo sha256sum")
123+
download_file(CAIRO_SHA256_URL, os.path.join(tmpdir, "cairo.sha256sum"))
124+
125+
logger.info("Verifying cairo sha256sum")
126+
with open(os.path.join(tmpdir, "cairo.sha256sum")) as file:
127+
sha256sum = file.read().split()[0]
128+
verify_sha256sum(os.path.join(tmpdir, "cairo.tar.xz"), sha256sum)
129+
130+
logger.info("Extracting cairo")
131+
extract_tar_xz(os.path.join(tmpdir, "cairo.tar.xz"), tmpdir)
132+
133+
with gha_group("Installing meson and ninja"):
134+
logger.info("Creating virtual environment")
135+
run_command([sys.executable, "-m", "venv", os.path.join(tmpdir, VENV_NAME)])
136+
137+
logger.info("Installing meson and ninja")
138+
run_command(
139+
[
140+
os.path.join(tmpdir, VENV_NAME, "bin", "pip"),
141+
"install",
142+
"meson",
143+
"ninja",
144+
]
145+
)
146+
147+
env_vars = {
148+
# add the venv bin directory to PATH so that meson can find ninja
149+
"PATH": f"{os.path.join(tmpdir, VENV_NAME, 'bin')}{os.pathsep}{os.environ['PATH']}",
150+
}
151+
152+
with gha_group("Building and Installing Cairo"):
153+
logger.info("Running meson setup")
154+
run_command(
155+
[
156+
os.path.join(tmpdir, VENV_NAME, "bin", "meson"),
157+
"setup",
158+
BUILD_DIR,
159+
f"--prefix={INSTALL_PREFIX.absolute().as_posix()}",
160+
"--buildtype=release",
161+
"-Dtests=disabled",
162+
],
163+
cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"),
164+
env=env_vars,
165+
)
166+
167+
logger.info("Running meson compile")
168+
run_command(
169+
[
170+
os.path.join(tmpdir, VENV_NAME, "bin", "meson"),
171+
"compile",
172+
"-C",
173+
BUILD_DIR,
174+
],
175+
cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"),
176+
env=env_vars,
177+
)
178+
179+
logger.info("Running meson install")
180+
run_command(
181+
[
182+
os.path.join(tmpdir, VENV_NAME, "bin", "meson"),
183+
"install",
184+
"-C",
185+
BUILD_DIR,
186+
],
187+
cwd=os.path.join(tmpdir, f"cairo-{CAIRO_VERSION}"),
188+
env=env_vars,
189+
)
190+
191+
logger.info(f"Successfully built cairo and installed it to {INSTALL_PREFIX}")
192+
193+
194+
if __name__ == "__main__":
195+
if "--set-env-vars" in sys.argv:
196+
with gha_group("Setting environment variables"):
197+
# append the pkgconfig directory to PKG_CONFIG_PATH
198+
set_env_var_gha(
199+
"PKG_CONFIG_PATH",
200+
f"{Path(get_ld_library_path(INSTALL_PREFIX), 'pkgconfig').as_posix()}{os.pathsep}"
201+
f'{os.getenv("PKG_CONFIG_PATH", "")}',
202+
)
203+
set_env_var_gha(
204+
"LD_LIBRARY_PATH",
205+
f"{get_ld_library_path(INSTALL_PREFIX)}{os.pathsep}"
206+
f'{os.getenv("LD_LIBRARY_PATH", "")}',
207+
)
208+
sys.exit(0)
209+
main()

.github/workflows/ci.yml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@ jobs:
7676
# start xvfb in background
7777
sudo /usr/bin/Xvfb $DISPLAY -screen 0 1280x1024x24 &
7878
79+
- name: Setup Cairo Cache
80+
uses: actions/cache@v3
81+
id: cache-cairo
82+
if: runner.os == 'Linux' || runner.os == 'macOS'
83+
with:
84+
path: ${{ github.workspace }}/third_party
85+
key: ${{ runner.os }}-dependencies-cairo-${{ hashFiles('.github/scripts/ci_build_cairo.py') }}
86+
87+
- name: Build and install Cairo (Linux and macOS)
88+
if: (runner.os == 'Linux' || runner.os == 'macOS') && steps.cache-cairo.outputs.cache-hit != 'true'
89+
run: python .github/scripts/ci_build_cairo.py
90+
91+
- name: Set env vars for Cairo (Linux and macOS)
92+
if: runner.os == 'Linux' || runner.os == 'macOS'
93+
run: python .github/scripts/ci_build_cairo.py --set-env-vars
94+
7995
- name: Setup macOS cache
8096
uses: actions/cache@v3
8197
id: cache-macos
@@ -103,10 +119,6 @@ jobs:
103119
export PATH="$oriPath"
104120
echo "Completed TinyTeX"
105121
106-
- name: Install cairo (MacOS)
107-
if: runner.os == 'macOS'
108-
run: brew install cairo
109-
110122
- name: Add macOS dependencies to PATH
111123
if: runner.os == 'macOS'
112124
shell: bash

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,6 @@ dist/
131131

132132
/media_dir.txt
133133
# ^TODO: Remove the need for this with a proper config file
134+
135+
# Ignore the built dependencies
136+
third_party/*

conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
except ModuleNotFoundError: # windows
1212
pass
1313

14+
import cairo
1415
import moderngl
1516

1617
# If it is running Doctest the current directory
@@ -39,6 +40,7 @@ def pytest_report_header(config):
3940
info = ctx.info
4041
ctx.release()
4142
return (
43+
f"\nCairo Version: {cairo.cairo_version()}",
4244
"\nOpenGL information",
4345
"------------------",
4446
f"vendor: {info['GL_VENDOR'].strip()}",

manim/utils/testing/frames_comparison.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from pathlib import Path
66
from typing import Callable
77

8+
import cairo
9+
import pytest
810
from _pytest.fixtures import FixtureRequest
911

1012
from manim import Scene
@@ -25,6 +27,7 @@
2527
SCENE_PARAMETER_NAME = "scene"
2628
_tests_root_dir_path = Path(__file__).absolute().parents[2]
2729
PATH_CONTROL_DATA = _tests_root_dir_path / Path("control_data", "graphical_units_data")
30+
MIN_CAIRO_VERSION = 11800
2831

2932

3033
def frames_comparison(
@@ -81,6 +84,12 @@ def decorator_maker(tested_scene_construct):
8184
@functools.wraps(tested_scene_construct)
8285
# The "request" parameter is meant to be used as a fixture by pytest. See below.
8386
def wrapper(*args, request: FixtureRequest, tmp_path, **kwargs):
87+
# check for cairo version
88+
if (
89+
renderer_class is CairoRenderer
90+
and cairo.cairo_version() < MIN_CAIRO_VERSION
91+
):
92+
pytest.skip("Cairo version is too old. Skipping cairo graphical tests.")
8493
# Wraps the test_function to a construct method, to "freeze" the eventual additional arguments (parametrizations fixtures).
8594
construct = functools.partial(tested_scene_construct, *args, **kwargs)
8695

poetry.lock

Lines changed: 33 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
-92 Bytes
Binary file not shown.
-91 Bytes
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)