-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[commands] New conan require
#19457
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
+236
−3
Merged
[commands] New conan require
#19457
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
7a21352
Added new commands dep and init. Tests included. Only conanfile.py co…
franramirez688 2eb46b2
wip
franramirez688 23768e9
wip
franramirez688 5828d53
Removed init command (useless). Added new demo template to new comman…
franramirez688 afd32a0
typo
franramirez688 33cbb0d
UX
franramirez688 e01b00d
Apply suggestions
franramirez688 bba786c
reverted conan new changes
franramirez688 3aed53f
wip
franramirez688 1ef58cf
Renamed to 'require'
franramirez688 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import os | ||
| import re | ||
|
|
||
| from conan.api.conan_api import ConanAPI | ||
| from conan.api.model import ListPattern, RecipeReference | ||
| from conan.api.output import ConanOutput | ||
| from conan.cli.command import conan_command, conan_subcommand | ||
| from conan.errors import ConanException | ||
| from conan.internal.util.files import save, load | ||
|
|
||
|
|
||
| @conan_subcommand() | ||
| def dep_remove(conan_api, parser, subparser, *args): | ||
| """ | ||
| Removes a requirement from your local conanfile. | ||
| """ | ||
| subparser.add_argument("--folder", | ||
| help="Path to a folder containing a recipe (conanfile.py). " | ||
| "Defaults to the current directory",) | ||
| subparser.add_argument("requires", nargs="*", help="Requirement name.") | ||
| subparser.add_argument("-tor", "--tool", action="append", default=[], | ||
| help="Tool requirement name.") | ||
| subparser.add_argument("-ter", "--test", action="append", default=[], | ||
| help="Test requirement name.") | ||
| args = parser.parse_args(*args) | ||
| path = conan_api.local.get_conanfile_path(args.folder or '.', os.getcwd(), py=True) | ||
| # Check if that requirement exists in the conanfile. If yes, abort. | ||
| conanfile = load(path) | ||
| ConanOutput().debug(f"Loaded conanfile from {path}.") | ||
| requires = [(r, "requires") for r in args.requires] | ||
| tool_requires = [(r, "tool_requires") for r in args.tool] | ||
| test_requires = [(r, "test_requires") for r in args.test] | ||
| success_msgs = [] | ||
| for (name, req_attr) in requires + tool_requires + test_requires: | ||
| if not re.search(rf"self\.{req_attr}\([\"']{name}", conanfile): | ||
| ConanOutput().warning(f"The {req_attr} {name} is not declared in your conanfile.") | ||
| continue | ||
| # Replace the whole line | ||
| conanfile = re.sub(rf"^\s*self\.{req_attr}\([\"']{name}.*\n?", '', | ||
| conanfile, flags=re.MULTILINE) | ||
| success_msgs.append(f"Removed {name} dependency as {req_attr}.") | ||
| save(path, conanfile) | ||
| ConanOutput().success('\n'.join(success_msgs)) | ||
|
|
||
|
|
||
| @conan_subcommand() | ||
| def dep_add(conan_api, parser, subparser, *args): | ||
| """ | ||
| Add a new requirement to your local conanfile as a version range. | ||
| By default, it will look for the requirement versions remotely. | ||
| """ | ||
| subparser.add_argument("--folder", | ||
| help="Path to a folder containing a recipe (conanfile.py). " | ||
| "Defaults to the current directory",) | ||
| subparser.add_argument("requires", nargs="*", help="Requirement name.") | ||
| subparser.add_argument("-tor", "--tool", action="append", default=[], | ||
| help="Tool requirement name.") | ||
| subparser.add_argument("-ter", "--test", action="append", default=[], | ||
| help="Test requirement name.") | ||
| group = subparser.add_mutually_exclusive_group() | ||
| group.add_argument("-r", "--remote", default=None, action="append", | ||
| help="Remote names. Accepts wildcards ('*' means all the remotes available)") | ||
| group.add_argument("-nr", "--no-remote", action="store_true", | ||
| help='Do not use remote, resolve exclusively in the cache') | ||
| args = parser.parse_args(*args) | ||
| requires = [(r, "requires", "requirements") for r in args.requires] | ||
| tool_requires = [(r, "tool_requires", "build_requirements") for r in args.tool] | ||
| test_requires = [(r, "test_requires", "build_requirements") for r in args.test] | ||
| if not any(requires + tool_requires + test_requires): | ||
| raise ConanException("You need to add any requires, tool_requires or test_requires.") | ||
| path = conan_api.local.get_conanfile_path(args.folder or ".", os.getcwd(), py=True) | ||
| remotes = conan_api.remotes.list(args.remote) if not args.no_remote else [None] | ||
| conanfile = load(path) | ||
| ConanOutput().debug(f"Loaded conanfile from {path}.") | ||
| cached_results = {} | ||
| success_msgs = [] | ||
| for (name, req_attr, req_func) in requires + tool_requires + test_requires: | ||
| # Check if that requirement exists in the conanfile. If yes, do nothing. | ||
| if re.search(rf"self\.{req_attr}\([\"']{name}", conanfile): | ||
| ConanOutput().warning(f"The {req_attr} {name} is already in use.") | ||
| continue | ||
| if name in cached_results: | ||
| # Avoid double-search in remotes/cache, e.g., protobuf | ||
| reference = RecipeReference.loads(f"{name}/{cached_results[name]}") | ||
| elif "/" in name: # it already brings a version | ||
| reference = RecipeReference.loads(name) | ||
| cached_results[name] = reference.version # caching the result | ||
| else: # Search the latest version in remotes/cache | ||
| ref_pattern = ListPattern(f"{name}/*") | ||
| # If neither remote nor cache are defined, show results only from cache | ||
| results = {} | ||
| for remote in remotes: | ||
| try: | ||
| pkglist = conan_api.list.select(ref_pattern, remote=remote) | ||
| except Exception as e: | ||
| remote_name = "Cache" if remote is None else remote.name | ||
| ConanOutput().warning(f"[{remote_name}] {str(e)}") | ||
| else: | ||
| results = pkglist.serialize() | ||
| if results: | ||
| break | ||
| if not results: | ||
| ConanOutput().error(f"Recipe {name} not found.") | ||
| continue | ||
| # Put the upper limit for that requirement (next major version) | ||
| reference = RecipeReference.loads(results.popitem()[0]) | ||
| cached_results[name] = reference.version # caching the result | ||
| try: | ||
| version_range = f"{reference.name}/[>={reference.version} <{str(reference.version.bump(0))}]" | ||
| except ConanException: # likely cannot bump the version, using it without ranges | ||
| version_range = str(reference) | ||
| full_version_range = f'self.{req_attr}("{version_range}")' | ||
| if full_version_range: | ||
| tab_space = " " * 4 | ||
| if f"def {req_func}(" in conanfile: | ||
| conanfile = conanfile.replace(f"def {req_func}(self):\n", | ||
| f"def {req_func}(self):\n{tab_space * 2}{full_version_range}\n") | ||
| else: | ||
| requirements_func = f"\n{tab_space}def {req_func}(self):\n{tab_space * 2}{full_version_range}\n" | ||
| conanfile += requirements_func | ||
| success_msgs.append(f"Added '{version_range}' as a new {req_attr}.") | ||
| save(path, conanfile) | ||
| ConanOutput().success('\n'.join(success_msgs)) | ||
|
|
||
|
|
||
| @conan_command(group="Consumer") | ||
| def dep(conan_api: ConanAPI, parser, *args): | ||
| """ | ||
| Adds/removes requirements to/from your local conanfile. | ||
| """ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import textwrap | ||
|
|
||
| from conan.test.utils.tools import GenConanfile, TestClient | ||
|
|
||
|
|
||
| def test_add_dep(): | ||
| """ | ||
| Testing the "conan dep add" command which should always use the highest version | ||
| found in the first remote server or the local cache | ||
| """ | ||
| client = TestClient(default_server_user=True, light=True) | ||
| # No conanfile is present - error | ||
| client.run("dep add hello", assert_error=True) | ||
| assert "ERROR: Conanfile not found at" in client.out | ||
| client.save({"conanfile.py": GenConanfile(name="app")}) | ||
| # No requirements define | ||
| client.run("dep add", assert_error=True) | ||
| assert "ERROR: You need to add any requires, tool_requires or test_requires." in client.out | ||
| # No remote recipe "hello" exists | ||
| client.run("dep add hello") | ||
| assert "ERROR: Recipe hello not found." in client.out | ||
| hello_lib = GenConanfile(name="hello") | ||
| client.save({"hello/conanfile.py": hello_lib}) | ||
| client.run("create hello --version=1.0") | ||
| client.run("create hello --version=2.0") | ||
| client.run("create hello --version=3.0") | ||
| client.run("upload * --confirm -r default") | ||
| # Save a normal requires "hello" | ||
| client.run("dep add hello") | ||
| assert "Added 'hello/[>=3.0 <4]' as a new requires." in client.out | ||
| content = client.load("conanfile.py") | ||
| assert 'self.requires("hello/[>=3.0 <4]")' in content | ||
| # Checking that it works | ||
| client.run("install .") | ||
| expected = textwrap.dedent("""\ | ||
| Resolved version ranges | ||
| hello/[>=3.0 <4]: hello/3.0 | ||
| """) | ||
| assert expected in client.out | ||
| # Let's add the same "hello" but now as tool_requires and test_requires | ||
| client.run("dep add --tool=hello --test=hello") # [tool|test]_requires | ||
| assert "Added 'hello/[>=3.0 <4]' as a new tool_requires." in client.out | ||
| assert "Added 'hello/[>=3.0 <4]' as a new test_requires." in client.out | ||
| # Try to add them again - does nothing and shows a warning | ||
| client.run("dep add hello --tool=hello --test=hello") | ||
| assert "The requires hello is already in use." in client.out | ||
| assert "The tool_requires hello is already in use." in client.out | ||
| assert "The test_requires hello is already in use." in client.out | ||
|
|
||
| # Using only the local cache | ||
| bye_lib = GenConanfile(name="bye") | ||
| client.save({"bye/conanfile.py": bye_lib}) | ||
| client.run("create bye --version=1.0") | ||
| client.run("create bye --version=2.0") | ||
| client.run("dep add bye --no-remote") # from cache | ||
| assert "Added 'bye/[>=2.0 <3]' as a new requires." in client.out | ||
|
|
||
| # Using a specific version (it does not look for it neither locally nor remotely) | ||
| client.run("dep add mylib/1.2") | ||
| assert "Added 'mylib/[>=1.2 <2]' as a new requires." in client.out | ||
|
|
||
| # Using commit as a version | ||
| client.run("dep add other/cci.20203034") # can not bump the version, won't use vrange | ||
| assert "Added 'other/cci.20203034' as a new requires." in client.out | ||
|
|
||
|
|
||
| def test_remove_dep(): | ||
| client = TestClient(light=True) | ||
| # No conanfile is present - error | ||
| client.run("dep remove hello", assert_error=True) | ||
| assert "ERROR: Conanfile not found at" in client.out | ||
| client.save({"conanfile.py": GenConanfile(name="app")}) | ||
| # No requirement "hello" declared | ||
| client.run("dep remove hello") | ||
| assert "WARN: The requires hello is not declared in your conanfile." in client.out | ||
| client.save({"conanfile.py": GenConanfile(name="app") | ||
| .with_requirement("hello/1.2") | ||
| .with_tool_requirement("hello/1.2") | ||
| .with_test_requirement("hello/1.2")}) | ||
| client.run("dep remove hello --tool=hello --test=hello") | ||
| assert "Removed hello dependency as requires." in client.out | ||
| assert "Removed hello dependency as tool_requires." in client.out | ||
| assert "Removed hello dependency as test_requires." in client.out | ||
| content = client.load("conanfile.py") | ||
| assert 'self.requires("hello/1.2"' not in content | ||
| assert 'self.tool_requires("hello/1.2"' not in content | ||
| assert 'self.test_requires("hello/1.2"' not in content | ||
| client.run("dep remove hello --tool=hello --test=hello") | ||
| assert "WARN: The requires hello is not declared in your conanfile." in client.out | ||
| assert "WARN: The tool_requires hello is not declared in your conanfile." in client.out | ||
| assert "WARN: The test_requires hello is not declared in your conanfile." in client.out |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.