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

validate_build over settings #11580

Merged
merged 8 commits into from Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions conans/client/conan_command_output.py
Expand Up @@ -140,6 +140,10 @@ def _grab_info_data(self, deps_graph, grab_paths):
item_data["build_id"] = build_id(conanfile)
item_data["context"] = conanfile.context

item_data["invalid_build"] = node.cant_build is not False
if node.cant_build:
item_data["invalid_build_reason"] = node.cant_build

python_requires = getattr(conanfile, "python_requires", None)
if python_requires and not isinstance(python_requires, dict): # no old python requires
item_data["python_requires"] = [repr(r)
Expand Down
2 changes: 2 additions & 0 deletions conans/client/graph/graph.py
Expand Up @@ -91,6 +91,8 @@ def __init__(self, ref, conanfile, context, recipe=None, path=None):
self.id_direct_prefs = None
self.id_indirect_prefs = None

self.cant_build = False # It will set to a str with a reason if the validate_build() fails

@property
def id(self):
return self._id
Expand Down
22 changes: 18 additions & 4 deletions conans/client/graph/graph_binaries.py
Expand Up @@ -48,7 +48,10 @@ def _evaluate_build(node, build_mode):
break
if build_mode.forced(conanfile, ref, with_deps_to_build):
conanfile.output.info('Forced build from source')
node.binary = BINARY_BUILD
if node.cant_build:
node.binary = BINARY_INVALID
else:
node.binary = BINARY_BUILD
node.prev = None
return True

Expand Down Expand Up @@ -178,7 +181,10 @@ def _evaluate_node(self, node, build_mode, update, remotes):
pref = PackageReference(locked.ref, locked.package_id, locked.prev) # Keep locked PREV
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.cant_build:
node.binary = BINARY_INVALID
else:
node.binary = BINARY_BUILD
if node.binary == BINARY_BUILD:
locked.unlock_prev()

Expand Down Expand Up @@ -231,7 +237,10 @@ def _evaluate_node(self, node, build_mode, update, remotes):
if node.binary == BINARY_MISSING and node.package_id == PACKAGE_ID_INVALID:
node.binary = BINARY_INVALID
if node.binary == BINARY_MISSING and build_mode.allowed(node.conanfile):
node.binary = BINARY_BUILD
if node.cant_build:
node.binary = BINARY_INVALID
else:
node.binary = BINARY_BUILD

if locked:
# package_id was not locked, this means a base lockfile that is being completed
Expand All @@ -253,6 +262,8 @@ def _process_node(self, node, pref, build_mode, update, remotes):
node.binary = BINARY_INVALID
return



if self._evaluate_build(node, build_mode):
return

Expand Down Expand Up @@ -293,7 +304,10 @@ def _process_node(self, node, pref, build_mode, update, remotes):
local_recipe_hash = package_layout.recipe_manifest().summary_hash
if local_recipe_hash != recipe_hash:
conanfile.output.info("Outdated package!")
node.binary = BINARY_BUILD
if node.cant_build:
node.binary = BINARY_INVALID
else:
node.binary = BINARY_BUILD
node.prev = None
else:
conanfile.output.info("Package is up to date")
Expand Down
10 changes: 9 additions & 1 deletion conans/client/graph/graph_builder.py
Expand Up @@ -3,7 +3,7 @@
from conans.client.conanfile.configure import run_configure_method
from conans.client.graph.graph import DepsGraph, Node, RECIPE_EDITABLE, CONTEXT_HOST, CONTEXT_BUILD
from conans.errors import (ConanException, ConanExceptionInUserConanfileMethod,
conanfile_exception_formatter)
conanfile_exception_formatter, ConanInvalidConfiguration)
from conans.model.conan_file import get_env_context_manager
from conans.model.ref import ConanFileReference
from conans.model.requires import Requirements, Requirement
Expand Down Expand Up @@ -471,4 +471,12 @@ def _create_new_node(self, current_node, dep_graph, requirement, check_updates,

dep_graph.add_node(new_node)
dep_graph.add_edge(current_node, new_node, requirement)

if hasattr(dep_conanfile, "validate_build") and callable(dep_conanfile.validate_build):
lasote marked this conversation as resolved.
Show resolved Hide resolved
with conanfile_exception_formatter(str(dep_conanfile), "validate_build"):
try:
dep_conanfile.validate_build()
except ConanInvalidConfiguration as e:
new_node.cant_build = str(e)

return new_node
3 changes: 2 additions & 1 deletion conans/client/graph/graph_manager.py
Expand Up @@ -9,7 +9,7 @@
from conans.client.graph.graph_binaries import RECIPE_CONSUMER, RECIPE_VIRTUAL, BINARY_EDITABLE, \
BINARY_UNKNOWN
from conans.client.graph.graph_builder import DepsGraphBuilder
from conans.errors import ConanException, conanfile_exception_formatter
from conans.errors import ConanException, conanfile_exception_formatter, ConanInvalidConfiguration
lasote marked this conversation as resolved.
Show resolved Hide resolved
from conans.model.conan_file import get_env_context_manager
from conans.model.graph_info import GraphInfo
from conans.model.graph_lock import GraphLock, GraphLockFile
Expand Down Expand Up @@ -124,6 +124,7 @@ def load_graph(self, reference, create_reference, graph_info, build_mode, check_
root_node = self._load_root_node(reference, create_reference, profile_host, graph_lock,
root_ref, lockfile_node_id, is_build_require,
require_overrides)

deps_graph = self._resolve_graph(root_node, profile_host, profile_build, graph_lock,
build_mode, check_updates, update, remotes, recorder,
apply_build_requires=apply_build_requires)
Expand Down
8 changes: 7 additions & 1 deletion conans/client/installer.py
Expand Up @@ -423,7 +423,13 @@ def _build(self, nodes_by_level, keep_build, root_node, profile_host, profile_bu
if invalid:
msg = ["There are invalid packages (packages that cannot exist for this configuration):"]
for node in invalid:
msg.append("{}: Invalid ID: {}".format(node.conanfile, node.conanfile.info.invalid))
if node.cant_build:
msg.append("{}: Cannot build "
"for this configuration: {}".format(node.conanfile,
node.cant_build))
else:
msg.append("{}: Invalid ID: {}".format(node.conanfile,
node.conanfile.info.invalid))
raise ConanInvalidConfiguration("\n".join(msg))
self._raise_missing(missing)
processed_package_refs = {}
Expand Down
54 changes: 54 additions & 0 deletions conans/test/integration/graph/test_validate_build.py
@@ -0,0 +1,54 @@
import json
import textwrap

from conans.test.utils.tools import TestClient


def test_basic_validate_build_test():

t = TestClient()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conans.errors import ConanInvalidConfiguration

class myConan(ConanFile):
name = "foo"
version = "1.0"
settings = "os", "arch", "compiler"

def validate_build(self):
if self.settings.compiler == "gcc":
raise ConanInvalidConfiguration("This doesn't build in GCC")

def package_id(self):
del self.info.settings.compiler
""")

settings_gcc = "-s compiler=gcc -s compiler.libcxx=libstdc++11 -s compiler.version=11"
settings_clang = "-s compiler=clang -s compiler.libcxx=libc++ -s compiler.version=8"

t.save({"conanfile.py": conanfile})
t.run(f"create . {settings_gcc}", assert_error=True)

assert "foo/1.0: Cannot build for this configuration: This doesn't build in GCC" in t.out

t.run(f"create . {settings_clang}")

# Now with GCC again, but now we have the binary, we don't need to build, so it doesn't fail
t.run(f"create . {settings_gcc} --build missing")
assert "foo/1.0: Already installed!" in t.out

# But if I force the build... it will fail
t.run(f"create . {settings_gcc} ", assert_error=True)
assert "foo/1.0: Cannot build for this configuration: This doesn't build in GCC" in t.out

# What happens with a conan info?
t.run(f"info foo/1.0@ {settings_gcc} --json=myjson")
myjson = json.loads(t.load("myjson"))
assert myjson[0]["invalid_build"] is True
assert myjson[0]["invalid_build_reason"] == "This doesn't build in GCC"

t.run(f"info foo/1.0@ {settings_clang} --json=myjson")
myjson = json.loads(t.load("myjson"))
assert myjson[0]["invalid_build"] is False
assert "invalid_build_reason" not in myjson[0]