Skip to content

Commit 178d65f

Browse files
authored
Clean Conan Plugin (#117)
1 parent 2f86929 commit 178d65f

File tree

1 file changed

+138
-93
lines changed

1 file changed

+138
-93
lines changed

cppython/plugins/conan/plugin.py

Lines changed: 138 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
installation, and synchronization with other tools.
66
"""
77

8-
import logging
98
from pathlib import Path
109
from typing import Any
1110

@@ -78,87 +77,139 @@ def _install_dependencies(self, *, update: bool = False) -> None:
7877
If False, use cached versions when available.
7978
"""
8079
try:
81-
logger = logging.getLogger('cppython.conan')
82-
logger.debug('Starting dependency installation/update (update=%s)', update)
80+
# Setup environment and generate conanfile
81+
conan_api, conanfile_path = self._prepare_installation()
8382

84-
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
85-
logger.debug(
86-
'Resolved %d dependencies: %s', len(resolved_dependencies), [str(dep) for dep in resolved_dependencies]
87-
)
83+
# Load dependency graph
84+
deps_graph = self._load_dependency_graph(conan_api, conanfile_path, update)
85+
86+
# Install dependencies
87+
self._install_binaries(conan_api, deps_graph, update)
88+
89+
# Generate consumer files
90+
self._generate_consumer_files(conan_api, deps_graph)
91+
92+
except Exception as e:
93+
operation = 'update' if update else 'install'
94+
raise ProviderInstallationError('conan', f'Failed to {operation} dependencies: {e}', e) from e
95+
96+
def _prepare_installation(self) -> tuple[ConanAPI, Path]:
97+
"""Prepare the installation environment and generate conanfile.
8898
89-
# Generate conanfile.py
99+
Returns:
100+
Tuple of (ConanAPI instance, conanfile path)
101+
102+
Raises:
103+
ProviderInstallationError: If conanfile generation or setup fails
104+
"""
105+
try:
106+
# Resolve dependencies and generate conanfile.py
107+
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
90108
self.builder.generate_conanfile(self.core_data.project_data.project_root, resolved_dependencies)
91-
logger.debug('Generated conanfile.py at %s', self.core_data.project_data.project_root)
92109

93110
# Ensure build directory exists
94111
self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True)
95-
logger.debug('Created build path: %s', self.core_data.cppython_data.build_path)
96112

97-
# Initialize Conan API
113+
# Setup paths and API
98114
conan_api = ConanAPI()
99-
100-
# Get project paths
101115
project_root = self.core_data.project_data.project_root
102116
conanfile_path = project_root / 'conanfile.py'
103117

104118
if not conanfile_path.exists():
105119
raise ProviderInstallationError('conan', 'Generated conanfile.py not found')
106120

107-
# Get all remotes
108-
all_remotes = conan_api.remotes.list()
109-
logger.debug('Available remotes: %s', [remote.name for remote in all_remotes])
121+
return conan_api, conanfile_path
110122

111-
# Get profiles from resolved data
112-
profile_host, profile_build = self.data.host_profile, self.data.build_profile
123+
except Exception as e:
124+
raise ProviderInstallationError('conan', f'Failed to prepare installation environment: {e}', e) from e
125+
126+
def _load_dependency_graph(self, conan_api: ConanAPI, conanfile_path: Path, update: bool):
127+
"""Load and build the dependency graph.
128+
129+
Args:
130+
conan_api: The Conan API instance
131+
conanfile_path: Path to the conanfile.py
132+
update: Whether to check for updates
133+
134+
Returns:
135+
The loaded dependency graph
113136
114-
path = str(conanfile_path)
115-
remotes = all_remotes
116-
update_flag = None if not update else True
117-
check_updates_flag = update
137+
Raises:
138+
ProviderInstallationError: If dependency graph loading fails
139+
"""
140+
try:
141+
all_remotes = conan_api.remotes.list()
142+
profile_host, profile_build = self.data.host_profile, self.data.build_profile
118143

119-
deps_graph = conan_api.graph.load_graph_consumer(
120-
path=path,
144+
return conan_api.graph.load_graph_consumer(
145+
path=str(conanfile_path),
121146
name=None,
122147
version=None,
123148
user=None,
124149
channel=None,
125150
lockfile=None,
126-
remotes=remotes,
127-
update=update_flag,
128-
check_updates=check_updates_flag,
151+
remotes=all_remotes,
152+
update=update or None,
153+
check_updates=update,
129154
is_build_require=False,
130155
profile_host=profile_host,
131156
profile_build=profile_build,
132157
)
133158

134-
logger.debug('Dependency graph loaded with %d nodes', len(deps_graph.nodes))
159+
except Exception as e:
160+
raise ProviderInstallationError('conan', f'Failed to load dependency graph: {e}', e) from e
161+
162+
def _install_binaries(self, conan_api: ConanAPI, deps_graph, update: bool) -> None:
163+
"""Analyze and install binary dependencies.
164+
165+
Args:
166+
conan_api: The Conan API instance
167+
deps_graph: The dependency graph
168+
update: Whether to check for updates
169+
170+
Raises:
171+
ProviderInstallationError: If binary analysis or installation fails
172+
"""
173+
try:
174+
all_remotes = conan_api.remotes.list()
135175

136176
# Analyze binaries to determine what needs to be built/downloaded
137177
conan_api.graph.analyze_binaries(
138178
graph=deps_graph,
139-
build_mode=['missing'], # Only build what's missing
179+
build_mode=['missing'],
140180
remotes=all_remotes,
141-
update=None if not update else True,
181+
update=update or None,
142182
lockfile=None,
143183
)
144184

145185
# Install all dependencies
146186
conan_api.install.install_binaries(deps_graph=deps_graph, remotes=all_remotes)
147187

148-
# Generate files for the consumer (conandata.yml, conan_toolchain.cmake, etc.)
188+
except Exception as e:
189+
raise ProviderInstallationError('conan', f'Failed to install binary dependencies: {e}', e) from e
190+
191+
def _generate_consumer_files(self, conan_api: ConanAPI, deps_graph) -> None:
192+
"""Generate consumer files (CMake toolchain, deps, etc.).
193+
194+
Args:
195+
conan_api: The Conan API instance
196+
deps_graph: The dependency graph
197+
198+
Raises:
199+
ProviderInstallationError: If consumer file generation fails
200+
"""
201+
try:
202+
project_root = self.core_data.project_data.project_root
203+
149204
conan_api.install.install_consumer(
150205
deps_graph=deps_graph,
151206
generators=['CMakeToolchain', 'CMakeDeps'],
152207
source_folder=str(project_root),
153208
output_folder=str(self.core_data.cppython_data.build_path),
154209
)
155210

156-
logger.debug('Successfully installed dependencies using Conan API')
157-
158211
except Exception as e:
159-
operation = 'update' if update else 'install'
160-
error_msg = str(e)
161-
raise ProviderInstallationError('conan', f'Failed to {operation} dependencies: {error_msg}', e) from e
212+
raise ProviderInstallationError('conan', f'Failed to generate consumer files: {e}', e) from e
162213

163214
def install(self) -> None:
164215
"""Installs the provider"""
@@ -199,7 +250,7 @@ def sync_data(self, consumer: SyncConsumer) -> SyncData:
199250
top_level_includes=self.core_data.cppython_data.install_path / 'conan_provider.cmake',
200251
)
201252

202-
raise NotSupportedError('OOF')
253+
raise NotSupportedError(f'Unsupported sync types: {consumer.sync_types()}')
203254

204255
@classmethod
205256
async def download_tooling(cls, directory: Path) -> None:
@@ -208,99 +259,93 @@ async def download_tooling(cls, directory: Path) -> None:
208259

209260
def publish(self) -> None:
210261
"""Publishes the package using conan create workflow."""
211-
# Get the project root directory where conanfile.py should be located
212262
project_root = self.core_data.project_data.project_root
213263
conanfile_path = project_root / 'conanfile.py'
214264

215265
if not conanfile_path.exists():
216266
raise FileNotFoundError(f'conanfile.py not found at {conanfile_path}')
217267

218-
# Initialize Conan API
219268
conan_api = ConanAPI()
220-
221-
# Get configured remotes from Conan API and filter by our configuration
222-
# TODO: We want to replace the global conan remotes with the ones configured in CPPython.
223269
all_remotes = conan_api.remotes.list()
224-
if not self.data.local_only:
225-
# Filter remotes to only include those specified in configuration
226-
configured_remotes = [remote for remote in all_remotes if remote.name in self.data.remotes]
227-
228-
if not configured_remotes:
229-
available_remotes = [remote.name for remote in all_remotes]
230-
raise ProviderConfigurationError(
231-
'conan',
232-
f'No configured remotes found. Available remotes: {available_remotes}, '
233-
f'Configured remotes: {self.data.remotes}',
234-
'remotes',
235-
)
236-
else:
237-
configured_remotes = []
238270

239-
# Step 1: Export the recipe to the cache
240-
# This is equivalent to the export part of `conan create`
241-
ref, conanfile = conan_api.export.export(
271+
# Configure remotes for upload
272+
configured_remotes = self._get_configured_remotes(all_remotes)
273+
274+
# Export the recipe to cache
275+
ref, _ = conan_api.export.export(
242276
path=str(conanfile_path),
243277
name=None,
244278
version=None,
245279
user=None,
246280
channel=None,
247281
lockfile=None,
248-
remotes=all_remotes, # Use all remotes for dependency resolution during export
282+
remotes=all_remotes,
249283
)
250284

251-
# Step 2: Get profiles from resolved data
285+
# Build dependency graph and install
252286
profile_host, profile_build = self.data.host_profile, self.data.build_profile
253-
254-
# Step 3: Build dependency graph for the package - prepare parameters
255-
path = str(conanfile_path)
256-
remotes = all_remotes # Use all remotes for dependency resolution
257-
258287
deps_graph = conan_api.graph.load_graph_consumer(
259-
path=path,
288+
path=str(conanfile_path),
260289
name=None,
261290
version=None,
262291
user=None,
263292
channel=None,
264293
lockfile=None,
265-
remotes=remotes,
294+
remotes=all_remotes,
266295
update=None,
267296
check_updates=False,
268297
is_build_require=False,
269298
profile_host=profile_host,
270299
profile_build=profile_build,
271300
)
272301

273-
# Step 4: Analyze binaries and install/build them if needed
302+
# Analyze and build binaries
274303
conan_api.graph.analyze_binaries(
275304
graph=deps_graph,
276-
build_mode=['*'], # Build from source (equivalent to the create behavior)
277-
remotes=all_remotes, # Use all remotes for dependency resolution
305+
build_mode=['*'],
306+
remotes=all_remotes,
278307
update=None,
279308
lockfile=None,
280309
)
281310

282-
# Step 5: Install all dependencies and build the package
283311
conan_api.install.install_binaries(deps_graph=deps_graph, remotes=all_remotes)
284312

285-
# If not local only, upload the package
313+
# Upload if not local only
286314
if not self.data.local_only:
287-
# Get all packages matching the created reference
288-
ref_pattern = ListPattern(f'{ref.name}/*', package_id='*', only_recipe=False)
289-
package_list = conan_api.list.select(ref_pattern)
290-
291-
if package_list.recipes:
292-
# Use the first configured remote for upload
293-
remote = configured_remotes[0]
294-
295-
# Upload the package to configured remotes
296-
conan_api.upload.upload_full(
297-
package_list=package_list,
298-
remote=remote,
299-
enabled_remotes=configured_remotes, # Only upload to configured remotes
300-
check_integrity=False,
301-
force=False,
302-
metadata=None,
303-
dry_run=False,
304-
)
305-
else:
306-
raise ProviderInstallationError('conan', 'No packages found to upload')
315+
self._upload_package(conan_api, ref, configured_remotes)
316+
317+
def _get_configured_remotes(self, all_remotes):
318+
"""Get and validate configured remotes for upload."""
319+
if self.data.local_only:
320+
return []
321+
322+
configured_remotes = [remote for remote in all_remotes if remote.name in self.data.remotes]
323+
324+
if not configured_remotes:
325+
available_remotes = [remote.name for remote in all_remotes]
326+
raise ProviderConfigurationError(
327+
'conan',
328+
f'No configured remotes found. Available: {available_remotes}, Configured: {self.data.remotes}',
329+
'remotes',
330+
)
331+
332+
return configured_remotes
333+
334+
def _upload_package(self, conan_api, ref, configured_remotes):
335+
"""Upload the package to configured remotes."""
336+
ref_pattern = ListPattern(f'{ref.name}/*', package_id='*', only_recipe=False)
337+
package_list = conan_api.list.select(ref_pattern)
338+
339+
if not package_list.recipes:
340+
raise ProviderInstallationError('conan', 'No packages found to upload')
341+
342+
remote = configured_remotes[0]
343+
conan_api.upload.upload_full(
344+
package_list=package_list,
345+
remote=remote,
346+
enabled_remotes=configured_remotes,
347+
check_integrity=False,
348+
force=False,
349+
metadata=None,
350+
dry_run=False,
351+
)

0 commit comments

Comments
 (0)