Skip to content
Merged
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
135 changes: 58 additions & 77 deletions cppython/plugins/conan/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,52 +76,54 @@ def _install_dependencies(self, *, update: bool = False) -> None:
update: If True, check remotes for newer versions/revisions and install those.
If False, use cached versions when available.
"""
operation = 'update' if update else 'install'

try:
# Setup environment and generate conanfile
conan_api, conanfile_path = self._prepare_installation()
except Exception as e:
raise ProviderInstallationError('conan', f'Failed to prepare {operation} environment: {e}', e) from e

try:
# Load dependency graph
deps_graph = self._load_dependency_graph(conan_api, conanfile_path, update)
except Exception as e:
raise ProviderInstallationError('conan', f'Failed to load dependency graph: {e}', e) from e

try:
# Install dependencies
self._install_binaries(conan_api, deps_graph, update)
except Exception as e:
raise ProviderInstallationError('conan', f'Failed to install binary dependencies: {e}', e) from e

try:
# Generate consumer files
self._generate_consumer_files(conan_api, deps_graph)

except Exception as e:
operation = 'update' if update else 'install'
raise ProviderInstallationError('conan', f'Failed to {operation} dependencies: {e}', e) from e
raise ProviderInstallationError('conan', f'Failed to generate consumer files: {e}', e) from e

def _prepare_installation(self) -> tuple[ConanAPI, Path]:
"""Prepare the installation environment and generate conanfile.

Returns:
Tuple of (ConanAPI instance, conanfile path)

Raises:
ProviderInstallationError: If conanfile generation or setup fails
"""
try:
# Resolve dependencies and generate conanfile.py
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
self.builder.generate_conanfile(self.core_data.project_data.project_root, resolved_dependencies)

# Ensure build directory exists
self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True)
# Resolve dependencies and generate conanfile.py
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
self.builder.generate_conanfile(self.core_data.project_data.project_root, resolved_dependencies)

# Setup paths and API
conan_api = ConanAPI()
project_root = self.core_data.project_data.project_root
conanfile_path = project_root / 'conanfile.py'
# Ensure build directory exists
self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True)

if not conanfile_path.exists():
raise ProviderInstallationError('conan', 'Generated conanfile.py not found')
# Setup paths and API
conan_api = ConanAPI()
project_root = self.core_data.project_data.project_root
conanfile_path = project_root / 'conanfile.py'

return conan_api, conanfile_path
if not conanfile_path.exists():
raise FileNotFoundError('Generated conanfile.py not found')

except Exception as e:
raise ProviderInstallationError('conan', f'Failed to prepare installation environment: {e}', e) from e
return conan_api, conanfile_path

def _load_dependency_graph(self, conan_api: ConanAPI, conanfile_path: Path, update: bool):
"""Load and build the dependency graph.
Expand All @@ -133,31 +135,24 @@ def _load_dependency_graph(self, conan_api: ConanAPI, conanfile_path: Path, upda

Returns:
The loaded dependency graph

Raises:
ProviderInstallationError: If dependency graph loading fails
"""
try:
all_remotes = conan_api.remotes.list()
profile_host, profile_build = self.data.host_profile, self.data.build_profile

return conan_api.graph.load_graph_consumer(
path=str(conanfile_path),
name=None,
version=None,
user=None,
channel=None,
lockfile=None,
remotes=all_remotes,
update=update or None,
check_updates=update,
is_build_require=False,
profile_host=profile_host,
profile_build=profile_build,
)
all_remotes = conan_api.remotes.list()
profile_host, profile_build = self.data.host_profile, self.data.build_profile

except Exception as e:
raise ProviderInstallationError('conan', f'Failed to load dependency graph: {e}', e) from e
return conan_api.graph.load_graph_consumer(
path=str(conanfile_path),
name=None,
version=None,
user=None,
channel=None,
lockfile=None,
remotes=all_remotes,
update=update or None,
check_updates=update,
is_build_require=False,
profile_host=profile_host,
profile_build=profile_build,
)

def _install_binaries(self, conan_api: ConanAPI, deps_graph, update: bool) -> None:
"""Analyze and install binary dependencies.
Expand All @@ -166,50 +161,36 @@ def _install_binaries(self, conan_api: ConanAPI, deps_graph, update: bool) -> No
conan_api: The Conan API instance
deps_graph: The dependency graph
update: Whether to check for updates

Raises:
ProviderInstallationError: If binary analysis or installation fails
"""
try:
all_remotes = conan_api.remotes.list()

# Analyze binaries to determine what needs to be built/downloaded
conan_api.graph.analyze_binaries(
graph=deps_graph,
build_mode=['missing'],
remotes=all_remotes,
update=update or None,
lockfile=None,
)
all_remotes = conan_api.remotes.list()

# Install all dependencies
conan_api.install.install_binaries(deps_graph=deps_graph, remotes=all_remotes)
# Analyze binaries to determine what needs to be built/downloaded
conan_api.graph.analyze_binaries(
graph=deps_graph,
build_mode=['missing'],
remotes=all_remotes,
update=update or None,
lockfile=None,
)

except Exception as e:
raise ProviderInstallationError('conan', f'Failed to install binary dependencies: {e}', e) from e
# Install all dependencies
conan_api.install.install_binaries(deps_graph=deps_graph, remotes=all_remotes)

def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None:
"""Generate consumer files (CMake toolchain, deps, etc.).

Args:
conan_api: The Conan API instance
deps_graph: The dependency graph

Raises:
ProviderInstallationError: If consumer file generation fails
"""
try:
project_root = self.core_data.project_data.project_root

conan_api.install.install_consumer(
deps_graph=deps_graph,
generators=['CMakeToolchain', 'CMakeDeps'],
source_folder=str(project_root),
output_folder=str(self.core_data.cppython_data.build_path),
)
project_root = self.core_data.project_data.project_root

except Exception as e:
raise ProviderInstallationError('conan', f'Failed to generate consumer files: {e}', e) from e
conan_api.install.install_consumer(
deps_graph=deps_graph,
generators=['CMakeToolchain', 'CMakeDeps'],
source_folder=str(project_root),
output_folder=str(self.core_data.cppython_data.build_path),
)

def install(self) -> None:
"""Installs the provider"""
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/plugins/conan/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ def mock_resolve(requirement: Requirement) -> ConanDependency:

# Execute and verify exception is raised
with pytest.raises(
ProviderInstallationError, match='Failed to install dependencies: Conan API error: package not found'
ProviderInstallationError,
match='Failed to load dependency graph: Conan API error: package not found',
):
plugin.install()

Expand Down