Skip to content

Commit bd7fe6f

Browse files
authored
Merge pull request #3174 from T-Dynamos/develop
`recipes`: new pycairo recipe
2 parents 95750da + c32eda8 commit bd7fe6f

File tree

6 files changed

+206
-2
lines changed

6 files changed

+206
-2
lines changed

pythonforandroid/recipe.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,10 @@ class MesonRecipe(PyProjectRecipe):
13471347
meson_version = "1.4.0"
13481348
ninja_version = "1.11.1.1"
13491349

1350+
skip_python = False
1351+
'''If true, skips all Python build and installation steps.
1352+
Useful for Meson projects written purely in C/C++ without Python bindings.'''
1353+
13501354
def sanitize_flags(self, *flag_strings):
13511355
return " ".join(flag_strings).strip().split(" ")
13521356

@@ -1428,7 +1432,8 @@ def build_arch(self, arch):
14281432
]:
14291433
if dep not in self.hostpython_prerequisites:
14301434
self.hostpython_prerequisites.append(dep)
1431-
super().build_arch(arch)
1435+
if not self.skip_python:
1436+
super().build_arch(arch)
14321437

14331438

14341439
class RustCompiledComponentsRecipe(PyProjectRecipe):
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from pythonforandroid.recipe import Recipe, MesonRecipe
2+
from os.path import join, exists
3+
from pythonforandroid.util import ensure_dir, current_directory
4+
from pythonforandroid.logger import shprint
5+
from multiprocessing import cpu_count
6+
import sh
7+
8+
9+
class LibCairoRecipe(MesonRecipe):
10+
name = 'libcairo'
11+
version = '1.18.4'
12+
url = 'https://gitlab.freedesktop.org/cairo/cairo/-/archive/{version}/cairo-{version}.tar.bz2'
13+
skip_python = True
14+
depends = ["png", "freetype"]
15+
patches = ["meson.patch"]
16+
built_libraries = {
17+
'libcairo.so': 'install/lib',
18+
'libpixman-1.so': 'install/lib',
19+
'libcairo-script-interpreter.so': 'install/lib'
20+
}
21+
22+
def get_recipe_env(self, arch, **kwargs):
23+
env = super().get_recipe_env(arch, **kwargs)
24+
cpufeatures = join(self.ctx.ndk.ndk_dir, "sources/android/cpufeatures")
25+
lib_dir = join(cpufeatures, "obj", "local", arch.arch)
26+
env["CFLAGS"] += f" -I{cpufeatures}"
27+
env["LDFLAGS"] += f" -L{lib_dir} -lcpufeatures"
28+
return env
29+
30+
def should_build(self, arch):
31+
return Recipe.should_build(self, arch)
32+
33+
def build_arch(self, arch):
34+
super().build_arch(arch)
35+
build_dir = self.get_build_dir(arch.arch)
36+
install_dir = join(build_dir, 'install')
37+
ensure_dir(install_dir)
38+
env = self.get_recipe_env(arch)
39+
40+
lib_dir = self.ctx.get_libs_dir(arch.arch)
41+
png_include = self.get_recipe('png', self.ctx).get_build_dir(arch.arch)
42+
freetype_inc = join(self.get_recipe('freetype', self.ctx).get_build_dir(arch), "include")
43+
44+
with current_directory(build_dir):
45+
46+
cpufeatures_dir = join(self.ctx.ndk.ndk_dir, "sources/android/cpufeatures")
47+
lib_file = join(cpufeatures_dir, "obj", "local", arch.arch, "libcpufeatures.a")
48+
49+
if not exists(lib_file):
50+
shprint(
51+
sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
52+
f"NDK_PROJECT_PATH={cpufeatures_dir}",
53+
f"APP_BUILD_SCRIPT={cpufeatures_dir}/Android.mk",
54+
f"APP_ABI={arch.arch}",
55+
"APP_PLATFORM=latest",
56+
_env=env
57+
)
58+
59+
shprint(sh.meson, 'setup', 'builddir',
60+
'--cross-file', join("/tmp", "android.meson.cross"),
61+
f'--prefix={install_dir}',
62+
'-Dpng=enabled',
63+
'-Dzlib=enabled',
64+
'-Dglib=disabled',
65+
'-Dgtk_doc=false',
66+
'-Dsymbol-lookup=disabled',
67+
68+
# deps
69+
f'-Dpng_include_dir={png_include}',
70+
f'-Dpng_lib_dir={lib_dir}',
71+
f'-Dfreetype_include_dir={freetype_inc}',
72+
f'-Dfreetype_lib_dir={lib_dir}',
73+
_env=env)
74+
75+
shprint(sh.ninja, '-C', 'builddir', '-j', str(cpu_count()), _env=env)
76+
# macOS fix: sometimes Ninja creates a dummy 'lib' file instead of a directory.
77+
# So we remove and recreate the install directory using shell commands,
78+
# since os.remove/os.makedirs behave inconsistently in this build env.
79+
shprint(sh.rm, '-rf', install_dir)
80+
shprint(sh.mkdir, install_dir)
81+
82+
shprint(sh.ninja, '-C', 'builddir', 'install', _env=env)
83+
84+
85+
recipe = LibCairoRecipe()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
diff '--color=auto' -uNr cairo-1.18.4/meson.build cairo-1.18.4.mod/meson.build
2+
--- cairo-1.18.4/meson.build 2025-03-08 18:53:25.000000000 +0530
3+
+++ cairo-1.18.4.mod/meson.build 2025-07-14 20:42:56.226164648 +0530
4+
@@ -235,11 +235,13 @@
5+
conf.set('HAVE_ZLIB', 1)
6+
endif
7+
8+
-png_dep = dependency('libpng',
9+
- required: get_option('png'),
10+
- version: libpng_required_version,
11+
- fallback: ['libpng', 'libpng_dep']
12+
+png_inc = include_directories(get_option('png_include_dir'))
13+
+png_lib = cc.find_library('png16', dirs: [get_option('png_lib_dir')], required: true)
14+
+png_dep = declare_dependency(
15+
+ include_directories: png_inc,
16+
+ dependencies: [png_lib]
17+
)
18+
+
19+
if png_dep.found()
20+
feature_conf.set('CAIRO_HAS_SVG_SURFACE', 1)
21+
feature_conf.set('CAIRO_HAS_PNG_FUNCTIONS', 1)
22+
@@ -265,7 +267,7 @@
23+
24+
# Disable fontconfig by default on platforms where it is optional
25+
fontconfig_option = get_option('fontconfig')
26+
-fontconfig_required = host_machine.system() not in ['windows', 'darwin']
27+
+fontconfig_required = false
28+
fontconfig_option = fontconfig_option.disable_auto_if(not fontconfig_required)
29+
30+
fontconfig_dep = dependency('fontconfig',
31+
@@ -304,11 +306,14 @@
32+
freetype_required = host_machine.system() not in ['windows', 'darwin']
33+
freetype_option = freetype_option.disable_auto_if(not freetype_required)
34+
35+
-freetype_dep = dependency('freetype2',
36+
- required: freetype_option,
37+
- version: freetype_required_version,
38+
- fallback: ['freetype2', 'freetype_dep'],
39+
+freetype_inc = include_directories(get_option('freetype_include_dir'))
40+
+freetype_lib = cc.find_library('freetype', dirs: [get_option('freetype_lib_dir')], required: true)
41+
+
42+
+freetype_dep = declare_dependency(
43+
+ include_directories: freetype_inc,
44+
+ dependencies: [freetype_lib]
45+
)
46+
+
47+
if freetype_dep.found()
48+
feature_conf.set('CAIRO_HAS_FT_FONT', 1)
49+
built_features += [{
50+
diff '--color=auto' -uNr cairo-1.18.4/meson.options cairo-1.18.4.mod/meson.options
51+
--- cairo-1.18.4/meson.options 2025-03-08 18:53:25.000000000 +0530
52+
+++ cairo-1.18.4.mod/meson.options 2025-07-14 20:43:00.473191452 +0530
53+
@@ -28,3 +28,11 @@
54+
# Documentation
55+
option('gtk_doc', type : 'boolean', value : false,
56+
description: 'Build the Cairo API reference (depends on gtk-doc)')
57+
+
58+
+# Deps
59+
+
60+
+option('png_include_dir', type: 'string', value: '', description: 'Path to PNG headers')
61+
+option('png_lib_dir', type: 'string', value: '', description: 'Path to PNG library')
62+
+option('freetype_include_dir', type: 'string', value: '', description: 'Path to FreeType headers')
63+
+option('freetype_lib_dir', type: 'string', value: '', description: 'Path to FreeType library')
64+
+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from pythonforandroid.recipe import MesonRecipe
2+
from os.path import join
3+
4+
5+
class PyCairoRecipe(MesonRecipe):
6+
version = '1.28.0'
7+
url = 'https://github.com/pygobject/pycairo/releases/download/v{version}/pycairo-{version}.tar.gz'
8+
name = 'pycairo'
9+
site_packages_name = 'cairo'
10+
depends = ['libcairo']
11+
patches = ["meson.patch"]
12+
13+
def build_arch(self, arch):
14+
15+
include_path = join(self.get_recipe('libcairo', self.ctx).get_build_dir(arch), "install", "include", "cairo")
16+
lib_path = self.ctx.get_libs_dir(arch.arch)
17+
18+
self.extra_build_args += [
19+
f'-Csetup-args=-Dcairo_include={include_path}',
20+
f'-Csetup-args=-Dcairo_lib={lib_path}',
21+
]
22+
23+
super().build_arch(arch)
24+
25+
26+
recipe = PyCairoRecipe()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
diff '--color=auto' -uNr pycairo-1.28.0/cairo/meson.build pycairo-1.28.0.mod/cairo/meson.build
2+
--- pycairo-1.28.0/cairo/meson.build 2025-04-15 00:22:30.000000000 +0530
3+
+++ pycairo-1.28.0.mod/cairo/meson.build 2025-07-14 21:56:34.782983845 +0530
4+
@@ -28,7 +28,10 @@
5+
fs.copyfile(python_file, python_file)
6+
endforeach
7+
8+
-cairo_dep = dependency('cairo', version: cair_version_req, required: cc.get_id() != 'msvc')
9+
+cairo_dep = declare_dependency(
10+
+ include_directories: include_directories(get_option('cairo_include')),
11+
+ link_args: ['-L' + get_option('cairo_lib'), '-lcairo']
12+
+)
13+
14+
if cc.get_id() == 'msvc' and not cairo_dep.found()
15+
if cc.has_header('cairo.h')
16+
diff '--color=auto' -uNr pycairo-1.28.0/meson_options.txt pycairo-1.28.0.mod/meson_options.txt
17+
--- pycairo-1.28.0/meson_options.txt 2025-04-15 00:22:30.000000000 +0530
18+
+++ pycairo-1.28.0.mod/meson_options.txt 2025-07-14 21:56:52.824191314 +0530
19+
@@ -1,3 +1,5 @@
20+
option('python', type : 'string', value : 'python3')
21+
option('tests', type : 'boolean', value : true, description : 'build unit tests')
22+
option('wheel', type : 'boolean', value : false, description : 'build for a Python wheel')
23+
+option('cairo_include', type: 'string', value: '', description: 'Path to cairo headers')
24+
+option('cairo_lib', type: 'string', value: '', description: 'Path to cairo libraries')

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# https://github.com/kivy/buildozer/issues/722
2222
install_reqs = [
2323
'appdirs', 'colorama>=0.3.3', 'jinja2',
24-
'sh>=2, <3.0; sys_platform!="win32"',
24+
'sh>=2, <3.0; sys_platform!="win32"', 'meson', 'ninja',
2525
'build', 'toml', 'packaging', 'setuptools', 'wheel~=0.43.0'
2626
]
2727
# (build and toml are used by pythonpackage.py)

0 commit comments

Comments
 (0)