Skip to content

Commit f9dc77f

Browse files
committed
add conan list
1 parent d4820c6 commit f9dc77f

File tree

14 files changed

+233
-147
lines changed

14 files changed

+233
-147
lines changed

src/cpp_dev/common/types.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from __future__ import annotations
88

9+
from dataclasses import dataclass
910
from typing import Literal
1011

1112
from pydantic import RootModel, model_validator
@@ -17,6 +18,18 @@
1718
CppStandard = Literal["c++11", "c++14", "c++17", "c++20", "c++23"]
1819

1920

21+
@dataclass
22+
class SemanticVersionParts:
23+
"""The semantic version components."""
24+
25+
major: int
26+
minor: int
27+
patch: int
28+
29+
def __lt__(self, other: SemanticVersionParts) -> bool:
30+
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
31+
32+
2033
class SemanticVersion(RootModel):
2134
"""A semantic version string restricted to the <major>.<minor>.<patch> format.
2235
@@ -53,17 +66,21 @@ def validate_version(self) -> SemanticVersion:
5366
return self
5467

5568
@property
56-
def parts(self) -> tuple[int, int, int]:
69+
def parts(self) -> SemanticVersionParts:
5770
"""Return the components of the semantic version."""
5871
major, minor, patch = tuple(map(int, self.root.split(".")))
59-
return major, minor, patch
72+
return SemanticVersionParts(major, minor, patch)
6073

6174
def __eq__(self, other: object) -> bool:
6275
"""Check if two semantic versions are equal."""
6376
if not isinstance(other, SemanticVersion):
6477
return NotImplemented
6578
return self.root == other.root
6679

80+
def __lt__(self, other: SemanticVersion) -> bool:
81+
"""Compare two semantic versions."""
82+
return self.parts < other.parts
83+
6784
def __hash__(self) -> int:
6885
"""Hash the semantic version string."""
6986
return hash(self.root)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright (c) 2024 Andi Hellmund. All rights reserved.
2+
3+
# This work is licensed under the terms of the BSD-3-Clause license.
4+
# For a copy, see <https://opensource.org/license/bsd-3-clause>.
5+
6+
import json
7+
from collections.abc import Mapping
8+
from pathlib import Path
9+
10+
from pydantic import BaseModel, RootModel
11+
12+
from cpp_dev.common.process import run_command
13+
14+
from .types import ConanPackageReference
15+
16+
###############################################################################
17+
# Public API ###
18+
###############################################################################
19+
20+
21+
def conan_config_install(conan_config_dir: Path) -> None:
22+
"""Run 'conan config install'."""
23+
run_command("conan", "config", "install", str(conan_config_dir))
24+
25+
26+
def conan_remote_login(remote: str, user: str, password: str) -> None:
27+
"""Run 'conan remote login'."""
28+
run_command(
29+
"conan",
30+
"remote",
31+
"login",
32+
remote,
33+
user,
34+
"-p",
35+
password,
36+
)
37+
38+
class ConanRemoteListResult(RootModel):
39+
root: Mapping[str, Mapping[str, dict]]
40+
41+
def conan_list(remote: str, name: str) -> Mapping[ConanPackageReference, dict]:
42+
stdout, stderr = run_command(
43+
"conan",
44+
"list",
45+
"--json",
46+
f"--remote={remote}",
47+
f"{name}",
48+
)
49+
return json.loads(stdout)[remote]

src/cpp_dev/conan/commands.py

Lines changed: 0 additions & 87 deletions
This file was deleted.

src/cpp_dev/conan/package.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright (c) 2024 Andi Hellmund. All rights reserved.
2+
3+
# This work is licensed under the terms of the BSD-3-Clause license.
4+
# For a copy, see <https://opensource.org/license/bsd-3-clause>.
5+
6+
from pathlib import Path
7+
8+
from cpp_dev.common.types import SemanticVersion
9+
from cpp_dev.conan.command_wrapper import conan_list
10+
from cpp_dev.conan.setup import CONAN_REMOTE
11+
from cpp_dev.conan.types import ConanPackageReference
12+
from cpp_dev.conan.utils import conan_env
13+
14+
###############################################################################
15+
# Public API ###
16+
###############################################################################
17+
18+
def get_available_versions(conan_home: Path, repository: str, name: str) -> list[SemanticVersion]:
19+
"""Retrieve available versions for a package represented by repository (aka. Conan user) and name.
20+
21+
Result:
22+
The versions get sorted in reverse order such that the latest version is first in the list.
23+
"""
24+
with conan_env(conan_home):
25+
package_references = _retrieve_conan_package_references(repository, name)
26+
available_versions = sorted([ref.version for ref in package_references], reverse=True)
27+
return available_versions
28+
29+
30+
31+
###############################################################################
32+
# Implementation ###
33+
###############################################################################
34+
35+
def _retrieve_conan_package_references(repository: str, name: str) -> list[ConanPackageReference]:
36+
package_data = conan_list(CONAN_REMOTE, name)
37+
package_references = [
38+
ref
39+
for ref in package_data.keys()
40+
if ref.user == repository
41+
]
42+
return package_references
Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55

66
from pathlib import Path
77

8-
from pydantic import BaseModel
8+
from .command_wrapper import conan_config_install, conan_remote_login
9+
from .utils import conan_env
910

1011
###############################################################################
1112
# Public API ###
1213
###############################################################################
1314

15+
CONAN_REMOTE = "cpd"
16+
17+
# The default Conan user and password are used to authenticate against the Conan
18+
# Important: this user has only READ permissions which is required to download packages
19+
# and obtain meta data.
20+
DEFAULT_CONAN_USER = "cpd_default"
21+
DEFAULT_CONAN_USER_PWD = "Cpd-Dev.1" # noqa: S105
1422

1523
def get_conan_config_source_dir() -> Path:
1624
"""Get the directory containing the Conan configuration files in the source tree.
@@ -22,21 +30,10 @@ def get_conan_config_source_dir() -> Path:
2230
return Path(__file__).parent / "config"
2331

2432

25-
class ConanRemote(BaseModel):
26-
"""A Conan remote."""
27-
28-
name: str
29-
url: str
30-
verify_ssl: bool
31-
32-
33-
class ConanRemotes(BaseModel):
34-
"""A list of Conan remotes."""
35-
36-
remotes: list[ConanRemote]
37-
3833

39-
def get_remotes(conan_config_dir: Path) -> ConanRemotes:
40-
"""Get the Conan remotes from the given configuration directory."""
41-
remotes_file = conan_config_dir / "remotes.json"
42-
return ConanRemotes.model_validate_json(remotes_file.read_text())
34+
def initialize_conan(conan_home: Path) -> None:
35+
"""Initialize Conan to use the given home directory."""
36+
with conan_env(conan_home):
37+
conan_config_dir = get_conan_config_source_dir()
38+
conan_config_install(conan_config_dir)
39+
conan_remote_login(CONAN_REMOTE, DEFAULT_CONAN_USER, DEFAULT_CONAN_USER_PWD)

src/cpp_dev/conan/types.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,54 @@
33
# This work is licensed under the terms of the BSD-3-Clause license.
44
# For a copy, see <https://opensource.org/license/bsd-3-clause>.
55

6+
from __future__ import annotations
7+
68
import re
79

10+
from pydantic import RootModel, model_validator
11+
812
from cpp_dev.common.types import SemanticVersion
913

1014
###############################################################################
1115
# Public API ###
1216
###############################################################################
1317

1418

15-
class ConanPackageReference:
16-
CONAN_REFERENCE_PATTERN = r"(?P<name>[a-zA-Z0-9_]+)/(?P<version>\d+\.\d+\.\d+)@(?P<user>[a-zA-Z0-9_]+)/(?P<channel>[a-zA-Z0-9_]+)"
17-
18-
def __init__(self, ref_str: str) -> None:
19-
"""
20-
Initialize a ConanPackageReference object.
21-
22-
This method parses a Conan package reference string and extracts its components.
23-
It also validates the semantic version format. A Conan package reference has the format:
24-
25-
name/version@user/channel
26-
"""
27-
match = re.match(self.CONAN_REFERENCE_PATTERN, ref_str)
19+
class ConanPackageReference(RootModel):
20+
"""A Conan package reference in the format name/version@user/channel."""
21+
22+
root: str
23+
24+
@model_validator(mode="after")
25+
def validate_reference(self) -> ConanPackageReference:
26+
CONAN_REFERENCE_PATTERN = r"(?P<name>[a-zA-Z0-9_]+)/(?P<version>\d+\.\d+\.\d+)@(?P<user>[a-zA-Z0-9_]+)/(?P<channel>[a-zA-Z0-9_]+)"
27+
match = re.match(CONAN_REFERENCE_PATTERN, self.root)
2828
if not match:
29-
raise ValueError(f"Invalid Conan package reference: {ref_str}")
30-
31-
self.name = match.group('name')
32-
self.version = SemanticVersion(match.group('version'))
33-
self.user = match.group('user')
34-
self.channel = match.group('channel')
29+
raise ValueError(f"Invalid Conan package reference: {self.root}")
30+
31+
self._name = match.group("name")
32+
self._version = SemanticVersion(match.group("version"))
33+
self._user = match.group("user")
34+
self._channel = match.group("channel")
35+
36+
@property
37+
def name(self) -> str:
38+
return self._name
39+
40+
@property
41+
def version(self) -> SemanticVersion:
42+
return self._version
43+
44+
@property
45+
def user(self) -> str:
46+
return self._user
47+
48+
@property
49+
def channel(self) -> str:
50+
return self._channel
51+
52+
def __hash__(self) -> int:
53+
return hash(self.root)
3554

3655
def __str__(self) -> str:
37-
return f"{self.name}/{self.version}@{self.user}/{self.channel}"
56+
return f"{self._name}/{self._version}@{self._user}/{self._channel}"

src/cpp_dev/conan/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright (c) 2024 Andi Hellmund. All rights reserved.
2+
3+
# This work is licensed under the terms of the BSD-3-Clause license.
4+
# For a copy, see <https://opensource.org/license/bsd-3-clause>.
5+
6+
from collections.abc import Generator
7+
from contextlib import contextmanager
8+
from pathlib import Path
9+
10+
from cpp_dev.common.utils import updated_env
11+
12+
###############################################################################
13+
# Public API ###
14+
###############################################################################
15+
16+
CONAN_HOME_ENV_VAR = "CONAN_HOME"
17+
18+
19+
@contextmanager
20+
def conan_env(conan_home: Path) -> Generator[None]:
21+
"""A context manager for setting the CONAN_HOME environment variable."""
22+
with updated_env(**{CONAN_HOME_ENV_VAR: str(conan_home)}):
23+
yield

src/cpp_dev/tool/init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from filelock import FileLock, Timeout
1010

1111
from cpp_dev.common.utils import ensure_dir_exists
12-
from cpp_dev.conan.commands import initialize_conan
12+
from cpp_dev.conan.setup import initialize_conan
1313
from cpp_dev.tool.version import get_cpd_version_from_code, read_version_file, write_version_file
1414

1515
###############################################################################

0 commit comments

Comments
 (0)