capycli 2.10.0.dev1__tar.gz → 2.10.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {capycli-2.10.0.dev1 → capycli-2.10.1}/PKG-INFO +5 -5
  2. {capycli-2.10.0.dev1 → capycli-2.10.1}/Readme.md +4 -4
  3. capycli-2.10.1/capycli/bom/bom_package.py +215 -0
  4. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/download_sources.py +7 -35
  5. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/handle_bom.py +9 -1
  6. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/show_bom.py +6 -3
  7. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/handle_dependencies.py +9 -1
  8. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/javascript.py +8 -4
  9. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/python.py +10 -9
  10. capycli-2.10.1/capycli/dependencies/rust.py +446 -0
  11. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/options.py +2 -9
  12. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/check_prerequisites.py +6 -2
  13. {capycli-2.10.0.dev1 → capycli-2.10.1}/pyproject.toml +1 -1
  14. {capycli-2.10.0.dev1 → capycli-2.10.1}/License.md +0 -0
  15. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/__init__.py +0 -0
  16. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/__main__.py +0 -0
  17. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/__init__.py +0 -0
  18. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/bom_convert.py +0 -0
  19. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/bom_validate.py +0 -0
  20. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/check_bom.py +0 -0
  21. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/check_bom_item_status.py +0 -0
  22. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/check_granularity.py +0 -0
  23. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/create_components.py +0 -0
  24. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/csv.py +0 -0
  25. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/diff_bom.py +0 -0
  26. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/filter_bom.py +0 -0
  27. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/findsources.py +0 -0
  28. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/html.py +0 -0
  29. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/legacy.py +0 -0
  30. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/legacy_cx.py +0 -0
  31. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/map_bom.py +0 -0
  32. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/merge_bom.py +0 -0
  33. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/bom/plaintext.py +0 -0
  34. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/__init__.py +0 -0
  35. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/capycli_bom_support.py +0 -0
  36. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/comparable_version.py +0 -0
  37. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/component_cache.py +0 -0
  38. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/dependencies_base.py +0 -0
  39. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/file_support.py +0 -0
  40. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/github_support.py +0 -0
  41. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/html_support.py +0 -0
  42. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/json_support.py +0 -0
  43. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/map_result.py +0 -0
  44. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/print.py +0 -0
  45. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/purl_service.py +0 -0
  46. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/purl_store.py +0 -0
  47. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/purl_utils.py +0 -0
  48. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/script_base.py +0 -0
  49. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/common/script_support.py +0 -0
  50. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/data/__init__.py +0 -0
  51. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/data/granularity_list.csv +0 -0
  52. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/__init__.py +0 -0
  53. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/maven_list.py +0 -0
  54. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/maven_pom.py +0 -0
  55. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/dependencies/nuget.py +0 -0
  56. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/__init__.py +0 -0
  57. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/application.py +0 -0
  58. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/argument_parser.py +0 -0
  59. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/cli.py +0 -0
  60. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/exceptions.py +0 -0
  61. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/main/result_codes.py +0 -0
  62. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/mapping/handle_mapping.py +0 -0
  63. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/mapping/mapping_to_html.py +0 -0
  64. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/mapping/mapping_to_xlsx.py +0 -0
  65. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/moverview/handle_moverview.py +0 -0
  66. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/moverview/moverview_to_html.py +0 -0
  67. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/moverview/moverview_to_xlsx.py +0 -0
  68. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/__init__.py +0 -0
  69. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/create_bom.py +0 -0
  70. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/create_project.py +0 -0
  71. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/create_readme.py +0 -0
  72. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/find_project.py +0 -0
  73. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/get_license_info.py +0 -0
  74. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/handle_project.py +0 -0
  75. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_ecc.py +0 -0
  76. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_licenses.py +0 -0
  77. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_project.py +0 -0
  78. {capycli-2.10.0.dev1 → capycli-2.10.1}/capycli/project/show_vulnerabilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capycli
3
- Version: 2.10.0.dev1
3
+ Version: 2.10.1
4
4
  Summary: CaPyCli - Clearing Automation Python Command Line Interface for SW360
5
5
  License-Expression: MIT
6
6
  License-File: License.md
@@ -38,17 +38,17 @@ Project-URL: issues, https://github.com/sw360/capycli/issues
38
38
  Description-Content-Type: text/markdown
39
39
 
40
40
  <!--
41
- # SPDX-FileCopyrightText: (c) 2018-2025 Siemens
41
+ # SPDX-FileCopyrightText: (c) 2018-2026 Siemens
42
42
  # SPDX-License-Identifier: MIT
43
43
  -->
44
44
 
45
- ![Header_Image](images/Github-social-capycli.png)
45
+ ![Header_Image](https://github.com/sw360/capycli/blob/0318f71ba1f9ba177432840717768fccfad1d6da/images/Github-social-capycli.png)
46
46
 
47
47
  # CaPyCli - Clearing Automation Python Command Line Tool for SW360
48
48
 
49
49
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/sw360/capycli/blob/main/License.md)
50
50
  [![PyPI](https://shields.io/pypi/v/capycli)](https://pypi.org/project/capycli/)
51
- [![Python Version](https://img.shields.io/badge/python-3.9%2C3.10%2C3.11-yellow?logo=python)](https://www.python.org/doc/versions/)
51
+ [![Python Version](https://img.shields.io/badge/python-3.10%2C3.14-yellow?logo=python)](https://www.python.org/doc/versions/)
52
52
  [![Static Checks](https://github.com/sw360/capycli/actions/workflows/static-checks.yml/badge.svg)](https://github.com/sw360/capycli/actions/workflows/static-checks.yml)
53
53
  [![Unit Tests](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
54
54
  [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tngraf/c8f15831ecdcf6e86ab2b69cbb2d4f89/raw/df1a91c074c5ee34dc1f0dcf82bc0e76e39b5b4e/capycli-cobertura-coverage.json&color=green)](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
@@ -300,7 +300,7 @@ This is a Python project managed using ```Poetry```.
300
300
 
301
301
  ## Copyright & License
302
302
 
303
- Copyright 2018-2025 Siemens
303
+ Copyright 2018-2026 Siemens
304
304
 
305
305
  This program and the accompanying materials are made
306
306
  available under the terms of the MIT License.
@@ -1,15 +1,15 @@
1
1
  <!--
2
- # SPDX-FileCopyrightText: (c) 2018-2025 Siemens
2
+ # SPDX-FileCopyrightText: (c) 2018-2026 Siemens
3
3
  # SPDX-License-Identifier: MIT
4
4
  -->
5
5
 
6
- ![Header_Image](images/Github-social-capycli.png)
6
+ ![Header_Image](https://github.com/sw360/capycli/blob/0318f71ba1f9ba177432840717768fccfad1d6da/images/Github-social-capycli.png)
7
7
 
8
8
  # CaPyCli - Clearing Automation Python Command Line Tool for SW360
9
9
 
10
10
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/sw360/capycli/blob/main/License.md)
11
11
  [![PyPI](https://shields.io/pypi/v/capycli)](https://pypi.org/project/capycli/)
12
- [![Python Version](https://img.shields.io/badge/python-3.9%2C3.10%2C3.11-yellow?logo=python)](https://www.python.org/doc/versions/)
12
+ [![Python Version](https://img.shields.io/badge/python-3.10%2C3.14-yellow?logo=python)](https://www.python.org/doc/versions/)
13
13
  [![Static Checks](https://github.com/sw360/capycli/actions/workflows/static-checks.yml/badge.svg)](https://github.com/sw360/capycli/actions/workflows/static-checks.yml)
14
14
  [![Unit Tests](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
15
15
  [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/tngraf/c8f15831ecdcf6e86ab2b69cbb2d4f89/raw/df1a91c074c5ee34dc1f0dcf82bc0e76e39b5b4e/capycli-cobertura-coverage.json&color=green)](https://github.com/sw360/capycli/actions/workflows/unit-tests.yml)
@@ -261,7 +261,7 @@ This is a Python project managed using ```Poetry```.
261
261
 
262
262
  ## Copyright & License
263
263
 
264
- Copyright 2018-2025 Siemens
264
+ Copyright 2018-2026 Siemens
265
265
 
266
266
  This program and the accompanying materials are made
267
267
  available under the terms of the MIT License.
@@ -0,0 +1,215 @@
1
+ # -------------------------------------------------------------------------------
2
+ # Copyright (c) 2025 Siemens
3
+ # All Rights Reserved.
4
+ # Author: thomas.graf@siemens.com
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ # -------------------------------------------------------------------------------
8
+
9
+ import logging
10
+ import os
11
+ import pathlib
12
+ import posixpath
13
+ import shutil
14
+ import sys
15
+ import tempfile
16
+ from typing import Any
17
+
18
+ from cyclonedx.model import ExternalReference, ExternalReferenceType, HashAlgorithm, HashType, XsUri
19
+ from cyclonedx.model.bom import Bom
20
+
21
+ import capycli.common.script_base
22
+ from capycli.bom.download_sources import BomDownloadSources
23
+ from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport, SbomWriter
24
+ from capycli.common.print import print_red, print_text, print_yellow
25
+ from capycli.common.script_support import ScriptSupport
26
+ from capycli.main.result_codes import ResultCode
27
+
28
+ LOG = capycli.get_logger(__name__)
29
+
30
+
31
+ class BomPackage(capycli.common.script_base.ScriptBase):
32
+ """
33
+ Create a single zip archive that contains the SBOM and all source and binary files.
34
+ The archive shoukd have the following structure:
35
+
36
+ sbom.cdx.json
37
+ +--- binaries
38
+ | +--- 77100a62c2e6f04b53977b9f541044d7d722693d
39
+ | | `--- some-binary.jar
40
+ | +--- 8031352b2bb0a49e67818bf04c027aa92e645d5c
41
+ | | `--- another-binary.jar
42
+ | `--- (... more ...)
43
+ `--- sources
44
+ +--- 6bb10559db88828dac3627de26974035a5dd4ddb
45
+ | `--- some-sources.jar
46
+ +--- 4d44e4edc4a7fb39f09b95b09f560a15976fa1ba
47
+ | `--- another-sources.jar
48
+ `--- (... more ...)
49
+ """
50
+ def download_files(self, sbom: Bom, target_folder: str) -> None:
51
+ """Download source and binary files for all items of the SBOM.
52
+
53
+ @params:
54
+ bom - Required : the bill of materials (BOM) (list)
55
+ target_folder - Required : folder to store the source files (string)
56
+ """
57
+
58
+ for component in sbom.components:
59
+ item_name = ScriptSupport.get_full_name_from_component(component)
60
+ print_text(" " + item_name)
61
+
62
+ # download source file
63
+ source_url = CycloneDxSupport.get_ext_ref_source_url(component)
64
+ if source_url:
65
+ result = BomDownloadSources.download_source_file(source_url._uri, target_folder)
66
+ else:
67
+ result = None
68
+ print_red(" No source URL specified!")
69
+
70
+ if result is not None:
71
+ (path, sha1) = result
72
+ # move file to appropriate location
73
+ filename = pathlib.Path(path).name
74
+ targetsha = os.path.join(target_folder, "sources", sha1)
75
+ os.makedirs(targetsha, exist_ok=True)
76
+ target = os.path.join(targetsha, filename)
77
+ shutil.move(path, target)
78
+
79
+ # update SBOM
80
+ new = False
81
+ ext_ref = CycloneDxSupport.get_ext_ref(
82
+ component, ExternalReferenceType.DISTRIBUTION, CaPyCliBom.SOURCE_FILE_COMMENT)
83
+ file_uri = posixpath.join("sources", sha1, filename)
84
+ if not file_uri.startswith("file://"):
85
+ file_uri = "file:///" + file_uri
86
+ if not ext_ref:
87
+ ext_ref = ExternalReference(
88
+ type=ExternalReferenceType.DISTRIBUTION,
89
+ comment=CaPyCliBom.SOURCE_FILE_COMMENT,
90
+ url=XsUri(file_uri))
91
+ new = True
92
+ else:
93
+ ext_ref.url = XsUri(file_uri)
94
+ ext_ref.hashes.add(HashType(
95
+ alg=HashAlgorithm.SHA_1,
96
+ content=sha1))
97
+ if new:
98
+ component.external_references.add(ext_ref)
99
+
100
+ # download binary file
101
+ binary_url = CycloneDxSupport.get_ext_ref_binary_url(component)
102
+ if binary_url:
103
+ result = BomDownloadSources.download_source_file(binary_url._uri, target_folder, is_binary=True)
104
+ else:
105
+ result = None
106
+ print_yellow(" No binary URL specified!")
107
+
108
+ if result is not None:
109
+ (path, sha1) = result
110
+ # move file to appropriate location
111
+ filename = pathlib.Path(path).name
112
+ targetsha = os.path.join(target_folder, "binaries", sha1)
113
+ os.makedirs(targetsha, exist_ok=True)
114
+ target = os.path.join(targetsha, filename)
115
+ shutil.move(path, target)
116
+
117
+ # update SBOM
118
+ new = False
119
+ ext_ref = CycloneDxSupport.get_ext_ref(
120
+ component, ExternalReferenceType.DISTRIBUTION, CaPyCliBom.BINARY_FILE_COMMENT)
121
+ file_uri = posixpath.join("binaries", sha1, filename)
122
+ if not file_uri.startswith("file://"):
123
+ file_uri = "file:///" + file_uri
124
+ if not ext_ref:
125
+ ext_ref = ExternalReference(
126
+ type=ExternalReferenceType.DISTRIBUTION,
127
+ comment=CaPyCliBom.BINARY_FILE_COMMENT,
128
+ url=XsUri(file_uri))
129
+ new = True
130
+ else:
131
+ ext_ref.url = XsUri(file_uri)
132
+ ext_ref.hashes.add(HashType(
133
+ alg=HashAlgorithm.SHA_1,
134
+ content=sha1))
135
+ if new:
136
+ component.external_references.add(ext_ref)
137
+
138
+ def run(self, args: Any) -> None:
139
+ """Main method
140
+
141
+ @params:
142
+ args - command line arguments
143
+ """
144
+ if args.debug:
145
+ global LOG
146
+ LOG = capycli.get_logger(__name__)
147
+ else:
148
+ # suppress (debug) log output from requests and urllib
149
+ logging.getLogger("requests").setLevel(logging.WARNING)
150
+ logging.getLogger("urllib3").setLevel(logging.WARNING)
151
+ logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
152
+
153
+ print_text(
154
+ "\n" + capycli.get_app_signature() +
155
+ " - create a single zip archive that contains the SBOM and all source and binary files\n")
156
+
157
+ if args.help:
158
+ print("usage: capycli bom bompackage -i bom.json")
159
+ print("")
160
+ print("optional arguments:")
161
+ print(" -h, --help show this help message and exit")
162
+ print(" -i INPUTFILE, input SBOM file to read from (JSON)")
163
+ print(" -o OUTPUT ARCHIVE, path of the output zip archive")
164
+ print(" -v be verbose")
165
+ return
166
+
167
+ if not args.inputfile:
168
+ print_red("No input file specified!")
169
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
170
+
171
+ if not os.path.isfile(args.inputfile):
172
+ print_red("Input file not found!")
173
+ sys.exit(ResultCode.RESULT_FILE_NOT_FOUND)
174
+
175
+ if not args.outputfile:
176
+ print_red("No output file specified!")
177
+ sys.exit(ResultCode.RESULT_COMMAND_ERROR)
178
+
179
+ print_text("Loading SBOM file " + args.inputfile)
180
+ try:
181
+ bom = CaPyCliBom.read_sbom(args.inputfile)
182
+ except Exception as ex:
183
+ print_red("Error reading input SBOM file: " + repr(ex))
184
+ sys.exit(ResultCode.RESULT_ERROR_READING_BOM)
185
+
186
+ if args.verbose:
187
+ print_text(" " + str(len(bom.components)) + "components read from SBOM file")
188
+
189
+ temp_dir = tempfile.TemporaryDirectory(prefix="capycli_bom_pkg_")
190
+ target_folder = temp_dir.name
191
+
192
+ pp = pathlib.Path(args.outputfile)
193
+ if pp.suffix.lower() != ".zip":
194
+ print_yellow("Warning: bom package file should have .zip extension")
195
+ args.bom_package = args.outputfile + ".zip"
196
+
197
+ print_text("\nDownloading files to folder " + target_folder + " ...")
198
+
199
+ self.download_files(bom, target_folder)
200
+
201
+ print_text("\nCreating BOM package " + args.outputfile)
202
+ try:
203
+ # add SBOM to temp folder
204
+ sbom_file = os.path.join(target_folder, "sbom.cdx.json")
205
+ SbomWriter.write_to_json(bom, sbom_file, True)
206
+ shutil.make_archive(
207
+ base_name=args.outputfile.rstrip(".zip"),
208
+ format="zip",
209
+ root_dir=target_folder)
210
+ temp_dir.cleanup()
211
+ except Exception as ex:
212
+ print_red("Error creating BOM package: " + repr(ex))
213
+ sys.exit(ResultCode.RESULT_ERROR_WRITING_BOM)
214
+
215
+ print("\n")
@@ -11,9 +11,7 @@ import logging
11
11
  import os
12
12
  import pathlib
13
13
  import re
14
- import shutil
15
14
  import sys
16
- import tempfile
17
15
  from typing import Any, Optional, Tuple
18
16
  from urllib.parse import urlparse
19
17
 
@@ -36,7 +34,8 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
36
34
  Download source files from the URL specified in the SBOM.
37
35
  """
38
36
 
39
- def get_filename_from_cd(self, cd: str) -> str:
37
+ @staticmethod
38
+ def get_filename_from_cd(cd: str) -> str:
40
39
  """
41
40
  Get filename from content-disposition.
42
41
  """
@@ -57,18 +56,20 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
57
56
  _, extension = os.path.splitext(filename)
58
57
  return extension in good_extensions
59
58
 
60
- def download_source_file(self, url: str, source_folder: str) -> Optional[Tuple[str, str]]:
59
+ @staticmethod
60
+ def download_source_file(url: str, source_folder: str, is_binary: bool = False) -> Optional[Tuple[str, str]]:
61
61
  """Download a file from a URL.
62
62
 
63
63
  @params:
64
64
  url - Required : url of the file to get uploaded (string)
65
65
  source_folder - Required : folder to store the source files (string)
66
+ is_binary - Optional : whether the file is a binary file (boolean, default: False)
66
67
  """
67
68
  print_text(" URL = " + url)
68
69
 
69
70
  try:
70
71
  response = requests.get(url, allow_redirects=True)
71
- filename = self.get_filename_from_cd(response.headers.get("content-disposition", ""))
72
+ filename = BomDownloadSources.get_filename_from_cd(response.headers.get("content-disposition", ""))
72
73
  if not filename:
73
74
  filename_ps = urlparse(url)
74
75
  if filename_ps:
@@ -82,7 +83,7 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
82
83
  path = os.path.join(source_folder, filename)
83
84
  if (response.status_code == requests.codes["ok"]):
84
85
  open(path, "wb").write(response.content)
85
- if not BomDownloadSources.is_good_source_file(path):
86
+ if not is_binary and not BomDownloadSources.is_good_source_file(path):
86
87
  print_yellow(" Downloaded file seems not to be a valid source file!")
87
88
  sha1 = hashlib.sha1(response.content).hexdigest()
88
89
  return (path, sha1)
@@ -187,7 +188,6 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
187
188
  print(" -source SOURCE source folder or additional source file")
188
189
  print(" -o OUTPUTFILE output file to write to")
189
190
  print(" -v be verbose")
190
- print(" -bp, --bom-package create a single zip archive that contains the SBOM and all source files")
191
191
  return
192
192
 
193
193
  if not args.inputfile:
@@ -209,23 +209,12 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
209
209
  print_text(" " + str(len(bom.components)) + "components read from SBOM file")
210
210
 
211
211
  source_folder = "./"
212
- cleanup_temp_dir = False
213
- if args.bom_package and not args.source:
214
- temp_dir = tempfile.TemporaryDirectory(prefix="capycli_bom_pkg_")
215
- source_folder = temp_dir.name
216
- cleanup_temp_dir = True
217
212
  if args.source:
218
213
  source_folder = args.source
219
214
  if (not source_folder) or (not os.path.isdir(source_folder)):
220
215
  print_red("Target source code folder does not exist!")
221
216
  sys.exit(ResultCode.RESULT_COMMAND_ERROR)
222
217
 
223
- if args.bom_package:
224
- pp = pathlib.Path(args.bom_package)
225
- if pp.suffix.lower() != ".zip":
226
- print_yellow("Warning: bom package file should have .zip extension")
227
- args.bom_package = args.bom_package + ".zip"
228
-
229
218
  print_text("\nDownloading source files to folder " + source_folder + " ...")
230
219
 
231
220
  self.download_sources(bom, source_folder)
@@ -244,21 +233,4 @@ class BomDownloadSources(capycli.common.script_base.ScriptBase):
244
233
  if args.verbose:
245
234
  print_text(" " + str(len(bom.components)) + " components written to SBOM file")
246
235
 
247
- if args.bom_package:
248
- print_text("\nCreating BOM package " + args.bom_package)
249
- try:
250
- # add SBOM to temp folder
251
- sbom_file = os.path.join(source_folder, "sbom.cdx.json")
252
- self.update_local_path(bom, sbom_file)
253
- SbomWriter.write_to_json(bom, sbom_file, True)
254
- shutil.make_archive(
255
- base_name=args.bom_package.rstrip(".zip"),
256
- format="zip",
257
- root_dir=source_folder)
258
- if cleanup_temp_dir:
259
- temp_dir.cleanup()
260
- except Exception as ex:
261
- print_red("Error creating BOM package: " + repr(ex))
262
- sys.exit(ResultCode.RESULT_ERROR_WRITING_BOM)
263
-
264
236
  print("\n")
@@ -1,5 +1,5 @@
1
1
  # -------------------------------------------------------------------------------
2
- # Copyright (c) 2019-24 Siemens
2
+ # Copyright (c) 2019-2025 Siemens
3
3
  # All Rights Reserved.
4
4
  # Author: thomas.graf@siemens.com
5
5
  #
@@ -10,6 +10,7 @@ import sys
10
10
  from typing import Any
11
11
 
12
12
  import capycli.bom.bom_convert
13
+ import capycli.bom.bom_package
13
14
  import capycli.bom.bom_validate
14
15
  import capycli.bom.check_bom
15
16
  import capycli.bom.check_bom_item_status
@@ -51,6 +52,7 @@ def run_bom_command(args: Any) -> None:
51
52
  print(" Merge merge two bills of material")
52
53
  print(" Findsources determine the source code for SBOM items")
53
54
  print(" Validate validate an SBOM")
55
+ print(" BomPackage create a single archive that contains the SBOM and all source and binary files")
54
56
  return
55
57
 
56
58
  subcommand = args.command[1].lower()
@@ -139,5 +141,11 @@ def run_bom_command(args: Any) -> None:
139
141
  app14.run(args)
140
142
  return
141
143
 
144
+ if subcommand == "bompackage":
145
+ """Validate an SBOM."""
146
+ app15 = capycli.bom.bom_package.BomPackage()
147
+ app15.run(args)
148
+ return
149
+
142
150
  print_red("Unknown sub-command: ")
143
151
  sys.exit(ResultCode.RESULT_COMMAND_ERROR)
@@ -1,5 +1,5 @@
1
1
  # -------------------------------------------------------------------------------
2
- # Copyright (c) 2019-2024 Siemens
2
+ # Copyright (c) 2019-2026 Siemens
3
3
  # All Rights Reserved.
4
4
  # Author: thomas.graf@siemens.com
5
5
  #
@@ -64,11 +64,14 @@ class ShowBom(capycli.common.script_base.ScriptBase):
64
64
  return
65
65
 
66
66
  for bomitem in bom.components:
67
+ name = bomitem.name
68
+ if bomitem.group:
69
+ name = bomitem.group + "/" + bomitem.name
67
70
  if not bomitem.version:
68
- print_text(" " + bomitem.name)
71
+ print_text(" " + name)
69
72
  print_yellow(" component version is missing!")
70
73
  else:
71
- print_text(" " + bomitem.name + ", " + bomitem.version)
74
+ print_text(" " + name + ", " + bomitem.version)
72
75
 
73
76
  if self.verbose:
74
77
  if bomitem.purl:
@@ -1,5 +1,5 @@
1
1
  # -------------------------------------------------------------------------------
2
- # Copyright (c) 2019-23 Siemens
2
+ # Copyright (c) 2019-2025 Siemens
3
3
  # All Rights Reserved.
4
4
  # Author: thomas.graf@siemens.com
5
5
  #
@@ -14,6 +14,7 @@ import capycli.dependencies.maven_list
14
14
  import capycli.dependencies.maven_pom
15
15
  import capycli.dependencies.nuget
16
16
  import capycli.dependencies.python
17
+ import capycli.dependencies.rust
17
18
  from capycli.common.print import print_red
18
19
  from capycli.main.result_codes import ResultCode
19
20
 
@@ -34,6 +35,7 @@ def run_dependency_command(args: Any) -> None:
34
35
  print(" Javascript determine dependencies for a JavaScript project")
35
36
  print(" MavenPom determine dependencies for a Java/Maven project using the pom.xml file")
36
37
  print(" MavenList determine dependencies for a Java/Maven project using a Maven command")
38
+ print(" Rust determine dependencies for a Rust project")
37
39
  return
38
40
 
39
41
  subcommand = args.command[1].lower()
@@ -67,5 +69,11 @@ def run_dependency_command(args: Any) -> None:
67
69
  app5.run(args)
68
70
  return
69
71
 
72
+ if subcommand == "rust":
73
+ """Determine Rust components/dependencies for a given project"""
74
+ app6 = capycli.dependencies.rust.GetRustDependencies()
75
+ app6.run(args)
76
+ return
77
+
70
78
  print_red("Unknown sub-command: " + subcommand)
71
79
  sys.exit(ResultCode.RESULT_COMMAND_ERROR)
@@ -105,8 +105,9 @@ class GetJavascriptDependencies(capycli.common.dependencies_base.DependenciesBas
105
105
  if "dev" in dep:
106
106
  isdev = dep["dev"]
107
107
 
108
+ version = dep.get("version", "")
108
109
  if isdev:
109
- # LOG.debug("Ignoring dev dependency: " + key + "," + dep["version"])
110
+ print_yellow("Ignoring dev dependency: " + key + "," + dep["version"])
110
111
  continue
111
112
 
112
113
  modified_key = ""
@@ -115,11 +116,14 @@ class GetJavascriptDependencies(capycli.common.dependencies_base.DependenciesBas
115
116
  else:
116
117
  modified_key = key
117
118
 
118
- LOG.debug("Checking dependency: " + modified_key + "," + dep["version"])
119
- purl = PackageURL("npm", "", modified_key, dep["version"], "", "")
119
+ if dep.get("link", ""):
120
+ print_yellow("Ignoring linked dependency: " + modified_key + "," + version)
121
+ continue
122
+ LOG.debug("Checking dependency: " + modified_key + "," + version)
123
+ purl = PackageURL("npm", "", modified_key, version, "", "")
120
124
  cxcomp = Component(
121
125
  name=modified_key.strip(),
122
- version=dep["version"].strip(),
126
+ version=version.strip(),
123
127
  purl=purl,
124
128
  bom_ref=purl.to_string())
125
129
 
@@ -779,7 +779,8 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
779
779
 
780
780
  return sbom
781
781
 
782
- def check_meta_data(self, sbom: Bom) -> bool:
782
+ @staticmethod
783
+ def check_meta_data(sbom: Bom, verbose: bool) -> bool:
783
784
  """
784
785
  Check whether all required meta-data is available.
785
786
 
@@ -790,37 +791,37 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
790
791
  bool: True if all required meta-data is available; otherwise False.
791
792
  """
792
793
 
793
- if self.verbose:
794
+ if verbose:
794
795
  print_text("\nChecking meta-data:")
795
796
 
796
797
  result = True
797
798
  cxcomp: Component
798
799
  for cxcomp in sbom.components:
799
- if self.verbose:
800
+ if verbose:
800
801
  print_text(f" {cxcomp.name}, {cxcomp.version}")
801
802
 
802
803
  if not cxcomp.purl:
803
804
  result = False
804
- if self.verbose:
805
+ if verbose:
805
806
  print_yellow(" package-url missing")
806
807
 
807
808
  homepage = CycloneDxSupport.get_ext_ref_website(cxcomp)
808
809
  if not homepage:
809
810
  result = False
810
- if self.verbose:
811
+ if verbose:
811
812
  print_yellow(" Homepage missing")
812
813
 
813
814
  if not cxcomp.licenses:
814
- if self.verbose:
815
+ if verbose:
815
816
  LOG.debug(" License missing")
816
817
  elif len(cxcomp.licenses) == 0:
817
- if self.verbose:
818
+ if verbose:
818
819
  LOG.debug(" License missing")
819
820
 
820
821
  src_url = CycloneDxSupport.get_ext_ref_source_url(cxcomp)
821
822
  if not src_url:
822
823
  result = False
823
- if self.verbose:
824
+ if verbose:
824
825
  print_yellow(" Source code URL missing")
825
826
 
826
827
  return result
@@ -884,7 +885,7 @@ class GetPythonDependencies(capycli.common.script_base.ScriptBase):
884
885
  print_text("Formatting package list...")
885
886
  sbom = self.convert_package_list(package_list, args.search_meta_data, args.package_source)
886
887
 
887
- self.check_meta_data(sbom)
888
+ GetPythonDependencies.check_meta_data(sbom, self.verbose)
888
889
 
889
890
  if self.verbose:
890
891
  print()