8000 organizing cli, moving to api by memsharded · Pull Request #17961 · conan-io/conan · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

organizing cli, moving to api #17961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions conan/api/subapi/command.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from conan.api.output import ConanOutput
from conan.errors import ConanException

Expand Down Expand Up @@ -25,9 +27,9 @@ def run(self, cmd):
# get redefined when running a command and leak to the calling scope
# if running from a custom command.
# Store the old one and restore it after the command execution as a workaround.
_conan_output_level = ConanOutput._conan_output_level
_silent_warn_tags = ConanOutput._silent_warn_tags
_warnings_as_errors = ConanOutput._warnings_as_errors
_conan_output_level = ConanOutput._conan_output_level # noqa
_silent_warn_tags = ConanOutput._silent_warn_tags # noqa
_warnings_as_errors = ConanOutput._warnings_as_errors # noqa

try:
result = command.run_cli(self.conan_api, args)
Expand All @@ -36,3 +38,25 @@ def run(self, cmd):
ConanOutput._silent_warn_tags = _silent_warn_tags
ConanOutput._warnings_as_errors = _warnings_as_errors
return result

@staticmethod
def get_runner(profile_host):
if profile_host.runner and not os.environ.get("CONAN_RUNNER_ENVIRONMENT"):
from conan.internal.runner.docker import DockerRunner
from conan.internal.runner.ssh import SSHRunner
from conan.internal.runner.wsl import WSLRunner
try:
runner_type = profile_host.runner['type'].lower()
except KeyError:
raise ConanException(f"Invalid runner configuration. 'type' must be defined")
runner_instances_map = {
'docker': DockerRunner,
# 'ssh': SSHRunner,
# 'wsl': WSLRunner,
}
try:
runner_instance = runner_instances_map[runner_type]
except KeyError:
raise ConanException(f"Invalid runner type '{runner_type}'. "
f"Allowed values: {', '.join(runner_instances_map.keys())}")
return runner_instance(profile_host.runner)
81 changes: 79 additions & 2 deletions conan/api/subapi/list.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import copy
import os
from collections import OrderedDict
from typing import Dict

from conan.api.model import PackagesList
from conan.api.model import PackagesList, MultiPackagesList, ListPattern
from conan.api.output import ConanOutput, TimedOutput
from conan.internal.api.list.query_parse import filter_package_configs
from conan.internal.conan_app import ConanBasicApp
Expand Down Expand Up @@ -242,7 +243,7 @@ def explain_missing_binaries(self, ref, conaninfo, remotes):
ConanOutput().info(f"Finding binaries in remote {remote.name}")
pkg_configurations = self.packages_configurations(ref, remote=remote)
except Exception as e:
ConanOutput(f"ERROR IN REMOTE {remote.name}: {e}")
ConanOutput().error(f"Error in remote '{remote.name}': {e}")
else:
candidates.extend(_BinaryDistance(pref, data, conaninfo, remote)
for pref, data in pkg_configurations.items())
Expand All @@ -266,6 +267,82 @@ def explain_missing_binaries(self, ref, conaninfo, remotes):
rev_dict["packages"][pref.package_id]["remote"] = remote
return pkglist

def find_remotes(self, package_list, remotes):
"""
(Experimental) Find the remotes where the current package lists can be found
"""
result = MultiPackagesList()
for r in remotes:
result_pkg_list = PackagesList()
for ref, recipe_bundle in package_list.refs().items():
ref_no_rev = copy.copy(ref) # TODO: Improve ugly API
ref_no_rev.revision = None
try:
revs = self.recipe_revisions(ref_no_rev, remote=r)
except NotFoundException:
continue
if ref not in revs: # not found
continue
result_pkg_list.add_refs([ref])
for pref, pref_bundle in package_list.prefs(ref, recipe_bundle).items():
pref_no_rev = copy.copy(pref) # TODO: Improve ugly API
pref_no_rev.revision = None
try:
prevs = self.package_revisions(pref_no_rev, remote=r)
except NotFoundException:
continue
if pref in prevs:
result_pkg_list.add_prefs(ref, [pref])
info = recipe_bundle["packages"][pref.package_id]["info"]
result_pkg_list.add_configurations({pref: info})
if result_pkg_list.recipes:
result.add(r.name, result_pkg_list)
return result

def outdated(self, deps_graph, remotes):
# DO NOT USE YET
# Data structure to store info per library
dependencies = deps_graph.nodes[1:]
dict_nodes = {}

# When there are no dependencies command should stop
if len(dependencies) == 0:
return dict_nodes

ConanOutput().title("Checking remotes")

for node in dependencies:
dict_nodes.setdefault(node.name, {"cache_refs": set(), "version_ranges": [],
"latest_remote": None})["cache_refs"].add(node.ref)

for version_range in deps_graph.resolved_ranges.keys():
dict_nodes[version_range.name]["version_ranges"].append(version_range)

# find in remotes
for node_name, node_info in dict_nodes.items():
ref_pattern = ListPattern(node_name, rrev=None, prev=None)
for remote in remotes:
try:
remote_ref_list = self.select(ref_pattern, package_query=None, remote=remote)
except NotFoundException:
continue
if not remote_ref_list.recipes:
continue
str_latest_ref = list(remote_ref_list.recipes.keys())[-1]
recipe_ref = RecipeReference.loads(str_latest_ref)
if (node_info["latest_remote"] is None
or node_info["latest_remote"]["ref"] < recipe_ref):
node_info["latest_remote"] = {"ref": recipe_ref, "remote": remote.name}

# Filter nodes with no outdated versions
filtered_nodes = {}
for node_name, node in dict_nodes.items():
if node['latest_remote'] is not None and sorted(list(node['cache_refs']))[0] < \
node['latest_remote']['ref']:
filtered_nodes[node_name] = node

return filtered_nodes


class _BinaryDistance:
def __init__(self, pref, binary, expected, remote=None):
Expand Down
4 changes: 2 additions & 2 deletions conan/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from conan.cli.command import ConanSubCommand
from conan.cli.exit_codes import SUCCESS, ERROR_MIGRATION, ERROR_GENERAL, USER_CTRL_C, \
ERROR_SIGTERM, USER_CTRL_BREAK, ERROR_INVALID_CONFIGURATION, ERROR_UNEXPECTED
from conan.internal.cache.home_paths import HomePaths
from conan import __version__
from conan.errors import ConanException, ConanInvalidConfiguration, ConanMigrationError

Expand Down Expand Up @@ -49,7 +48,8 @@ def add_commands(self):
for k, v in self._commands.items(): # Fill groups data too
self._groups[v.group].append(k)

conan_custom_commands_path = HomePaths(self._conan_api.cache_folder).custom_commands_path
conan_custom_commands_path = os.path.join(self._conan_api.cache_folder, "extensions",
"commands")
# Important! This variable should be only used for testing/debugging purpose
developer_custom_commands_path = os.getenv(_CONAN_INTERNAL_CUSTOM_COMMANDS_PATH)
# Notice that in case of having same custom commands file names, the developer one has
Expand Down
27 changes: 5 additions & 22 deletions conan/cli/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
from conan.cli.printers import print_profiles
from conan.cli.printers.graph import print_graph_packages, print_graph_basic
from conan.errors import ConanException
from conans.client.graph.graph import BINARY_BUILD
from conans.util.files import mkdir


@conan_command(group="Creator", formatters={"json": format_graph_json})
Expand Down Expand Up @@ -63,24 +61,9 @@ def create(conan_api, parser, *args):
lockfile = conan_api.lockfile.update_lockfile_export(lockfile, conanfile, ref, is_build)

print_profiles(profile_host, profile_build)
if profile_host.runner and not os.environ.get("CONAN_RUNNER_ENVIRONMENT"):
from conan.internal.runner.docker import DockerRunner
from conan.internal.runner.ssh import SSHRunner
from conan.internal.runner.wsl import WSLRunner
try:
runner_type = profile_host.runner['type'].lower()
except KeyError:
raise ConanException(f"Invalid runner configuration. 'type' must be defined")
runner_instances_map = {
'docker': DockerRunner,
# 'ssh': SSHRunner,
# 'wsl': WSLRunner,
}
try:
runner_instance = runner_instances_map[runner_type]
except KeyError:
raise ConanException(f"Invalid runner type '{runner_type}'. Allowed values: {', '.join(runner_instances_map.keys())}")
return runner_instance(conan_api, 'create', profile_host, profile_build, args, raw_args).run()
runner = conan_api.command.get_runner(profile_host)
if runner is not None:
return runner(conan_api, 'create', profile_host, profile_build, args, raw_args).run()

if args.build is not None and args.build_test is None:
args.build_test = args.build
Expand Down Expand Up @@ -125,7 +108,7 @@ def create(conan_api, parser, *args):
test_conanfile_path = _get_test_conanfile_path(test_package_folder, path)
# If the user provide --test-missing and the binary was not built from source, skip test_package
if args.test_missing and deps_graph.root.dependencies\
and deps_graph.root.dependencies[0].dst.binary != BINARY_BUILD:
and deps_graph.root.dependencies[0].dst.binary != "Build":
test_conanfile_path = None # disable it

if test_conanfile_path:
Expand Down Expand Up @@ -176,7 +159,7 @@ def test_package(conan_api, deps_graph, test_conanfile_path):
out.info("Removing previously existing 'test_package' build folder: "
f"{conanfile.build_folder}")
shutil.rmtree(conanfile.build_folder, ignore_errors=True)
mkdir(conanfile.build_folder)
os.makedirs(conanfile.build_folder, exist_ok=True)
conanfile.output.info(f"Test package build: {conanfile.folders.build}")
conanfile.output.info(f"Test package build folder: {conanfile.build_folder}")
conan_api.install.install_consumer(deps_graph=deps_graph,
Expand Down
55 changes: 5 additions & 50 deletions conan/cli/commands/graph.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
import os

from conan.api.model import ListPattern
from conan.api.output import ConanOutput, cli_out_write, Color
from conan.cli import make_abs_path
from conan.cli.args import common_graph_args, validate_common_graph_args
Expand All @@ -14,8 +13,6 @@
from conan.cli.printers.graph import print_graph_packages, print_graph_basic
from conan.errors import ConanException
from conans.client.graph.install_graph import InstallGraph, ProfileArgs
from conan.internal.errors import NotFoundException
from conan.api.model import RecipeReference


def explain_formatter_text(data):
Expand All @@ -38,7 +35,7 @@ def explain_formatter_json(data):


@conan_command(group="Consumer")
def graph(conan_api, parser, *args):
def graph(conan_api, parser, *args): # noqa
"""
Compute a dependency graph, without installing or building the binaries.
"""
Expand Down Expand Up @@ -135,7 +132,7 @@ def graph_build_order(conan_api, parser, subparser, *args):

@conan_subcommand(formatters={"text": cli_build_order, "json": json_build_order,
"html": format_build_order_html})
def graph_build_order_merge(conan_api, parser, subparser, *args):
def graph_build_order_merge(conan_api, parser, subparser, *args): # noqa
"""
Merge more than 1 build-order file.
"""
Expand Down Expand Up @@ -378,48 +375,6 @@ def graph_outdated(conan_api, parser, subparser, *args):
print_graph_basic(deps_graph)

# Data structure to store info per library
dependencies = deps_graph.nodes[1:]
dict_nodes = {}

# When there are no dependencies command should stop
if len(dependencies) == 0:
return dict_nodes

ConanOutput().title("Checking remotes")

for node in dependencies:
dict_nodes.setdefault(node.name, {"cache_refs": set(), "version_ranges": [],
"latest_remote": None})["cache_refs"].add(node.ref)

for version_range in deps_graph.resolved_ranges.keys():
dict_nodes[version_range.name]["version_ranges"].append(version_range)

# find in remotes
_find_in_remotes(conan_api, dict_nodes, remotes)

# Filter nodes with no outdated versions
filtered_nodes = {}
for node_name, node in dict_nodes.items():
if node['latest_remote'] is not None and sorted(list(node['cache_refs']))[0] < \
node['latest_remote']['ref']:
filtered_nodes[node_name] = node

return filtered_nodes


def _find_in_remotes(conan_api, dict_nodes, remotes):
for node_name, node_info in dict_nodes.items():
ref_pattern = ListPattern(node_name, rrev=None, prev=None)
for remote in remotes:
try:
remote_ref_list = conan_api.list.select(ref_pattern, package_query=None,
remote=remote)
except NotFoundException:
continue
if not remote_ref_list.recipes:
continue
str_latest_ref = list(remote_ref_list.recipes.keys())[-1]
recipe_ref = RecipeReference.loads(str_latest_ref)
if (node_info["latest_remote"] is None
or node_info["latest_remote"]["ref"] < recipe_ref):
node_info["latest_remote"] = {"ref": recipe_ref, "remote": remote.name}
# DO NOT USE this API call yet, it is not stable
outdated = conan_api.list.outdated(deps_graph, remotes)
return outdated
38 changes: 4 additions & 34 deletions conan/cli/commands/pkglist.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import copy

from conan.api.conan_api import ConanAPI
from conan.api.model import MultiPackagesList, PackagesList
from conan.api.model import MultiPackagesList
from conan.cli import make_abs_path
from conan.cli.command import conan_command, conan_subcommand
from conan.cli.commands.list import print_list_text, print_list_json
from conan.cli.formatters.list import list_packages_html
from conan.internal.errors import NotFoundException


@conan_command(group="Consumer")
Expand All @@ -33,34 +30,7 @@ def pkglist_find_remote(conan_api, parser, subparser, *args):
multi_pkglist = MultiPackagesList.load(listfile)
package_list = multi_pkglist["Local Cache"]
selected_remotes = conan_api.remotes.list(args.remote)

result = MultiPackagesList()
for r in selected_remotes:
result_pkg_list = PackagesList()
for ref, recipe_bundle in package_list.refs().items():
ref_no_rev = copy.copy(ref) # TODO: Improve ugly API
ref_no_rev.revision = None
try:
revs = conan_api.list.recipe_revisions(ref_no_rev, remote=r)
except NotFoundException:
continue
if ref not in revs: # not found
continue
result_pkg_list.add_refs([ref])
for pref, pref_bundle in package_list.prefs(ref, recipe_bundle).items():
pref_no_rev = copy.copy(pref) # TODO: Improve ugly API
pref_no_rev.revision = None
try:
prevs = conan_api.list.package_revisions(pref_no_rev, remote=r)
except NotFoundException:
continue
if pref in prevs:
result_pkg_list.add_prefs(ref, [pref])
info = recipe_bundle["packages"][pref.package_id]["info"]
result_pkg_list.add_configurations({pref: info})
if result_pkg_list.recipes:
result.add(r.name, result_pkg_list)

result = conan_api.list.find_remotes(package_list, selected_remotes)
return {
"results": result.serialize(),
"conan_api": conan_api,
Expand All @@ -80,8 +50,8 @@ def pkglist_merge(conan_api, parser, subparser, *args):
args = parser.parse_args(*args)

result = MultiPackagesList()
for pkglist in args.list:
listfile = make_abs_path(pkglist)
for pkg_list in args.list:
listfile = make_abs_path(pkg_list)
multi_pkglist = MultiPackagesList.load(listfile)
result.merge(multi_pkglist)

Expand Down
Loading
0