Skip to content

Commit 7b7c671

Browse files
committed
add conan package reference
1 parent 46d2457 commit 7b7c671

8 files changed

Lines changed: 270 additions & 31 deletions

File tree

conan/recipes/googletest/conanfile.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
1-
from conan import ConanFile
1+
import os
2+
23
from conan.errors import ConanInvalidConfiguration
34
from conan.tools.build import check_min_cppstd
45
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
5-
from conan.tools.files import (
6-
apply_conandata_patches,
7-
copy,
8-
export_conandata_patches,
9-
get,
10-
replace_in_file,
11-
rm,
12-
rmdir,
13-
)
6+
from conan.tools.files import (apply_conandata_patches, copy,
7+
export_conandata_patches, get, replace_in_file,
8+
rm, rmdir)
149
from conan.tools.microsoft import is_msvc_static_runtime, msvc_runtime_flag
1510
from conan.tools.scm import Version
16-
import os
11+
12+
from conan import ConanFile
1713

1814
required_conan_version = ">=1.54.0"
1915

2016

2117
class GTestConan(ConanFile):
2218
name = "gtest"
23-
user = "cppdev"
24-
channel = "official"
19+
user = "official"
20+
channel = "cppdev"
2521
description = "Google's C++ test framework"
2622
license = "BSD-3-Clause"
2723
url = "https://github.com/conan-io/conan-center-index"

conan/recipes/llvm/conanfile.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import os
22

3-
from conan import ConanFile
3+
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
44
from conan.tools.files import collect_libs, copy, get
5-
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
65
from conan.tools.files.symlinks import absolute_to_relative_symlinks
76

7+
from conan import ConanFile
8+
89

910
class LlvmRecipe(ConanFile):
1011
name = "llvm"
11-
user = "cppdev"
12-
channel = "official"
12+
user = "official"
13+
channel = "cppdev"
1314
major = 19
1415
minor = 1
1516
patch = 0

src/cpp_dev/conan/commands.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99

1010
from cpp_dev.common.process import run_command
11+
from cpp_dev.common.types import SemanticVersion
1112
from cpp_dev.common.utils import updated_env
1213
from cpp_dev.conan.config import get_conan_config_source_dir, get_remotes
1314

@@ -17,6 +18,7 @@
1718

1819

1920
CONAN_HOME_ENV_VAR = "CONAN_HOME"
21+
CONAN_REMOTE = "cpd"
2022

2123

2224
def initialize_conan(conan_home: Path) -> None:
@@ -27,6 +29,12 @@ def initialize_conan(conan_home: Path) -> None:
2729
_set_conan_default_user_and_password(conan_config_dir)
2830

2931

32+
def get_available_versions(conan_home: Path, repository: str, name: str) -> list[SemanticVersion]:
33+
"""Retrieve available versions for a package represented by repository (aka. Conan user) and name."""
34+
with _conan_env(conan_home):
35+
versions = _list_conan_versions(repository, name)
36+
37+
3038
###############################################################################
3139
# Implementation ###
3240
###############################################################################
@@ -51,12 +59,29 @@ def _install_conan_config(conan_config_dir: Path) -> None:
5159
def _set_conan_default_user_and_password(conan_config_dir: Path) -> None:
5260
conan_remotes = get_remotes(conan_config_dir)
5361
for remote in conan_remotes.remotes:
54-
run_command(
55-
"conan",
56-
"remote",
57-
"login",
58-
remote.name,
59-
_DEFAULT_CONAN_USER,
60-
"-p",
61-
_DEFAULT_CONAN_USER_PWD,
62-
)
62+
_command_wrapper_conan_remote_login(remote.name, _DEFAULT_CONAN_USER, _DEFAULT_CONAN_USER_PWD)
63+
64+
def _command_wrapper_conan_remote_login(remote: str, user: str, password: str) -> None:
65+
run_command(
66+
"conan",
67+
"remote",
68+
"login",
69+
remote,
70+
user,
71+
"-p",
72+
password,
73+
)
74+
75+
76+
def _list_conan_versions(repository: str, name: str) -> list[SemanticVersion]:
77+
stdout, stderr = run_command("conan", "list", "--json", f"")
78+
79+
80+
def _command_wrapper_conan_list(remote: str, name: str) -> list[str]:
81+
stdout, stderr = run_command(
82+
"conan",
83+
"list",
84+
"--json",
85+
f"--remote={remote}",
86+
f"{repository}/{name}",
87+
)

src/cpp_dev/conan/config/remotes.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
{
22
"remotes": [
3-
{
4-
"name": "cpd_official",
5-
"url": "http://localhost:8081/artifactory/api/conan/cpd_official",
6-
"verify_ssl": false
7-
},
83
{
94
"name": "cpd",
105
"url": "http://localhost:8081/artifactory/api/conan/cpd",

src/cpp_dev/conan/types.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 re
7+
8+
from cpp_dev.common.types import SemanticVersion
9+
10+
###############################################################################
11+
# Public API ###
12+
###############################################################################
13+
14+
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)
28+
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')
35+
36+
def __str__(self) -> str:
37+
return f"{self.name}/{self.version}@{self.user}/{self.channel}"

src/cpp_dev/project/management.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
7+
from pathlib import Path
8+
from textwrap import dedent
9+
10+
from cpp_dev.project.dependency.types import PackageDependency
11+
from cpp_dev.project.lockfile import create_initial_lock_file
12+
13+
from .config import create_project_config
14+
from .constants import compose_include_file, compose_source_file
15+
from .types import DependencyType, ProjectConfig
16+
17+
###############################################################################
18+
# Public API ###
19+
###############################################################################
20+
21+
22+
def setup_project(
23+
project_config: ProjectConfig,
24+
parent_dir: Path | None = None,
25+
) -> Path:
26+
"""Create a new cpp-dev project in the specified parent directory.
27+
28+
The path to the new project directory is returned.
29+
"""
30+
project_dir = _validate_project_dir(parent_dir, project_config.name)
31+
create_project_config(project_dir, project_config)
32+
create_initial_lock_file(project_dir)
33+
_add_default_cpd_dependencies(project_dir)
34+
_create_project_files(project_dir, project_config.name)
35+
return project_dir
36+
37+
38+
def add_package_dependency(project_dir: Path, deps: list[PackageDependency], dep_type: DependencyType) -> None:
39+
"""Add package dependencies to the project for the given type."""
40+
41+
42+
###############################################################################
43+
# Implementation ###
44+
###############################################################################
45+
46+
47+
def _validate_project_dir(parent_dir: Path | None, name: str) -> Path:
48+
"""Check and validate if the project directory does not yet exist."""
49+
if parent_dir is None:
50+
parent_dir = Path.cwd()
51+
project_dir = parent_dir / name
52+
if project_dir.exists():
53+
raise ValueError(f"Project directory {project_dir} already exists.")
54+
project_dir.mkdir(parents=True)
55+
return project_dir
56+
57+
58+
def _add_default_cpd_dependencies(project_dir: Path) -> None:
59+
add_package_dependency(project_dir, [PackageDependency("llvm"), PackageDependency("gtest")], "cpd")
60+
61+
62+
def _create_project_files(project_dir: Path, name: str) -> None:
63+
"""Create the necessary project files for the cpp-dev package."""
64+
_create_library_include_file(project_dir, name)
65+
_create_library_source_file(project_dir, name)
66+
_create_library_test_file(project_dir, name)
67+
68+
69+
def _create_library_include_file(project_dir: Path, name: str) -> None:
70+
include_file = compose_include_file(project_dir, name, f"{name}.hpp")
71+
include_file.parent.mkdir(parents=True)
72+
include_file.write_text(
73+
dedent(
74+
f"""\
75+
#pragma once
76+
77+
namespace {name} {{
78+
int api();
79+
}}
80+
""",
81+
),
82+
)
83+
84+
85+
def _create_library_source_file(project_dir: Path, name: str) -> None:
86+
source_file = compose_source_file(project_dir, f"{name}.cpp")
87+
source_file.parent.mkdir(parents=True)
88+
source_file.write_text(
89+
dedent(
90+
f"""\
91+
#include "{name}/{name}.hpp"
92+
93+
int {name}::api() {{
94+
return 42;
95+
}}
96+
""",
97+
),
98+
)
99+
100+
101+
def _create_library_test_file(project_dir: Path, name: str) -> None:
102+
test_file = compose_source_file(project_dir, f"{name}.test.cpp")
103+
test_file.write_text(
104+
dedent(
105+
f"""\
106+
#include <gtest/gtest.h>
107+
#include "{name}/{name}.hpp"
108+
109+
TEST({name}, api) {{
110+
EXPECT_EQ({name}::api(), 42);
111+
}}
112+
""",
113+
),
114+
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import pytest
2+
3+
from cpp_dev.common.types import SemanticVersion
4+
from cpp_dev.conan.types import ConanPackageReference
5+
6+
7+
@pytest.mark.parametrize("invalid_ref", [
8+
"",
9+
"invalid_reference",
10+
"mypackage/@myuser/stable",
11+
])
12+
def test_conan_package_reference_invalid(invalid_ref):
13+
with pytest.raises(ValueError, match="Invalid Conan package reference:"):
14+
ConanPackageReference(invalid_ref)
15+
16+
def test_conan_package_reference_valid() -> None:
17+
package_ref = ConanPackageReference("name/1.2.3@user/channel")
18+
19+
assert package_ref.name == "name"
20+
assert package_ref.version == SemanticVersion("1.2.3")
21+
assert package_ref.user == "user"
22+
assert package_ref.channel == "channel"
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+
7+
from pathlib import Path
8+
9+
from cpp_dev.project.config import load_project_config
10+
from cpp_dev.project.constants import compose_project_config_file, compose_project_lock_file
11+
from cpp_dev.project.lockfile import load_lock_file
12+
from cpp_dev.project.management import setup_project
13+
from cpp_dev.project.types import ProjectConfig, SemanticVersion
14+
15+
16+
def test_setup_project(tmp_path: Path) -> None:
17+
project_config = ProjectConfig(
18+
name="test_package",
19+
version=SemanticVersion("1.0.0"),
20+
std="c++17",
21+
author="author",
22+
license="license",
23+
description="description",
24+
dependencies=[],
25+
dev_dependencies=[],
26+
cpd_dependencies=[],
27+
)
28+
29+
project_dir = setup_project(project_config, parent_dir=tmp_path)
30+
31+
assert project_dir.exists()
32+
assert project_dir == tmp_path / project_config.name
33+
34+
assert compose_project_config_file(project_dir).exists()
35+
36+
config = load_project_config(project_dir)
37+
assert config == project_config
38+
39+
assert len(config.dependencies) == 0
40+
assert len(config.dev_dependencies) == 0
41+
assert len(config.cpd_dependencies) == 0
42+
43+
assert compose_project_lock_file(project_dir).exists()
44+
locked_dependencies = load_lock_file(project_dir)
45+
assert len(locked_dependencies.packages) == 0
46+
47+
assert (project_dir / "include" / project_config.name / f"{project_config.name}.hpp").exists()
48+
assert (project_dir / "src" / f"{project_config.name}.cpp").exists()
49+
assert (project_dir / "src" / f"{project_config.name}.test.cpp").exists()

0 commit comments

Comments
 (0)