-
Notifications
You must be signed in to change notification settings - Fork 989
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
subgraph with test and cyclone as tool and test
- Loading branch information
Showing
6 changed files
with
253 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
|
||
def cyclonedx_1_4(graph, **kwargs): | ||
import uuid | ||
import time | ||
from datetime import datetime, timezone | ||
|
||
has_special_root_node = not (getattr(graph.root.ref, "name", False) and getattr(graph.root.ref, "version", False) and getattr(graph.root.ref, "revision", False)) | ||
special_id = str(uuid.uuid4()) | ||
|
||
components = [node for node in graph.nodes] | ||
if has_special_root_node: | ||
components = components[1:] | ||
|
||
dependencies = [] | ||
if has_special_root_node: | ||
deps = {"ref": special_id, | ||
"dependsOn": [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" | ||
for d in graph.root.dependencies]} | ||
dependencies.append(deps) | ||
for c in components: | ||
deps = {"ref": f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}"} | ||
depends_on = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in c.dependencies] | ||
if depends_on: | ||
deps["dependsOn"] = depends_on | ||
dependencies.append(deps) | ||
|
||
def _calculate_licenses(component): | ||
if isinstance(component.conanfile.license, str): # Just one license | ||
return [{"license": { | ||
"id": component.conanfile.license | ||
}}] | ||
return [{"license": { | ||
"id": l | ||
}} for l in c.conanfile.license] | ||
|
||
sbom_cyclonedx_1_4 = { | ||
**({"components": [{ | ||
"author": "Conan", | ||
"bom-ref": special_id if has_special_root_node else f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}", | ||
"description": c.conanfile.description, | ||
**({"externalReferences": [{ | ||
"type": "website", | ||
"url": c.conanfile.homepage | ||
}]} if c.conanfile.homepage else {}), | ||
**({"licenses": _calculate_licenses(c)} if c.conanfile.license else {}), | ||
"name": c.name, | ||
"fpurl": f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}", | ||
"type": "library", | ||
"version": str(c.ref.version), | ||
} for c in components]} if components else {}), | ||
**({"dependencies": dependencies} if dependencies else {}), | ||
"metadata": { | ||
"component": { | ||
"author": "Conan", | ||
"bom-ref": special_id if has_special_root_node else f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}", | ||
"name": graph.root.conanfile.display_name, | ||
"type": "library" | ||
}, | ||
"timestamp": f"{datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}", | ||
"tools": [{ | ||
"externalReferences": [{ | ||
"type": "website", | ||
"url": "https://github.com/conan-io/conan" | ||
}], | ||
"name": "Conan-io" | ||
}], | ||
}, | ||
"serialNumber": f"urn:uuid:{uuid.uuid4()}", | ||
"bomFormat": "CycloneDX", | ||
"specVersion": "1.4", | ||
"version": 1, | ||
} | ||
return sbom_cyclonedx_1_4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import textwrap | ||
|
||
import pytest | ||
|
||
from conan.test.assets.genconanfile import GenConanfile | ||
from conan.test.utils.tools import TestClient | ||
from conans.util.files import save | ||
import os | ||
|
||
sbom_hook = """ | ||
import json | ||
import os | ||
from conan.errors import ConanException | ||
from conan.api.output import ConanOutput | ||
from conan.tools.sbom.cycloneDX import cyclonedx_1_4 | ||
def _generate_cyclonedx_1_4_file(conanfile): | ||
try: | ||
sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph) | ||
metadata_folder = conanfile.package_metadata_folder | ||
file_name = "cyclonedx_1_4.json" | ||
with open(os.path.join(metadata_folder, file_name), 'w') as f: | ||
json.dump(sbom_cyclonedx_1_4, f, indent=4) | ||
ConanOutput().success(f"CYCLONEDX CREATED - {conanfile.package_metadata_folder}") | ||
except Exception as e: | ||
ConanException("error generating CYCLONEDX file") | ||
def post_package(conanfile): | ||
_generate_cyclonedx_1_4_file(conanfile) | ||
def post_generate(conanfile): | ||
_generate_cyclonedx_1_4_file(conanfile) | ||
""" | ||
|
||
@pytest.fixture() | ||
def hook_setup(): | ||
tc = TestClient() | ||
hook_path = os.path.join(tc.paths.hooks_path, "hook_sbom.py") | ||
save(hook_path, sbom_hook) | ||
return tc | ||
|
||
def test_sbom_generation_create(hook_setup): | ||
tc = hook_setup | ||
tc.run("new cmake_lib -d name=dep -d version=1.0") | ||
tc.run("export .") | ||
tc.run("new cmake_lib -d name=foo -d version=1.0 -d requires=dep/1.0 -f") | ||
tc.run("export .") | ||
tc.run("new cmake_lib -d name=bar -d version=1.0 -d requires=foo/1.0 -f") | ||
# bar -> foo -> dep | ||
tc.run("create . --build=missing") | ||
bar_layout = tc.created_layout() | ||
assert os.path.exists(os.path.join(bar_layout.build(),"..", "d", "metadata", "cyclonedx_1_4.json")) | ||
|
||
def test_sbom_generation_install_requires(hook_setup): | ||
tc = hook_setup | ||
tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), | ||
"conanfile.py": GenConanfile("foo", "1.0").with_requires("dep/1.0")}) | ||
tc.run("export dep") | ||
tc.run("create . --build=missing") | ||
|
||
#cli -> foo -> dep | ||
tc.run("install --requires=foo/1.0") | ||
assert os.path.exists(os.path.join(tc.current_folder, "cyclonedx_1_4.json")) | ||
|
||
def test_sbom_generation_install_path(hook_setup): | ||
tc = hook_setup | ||
tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), | ||
"conanfile.py": GenConanfile("foo", "1.0").with_requires("dep/1.0")}) | ||
tc.run("create dep") | ||
|
||
#foo -> dep | ||
tc.run("install .") | ||
assert os.path.exists(os.path.join(tc.current_folder, "cyclonedx_1_4.json")) | ||
|
||
def test_sbom_generation_install_path_consumer(hook_setup): | ||
tc = hook_setup | ||
tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), | ||
"conanfile.py": GenConanfile().with_requires("dep/1.0")}) | ||
tc.run("create dep") | ||
|
||
#conanfile.py -> dep | ||
tc.run("install .") | ||
assert os.path.exists(os.path.join(tc.current_folder, "cyclonedx_1_4.json")) | ||
|
||
def test_sbom_generation_install_path_txt(hook_setup): | ||
tc = hook_setup | ||
tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), | ||
"conanfile.txt": textwrap.dedent( | ||
""" | ||
[requires] | ||
dep/1.0 | ||
""" | ||
)}) | ||
tc.run("create dep") | ||
|
||
#foo -> dep | ||
tc.run("install .") | ||
assert os.path.exists(os.path.join(tc.current_folder, "cyclonedx_1_4.json")) | ||
|
||
def test_sbom_generation_skipped_dependencies(hook_setup): | ||
tc = hook_setup | ||
tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), | ||
"app/conanfile.py": GenConanfile("app", "1.0") | ||
.with_package_type("application") | ||
.with_requires("dep/1.0"), | ||
"conanfile.py": GenConanfile("foo", "1.0").with_tool_requires("app/1.0")}) | ||
tc.run("create dep") | ||
tc.run("create app") | ||
tc.run("create .") | ||
create_layout = tc.created_layout() | ||
|
||
cyclone_path = os.path.join(create_layout.build(), "..", "d", "metadata", "cyclonedx_1_4.json") | ||
content = tc.load(cyclone_path) | ||
# A skipped dependency also shows up in the sbom | ||
assert "pkg:conan/dep@1.0?rref=6a99f55e933fb6feeb96df134c33af44" in content |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import json | ||
import os | ||
import textwrap | ||
|
||
from conan.test.assets.genconanfile import GenConanfile | ||
from conan.test.utils.tools import TestClient | ||
from conans.util.files import load | ||
|
||
|
||
def test_subgraph_reports(): | ||
c = TestClient() | ||
subgraph_hook = textwrap.dedent("""\ | ||
import os, json | ||
from conan.tools.files import save | ||
from conans.model.graph_lock import Lockfile | ||
def post_package(conanfile): | ||
subgraph = conanfile.subgraph | ||
save(conanfile, os.path.join(conanfile.package_folder, "..", "..", f"{conanfile.name}-conangraph.json"), | ||
json.dumps(subgraph.serialize(), indent=2)) | ||
save(conanfile, os.path.join(conanfile.package_folder, "..", "..", f"{conanfile.name}-conan.lock"), | ||
Lockfile(subgraph).dumps()) | ||
""") | ||
|
||
c.save_home({"extensions/hooks/subgraph_hook/hook_subgraph.py": subgraph_hook}) | ||
c.save({"dep/conanfile.py": GenConanfile("dep", "0.1"), | ||
"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_requirement("dep/0.1"), | ||
"app/conanfile.py": GenConanfile("app", "0.1").with_requirement("pkg/0.1")}) | ||
c.run("export dep") | ||
c.run("export pkg") | ||
# app -> pkg -> dep | ||
c.run("create app --build=missing --format=json") | ||
|
||
app_graph = json.loads(load(os.path.join(c.cache.builds_folder, "app-conangraph.json"))) | ||
pkg_graph = json.loads(load(os.path.join(c.cache.builds_folder, "pkg-conangraph.json"))) | ||
dep_graph = json.loads(load(os.path.join(c.cache.builds_folder, "dep-conangraph.json"))) | ||
|
||
app_lock = json.loads(load(os.path.join(c.cache.builds_folder, "app-conan.lock"))) | ||
pkg_lock = json.loads(load(os.path.join(c.cache.builds_folder, "pkg-conan.lock"))) | ||
dep_lock = json.loads(load(os.path.join(c.cache.builds_folder, "dep-conan.lock"))) | ||
|
||
assert len(app_graph["nodes"]) == len(app_lock["requires"]) | ||
assert len(pkg_graph["nodes"]) == len(pkg_lock["requires"]) | ||
assert len(dep_graph["nodes"]) == len(dep_lock["requires"]) |