From c2bceceffb78f65be816627c10c82edab0c22b9c Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Wed, 23 Oct 2024 09:47:12 +0200 Subject: [PATCH 01/28] initial --- conan/api/subapi/graph.py | 19 +++++++++++++++ conan/cli/args.py | 19 ++++++++++++++- conan/cli/commands/graph.py | 39 +++++++++++++++++++++++++++++- conan/internal/cache/home_paths.py | 4 +++ conans/client/graph/sbom.py | 19 +++++++++++++++ conans/client/migrations.py | 3 +++ 6 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 conans/client/graph/sbom.py diff --git a/conan/api/subapi/graph.py b/conan/api/subapi/graph.py index b6ff4f844de..4dc21ee45bb 100644 --- a/conan/api/subapi/graph.py +++ b/conan/api/subapi/graph.py @@ -1,4 +1,7 @@ +import os + from conan.api.output import ConanOutput +from conan.internal.cache.home_paths import HomePaths from conan.internal.conan_app import ConanApp from conans.client.graph.graph import Node, RECIPE_CONSUMER, CONTEXT_HOST, RECIPE_VIRTUAL, \ CONTEXT_BUILD @@ -6,6 +9,7 @@ from conans.client.graph.graph_builder import DepsGraphBuilder from conans.client.graph.profile_node_definer import initialize_conanfile_profile, consumer_definer from conan.errors import ConanException +from conans.client.loader import load_python_file from conans.model.recipe_ref import RecipeReference @@ -200,3 +204,18 @@ def analyze_binaries(self, graph, build_mode=None, remotes=None, update=None, lo binaries_analyzer = GraphBinariesAnalyzer(conan_app, self.conan_api.config.global_conf) binaries_analyzer.evaluate_graph(graph, build_mode, lockfile, remotes, update, build_modes_test, tested_graph) + + def sbom(self, graph, manifest): + ConanOutput().warning(f"generating sbom for {manifest} format") + sbom_manifest = HomePaths(self.conan_api.cache_folder).sbom_manifest_plugin_path + chosen_manifest_path = os.path.join(sbom_manifest, f"{manifest}.py") + mod, _ = load_python_file(chosen_manifest_path) + + if not hasattr(mod, "generate_sbom"): + raise ConanException( + f"SBOM manifest plugin '{manifest}' does not have 'generate_sbom' method") + if not callable(mod.generate_sbom): + raise ConanException( + f"SBOM manifest plugin '{manifest}' 'generate_sbom' is not a function") + + return mod.generate_sbom(self.conan_api, graph.serialize()) diff --git a/conan/cli/args.py b/conan/cli/args.py index d25b2c2927b..ccede230b5e 100644 --- a/conan/cli/args.py +++ b/conan/cli/args.py @@ -1,7 +1,9 @@ import argparse +import os from conan.cli.command import OnceArgument from conan.errors import ConanException +from conan.internal.cache.home_paths import HomePaths _help_build_policies = '''Optional, specify which packages to build from source. Combining multiple '--build' options on one command line is allowed. @@ -80,7 +82,7 @@ def create_config(short, long, example=None): f'By default, or if specifying -{short}:h (--{long}:host), it applies to the host context. ' f'Use -{short}:b (--{long}:build) to specify the build context, ' f'or -{short}:a (--{long}:all) to specify both contexts at once' - + ('' if not example else f". Example: {example}")) + + ('' if not example else f". Example: {example}")) for context in contexts: parser.add_argument(f"-{short}:{context[0]}", f"--{long}:{context}", default=None, @@ -137,3 +139,18 @@ def validate_common_graph_args(args): f"[path] '{args.path}' argument") if not args.path and args.build_require: raise ConanException("--build-require should only be used with argument") + + +def sbom_graph_args(parser, conan_api): + sbom_manifest = HomePaths(conan_api.cache_folder).sbom_manifest_plugin_path + choices = [] + if os.path.exists(sbom_manifest): + if not os.path.isdir(sbom_manifest): + raise ConanException(f"SBOM manifest plugin path '{sbom_manifest}' is not a directory") + + for manifest_format in os.listdir(sbom_manifest): + if not manifest_format.startswith("_"): + choices.append(manifest_format.replace(".py", "")) + + parser.add_argument("--manifest", choices=choices, default="spdx", + help="Output format for the SBOM") diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py index aa1fb298c2e..1ef241626e0 100644 --- a/conan/cli/commands/graph.py +++ b/conan/cli/commands/graph.py @@ -4,19 +4,22 @@ from conan.api.model import ListPattern from conan.api.output import ConanOutput, cli_out_write, Color from conan.cli import make_abs_path -from conan.cli.args import common_graph_args, validate_common_graph_args +from conan.cli.args import common_graph_args, validate_common_graph_args, sbom_graph_args from conan.cli.command import conan_command, conan_subcommand from conan.cli.commands.list import prepare_pkglist_compact, print_serial +from conan.cli.formatters import default_json_formatter from conan.cli.formatters.graph import format_graph_html, format_graph_json, format_graph_dot from conan.cli.formatters.graph.build_order_html import format_build_order_html from conan.cli.formatters.graph.graph_info_text import format_graph_info from conan.cli.printers import print_profiles from conan.cli.printers.graph import print_graph_packages, print_graph_basic from conan.errors import ConanException +from conan.internal.cache.home_paths import HomePaths from conan.internal.deploy import do_deploys from conans.client.graph.graph import BINARY_MISSING from conans.client.graph.install_graph import InstallGraph, ProfileArgs from conan.internal.errors import NotFoundException +from conans.client.loader import load_python_file from conans.model.recipe_ref import ref_matches, RecipeReference @@ -418,6 +421,40 @@ def graph_outdated(conan_api, parser, subparser, *args): return filtered_nodes +@conan_subcommand(formatters={"text": print_serial, "json": default_json_formatter}) +def graph_sbom(conan_api, parser, subparser, *args): + """ + Generate a Software Bill of Materials (SBOM) for the dependency graph + """ + common_graph_args(subparser) + sbom_graph_args(subparser, conan_api) + args = parser.parse_args(*args) + validate_common_graph_args(args) + + cwd = os.getcwd() + path = conan_api.local.get_conanfile_path(args.path, cwd, py=None) if args.path else None + remotes = conan_api.remotes.list(args.remote) if not args.no_remote else [] + overrides = eval(args.lockfile_overrides) if args.lockfile_overrides else None + lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile, + conanfile_path=path, + cwd=cwd, + partial=args.lockfile_partial, + overrides=overrides) + profile_host, profile_build = conan_api.profiles.get_profiles_from_args(args) + if path: + deps_graph = conan_api.graph.load_graph_consumer(path, args.name, args.version, + args.user, args.channel, + profile_host, profile_build, lockfile, + remotes, args.update) + else: + deps_graph = conan_api.graph.load_graph_requires(args.requires, args.tool_requires, + profile_host, profile_build, lockfile, + remotes, args.update) + + generated_sbom = conan_api.graph.sbom(deps_graph, args.manifest) + return generated_sbom + + def _find_in_remotes(conan_api, dict_nodes, remotes): for node_name, node_info in dict_nodes.items(): ref_pattern = ListPattern(node_name, rrev=None, prev=None) diff --git a/conan/internal/cache/home_paths.py b/conan/internal/cache/home_paths.py index 30e79002b34..a7b56d79820 100644 --- a/conan/internal/cache/home_paths.py +++ b/conan/internal/cache/home_paths.py @@ -67,6 +67,10 @@ def auth_source_plugin_path(self): def sign_plugin_path(self): return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sign", "sign.py") + @property + def sbom_manifest_plugin_path(self): + return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sbom") + @property def remotes_path(self): return os.path.join(self._home, "remotes.json") diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py new file mode 100644 index 00000000000..a960d99ffb5 --- /dev/null +++ b/conans/client/graph/sbom.py @@ -0,0 +1,19 @@ +import os + +from conan.internal.cache.home_paths import HomePaths + +_default_spdx_json = """ +todo implement _default_spdx_json +""" + +_default_cyclonedx_1_4 = """ +todo implement _default_1_4_cyclonedx +""" + +def migrate_sbom_files(cache_folder): + from conans.client.migrations import update_file + sbom_folder = HomePaths(cache_folder).sbom_manifest_plugin_path + spdx_json_file = os.path.join(sbom_folder, "spdx.py") + cyclone_1_4_file = os.path.join(sbom_folder, "cyclonedx.py") + update_file(spdx_json_file, _default_spdx_json) + update_file(cyclone_1_4_file, _default_cyclonedx_1_4) diff --git a/conans/client/migrations.py b/conans/client/migrations.py index 82997ba4e9e..e8d175bab2a 100644 --- a/conans/client/migrations.py +++ b/conans/client/migrations.py @@ -52,6 +52,9 @@ def _apply_migrations(self, old_version): # Update profile plugin from conan.internal.api.profile.profile_loader import migrate_profile_plugin migrate_profile_plugin(self.cache_folder) + # Update sbom manifest plugins + from conans.client.graph.sbom import migrate_sbom_files + migrate_sbom_files(self.cache_folder) if old_version and old_version < "2.0.14-": _migrate_pkg_db_lru(self.cache_folder, old_version) From 7d10e596b7d74a7f56fb161c413ef0e38a699e3d Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Wed, 23 Oct 2024 10:31:51 +0200 Subject: [PATCH 02/28] use sbom in install --- conan/cli/commands/install.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index faff8ed2a96..d20e144eb64 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -2,7 +2,7 @@ from conan.api.output import ConanOutput from conan.cli import make_abs_path -from conan.cli.args import common_graph_args, validate_common_graph_args +from conan.cli.args import common_graph_args, validate_common_graph_args, sbom_graph_args from conan.cli.command import conan_command from conan.cli.formatters.graph import format_graph_json from conan.cli.printers import print_profiles @@ -41,6 +41,7 @@ def install(conan_api, parser, *args): help='Whether the provided path is a build-require') parser.add_argument("--envs-generation", default=None, choices=["false"], help="Generation strategy for virtual environment files for the root") + sbom_graph_args(parser, conan_api) args = parser.parse_args(*args) validate_common_graph_args(args) # basic paths @@ -84,5 +85,10 @@ def install(conan_api, parser, *args): lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages, clean=args.lockfile_clean) conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd) + + # Generate sbom + if args.manifest: + conan_api.graph.sbom(deps_graph, args.manifest) + return {"graph": deps_graph, "conan_api": conan_api} From 005fb39eb44e577eefe890d90267f031bdea77eb Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 24 Oct 2024 08:48:27 +0200 Subject: [PATCH 03/28] Revert "initial" This reverts commit c2bceceffb78f65be816627c10c82edab0c22b9c. --- conan/api/subapi/graph.py | 19 --------------- conan/cli/args.py | 19 +-------------- conan/cli/commands/graph.py | 39 +----------------------------- conan/internal/cache/home_paths.py | 4 --- conans/client/graph/sbom.py | 19 --------------- conans/client/migrations.py | 3 --- 6 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 conans/client/graph/sbom.py diff --git a/conan/api/subapi/graph.py b/conan/api/subapi/graph.py index 4dc21ee45bb..b6ff4f844de 100644 --- a/conan/api/subapi/graph.py +++ b/conan/api/subapi/graph.py @@ -1,7 +1,4 @@ -import os - from conan.api.output import ConanOutput -from conan.internal.cache.home_paths import HomePaths from conan.internal.conan_app import ConanApp from conans.client.graph.graph import Node, RECIPE_CONSUMER, CONTEXT_HOST, RECIPE_VIRTUAL, \ CONTEXT_BUILD @@ -9,7 +6,6 @@ from conans.client.graph.graph_builder import DepsGraphBuilder from conans.client.graph.profile_node_definer import initialize_conanfile_profile, consumer_definer from conan.errors import ConanException -from conans.client.loader import load_python_file from conans.model.recipe_ref import RecipeReference @@ -204,18 +200,3 @@ def analyze_binaries(self, graph, build_mode=None, remotes=None, update=None, lo binaries_analyzer = GraphBinariesAnalyzer(conan_app, self.conan_api.config.global_conf) binaries_analyzer.evaluate_graph(graph, build_mode, lockfile, remotes, update, build_modes_test, tested_graph) - - def sbom(self, graph, manifest): - ConanOutput().warning(f"generating sbom for {manifest} format") - sbom_manifest = HomePaths(self.conan_api.cache_folder).sbom_manifest_plugin_path - chosen_manifest_path = os.path.join(sbom_manifest, f"{manifest}.py") - mod, _ = load_python_file(chosen_manifest_path) - - if not hasattr(mod, "generate_sbom"): - raise ConanException( - f"SBOM manifest plugin '{manifest}' does not have 'generate_sbom' method") - if not callable(mod.generate_sbom): - raise ConanException( - f"SBOM manifest plugin '{manifest}' 'generate_sbom' is not a function") - - return mod.generate_sbom(self.conan_api, graph.serialize()) diff --git a/conan/cli/args.py b/conan/cli/args.py index ccede230b5e..d25b2c2927b 100644 --- a/conan/cli/args.py +++ b/conan/cli/args.py @@ -1,9 +1,7 @@ import argparse -import os from conan.cli.command import OnceArgument from conan.errors import ConanException -from conan.internal.cache.home_paths import HomePaths _help_build_policies = '''Optional, specify which packages to build from source. Combining multiple '--build' options on one command line is allowed. @@ -82,7 +80,7 @@ def create_config(short, long, example=None): f'By default, or if specifying -{short}:h (--{long}:host), it applies to the host context. ' f'Use -{short}:b (--{long}:build) to specify the build context, ' f'or -{short}:a (--{long}:all) to specify both contexts at once' - + ('' if not example else f". Example: {example}")) + + ('' if not example else f". Example: {example}")) for context in contexts: parser.add_argument(f"-{short}:{context[0]}", f"--{long}:{context}", default=None, @@ -139,18 +137,3 @@ def validate_common_graph_args(args): f"[path] '{args.path}' argument") if not args.path and args.build_require: raise ConanException("--build-require should only be used with argument") - - -def sbom_graph_args(parser, conan_api): - sbom_manifest = HomePaths(conan_api.cache_folder).sbom_manifest_plugin_path - choices = [] - if os.path.exists(sbom_manifest): - if not os.path.isdir(sbom_manifest): - raise ConanException(f"SBOM manifest plugin path '{sbom_manifest}' is not a directory") - - for manifest_format in os.listdir(sbom_manifest): - if not manifest_format.startswith("_"): - choices.append(manifest_format.replace(".py", "")) - - parser.add_argument("--manifest", choices=choices, default="spdx", - help="Output format for the SBOM") diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py index 1ef241626e0..aa1fb298c2e 100644 --- a/conan/cli/commands/graph.py +++ b/conan/cli/commands/graph.py @@ -4,22 +4,19 @@ from conan.api.model import ListPattern from conan.api.output import ConanOutput, cli_out_write, Color from conan.cli import make_abs_path -from conan.cli.args import common_graph_args, validate_common_graph_args, sbom_graph_args +from conan.cli.args import common_graph_args, validate_common_graph_args from conan.cli.command import conan_command, conan_subcommand from conan.cli.commands.list import prepare_pkglist_compact, print_serial -from conan.cli.formatters import default_json_formatter from conan.cli.formatters.graph import format_graph_html, format_graph_json, format_graph_dot from conan.cli.formatters.graph.build_order_html import format_build_order_html from conan.cli.formatters.graph.graph_info_text import format_graph_info from conan.cli.printers import print_profiles from conan.cli.printers.graph import print_graph_packages, print_graph_basic from conan.errors import ConanException -from conan.internal.cache.home_paths import HomePaths from conan.internal.deploy import do_deploys from conans.client.graph.graph import BINARY_MISSING from conans.client.graph.install_graph import InstallGraph, ProfileArgs from conan.internal.errors import NotFoundException -from conans.client.loader import load_python_file from conans.model.recipe_ref import ref_matches, RecipeReference @@ -421,40 +418,6 @@ def graph_outdated(conan_api, parser, subparser, *args): return filtered_nodes -@conan_subcommand(formatters={"text": print_serial, "json": default_json_formatter}) -def graph_sbom(conan_api, parser, subparser, *args): - """ - Generate a Software Bill of Materials (SBOM) for the dependency graph - """ - common_graph_args(subparser) - sbom_graph_args(subparser, conan_api) - args = parser.parse_args(*args) - validate_common_graph_args(args) - - cwd = os.getcwd() - path = conan_api.local.get_conanfile_path(args.path, cwd, py=None) if args.path else None - remotes = conan_api.remotes.list(args.remote) if not args.no_remote else [] - overrides = eval(args.lockfile_overrides) if args.lockfile_overrides else None - lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile, - conanfile_path=path, - cwd=cwd, - partial=args.lockfile_partial, - overrides=overrides) - profile_host, profile_build = conan_api.profiles.get_profiles_from_args(args) - if path: - deps_graph = conan_api.graph.load_graph_consumer(path, args.name, args.version, - args.user, args.channel, - profile_host, profile_build, lockfile, - remotes, args.update) - else: - deps_graph = conan_api.graph.load_graph_requires(args.requires, args.tool_requires, - profile_host, profile_build, lockfile, - remotes, args.update) - - generated_sbom = conan_api.graph.sbom(deps_graph, args.manifest) - return generated_sbom - - def _find_in_remotes(conan_api, dict_nodes, remotes): for node_name, node_info in dict_nodes.items(): ref_pattern = ListPattern(node_name, rrev=None, prev=None) diff --git a/conan/internal/cache/home_paths.py b/conan/internal/cache/home_paths.py index a7b56d79820..30e79002b34 100644 --- a/conan/internal/cache/home_paths.py +++ b/conan/internal/cache/home_paths.py @@ -67,10 +67,6 @@ def auth_source_plugin_path(self): def sign_plugin_path(self): return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sign", "sign.py") - @property - def sbom_manifest_plugin_path(self): - return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sbom") - @property def remotes_path(self): return os.path.join(self._home, "remotes.json") diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py deleted file mode 100644 index a960d99ffb5..00000000000 --- a/conans/client/graph/sbom.py +++ /dev/null @@ -1,19 +0,0 @@ -import os - -from conan.internal.cache.home_paths import HomePaths - -_default_spdx_json = """ -todo implement _default_spdx_json -""" - -_default_cyclonedx_1_4 = """ -todo implement _default_1_4_cyclonedx -""" - -def migrate_sbom_files(cache_folder): - from conans.client.migrations import update_file - sbom_folder = HomePaths(cache_folder).sbom_manifest_plugin_path - spdx_json_file = os.path.join(sbom_folder, "spdx.py") - cyclone_1_4_file = os.path.join(sbom_folder, "cyclonedx.py") - update_file(spdx_json_file, _default_spdx_json) - update_file(cyclone_1_4_file, _default_cyclonedx_1_4) diff --git a/conans/client/migrations.py b/conans/client/migrations.py index e8d175bab2a..82997ba4e9e 100644 --- a/conans/client/migrations.py +++ b/conans/client/migrations.py @@ -52,9 +52,6 @@ def _apply_migrations(self, old_version): # Update profile plugin from conan.internal.api.profile.profile_loader import migrate_profile_plugin migrate_profile_plugin(self.cache_folder) - # Update sbom manifest plugins - from conans.client.graph.sbom import migrate_sbom_files - migrate_sbom_files(self.cache_folder) if old_version and old_version < "2.0.14-": _migrate_pkg_db_lru(self.cache_folder, old_version) From 94dd2748e375f1b5faee4fee0ee1c7b1f7fd81cc Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 24 Oct 2024 08:48:58 +0200 Subject: [PATCH 04/28] Revert "use sbom in install" This reverts commit 7d10e596b7d74a7f56fb161c413ef0e38a699e3d. --- conan/cli/commands/install.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index d20e144eb64..faff8ed2a96 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -2,7 +2,7 @@ from conan.api.output import ConanOutput from conan.cli import make_abs_path -from conan.cli.args import common_graph_args, validate_common_graph_args, sbom_graph_args +from conan.cli.args import common_graph_args, validate_common_graph_args from conan.cli.command import conan_command from conan.cli.formatters.graph import format_graph_json from conan.cli.printers import print_profiles @@ -41,7 +41,6 @@ def install(conan_api, parser, *args): help='Whether the provided path is a build-require') parser.add_argument("--envs-generation", default=None, choices=["false"], help="Generation strategy for virtual environment files for the root") - sbom_graph_args(parser, conan_api) args = parser.parse_args(*args) validate_common_graph_args(args) # basic paths @@ -85,10 +84,5 @@ def install(conan_api, parser, *args): lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages, clean=args.lockfile_clean) conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd) - - # Generate sbom - if args.manifest: - conan_api.graph.sbom(deps_graph, args.manifest) - return {"graph": deps_graph, "conan_api": conan_api} From e4f9591feb0b714f07b674a75adcd8b75ca3b3dc Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 24 Oct 2024 08:52:07 +0200 Subject: [PATCH 05/28] wip --- conan/cli/commands/install.py | 33 +++++++++++++++++++++++++++++- conan/internal/cache/home_paths.py | 4 ++++ conans/client/graph/sbom.py | 17 +++++++++++++++ conans/client/migrations.py | 3 +++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 conans/client/graph/sbom.py diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index faff8ed2a96..3824971c95c 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -2,11 +2,14 @@ from conan.api.output import ConanOutput from conan.cli import make_abs_path -from conan.cli.args import common_graph_args, validate_common_graph_args +from conan.cli.args import common_graph_args, validate_common_graph_args, sbom_graph_args from conan.cli.command import conan_command from conan.cli.formatters.graph import format_graph_json from conan.cli.printers import print_profiles from conan.cli.printers.graph import print_graph_packages, print_graph_basic +from conan.internal.cache.home_paths import HomePaths +from conan.errors import ConanException +from conans.client.loader import load_python_file @conan_command(group="Consumer", formatters={"json": format_graph_json}) @@ -41,6 +44,7 @@ def install(conan_api, parser, *args): help='Whether the provided path is a build-require') parser.add_argument("--envs-generation", default=None, choices=["false"], help="Generation strategy for virtual environment files for the root") + sbom_graph_args(parser, conan_api) args = parser.parse_args(*args) validate_common_graph_args(args) # basic paths @@ -84,5 +88,32 @@ def install(conan_api, parser, *args): lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages, clean=args.lockfile_clean) conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd) + + # Generate sbom + _generate_sbom(conan_api, deps_graph) + return {"graph": deps_graph, "conan_api": conan_api} + +def _generate_sbom(conan_api, graph): + sbom_plugin_path = HomePaths(conan_api.cache_folder).sbom_manifest_plugin_path + sbom_model = "spdx_json.py" + if os.path.exists(sbom_plugin_path): + if not os.path.isdir(sbom_plugin_path): + raise ConanException(f"SBOM manifest plugin path '{sbom_plugin_path}' is not a directory") + + chosen_manifest_path = os.path.join(sbom_plugin_path, sbom_model) + mod, _ = load_python_file(chosen_manifest_path) + + if not hasattr(mod, "generate_sbom"): + raise ConanException( + f"SBOM manifest plugin '{sbom_model}' does not have 'generate_sbom' method") + if not callable(mod.generate_sbom): + raise ConanException( + f"SBOM manifest plugin '{sbom_model}' 'generate_sbom' is not a function") + + ConanOutput().warning(f"generating sbom for {sbom_model} format") + return mod.generate_sbom(conan_api, graph.serialize()) + + + diff --git a/conan/internal/cache/home_paths.py b/conan/internal/cache/home_paths.py index 30e79002b34..a7b56d79820 100644 --- a/conan/internal/cache/home_paths.py +++ b/conan/internal/cache/home_paths.py @@ -67,6 +67,10 @@ def auth_source_plugin_path(self): def sign_plugin_path(self): return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sign", "sign.py") + @property + def sbom_manifest_plugin_path(self): + return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sbom") + @property def remotes_path(self): return os.path.join(self._home, "remotes.json") diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py new file mode 100644 index 00000000000..bb573093bbb --- /dev/null +++ b/conans/client/graph/sbom.py @@ -0,0 +1,17 @@ +import os + +from conan.internal.cache.home_paths import HomePaths + +_default_spdx_json = """ +def generate_sbom(conan_api, graph): + d = {} + return d + +""" + + +def migrate_sbom_files(cache_folder): + from conans.client.migrations import update_file + sbom_folder = HomePaths(cache_folder).sbom_manifest_plugin_path + spdx_json_file = os.path.join(sbom_folder, "spdx_json.py") + update_file(spdx_json_file, _default_spdx_json) diff --git a/conans/client/migrations.py b/conans/client/migrations.py index 82997ba4e9e..e8d175bab2a 100644 --- a/conans/client/migrations.py +++ b/conans/client/migrations.py @@ -52,6 +52,9 @@ def _apply_migrations(self, old_version): # Update profile plugin from conan.internal.api.profile.profile_loader import migrate_profile_plugin migrate_profile_plugin(self.cache_folder) + # Update sbom manifest plugins + from conans.client.graph.sbom import migrate_sbom_files + migrate_sbom_files(self.cache_folder) if old_version and old_version < "2.0.14-": _migrate_pkg_db_lru(self.cache_folder, old_version) From bc25a617e6d35e2942b47b1bb886033c093018e1 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 24 Oct 2024 09:08:37 +0200 Subject: [PATCH 06/28] wip --- conan/cli/commands/install.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index 3824971c95c..73fc0714c7c 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -2,7 +2,7 @@ from conan.api.output import ConanOutput from conan.cli import make_abs_path -from conan.cli.args import common_graph_args, validate_common_graph_args, sbom_graph_args +from conan.cli.args import common_graph_args, validate_common_graph_args from conan.cli.command import conan_command from conan.cli.formatters.graph import format_graph_json from conan.cli.printers import print_profiles @@ -44,7 +44,6 @@ def install(conan_api, parser, *args): help='Whether the provided path is a build-require') parser.add_argument("--envs-generation", default=None, choices=["false"], help="Generation strategy for virtual environment files for the root") - sbom_graph_args(parser, conan_api) args = parser.parse_args(*args) validate_common_graph_args(args) # basic paths From 251124544fdf12fc2cb3ce20157017d2a4c8e634 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 24 Oct 2024 09:45:16 +0200 Subject: [PATCH 07/28] wip --- conan/cli/commands/install.py | 29 ------------------------ conan/internal/api/install/generators.py | 22 ++++++++++++++++++ conan/internal/cache/home_paths.py | 2 +- conans/client/graph/sbom.py | 9 ++++---- conans/client/migrations.py | 4 ++-- test/integration/sbom/__init__.py | 0 test/integration/sbom/test_sbom.py | 10 ++++++++ 7 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 test/integration/sbom/__init__.py create mode 100644 test/integration/sbom/test_sbom.py diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index 73fc0714c7c..884b305058d 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -7,9 +7,6 @@ from conan.cli.formatters.graph import format_graph_json from conan.cli.printers import print_profiles from conan.cli.printers.graph import print_graph_packages, print_graph_basic -from conan.internal.cache.home_paths import HomePaths -from conan.errors import ConanException -from conans.client.loader import load_python_file @conan_command(group="Consumer", formatters={"json": format_graph_json}) @@ -88,31 +85,5 @@ def install(conan_api, parser, *args): clean=args.lockfile_clean) conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd) - # Generate sbom - _generate_sbom(conan_api, deps_graph) - return {"graph": deps_graph, "conan_api": conan_api} - -def _generate_sbom(conan_api, graph): - sbom_plugin_path = HomePaths(conan_api.cache_folder).sbom_manifest_plugin_path - sbom_model = "spdx_json.py" - if os.path.exists(sbom_plugin_path): - if not os.path.isdir(sbom_plugin_path): - raise ConanException(f"SBOM manifest plugin path '{sbom_plugin_path}' is not a directory") - - chosen_manifest_path = os.path.join(sbom_plugin_path, sbom_model) - mod, _ = load_python_file(chosen_manifest_path) - - if not hasattr(mod, "generate_sbom"): - raise ConanException( - f"SBOM manifest plugin '{sbom_model}' does not have 'generate_sbom' method") - if not callable(mod.generate_sbom): - raise ConanException( - f"SBOM manifest plugin '{sbom_model}' 'generate_sbom' is not a function") - - ConanOutput().warning(f"generating sbom for {sbom_model} format") - return mod.generate_sbom(conan_api, graph.serialize()) - - - diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index 3fd02da74ef..ce580d81441 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -3,6 +3,7 @@ import traceback import importlib +from conan.api.output import ConanOutput from conan.internal.cache.home_paths import HomePaths from conans.client.subsystems import deduce_subsystem, subsystem_path from conan.internal.errors import conanfile_exception_formatter @@ -80,6 +81,9 @@ def write_generators(conanfile, app, envs_generation=None): global_generators = load_cache_generators(HomePaths(app.cache_folder).custom_generators_path) hook_manager.execute("pre_generate", conanfile=conanfile) + # Generate sbom + _generate_sbom(conanfile._conan_helpers.home_folder, conanfile) + if conanfile.generators: conanfile.output.highlight(f"Writing generators to {new_gen_folder}") # generators check that they are not present in the generators field, @@ -152,6 +156,24 @@ def _receive_conf(conanfile): conanfile.conf.compose_conf(build_require.conf_info) +def _generate_sbom(cache_folder, conanfile): + from conans.client.loader import load_python_file + sbom_plugin_path = HomePaths(cache_folder).sbom_manifest_plugin_path + if os.path.exists(sbom_plugin_path): + mod, _ = load_python_file(sbom_plugin_path) + + if not hasattr(mod, "generate_sbom"): + raise ConanException( + f"SBOM manifest plugin does not have a 'generate_sbom' method") + if not callable(mod.generate_sbom): + raise ConanException( + f"SBOM manifest plugin 'generate_sbom' is not a function") + + ConanOutput().warning(f"generating sbom") + # TODO think if this is conanfile or conanfile._conan_node + return mod.generate_sbom(conanfile) + + def _generate_aggregated_env(conanfile): def deactivates(filenames): diff --git a/conan/internal/cache/home_paths.py b/conan/internal/cache/home_paths.py index a7b56d79820..bef60942465 100644 --- a/conan/internal/cache/home_paths.py +++ b/conan/internal/cache/home_paths.py @@ -69,7 +69,7 @@ def sign_plugin_path(self): @property def sbom_manifest_plugin_path(self): - return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sbom") + return os.path.join(self._home, _EXTENSIONS_FOLDER, _PLUGINS, "sbom.py") @property def remotes_path(self): diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py index bb573093bbb..3d8532ceea2 100644 --- a/conans/client/graph/sbom.py +++ b/conans/client/graph/sbom.py @@ -3,15 +3,14 @@ from conan.internal.cache.home_paths import HomePaths _default_spdx_json = """ -def generate_sbom(conan_api, graph): +def generate_sbom(conanfile, **kwargs): d = {} return d """ -def migrate_sbom_files(cache_folder): +def migrate_sbom_file(cache_folder): from conans.client.migrations import update_file - sbom_folder = HomePaths(cache_folder).sbom_manifest_plugin_path - spdx_json_file = os.path.join(sbom_folder, "spdx_json.py") - update_file(spdx_json_file, _default_spdx_json) + sbom_path = HomePaths(cache_folder).sbom_manifest_plugin_path + update_file(sbom_path, _default_spdx_json) diff --git a/conans/client/migrations.py b/conans/client/migrations.py index e8d175bab2a..03487d423ef 100644 --- a/conans/client/migrations.py +++ b/conans/client/migrations.py @@ -53,8 +53,8 @@ def _apply_migrations(self, old_version): from conan.internal.api.profile.profile_loader import migrate_profile_plugin migrate_profile_plugin(self.cache_folder) # Update sbom manifest plugins - from conans.client.graph.sbom import migrate_sbom_files - migrate_sbom_files(self.cache_folder) + from conans.client.graph.sbom import migrate_sbom_file + migrate_sbom_file(self.cache_folder) if old_version and old_version < "2.0.14-": _migrate_pkg_db_lru(self.cache_folder, old_version) diff --git a/test/integration/sbom/__init__.py b/test/integration/sbom/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py new file mode 100644 index 00000000000..834f9eeb0c6 --- /dev/null +++ b/test/integration/sbom/test_sbom.py @@ -0,0 +1,10 @@ +from conan.test.assets.genconanfile import GenConanfile +from conan.test.utils.tools import TestClient + + +def test_sbom_generation(): + tc = TestClient(light=True) + 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") From 036fb82a1b1294cd94de2e7d3d5002846b619ab6 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Fri, 25 Oct 2024 14:12:33 +0200 Subject: [PATCH 08/28] sbom generator --- conans/client/graph/sbom.py | 66 ++++++++++++++++++++++++++++-- test/integration/sbom/test_sbom.py | 3 ++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py index 3d8532ceea2..78046f3a247 100644 --- a/conans/client/graph/sbom.py +++ b/conans/client/graph/sbom.py @@ -3,9 +3,69 @@ from conan.internal.cache.home_paths import HomePaths _default_spdx_json = """ -def generate_sbom(conanfile, **kwargs): - d = {} - return d +import time +import json +import os +from datetime import datetime, timezone +from conan import conan_version +from conan.errors import ConanException + +# https://spdx.org/licenses/ +spdx_licences = {'0BSD', '3D-Slicer-1.0', 'AAL', 'Abstyles', 'AdaCore-doc', 'Adobe-2006', 'Adobe-Display-PostScript', 'Adobe-Glyph', 'Adobe-Utopia', 'ADSL', 'AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', 'Afmparse', 'AGPL-1.0-only', 'AGPL-1.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'Aladdin', 'AMD-newlib', 'AMDPLPA', 'AML', 'AML-glslang', 'AMPAS', 'ANTLR-PD', 'ANTLR-PD-fallback', 'any-OSI', 'Apache-1.0', 'Apache-1.1', 'Apache-2.0', 'APAFML', 'APL-1.0', 'App-s2p', 'APSL-1.0', 'APSL-1.1', 'APSL-1.2', 'APSL-2.0', 'Arphic-1999', 'Artistic-1.0', 'Artistic-1.0-cl8', 'Artistic-1.0-Perl', 'Artistic-2.0', 'ASWF-Digital-Assets-1.0', 'ASWF-Digital-Assets-1.1', 'Baekmuk', 'Bahyph', 'Barr', 'bcrypt-Solar-Designer', 'Beerware', 'Bitstream-Charter', 'Bitstream-Vera', 'BitTorrent-1.0', 'BitTorrent-1.1', 'blessing', 'BlueOak-1.0.0', 'Boehm-GC', 'Borceux', 'Brian-Gladman-2-Clause', 'Brian-Gladman-3-Clause', 'BSD-1-Clause', 'BSD-2-Clause', 'BSD-2-Clause-Darwin', 'BSD-2-Clause-first-lines', 'BSD-2-Clause-Patent', 'BSD-2-Clause-Views', 'BSD-3-Clause', 'BSD-3-Clause-acpica', 'BSD-3-Clause-Attribution', 'BSD-3-Clause-Clear', 'BSD-3-Clause-flex', 'BSD-3-Clause-HP', 'BSD-3-Clause-LBNL', 'BSD-3-Clause-Modification', 'BSD-3-Clause-No-Military-License', 'BSD-3-Clause-No-Nuclear-License', 'BSD-3-Clause-No-Nuclear-License-2014', 'BSD-3-Clause-No-Nuclear-Warranty', 'BSD-3-Clause-Open-MPI', 'BSD-3-Clause-Sun', 'BSD-4-Clause', 'BSD-4-Clause-Shortened', 'BSD-4-Clause-UC', 'BSD-4.3RENO', 'BSD-4.3TAHOE', 'BSD-Advertising-Acknowledgement', 'BSD-Attribution-HPND-disclaimer', 'BSD-Inferno-Nettverk', 'BSD-Protection', 'BSD-Source-beginning-file', 'BSD-Source-Code', 'BSD-Systemics', 'BSD-Systemics-W3Works', 'BSL-1.0', 'BUSL-1.1', 'bzip2-1.0.6', 'C-UDA-1.0', 'CAL-1.0', 'CAL-1.0-Combined-Work-Exception', 'Caldera', 'Caldera-no-preamble', 'Catharon', 'CATOSL-1.1', 'CC-BY-1.0', 'CC-BY-2.0', 'CC-BY-2.5', 'CC-BY-2.5-AU', 'CC-BY-3.0', 'CC-BY-3.0-AT', 'CC-BY-3.0-AU', 'CC-BY-3.0-DE', 'CC-BY-3.0-IGO', 'CC-BY-3.0-NL', 'CC-BY-3.0-US', 'CC-BY-4.0', 'CC-BY-NC-1.0', 'CC-BY-NC-2.0', 'CC-BY-NC-2.5', 'CC-BY-NC-3.0', 'CC-BY-NC-3.0-DE', 'CC-BY-NC-4.0', 'CC-BY-NC-ND-1.0', 'CC-BY-NC-ND-2.0', 'CC-BY-NC-ND-2.5', 'CC-BY-NC-ND-3.0', 'CC-BY-NC-ND-3.0-DE', 'CC-BY-NC-ND-3.0-IGO', 'CC-BY-NC-ND-4.0', 'CC-BY-NC-SA-1.0', 'CC-BY-NC-SA-2.0', 'CC-BY-NC-SA-2.0-DE', 'CC-BY-NC-SA-2.0-FR', 'CC-BY-NC-SA-2.0-UK', 'CC-BY-NC-SA-2.5', 'CC-BY-NC-SA-3.0', 'CC-BY-NC-SA-3.0-DE', 'CC-BY-NC-SA-3.0-IGO', 'CC-BY-NC-SA-4.0', 'CC-BY-ND-1.0', 'CC-BY-ND-2.0', 'CC-BY-ND-2.5', 'CC-BY-ND-3.0', 'CC-BY-ND-3.0-DE', 'CC-BY-ND-4.0', 'CC-BY-SA-1.0', 'CC-BY-SA-2.0', 'CC-BY-SA-2.0-UK', 'CC-BY-SA-2.1-JP', 'CC-BY-SA-2.5', 'CC-BY-SA-3.0', 'CC-BY-SA-3.0-AT', 'CC-BY-SA-3.0-DE', 'CC-BY-SA-3.0-IGO', 'CC-BY-SA-4.0', 'CC-PDDC', 'CC0-1.0', 'CDDL-1.0', 'CDDL-1.1', 'CDL-1.0', 'CDLA-Permissive-1.0', 'CDLA-Permissive-2.0', 'CDLA-Sharing-1.0', 'CECILL-1.0', 'CECILL-1.1', 'CECILL-2.0', 'CECILL-2.1', 'CECILL-B', 'CECILL-C', 'CERN-OHL-1.1', 'CERN-OHL-1.2', 'CERN-OHL-P-2.0', 'CERN-OHL-S-2.0', 'CERN-OHL-W-2.0', 'CFITSIO', 'check-cvs', 'checkmk', 'ClArtistic', 'Clips', 'CMU-Mach', 'CMU-Mach-nodoc', 'CNRI-Jython', 'CNRI-Python', 'CNRI-Python-GPL-Compatible', 'COIL-1.0', 'Community-Spec-1.0', 'Condor-1.1', 'copyleft-next-0.3.0', 'copyleft-next-0.3.1', 'Cornell-Lossless-JPEG', 'CPAL-1.0', 'CPL-1.0', 'CPOL-1.02', 'Cronyx', 'Crossword', 'CrystalStacker', 'CUA-OPL-1.0', 'Cube', 'curl', 'cve-tou', 'D-FSL-1.0', 'DEC-3-Clause', 'diffmark', 'DL-DE-BY-2.0', 'DL-DE-ZERO-2.0', 'DOC', 'DocBook-Schema', 'DocBook-XML', 'Dotseqn', 'DRL-1.0', 'DRL-1.1', 'DSDP', 'dtoa', 'dvipdfm', 'ECL-1.0', 'ECL-2.0', 'EFL-1.0', 'EFL-2.0', 'eGenix', 'Elastic-2.0', 'Entessa', 'EPICS', 'EPL-1.0', 'EPL-2.0', 'ErlPL-1.1', 'etalab-2.0', 'EUDatagrid', 'EUPL-1.0', 'EUPL-1.1', 'EUPL-1.2', 'Eurosym', 'Fair', 'FBM', 'FDK-AAC', 'Ferguson-Twofish', 'Frameworx-1.0', 'FreeBSD-DOC', 'FreeImage', 'FSFAP', 'FSFAP-no-warranty-disclaimer', 'FSFUL', 'FSFULLR', 'FSFULLRWD', 'FTL', 'Furuseth', 'fwlw', 'GCR-docs', 'GD', 'GFDL-1.1-invariants-only', 'GFDL-1.1-invariants-or-later', 'GFDL-1.1-no-invariants-only', 'GFDL-1.1-no-invariants-or-later', 'GFDL-1.1-only', 'GFDL-1.1-or-later', 'GFDL-1.2-invariants-only', 'GFDL-1.2-invariants-or-later', 'GFDL-1.2-no-invariants-only', 'GFDL-1.2-no-invariants-or-later', 'GFDL-1.2-only', 'GFDL-1.2-or-later', 'GFDL-1.3-invariants-only', 'GFDL-1.3-invariants-or-later', 'GFDL-1.3-no-invariants-only', 'GFDL-1.3-no-invariants-or-later', 'GFDL-1.3-only', 'GFDL-1.3-or-later', 'Giftware', 'GL2PS', 'Glide', 'Glulxe', 'GLWTPL', 'gnuplot', 'GPL-1.0-only', 'GPL-1.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Graphics-Gems', 'gSOAP-1.3b', 'gtkbook', 'Gutmann', 'HaskellReport', 'hdparm', 'HIDAPI', 'Hippocratic-2.1', 'HP-1986', 'HP-1989', 'HPND', 'HPND-DEC', 'HPND-doc', 'HPND-doc-sell', 'HPND-export-US', 'HPND-export-US-acknowledgement', 'HPND-export-US-modify', 'HPND-export2-US', 'HPND-Fenneberg-Livingston', 'HPND-INRIA-IMAG', 'HPND-Intel', 'HPND-Kevlin-Henney', 'HPND-Markus-Kuhn', 'HPND-merchantability-variant', 'HPND-MIT-disclaimer', 'HPND-Netrek', 'HPND-Pbmplus', 'HPND-sell-MIT-disclaimer-xserver', 'HPND-sell-regexpr', 'HPND-sell-variant', 'HPND-sell-variant-MIT-disclaimer', 'HPND-sell-variant-MIT-disclaimer-rev', 'HPND-UC', 'HPND-UC-export-US', 'HTMLTIDY', 'IBM-pibs', 'ICU', 'IEC-Code-Components-EULA', 'IJG', 'IJG-short', 'ImageMagick', 'iMatix', 'Imlib2', 'Info-ZIP', 'Inner-Net-2.0', 'Intel', 'Intel-ACPI', 'Interbase-1.0', 'IPA', 'IPL-1.0', 'ISC', 'ISC-Veillard', 'Jam', 'JasPer-2.0', 'JPL-image', 'JPNIC', 'JSON', 'Kastrup', 'Kazlib', 'Knuth-CTAN', 'LAL-1.2', 'LAL-1.3', 'Latex2e', 'Latex2e-translated-notice', 'Leptonica', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'LGPLLR', 'Libpng', 'libpng-2.0', 'libselinux-1.0', 'libtiff', 'libutil-David-Nugent', 'LiLiQ-P-1.1', 'LiLiQ-R-1.1', 'LiLiQ-Rplus-1.1', 'Linux-man-pages-1-para', 'Linux-man-pages-copyleft', 'Linux-man-pages-copyleft-2-para', 'Linux-man-pages-copyleft-var', 'Linux-OpenIB', 'LOOP', 'LPD-document', 'LPL-1.0', 'LPL-1.02', 'LPPL-1.0', 'LPPL-1.1', 'LPPL-1.2', 'LPPL-1.3a', 'LPPL-1.3c', 'lsof', 'Lucida-Bitmap-Fonts', 'LZMA-SDK-9.11-to-9.20', 'LZMA-SDK-9.22', 'Mackerras-3-Clause', 'Mackerras-3-Clause-acknowledgment', 'magaz', 'mailprio', 'MakeIndex', 'Martin-Birgmeier', 'McPhee-slideshow', 'metamail', 'Minpack', 'MirOS', 'MIT', 'MIT-0', 'MIT-advertising', 'MIT-CMU', 'MIT-enna', 'MIT-feh', 'MIT-Festival', 'MIT-Khronos-old', 'MIT-Modern-Variant', 'MIT-open-group', 'MIT-testregex', 'MIT-Wu', 'MITNFA', 'MMIXware', 'Motosoto', 'MPEG-SSG', 'mpi-permissive', 'mpich2', 'MPL-1.0', 'MPL-1.1', 'MPL-2.0', 'MPL-2.0-no-copyleft-exception', 'mplus', 'MS-LPL', 'MS-PL', 'MS-RL', 'MTLL', 'MulanPSL-1.0', 'MulanPSL-2.0', 'Multics', 'Mup', 'NAIST-2003', 'NASA-1.3', 'Naumen', 'NBPL-1.0', 'NCBI-PD', 'NCGL-UK-2.0', 'NCL', 'NCSA', 'NetCDF', 'Newsletr', 'NGPL', 'NICTA-1.0', 'NIST-PD', 'NIST-PD-fallback', 'NIST-Software', 'NLOD-1.0', 'NLOD-2.0', 'NLPL', 'Nokia', 'NOSL', 'Noweb', 'NPL-1.0', 'NPL-1.1', 'NPOSL-3.0', 'NRL', 'NTP', 'NTP-0', 'O-UDA-1.0', 'OAR', 'OCCT-PL', 'OCLC-2.0', 'ODbL-1.0', 'ODC-By-1.0', 'OFFIS', 'OFL-1.0', 'OFL-1.0-no-RFN', 'OFL-1.0-RFN', 'OFL-1.1', 'OFL-1.1-no-RFN', 'OFL-1.1-RFN', 'OGC-1.0', 'OGDL-Taiwan-1.0', 'OGL-Canada-2.0', 'OGL-UK-1.0', 'OGL-UK-2.0', 'OGL-UK-3.0', 'OGTSL', 'OLDAP-1.1', 'OLDAP-1.2', 'OLDAP-1.3', 'OLDAP-1.4', 'OLDAP-2.0', 'OLDAP-2.0.1', 'OLDAP-2.1', 'OLDAP-2.2', 'OLDAP-2.2.1', 'OLDAP-2.2.2', 'OLDAP-2.3', 'OLDAP-2.4', 'OLDAP-2.5', 'OLDAP-2.6', 'OLDAP-2.7', 'OLDAP-2.8', 'OLFL-1.3', 'OML', 'OpenPBS-2.3', 'OpenSSL', 'OpenSSL-standalone', 'OpenVision', 'OPL-1.0', 'OPL-UK-3.0', 'OPUBL-1.0', 'OSET-PL-2.1', 'OSL-1.0', 'OSL-1.1', 'OSL-2.0', 'OSL-2.1', 'OSL-3.0', 'PADL', 'Parity-6.0.0', 'Parity-7.0.0', 'PDDL-1.0', 'PHP-3.0', 'PHP-3.01', 'Pixar', 'pkgconf', 'Plexus', 'pnmstitch', 'PolyForm-Noncommercial-1.0.0', 'PolyForm-Small-Business-1.0.0', 'PostgreSQL', 'PPL', 'PSF-2.0', 'psfrag', 'psutils', 'Python-2.0', 'Python-2.0.1', 'python-ldap', 'Qhull', 'QPL-1.0', 'QPL-1.0-INRIA-2004', 'radvd', 'Rdisc', 'RHeCos-1.1', 'RPL-1.1', 'RPL-1.5', 'RPSL-1.0', 'RSA-MD', 'RSCPL', 'Ruby', 'Ruby-pty', 'SAX-PD', 'SAX-PD-2.0', 'Saxpath', 'SCEA', 'SchemeReport', 'Sendmail', 'Sendmail-8.23', 'SGI-B-1.0', 'SGI-B-1.1', 'SGI-B-2.0', 'SGI-OpenGL', 'SGP4', 'SHL-0.5', 'SHL-0.51', 'SimPL-2.0', 'SISSL', 'SISSL-1.2', 'SL', 'Sleepycat', 'SMLNJ', 'SMPPL', 'SNIA', 'snprintf', 'softSurfer', 'Soundex', 'Spencer-86', 'Spencer-94', 'Spencer-99', 'SPL-1.0', 'ssh-keyscan', 'SSH-OpenSSH', 'SSH-short', 'SSLeay-standalone', 'SSPL-1.0', 'SugarCRM-1.1.3', 'Sun-PPP', 'Sun-PPP-2000', 'SunPro', 'SWL', 'swrule', 'Symlinks', 'TAPR-OHL-1.0', 'TCL', 'TCP-wrappers', 'TermReadKey', 'TGPPL-1.0', 'threeparttable', 'TMate', 'TORQUE-1.1', 'TOSL', 'TPDL', 'TPL-1.0', 'TTWL', 'TTYP0', 'TU-Berlin-1.0', 'TU-Berlin-2.0', 'Ubuntu-font-1.0', 'UCAR', 'UCL-1.0', 'ulem', 'UMich-Merit', 'Unicode-3.0', 'Unicode-DFS-2015', 'Unicode-DFS-2016', 'Unicode-TOU', 'UnixCrypt', 'Unlicense', 'UPL-1.0', 'URT-RLE', 'Vim', 'VOSTROM', 'VSL-1.0', 'W3C', 'W3C-19980720', 'W3C-20150513', 'w3m', 'Watcom-1.0', 'Widget-Workshop', 'Wsuipa', 'WTFPL', 'X11', 'X11-distribute-modifications-variant', 'X11-swapped', 'Xdebug-1.03', 'Xerox', 'Xfig', 'XFree86-1.1', 'xinetd', 'xkeyboard-config-Zinoviev', 'xlock', 'Xnet', 'xpp', 'XSkat', 'xzoom', 'YPL-1.0', 'YPL-1.1', 'Zed', 'Zeeff', 'Zend-2.0', 'Zimbra-1.3', 'Zimbra-1.4', 'Zlib', 'zlib-acknowledgement', 'ZPL-1.1', 'ZPL-2.0', 'ZPL-2.1'} + +def generate_sbom(conanfile): + name = conanfile.ref.name + version = conanfile.ref.version + date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') + packages_to_check = [conanfile] + packages = [] + while packages_to_check: + dependency = packages_to_check.pop() + packages.append( + { + "name": dependency.ref.name, + "SPDXID": f"SPDXRef-{dependency.ref}", + "version": str(dependency.ref.version), + "license": dependency.license if dependency.license in spdx_licences else "NOASSERTION", + }) + packages_to_check += [dependency for require, dependency in dependency.dependencies.items()] + files = [] + # https://spdx.github.io/spdx-spec/v2.2.2/package-information/ + data = { + "SPDXVersion": "SPDX-2.2", + "DataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "DocumentName": f"{name}-{version}", + "DocumentNamespace": f"http://spdx.org/spdxdocs/{name}-{version}-{date}", # the date or hash to make it unique + "Creator": f"Tool: Conan-{conan_version}", + "Created": date, #YYYY-MM-DDThh:mm:ssZ + "Packages": [{ + "PackageName": p["name"], + "SPDXID": p["SPDXID"], + "PackageVersion": p["version"], + "PackageDownloadLocation": "NOASSERTION", + "FilesAnalyzed": False, + "PackageLicenseConcluded": p["license"], + "PackageLicenseDeclared": p["license"], + } for p in packages], + # "Files": [{ + # "FileName": f["path"], # Path to file + # "SPDXID": f["SPDXID"], + # "FileChecksum": f'{f["checksum_algorithm"]}: {f["checksum_algorithm_value"]}', + # "LicenseConcluded": f["licence"], + # "LicenseInfoInFile": f["licence"], + # "FileCopyrightText": "NOASSERTION" + # } for f in files], + } + path = "./spdx/" + file_name = f"{conanfile.ref}-SPDX.json".replace("/", "-") + try: + if not os.path.exists(path): + os.makedirs(path) + with open(os.path.join(path, file_name), 'w') as f: + json.dump(data, f, indent=4) + except Exception as e: + ConanException("error generating spdx file") """ diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index 834f9eeb0c6..8d6dc0c2256 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -1,5 +1,6 @@ from conan.test.assets.genconanfile import GenConanfile from conan.test.utils.tools import TestClient +import os def test_sbom_generation(): @@ -8,3 +9,5 @@ def test_sbom_generation(): "conanfile.py": GenConanfile("foo", "1.0").with_requires("dep/1.0")}) tc.run("export dep") tc.run("create . --build=missing") + assert os.path.exists(os.path.join(tc.current_folder, "spdx", "dep-1.0-SPDX.json")) + assert os.path.exists(os.path.join(tc.current_folder, "spdx", "foo-1.0-SPDX.json")) From 7e4cf575fb80fe6dac306075d662f1a95179d3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Fri, 25 Oct 2024 15:35:51 +0200 Subject: [PATCH 09/28] review suggestions --- conan/internal/api/install/generators.py | 5 ++++- conans/client/graph/sbom.py | 14 ++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index ce580d81441..2b0f809c96d 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -170,8 +170,11 @@ def _generate_sbom(cache_folder, conanfile): f"SBOM manifest plugin 'generate_sbom' is not a function") ConanOutput().warning(f"generating sbom") + # TODO: Think where it makes sense to generate the file at this point + safe_ref_filename = str(conan.ref).replace("/", "_").replace(".", "_") + outfile = os.path.join(os.path.curdir, f"sbom_{safe_ref_filename}.json") # Temp # TODO think if this is conanfile or conanfile._conan_node - return mod.generate_sbom(conanfile) + return mod.generate_sbom(conanfile, outfile) def _generate_aggregated_env(conanfile): diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py index 78046f3a247..ef669f5a41b 100644 --- a/conans/client/graph/sbom.py +++ b/conans/client/graph/sbom.py @@ -13,14 +13,13 @@ # https://spdx.org/licenses/ spdx_licences = {'0BSD', '3D-Slicer-1.0', 'AAL', 'Abstyles', 'AdaCore-doc', 'Adobe-2006', 'Adobe-Display-PostScript', 'Adobe-Glyph', 'Adobe-Utopia', 'ADSL', 'AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', 'Afmparse', 'AGPL-1.0-only', 'AGPL-1.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'Aladdin', 'AMD-newlib', 'AMDPLPA', 'AML', 'AML-glslang', 'AMPAS', 'ANTLR-PD', 'ANTLR-PD-fallback', 'any-OSI', 'Apache-1.0', 'Apache-1.1', 'Apache-2.0', 'APAFML', 'APL-1.0', 'App-s2p', 'APSL-1.0', 'APSL-1.1', 'APSL-1.2', 'APSL-2.0', 'Arphic-1999', 'Artistic-1.0', 'Artistic-1.0-cl8', 'Artistic-1.0-Perl', 'Artistic-2.0', 'ASWF-Digital-Assets-1.0', 'ASWF-Digital-Assets-1.1', 'Baekmuk', 'Bahyph', 'Barr', 'bcrypt-Solar-Designer', 'Beerware', 'Bitstream-Charter', 'Bitstream-Vera', 'BitTorrent-1.0', 'BitTorrent-1.1', 'blessing', 'BlueOak-1.0.0', 'Boehm-GC', 'Borceux', 'Brian-Gladman-2-Clause', 'Brian-Gladman-3-Clause', 'BSD-1-Clause', 'BSD-2-Clause', 'BSD-2-Clause-Darwin', 'BSD-2-Clause-first-lines', 'BSD-2-Clause-Patent', 'BSD-2-Clause-Views', 'BSD-3-Clause', 'BSD-3-Clause-acpica', 'BSD-3-Clause-Attribution', 'BSD-3-Clause-Clear', 'BSD-3-Clause-flex', 'BSD-3-Clause-HP', 'BSD-3-Clause-LBNL', 'BSD-3-Clause-Modification', 'BSD-3-Clause-No-Military-License', 'BSD-3-Clause-No-Nuclear-License', 'BSD-3-Clause-No-Nuclear-License-2014', 'BSD-3-Clause-No-Nuclear-Warranty', 'BSD-3-Clause-Open-MPI', 'BSD-3-Clause-Sun', 'BSD-4-Clause', 'BSD-4-Clause-Shortened', 'BSD-4-Clause-UC', 'BSD-4.3RENO', 'BSD-4.3TAHOE', 'BSD-Advertising-Acknowledgement', 'BSD-Attribution-HPND-disclaimer', 'BSD-Inferno-Nettverk', 'BSD-Protection', 'BSD-Source-beginning-file', 'BSD-Source-Code', 'BSD-Systemics', 'BSD-Systemics-W3Works', 'BSL-1.0', 'BUSL-1.1', 'bzip2-1.0.6', 'C-UDA-1.0', 'CAL-1.0', 'CAL-1.0-Combined-Work-Exception', 'Caldera', 'Caldera-no-preamble', 'Catharon', 'CATOSL-1.1', 'CC-BY-1.0', 'CC-BY-2.0', 'CC-BY-2.5', 'CC-BY-2.5-AU', 'CC-BY-3.0', 'CC-BY-3.0-AT', 'CC-BY-3.0-AU', 'CC-BY-3.0-DE', 'CC-BY-3.0-IGO', 'CC-BY-3.0-NL', 'CC-BY-3.0-US', 'CC-BY-4.0', 'CC-BY-NC-1.0', 'CC-BY-NC-2.0', 'CC-BY-NC-2.5', 'CC-BY-NC-3.0', 'CC-BY-NC-3.0-DE', 'CC-BY-NC-4.0', 'CC-BY-NC-ND-1.0', 'CC-BY-NC-ND-2.0', 'CC-BY-NC-ND-2.5', 'CC-BY-NC-ND-3.0', 'CC-BY-NC-ND-3.0-DE', 'CC-BY-NC-ND-3.0-IGO', 'CC-BY-NC-ND-4.0', 'CC-BY-NC-SA-1.0', 'CC-BY-NC-SA-2.0', 'CC-BY-NC-SA-2.0-DE', 'CC-BY-NC-SA-2.0-FR', 'CC-BY-NC-SA-2.0-UK', 'CC-BY-NC-SA-2.5', 'CC-BY-NC-SA-3.0', 'CC-BY-NC-SA-3.0-DE', 'CC-BY-NC-SA-3.0-IGO', 'CC-BY-NC-SA-4.0', 'CC-BY-ND-1.0', 'CC-BY-ND-2.0', 'CC-BY-ND-2.5', 'CC-BY-ND-3.0', 'CC-BY-ND-3.0-DE', 'CC-BY-ND-4.0', 'CC-BY-SA-1.0', 'CC-BY-SA-2.0', 'CC-BY-SA-2.0-UK', 'CC-BY-SA-2.1-JP', 'CC-BY-SA-2.5', 'CC-BY-SA-3.0', 'CC-BY-SA-3.0-AT', 'CC-BY-SA-3.0-DE', 'CC-BY-SA-3.0-IGO', 'CC-BY-SA-4.0', 'CC-PDDC', 'CC0-1.0', 'CDDL-1.0', 'CDDL-1.1', 'CDL-1.0', 'CDLA-Permissive-1.0', 'CDLA-Permissive-2.0', 'CDLA-Sharing-1.0', 'CECILL-1.0', 'CECILL-1.1', 'CECILL-2.0', 'CECILL-2.1', 'CECILL-B', 'CECILL-C', 'CERN-OHL-1.1', 'CERN-OHL-1.2', 'CERN-OHL-P-2.0', 'CERN-OHL-S-2.0', 'CERN-OHL-W-2.0', 'CFITSIO', 'check-cvs', 'checkmk', 'ClArtistic', 'Clips', 'CMU-Mach', 'CMU-Mach-nodoc', 'CNRI-Jython', 'CNRI-Python', 'CNRI-Python-GPL-Compatible', 'COIL-1.0', 'Community-Spec-1.0', 'Condor-1.1', 'copyleft-next-0.3.0', 'copyleft-next-0.3.1', 'Cornell-Lossless-JPEG', 'CPAL-1.0', 'CPL-1.0', 'CPOL-1.02', 'Cronyx', 'Crossword', 'CrystalStacker', 'CUA-OPL-1.0', 'Cube', 'curl', 'cve-tou', 'D-FSL-1.0', 'DEC-3-Clause', 'diffmark', 'DL-DE-BY-2.0', 'DL-DE-ZERO-2.0', 'DOC', 'DocBook-Schema', 'DocBook-XML', 'Dotseqn', 'DRL-1.0', 'DRL-1.1', 'DSDP', 'dtoa', 'dvipdfm', 'ECL-1.0', 'ECL-2.0', 'EFL-1.0', 'EFL-2.0', 'eGenix', 'Elastic-2.0', 'Entessa', 'EPICS', 'EPL-1.0', 'EPL-2.0', 'ErlPL-1.1', 'etalab-2.0', 'EUDatagrid', 'EUPL-1.0', 'EUPL-1.1', 'EUPL-1.2', 'Eurosym', 'Fair', 'FBM', 'FDK-AAC', 'Ferguson-Twofish', 'Frameworx-1.0', 'FreeBSD-DOC', 'FreeImage', 'FSFAP', 'FSFAP-no-warranty-disclaimer', 'FSFUL', 'FSFULLR', 'FSFULLRWD', 'FTL', 'Furuseth', 'fwlw', 'GCR-docs', 'GD', 'GFDL-1.1-invariants-only', 'GFDL-1.1-invariants-or-later', 'GFDL-1.1-no-invariants-only', 'GFDL-1.1-no-invariants-or-later', 'GFDL-1.1-only', 'GFDL-1.1-or-later', 'GFDL-1.2-invariants-only', 'GFDL-1.2-invariants-or-later', 'GFDL-1.2-no-invariants-only', 'GFDL-1.2-no-invariants-or-later', 'GFDL-1.2-only', 'GFDL-1.2-or-later', 'GFDL-1.3-invariants-only', 'GFDL-1.3-invariants-or-later', 'GFDL-1.3-no-invariants-only', 'GFDL-1.3-no-invariants-or-later', 'GFDL-1.3-only', 'GFDL-1.3-or-later', 'Giftware', 'GL2PS', 'Glide', 'Glulxe', 'GLWTPL', 'gnuplot', 'GPL-1.0-only', 'GPL-1.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Graphics-Gems', 'gSOAP-1.3b', 'gtkbook', 'Gutmann', 'HaskellReport', 'hdparm', 'HIDAPI', 'Hippocratic-2.1', 'HP-1986', 'HP-1989', 'HPND', 'HPND-DEC', 'HPND-doc', 'HPND-doc-sell', 'HPND-export-US', 'HPND-export-US-acknowledgement', 'HPND-export-US-modify', 'HPND-export2-US', 'HPND-Fenneberg-Livingston', 'HPND-INRIA-IMAG', 'HPND-Intel', 'HPND-Kevlin-Henney', 'HPND-Markus-Kuhn', 'HPND-merchantability-variant', 'HPND-MIT-disclaimer', 'HPND-Netrek', 'HPND-Pbmplus', 'HPND-sell-MIT-disclaimer-xserver', 'HPND-sell-regexpr', 'HPND-sell-variant', 'HPND-sell-variant-MIT-disclaimer', 'HPND-sell-variant-MIT-disclaimer-rev', 'HPND-UC', 'HPND-UC-export-US', 'HTMLTIDY', 'IBM-pibs', 'ICU', 'IEC-Code-Components-EULA', 'IJG', 'IJG-short', 'ImageMagick', 'iMatix', 'Imlib2', 'Info-ZIP', 'Inner-Net-2.0', 'Intel', 'Intel-ACPI', 'Interbase-1.0', 'IPA', 'IPL-1.0', 'ISC', 'ISC-Veillard', 'Jam', 'JasPer-2.0', 'JPL-image', 'JPNIC', 'JSON', 'Kastrup', 'Kazlib', 'Knuth-CTAN', 'LAL-1.2', 'LAL-1.3', 'Latex2e', 'Latex2e-translated-notice', 'Leptonica', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'LGPLLR', 'Libpng', 'libpng-2.0', 'libselinux-1.0', 'libtiff', 'libutil-David-Nugent', 'LiLiQ-P-1.1', 'LiLiQ-R-1.1', 'LiLiQ-Rplus-1.1', 'Linux-man-pages-1-para', 'Linux-man-pages-copyleft', 'Linux-man-pages-copyleft-2-para', 'Linux-man-pages-copyleft-var', 'Linux-OpenIB', 'LOOP', 'LPD-document', 'LPL-1.0', 'LPL-1.02', 'LPPL-1.0', 'LPPL-1.1', 'LPPL-1.2', 'LPPL-1.3a', 'LPPL-1.3c', 'lsof', 'Lucida-Bitmap-Fonts', 'LZMA-SDK-9.11-to-9.20', 'LZMA-SDK-9.22', 'Mackerras-3-Clause', 'Mackerras-3-Clause-acknowledgment', 'magaz', 'mailprio', 'MakeIndex', 'Martin-Birgmeier', 'McPhee-slideshow', 'metamail', 'Minpack', 'MirOS', 'MIT', 'MIT-0', 'MIT-advertising', 'MIT-CMU', 'MIT-enna', 'MIT-feh', 'MIT-Festival', 'MIT-Khronos-old', 'MIT-Modern-Variant', 'MIT-open-group', 'MIT-testregex', 'MIT-Wu', 'MITNFA', 'MMIXware', 'Motosoto', 'MPEG-SSG', 'mpi-permissive', 'mpich2', 'MPL-1.0', 'MPL-1.1', 'MPL-2.0', 'MPL-2.0-no-copyleft-exception', 'mplus', 'MS-LPL', 'MS-PL', 'MS-RL', 'MTLL', 'MulanPSL-1.0', 'MulanPSL-2.0', 'Multics', 'Mup', 'NAIST-2003', 'NASA-1.3', 'Naumen', 'NBPL-1.0', 'NCBI-PD', 'NCGL-UK-2.0', 'NCL', 'NCSA', 'NetCDF', 'Newsletr', 'NGPL', 'NICTA-1.0', 'NIST-PD', 'NIST-PD-fallback', 'NIST-Software', 'NLOD-1.0', 'NLOD-2.0', 'NLPL', 'Nokia', 'NOSL', 'Noweb', 'NPL-1.0', 'NPL-1.1', 'NPOSL-3.0', 'NRL', 'NTP', 'NTP-0', 'O-UDA-1.0', 'OAR', 'OCCT-PL', 'OCLC-2.0', 'ODbL-1.0', 'ODC-By-1.0', 'OFFIS', 'OFL-1.0', 'OFL-1.0-no-RFN', 'OFL-1.0-RFN', 'OFL-1.1', 'OFL-1.1-no-RFN', 'OFL-1.1-RFN', 'OGC-1.0', 'OGDL-Taiwan-1.0', 'OGL-Canada-2.0', 'OGL-UK-1.0', 'OGL-UK-2.0', 'OGL-UK-3.0', 'OGTSL', 'OLDAP-1.1', 'OLDAP-1.2', 'OLDAP-1.3', 'OLDAP-1.4', 'OLDAP-2.0', 'OLDAP-2.0.1', 'OLDAP-2.1', 'OLDAP-2.2', 'OLDAP-2.2.1', 'OLDAP-2.2.2', 'OLDAP-2.3', 'OLDAP-2.4', 'OLDAP-2.5', 'OLDAP-2.6', 'OLDAP-2.7', 'OLDAP-2.8', 'OLFL-1.3', 'OML', 'OpenPBS-2.3', 'OpenSSL', 'OpenSSL-standalone', 'OpenVision', 'OPL-1.0', 'OPL-UK-3.0', 'OPUBL-1.0', 'OSET-PL-2.1', 'OSL-1.0', 'OSL-1.1', 'OSL-2.0', 'OSL-2.1', 'OSL-3.0', 'PADL', 'Parity-6.0.0', 'Parity-7.0.0', 'PDDL-1.0', 'PHP-3.0', 'PHP-3.01', 'Pixar', 'pkgconf', 'Plexus', 'pnmstitch', 'PolyForm-Noncommercial-1.0.0', 'PolyForm-Small-Business-1.0.0', 'PostgreSQL', 'PPL', 'PSF-2.0', 'psfrag', 'psutils', 'Python-2.0', 'Python-2.0.1', 'python-ldap', 'Qhull', 'QPL-1.0', 'QPL-1.0-INRIA-2004', 'radvd', 'Rdisc', 'RHeCos-1.1', 'RPL-1.1', 'RPL-1.5', 'RPSL-1.0', 'RSA-MD', 'RSCPL', 'Ruby', 'Ruby-pty', 'SAX-PD', 'SAX-PD-2.0', 'Saxpath', 'SCEA', 'SchemeReport', 'Sendmail', 'Sendmail-8.23', 'SGI-B-1.0', 'SGI-B-1.1', 'SGI-B-2.0', 'SGI-OpenGL', 'SGP4', 'SHL-0.5', 'SHL-0.51', 'SimPL-2.0', 'SISSL', 'SISSL-1.2', 'SL', 'Sleepycat', 'SMLNJ', 'SMPPL', 'SNIA', 'snprintf', 'softSurfer', 'Soundex', 'Spencer-86', 'Spencer-94', 'Spencer-99', 'SPL-1.0', 'ssh-keyscan', 'SSH-OpenSSH', 'SSH-short', 'SSLeay-standalone', 'SSPL-1.0', 'SugarCRM-1.1.3', 'Sun-PPP', 'Sun-PPP-2000', 'SunPro', 'SWL', 'swrule', 'Symlinks', 'TAPR-OHL-1.0', 'TCL', 'TCP-wrappers', 'TermReadKey', 'TGPPL-1.0', 'threeparttable', 'TMate', 'TORQUE-1.1', 'TOSL', 'TPDL', 'TPL-1.0', 'TTWL', 'TTYP0', 'TU-Berlin-1.0', 'TU-Berlin-2.0', 'Ubuntu-font-1.0', 'UCAR', 'UCL-1.0', 'ulem', 'UMich-Merit', 'Unicode-3.0', 'Unicode-DFS-2015', 'Unicode-DFS-2016', 'Unicode-TOU', 'UnixCrypt', 'Unlicense', 'UPL-1.0', 'URT-RLE', 'Vim', 'VOSTROM', 'VSL-1.0', 'W3C', 'W3C-19980720', 'W3C-20150513', 'w3m', 'Watcom-1.0', 'Widget-Workshop', 'Wsuipa', 'WTFPL', 'X11', 'X11-distribute-modifications-variant', 'X11-swapped', 'Xdebug-1.03', 'Xerox', 'Xfig', 'XFree86-1.1', 'xinetd', 'xkeyboard-config-Zinoviev', 'xlock', 'Xnet', 'xpp', 'XSkat', 'xzoom', 'YPL-1.0', 'YPL-1.1', 'Zed', 'Zeeff', 'Zend-2.0', 'Zimbra-1.3', 'Zimbra-1.4', 'Zlib', 'zlib-acknowledgement', 'ZPL-1.1', 'ZPL-2.0', 'ZPL-2.1'} -def generate_sbom(conanfile): +def generate_sbom(conanfile, out_file, **kwargs): name = conanfile.ref.name version = conanfile.ref.version date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') - packages_to_check = [conanfile] packages = [] - while packages_to_check: - dependency = packages_to_check.pop() + + for dependency in [conanfile] + conanfile.dependencies.values(): packages.append( { "name": dependency.ref.name, @@ -28,7 +27,6 @@ def generate_sbom(conanfile): "version": str(dependency.ref.version), "license": dependency.license if dependency.license in spdx_licences else "NOASSERTION", }) - packages_to_check += [dependency for require, dependency in dependency.dependencies.items()] files = [] # https://spdx.github.io/spdx-spec/v2.2.2/package-information/ data = { @@ -57,12 +55,8 @@ def generate_sbom(conanfile): # "FileCopyrightText": "NOASSERTION" # } for f in files], } - path = "./spdx/" - file_name = f"{conanfile.ref}-SPDX.json".replace("/", "-") try: - if not os.path.exists(path): - os.makedirs(path) - with open(os.path.join(path, file_name), 'w') as f: + with open(out_path, 'w') as f: json.dump(data, f, indent=4) except Exception as e: ConanException("error generating spdx file") From 00c578111215975072658241a68cce34cf2f7c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Fri, 25 Oct 2024 15:36:49 +0200 Subject: [PATCH 10/28] Cleanup --- conan/internal/api/install/generators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index 2b0f809c96d..9a85c73e710 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -82,7 +82,7 @@ def write_generators(conanfile, app, envs_generation=None): hook_manager.execute("pre_generate", conanfile=conanfile) # Generate sbom - _generate_sbom(conanfile._conan_helpers.home_folder, conanfile) + _generate_sbom(conanfile) if conanfile.generators: conanfile.output.highlight(f"Writing generators to {new_gen_folder}") @@ -156,7 +156,8 @@ def _receive_conf(conanfile): conanfile.conf.compose_conf(build_require.conf_info) -def _generate_sbom(cache_folder, conanfile): +def _generate_sbom(conanfile): + cache_folder = conanfile._conan_helpers.home_folder, from conans.client.loader import load_python_file sbom_plugin_path = HomePaths(cache_folder).sbom_manifest_plugin_path if os.path.exists(sbom_plugin_path): From b7f90eddb301dffffa75e7846bd44515ac0f104c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Fri, 25 Oct 2024 15:38:08 +0200 Subject: [PATCH 11/28] Update conans/client/graph/sbom.py --- conans/client/graph/sbom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py index ef669f5a41b..f66c99840a8 100644 --- a/conans/client/graph/sbom.py +++ b/conans/client/graph/sbom.py @@ -56,7 +56,7 @@ def generate_sbom(conanfile, out_file, **kwargs): # } for f in files], } try: - with open(out_path, 'w') as f: + with open(out_file, 'w') as f: json.dump(data, f, indent=4) except Exception as e: ConanException("error generating spdx file") From 51f1189e43113c24867ca753bbb437246ad6548e Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 31 Oct 2024 14:02:15 +0100 Subject: [PATCH 12/28] rename and move sbom method, move spdx script to different file and --- conan/cli/commands/install.py | 1 - conan/internal/api/install/generators.py | 16 +++--- conans/client/graph/sbom.py | 68 ++---------------------- conans/client/graph/spdx.py | 58 ++++++++++++++++++++ 4 files changed, 68 insertions(+), 75 deletions(-) create mode 100644 conans/client/graph/spdx.py diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index 884b305058d..faff8ed2a96 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -84,6 +84,5 @@ def install(conan_api, parser, *args): lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages, clean=args.lockfile_clean) conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd) - return {"graph": deps_graph, "conan_api": conan_api} diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index 9a85c73e710..b6289829b42 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -81,9 +81,6 @@ def write_generators(conanfile, app, envs_generation=None): global_generators = load_cache_generators(HomePaths(app.cache_folder).custom_generators_path) hook_manager.execute("pre_generate", conanfile=conanfile) - # Generate sbom - _generate_sbom(conanfile) - if conanfile.generators: conanfile.output.highlight(f"Writing generators to {new_gen_folder}") # generators check that they are not present in the generators field, @@ -140,6 +137,8 @@ def write_generators(conanfile, app, envs_generation=None): _generate_aggregated_env(conanfile) + _generate_graph_manifests(conanfile) + hook_manager.execute("post_generate", conanfile=conanfile) @@ -156,8 +155,8 @@ def _receive_conf(conanfile): conanfile.conf.compose_conf(build_require.conf_info) -def _generate_sbom(conanfile): - cache_folder = conanfile._conan_helpers.home_folder, +def _generate_graph_manifests(conanfile): + cache_folder = conanfile._conan_helpers.home_folder, from conans.client.loader import load_python_file sbom_plugin_path = HomePaths(cache_folder).sbom_manifest_plugin_path if os.path.exists(sbom_plugin_path): @@ -170,12 +169,9 @@ def _generate_sbom(conanfile): raise ConanException( f"SBOM manifest plugin 'generate_sbom' is not a function") - ConanOutput().warning(f"generating sbom") - # TODO: Think where it makes sense to generate the file at this point - safe_ref_filename = str(conan.ref).replace("/", "_").replace(".", "_") - outfile = os.path.join(os.path.curdir, f"sbom_{safe_ref_filename}.json") # Temp + ConanOutput().warning(f"generating sbom", warn_tag="experimental") # TODO think if this is conanfile or conanfile._conan_node - return mod.generate_sbom(conanfile, outfile) + return mod.generate_sbom(conanfile) def _generate_aggregated_env(conanfile): diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py index f66c99840a8..2f384daef49 100644 --- a/conans/client/graph/sbom.py +++ b/conans/client/graph/sbom.py @@ -2,69 +2,9 @@ from conan.internal.cache.home_paths import HomePaths -_default_spdx_json = """ -import time -import json -import os -from datetime import datetime, timezone -from conan import conan_version -from conan.errors import ConanException - -# https://spdx.org/licenses/ -spdx_licences = {'0BSD', '3D-Slicer-1.0', 'AAL', 'Abstyles', 'AdaCore-doc', 'Adobe-2006', 'Adobe-Display-PostScript', 'Adobe-Glyph', 'Adobe-Utopia', 'ADSL', 'AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', 'Afmparse', 'AGPL-1.0-only', 'AGPL-1.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'Aladdin', 'AMD-newlib', 'AMDPLPA', 'AML', 'AML-glslang', 'AMPAS', 'ANTLR-PD', 'ANTLR-PD-fallback', 'any-OSI', 'Apache-1.0', 'Apache-1.1', 'Apache-2.0', 'APAFML', 'APL-1.0', 'App-s2p', 'APSL-1.0', 'APSL-1.1', 'APSL-1.2', 'APSL-2.0', 'Arphic-1999', 'Artistic-1.0', 'Artistic-1.0-cl8', 'Artistic-1.0-Perl', 'Artistic-2.0', 'ASWF-Digital-Assets-1.0', 'ASWF-Digital-Assets-1.1', 'Baekmuk', 'Bahyph', 'Barr', 'bcrypt-Solar-Designer', 'Beerware', 'Bitstream-Charter', 'Bitstream-Vera', 'BitTorrent-1.0', 'BitTorrent-1.1', 'blessing', 'BlueOak-1.0.0', 'Boehm-GC', 'Borceux', 'Brian-Gladman-2-Clause', 'Brian-Gladman-3-Clause', 'BSD-1-Clause', 'BSD-2-Clause', 'BSD-2-Clause-Darwin', 'BSD-2-Clause-first-lines', 'BSD-2-Clause-Patent', 'BSD-2-Clause-Views', 'BSD-3-Clause', 'BSD-3-Clause-acpica', 'BSD-3-Clause-Attribution', 'BSD-3-Clause-Clear', 'BSD-3-Clause-flex', 'BSD-3-Clause-HP', 'BSD-3-Clause-LBNL', 'BSD-3-Clause-Modification', 'BSD-3-Clause-No-Military-License', 'BSD-3-Clause-No-Nuclear-License', 'BSD-3-Clause-No-Nuclear-License-2014', 'BSD-3-Clause-No-Nuclear-Warranty', 'BSD-3-Clause-Open-MPI', 'BSD-3-Clause-Sun', 'BSD-4-Clause', 'BSD-4-Clause-Shortened', 'BSD-4-Clause-UC', 'BSD-4.3RENO', 'BSD-4.3TAHOE', 'BSD-Advertising-Acknowledgement', 'BSD-Attribution-HPND-disclaimer', 'BSD-Inferno-Nettverk', 'BSD-Protection', 'BSD-Source-beginning-file', 'BSD-Source-Code', 'BSD-Systemics', 'BSD-Systemics-W3Works', 'BSL-1.0', 'BUSL-1.1', 'bzip2-1.0.6', 'C-UDA-1.0', 'CAL-1.0', 'CAL-1.0-Combined-Work-Exception', 'Caldera', 'Caldera-no-preamble', 'Catharon', 'CATOSL-1.1', 'CC-BY-1.0', 'CC-BY-2.0', 'CC-BY-2.5', 'CC-BY-2.5-AU', 'CC-BY-3.0', 'CC-BY-3.0-AT', 'CC-BY-3.0-AU', 'CC-BY-3.0-DE', 'CC-BY-3.0-IGO', 'CC-BY-3.0-NL', 'CC-BY-3.0-US', 'CC-BY-4.0', 'CC-BY-NC-1.0', 'CC-BY-NC-2.0', 'CC-BY-NC-2.5', 'CC-BY-NC-3.0', 'CC-BY-NC-3.0-DE', 'CC-BY-NC-4.0', 'CC-BY-NC-ND-1.0', 'CC-BY-NC-ND-2.0', 'CC-BY-NC-ND-2.5', 'CC-BY-NC-ND-3.0', 'CC-BY-NC-ND-3.0-DE', 'CC-BY-NC-ND-3.0-IGO', 'CC-BY-NC-ND-4.0', 'CC-BY-NC-SA-1.0', 'CC-BY-NC-SA-2.0', 'CC-BY-NC-SA-2.0-DE', 'CC-BY-NC-SA-2.0-FR', 'CC-BY-NC-SA-2.0-UK', 'CC-BY-NC-SA-2.5', 'CC-BY-NC-SA-3.0', 'CC-BY-NC-SA-3.0-DE', 'CC-BY-NC-SA-3.0-IGO', 'CC-BY-NC-SA-4.0', 'CC-BY-ND-1.0', 'CC-BY-ND-2.0', 'CC-BY-ND-2.5', 'CC-BY-ND-3.0', 'CC-BY-ND-3.0-DE', 'CC-BY-ND-4.0', 'CC-BY-SA-1.0', 'CC-BY-SA-2.0', 'CC-BY-SA-2.0-UK', 'CC-BY-SA-2.1-JP', 'CC-BY-SA-2.5', 'CC-BY-SA-3.0', 'CC-BY-SA-3.0-AT', 'CC-BY-SA-3.0-DE', 'CC-BY-SA-3.0-IGO', 'CC-BY-SA-4.0', 'CC-PDDC', 'CC0-1.0', 'CDDL-1.0', 'CDDL-1.1', 'CDL-1.0', 'CDLA-Permissive-1.0', 'CDLA-Permissive-2.0', 'CDLA-Sharing-1.0', 'CECILL-1.0', 'CECILL-1.1', 'CECILL-2.0', 'CECILL-2.1', 'CECILL-B', 'CECILL-C', 'CERN-OHL-1.1', 'CERN-OHL-1.2', 'CERN-OHL-P-2.0', 'CERN-OHL-S-2.0', 'CERN-OHL-W-2.0', 'CFITSIO', 'check-cvs', 'checkmk', 'ClArtistic', 'Clips', 'CMU-Mach', 'CMU-Mach-nodoc', 'CNRI-Jython', 'CNRI-Python', 'CNRI-Python-GPL-Compatible', 'COIL-1.0', 'Community-Spec-1.0', 'Condor-1.1', 'copyleft-next-0.3.0', 'copyleft-next-0.3.1', 'Cornell-Lossless-JPEG', 'CPAL-1.0', 'CPL-1.0', 'CPOL-1.02', 'Cronyx', 'Crossword', 'CrystalStacker', 'CUA-OPL-1.0', 'Cube', 'curl', 'cve-tou', 'D-FSL-1.0', 'DEC-3-Clause', 'diffmark', 'DL-DE-BY-2.0', 'DL-DE-ZERO-2.0', 'DOC', 'DocBook-Schema', 'DocBook-XML', 'Dotseqn', 'DRL-1.0', 'DRL-1.1', 'DSDP', 'dtoa', 'dvipdfm', 'ECL-1.0', 'ECL-2.0', 'EFL-1.0', 'EFL-2.0', 'eGenix', 'Elastic-2.0', 'Entessa', 'EPICS', 'EPL-1.0', 'EPL-2.0', 'ErlPL-1.1', 'etalab-2.0', 'EUDatagrid', 'EUPL-1.0', 'EUPL-1.1', 'EUPL-1.2', 'Eurosym', 'Fair', 'FBM', 'FDK-AAC', 'Ferguson-Twofish', 'Frameworx-1.0', 'FreeBSD-DOC', 'FreeImage', 'FSFAP', 'FSFAP-no-warranty-disclaimer', 'FSFUL', 'FSFULLR', 'FSFULLRWD', 'FTL', 'Furuseth', 'fwlw', 'GCR-docs', 'GD', 'GFDL-1.1-invariants-only', 'GFDL-1.1-invariants-or-later', 'GFDL-1.1-no-invariants-only', 'GFDL-1.1-no-invariants-or-later', 'GFDL-1.1-only', 'GFDL-1.1-or-later', 'GFDL-1.2-invariants-only', 'GFDL-1.2-invariants-or-later', 'GFDL-1.2-no-invariants-only', 'GFDL-1.2-no-invariants-or-later', 'GFDL-1.2-only', 'GFDL-1.2-or-later', 'GFDL-1.3-invariants-only', 'GFDL-1.3-invariants-or-later', 'GFDL-1.3-no-invariants-only', 'GFDL-1.3-no-invariants-or-later', 'GFDL-1.3-only', 'GFDL-1.3-or-later', 'Giftware', 'GL2PS', 'Glide', 'Glulxe', 'GLWTPL', 'gnuplot', 'GPL-1.0-only', 'GPL-1.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Graphics-Gems', 'gSOAP-1.3b', 'gtkbook', 'Gutmann', 'HaskellReport', 'hdparm', 'HIDAPI', 'Hippocratic-2.1', 'HP-1986', 'HP-1989', 'HPND', 'HPND-DEC', 'HPND-doc', 'HPND-doc-sell', 'HPND-export-US', 'HPND-export-US-acknowledgement', 'HPND-export-US-modify', 'HPND-export2-US', 'HPND-Fenneberg-Livingston', 'HPND-INRIA-IMAG', 'HPND-Intel', 'HPND-Kevlin-Henney', 'HPND-Markus-Kuhn', 'HPND-merchantability-variant', 'HPND-MIT-disclaimer', 'HPND-Netrek', 'HPND-Pbmplus', 'HPND-sell-MIT-disclaimer-xserver', 'HPND-sell-regexpr', 'HPND-sell-variant', 'HPND-sell-variant-MIT-disclaimer', 'HPND-sell-variant-MIT-disclaimer-rev', 'HPND-UC', 'HPND-UC-export-US', 'HTMLTIDY', 'IBM-pibs', 'ICU', 'IEC-Code-Components-EULA', 'IJG', 'IJG-short', 'ImageMagick', 'iMatix', 'Imlib2', 'Info-ZIP', 'Inner-Net-2.0', 'Intel', 'Intel-ACPI', 'Interbase-1.0', 'IPA', 'IPL-1.0', 'ISC', 'ISC-Veillard', 'Jam', 'JasPer-2.0', 'JPL-image', 'JPNIC', 'JSON', 'Kastrup', 'Kazlib', 'Knuth-CTAN', 'LAL-1.2', 'LAL-1.3', 'Latex2e', 'Latex2e-translated-notice', 'Leptonica', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'LGPLLR', 'Libpng', 'libpng-2.0', 'libselinux-1.0', 'libtiff', 'libutil-David-Nugent', 'LiLiQ-P-1.1', 'LiLiQ-R-1.1', 'LiLiQ-Rplus-1.1', 'Linux-man-pages-1-para', 'Linux-man-pages-copyleft', 'Linux-man-pages-copyleft-2-para', 'Linux-man-pages-copyleft-var', 'Linux-OpenIB', 'LOOP', 'LPD-document', 'LPL-1.0', 'LPL-1.02', 'LPPL-1.0', 'LPPL-1.1', 'LPPL-1.2', 'LPPL-1.3a', 'LPPL-1.3c', 'lsof', 'Lucida-Bitmap-Fonts', 'LZMA-SDK-9.11-to-9.20', 'LZMA-SDK-9.22', 'Mackerras-3-Clause', 'Mackerras-3-Clause-acknowledgment', 'magaz', 'mailprio', 'MakeIndex', 'Martin-Birgmeier', 'McPhee-slideshow', 'metamail', 'Minpack', 'MirOS', 'MIT', 'MIT-0', 'MIT-advertising', 'MIT-CMU', 'MIT-enna', 'MIT-feh', 'MIT-Festival', 'MIT-Khronos-old', 'MIT-Modern-Variant', 'MIT-open-group', 'MIT-testregex', 'MIT-Wu', 'MITNFA', 'MMIXware', 'Motosoto', 'MPEG-SSG', 'mpi-permissive', 'mpich2', 'MPL-1.0', 'MPL-1.1', 'MPL-2.0', 'MPL-2.0-no-copyleft-exception', 'mplus', 'MS-LPL', 'MS-PL', 'MS-RL', 'MTLL', 'MulanPSL-1.0', 'MulanPSL-2.0', 'Multics', 'Mup', 'NAIST-2003', 'NASA-1.3', 'Naumen', 'NBPL-1.0', 'NCBI-PD', 'NCGL-UK-2.0', 'NCL', 'NCSA', 'NetCDF', 'Newsletr', 'NGPL', 'NICTA-1.0', 'NIST-PD', 'NIST-PD-fallback', 'NIST-Software', 'NLOD-1.0', 'NLOD-2.0', 'NLPL', 'Nokia', 'NOSL', 'Noweb', 'NPL-1.0', 'NPL-1.1', 'NPOSL-3.0', 'NRL', 'NTP', 'NTP-0', 'O-UDA-1.0', 'OAR', 'OCCT-PL', 'OCLC-2.0', 'ODbL-1.0', 'ODC-By-1.0', 'OFFIS', 'OFL-1.0', 'OFL-1.0-no-RFN', 'OFL-1.0-RFN', 'OFL-1.1', 'OFL-1.1-no-RFN', 'OFL-1.1-RFN', 'OGC-1.0', 'OGDL-Taiwan-1.0', 'OGL-Canada-2.0', 'OGL-UK-1.0', 'OGL-UK-2.0', 'OGL-UK-3.0', 'OGTSL', 'OLDAP-1.1', 'OLDAP-1.2', 'OLDAP-1.3', 'OLDAP-1.4', 'OLDAP-2.0', 'OLDAP-2.0.1', 'OLDAP-2.1', 'OLDAP-2.2', 'OLDAP-2.2.1', 'OLDAP-2.2.2', 'OLDAP-2.3', 'OLDAP-2.4', 'OLDAP-2.5', 'OLDAP-2.6', 'OLDAP-2.7', 'OLDAP-2.8', 'OLFL-1.3', 'OML', 'OpenPBS-2.3', 'OpenSSL', 'OpenSSL-standalone', 'OpenVision', 'OPL-1.0', 'OPL-UK-3.0', 'OPUBL-1.0', 'OSET-PL-2.1', 'OSL-1.0', 'OSL-1.1', 'OSL-2.0', 'OSL-2.1', 'OSL-3.0', 'PADL', 'Parity-6.0.0', 'Parity-7.0.0', 'PDDL-1.0', 'PHP-3.0', 'PHP-3.01', 'Pixar', 'pkgconf', 'Plexus', 'pnmstitch', 'PolyForm-Noncommercial-1.0.0', 'PolyForm-Small-Business-1.0.0', 'PostgreSQL', 'PPL', 'PSF-2.0', 'psfrag', 'psutils', 'Python-2.0', 'Python-2.0.1', 'python-ldap', 'Qhull', 'QPL-1.0', 'QPL-1.0-INRIA-2004', 'radvd', 'Rdisc', 'RHeCos-1.1', 'RPL-1.1', 'RPL-1.5', 'RPSL-1.0', 'RSA-MD', 'RSCPL', 'Ruby', 'Ruby-pty', 'SAX-PD', 'SAX-PD-2.0', 'Saxpath', 'SCEA', 'SchemeReport', 'Sendmail', 'Sendmail-8.23', 'SGI-B-1.0', 'SGI-B-1.1', 'SGI-B-2.0', 'SGI-OpenGL', 'SGP4', 'SHL-0.5', 'SHL-0.51', 'SimPL-2.0', 'SISSL', 'SISSL-1.2', 'SL', 'Sleepycat', 'SMLNJ', 'SMPPL', 'SNIA', 'snprintf', 'softSurfer', 'Soundex', 'Spencer-86', 'Spencer-94', 'Spencer-99', 'SPL-1.0', 'ssh-keyscan', 'SSH-OpenSSH', 'SSH-short', 'SSLeay-standalone', 'SSPL-1.0', 'SugarCRM-1.1.3', 'Sun-PPP', 'Sun-PPP-2000', 'SunPro', 'SWL', 'swrule', 'Symlinks', 'TAPR-OHL-1.0', 'TCL', 'TCP-wrappers', 'TermReadKey', 'TGPPL-1.0', 'threeparttable', 'TMate', 'TORQUE-1.1', 'TOSL', 'TPDL', 'TPL-1.0', 'TTWL', 'TTYP0', 'TU-Berlin-1.0', 'TU-Berlin-2.0', 'Ubuntu-font-1.0', 'UCAR', 'UCL-1.0', 'ulem', 'UMich-Merit', 'Unicode-3.0', 'Unicode-DFS-2015', 'Unicode-DFS-2016', 'Unicode-TOU', 'UnixCrypt', 'Unlicense', 'UPL-1.0', 'URT-RLE', 'Vim', 'VOSTROM', 'VSL-1.0', 'W3C', 'W3C-19980720', 'W3C-20150513', 'w3m', 'Watcom-1.0', 'Widget-Workshop', 'Wsuipa', 'WTFPL', 'X11', 'X11-distribute-modifications-variant', 'X11-swapped', 'Xdebug-1.03', 'Xerox', 'Xfig', 'XFree86-1.1', 'xinetd', 'xkeyboard-config-Zinoviev', 'xlock', 'Xnet', 'xpp', 'XSkat', 'xzoom', 'YPL-1.0', 'YPL-1.1', 'Zed', 'Zeeff', 'Zend-2.0', 'Zimbra-1.3', 'Zimbra-1.4', 'Zlib', 'zlib-acknowledgement', 'ZPL-1.1', 'ZPL-2.0', 'ZPL-2.1'} - -def generate_sbom(conanfile, out_file, **kwargs): - name = conanfile.ref.name - version = conanfile.ref.version - date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') - packages = [] - - for dependency in [conanfile] + conanfile.dependencies.values(): - packages.append( - { - "name": dependency.ref.name, - "SPDXID": f"SPDXRef-{dependency.ref}", - "version": str(dependency.ref.version), - "license": dependency.license if dependency.license in spdx_licences else "NOASSERTION", - }) - files = [] - # https://spdx.github.io/spdx-spec/v2.2.2/package-information/ - data = { - "SPDXVersion": "SPDX-2.2", - "DataLicense": "CC0-1.0", - "SPDXID": "SPDXRef-DOCUMENT", - "DocumentName": f"{name}-{version}", - "DocumentNamespace": f"http://spdx.org/spdxdocs/{name}-{version}-{date}", # the date or hash to make it unique - "Creator": f"Tool: Conan-{conan_version}", - "Created": date, #YYYY-MM-DDThh:mm:ssZ - "Packages": [{ - "PackageName": p["name"], - "SPDXID": p["SPDXID"], - "PackageVersion": p["version"], - "PackageDownloadLocation": "NOASSERTION", - "FilesAnalyzed": False, - "PackageLicenseConcluded": p["license"], - "PackageLicenseDeclared": p["license"], - } for p in packages], - # "Files": [{ - # "FileName": f["path"], # Path to file - # "SPDXID": f["SPDXID"], - # "FileChecksum": f'{f["checksum_algorithm"]}: {f["checksum_algorithm_value"]}', - # "LicenseConcluded": f["licence"], - # "LicenseInfoInFile": f["licence"], - # "FileCopyrightText": "NOASSERTION" - # } for f in files], - } - try: - with open(out_file, 'w') as f: - json.dump(data, f, indent=4) - except Exception as e: - ConanException("error generating spdx file") - -""" - - def migrate_sbom_file(cache_folder): from conans.client.migrations import update_file - sbom_path = HomePaths(cache_folder).sbom_manifest_plugin_path - update_file(sbom_path, _default_spdx_json) + with open("spdx.py", "r") as file: + default_spdx_json = file.read() + sbom_path = HomePaths(cache_folder).sbom_manifest_plugin_path + update_file(sbom_path, default_spdx_json) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py new file mode 100644 index 00000000000..75fc5f1bbc7 --- /dev/null +++ b/conans/client/graph/spdx.py @@ -0,0 +1,58 @@ + +import time +import json +import os +from datetime import datetime, timezone +from conan import conan_version +from conan.errors import ConanException + +# https://spdx.org/licenses/ +spdx_licences = {'0BSD', '3D-Slicer-1.0', 'AAL', 'Abstyles', 'AdaCore-doc', 'Adobe-2006', 'Adobe-Display-PostScript', 'Adobe-Glyph', 'Adobe-Utopia', 'ADSL', 'AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', 'Afmparse', 'AGPL-1.0-only', 'AGPL-1.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'Aladdin', 'AMD-newlib', 'AMDPLPA', 'AML', 'AML-glslang', 'AMPAS', 'ANTLR-PD', 'ANTLR-PD-fallback', 'any-OSI', 'Apache-1.0', 'Apache-1.1', 'Apache-2.0', 'APAFML', 'APL-1.0', 'App-s2p', 'APSL-1.0', 'APSL-1.1', 'APSL-1.2', 'APSL-2.0', 'Arphic-1999', 'Artistic-1.0', 'Artistic-1.0-cl8', 'Artistic-1.0-Perl', 'Artistic-2.0', 'ASWF-Digital-Assets-1.0', 'ASWF-Digital-Assets-1.1', 'Baekmuk', 'Bahyph', 'Barr', 'bcrypt-Solar-Designer', 'Beerware', 'Bitstream-Charter', 'Bitstream-Vera', 'BitTorrent-1.0', 'BitTorrent-1.1', 'blessing', 'BlueOak-1.0.0', 'Boehm-GC', 'Borceux', 'Brian-Gladman-2-Clause', 'Brian-Gladman-3-Clause', 'BSD-1-Clause', 'BSD-2-Clause', 'BSD-2-Clause-Darwin', 'BSD-2-Clause-first-lines', 'BSD-2-Clause-Patent', 'BSD-2-Clause-Views', 'BSD-3-Clause', 'BSD-3-Clause-acpica', 'BSD-3-Clause-Attribution', 'BSD-3-Clause-Clear', 'BSD-3-Clause-flex', 'BSD-3-Clause-HP', 'BSD-3-Clause-LBNL', 'BSD-3-Clause-Modification', 'BSD-3-Clause-No-Military-License', 'BSD-3-Clause-No-Nuclear-License', 'BSD-3-Clause-No-Nuclear-License-2014', 'BSD-3-Clause-No-Nuclear-Warranty', 'BSD-3-Clause-Open-MPI', 'BSD-3-Clause-Sun', 'BSD-4-Clause', 'BSD-4-Clause-Shortened', 'BSD-4-Clause-UC', 'BSD-4.3RENO', 'BSD-4.3TAHOE', 'BSD-Advertising-Acknowledgement', 'BSD-Attribution-HPND-disclaimer', 'BSD-Inferno-Nettverk', 'BSD-Protection', 'BSD-Source-beginning-file', 'BSD-Source-Code', 'BSD-Systemics', 'BSD-Systemics-W3Works', 'BSL-1.0', 'BUSL-1.1', 'bzip2-1.0.6', 'C-UDA-1.0', 'CAL-1.0', 'CAL-1.0-Combined-Work-Exception', 'Caldera', 'Caldera-no-preamble', 'Catharon', 'CATOSL-1.1', 'CC-BY-1.0', 'CC-BY-2.0', 'CC-BY-2.5', 'CC-BY-2.5-AU', 'CC-BY-3.0', 'CC-BY-3.0-AT', 'CC-BY-3.0-AU', 'CC-BY-3.0-DE', 'CC-BY-3.0-IGO', 'CC-BY-3.0-NL', 'CC-BY-3.0-US', 'CC-BY-4.0', 'CC-BY-NC-1.0', 'CC-BY-NC-2.0', 'CC-BY-NC-2.5', 'CC-BY-NC-3.0', 'CC-BY-NC-3.0-DE', 'CC-BY-NC-4.0', 'CC-BY-NC-ND-1.0', 'CC-BY-NC-ND-2.0', 'CC-BY-NC-ND-2.5', 'CC-BY-NC-ND-3.0', 'CC-BY-NC-ND-3.0-DE', 'CC-BY-NC-ND-3.0-IGO', 'CC-BY-NC-ND-4.0', 'CC-BY-NC-SA-1.0', 'CC-BY-NC-SA-2.0', 'CC-BY-NC-SA-2.0-DE', 'CC-BY-NC-SA-2.0-FR', 'CC-BY-NC-SA-2.0-UK', 'CC-BY-NC-SA-2.5', 'CC-BY-NC-SA-3.0', 'CC-BY-NC-SA-3.0-DE', 'CC-BY-NC-SA-3.0-IGO', 'CC-BY-NC-SA-4.0', 'CC-BY-ND-1.0', 'CC-BY-ND-2.0', 'CC-BY-ND-2.5', 'CC-BY-ND-3.0', 'CC-BY-ND-3.0-DE', 'CC-BY-ND-4.0', 'CC-BY-SA-1.0', 'CC-BY-SA-2.0', 'CC-BY-SA-2.0-UK', 'CC-BY-SA-2.1-JP', 'CC-BY-SA-2.5', 'CC-BY-SA-3.0', 'CC-BY-SA-3.0-AT', 'CC-BY-SA-3.0-DE', 'CC-BY-SA-3.0-IGO', 'CC-BY-SA-4.0', 'CC-PDDC', 'CC0-1.0', 'CDDL-1.0', 'CDDL-1.1', 'CDL-1.0', 'CDLA-Permissive-1.0', 'CDLA-Permissive-2.0', 'CDLA-Sharing-1.0', 'CECILL-1.0', 'CECILL-1.1', 'CECILL-2.0', 'CECILL-2.1', 'CECILL-B', 'CECILL-C', 'CERN-OHL-1.1', 'CERN-OHL-1.2', 'CERN-OHL-P-2.0', 'CERN-OHL-S-2.0', 'CERN-OHL-W-2.0', 'CFITSIO', 'check-cvs', 'checkmk', 'ClArtistic', 'Clips', 'CMU-Mach', 'CMU-Mach-nodoc', 'CNRI-Jython', 'CNRI-Python', 'CNRI-Python-GPL-Compatible', 'COIL-1.0', 'Community-Spec-1.0', 'Condor-1.1', 'copyleft-next-0.3.0', 'copyleft-next-0.3.1', 'Cornell-Lossless-JPEG', 'CPAL-1.0', 'CPL-1.0', 'CPOL-1.02', 'Cronyx', 'Crossword', 'CrystalStacker', 'CUA-OPL-1.0', 'Cube', 'curl', 'cve-tou', 'D-FSL-1.0', 'DEC-3-Clause', 'diffmark', 'DL-DE-BY-2.0', 'DL-DE-ZERO-2.0', 'DOC', 'DocBook-Schema', 'DocBook-XML', 'Dotseqn', 'DRL-1.0', 'DRL-1.1', 'DSDP', 'dtoa', 'dvipdfm', 'ECL-1.0', 'ECL-2.0', 'EFL-1.0', 'EFL-2.0', 'eGenix', 'Elastic-2.0', 'Entessa', 'EPICS', 'EPL-1.0', 'EPL-2.0', 'ErlPL-1.1', 'etalab-2.0', 'EUDatagrid', 'EUPL-1.0', 'EUPL-1.1', 'EUPL-1.2', 'Eurosym', 'Fair', 'FBM', 'FDK-AAC', 'Ferguson-Twofish', 'Frameworx-1.0', 'FreeBSD-DOC', 'FreeImage', 'FSFAP', 'FSFAP-no-warranty-disclaimer', 'FSFUL', 'FSFULLR', 'FSFULLRWD', 'FTL', 'Furuseth', 'fwlw', 'GCR-docs', 'GD', 'GFDL-1.1-invariants-only', 'GFDL-1.1-invariants-or-later', 'GFDL-1.1-no-invariants-only', 'GFDL-1.1-no-invariants-or-later', 'GFDL-1.1-only', 'GFDL-1.1-or-later', 'GFDL-1.2-invariants-only', 'GFDL-1.2-invariants-or-later', 'GFDL-1.2-no-invariants-only', 'GFDL-1.2-no-invariants-or-later', 'GFDL-1.2-only', 'GFDL-1.2-or-later', 'GFDL-1.3-invariants-only', 'GFDL-1.3-invariants-or-later', 'GFDL-1.3-no-invariants-only', 'GFDL-1.3-no-invariants-or-later', 'GFDL-1.3-only', 'GFDL-1.3-or-later', 'Giftware', 'GL2PS', 'Glide', 'Glulxe', 'GLWTPL', 'gnuplot', 'GPL-1.0-only', 'GPL-1.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Graphics-Gems', 'gSOAP-1.3b', 'gtkbook', 'Gutmann', 'HaskellReport', 'hdparm', 'HIDAPI', 'Hippocratic-2.1', 'HP-1986', 'HP-1989', 'HPND', 'HPND-DEC', 'HPND-doc', 'HPND-doc-sell', 'HPND-export-US', 'HPND-export-US-acknowledgement', 'HPND-export-US-modify', 'HPND-export2-US', 'HPND-Fenneberg-Livingston', 'HPND-INRIA-IMAG', 'HPND-Intel', 'HPND-Kevlin-Henney', 'HPND-Markus-Kuhn', 'HPND-merchantability-variant', 'HPND-MIT-disclaimer', 'HPND-Netrek', 'HPND-Pbmplus', 'HPND-sell-MIT-disclaimer-xserver', 'HPND-sell-regexpr', 'HPND-sell-variant', 'HPND-sell-variant-MIT-disclaimer', 'HPND-sell-variant-MIT-disclaimer-rev', 'HPND-UC', 'HPND-UC-export-US', 'HTMLTIDY', 'IBM-pibs', 'ICU', 'IEC-Code-Components-EULA', 'IJG', 'IJG-short', 'ImageMagick', 'iMatix', 'Imlib2', 'Info-ZIP', 'Inner-Net-2.0', 'Intel', 'Intel-ACPI', 'Interbase-1.0', 'IPA', 'IPL-1.0', 'ISC', 'ISC-Veillard', 'Jam', 'JasPer-2.0', 'JPL-image', 'JPNIC', 'JSON', 'Kastrup', 'Kazlib', 'Knuth-CTAN', 'LAL-1.2', 'LAL-1.3', 'Latex2e', 'Latex2e-translated-notice', 'Leptonica', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'LGPLLR', 'Libpng', 'libpng-2.0', 'libselinux-1.0', 'libtiff', 'libutil-David-Nugent', 'LiLiQ-P-1.1', 'LiLiQ-R-1.1', 'LiLiQ-Rplus-1.1', 'Linux-man-pages-1-para', 'Linux-man-pages-copyleft', 'Linux-man-pages-copyleft-2-para', 'Linux-man-pages-copyleft-var', 'Linux-OpenIB', 'LOOP', 'LPD-document', 'LPL-1.0', 'LPL-1.02', 'LPPL-1.0', 'LPPL-1.1', 'LPPL-1.2', 'LPPL-1.3a', 'LPPL-1.3c', 'lsof', 'Lucida-Bitmap-Fonts', 'LZMA-SDK-9.11-to-9.20', 'LZMA-SDK-9.22', 'Mackerras-3-Clause', 'Mackerras-3-Clause-acknowledgment', 'magaz', 'mailprio', 'MakeIndex', 'Martin-Birgmeier', 'McPhee-slideshow', 'metamail', 'Minpack', 'MirOS', 'MIT', 'MIT-0', 'MIT-advertising', 'MIT-CMU', 'MIT-enna', 'MIT-feh', 'MIT-Festival', 'MIT-Khronos-old', 'MIT-Modern-Variant', 'MIT-open-group', 'MIT-testregex', 'MIT-Wu', 'MITNFA', 'MMIXware', 'Motosoto', 'MPEG-SSG', 'mpi-permissive', 'mpich2', 'MPL-1.0', 'MPL-1.1', 'MPL-2.0', 'MPL-2.0-no-copyleft-exception', 'mplus', 'MS-LPL', 'MS-PL', 'MS-RL', 'MTLL', 'MulanPSL-1.0', 'MulanPSL-2.0', 'Multics', 'Mup', 'NAIST-2003', 'NASA-1.3', 'Naumen', 'NBPL-1.0', 'NCBI-PD', 'NCGL-UK-2.0', 'NCL', 'NCSA', 'NetCDF', 'Newsletr', 'NGPL', 'NICTA-1.0', 'NIST-PD', 'NIST-PD-fallback', 'NIST-Software', 'NLOD-1.0', 'NLOD-2.0', 'NLPL', 'Nokia', 'NOSL', 'Noweb', 'NPL-1.0', 'NPL-1.1', 'NPOSL-3.0', 'NRL', 'NTP', 'NTP-0', 'O-UDA-1.0', 'OAR', 'OCCT-PL', 'OCLC-2.0', 'ODbL-1.0', 'ODC-By-1.0', 'OFFIS', 'OFL-1.0', 'OFL-1.0-no-RFN', 'OFL-1.0-RFN', 'OFL-1.1', 'OFL-1.1-no-RFN', 'OFL-1.1-RFN', 'OGC-1.0', 'OGDL-Taiwan-1.0', 'OGL-Canada-2.0', 'OGL-UK-1.0', 'OGL-UK-2.0', 'OGL-UK-3.0', 'OGTSL', 'OLDAP-1.1', 'OLDAP-1.2', 'OLDAP-1.3', 'OLDAP-1.4', 'OLDAP-2.0', 'OLDAP-2.0.1', 'OLDAP-2.1', 'OLDAP-2.2', 'OLDAP-2.2.1', 'OLDAP-2.2.2', 'OLDAP-2.3', 'OLDAP-2.4', 'OLDAP-2.5', 'OLDAP-2.6', 'OLDAP-2.7', 'OLDAP-2.8', 'OLFL-1.3', 'OML', 'OpenPBS-2.3', 'OpenSSL', 'OpenSSL-standalone', 'OpenVision', 'OPL-1.0', 'OPL-UK-3.0', 'OPUBL-1.0', 'OSET-PL-2.1', 'OSL-1.0', 'OSL-1.1', 'OSL-2.0', 'OSL-2.1', 'OSL-3.0', 'PADL', 'Parity-6.0.0', 'Parity-7.0.0', 'PDDL-1.0', 'PHP-3.0', 'PHP-3.01', 'Pixar', 'pkgconf', 'Plexus', 'pnmstitch', 'PolyForm-Noncommercial-1.0.0', 'PolyForm-Small-Business-1.0.0', 'PostgreSQL', 'PPL', 'PSF-2.0', 'psfrag', 'psutils', 'Python-2.0', 'Python-2.0.1', 'python-ldap', 'Qhull', 'QPL-1.0', 'QPL-1.0-INRIA-2004', 'radvd', 'Rdisc', 'RHeCos-1.1', 'RPL-1.1', 'RPL-1.5', 'RPSL-1.0', 'RSA-MD', 'RSCPL', 'Ruby', 'Ruby-pty', 'SAX-PD', 'SAX-PD-2.0', 'Saxpath', 'SCEA', 'SchemeReport', 'Sendmail', 'Sendmail-8.23', 'SGI-B-1.0', 'SGI-B-1.1', 'SGI-B-2.0', 'SGI-OpenGL', 'SGP4', 'SHL-0.5', 'SHL-0.51', 'SimPL-2.0', 'SISSL', 'SISSL-1.2', 'SL', 'Sleepycat', 'SMLNJ', 'SMPPL', 'SNIA', 'snprintf', 'softSurfer', 'Soundex', 'Spencer-86', 'Spencer-94', 'Spencer-99', 'SPL-1.0', 'ssh-keyscan', 'SSH-OpenSSH', 'SSH-short', 'SSLeay-standalone', 'SSPL-1.0', 'SugarCRM-1.1.3', 'Sun-PPP', 'Sun-PPP-2000', 'SunPro', 'SWL', 'swrule', 'Symlinks', 'TAPR-OHL-1.0', 'TCL', 'TCP-wrappers', 'TermReadKey', 'TGPPL-1.0', 'threeparttable', 'TMate', 'TORQUE-1.1', 'TOSL', 'TPDL', 'TPL-1.0', 'TTWL', 'TTYP0', 'TU-Berlin-1.0', 'TU-Berlin-2.0', 'Ubuntu-font-1.0', 'UCAR', 'UCL-1.0', 'ulem', 'UMich-Merit', 'Unicode-3.0', 'Unicode-DFS-2015', 'Unicode-DFS-2016', 'Unicode-TOU', 'UnixCrypt', 'Unlicense', 'UPL-1.0', 'URT-RLE', 'Vim', 'VOSTROM', 'VSL-1.0', 'W3C', 'W3C-19980720', 'W3C-20150513', 'w3m', 'Watcom-1.0', 'Widget-Workshop', 'Wsuipa', 'WTFPL', 'X11', 'X11-distribute-modifications-variant', 'X11-swapped', 'Xdebug-1.03', 'Xerox', 'Xfig', 'XFree86-1.1', 'xinetd', 'xkeyboard-config-Zinoviev', 'xlock', 'Xnet', 'xpp', 'XSkat', 'xzoom', 'YPL-1.0', 'YPL-1.1', 'Zed', 'Zeeff', 'Zend-2.0', 'Zimbra-1.3', 'Zimbra-1.4', 'Zlib', 'zlib-acknowledgement', 'ZPL-1.1', 'ZPL-2.0', 'ZPL-2.1'} + +def generate_sbom(conanfile, **kwargs): + name = conanfile.ref.name + version = conanfile.ref.version + date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') + packages = [] + + for dependency in [conanfile] + conanfile.dependencies.values(): + packages.append( + { + "name": dependency.ref.name, + "SPDXID": f"SPDXRef-{dependency.ref}", + "version": str(dependency.ref.version), + "license": dependency.license if dependency.license in spdx_licences else "NOASSERTION", + }) + files = [] + # https://spdx.github.io/spdx-spec/v2.2.2/package-information/ + data = { + "SPDXVersion": "SPDX-2.2", + "DataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "DocumentName": f"{name}-{version}", + "DocumentNamespace": f"http://spdx.org/spdxdocs/{name}-{version}-{date}", # the date or hash to make it unique + "Creator": f"Tool: Conan-{conan_version}", + "Created": date, #YYYY-MM-DDThh:mm:ssZ + "Packages": [{ + "PackageName": p["name"], + "SPDXID": p["SPDXID"], + "PackageVersion": p["version"], + "PackageDownloadLocation": "NOASSERTION", + "FilesAnalyzed": False, + "PackageLicenseConcluded": p["license"], + "PackageLicenseDeclared": p["license"], + } for p in packages], + # "Files": [{ + # "FileName": f["path"], # Path to file + # "SPDXID": f["SPDXID"], + # "FileChecksum": f'{f["checksum_algorithm"]}: {f["checksum_algorithm_value"]}', + # "LicenseConcluded": f["licence"], + # "LicenseInfoInFile": f["licence"], + # "FileCopyrightText": "NOASSERTION" + # } for f in files], + } + try: + with open(f"{name}-{version}.spdx.json", 'w') as f: + json.dump(data, f, indent=4) + except Exception as e: + ConanException("error generating spdx file") From 73ea04ec6e973dcb458ac9d3552e1ba201eceea4 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Wed, 6 Nov 2024 18:29:30 +0100 Subject: [PATCH 13/28] add subgraph implementation and test --- conans/client/graph/graph.py | 17 +++++++ conans/model/conan_file.py | 4 ++ .../graph/test_subgraph_reports.py | 44 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 test/integration/graph/test_subgraph_reports.py diff --git a/conans/client/graph/graph.py b/conans/client/graph/graph.py index d79cf16ba1f..00d8f4512a1 100644 --- a/conans/client/graph/graph.py +++ b/conans/client/graph/graph.py @@ -69,6 +69,23 @@ def __init__(self, ref, conanfile, context, recipe=None, path=None, test=False): self.is_conf = False self.replaced_requires = {} # To track the replaced requires for self.dependencies[old-ref] + def subgraph(self): + nodes = [self] + opened = [self] + while opened: + new_opened = [] + for o in opened: + for n in o.neighbors(): + if n not in nodes: + nodes.append(n) + if n not in opened: + new_opened.append(n) + opened = new_opened + + graph = DepsGraph() + graph.nodes = nodes + return graph + def __lt__(self, other): """ @type other: Node diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py index f61cee57d38..b25bf331e11 100644 --- a/conans/model/conan_file.py +++ b/conans/model/conan_file.py @@ -186,6 +186,10 @@ def output(self): def context(self): return self._conan_node.context + @property + def subgraph(self): + return self._conan_node.subgraph() + @property def dependencies(self): # Caching it, this object is requested many times diff --git a/test/integration/graph/test_subgraph_reports.py b/test/integration/graph/test_subgraph_reports.py new file mode 100644 index 00000000000..95b425b3594 --- /dev/null +++ b/test/integration/graph/test_subgraph_reports.py @@ -0,0 +1,44 @@ +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"]) + From e1833ae24b8390f09418665d14189ef6f273e42a Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Wed, 6 Nov 2024 18:30:35 +0100 Subject: [PATCH 14/28] use new subgraph api, update generator, spdx and test --- conan/api/subapi/install.py | 2 +- conan/internal/api/install/generators.py | 13 ++++++------- conans/client/graph/sbom.py | 9 +++------ conans/client/graph/spdx.py | 20 +++++++++----------- conans/client/installer.py | 2 +- test/integration/sbom/test_sbom.py | 19 +++++++++++++++---- 6 files changed, 35 insertions(+), 30 deletions(-) diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py index cf3a9c4e744..9630105ac65 100644 --- a/conan/api/subapi/install.py +++ b/conan/api/subapi/install.py @@ -88,4 +88,4 @@ def install_consumer(self, deps_graph, generators=None, source_folder=None, outp final_generators.append(gen) conanfile.generators = final_generators app = ConanApp(self.conan_api) - write_generators(conanfile, app, envs_generation=envs_generation) + write_generators(conanfile, app, envs_generation=envs_generation) # TODO add deps_graph without cli node diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index b6289829b42..5f8d418d51b 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -72,7 +72,7 @@ def load_cache_generators(path): return result -def write_generators(conanfile, app, envs_generation=None): +def write_generators(conanfile, app, envs_generation=None, deps_graph=None): new_gen_folder = conanfile.generators_folder _receive_conf(conanfile) @@ -136,8 +136,8 @@ def write_generators(conanfile, app, envs_generation=None): env.generate() _generate_aggregated_env(conanfile) - - _generate_graph_manifests(conanfile) + if deps_graph: + _generate_graph_manifests(deps_graph, app) hook_manager.execute("post_generate", conanfile=conanfile) @@ -155,10 +155,9 @@ def _receive_conf(conanfile): conanfile.conf.compose_conf(build_require.conf_info) -def _generate_graph_manifests(conanfile): - cache_folder = conanfile._conan_helpers.home_folder, +def _generate_graph_manifests(sub_graph, app): from conans.client.loader import load_python_file - sbom_plugin_path = HomePaths(cache_folder).sbom_manifest_plugin_path + sbom_plugin_path = HomePaths(app.cache_folder).sbom_manifest_plugin_path if os.path.exists(sbom_plugin_path): mod, _ = load_python_file(sbom_plugin_path) @@ -171,7 +170,7 @@ def _generate_graph_manifests(conanfile): ConanOutput().warning(f"generating sbom", warn_tag="experimental") # TODO think if this is conanfile or conanfile._conan_node - return mod.generate_sbom(conanfile) + return mod.generate_sbom(sub_graph) def _generate_aggregated_env(conanfile): diff --git a/conans/client/graph/sbom.py b/conans/client/graph/sbom.py index 2f384daef49..afacdac92f1 100644 --- a/conans/client/graph/sbom.py +++ b/conans/client/graph/sbom.py @@ -1,10 +1,7 @@ -import os - +from conans.client.graph.spdx import spdx_json_generator from conan.internal.cache.home_paths import HomePaths def migrate_sbom_file(cache_folder): from conans.client.migrations import update_file - with open("spdx.py", "r") as file: - default_spdx_json = file.read() - sbom_path = HomePaths(cache_folder).sbom_manifest_plugin_path - update_file(sbom_path, default_spdx_json) + sbom_path = HomePaths(cache_folder).sbom_manifest_plugin_path + update_file(sbom_path, spdx_json_generator) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index 75fc5f1bbc7..d5adc820575 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -1,27 +1,24 @@ - +spdx_json_generator = """ import time import json -import os from datetime import datetime, timezone from conan import conan_version from conan.errors import ConanException -# https://spdx.org/licenses/ -spdx_licences = {'0BSD', '3D-Slicer-1.0', 'AAL', 'Abstyles', 'AdaCore-doc', 'Adobe-2006', 'Adobe-Display-PostScript', 'Adobe-Glyph', 'Adobe-Utopia', 'ADSL', 'AFL-1.1', 'AFL-1.2', 'AFL-2.0', 'AFL-2.1', 'AFL-3.0', 'Afmparse', 'AGPL-1.0-only', 'AGPL-1.0-or-later', 'AGPL-3.0-only', 'AGPL-3.0-or-later', 'Aladdin', 'AMD-newlib', 'AMDPLPA', 'AML', 'AML-glslang', 'AMPAS', 'ANTLR-PD', 'ANTLR-PD-fallback', 'any-OSI', 'Apache-1.0', 'Apache-1.1', 'Apache-2.0', 'APAFML', 'APL-1.0', 'App-s2p', 'APSL-1.0', 'APSL-1.1', 'APSL-1.2', 'APSL-2.0', 'Arphic-1999', 'Artistic-1.0', 'Artistic-1.0-cl8', 'Artistic-1.0-Perl', 'Artistic-2.0', 'ASWF-Digital-Assets-1.0', 'ASWF-Digital-Assets-1.1', 'Baekmuk', 'Bahyph', 'Barr', 'bcrypt-Solar-Designer', 'Beerware', 'Bitstream-Charter', 'Bitstream-Vera', 'BitTorrent-1.0', 'BitTorrent-1.1', 'blessing', 'BlueOak-1.0.0', 'Boehm-GC', 'Borceux', 'Brian-Gladman-2-Clause', 'Brian-Gladman-3-Clause', 'BSD-1-Clause', 'BSD-2-Clause', 'BSD-2-Clause-Darwin', 'BSD-2-Clause-first-lines', 'BSD-2-Clause-Patent', 'BSD-2-Clause-Views', 'BSD-3-Clause', 'BSD-3-Clause-acpica', 'BSD-3-Clause-Attribution', 'BSD-3-Clause-Clear', 'BSD-3-Clause-flex', 'BSD-3-Clause-HP', 'BSD-3-Clause-LBNL', 'BSD-3-Clause-Modification', 'BSD-3-Clause-No-Military-License', 'BSD-3-Clause-No-Nuclear-License', 'BSD-3-Clause-No-Nuclear-License-2014', 'BSD-3-Clause-No-Nuclear-Warranty', 'BSD-3-Clause-Open-MPI', 'BSD-3-Clause-Sun', 'BSD-4-Clause', 'BSD-4-Clause-Shortened', 'BSD-4-Clause-UC', 'BSD-4.3RENO', 'BSD-4.3TAHOE', 'BSD-Advertising-Acknowledgement', 'BSD-Attribution-HPND-disclaimer', 'BSD-Inferno-Nettverk', 'BSD-Protection', 'BSD-Source-beginning-file', 'BSD-Source-Code', 'BSD-Systemics', 'BSD-Systemics-W3Works', 'BSL-1.0', 'BUSL-1.1', 'bzip2-1.0.6', 'C-UDA-1.0', 'CAL-1.0', 'CAL-1.0-Combined-Work-Exception', 'Caldera', 'Caldera-no-preamble', 'Catharon', 'CATOSL-1.1', 'CC-BY-1.0', 'CC-BY-2.0', 'CC-BY-2.5', 'CC-BY-2.5-AU', 'CC-BY-3.0', 'CC-BY-3.0-AT', 'CC-BY-3.0-AU', 'CC-BY-3.0-DE', 'CC-BY-3.0-IGO', 'CC-BY-3.0-NL', 'CC-BY-3.0-US', 'CC-BY-4.0', 'CC-BY-NC-1.0', 'CC-BY-NC-2.0', 'CC-BY-NC-2.5', 'CC-BY-NC-3.0', 'CC-BY-NC-3.0-DE', 'CC-BY-NC-4.0', 'CC-BY-NC-ND-1.0', 'CC-BY-NC-ND-2.0', 'CC-BY-NC-ND-2.5', 'CC-BY-NC-ND-3.0', 'CC-BY-NC-ND-3.0-DE', 'CC-BY-NC-ND-3.0-IGO', 'CC-BY-NC-ND-4.0', 'CC-BY-NC-SA-1.0', 'CC-BY-NC-SA-2.0', 'CC-BY-NC-SA-2.0-DE', 'CC-BY-NC-SA-2.0-FR', 'CC-BY-NC-SA-2.0-UK', 'CC-BY-NC-SA-2.5', 'CC-BY-NC-SA-3.0', 'CC-BY-NC-SA-3.0-DE', 'CC-BY-NC-SA-3.0-IGO', 'CC-BY-NC-SA-4.0', 'CC-BY-ND-1.0', 'CC-BY-ND-2.0', 'CC-BY-ND-2.5', 'CC-BY-ND-3.0', 'CC-BY-ND-3.0-DE', 'CC-BY-ND-4.0', 'CC-BY-SA-1.0', 'CC-BY-SA-2.0', 'CC-BY-SA-2.0-UK', 'CC-BY-SA-2.1-JP', 'CC-BY-SA-2.5', 'CC-BY-SA-3.0', 'CC-BY-SA-3.0-AT', 'CC-BY-SA-3.0-DE', 'CC-BY-SA-3.0-IGO', 'CC-BY-SA-4.0', 'CC-PDDC', 'CC0-1.0', 'CDDL-1.0', 'CDDL-1.1', 'CDL-1.0', 'CDLA-Permissive-1.0', 'CDLA-Permissive-2.0', 'CDLA-Sharing-1.0', 'CECILL-1.0', 'CECILL-1.1', 'CECILL-2.0', 'CECILL-2.1', 'CECILL-B', 'CECILL-C', 'CERN-OHL-1.1', 'CERN-OHL-1.2', 'CERN-OHL-P-2.0', 'CERN-OHL-S-2.0', 'CERN-OHL-W-2.0', 'CFITSIO', 'check-cvs', 'checkmk', 'ClArtistic', 'Clips', 'CMU-Mach', 'CMU-Mach-nodoc', 'CNRI-Jython', 'CNRI-Python', 'CNRI-Python-GPL-Compatible', 'COIL-1.0', 'Community-Spec-1.0', 'Condor-1.1', 'copyleft-next-0.3.0', 'copyleft-next-0.3.1', 'Cornell-Lossless-JPEG', 'CPAL-1.0', 'CPL-1.0', 'CPOL-1.02', 'Cronyx', 'Crossword', 'CrystalStacker', 'CUA-OPL-1.0', 'Cube', 'curl', 'cve-tou', 'D-FSL-1.0', 'DEC-3-Clause', 'diffmark', 'DL-DE-BY-2.0', 'DL-DE-ZERO-2.0', 'DOC', 'DocBook-Schema', 'DocBook-XML', 'Dotseqn', 'DRL-1.0', 'DRL-1.1', 'DSDP', 'dtoa', 'dvipdfm', 'ECL-1.0', 'ECL-2.0', 'EFL-1.0', 'EFL-2.0', 'eGenix', 'Elastic-2.0', 'Entessa', 'EPICS', 'EPL-1.0', 'EPL-2.0', 'ErlPL-1.1', 'etalab-2.0', 'EUDatagrid', 'EUPL-1.0', 'EUPL-1.1', 'EUPL-1.2', 'Eurosym', 'Fair', 'FBM', 'FDK-AAC', 'Ferguson-Twofish', 'Frameworx-1.0', 'FreeBSD-DOC', 'FreeImage', 'FSFAP', 'FSFAP-no-warranty-disclaimer', 'FSFUL', 'FSFULLR', 'FSFULLRWD', 'FTL', 'Furuseth', 'fwlw', 'GCR-docs', 'GD', 'GFDL-1.1-invariants-only', 'GFDL-1.1-invariants-or-later', 'GFDL-1.1-no-invariants-only', 'GFDL-1.1-no-invariants-or-later', 'GFDL-1.1-only', 'GFDL-1.1-or-later', 'GFDL-1.2-invariants-only', 'GFDL-1.2-invariants-or-later', 'GFDL-1.2-no-invariants-only', 'GFDL-1.2-no-invariants-or-later', 'GFDL-1.2-only', 'GFDL-1.2-or-later', 'GFDL-1.3-invariants-only', 'GFDL-1.3-invariants-or-later', 'GFDL-1.3-no-invariants-only', 'GFDL-1.3-no-invariants-or-later', 'GFDL-1.3-only', 'GFDL-1.3-or-later', 'Giftware', 'GL2PS', 'Glide', 'Glulxe', 'GLWTPL', 'gnuplot', 'GPL-1.0-only', 'GPL-1.0-or-later', 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later', 'Graphics-Gems', 'gSOAP-1.3b', 'gtkbook', 'Gutmann', 'HaskellReport', 'hdparm', 'HIDAPI', 'Hippocratic-2.1', 'HP-1986', 'HP-1989', 'HPND', 'HPND-DEC', 'HPND-doc', 'HPND-doc-sell', 'HPND-export-US', 'HPND-export-US-acknowledgement', 'HPND-export-US-modify', 'HPND-export2-US', 'HPND-Fenneberg-Livingston', 'HPND-INRIA-IMAG', 'HPND-Intel', 'HPND-Kevlin-Henney', 'HPND-Markus-Kuhn', 'HPND-merchantability-variant', 'HPND-MIT-disclaimer', 'HPND-Netrek', 'HPND-Pbmplus', 'HPND-sell-MIT-disclaimer-xserver', 'HPND-sell-regexpr', 'HPND-sell-variant', 'HPND-sell-variant-MIT-disclaimer', 'HPND-sell-variant-MIT-disclaimer-rev', 'HPND-UC', 'HPND-UC-export-US', 'HTMLTIDY', 'IBM-pibs', 'ICU', 'IEC-Code-Components-EULA', 'IJG', 'IJG-short', 'ImageMagick', 'iMatix', 'Imlib2', 'Info-ZIP', 'Inner-Net-2.0', 'Intel', 'Intel-ACPI', 'Interbase-1.0', 'IPA', 'IPL-1.0', 'ISC', 'ISC-Veillard', 'Jam', 'JasPer-2.0', 'JPL-image', 'JPNIC', 'JSON', 'Kastrup', 'Kazlib', 'Knuth-CTAN', 'LAL-1.2', 'LAL-1.3', 'Latex2e', 'Latex2e-translated-notice', 'Leptonica', 'LGPL-2.0-only', 'LGPL-2.0-or-later', 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later', 'LGPLLR', 'Libpng', 'libpng-2.0', 'libselinux-1.0', 'libtiff', 'libutil-David-Nugent', 'LiLiQ-P-1.1', 'LiLiQ-R-1.1', 'LiLiQ-Rplus-1.1', 'Linux-man-pages-1-para', 'Linux-man-pages-copyleft', 'Linux-man-pages-copyleft-2-para', 'Linux-man-pages-copyleft-var', 'Linux-OpenIB', 'LOOP', 'LPD-document', 'LPL-1.0', 'LPL-1.02', 'LPPL-1.0', 'LPPL-1.1', 'LPPL-1.2', 'LPPL-1.3a', 'LPPL-1.3c', 'lsof', 'Lucida-Bitmap-Fonts', 'LZMA-SDK-9.11-to-9.20', 'LZMA-SDK-9.22', 'Mackerras-3-Clause', 'Mackerras-3-Clause-acknowledgment', 'magaz', 'mailprio', 'MakeIndex', 'Martin-Birgmeier', 'McPhee-slideshow', 'metamail', 'Minpack', 'MirOS', 'MIT', 'MIT-0', 'MIT-advertising', 'MIT-CMU', 'MIT-enna', 'MIT-feh', 'MIT-Festival', 'MIT-Khronos-old', 'MIT-Modern-Variant', 'MIT-open-group', 'MIT-testregex', 'MIT-Wu', 'MITNFA', 'MMIXware', 'Motosoto', 'MPEG-SSG', 'mpi-permissive', 'mpich2', 'MPL-1.0', 'MPL-1.1', 'MPL-2.0', 'MPL-2.0-no-copyleft-exception', 'mplus', 'MS-LPL', 'MS-PL', 'MS-RL', 'MTLL', 'MulanPSL-1.0', 'MulanPSL-2.0', 'Multics', 'Mup', 'NAIST-2003', 'NASA-1.3', 'Naumen', 'NBPL-1.0', 'NCBI-PD', 'NCGL-UK-2.0', 'NCL', 'NCSA', 'NetCDF', 'Newsletr', 'NGPL', 'NICTA-1.0', 'NIST-PD', 'NIST-PD-fallback', 'NIST-Software', 'NLOD-1.0', 'NLOD-2.0', 'NLPL', 'Nokia', 'NOSL', 'Noweb', 'NPL-1.0', 'NPL-1.1', 'NPOSL-3.0', 'NRL', 'NTP', 'NTP-0', 'O-UDA-1.0', 'OAR', 'OCCT-PL', 'OCLC-2.0', 'ODbL-1.0', 'ODC-By-1.0', 'OFFIS', 'OFL-1.0', 'OFL-1.0-no-RFN', 'OFL-1.0-RFN', 'OFL-1.1', 'OFL-1.1-no-RFN', 'OFL-1.1-RFN', 'OGC-1.0', 'OGDL-Taiwan-1.0', 'OGL-Canada-2.0', 'OGL-UK-1.0', 'OGL-UK-2.0', 'OGL-UK-3.0', 'OGTSL', 'OLDAP-1.1', 'OLDAP-1.2', 'OLDAP-1.3', 'OLDAP-1.4', 'OLDAP-2.0', 'OLDAP-2.0.1', 'OLDAP-2.1', 'OLDAP-2.2', 'OLDAP-2.2.1', 'OLDAP-2.2.2', 'OLDAP-2.3', 'OLDAP-2.4', 'OLDAP-2.5', 'OLDAP-2.6', 'OLDAP-2.7', 'OLDAP-2.8', 'OLFL-1.3', 'OML', 'OpenPBS-2.3', 'OpenSSL', 'OpenSSL-standalone', 'OpenVision', 'OPL-1.0', 'OPL-UK-3.0', 'OPUBL-1.0', 'OSET-PL-2.1', 'OSL-1.0', 'OSL-1.1', 'OSL-2.0', 'OSL-2.1', 'OSL-3.0', 'PADL', 'Parity-6.0.0', 'Parity-7.0.0', 'PDDL-1.0', 'PHP-3.0', 'PHP-3.01', 'Pixar', 'pkgconf', 'Plexus', 'pnmstitch', 'PolyForm-Noncommercial-1.0.0', 'PolyForm-Small-Business-1.0.0', 'PostgreSQL', 'PPL', 'PSF-2.0', 'psfrag', 'psutils', 'Python-2.0', 'Python-2.0.1', 'python-ldap', 'Qhull', 'QPL-1.0', 'QPL-1.0-INRIA-2004', 'radvd', 'Rdisc', 'RHeCos-1.1', 'RPL-1.1', 'RPL-1.5', 'RPSL-1.0', 'RSA-MD', 'RSCPL', 'Ruby', 'Ruby-pty', 'SAX-PD', 'SAX-PD-2.0', 'Saxpath', 'SCEA', 'SchemeReport', 'Sendmail', 'Sendmail-8.23', 'SGI-B-1.0', 'SGI-B-1.1', 'SGI-B-2.0', 'SGI-OpenGL', 'SGP4', 'SHL-0.5', 'SHL-0.51', 'SimPL-2.0', 'SISSL', 'SISSL-1.2', 'SL', 'Sleepycat', 'SMLNJ', 'SMPPL', 'SNIA', 'snprintf', 'softSurfer', 'Soundex', 'Spencer-86', 'Spencer-94', 'Spencer-99', 'SPL-1.0', 'ssh-keyscan', 'SSH-OpenSSH', 'SSH-short', 'SSLeay-standalone', 'SSPL-1.0', 'SugarCRM-1.1.3', 'Sun-PPP', 'Sun-PPP-2000', 'SunPro', 'SWL', 'swrule', 'Symlinks', 'TAPR-OHL-1.0', 'TCL', 'TCP-wrappers', 'TermReadKey', 'TGPPL-1.0', 'threeparttable', 'TMate', 'TORQUE-1.1', 'TOSL', 'TPDL', 'TPL-1.0', 'TTWL', 'TTYP0', 'TU-Berlin-1.0', 'TU-Berlin-2.0', 'Ubuntu-font-1.0', 'UCAR', 'UCL-1.0', 'ulem', 'UMich-Merit', 'Unicode-3.0', 'Unicode-DFS-2015', 'Unicode-DFS-2016', 'Unicode-TOU', 'UnixCrypt', 'Unlicense', 'UPL-1.0', 'URT-RLE', 'Vim', 'VOSTROM', 'VSL-1.0', 'W3C', 'W3C-19980720', 'W3C-20150513', 'w3m', 'Watcom-1.0', 'Widget-Workshop', 'Wsuipa', 'WTFPL', 'X11', 'X11-distribute-modifications-variant', 'X11-swapped', 'Xdebug-1.03', 'Xerox', 'Xfig', 'XFree86-1.1', 'xinetd', 'xkeyboard-config-Zinoviev', 'xlock', 'Xnet', 'xpp', 'XSkat', 'xzoom', 'YPL-1.0', 'YPL-1.1', 'Zed', 'Zeeff', 'Zend-2.0', 'Zimbra-1.3', 'Zimbra-1.4', 'Zlib', 'zlib-acknowledgement', 'ZPL-1.1', 'ZPL-2.0', 'ZPL-2.1'} +import pathlib -def generate_sbom(conanfile, **kwargs): - name = conanfile.ref.name - version = conanfile.ref.version +def generate_sbom(graph, **kwargs): + name = graph.root.name + version = graph.root.ref.version date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') packages = [] - - for dependency in [conanfile] + conanfile.dependencies.values(): + for dependency in graph.nodes: packages.append( { "name": dependency.ref.name, "SPDXID": f"SPDXRef-{dependency.ref}", "version": str(dependency.ref.version), - "license": dependency.license if dependency.license in spdx_licences else "NOASSERTION", + "license": dependency.conanfile.license or "NOASSERTION", }) files = [] # https://spdx.github.io/spdx-spec/v2.2.2/package-information/ @@ -52,7 +49,8 @@ def generate_sbom(conanfile, **kwargs): # } for f in files], } try: - with open(f"{name}-{version}.spdx.json", 'w') as f: + with open(f"../{name}-{version}.spdx.json", 'w') as f: json.dump(data, f, indent=4) except Exception as e: ConanException("error generating spdx file") +""" diff --git a/conans/client/installer.py b/conans/client/installer.py index bdeb24efe63..aba0c01f46d 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -91,7 +91,7 @@ def _copy_sources(conanfile, source_folder, build_folder): raise ConanException("%s\nError copying sources to build folder" % msg) def _build(self, conanfile, pref): - write_generators(conanfile, self._app) + write_generators(conanfile, self._app, deps_graph=conanfile.subgraph) try: run_build_method(conanfile, self._hook_manager) diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index 8d6dc0c2256..0e7a02e566a 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -3,11 +3,22 @@ import os -def test_sbom_generation(): - tc = TestClient(light=True) +def test_sbom_generation_create(): + tc = TestClient() 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") - assert os.path.exists(os.path.join(tc.current_folder, "spdx", "dep-1.0-SPDX.json")) - assert os.path.exists(os.path.join(tc.current_folder, "spdx", "foo-1.0-SPDX.json")) + foo_layout = tc.created_layout() + + assert os.path.exists(os.path.join(foo_layout.build(), "foo-1.0.spdx.json")) + +def test_sbom_generation_install(): + tc = TestClient() + 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") + + tc.run("install --requires=foo/1.0") + assert os.path.exists(os.path.join(tc.current_folder, "foo-1.0.spdx.json")) #TODO FIX this test From cffeffbe901072bdbba8c8e22f4a2a1bd2f8ba64 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 28 Nov 2024 13:43:42 +0100 Subject: [PATCH 15/28] sbom 0.1 --- conan/internal/api/install/generators.py | 11 +- conans/client/conanfile/package.py | 2 + conans/client/graph/spdx.py | 157 ++++++++++++++++++----- conans/client/installer.py | 2 +- test/integration/sbom/test_sbom.py | 14 +- 5 files changed, 141 insertions(+), 45 deletions(-) diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index 5f8d418d51b..35804e57126 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -72,7 +72,7 @@ def load_cache_generators(path): return result -def write_generators(conanfile, app, envs_generation=None, deps_graph=None): +def write_generators(conanfile, app, envs_generation=None): new_gen_folder = conanfile.generators_folder _receive_conf(conanfile) @@ -136,8 +136,8 @@ def write_generators(conanfile, app, envs_generation=None, deps_graph=None): env.generate() _generate_aggregated_env(conanfile) - if deps_graph: - _generate_graph_manifests(deps_graph, app) + + _generate_graph_manifests(conanfile, app.cache_folder) hook_manager.execute("post_generate", conanfile=conanfile) @@ -155,9 +155,10 @@ def _receive_conf(conanfile): conanfile.conf.compose_conf(build_require.conf_info) -def _generate_graph_manifests(sub_graph, app): +def _generate_graph_manifests(conanfile, home_folder): from conans.client.loader import load_python_file - sbom_plugin_path = HomePaths(app.cache_folder).sbom_manifest_plugin_path + sub_graph = conanfile.subgraph + sbom_plugin_path = HomePaths(home_folder).sbom_manifest_plugin_path if os.path.exists(sbom_plugin_path): mod, _ = load_python_file(sbom_plugin_path) diff --git a/conans/client/conanfile/package.py b/conans/client/conanfile/package.py index dd90d313879..c169fb29e86 100644 --- a/conans/client/conanfile/package.py +++ b/conans/client/conanfile/package.py @@ -7,6 +7,7 @@ from conans.model.package_ref import PkgReference from conan.internal.paths import CONANINFO from conans.util.files import save, mkdir, chdir +from conan.internal.api.install.generators import _generate_graph_manifests def run_package_method(conanfile, package_id, hook_manager, ref): @@ -33,6 +34,7 @@ def run_package_method(conanfile, package_id, hook_manager, ref): with conanfile_remove_attr(conanfile, ['info'], "package"): conanfile.package() hook_manager.execute("post_package", conanfile=conanfile) + _generate_graph_manifests(conanfile, conanfile._conan_helpers.home_folder) save(os.path.join(conanfile.package_folder, CONANINFO), conanfile.info.dumps()) manifest = FileTreeManifest.create(conanfile.package_folder) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index d5adc820575..b6af6d34767 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -1,56 +1,147 @@ spdx_json_generator = """ +import os import time import json +import pathlib +from glob import glob from datetime import datetime, timezone from conan import conan_version from conan.errors import ConanException +from conan.api.output import ConanOutput -import pathlib def generate_sbom(graph, **kwargs): - name = graph.root.name - version = graph.root.ref.version + name = graph.root.name if graph.root.name else "CLI" + version = "SPDX-2.2" date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') packages = [] - for dependency in graph.nodes: - packages.append( + files = [] + relationships = [] + + # --- Root node --- + if graph.root.recipe != "Cli": + conan_data = graph.root.conanfile.conan_data + url_location = conan_data.get("sources", {}).get(graph.root.conanfile.version, {}).get("url", {}) if conan_data else None + checksum = conan_data.get("sources", {}).get(graph.root.conanfile.version, {}).get("sha256", {}) if conan_data else None + packages.extend([ { - "name": dependency.ref.name, - "SPDXID": f"SPDXRef-{dependency.ref}", - "version": str(dependency.ref.version), - "license": dependency.conanfile.license or "NOASSERTION", + "name": graph.root.ref.name, + "SPDXID": f"SPDXRef-{graph.root.ref}", + "version": str(graph.root.ref.version), + "downloadLocation": graph.root.conanfile.url or "NOASSERTION", + "homePage": graph.root.conanfile.homepage or "NOASSERTION", + "licenseConcluded": graph.root.conanfile.license or "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "description": graph.root.conanfile.description or "NOASSERTION", + "comment": f"This is the {graph.root.ref.name} package in the remote" # TODO It could be a local package + + }, + { + "name": f"{graph.root.pref} binary", + "SPDXID": f"SPDXRef-binary-{graph.root.ref}", + "downloadLocation": graph.root.remote.url if graph.root.remote else "NONE", + "licenseConcluded": graph.root.conanfile.license or "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "comment": f"This is the {graph.root.ref} binary generated by conan" + }, + { + "name": f"{graph.root.ref.name} upstream", + "SPDXID": f"SPDXRef-resource-{graph.root.ref}", + "downloadLocation": url_location or "NONE", + **({"checksum": { + "algorithm": "SHA256", + "checksumValue": checksum + }} if checksum else {}), + "licenseConcluded": graph.root.conanfile.license or "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "comment": f"This is the {graph.root.ref.name} release file" + }]) + + relationships.extend([{ + "spdxElementId": f"SPDXRef-binary-{graph.root.ref}", + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": f"SPDXRef-binary-{d.dst.ref}", + }for d in graph.root.dependencies]) + + relationships.append({ + "spdxElementId": f"SPDXRef-{graph.root.ref}", + "relationshipType": "GENERATES", + "relatedSpdxElement": f"SPDXRef-binary-{graph.root.ref}", + }) + + exported_path = graph.root.conanfile.recipe_folder # /e folder + external_files = [f for f in glob(os.path.join(exported_path, "**", "*"), recursive=True) if not f.endswith('/')] if exported_path else [] + + try: + with open(os.path.join(graph.root.conanfile.recipe_folder, "conanmanifest.txt")) as conanmanifest: + external_files.extend([os.path.join(exported_path[:-1], *line.split(" ")[0][:-1].split("/")) for line in conanmanifest.readlines()[2:]]) + except Exception: + pass + + for i, file_name in enumerate(external_files): + checksum = None + files.append( + { + "fileName": file_name, + "SPDXID": f"SPDXRef-file-{graph.root.ref}-{i}", + **({"checksums":{ + "algorithm": "SHA256", + "checksumValues": checksum, + }} if checksum else {}), + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ) + relationships.append({ + "spdxElementId": f"SPDXRef-{graph.root.ref}", + "relationshipType": "CONTAINS", + "relatedSpdxElement": f"SPDXRef-file-{graph.root.ref}-{i}", }) - files = [] + + # --- Just the binaries for dependencies --- + for node in graph.nodes[1:]: + conan_data = node.conanfile.conan_data + url_location = conan_data.get("sources", {}).get(node.conanfile.version, {}).get("url", {}) if conan_data else None + checksum = conan_data.get("sources", {}).get(node.conanfile.version, {}).get("sha256", {}) if conan_data else None + packages.extend([ + { + "name": f"{node.pref} binary", + "SPDXID": f"SPDXRef-binary-{node.ref}", + "downloadLocation": node.remote.url if node.remote else "NONE", + "licenseConcluded": node.conanfile.license or "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "comment": f"This is the {node.ref} binary generated by conan" + }]) + + relationships.extend([{ + "spdxElementId": f"SPDXRef-binary-{node.ref}", + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": f"SPDXRef-binary-{d.dst.ref}", + }for d in node.dependencies]) + + # https://spdx.github.io/spdx-spec/v2.2.2/package-information/ data = { "SPDXVersion": "SPDX-2.2", - "DataLicense": "CC0-1.0", + "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", - "DocumentName": f"{name}-{version}", - "DocumentNamespace": f"http://spdx.org/spdxdocs/{name}-{version}-{date}", # the date or hash to make it unique - "Creator": f"Tool: Conan-{conan_version}", - "Created": date, #YYYY-MM-DDThh:mm:ssZ - "Packages": [{ - "PackageName": p["name"], - "SPDXID": p["SPDXID"], - "PackageVersion": p["version"], - "PackageDownloadLocation": "NOASSERTION", - "FilesAnalyzed": False, - "PackageLicenseConcluded": p["license"], - "PackageLicenseDeclared": p["license"], - } for p in packages], - # "Files": [{ - # "FileName": f["path"], # Path to file - # "SPDXID": f["SPDXID"], - # "FileChecksum": f'{f["checksum_algorithm"]}: {f["checksum_algorithm_value"]}', - # "LicenseConcluded": f["licence"], - # "LicenseInfoInFile": f["licence"], - # "FileCopyrightText": "NOASSERTION" - # } for f in files], + "documentName": f"{name}-{version}", + "documentNamespace": f"http://spdx.org/spdxdocs/{name}-{version}-{date}", # the date or hash to make it unique + "creator": f"Tool: Conan-{conan_version}", + "created": date, #YYYY-MM-DDThh:mm:ssZ + "packages": packages, + **({"files": files} if files else {}), + **({"relationships": relationships} if relationships else {}), } try: - with open(f"../{name}-{version}.spdx.json", 'w') as f: + metadata_folder = graph.root.conanfile.package_metadata_folder + with open(os.path.join(metadata_folder, f"{name}-{graph.root.ref.version if graph.root.ref else "local"}.spdx.json"), 'w') as f: json.dump(data, f, indent=4) + ConanOutput().success(f"SPDX CREATED - {graph.root.conanfile.package_metadata_folder}") except Exception as e: ConanException("error generating spdx file") """ diff --git a/conans/client/installer.py b/conans/client/installer.py index aba0c01f46d..bdeb24efe63 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -91,7 +91,7 @@ def _copy_sources(conanfile, source_folder, build_folder): raise ConanException("%s\nError copying sources to build folder" % msg) def _build(self, conanfile, pref): - write_generators(conanfile, self._app, deps_graph=conanfile.subgraph) + write_generators(conanfile, self._app) try: run_build_method(conanfile, self._hook_manager) diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index 0e7a02e566a..d2931bd13d9 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -5,13 +5,14 @@ def test_sbom_generation_create(): tc = TestClient() - 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("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") + # foo -> dep tc.run("create . --build=missing") - foo_layout = tc.created_layout() - assert os.path.exists(os.path.join(foo_layout.build(), "foo-1.0.spdx.json")) + foo_layout = tc.created_layout() + assert os.path.exists(os.path.join(foo_layout.build(),"..", "d", "metadata", "foo-1.0.spdx.json")) def test_sbom_generation_install(): tc = TestClient() @@ -20,5 +21,6 @@ def test_sbom_generation_install(): 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, "foo-1.0.spdx.json")) #TODO FIX this test + assert os.path.exists(os.path.join(tc.current_folder, "CLI-local.spdx.json")) From 61cfe656cc4d8c2388a9043440fd02bdf69209f3 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Wed, 4 Dec 2024 14:17:00 +0100 Subject: [PATCH 16/28] support cyclonedx --- conans/client/graph/spdx.py | 105 ++++++++++++++++++++++++++--- test/integration/sbom/test_sbom.py | 11 +-- 2 files changed, 101 insertions(+), 15 deletions(-) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index b6af6d34767..0a08a01b997 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -1,16 +1,101 @@ -spdx_json_generator = """ -import os -import time -import json -import pathlib -from glob import glob -from datetime import datetime, timezone -from conan import conan_version -from conan.errors import ConanException -from conan.api.output import ConanOutput +# TODO RENAME THIS FILE +spdx_json_generator = """ def generate_sbom(graph, **kwargs): + cyclonedx_1_4(graph, **kwargs) + #spdx_sbom(graph, **kwargs) + +def cyclonedx_1_4(graph, **kwargs): + import json + import os + import uuid + import time + from datetime import datetime, timezone + from conan import conan_version + from conan.errors import ConanException + from conan.api.subapi.graph import CONTEXT_BUILD + from conan.api.output import ConanOutput + + def filter_context(n): return n.context != CONTEXT_BUILD + + components = [node for node in graph.nodes if filter_context(node) and node.recipe != "Cli"] + IS_CLI = graph.root.recipe == "Cli" + CLI_ID = str(uuid.uuid4()) + + dependencies = [] + if IS_CLI: + deps = {"ref": CLI_ID} + deps["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}"} + dependsOn = [f"pkg:conan/{d.dst.name}@{d.dst.ref.version}?rref={d.dst.ref.revision}" for d in c.dependencies] + if dependsOn: + deps["dependsOn"] = dependsOn + dependencies.append(deps) + + sbom_cyclonedx_1_4 = { + **({"components": [{ + "author": "Conan", + "bom-ref": CLI_ID if IS_CLI else "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": [{ + "license": { + "id": c.conanfile.license + } + }], + "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": "pkg:conan/{graph.root.name}@{graph.root.ref.version}?rref={graph.root.ref.revision}", + "name": graph.root.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, + } + try: + metadata_folder = graph.root.conanfile.package_metadata_folder + file_name = "CLI-cyclonedx.json" if IS_CLI else f"{graph.root.name}-{graph.root.ref.version}-cyclonedx.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 - {graph.root.conanfile.package_metadata_folder}") + except Exception as e: + ConanException("error generating CYCLONEDX file") + +def spdx_sbom(graph, **kwargs): + import os + import time + import json + import pathlib + from glob import glob + from datetime import datetime, timezone + from conan import conan_version + from conan.errors import ConanException + from conan.api.output import ConanOutput + name = graph.root.name if graph.root.name else "CLI" version = "SPDX-2.2" date = datetime.fromtimestamp(time.time(), tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index d2931bd13d9..4012dabf827 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -8,11 +8,12 @@ def test_sbom_generation_create(): 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") - # foo -> dep + 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") - - foo_layout = tc.created_layout() - assert os.path.exists(os.path.join(foo_layout.build(),"..", "d", "metadata", "foo-1.0.spdx.json")) + bar_layout = tc.created_layout() + assert os.path.exists(os.path.join(bar_layout.build(),"..", "d", "metadata", "bar-1.0-cyclonedx.json")) def test_sbom_generation_install(): tc = TestClient() @@ -23,4 +24,4 @@ def test_sbom_generation_install(): #cli -> foo -> dep tc.run("install --requires=foo/1.0") - assert os.path.exists(os.path.join(tc.current_folder, "CLI-local.spdx.json")) + assert os.path.exists(os.path.join(tc.current_folder, "CLI-cyclonedx.json")) From 548478bad9a96174e4fe1b693cf0650c72b8e84f Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Tue, 10 Dec 2024 14:31:49 +0100 Subject: [PATCH 17/28] solve conflits --- conan/internal/methods.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conan/internal/methods.py b/conan/internal/methods.py index 17050dbef04..4a6749db4b9 100644 --- a/conan/internal/methods.py +++ b/conan/internal/methods.py @@ -9,6 +9,7 @@ from conans.model.pkg_type import PackageType from conans.model.requires import BuildRequirements, TestRequirements, ToolRequirements from conans.util.files import mkdir, chdir, save +from conan.internal.api.install.generators import generate_graph_manifests def run_source_method(conanfile, hook_manager): @@ -63,6 +64,7 @@ def run_package_method(conanfile, package_id, hook_manager, ref): with conanfile_remove_attr(conanfile, ['info'], "package"): conanfile.package() hook_manager.execute("post_package", conanfile=conanfile) + generate_graph_manifests(conanfile, conanfile._conan_helpers.home_folder) save(os.path.join(conanfile.package_folder, CONANINFO), conanfile.info.dumps()) manifest = FileTreeManifest.create(conanfile.package_folder) From a6d359b2c5e2d6e7e6b81af6557bd9638df3f8b9 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Tue, 10 Dec 2024 14:33:25 +0100 Subject: [PATCH 18/28] Update conan/api/subapi/install.py --- conan/api/subapi/install.py | 1 - 1 file changed, 1 deletion(-) diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py index 3610d43d50e..34aa8374e11 100644 --- a/conan/api/subapi/install.py +++ b/conan/api/subapi/install.py @@ -89,7 +89,6 @@ def install_consumer(self, deps_graph, generators=None, source_folder=None, outp if gen not in final_generators: final_generators.append(gen) conanfile.generators = final_generators - app = ConanApp(self.conan_api) hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path) write_generators(conanfile, hook_manager, self.conan_api.home_folder, envs_generation=envs_generation) From 89264b358c6f8be853604eefc49a9504141efd00 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Tue, 10 Dec 2024 17:37:51 +0100 Subject: [PATCH 19/28] skip test sbom, calculate licenses, small fix --- conans/client/graph/spdx.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index 0a08a01b997..890975b792e 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -17,9 +17,10 @@ def cyclonedx_1_4(graph, **kwargs): from conan.api.subapi.graph import CONTEXT_BUILD from conan.api.output import ConanOutput - def filter_context(n): return n.context != CONTEXT_BUILD + if graph.root.conanfile.tested_reference_str: # Is a test package + return - components = [node for node in graph.nodes if filter_context(node) and node.recipe != "Cli"] + components = [node for node in graph.nodes if node.recipe != "Cli"] IS_CLI = graph.root.recipe == "Cli" CLI_ID = str(uuid.uuid4()) @@ -35,6 +36,15 @@ def filter_context(n): return n.context != CONTEXT_BUILD deps["dependsOn"] = dependsOn 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": license + }} for license in c.conanfile.license] + sbom_cyclonedx_1_4 = { **({"components": [{ "author": "Conan", @@ -44,11 +54,7 @@ def filter_context(n): return n.context != CONTEXT_BUILD "type": "website", "url": c.conanfile.homepage }]} if c.conanfile.homepage else {}), - "licenses": [{ - "license": { - "id": c.conanfile.license - } - }], + **({"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", @@ -224,7 +230,7 @@ def spdx_sbom(graph, **kwargs): } try: metadata_folder = graph.root.conanfile.package_metadata_folder - with open(os.path.join(metadata_folder, f"{name}-{graph.root.ref.version if graph.root.ref else "local"}.spdx.json"), 'w') as f: + with open(os.path.join(metadata_folder, f"{name}-{graph.root.ref.version if graph.root.ref else 'local'}.spdx.json"), 'w') as f: json.dump(data, f, indent=4) ConanOutput().success(f"SPDX CREATED - {graph.root.conanfile.package_metadata_folder}") except Exception as e: From f32ae949b2f95dadff921a1ad20acaa3ceb70f20 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Tue, 10 Dec 2024 17:47:33 +0100 Subject: [PATCH 20/28] fix f string --- conans/client/graph/spdx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index 890975b792e..26a754f1e36 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -48,7 +48,7 @@ def _calculate_licenses(component): sbom_cyclonedx_1_4 = { **({"components": [{ "author": "Conan", - "bom-ref": CLI_ID if IS_CLI else "pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}", + "bom-ref": CLI_ID if IS_CLI else f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}", "description": c.conanfile.description, **({"externalReferences": [{ "type": "website", @@ -64,7 +64,7 @@ def _calculate_licenses(component): "metadata": { "component": { "author": "Conan", - "bom-ref": "pkg:conan/{graph.root.name}@{graph.root.ref.version}?rref={graph.root.ref.revision}", + "bom-ref": f"pkg:conan/{graph.root.name}@{graph.root.ref.version}?rref={graph.root.ref.revision}", "name": graph.root.name, "type": "library" }, From aedc776508bc26464655b052a4c2299f6e3d4257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Tue, 10 Dec 2024 17:59:01 +0100 Subject: [PATCH 21/28] Add test for skipped binary --- test/integration/sbom/test_sbom.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index 4012dabf827..80049be48ef 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -25,3 +25,20 @@ def test_sbom_generation_install(): #cli -> foo -> dep tc.run("install --requires=foo/1.0") assert os.path.exists(os.path.join(tc.current_folder, "CLI-cyclonedx.json")) + +def test_sbom_generation_skipped_dependencies(): + tc = TestClient() + 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", "foo-1.0-cyclonedx.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 From 56f4a8a1c31e96e660e5a6ff510db1c654071139 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Tue, 10 Dec 2024 18:20:18 +0100 Subject: [PATCH 22/28] fix --- conans/client/graph/spdx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index 26a754f1e36..7c016a1c9c6 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -64,7 +64,7 @@ def _calculate_licenses(component): "metadata": { "component": { "author": "Conan", - "bom-ref": f"pkg:conan/{graph.root.name}@{graph.root.ref.version}?rref={graph.root.ref.revision}", + "bom-ref": CLI_ID if IS_CLI else f"pkg:conan/{graph.root.name}@{graph.root.ref.version}?rref={graph.root.ref.revision}", "name": graph.root.name, "type": "library" }, From b060f80468e9c2ef8eceb54f7a07a7477a845710 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Thu, 12 Dec 2024 16:14:07 +0100 Subject: [PATCH 23/28] "special root node" tests and fixes --- conan/internal/api/install/generators.py | 1 + conans/client/graph/spdx.py | 29 ++++++++++------- test/integration/sbom/test_sbom.py | 41 +++++++++++++++++++++++- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index 5788d17fd32..1dc0ba57157 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -162,6 +162,7 @@ def _receive_conf(conanfile): def generate_graph_manifests(conanfile, home_folder): from conans.client.loader import load_python_file + mkdir(conanfile.package_metadata_folder) sub_graph = conanfile.subgraph sbom_plugin_path = HomePaths(home_folder).sbom_manifest_plugin_path if os.path.exists(sbom_plugin_path): diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index 7c016a1c9c6..7d46949bcf9 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -17,16 +17,21 @@ def cyclonedx_1_4(graph, **kwargs): from conan.api.subapi.graph import CONTEXT_BUILD from conan.api.output import ConanOutput - if graph.root.conanfile.tested_reference_str: # Is a test package - return - components = [node for node in graph.nodes if node.recipe != "Cli"] - IS_CLI = graph.root.recipe == "Cli" - CLI_ID = str(uuid.uuid4()) + SPECIAL_LIST = ["cli", "conanfile.txt", "conanfile.py"] + is_test = bool(graph.root.conanfile.tested_reference_str) + + has_special_root_node = is_test or graph.root.conanfile.display_name in SPECIAL_LIST + special_id = str(uuid.uuid4()) + special_name = graph.root.conanfile.display_name.replace(".", "-").replace(" ", "_").replace("/", "-").upper() + + components = [node for node in graph.nodes] + if has_special_root_node: + components = components[1:] dependencies = [] - if IS_CLI: - deps = {"ref": CLI_ID} + if has_special_root_node: + deps = {"ref": special_id} deps["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: @@ -48,7 +53,7 @@ def _calculate_licenses(component): sbom_cyclonedx_1_4 = { **({"components": [{ "author": "Conan", - "bom-ref": CLI_ID if IS_CLI else f"pkg:conan/{c.name}@{c.ref.version}?rref={c.ref.revision}", + "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", @@ -64,8 +69,8 @@ def _calculate_licenses(component): "metadata": { "component": { "author": "Conan", - "bom-ref": CLI_ID if IS_CLI else f"pkg:conan/{graph.root.name}@{graph.root.ref.version}?rref={graph.root.ref.revision}", - "name": graph.root.name, + "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')}", @@ -84,11 +89,13 @@ def _calculate_licenses(component): } try: metadata_folder = graph.root.conanfile.package_metadata_folder - file_name = "CLI-cyclonedx.json" if IS_CLI else f"{graph.root.name}-{graph.root.ref.version}-cyclonedx.json" + file_name = f"{special_name}-cyclonedx.json" if has_special_root_node else f"{graph.root.name}-{graph.root.ref.version}-cyclonedx.json" with open(os.path.join(metadata_folder, file_name), 'w') as f: json.dump(sbom_cyclonedx_1_4, f, indent=4) + breakpoint() ConanOutput().success(f"CYCLONEDX CREATED - {graph.root.conanfile.package_metadata_folder}") except Exception as e: + breakpoint() ConanException("error generating CYCLONEDX file") def spdx_sbom(graph, **kwargs): diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index 80049be48ef..f3af473b04a 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -1,3 +1,5 @@ +import textwrap + from conan.test.assets.genconanfile import GenConanfile from conan.test.utils.tools import TestClient import os @@ -15,7 +17,7 @@ def test_sbom_generation_create(): bar_layout = tc.created_layout() assert os.path.exists(os.path.join(bar_layout.build(),"..", "d", "metadata", "bar-1.0-cyclonedx.json")) -def test_sbom_generation_install(): +def test_sbom_generation_install_requires(): tc = TestClient() tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), "conanfile.py": GenConanfile("foo", "1.0").with_requires("dep/1.0")}) @@ -26,6 +28,43 @@ def test_sbom_generation_install(): tc.run("install --requires=foo/1.0") assert os.path.exists(os.path.join(tc.current_folder, "CLI-cyclonedx.json")) +def test_sbom_generation_install_path(): + tc = TestClient() + 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, "foo-1.0-cyclonedx.json")) + +def test_sbom_generation_install_path_consumer(): + # There is not .../d/metadata/... + tc = TestClient() + 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, "CONANFILE-PY-cyclonedx.json")) + +def test_sbom_generation_install_path_txt(): + # There is not .../d/metadata/... + tc = TestClient() + 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, "CONANFILE-TXT-cyclonedx.json")) + def test_sbom_generation_skipped_dependencies(): tc = TestClient() tc.save({"dep/conanfile.py": GenConanfile("dep", "1.0"), From d1a27a425ce16b9e98b73859538908843c307495 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Fri, 13 Dec 2024 14:22:25 +0100 Subject: [PATCH 24/28] fix breakpoints --- conans/client/graph/spdx.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index 7d46949bcf9..dae0c7aa1b2 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -92,10 +92,8 @@ def _calculate_licenses(component): file_name = f"{special_name}-cyclonedx.json" if has_special_root_node else f"{graph.root.name}-{graph.root.ref.version}-cyclonedx.json" with open(os.path.join(metadata_folder, file_name), 'w') as f: json.dump(sbom_cyclonedx_1_4, f, indent=4) - breakpoint() ConanOutput().success(f"CYCLONEDX CREATED - {graph.root.conanfile.package_metadata_folder}") except Exception as e: - breakpoint() ConanException("error generating CYCLONEDX file") def spdx_sbom(graph, **kwargs): From 215a3e8bed8340cf6f3abe7972d06f5ea76e6364 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Fri, 13 Dec 2024 15:23:18 +0100 Subject: [PATCH 25/28] fix --- conans/client/graph/spdx.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/conans/client/graph/spdx.py b/conans/client/graph/spdx.py index dae0c7aa1b2..5ca3e3b51de 100644 --- a/conans/client/graph/spdx.py +++ b/conans/client/graph/spdx.py @@ -17,11 +17,7 @@ def cyclonedx_1_4(graph, **kwargs): from conan.api.subapi.graph import CONTEXT_BUILD from conan.api.output import ConanOutput - - SPECIAL_LIST = ["cli", "conanfile.txt", "conanfile.py"] - is_test = bool(graph.root.conanfile.tested_reference_str) - - has_special_root_node = is_test or graph.root.conanfile.display_name in SPECIAL_LIST + 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()) special_name = graph.root.conanfile.display_name.replace(".", "-").replace(" ", "_").replace("/", "-").upper() From 3b7125ad317e615315df4a02ca4864340f99b4ae Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Fri, 13 Dec 2024 17:28:48 +0100 Subject: [PATCH 26/28] fix tests: Detect the new file in metadata when upload files --- .../integration/command/upload/upload_complete_test.py | 4 ++-- test/integration/metadata/test_metadata_commands.py | 10 +++++----- test/integration/metadata/test_metadata_logs.py | 6 +++--- test/integration/sbom/test_sbom.py | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/integration/command/upload/upload_complete_test.py b/test/integration/command/upload/upload_complete_test.py index 24d6c837fb3..e3c384179b4 100644 --- a/test/integration/command/upload/upload_complete_test.py +++ b/test/integration/command/upload/upload_complete_test.py @@ -165,7 +165,7 @@ def test_upload_error(self): client.run("install --requires=hello0/1.2.1@frodo/stable --build='*' -r default") self._set_global_conf(client, retry=3, retry_wait=0) client.run("upload hello* --confirm -r default") - self.assertEqual(str(client.out).count("WARN: network: Pair file, error!"), 5) + self.assertEqual(str(client.out).count("WARN: network: Pair file, error!"), 6) def _set_global_conf(self, client, retry=None, retry_wait=None): lines = [] @@ -222,7 +222,7 @@ def test_upload_error_with_config(self): client.run("install --requires=hello0/1.2.1@frodo/stable --build='*'") self._set_global_conf(client, retry=3, retry_wait=0) client.run("upload hello* --confirm -r default") - self.assertEqual(str(client.out).count("WARN: network: Pair file, error!"), 5) + self.assertEqual(str(client.out).count("WARN: network: Pair file, error!"), 6) def test_upload_same_package_dont_compress(self): client = self._get_client() diff --git a/test/integration/metadata/test_metadata_commands.py b/test/integration/metadata/test_metadata_commands.py index 097ac4c4247..481b03b77f7 100644 --- a/test/integration/metadata/test_metadata_commands.py +++ b/test/integration/metadata/test_metadata_commands.py @@ -33,7 +33,7 @@ def test_upload(self, create_conan_pkg): # Now upload everything c.run("upload * -c -r=default") assert "pkg/0.1: Recipe metadata: 1 files" in c.out - assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 1 files" in c.out + assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 2 files" in c.out # Add new files to the metadata self.save_metadata_file(c, "pkg/0.1", "mylogs2.txt") @@ -42,7 +42,7 @@ def test_upload(self, create_conan_pkg): # adding the new metadata logs files c.run("upload * -c -r=default --metadata=*") assert "pkg/0.1: Recipe metadata: 2 files" in c.out - assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 2 files" in c.out + assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 3 files" in c.out c.run("remove * -c") c.run("install --requires=pkg/0.1") # wont install metadata by default @@ -116,7 +116,7 @@ def test_direct_download_redownload(self, create_conan_pkg): # Now upload everything c.run("upload * -c -r=default") assert "pkg/0.1: Recipe metadata: 1 files" in c.out - assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 1 files" in c.out + assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 2 files" in c.out c.run("remove * -c") @@ -146,7 +146,7 @@ def test_no_download_cached(self, create_conan_pkg): # Now upload everything c.run("upload * -c -r=default") assert "pkg/0.1: Recipe metadata: 1 files" in c.out - assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 1 files" in c.out + assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 2 files" in c.out c2 = TestClient(servers=c.servers) tmp_folder = temp_folder() @@ -169,7 +169,7 @@ def test_no_download_cached(self, create_conan_pkg): save(mypkgfile, "mybuildlogs2!!!!") c.run("upload * -c -r=default --metadata=*") assert "pkg/0.1: Recipe metadata: 1 files" in c.out - assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 1 files" in c.out + assert "pkg/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709: Package metadata: 2 files" in c.out # re-download of metadata in c2 c2.run("remove * -c") # to make sure the download cache works diff --git a/test/integration/metadata/test_metadata_logs.py b/test/integration/metadata/test_metadata_logs.py index 6de26128cb2..52d628a7e3d 100644 --- a/test/integration/metadata/test_metadata_logs.py +++ b/test/integration/metadata/test_metadata_logs.py @@ -52,7 +52,7 @@ def test_metadata_logs(self): pref = c.get_latest_package_reference(ref) pref_layout = c.get_latest_pkg_layout(pref) - assert os.listdir(pref_layout.metadata()) == ["logs"] + assert os.listdir(pref_layout.metadata()) == ["logs", "pkg-0.1-cyclonedx.json"] assert os.listdir(os.path.join(pref_layout.metadata(), "logs")) == ["mylogs.txt"] assert load(os.path.join(pref_layout.metadata(), "logs", "mylogs.txt")) == "some logs!!!" @@ -82,7 +82,7 @@ def test_download_pkg_list_from_graph(self): ref = RecipeReference.loads("pkg/0.1") pref = c.get_latest_package_reference(ref) pref_layout = c.get_latest_pkg_layout(pref) - assert os.listdir(pref_layout.metadata()) == ["logs"] + assert os.listdir(pref_layout.metadata()) == ["logs", "pkg-0.1-cyclonedx.json"] assert os.listdir(os.path.join(pref_layout.metadata(), "logs")) == ["mylogs.txt"] assert load(os.path.join(pref_layout.metadata(), "logs", "mylogs.txt")) == "some logs!!!" @@ -218,6 +218,6 @@ def package(self): c.run("export-pkg .") # Test local cache looks good pkg_layout = c.created_layout() - assert os.listdir(pkg_layout.metadata()) == ["logs"] + assert os.listdir(pkg_layout.metadata()) == ["logs", "pkg-0.1-cyclonedx.json"] assert os.listdir(os.path.join(pkg_layout.metadata(), "logs")) == ["mylogs.txt"] assert load(os.path.join(pkg_layout.metadata(), "logs", "mylogs.txt")) == "some logs!!!" diff --git a/test/integration/sbom/test_sbom.py b/test/integration/sbom/test_sbom.py index f3af473b04a..b888f5a819c 100644 --- a/test/integration/sbom/test_sbom.py +++ b/test/integration/sbom/test_sbom.py @@ -36,7 +36,7 @@ def test_sbom_generation_install_path(): #foo -> dep tc.run("install .") - assert os.path.exists(os.path.join(tc.current_folder, "foo-1.0-cyclonedx.json")) + assert os.path.exists(os.path.join(tc.current_folder, "CONANFILE-PY_(FOO-1-0)-cyclonedx.json")) def test_sbom_generation_install_path_consumer(): # There is not .../d/metadata/... From dce962e6762a86b7904ed4b74c8e465554358a95 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Mon, 16 Dec 2024 13:07:15 +0100 Subject: [PATCH 27/28] fix meradata logs and warn_tag test --- conan/internal/api/install/generators.py | 2 +- test/integration/metadata/test_metadata_logs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conan/internal/api/install/generators.py b/conan/internal/api/install/generators.py index 1dc0ba57157..2ac8efb5d9e 100644 --- a/conan/internal/api/install/generators.py +++ b/conan/internal/api/install/generators.py @@ -175,7 +175,7 @@ def generate_graph_manifests(conanfile, home_folder): raise ConanException( f"SBOM manifest plugin 'generate_sbom' is not a function") - ConanOutput().warning(f"generating sbom", warn_tag="experimental") + conanfile.output.info(f"generating sbom") # TODO think if this is conanfile or conanfile._conan_node return mod.generate_sbom(sub_graph) diff --git a/test/integration/metadata/test_metadata_logs.py b/test/integration/metadata/test_metadata_logs.py index 52d628a7e3d..4fc7a9b1230 100644 --- a/test/integration/metadata/test_metadata_logs.py +++ b/test/integration/metadata/test_metadata_logs.py @@ -180,7 +180,7 @@ def test_metadata_logs_hook(self, _client): pref = c.get_latest_package_reference(ref) pref_layout = c.get_latest_pkg_layout(pref) - assert os.listdir(pref_layout.metadata()) == ["logs"] + assert "logs" in os.listdir(pref_layout.metadata()) assert os.listdir(os.path.join(pref_layout.metadata(), "logs")) == ["mylogs.txt"] assert load(os.path.join(pref_layout.metadata(), "logs", "mylogs.txt")) == "some logs!!!" From 0629aa8c413ff1923d7dc337e9b60aaa903d58c1 Mon Sep 17 00:00:00 2001 From: Ernesto de Gracia Herranz Date: Mon, 16 Dec 2024 17:28:48 +0100 Subject: [PATCH 28/28] move sbom test from integrations to funcional --- test/{integration => functional}/sbom/__init__.py | 0 test/{integration => functional}/sbom/test_sbom.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/{integration => functional}/sbom/__init__.py (100%) rename test/{integration => functional}/sbom/test_sbom.py (100%) diff --git a/test/integration/sbom/__init__.py b/test/functional/sbom/__init__.py similarity index 100% rename from test/integration/sbom/__init__.py rename to test/functional/sbom/__init__.py diff --git a/test/integration/sbom/test_sbom.py b/test/functional/sbom/test_sbom.py similarity index 100% rename from test/integration/sbom/test_sbom.py rename to test/functional/sbom/test_sbom.py