Skip to content

Commit

Permalink
Add support for Conan v2
Browse files Browse the repository at this point in the history
  • Loading branch information
dean0x7d committed Apr 27, 2024
1 parent 61a3acc commit 9808e55
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 44 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.pyc
__pycache__/
*/test_package/build
CMakeUserPresets.json
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## v1.9.0 | In development

- Added support for Conan v2.
- Removed the obsolete `openssl_variant` option.

## v1.8.2 | 2023-11-17
Expand Down
8 changes: 6 additions & 2 deletions conanfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import re
import pathlib
import conan
from conan import ConanFile
from conan.tools import files, scm

Expand Down Expand Up @@ -36,10 +37,13 @@ class EmbeddedPython(ConanFile):
exports_sources = "embedded_python.cmake"

def requirements(self):
self.requires(f"embedded_python-core/1.2.2@{self.user}/{self.channel}")
self.requires(f"embedded_python-core/1.3.0@{self.user}/{self.channel}")

def configure(self):
self.options["embedded_python-core"].version = self.options.version
if conan.__version__.startswith("2"):
self.options["embedded_python-core/*"].version = self.options.version
else:
self.options["embedded_python-core"].version = self.options.version

@property
def pyversion(self):
Expand Down
10 changes: 5 additions & 5 deletions core/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import io
import os
import subprocess
import sys
import shutil
import pathlib
from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools import files, scm

required_conan_version = ">=1.59.0"
Expand All @@ -12,7 +13,7 @@
# noinspection PyUnresolvedReferences
class EmbeddedPythonCore(ConanFile):
name = "embedded_python-core"
version = "1.2.2" # of the Conan package, `options.version` is the Python version
version = "1.3.0" # of the Conan package, `options.version` is the Python version
license = "PSFL"
description = "The core embedded Python (no extra pip packages)"
topics = "embedded", "python"
Expand Down Expand Up @@ -154,9 +155,8 @@ def _patch_libpython_path(self, dst):
return

exe = dst / f"python{self.short_pyversion}"
buffer = io.StringIO()
self.run(f"otool -L {exe}", output=buffer)
lines = buffer.getvalue().strip().split("\n")[1:]
p = subprocess.run(["otool", "-L", str(exe)], check=True, text=True, capture_output=True)
lines = str(p.stdout).strip().split("\n")[1:]
libraries = [line.split()[0] for line in lines]
hardcoded_libraries = [lib for lib in libraries if lib.startswith(str(dst))]
for lib in hardcoded_libraries:
Expand Down
12 changes: 6 additions & 6 deletions core/embedded_python_tools.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import shutil
import pathlib
from conan.tools.files import copy
from conan.tools import files


def _symlink_compat(conanfile, src, dst):
Expand All @@ -12,7 +12,7 @@ def _symlink_compat(conanfile, src, dst):
try:
_winapi.CreateJunction(str(src), str(dst))
except OSError:
copy(conanfile, "*", src, dst)
files.copy(conanfile, "*", src, dst)
else:
os.symlink(src, dst)

Expand Down Expand Up @@ -56,7 +56,7 @@ def imports(self):
_symlink_compat(conanfile, src, dst)

bin = pathlib.Path(bin).absolute()
copy(conanfile, "python*.dll", src, bin, keep_path=False)
copy(conanfile, "libpython*.so*", src / "lib", bin, keep_path=False)
copy(conanfile, "libpython*.dylib", src / "lib", bin, keep_path=False)
copy(conanfile, "python*.zip", src, bin, keep_path=False)
files.copy(conanfile, "python*.dll", src, bin, keep_path=False)
files.copy(conanfile, "libpython*.so*", src / "lib", bin, keep_path=False)
files.copy(conanfile, "libpython*.dylib", src / "lib", bin, keep_path=False)
files.copy(conanfile, "python*.zip", src, bin, keep_path=False)
4 changes: 2 additions & 2 deletions core/test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ project(test_package)
find_package(embedded_python-core)

message(STATUS "Python_EXECUTABLE: ${Python_EXECUTABLE}")
if(NOT Python_EXECUTABLE MATCHES "embedded_python-core")
message(FATAL_ERROR "CMake failed to find the correct Python")
if(NOT Python_EXECUTABLE MATCHES "${EXPECTED_PYTHON_CORE_PATH}")
message(FATAL_ERROR "CMake failed to find the correct Python: ${EXPECTED_PYTHON_CORE_PATH}")
endif()

add_executable(test_package src/main.cpp)
Expand Down
36 changes: 25 additions & 11 deletions core/test_package/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import sys
import pathlib
from io import StringIO
import subprocess
import conan
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout


# noinspection PyUnresolvedReferences
class TestEmbeddedPythonCore(ConanFile):
name = "test_embedded_python"
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeDeps"
default_options = {"embedded_python-core:version": "3.11.3"}
generators = "CMakeDeps", "VirtualRunEnv"
default_options = {"embedded_python-core/*:version": "3.11.3"}
test_type = "explicit"

def layout(self):
cmake_layout(self)

def requirements(self):
self.requires(self.tested_reference_str)

Expand All @@ -22,11 +27,13 @@ def generate(self):
tc.generate()

def build(self):
sys.path.append(str(self._core_package_path))

import embedded_python_tools

embedded_python_tools.symlink_import(self, dst="bin/python")
cmake = CMake(self)
cmake.configure()
cmake.configure(variables={"EXPECTED_PYTHON_CORE_PATH": self._core_package_path.as_posix()})
cmake.build()

@property
Expand All @@ -36,6 +43,13 @@ def _py_exe(self):
else:
return pathlib.Path(self.build_folder, "bin/python/bin/python3")

@property
def _core_package_path(self):
if conan.__version__.startswith("2"):
return pathlib.Path(self.dependencies["embedded_python-core"].package_folder)
else:
return pathlib.Path(self.deps_cpp_info["embedded_python-core"].rootpath)

def _test_stdlib(self):
"""Ensure that Python runs and built the optional stdlib modules"""
self.run(f'{self._py_exe} -c "import sys; print(sys.version);"')
Expand All @@ -46,9 +60,10 @@ def _test_libpython_path(self):
if self.settings.os != "Macos":
return

buffer = StringIO()
self.run(f"otool -L {self._py_exe}", run_environment=True, output=buffer)
lines = buffer.getvalue().strip().split("\n")[1:]
p = subprocess.run(
["otool", "-L", str(self._py_exe)], check=True, text=True, capture_output=True
)
lines = str(p.stdout).strip().split("\n")[1:]
libraries = [line.split()[0] for line in lines]
candidates = [lib for lib in libraries if "libpython" in lib]
assert candidates, f"libpython dependency not found in 'otool' output: {libraries}"
Expand All @@ -57,12 +72,11 @@ def _test_libpython_path(self):

def _test_embed(self):
"""Ensure that everything is available to compile and link to the embedded Python"""
self.run(pathlib.Path("bin", "test_package"), run_environment=True)
self.run(pathlib.Path("bin", "test_package"), env="conanrun")

def _test_licenses(self):
"""Ensure that the license file is included"""
license_dir = pathlib.Path(self.deps_cpp_info["embedded_python-core"].rootpath, "licenses")
file = license_dir / "LICENSE.txt"
file = self._core_package_path / "licenses/LICENSE.txt"
print(f"{file}: {file.stat().st_size}")

def test(self):
Expand Down
12 changes: 6 additions & 6 deletions test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ project(test_package)
find_package(embedded_python)

message(STATUS "Python_EXECUTABLE: ${Python_EXECUTABLE}")
if(NOT Python_EXECUTABLE MATCHES "embedded_python-core")
message(FATAL_ERROR "CMake failed to find the correct Python")
if(NOT Python_EXECUTABLE MATCHES "${EXPECTED_PYTHON_CORE_PATH}")
message(FATAL_ERROR "CMake failed to find the correct Python: ${EXPECTED_PYTHON_CORE_PATH}")
endif()
message(STATUS "EmbeddedPython_EXECUTABLE: ${EmbeddedPython_EXECUTABLE}")
if(EmbeddedPython_EXECUTABLE MATCHES "embedded_python-core")
message(FATAL_ERROR "CMake found the `-core` package instead of the full environment")
if(EmbeddedPython_EXECUTABLE MATCHES "${EXPECTED_PYTHON_CORE_PATH}")
message(FATAL_ERROR "CMake found the `-core` package instead of the full environment: ${EXPECTED_PYTHON_PATH}")
endif()
if(NOT EmbeddedPython_EXECUTABLE MATCHES "embedded_python")
message(FATAL_ERROR "CMake failed to find the correct Python")
if(NOT EmbeddedPython_EXECUTABLE MATCHES "${EXPECTED_PYTHON_PATH}")
message(FATAL_ERROR "CMake failed to find the correct Python: ${EXPECTED_PYTHON_PATH}")
endif()

add_executable(test_package ../core/test_package/src/main.cpp)
Expand Down
51 changes: 39 additions & 12 deletions test_package/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import pathlib
import sys
from io import StringIO
import pathlib
import subprocess
import conan
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout

project_root = pathlib.Path(__file__).parent

Expand All @@ -16,13 +17,33 @@ def _read_env(name):
class TestEmbeddedPython(ConanFile):
name = "test_embedded_python"
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeDeps"
generators = "CMakeDeps", "VirtualRunEnv"
options = {"env": [None, "ANY"]}
default_options = {
"env": None,
"embedded_python:version": "3.11.5",
"embedded_python/*:version": "3.11.5",
}

@property
def _core_package_path(self):
if conan.__version__.startswith("2"):
return pathlib.Path(self.dependencies["embedded_python-core"].package_folder)
else:
return pathlib.Path(self.deps_cpp_info["embedded_python-core"].rootpath)

@property
def _package_path(self):
if conan.__version__.startswith("2"):
return pathlib.Path(self.dependencies["embedded_python"].package_folder)
else:
return pathlib.Path(self.deps_cpp_info["embedded_python"].rootpath)

def layout(self):
cmake_layout(self)

def requirements(self):
self.requires(self.tested_reference_str)

def configure(self):
if self.options.env:
self.options["embedded_python"].packages = _read_env(self.options.env)
Expand All @@ -34,12 +55,19 @@ def generate(self):
tc.generate()

def build(self):
sys.path.append(str(self._package_path))

import embedded_python_tools

embedded_python_tools.symlink_import(self, dst="bin/python")

cmake = CMake(self)
cmake.configure()
cmake.configure(
variables={
"EXPECTED_PYTHON_CORE_PATH": self._core_package_path.as_posix(),
"EXPECTED_PYTHON_PATH": self._package_path.as_posix(),
}
)
cmake.build()

def _test_env(self):
Expand All @@ -52,16 +80,15 @@ def _test_env(self):
self.run(f'{python_exe} -c "import sys; print(sys.version);"')

name = str(self.options.env) if self.options.env else "baseline"
self.run(f"{python_exe} {project_root / name / 'test.py'}", run_environment=True)
self.run(f"{python_exe} {project_root / name / 'test.py'}", env="conanrun")

def _test_libpython_path(self):
if self.settings.os != "Macos":
return

python_exe = str(pathlib.Path("./bin/python/bin/python3").resolve())
buffer = StringIO()
self.run(f"otool -L {python_exe}", run_environment=True, output=buffer)
lines = buffer.getvalue().strip().split("\n")[1:]
p = subprocess.run(["otool", "-L", python_exe], check=True, text=True, capture_output=True)
lines = str(p.stdout).strip().split("\n")[1:]
libraries = [line.split()[0] for line in lines]
candidates = [lib for lib in libraries if "libpython" in lib]
assert candidates, f"libpython dependency not found in 'otool' output: {libraries}"
Expand All @@ -71,11 +98,11 @@ def _test_libpython_path(self):

def _test_embed(self):
"""Ensure that everything is available to compile and link to the embedded Python"""
self.run(pathlib.Path("bin", "test_package"), run_environment=True)
self.run(pathlib.Path("bin", "test_package"), env="conanrun")

def _test_licenses(self):
"""Ensure that the licenses have been gathered"""
license_dir = pathlib.Path(self.deps_cpp_info["embedded_python"].rootpath, "licenses")
license_dir = self._package_path / "licenses"
license_files = [license_dir / "LICENSE.txt"]
if self.options.env:
license_files += [license_dir / "package_licenses.txt"]
Expand Down

0 comments on commit 9808e55

Please sign in to comment.