Skip to content
Draft
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
5 changes: 3 additions & 2 deletions conan/api/subapi/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def editable_list(self):
return self._helpers.editable_packages.edited_refs

def source(self, path, name=None, version=None, user=None, channel=None,
remotes: List[Remote] = None):
remotes: List[Remote] = None, lockfile=None):
""" Calls the ``source()`` method of the current (user folder) ``conanfile.py``

This method does not require computing a dependency graph, because the ``source()``
Expand All @@ -114,10 +114,11 @@ def source(self, path, name=None, version=None, user=None, channel=None,
:param user: The user of the package. If not defined, it is taken from conanfile
:param channel: The channel of the package. If not defined, it is taken from conanfile
:param remotes: The remotes to resolve possible ``python-requires`` for this recipe if needed.
:param lockfile: The lockfile to use for the ``python-requires`` resolution, if needed.
"""
loader = self._helpers.loader
conanfile = loader.load_consumer(path, name=name, version=version,
user=user, channel=channel, graph_lock=None,
user=user, channel=channel, graph_lock=lockfile,
remotes=remotes)
# This profile is empty, but with the conf from global.conf
profile = self._conan_api.profiles.get_profile([])
Expand Down
14 changes: 11 additions & 3 deletions conan/cli/commands/source.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os

from conan.cli.command import conan_command
from conan.cli.command import conan_command, OnceArgument
from conan.cli.args import add_reference_args


Expand All @@ -12,12 +12,20 @@ def source(conan_api, parser, *args):
parser.add_argument("path", help="Path to a folder containing a conanfile.py. "
"Defaults to current directory",
default=".", nargs="?")
parser.add_argument("-l", "--lockfile", action=OnceArgument,
help="Path to a lockfile for python-requires resolution. Use "
"--lockfile=\"\" to avoid automatic use of existing 'conan.lock' file")
parser.add_argument("--lockfile-partial", action="store_true",
help="Do not raise an error if some dependency is not found in lockfile")
add_reference_args(parser)
args = parser.parse_args(*args)

cwd = os.getcwd()
path = conan_api.local.get_conanfile_path(args.path, cwd, py=True)
enabled_remotes = conan_api.remotes.list() # for python_requires not local
# TODO: Missing lockfile for python_requires
lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile,
conanfile_path=path,
cwd=cwd,
partial=args.lockfile_partial)
conan_api.local.source(path, name=args.name, version=args.version, user=args.user,
channel=args.channel, remotes=enabled_remotes)
channel=args.channel, remotes=enabled_remotes, lockfile=lockfile)
33 changes: 33 additions & 0 deletions test/integration/lockfile/test_lock_pyrequires.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ class MyConanfileBase(ConanFile):
assert "dep/0.1" not in client.out


def test_source_should_respect_lockfile_pyrequires():
# "conan source" currently does not resolve python_requires against a lockfile the same way
# "install"/"create"/"export"/"build" do
client = TestClient(light=True)
consumer = textwrap.dedent("""
from conan import ConanFile
class Consumer(ConanFile):
python_requires = "dep/[>0.0]@user/channel"
def source(self):
v = self.python_requires["dep"].ref.version
self.output.info("SOURCE DEP VERSION: {}".format(v))
""")
client.save({"dep/conanfile.py": GenConanfile(),
"conanfile.py": consumer})

client.run("export dep --name=dep --version=0.1 --user=user --channel=channel")
client.run("lock create conanfile.py") # writes conan.lock, locks dep/0.1
lockfile = os.path.join(client.current_folder, "conan.lock")
assert os.path.isfile(lockfile) # the lock is indeed present when "source" runs below

client.run("export dep --name=dep --version=0.2 --user=user --channel=channel")

# "install" auto-discovers "conan.lock" and correctly keeps resolving dep/0.1
client.run("install conanfile.py")
assert "dep/0.1@user/channel" in client.out
assert "dep/0.2" not in client.out

# "source" should behave the same way and also resolve the locked dep/0.1
client.run("source conanfile.py")
# This fails, it currently resolves to dep/0.2
assert "SOURCE DEP VERSION: 0.1" in client.out


def test_transitive_matching_ranges():
client = TestClient(light=True)
tool = textwrap.dedent("""
Expand Down
Loading