|
9 | 9 |
|
10 | 10 | from cppython.core.exception import ConfigException |
11 | 11 | 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 | +) |
13 | 19 | from cppython.utility.exception import ProviderConfigurationError |
14 | 20 |
|
15 | 21 |
|
@@ -121,26 +127,90 @@ def _resolve_profile(profile_name: str | None, is_host: bool) -> Profile: |
121 | 127 | return host_profile, build_profile |
122 | 128 |
|
123 | 129 |
|
| 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 | + |
124 | 160 | 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 | + """ |
126 | 169 | specifiers = requirement.specifier |
127 | 170 |
|
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) |
131 | 174 |
|
132 | | - # Extract the version from the single specifier |
133 | | - min_version = None |
| 175 | + # Handle single specifier (most common case) |
134 | 176 | 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)) |
144 | 214 |
|
145 | 215 |
|
146 | 216 | def resolve_conan_data(data: dict[str, Any], core_data: CorePluginData) -> ConanData: |
|
0 commit comments