Skip to content

Commit

Permalink
add conan list
Browse files Browse the repository at this point in the history
  • Loading branch information
amhellmund committed Dec 31, 2024
1 parent d4820c6 commit f9dc77f
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 147 deletions.
21 changes: 19 additions & 2 deletions src/cpp_dev/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

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


@dataclass
class SemanticVersionParts:
"""The semantic version components."""

major: int
minor: int
patch: int

def __lt__(self, other: SemanticVersionParts) -> bool:
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)


class SemanticVersion(RootModel):
"""A semantic version string restricted to the <major>.<minor>.<patch> format.
Expand Down Expand Up @@ -53,17 +66,21 @@ def validate_version(self) -> SemanticVersion:
return self

@property
def parts(self) -> tuple[int, int, int]:
def parts(self) -> SemanticVersionParts:
"""Return the components of the semantic version."""
major, minor, patch = tuple(map(int, self.root.split(".")))
return major, minor, patch
return SemanticVersionParts(major, minor, patch)

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

def __lt__(self, other: SemanticVersion) -> bool:
"""Compare two semantic versions."""
return self.parts < other.parts

def __hash__(self) -> int:
"""Hash the semantic version string."""
return hash(self.root)
Expand Down
49 changes: 49 additions & 0 deletions src/cpp_dev/conan/command_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) 2024 Andi Hellmund. All rights reserved.

# This work is licensed under the terms of the BSD-3-Clause license.
# For a copy, see <https://opensource.org/license/bsd-3-clause>.

import json
from collections.abc import Mapping
from pathlib import Path

from pydantic import BaseModel, RootModel

from cpp_dev.common.process import run_command

from .types import ConanPackageReference

###############################################################################
# Public API ###
###############################################################################


def conan_config_install(conan_config_dir: Path) -> None:
"""Run 'conan config install'."""
run_command("conan", "config", "install", str(conan_config_dir))


def conan_remote_login(remote: str, user: str, password: str) -> None:
"""Run 'conan remote login'."""
run_command(
"conan",
"remote",
"login",
remote,
user,
"-p",
password,
)

class ConanRemoteListResult(RootModel):
root: Mapping[str, Mapping[str, dict]]

def conan_list(remote: str, name: str) -> Mapping[ConanPackageReference, dict]:
stdout, stderr = run_command(
"conan",
"list",
"--json",
f"--remote={remote}",
f"{name}",
)
return json.loads(stdout)[remote]
87 changes: 0 additions & 87 deletions src/cpp_dev/conan/commands.py

This file was deleted.

42 changes: 42 additions & 0 deletions src/cpp_dev/conan/package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (c) 2024 Andi Hellmund. All rights reserved.

# This work is licensed under the terms of the BSD-3-Clause license.
# For a copy, see <https://opensource.org/license/bsd-3-clause>.

from pathlib import Path

from cpp_dev.common.types import SemanticVersion
from cpp_dev.conan.command_wrapper import conan_list
from cpp_dev.conan.setup import CONAN_REMOTE
from cpp_dev.conan.types import ConanPackageReference
from cpp_dev.conan.utils import conan_env

###############################################################################
# Public API ###
###############################################################################

def get_available_versions(conan_home: Path, repository: str, name: str) -> list[SemanticVersion]:
"""Retrieve available versions for a package represented by repository (aka. Conan user) and name.
Result:
The versions get sorted in reverse order such that the latest version is first in the list.
"""
with conan_env(conan_home):
package_references = _retrieve_conan_package_references(repository, name)
available_versions = sorted([ref.version for ref in package_references], reverse=True)
return available_versions



###############################################################################
# Implementation ###
###############################################################################

def _retrieve_conan_package_references(repository: str, name: str) -> list[ConanPackageReference]:
package_data = conan_list(CONAN_REMOTE, name)
package_references = [
ref
for ref in package_data.keys()
if ref.user == repository
]
return package_references
33 changes: 15 additions & 18 deletions src/cpp_dev/conan/config.py → src/cpp_dev/conan/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@

from pathlib import Path

from pydantic import BaseModel
from .command_wrapper import conan_config_install, conan_remote_login
from .utils import conan_env

###############################################################################
# Public API ###
###############################################################################

CONAN_REMOTE = "cpd"

# The default Conan user and password are used to authenticate against the Conan
# Important: this user has only READ permissions which is required to download packages
# and obtain meta data.
DEFAULT_CONAN_USER = "cpd_default"
DEFAULT_CONAN_USER_PWD = "Cpd-Dev.1" # noqa: S105

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


class ConanRemote(BaseModel):
"""A Conan remote."""

name: str
url: str
verify_ssl: bool


class ConanRemotes(BaseModel):
"""A list of Conan remotes."""

remotes: list[ConanRemote]


def get_remotes(conan_config_dir: Path) -> ConanRemotes:
"""Get the Conan remotes from the given configuration directory."""
remotes_file = conan_config_dir / "remotes.json"
return ConanRemotes.model_validate_json(remotes_file.read_text())
def initialize_conan(conan_home: Path) -> None:
"""Initialize Conan to use the given home directory."""
with conan_env(conan_home):
conan_config_dir = get_conan_config_source_dir()
conan_config_install(conan_config_dir)
conan_remote_login(CONAN_REMOTE, DEFAULT_CONAN_USER, DEFAULT_CONAN_USER_PWD)
59 changes: 39 additions & 20 deletions src/cpp_dev/conan/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,54 @@
# This work is licensed under the terms of the BSD-3-Clause license.
# For a copy, see <https://opensource.org/license/bsd-3-clause>.

from __future__ import annotations

import re

from pydantic import RootModel, model_validator

from cpp_dev.common.types import SemanticVersion

###############################################################################
# Public API ###
###############################################################################


class ConanPackageReference:
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_]+)"

def __init__(self, ref_str: str) -> None:
"""
Initialize a ConanPackageReference object.
This method parses a Conan package reference string and extracts its components.
It also validates the semantic version format. A Conan package reference has the format:
name/version@user/channel
"""
match = re.match(self.CONAN_REFERENCE_PATTERN, ref_str)
class ConanPackageReference(RootModel):
"""A Conan package reference in the format name/version@user/channel."""

root: str

@model_validator(mode="after")
def validate_reference(self) -> ConanPackageReference:
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_]+)"
match = re.match(CONAN_REFERENCE_PATTERN, self.root)
if not match:
raise ValueError(f"Invalid Conan package reference: {ref_str}")

self.name = match.group('name')
self.version = SemanticVersion(match.group('version'))
self.user = match.group('user')
self.channel = match.group('channel')
raise ValueError(f"Invalid Conan package reference: {self.root}")

self._name = match.group("name")
self._version = SemanticVersion(match.group("version"))
self._user = match.group("user")
self._channel = match.group("channel")

@property
def name(self) -> str:
return self._name

@property
def version(self) -> SemanticVersion:
return self._version

@property
def user(self) -> str:
return self._user

@property
def channel(self) -> str:
return self._channel

def __hash__(self) -> int:
return hash(self.root)

def __str__(self) -> str:
return f"{self.name}/{self.version}@{self.user}/{self.channel}"
return f"{self._name}/{self._version}@{self._user}/{self._channel}"
23 changes: 23 additions & 0 deletions src/cpp_dev/conan/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) 2024 Andi Hellmund. All rights reserved.

# This work is licensed under the terms of the BSD-3-Clause license.
# For a copy, see <https://opensource.org/license/bsd-3-clause>.

from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path

from cpp_dev.common.utils import updated_env

###############################################################################
# Public API ###
###############################################################################

CONAN_HOME_ENV_VAR = "CONAN_HOME"


@contextmanager
def conan_env(conan_home: Path) -> Generator[None]:
"""A context manager for setting the CONAN_HOME environment variable."""
with updated_env(**{CONAN_HOME_ENV_VAR: str(conan_home)}):
yield
2 changes: 1 addition & 1 deletion src/cpp_dev/tool/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from filelock import FileLock, Timeout

from cpp_dev.common.utils import ensure_dir_exists
from cpp_dev.conan.commands import initialize_conan
from cpp_dev.conan.setup import initialize_conan
from cpp_dev.tool.version import get_cpd_version_from_code, read_version_file, write_version_file

###############################################################################
Expand Down
Loading

0 comments on commit f9dc77f

Please sign in to comment.