Skip to content

Commit

Permalink
allow relaxing a lockfile with --build (#8054)
Browse files Browse the repository at this point in the history
* allow relaxing a lockfile with --build

* fix test

* improved

* fix test

* fixing more tests

* duplicated test

* fix test
  • Loading branch information
memsharded committed Nov 18, 2020
1 parent 6285337 commit edc52f3
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 21 deletions.
5 changes: 2 additions & 3 deletions conans/client/graph/graph_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,8 @@ def _evaluate_node(self, node, build_mode, update, remotes):
self._process_node(node, pref, build_mode, update, remotes)
if node.binary == BINARY_MISSING and build_mode.allowed(node.conanfile):
node.binary = BINARY_BUILD
if node.binary == BINARY_BUILD and locked.prev:
raise ConanException("Cannot build '%s' because it is already locked in the input "
"lockfile" % repr(node.ref))
if node.binary == BINARY_BUILD:
locked.unlock_prev()
else:
assert node.prev is None, "Non locked node shouldn't have PREV in evaluate_node"
assert node.binary is None, "Node.binary should be None if not locked"
Expand Down
10 changes: 10 additions & 0 deletions conans/model/graph_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,16 @@ def prev(self, value):
self._modified = True # Only for conan_build_info
self._prev = value

def unlock_prev(self):
""" for creating a new lockfile from an existing one, when specifying --build, it
should make prev=None in order to unlock it and allow building again"""
if self._prev is None:
return # Already unlocked
if not self._relaxed:
raise ConanException("Cannot build '%s' because it is already locked in the "
"input lockfile" % repr(self._ref))
self._prev = None

def complete_base_node(self, package_id, prev):
# completing a node from a base lockfile shouldn't mark the node as modified
self.package_id = package_id
Expand Down
25 changes: 15 additions & 10 deletions conans/test/functional/graph_lock/build_order_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ def test_build_locked_error(self):
self.assertEqual([], jsonbo)
# if we try to build anyway, error
client.run("install test4/0.1@ --lockfile=conan.lock --build", assert_error=True)
self.assertIn("Cannot build 'test4/0.1#f876ec9ea0f44cb7adb1588e431b391a' because it is "
"already locked in the input lockfile", client.out)
rev = "#f876ec9ea0f44cb7adb1588e431b391a" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'test4/0.1{}' because it is "
"already locked in the input lockfile".format(rev), client.out)

@parameterized.expand([(True,), (False,)])
def test_transitive_build_not_locked(self, export):
Expand Down Expand Up @@ -166,8 +167,9 @@ def test_transitive_build_not_locked(self, export):
self.assertEqual(build_order[1:], jsonbo)

client.run("install pkg/0.1@ --lockfile=conan.lock --build", assert_error=True)
self.assertIn("Cannot build 'dep/0.1#f3367e0e7d170aa12abccb175fee5f97' because it is "
"already locked in the input lockfile", client.out)
rev = "#f3367e0e7d170aa12abccb175fee5f97" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'dep/0.1{}' because it is "
"already locked in the input lockfile".format(rev), client.out)
client.run("install {0} --lockfile=conan.lock --lockfile-out=conan.lock "
"--build={0}".format(build_order[1][0][0]))
self.assertIn("dep/0.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache", client.out)
Expand All @@ -185,8 +187,9 @@ def test_transitive_build_not_locked(self, export):
self.assertEqual(build_order[2:], jsonbo)

client.run("install app/0.1@ --lockfile=conan.lock --build", assert_error=True)
self.assertIn("Cannot build 'dep/0.1#f3367e0e7d170aa12abccb175fee5f97' because it is "
"already locked in the input lockfile", client.out)
rev = "#f3367e0e7d170aa12abccb175fee5f97" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'dep/0.1{}' because it is "
"already locked in the input lockfile".format(rev), client.out)
client.run("install {0} --lockfile=conan.lock --lockfile-out=conan.lock "
"--build={0}".format(build_order[2][0][0]))
self.assertIn("dep/0.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache", client.out)
Expand Down Expand Up @@ -329,8 +332,9 @@ def test_transitive_build_not_locked(self, export):
self.assertEqual(build_order[1:], jsonbo)

client.run("install pkg/0.1@ --lockfile=conan.lock --build", assert_error=True)
self.assertIn("Cannot build 'dep/0.1#f3367e0e7d170aa12abccb175fee5f97' because it is "
"already locked in the input lockfile", client.out)
rrev = "#f3367e0e7d170aa12abccb175fee5f97" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'dep/0.1{}' because it is "
"already locked in the input lockfile".format(rrev), client.out)
client.run("install {0} --lockfile=conan.lock --lockfile-out=conan.lock "
"--build={0}".format(build_order[1][0][0]))
self.assertIn("dep/0.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Cache", client.out)
Expand All @@ -348,8 +352,9 @@ def test_transitive_build_not_locked(self, export):
self.assertEqual(build_order[2:], jsonbo)

client.run("install app/0.1@ --lockfile=conan.lock --build", assert_error=True)
self.assertIn("Cannot build 'pkg/0.1#1364f701b47130c7e38f04c5e5fab985' because it is "
"already locked in the input lockfile", client.out)
rrev = "#1364f701b47130c7e38f04c5e5fab985" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'pkg/0.1{}' because it is "
"already locked in the input lockfile".format(rrev), client.out)
client.run("install {0} --lockfile=conan.lock --lockfile-out=conan.lock "
"--build={0}".format(build_order[2][0][0]))
self.assertNotIn("dep/0.1", client.out)
Expand Down
43 changes: 40 additions & 3 deletions conans/test/functional/graph_lock/dynamic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def test_add_dep(self):
updated = client.load("updated.lock")
self.assertEqual(updated, new)

def augment_test_package_requires(self):
def test_augment_test_package_requires(self):
# https://github.com/conan-io/conan/issues/6067
client = TestClient()
client.save({"conanfile.py": GenConanfile("tool", "0.1")})
Expand Down Expand Up @@ -312,6 +312,43 @@ def test_partial_intermediate_package_lock(self):
client.run("create . LibB/1.1@ --lockfile=libb.lock")
self.assertIn("LibA/1.0 from local cache - Cache", client.out)

def test_relax_lockfile_to_build(self):
client = TestClient()
client.save({"conanfile.py": GenConanfile()})
client.run("create . LibA/1.0@")
client.save({"conanfile.py": GenConanfile().with_require("LibA/[>=1.0]")})
client.run("create . LibB/1.0@")
client.save({"conanfile.py": GenConanfile().with_require("LibB/[>=1.0]")})
client.run("create . LibC/1.0@")
client.run("lock create --reference=LibC/1.0 --lockfile-out=libc.lock")

# New version of LibA/1.0.1, that should never be used
client.save({"conanfile.py": GenConanfile()})
client.run("create . LibA/1.0.1@")

client.run("lock create --reference=LibC/1.0 --build=LibC --lockfile=libc.lock "
"--lockfile-out=libc2.lock")
libc2_json = json.loads(client.load("libc2.lock"))
libc = libc2_json["graph_lock"]["nodes"]["1"]
self.assertIn("LibC/1.0", libc["ref"])
self.assertIsNone(libc.get("prev"))
# Now it is possible to build it again
client.run("install LibC/1.0@ --build=LibC --lockfile=libc2.lock --lockfile-out=libc3.lock")
self.assertIn("LibC/1.0:3f278cfc7b3c4509db7f72c9bf2e472732c4f69f - Build", client.out)
self.assertIn("LibC/1.0: Created package", client.out)
libc3_json = json.loads(client.load("libc3.lock"))
libc = libc3_json["graph_lock"]["nodes"]["1"]
self.assertIn("LibC/1.0", libc["ref"])
self.assertIsNotNone(libc.get("prev"))

# Now unlock/build everything
client.run("lock create --reference=LibC/1.0 --build --lockfile=libc3.lock "
"--lockfile-out=libc4.lock")
client.run("lock build-order libc4.lock")
self.assertIn("LibA/1.0@", client.out)
self.assertIn("LibB/1.0@", client.out)
self.assertIn("LibC/1.0@", client.out)


class PartialOptionsTest(unittest.TestCase):
"""
Expand All @@ -337,8 +374,8 @@ def setUp(self):
self.client = client

def test_partial_lock_option_command_line(self):
# When 'LibA:myoption' is set in command line, the option value is saved in the libb.lock and it is applied to all
# graph, overriding LibC.
# When 'LibA:myoption' is set in command line, the option value is saved in the
# libb.lock and it is applied to all graph, overriding LibC.
client = self.client
client.save({"conanfile.py": GenConanfile().with_require("LibA/1.0")})
client.run("create . LibB/1.0@ -o LibA:myoption=True")
Expand Down
11 changes: 7 additions & 4 deletions conans/test/functional/graph_lock/graph_lock_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,10 @@ def test_cannot_create_twice(self):
client.run("create . --lockfile=conan.lock --lockfile-out=conan.lock")
client.run("install PkgA/0.1@ --build=PkgA --lockfile=conan.lock --lockfile-out=conan.lock",
assert_error=True)
self.assertIn("Cannot build 'PkgA/0.1#fa090239f8ba41ad559f8e934494ee2a' because it is "
"already locked", client.out)
rev = "#fa090239f8ba41ad559f8e934494ee2a" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'PkgA/0.1{}' because it is already locked".format(rev),
client.out)

client.run("create . --lockfile=conan.lock --lockfile-out=conan.lock", assert_error=True)
self.assertIn("ERROR: Attempt to modify locked PkgA/0.1", client.out)

Expand Down Expand Up @@ -401,8 +403,9 @@ def test_lockfile_argument_updated_install(self):
previous_lock = client.load("somelib.lock")
# This should fail, because somelib is locked
client.run("install somelib/1.0@ --lockfile=somelib.lock --build somelib", assert_error=True)
self.assertIn("Cannot build 'somelib/1.0#f3367e0e7d170aa12abccb175fee5f97' because it "
"is already locked in the input lockfile", client.out)
rev = "#f3367e0e7d170aa12abccb175fee5f97" if client.cache.config.revisions_enabled else ""
self.assertIn("Cannot build 'somelib/1.0{}' because it "
"is already locked in the input lockfile".format(rev), client.out)
new_lock = client.load("somelib.lock")
self.assertEqual(previous_lock, new_lock)

Expand Down
2 changes: 1 addition & 1 deletion conans/test/unittests/util/client_conf_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def test_log_level_numbers_env_var_debug(self):
config = ConanClientConfigParser(os.path.join(self.tmp_dir, CONAN_CONF))
self.assertEqual(logging.DEBUG, config.logging_level)

def test_log_level_numbers_env_var_debug(self):
def test_log_level_numbers_env_var_debug_text(self):
with environment_append({"CONAN_LOGGING_LEVEL": "WakaWaka"}):
save(os.path.join(self.tmp_dir, CONAN_CONF), default_client_conf)
config = ConanClientConfigParser(os.path.join(self.tmp_dir, CONAN_CONF))
Expand Down

0 comments on commit edc52f3

Please sign in to comment.