diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h index 5bb065847..22a025afb 100644 --- a/include/libcamera/camera.h +++ b/include/libcamera/camera.h @@ -82,8 +82,7 @@ class CameraConfiguration std::vector config_; }; -class Camera final : public Object, public std::enable_shared_from_this, - public Extensible +class Camera final : public Object, public std::enable_shared_from_this, public Extensible { LIBCAMERA_DECLARE_PRIVATE() @@ -114,12 +113,13 @@ class Camera final : public Object, public std::enable_shared_from_this, int start(const ControlList *controls = nullptr); int stop(); + ~Camera(); + private: LIBCAMERA_DISABLE_COPY(Camera) Camera(std::unique_ptr d, const std::string &id, const std::set &streams); - ~Camera(); friend class PipelineHandler; void disconnect(); diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/codegen.py similarity index 75% rename from src/py/libcamera/gen-py-controls.py rename to src/py/codegen.py index 99f3bbcf5..1a976ab72 100755 --- a/src/py/libcamera/gen-py-controls.py +++ b/src/py/codegen.py @@ -9,6 +9,25 @@ import yaml +def generate(formats): + fmts = [] + + for format in formats: + name, format = format.popitem() + fmts.append(f'\t\t.def_readonly_static("{name}", &libcamera::formats::{name})') + + return {'formats': '\n'.join(fmts)} + + +def fill_template(template, data): + with open(template, encoding='utf-8') as f: + template = f.read() + + template = string.Template(template) + return template.substitute(data) + + + def find_common_prefix(strings): prefix = strings[0] @@ -68,16 +87,9 @@ def generate_py(controls, mode): return {'controls': out} -def fill_template(template, data): - template = open(template, 'rb').read() - template = template.decode('utf-8') - template = string.Template(template) - return template.substitute(data) - - -def main(argv): +def main(argv: list[str] = sys.argv[1:]): # Parse command line arguments - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(prog="codegen_controls.py") parser.add_argument('-o', dest='output', metavar='file', type=str, help='Output file name. Defaults to standard output if not specified.') parser.add_argument('input', type=str, @@ -85,29 +97,29 @@ def main(argv): parser.add_argument('template', type=str, help='Template file name.') parser.add_argument('--mode', type=str, required=True, + choices=['controls', 'properties', 'formats'], help='Mode is either "controls" or "properties"') - args = parser.parse_args(argv[1:]) + args = parser.parse_args(argv) - if args.mode not in ['controls', 'properties']: - print(f'Invalid mode option "{args.mode}"', file=sys.stderr) - return -1 + with open(args.input, 'rb') as f: + input_yml = yaml.safe_load(f) - data = open(args.input, 'rb').read() - controls = yaml.safe_load(data)['controls'] - - data = generate_py(controls, args.mode) + if args.mode == 'formats': + data = generate(input_yml['formats']) + else: + data = generate_py(input_yml['controls'], args.mode) - data = fill_template(args.template, data) + formatted = fill_template(args.template, data) if args.output: output = open(args.output, 'wb') - output.write(data.encode('utf-8')) + output.write(formatted.encode('utf-8')) output.close() else: - sys.stdout.write(data) + sys.stdout.write(formatted) return 0 if __name__ == '__main__': - sys.exit(main(sys.argv)) + main() diff --git a/src/py/libcamera/gen-py-formats.py b/src/py/libcamera/gen-py-formats.py deleted file mode 100755 index 0ff1d12ac..000000000 --- a/src/py/libcamera/gen-py-formats.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Generate Python format definitions from YAML - -import argparse -import string -import sys -import yaml - - -def generate(formats): - fmts = [] - - for format in formats: - name, format = format.popitem() - fmts.append(f'\t\t.def_readonly_static("{name}", &libcamera::formats::{name})') - - return {'formats': '\n'.join(fmts)} - - -def fill_template(template, data): - with open(template, encoding='utf-8') as f: - template = f.read() - - template = string.Template(template) - return template.substitute(data) - - -def main(argv): - parser = argparse.ArgumentParser() - parser.add_argument('-o', dest='output', metavar='file', type=str, - help='Output file name. Defaults to standard output if not specified.') - parser.add_argument('input', type=str, - help='Input file name.') - parser.add_argument('template', type=str, - help='Template file name.') - args = parser.parse_args(argv[1:]) - - with open(args.input, encoding='utf-8') as f: - formats = yaml.safe_load(f)['formats'] - - data = generate(formats) - data = fill_template(args.template, data) - - if args.output: - with open(args.output, 'w', encoding='utf-8') as f: - f.write(data) - else: - sys.stdout.write(data) - - return 0 - - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build index af19ffdd2..0fb1c6014 100644 --- a/src/py/libcamera/meson.build +++ b/src/py/libcamera/meson.build @@ -1,99 +1,74 @@ # SPDX-License-Identifier: CC0-1.0 -py3_dep = dependency('python3', required : get_option('pycamera')) - -if not py3_dep.found() - pycamera_enabled = false - subdir_done() -endif - -pycamera_enabled = true - -pybind11_proj = subproject('pybind11') -pybind11_dep = pybind11_proj.get_variable('pybind11_dep') - pycamera_sources = files([ + 'py_camera_manager.h', 'py_camera_manager.cpp', 'py_enums.cpp', 'py_geometry.cpp', + 'py_helpers.h', 'py_helpers.cpp', + 'py_main.h', 'py_main.cpp', ]) -# Generate controls +foreach f : pycamera_sources + configure_file( + input: f, + output: '@PLAINNAME@', + copy: true, + ) +endforeach +# Generate controls gen_py_controls_input_files = files([ '../../libcamera/control_ids.yaml', - 'py_controls_generated.cpp.in', + '../templates/py_controls_generated.cpp.in', ]) -gen_py_controls = files('gen-py-controls.py') +gen_py_controls = files('../codegen.py') -pycamera_sources += custom_target('py_gen_controls', - input : gen_py_controls_input_files, - output : ['py_controls_generated.cpp'], - command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', '@INPUT@']) +custom_target('py_gen_controls', + input : gen_py_controls_input_files, + output : ['py_controls_generated.cpp'], + command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@', '@INPUT@'], + build_always: true +) # Generate properties - gen_py_property_enums_input_files = files([ '../../libcamera/property_ids.yaml', - 'py_properties_generated.cpp.in', + '../templates/py_properties_generated.cpp.in', ]) -pycamera_sources += custom_target('py_gen_properties', - input : gen_py_property_enums_input_files, - output : ['py_properties_generated.cpp'], - command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', '@INPUT@']) +custom_target('py_gen_properties', + input : gen_py_property_enums_input_files, + output : ['py_properties_generated.cpp'], + command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@', '@INPUT@'], + build_always: true +) # Generate formats - gen_py_formats_input_files = files([ '../../libcamera/formats.yaml', - 'py_formats_generated.cpp.in', + '../templates/py_formats_generated.cpp.in', ]) -gen_py_formats = files('gen-py-formats.py') - -pycamera_sources += custom_target('py_gen_formats', - input : gen_py_formats_input_files, - output : ['py_formats_generated.cpp'], - command : [gen_py_formats, '-o', '@OUTPUT@', '@INPUT@']) - -pycamera_deps = [ - libcamera_private, - py3_dep, - pybind11_dep, -] - -pycamera_args = [ - '-fvisibility=hidden', - '-Wno-shadow', - '-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT', -] - -destdir = get_option('libdir') / ('python' + py3_dep.version()) / 'site-packages' / 'libcamera' - -pycamera = shared_module('_libcamera', - pycamera_sources, - install : true, - install_dir : destdir, - name_prefix : '', - dependencies : pycamera_deps, - cpp_args : pycamera_args) - -# Create symlinks from the build dir to the source dir so that we can use the -# Python module directly from the build dir. +custom_target('py_gen_formats', + input : gen_py_formats_input_files, + output : ['py_formats_generated.cpp'], + command : [gen_py_controls, '--mode', 'formats', '-o', '@OUTPUT@', '@INPUT@'], + build_always: true +) -run_command('ln', '-fsrT', files('__init__.py'), +run_command('cp', files('__init__.py'), meson.current_build_dir() / '__init__.py', check: true) -run_command('ln', '-fsrT', meson.current_source_dir() / 'utils', +run_command('cp', '-r', meson.current_source_dir() / 'utils', meson.current_build_dir() / 'utils', check: true) -install_data(['__init__.py'], install_dir : destdir) +subdir_done() # \todo Generate stubs when building. See https://peps.python.org/pep-0484/#stub-files # Note: Depends on pybind11-stubgen. To generate pylibcamera stubs: diff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h index 3525057d9..3574db236 100644 --- a/src/py/libcamera/py_camera_manager.h +++ b/src/py/libcamera/py_camera_manager.h @@ -9,7 +9,7 @@ #include -#include +#include using namespace libcamera; diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp index 96d4beef4..803c4e7ee 100644 --- a/src/py/libcamera/py_enums.cpp +++ b/src/py/libcamera/py_enums.cpp @@ -7,7 +7,7 @@ #include -#include +#include namespace py = pybind11; diff --git a/src/py/libcamera/py_geometry.cpp b/src/py/libcamera/py_geometry.cpp index 84b0cb087..5c2aeac48 100644 --- a/src/py/libcamera/py_geometry.cpp +++ b/src/py/libcamera/py_geometry.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include namespace py = pybind11; diff --git a/src/py/libcamera/py_helpers.h b/src/py/libcamera/py_helpers.h index cd31e2cc5..983969dff 100644 --- a/src/py/libcamera/py_helpers.h +++ b/src/py/libcamera/py_helpers.h @@ -7,7 +7,7 @@ #include -#include +#include pybind11::object controlValueToPy(const libcamera::ControlValue &cv); libcamera::ControlValue pyToControlValue(const pybind11::object &ob, libcamera::ControlType type); diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index d14e18e25..5092b6a5e 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include @@ -61,8 +61,8 @@ PYBIND11_MODULE(_libcamera, m) * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings */ - auto pyCameraManager = py::class_(m, "CameraManager"); - auto pyCamera = py::class_(m, "Camera"); + auto pyCameraManager = py::class_>(m, "CameraManager"); + auto pyCamera = py::class_>(m, "Camera"); auto pyCameraConfiguration = py::class_(m, "CameraConfiguration"); auto pyCameraConfigurationStatus = py::enum_(pyCameraConfiguration, "Status"); auto pyStreamConfiguration = py::class_(m, "StreamConfiguration"); diff --git a/src/py/meson.build b/src/py/meson.build index a4586b4ae..3c6a85aef 100644 --- a/src/py/meson.build +++ b/src/py/meson.build @@ -1,3 +1,37 @@ # SPDX-License-Identifier: CC0-1.0 +py3 = find_program('python', required : get_option('pycamera')) + +if not py3.found() + pycamera_enabled = false + warning('Python 3 not found, disabling pycamera') + subdir_done() +endif + +# Check to make sure version is sufficient +version_string = run_command(py3, '--version', check: true).stdout().strip() +message('Building against Python version: ' + version_string) +is_py3 = version_string.split()[1].split('.')[0].to_int() >= 3 + +if not is_py3 + pycamera_enabled = false + warning('Python 3 not found, disabling pycamera found: ' + version_string) + subdir_done() +endif + +pycamera_enabled = true + subdir('libcamera') + +setup_py = configure_file(input: 'setup.py', output: 'setup.py', copy: true) + +pycamera = custom_target('python_setup_build', + input: pycamera_sources, + command: [py3, setup_py, 'bdist_wheel'], + output: ['libcamera.cpython-39-x86_64-linux-gnu.so'], + depends: libcamera, + build_by_default: true, +) + +meson.add_install_script(py3, setup_py, 'install') + diff --git a/src/py/setup.py b/src/py/setup.py new file mode 100644 index 000000000..ba13228d4 --- /dev/null +++ b/src/py/setup.py @@ -0,0 +1,83 @@ +from glob import glob +from setuptools import setup +from pybind11.setup_helpers import Pybind11Extension + +# from codegen import main as codegen +from pybind11_stubgen import main as gen_stub + + +# NOTE(meawoppl) These can be vastly simplified to not go through +# the argparse and assorted tomfoolery that remains +# codegen([ +# '--mode', 'controls', +# '-o', "libcamera/py_controls_generated.cpp", +# '../libcamera/control_ids.yaml', +# 'libcamera/py_controls_generated.cpp.in', +# ]) + +# codegen([ +# '--mode', 'properties', +# '-o', 'libcamera/py_properties_generated.cpp', +# '../libcamera/property_ids.yaml', +# 'libcamera/py_properties_generated.cpp.in' +# ]) + +# codegen(["placeholder", +# '--mode', 'formats', +# '-o', 'libcamera/py_formats_generated.cpp', +# '../libcamera/formats.yaml', +# 'libcamera/py_formats_generated.cpp.in', +# ]) + + + +import os +this_dir = os.path.dirname(os.path.abspath(__file__)) +print("Running from ", this_dir) +os.chdir(this_dir) + +cpp_sources = list(sorted(glob("libcamera/*.cpp"))) + +ext_modules = [ + Pybind11Extension( + "libcamera._libcamera", + cpp_sources, + libraries=['camera'], + include_dirs=['../../include', "../../../include"], + library_dirs=['../libcamera'], + extra_compile_args=[ + '-fvisibility=hidden', + '-Wno-shadow', + '-DLIBCAMERA_BASE_PRIVATE', + '-std=c++17', + ], + ), +] + +from setuptools import setup, Extension, find_packages, Command + +# Custom command to generate stubs using pybind11-stubgen +class GenerateStubs(Command): + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + gen_stub(["libcamera", "-o", "libcamera"]) + + +setup( + name='libcamera', + version='0.0.5', + description='Python wrapper to `libcamera`', + author='Libcamera Developers', + author_email=' libcamera-devel@lists.libcamera.org', + url='https://libcamera.org/', + packages=['libcamera', 'libcamera.utils'], + ext_modules=ext_modules, + cmdclass={"generate_stubs": GenerateStubs}, +) diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/templates/py_controls_generated.cpp.in similarity index 93% rename from src/py/libcamera/py_controls_generated.cpp.in rename to src/py/templates/py_controls_generated.cpp.in index cb8442bae..18fa57d94 100644 --- a/src/py/libcamera/py_controls_generated.cpp.in +++ b/src/py/templates/py_controls_generated.cpp.in @@ -9,7 +9,7 @@ #include -#include +#include namespace py = pybind11; diff --git a/src/py/libcamera/py_formats_generated.cpp.in b/src/py/templates/py_formats_generated.cpp.in similarity index 92% rename from src/py/libcamera/py_formats_generated.cpp.in rename to src/py/templates/py_formats_generated.cpp.in index b88807f3a..a3f7f94d5 100644 --- a/src/py/libcamera/py_formats_generated.cpp.in +++ b/src/py/templates/py_formats_generated.cpp.in @@ -9,7 +9,7 @@ #include -#include +#include namespace py = pybind11; diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/templates/py_properties_generated.cpp.in similarity index 93% rename from src/py/libcamera/py_properties_generated.cpp.in rename to src/py/templates/py_properties_generated.cpp.in index 044b2b2a6..e49b6e91b 100644 --- a/src/py/libcamera/py_properties_generated.cpp.in +++ b/src/py/templates/py_properties_generated.cpp.in @@ -9,7 +9,7 @@ #include -#include +#include namespace py = pybind11; diff --git a/subprojects/packagefiles/pybind11/meson.build b/subprojects/packagefiles/pybind11/meson.build deleted file mode 100644 index 1be47ca45..000000000 --- a/subprojects/packagefiles/pybind11/meson.build +++ /dev/null @@ -1,7 +0,0 @@ -project('pybind11', 'cpp', - version : '2.9.1', - license : 'BSD-3-Clause') - -pybind11_incdir = include_directories('include') - -pybind11_dep = declare_dependency(include_directories : pybind11_incdir) diff --git a/subprojects/pybind11.wrap b/subprojects/pybind11.wrap deleted file mode 100644 index dd02687b5..000000000 --- a/subprojects/pybind11.wrap +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 - -[wrap-git] -url = https://github.com/pybind/pybind11.git -# This is the head of 'smart_holder' branch -revision = aebdf00cd060b871c5a1e0c2cf4a333503dd0431 -depth = 1 -patch_directory = pybind11 - -[provide] -pybind11 = pybind11_dep