Skip to content
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

[develop2] editable ui #13093

Merged
merged 5 commits into from
Feb 13, 2023
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
27 changes: 26 additions & 1 deletion conan/api/subapi/local.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import os

from conan.cli.commands import make_abs_path
from conan.internal.conan_app import ConanApp
from conans.errors import ConanException
from conans.model.recipe_ref import RecipeReference


class LocalAPI:

def __init__(self, conan_api):
self.conan_api = conan_api
self._conan_api = conan_api

@staticmethod
def get_conanfile_path(path, cwd, py):
Expand All @@ -33,3 +35,26 @@ def get_conanfile_path(path, cwd, py):
raise ConanException("A conanfile.py is needed, " + path + " is not acceptable")

return path

def editable_add(self, path, name=None, version=None, user=None, channel=None, cwd=None,
output_folder=None):
path = self._conan_api.local.get_conanfile_path(path, cwd, py=True)
app = ConanApp(self._conan_api.cache_folder)
conanfile = app.loader.load_named(path, name, version, user, channel)
ref = RecipeReference(conanfile.name, conanfile.version, conanfile.user, conanfile.channel)
# Retrieve conanfile.py from target_path
target_path = self._conan_api.local.get_conanfile_path(path=path, cwd=cwd, py=True)
output_folder = make_abs_path(output_folder) if output_folder else None
# Check the conanfile is there, and name/version matches
app.cache.editable_packages.add(ref, target_path, output_folder=output_folder)
return ref

def editable_remove(self, path=None, requires=None, cwd=None):
app = ConanApp(self._conan_api.cache_folder)
if path:
path = self._conan_api.local.get_conanfile_path(path, cwd, py=True)
return app.cache.editable_packages.remove(path, requires)

def editable_list(self):
app = ConanApp(self._conan_api.cache_folder)
return app.cache.editable_packages.edited_refs
70 changes: 35 additions & 35 deletions conan/cli/commands/editable.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import json
import os

from conan.api.output import ConanOutput
from conan.api.output import ConanOutput, cli_out_write
from conan.cli.args import add_reference_args
from conan.cli.command import conan_command, conan_subcommand
from conan.cli.commands import make_abs_path
from conan.internal.conan_app import ConanApp
from conans.model.recipe_ref import RecipeReference


@conan_command(group="Creator")
Expand All @@ -21,55 +20,56 @@ def editable_add(conan_api, parser, subparser, *args):
package is required, it is used from this <path> location instead of from the cache
"""
subparser.add_argument('path', help='Path to the package folder in the user workspace')
subparser.add_argument('reference', help='Package reference e.g.: mylib/1.X@user/channel')
add_reference_args(subparser)
czoido marked this conversation as resolved.
Show resolved Hide resolved
subparser.add_argument("-of", "--output-folder",
help='The root output folder for generated and build files')
czoido marked this conversation as resolved.
Show resolved Hide resolved
args = parser.parse_args(*args)

path = args.path
reference = args.reference
cwd = os.getcwd()

# TODO: Decide in which API we put this
app = ConanApp(conan_api.cache_folder)
# Retrieve conanfile.py from target_path
target_path = conan_api.local.get_conanfile_path(path=path, cwd=cwd, py=True)
output_folder = make_abs_path(args.output_folder) if args.output_folder else None
# Check the conanfile is there, and name/version matches
ref = RecipeReference.loads(reference)
app.cache.editable_packages.add(ref, target_path, output_folder=output_folder)
ConanOutput().success("Reference '{}' in editable mode".format(reference))
ref = conan_api.local.editable_add(args.path, args.name, args.version, args.user, args.channel,
cwd, args.output_folder)
ConanOutput().success("Reference '{}' in editable mode".format(ref))


@conan_subcommand()
def editable_remove(conan_api, parser, subparser, *args):
"""
Remove the "editable" mode for this reference.
"""
subparser.add_argument('reference', help='Package reference e.g.: mylib/1.X@user/channel')
subparser.add_argument("path", nargs="?",
help="Path to a folder containing a recipe (conanfile.py "
"or conanfile.txt) or to a recipe file. e.g., "
"./my_project/conanfile.txt.")
subparser.add_argument("-r", "--refs", action="append",
help='Directly provide reference patterns')
args = parser.parse_args(*args)

app = ConanApp(conan_api.cache_folder)
ref = RecipeReference.loads(args.reference)
ret = app.cache.editable_packages.remove(ref)
editables = conan_api.local.editable_remove(args.path, args.refs)
out = ConanOutput()
if ret:
out.success("Removed editable mode for reference '{}'".format(ref))
if editables:
for ref, info in editables.items():
out.success(f"Removed editable '{ref}': {info['path']}")
else:
out.warning("Reference '{}' was not installed as editable".format(ref))
out.warning("No editables were removed")


@conan_subcommand()
def print_editables_json(data):
results = {str(k): v for k, v in data.items()}
myjson = json.dumps(results, indent=4)
cli_out_write(myjson)


def print_editables_text(data):
for k, v in data.items():
cli_out_write("%s" % k)
cli_out_write(" Path: %s" % v["path"])
if v.get("output_folder"):
cli_out_write(" Output: %s" % v["output_folder"])


@conan_subcommand(formatters={"text": print_editables_text, "json": print_editables_json})
def editable_list(conan_api, parser, subparser, *args):
"""
List packages in editable mode
"""
app = ConanApp(conan_api.cache_folder)
result = {str(k): v for k, v in app.cache.editable_packages.edited_refs.items()}

app = ConanApp(conan_api.cache_folder)
out = ConanOutput()
for k, v in app.cache.editable_packages.edited_refs.items():
out.info("%s" % k)
out.info(" Path: %s" % v["path"])
return result
editables = conan_api.local.editable_list()
return editables
27 changes: 19 additions & 8 deletions conans/client/cache/editable.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import copy
import fnmatch
import json
import os
from os.path import join, normpath
Expand Down Expand Up @@ -46,11 +47,21 @@ def add(self, ref, path, output_folder=None):
self._edited_refs[ref] = {"path": path, "output_folder": output_folder}
self.save()

def remove(self, ref):
assert isinstance(ref, RecipeReference)
_tmp = copy.copy(ref)
_tmp.revision = None
if self._edited_refs.pop(_tmp, None):
self.save()
return True
return False
def remove(self, path, requires):
removed = {}
kept = {}
for ref, info in self._edited_refs.items():
to_remove = False
if path and info["path"] == path:
to_remove = True
else:
for r in requires or []:
if fnmatch.fnmatch(str(ref), r):
to_remove = True
if to_remove:
removed[ref] = info
else:
kept[ref] = info
self._edited_refs = kept
self.save()
return removed
2 changes: 1 addition & 1 deletion conans/test/functional/command/test_install_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def test_deploy_editable():
c = TestClient()
c.save({"conanfile.py": GenConanfile("pkg", "1.0"),
"src/include/hi.h": "hi"})
c.run("editable add . pkg/1.0")
c.run("editable add .")

# If we don't change to another folder, the full_deploy will be recursive and fail
with c.chdir(temp_folder()):
Expand Down
6 changes: 3 additions & 3 deletions conans/test/functional/layout/test_editable_cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def build_dep():
c.run("build . -s build_type=Debug {}".format(output_folder))

with c.chdir("dep"):
c.run("editable add . dep/0.1@ {}".format(output_folder))
c.run("editable add . {}".format(output_folder))

build_dep()

Expand Down Expand Up @@ -56,7 +56,7 @@ def build_pkg(msg):
build_pkg("SUPERDEP")

# Check that create is still possible
c.run("editable remove dep/0.1@")
c.run("editable remove dep")
c.run("create dep")
c.run("create pkg")
# print(c.out)
Expand Down Expand Up @@ -104,7 +104,7 @@ def build_dep():
c.run("build . -s build_type=Debug -o dep/*:shared=True")

with c.chdir("dep"):
c.run("editable add . dep/0.1@")
c.run("editable add .")
build_dep()

def run_pkg(msg):
Expand Down
6 changes: 3 additions & 3 deletions conans/test/functional/layout/test_editable_msbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def build_dep():
c.run("build . -s build_type=Debug {}".format(build_folder))

with c.chdir("dep"):
c.run("editable add . dep/0.1@ {}".format(output_folder))
c.run("editable add . {}".format(output_folder))
build_dep()

def build_pkg(msg):
Expand Down Expand Up @@ -59,7 +59,7 @@ def build_pkg(msg):
build_pkg("SUPERDEP/0.1")

# Check that create is still possible
c.run("editable remove dep/0.1@")
c.run("editable remove dep")
c.run("create dep")
c.run("create pkg")
# print(c.out)
Expand All @@ -82,7 +82,7 @@ def layout(self):
"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_settings("build_type", "arch")
.with_requires("dep/0.1")
.with_generator("MSBuildDeps")})
c.run("editable add dep dep/0.1")
c.run("editable add dep")
c.run("install pkg")
# It doesn't crash anymore
assert "dep/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709 - Editable" in c.out
6 changes: 3 additions & 3 deletions conans/test/functional/layout/test_editables_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def build(self):

# When hello is editable
client.save({"conanfile.py": conan_hello})
client.run("editable add . hello/1.0@")
client.run("editable add . --name=hello --version=1.0")

# Create the consumer again, now it will use the hello editable
client2.run("create . --name=lib --version=1.0")
Expand Down Expand Up @@ -236,7 +236,7 @@ def build(self):

# When hello is editable
client.save({"conanfile.py": conan_hello})
client.run("editable add . hello/1.0@")
client.run("editable add . --name=hello --version=1.0")

# Create the consumer again, now it will use the hello editable
client2.run("create . --name=lib --version=1.0")
Expand Down Expand Up @@ -284,6 +284,6 @@ def layout(self):
""")
c.save({"conanfile.py": conanfile})
c.run("create .")
c.run("editable add . pkg/0.1")
c.run("editable add .")
c.run("install --requires=pkg/0.1@")
assert "pkg/0.1: PKG FOLDER=None!!!"
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def package_info(self):
"consumer/CMakeLists.txt": consumer})
with client.chdir("dep"):
client.run("install .")
client.run("editable add . hello/0.1@")
client.run("editable add . --name=hello --version=0.1")

with client.chdir("consumer"):
client.run("install .")
Expand Down
2 changes: 1 addition & 1 deletion conans/test/integration/command/info/info_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def layout(self):
""")
c.save({"pkg/conanfile.py": conanfile,
"consumer/conanfile.py": GenConanfile().with_require("pkg/0.1")})
c.run("editable add pkg pkg/0.1@")
c.run("editable add pkg --name=pkg --version=0.1")
# TODO: Check this --package-filter with *
c.run("graph info consumer --package-filter=pkg*")
# FIXME: Paths are not diplayed yet
Expand Down
2 changes: 1 addition & 1 deletion conans/test/integration/conanfile/folders_access_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def test_editable(self):
"pkg/file.txt": "MYFILE!",
"consumer/conanfile.py":
GenConanfile().with_require("pkg/0.1@user/stable")})
client.run("editable add pkg pkg/0.1@user/stable")
client.run("editable add pkg --name=pkg --version=0.1 --user=user --channel=stable")

client.run("install consumer")
client.assert_listed_require({"pkg/0.1@user/stable": "Editable"})
Expand Down
7 changes: 3 additions & 4 deletions conans/test/integration/editable/editable_add_test.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
from conans.model.recipe_ref import RecipeReference
from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient


class TestEditablePackageTest:

def test_install_ok(self):
ref = RecipeReference.loads('lib/version@user/name')
ref = "--name=lib --version=version --user=user --channel=name"
t = TestClient()
t.save({'conanfile.py': GenConanfile()})
t.run('editable add . {}'.format(ref))
assert "Reference 'lib/version@user/name' in editable mode" in t.out

def test_editable_list_search(self):
ref = RecipeReference.loads('lib/version@user/name')
ref = "--name=lib --version=version --user=user --channel=name"
t = TestClient()
t.save({'conanfile.py': GenConanfile()})
t.run('editable add . {}'.format(ref))
Expand All @@ -29,7 +28,7 @@ def test_missing_subarguments(self):
def test_conanfile_name(self):
t = TestClient()
t.save({'othername.py': GenConanfile("lib", "version")})
t.run('editable add ./othername.py lib/version@user/name')
t.run('editable add ./othername.py --user=user --channel=name')
assert "Reference 'lib/version@user/name' in editable mode" in t.out
t.run('install --requires=lib/version@user/name')
t.assert_listed_require({"lib/version@user/name": "Editable"})
53 changes: 32 additions & 21 deletions conans/test/integration/editable/editable_remove_test.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
# coding=utf-8

import unittest
import pytest

from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient


class RemoveEditablePackageTest(unittest.TestCase):
class TestRemoveEditablePackageTest:

@pytest.fixture()
def client(self):
t = TestClient()
t.save({'conanfile.py': GenConanfile()})
t.run('editable add . --name=lib --version=version --user=user --channel=name')
t.run("editable list")
assert "lib" in t.out
return t

def test_unlink(self, client):
client.run('editable remove -r=lib/version@user/name')
assert "Removed editable 'lib/version@user/name':" in client.out
client.run("editable list")
assert "lib" not in client.out

def setUp(self):
self.t = TestClient()
self.t.save(files={'conanfile.py': GenConanfile()})
self.t.run('editable add . lib/version@user/name')
self.t.run("editable list")
assert "lib" in self.t.out
def test_unlink_pattern(self, client):
client.run('editable remove -r=*')
assert "Removed editable 'lib/version@user/name':" in client.out
client.run("editable list")
assert "lib" not in client.out

def test_unlink(self):
self.t.run('editable remove lib/version@user/name')
self.assertIn("Removed editable mode for reference 'lib/version@user/name'", self.t.out)
self.t.run("editable list")
assert "lib" not in self.t.out
def test_remove_path(self, client):
client.run("editable remove .")
assert "Removed editable 'lib/version@user/name':" in client.out
client.run("editable list")
assert "lib" not in client.out

def test_unlink_not_linked(self):
self.t.run('editable remove otherlib/version@user/name')
self.assertIn("Reference 'otherlib/version@user/name' was not installed as editable",
self.t.out)
self.t.run("editable list")
assert "lib" in self.t.out
def test_unlink_not_linked(self, client):
client.run('editable remove -r=otherlib/version@user/name')
assert "WARN: No editables were removed" in client.out
client.run("editable list")
assert "lib" in client.out
Loading