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

fix: fix build-info error on binary skip #167

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 12 additions & 3 deletions extensions/commands/art/cmd_build_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path

from conan.api.conan_api import ConanAPI
from conan.api.output import cli_out_write
from conan.api.output import cli_out_write, ConanOutput
from conan.cli.command import conan_command, conan_subcommand
from conan.errors import ConanException
from conans.model.recipe_ref import RecipeReference
Expand Down Expand Up @@ -142,6 +142,14 @@ def get_artifacts(self, node, artifact_type, is_dependency=False):

def _get_local_artifacts():
local_artifacts = []
missing_artifacts = []
artifacts_folder = node.get("package_folder") if artifact_type == "package" else node.get("recipe_folder")
if artifacts_folder is None and artifact_type == "package" and node.get("binary") == "Skip":
ConanOutput().warning(f"Package marked as 'Skip' for {node.get('ref')}. "
"Using -c a:tools.graph:skip_binaries=False might force its download for the "
"conan create/install command so it can be included in the Build Info.")
return (local_artifacts, missing_artifacts)

artifacts_folder = Path(node.get("package_folder")) if artifact_type == "package" else Path(node.get("recipe_folder"))
dl_folder = artifacts_folder.parents[0] / "d"
dl_folder_files = [file for file in dl_folder.glob("*") if file.name in artifacts_names]
Expand All @@ -168,7 +176,7 @@ def _get_local_artifacts():
artifact_info.update({"id": f"{ref}{pkg} :: {file_name}"})

local_artifacts.append(artifact_info)

missing_files = set(artifacts_names) - processed_files
return (local_artifacts, missing_files)

Expand Down Expand Up @@ -219,7 +227,8 @@ def _get_remote_artifacts(artifact):
if sources_artifact:
artifacts.append(sources_artifact)

if not artifacts:
folder = node.get("package_folder") if artifact_type == "package" else node.get("recipe_folder")
if not artifacts and folder:
raise ConanException(f"There are missing artifacts for the {node.get('ref')} {artifact_type}. "
"Check that you have all the packages installed in the Conan cache when creating the Build Info.")

Expand Down
132 changes: 132 additions & 0 deletions tests/test_build_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import json
import tempfile
import textwrap
import os

import pytest

from tools import load, save, replace_in_file, run


@pytest.fixture(autouse=True)
def conan_test():
old_env = dict(os.environ)
conan_home = tempfile.mkdtemp(suffix='conans')
env_vars = {"CONAN_HOME": conan_home}
os.environ.update(env_vars)
current = tempfile.mkdtemp(suffix="conans")
cwd = os.getcwd()
os.chdir(current)

repo = os.path.join(os.path.dirname(__file__), "..")
run(f"conan config install {repo}")
run("conan profile detect")
_patch_cmd_build_info_get_remote_artifacts(conan_home)
try:
yield
finally:
os.chdir(cwd)
os.environ.clear()
os.environ.update(old_env)


def _patch_cmd_build_info_get_remote_artifacts(conan_home):
# Patch _get_remote_artifacts() function in cmd_build_info.py file to avoid contacting Artifactory for the test
custom_commands_cmd_build_info_path = os.path.join(conan_home, "extensions", "commands", "art", "cmd_build_info.py")
replace_in_file(custom_commands_cmd_build_info_path, "def _get_remote_artifacts(artifact):",
"def _get_remote_artifacts(artifact):\n"
" return None\n"
" def _patched(artifact):")


def test_static_library_skip_binaries():
"""
Test skip binaries behavior with static and shared libraries
"""
run("conan new cmake_lib -d name=liba -d version=1.0")
conanfile = load("conanfile.py")
conanfile = conanfile.replace('package_type = "library"', 'package_type = "static-library"')
save("conanfile.py", conanfile)
run("conan create .")

run("conan new cmake_lib -d name=libb -d version=1.0 -d requires=liba/1.0 --force")
conanfile = load("conanfile.py")
conanfile = conanfile.replace('package_type = "library"', 'package_type = "shared-library"')
save("conanfile.py", conanfile)
run("conan create . -o libb/*:shared=True")

run("conan new cmake_lib -d name=libc -d version=1.0 -d requires=libb/1.0 --force")

run("conan create . -o libb/1.0:shared=True -f json > create.json")
graph = json.loads(load("create.json"))["graph"]
assert graph["nodes"]["3"]["binary"] == "Skip"
assert graph["nodes"]["3"]["package_folder"] is None

out = run("conan art:build-info create create.json build_name 1 repo --with-dependencies > bi.json")
assert "WARN: Package marked as 'Skip' for liba/1.0" in out
build_info = json.loads(load("bi.json"))
assert "libc/1.0" in build_info["modules"][1]["id"].split(":")[0] # libc package
# Remove package id and rrev leave them as "<name>/<version> :: <file>" to make the asserts platform agnostic
dependencies_ids = [f"{dep['id'].split('#')[0]} ::{dep['id'].split('::')[-1]}" for dep in build_info["modules"][1]["dependencies"]]
assert len(dependencies_ids) == 2 # liba package files are not present as binary is skipped
assert "libb/1.0 :: conaninfo.txt" in dependencies_ids
assert "libb/1.0 :: conanmanifest.txt" in dependencies_ids

run("conan create . -o libb/1.0:shared=True -c tools.graph:skip_binaries=False -f json > create.json")
graph = json.loads(load("create.json"))["graph"]
assert graph["nodes"]["3"]["binary"] == "Cache"
assert graph["nodes"]["3"]["package_folder"] is not None
out = run("conan art:build-info create create.json build_name 1 repo --with-dependencies > bi.json")
assert "WARN: Package marked as 'Skip' for liba/1.0" not in out
build_info = json.loads(load("bi.json"))
assert "libc/1.0#95c7f81c13006116d5f1abc3f58af7f8" in build_info["modules"][1]["id"].split(":")[0] # libc package
# Remove package id and rrev leave them as "<name>/<version>#rrev :: <file>" to make the asserts platform agnostic
dependencies_ids = [f"{dep['id'].split('#')[0]} ::{dep['id'].split('::')[-1]}" for dep in build_info["modules"][1]["dependencies"]]
assert len(dependencies_ids) == 4 # liba package files now should be present
assert "libb/1.0 :: conaninfo.txt" in dependencies_ids
assert "libb/1.0 :: conanmanifest.txt" in dependencies_ids
assert "liba/1.0 :: conaninfo.txt" in dependencies_ids
assert "liba/1.0 :: conanmanifest.txt" in dependencies_ids


def test_tool_require_skip_binaries():
"""
Test skip binaries behavior on tool require added as cache dependency
"""
conanfile = textwrap.dedent("""
from conan import ConanFile

class Meson(ConanFile):
name = "meson"
version = 1.0
package_type = "application"

def package_info(self):
self.cpp_info.includedirs = []
self.cpp_info.libdirs = []
self.cpp_info.bindirs = []
""")
save(os.path.join(os.curdir, "conanfile.py"), conanfile)
run("conan create .")

run("conan new cmake_lib -d name=libb -d version=1.0 --force")
conanfile = load("conanfile.py")
conanfile = conanfile.replace('package_type = "library"',
'package_type = "static-library"\n tool_requires = "meson/1.0"')
save("conanfile.py", conanfile)
run("conan create .")

run("conan new cmake_lib -d name=libc -d version=1.0 -d requires=libb/1.0 --force")

run("conan create . -f json > create.json")
graph = json.loads(load("create.json"))["graph"]
assert graph["nodes"]["3"]["binary"] == "Skip"
assert graph["nodes"]["3"]["package_folder"] is None
out = run("conan art:build-info create create.json build_name 1 repo --add-cached-deps --with-dependencies > bi.json")
assert "WARN: Package marked as 'Skip' for meson/1.0" in out
bi = load("bi.json")
build_info = json.loads(bi)
# Check libb recipe depends on meson recipe
assert "meson" in build_info["modules"][2]["dependencies"][0]["id"]
# Check libb package has no dependencies (meson package not in cache bc is was skipped as not needed)
assert len(build_info["modules"][3]["dependencies"]) == 0
5 changes: 5 additions & 0 deletions tests/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ def save(f, content):
def load(f):
with open(f, "r") as f:
return f.read()

def replace_in_file(f, original_text, new_text):
content = load(f)
content = content.replace(original_text, new_text)
save(f, content)
Loading