Skip to content

Commit 50df160

Browse files
committed
Update Conan Version Schema
1 parent 584592a commit 50df160

File tree

4 files changed

+514
-35
lines changed

4 files changed

+514
-35
lines changed

cppython/plugins/conan/resolution.py

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99

1010
from cppython.core.exception import ConfigException
1111
from cppython.core.schema import CorePluginData
12-
from cppython.plugins.conan.schema import ConanConfiguration, ConanData, ConanDependency
12+
from cppython.plugins.conan.schema import (
13+
ConanConfiguration,
14+
ConanData,
15+
ConanDependency,
16+
ConanVersion,
17+
ConanVersionRange,
18+
)
1319
from cppython.utility.exception import ProviderConfigurationError
1420

1521

@@ -121,26 +127,90 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile:
121127
return host_profile, build_profile
122128

123129

130+
def _handle_single_specifier(name: str, specifier) -> ConanDependency:
131+
"""Handle a single version specifier."""
132+
MINIMUM_VERSION_PARTS = 2
133+
134+
operator_handlers = {
135+
'==': lambda v: ConanDependency(name=name, version=ConanVersion.from_string(v)),
136+
'>=': lambda v: ConanDependency(name=name, version_range=ConanVersionRange(expression=f'>={v}')),
137+
'>': lambda v: ConanDependency(name=name, version_range=ConanVersionRange(expression=f'>{v}')),
138+
'<': lambda v: ConanDependency(name=name, version_range=ConanVersionRange(expression=f'<{v}')),
139+
'<=': lambda v: ConanDependency(name=name, version_range=ConanVersionRange(expression=f'<={v}')),
140+
'!=': lambda v: ConanDependency(name=name, version_range=ConanVersionRange(expression=f'!={v}')),
141+
}
142+
143+
if specifier.operator in operator_handlers:
144+
return operator_handlers[specifier.operator](specifier.version)
145+
elif specifier.operator == '~=':
146+
# Compatible release - convert to Conan tilde syntax
147+
version_parts = specifier.version.split('.')
148+
if len(version_parts) >= MINIMUM_VERSION_PARTS:
149+
conan_version = '.'.join(version_parts[:MINIMUM_VERSION_PARTS])
150+
return ConanDependency(name=name, version_range=ConanVersionRange(expression=f'~{conan_version}'))
151+
else:
152+
return ConanDependency(name=name, version_range=ConanVersionRange(expression=f'>={specifier.version}'))
153+
else:
154+
raise ConfigException(
155+
f"Unsupported single specifier '{specifier.operator}'. Supported: '==', '>=', '>', '<', '<=', '!=', '~='",
156+
[],
157+
)
158+
159+
124160
def resolve_conan_dependency(requirement: Requirement) -> ConanDependency:
125-
"""Resolves a Conan dependency from a requirement"""
161+
"""Resolves a Conan dependency from a Python requirement string.
162+
163+
Converts Python packaging requirements to Conan version specifications:
164+
- package>=1.0.0 -> package/[>=1.0.0]
165+
- package==1.0.0 -> package/1.0.0
166+
- package~=1.2.0 -> package/[~1.2]
167+
- package>=1.0,<2.0 -> package/[>=1.0 <2.0]
168+
"""
126169
specifiers = requirement.specifier
127170

128-
# If the length of specifiers is greater than one, raise a configuration error
129-
if len(specifiers) > 1:
130-
raise ConfigException('Multiple specifiers are not supported. Please provide a single specifier.', [])
171+
# Handle no version specifiers
172+
if not specifiers:
173+
return ConanDependency(name=requirement.name)
131174

132-
# Extract the version from the single specifier
133-
min_version = None
175+
# Handle single specifier (most common case)
134176
if len(specifiers) == 1:
135-
specifier = next(iter(specifiers))
136-
if specifier.operator != '>=':
137-
raise ConfigException(f"Unsupported specifier '{specifier.operator}'. Only '>=' is supported.", [])
138-
min_version = specifier.version
139-
140-
return ConanDependency(
141-
name=requirement.name,
142-
version_ge=min_version,
143-
)
177+
return _handle_single_specifier(requirement.name, next(iter(specifiers)))
178+
179+
# Handle multiple specifiers - convert to Conan range syntax
180+
range_parts = []
181+
182+
# Define order for operators to ensure consistent output
183+
operator_order = ['>=', '>', '<=', '<', '!=']
184+
185+
# Group specifiers by operator to ensure consistent ordering
186+
specifier_groups = {op: [] for op in operator_order}
187+
188+
for specifier in specifiers:
189+
if specifier.operator in ('>=', '>', '<', '<=', '!='):
190+
specifier_groups[specifier.operator].append(specifier.version)
191+
elif specifier.operator == '==':
192+
# Multiple == operators would be contradictory
193+
raise ConfigException(
194+
"Multiple '==' specifiers are contradictory. Use a single '==' or range operators.", []
195+
)
196+
elif specifier.operator == '~=':
197+
# ~= with other operators is complex, for now treat as >=
198+
specifier_groups['>='].append(specifier.version)
199+
else:
200+
raise ConfigException(
201+
f"Unsupported specifier '{specifier.operator}' in multi-specifier requirement. "
202+
f"Supported: '>=', '>', '<', '<=', '!='",
203+
[],
204+
)
205+
206+
# Build range parts in consistent order
207+
for operator in operator_order:
208+
for version in specifier_groups[operator]:
209+
range_parts.append(f'{operator}{version}')
210+
211+
# Join range parts with spaces (Conan AND syntax)
212+
version_range = ' '.join(range_parts)
213+
return ConanDependency(name=requirement.name, version_range=ConanVersionRange(expression=version_range))
144214

145215

146216
def resolve_conan_data(data: dict[str, Any], core_data: CorePluginData) -> ConanData:

0 commit comments

Comments
 (0)