CycloneDX Interface eric7

Fri, 03 Jun 2022 19:54:57 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 03 Jun 2022 19:54:57 +0200
branch
eric7
changeset 9117
c6afba2049cf
parent 9116
698660ef7350
child 9118
9858a6c957f5

CycloneDX Interface
- added capability to create a Software Bill of Materials (SBOM) file in CycloneDX format
- added this capability to the pip Interface and Project

docs/changelog file | annotate | diff | comparison | revisions
eric7.epj file | annotate | diff | comparison | revisions
eric7/APIs/Python3/eric7.api file | annotate | diff | comparison | revisions
eric7/APIs/Python3/eric7.bas file | annotate | diff | comparison | revisions
eric7/CycloneDXInterface/CycloneDXConfigDialog.py file | annotate | diff | comparison | revisions
eric7/CycloneDXInterface/CycloneDXConfigDialog.ui file | annotate | diff | comparison | revisions
eric7/CycloneDXInterface/CycloneDXUtilities.py file | annotate | diff | comparison | revisions
eric7/CycloneDXInterface/__init__.py file | annotate | diff | comparison | revisions
eric7/Documentation/Help/source.qch file | annotate | diff | comparison | revisions
eric7/Documentation/Help/source.qhp file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.CycloneDXInterface.CycloneDXConfigDialog.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.CycloneDXInterface.CycloneDXUtilities.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.PipInterface.PipLicensesDialog.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.PipInterface.PipPackagesWidget.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Project.Project.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/eric7.Utilities.__init__.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/index-eric7.CycloneDXInterface.html file | annotate | diff | comparison | revisions
eric7/Documentation/Source/index-eric7.html file | annotate | diff | comparison | revisions
eric7/PipInterface/PipPackagesWidget.py file | annotate | diff | comparison | revisions
eric7/Project/Project.py file | annotate | diff | comparison | revisions
scripts/install.py file | annotate | diff | comparison | revisions
setup.py file | annotate | diff | comparison | revisions
--- a/docs/changelog	Fri Jun 03 12:09:02 2022 +0200
+++ b/docs/changelog	Fri Jun 03 19:54:57 2022 +0200
@@ -2,6 +2,13 @@
 ----------
 Version 22.7:
 - bug fixes
+- CycloneDX Interface
+  -- added capability to create a Software Bill of Materials (SBOM) file in
+     CycloneDX format
+- pip Interface
+  -- added SBOM capability
+- Project
+  -- added SBOM capability
 
 Version 22.6:
 - bug fixes
--- a/eric7.epj	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7.epj	Fri Jun 03 19:54:57 2022 +0200
@@ -275,6 +275,7 @@
       "eric7/CondaInterface/CondaPackageDetailsWidget.ui",
       "eric7/CondaInterface/CondaPackagesWidget.ui",
       "eric7/Cooperation/ChatWidget.ui",
+      "eric7/CycloneDXInterface/CycloneDXConfigDialog.ui",
       "eric7/DataViews/CodeMetricsDialog.ui",
       "eric7/DataViews/PyCoverageDialog.ui",
       "eric7/DataViews/PyCoverageHtmlReportDialog.ui",
@@ -987,6 +988,9 @@
       "eric7/Cooperation/CooperationClient.py",
       "eric7/Cooperation/CooperationServer.py",
       "eric7/Cooperation/__init__.py",
+      "eric7/CycloneDXInterface/CycloneDXConfigDialog.py",
+      "eric7/CycloneDXInterface/CycloneDXUtilities.py",
+      "eric7/CycloneDXInterface/__init__.py",
       "eric7/DataViews/CodeMetrics.py",
       "eric7/DataViews/CodeMetricsDialog.py",
       "eric7/DataViews/PyCoverageDialog.py",
--- a/eric7/APIs/Python3/eric7.api	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/APIs/Python3/eric7.api	Fri Jun 03 19:54:57 2022 +0200
@@ -167,6 +167,15 @@
 eric7.Cooperation.CooperationServer.CooperationServer.newConnection?7
 eric7.Cooperation.CooperationServer.CooperationServer.startListening?4(port=-1, findFreePort=False)
 eric7.Cooperation.CooperationServer.CooperationServer?1(address, parent=None)
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog.DefaultFileFormat?7
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog.DefaultFileNames?7
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog.Sources?7
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog.SupportedSchemas?7
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog.getData?4()
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged?4(fileFormat)
+eric7.CycloneDXInterface.CycloneDXConfigDialog.CycloneDXConfigDialog?1(environment, parent=None)
+eric7.CycloneDXInterface.CycloneDXUtilities.CycloneDXEnvironmentParser?1(venvName)
+eric7.CycloneDXInterface.CycloneDXUtilities.createCycloneDXFile?4(venvName)
 eric7.DataViews.CodeMetrics.COMMENT?7
 eric7.DataViews.CodeMetrics.DEDENT?7
 eric7.DataViews.CodeMetrics.EMPTY?7
--- a/eric7/APIs/Python3/eric7.bas	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/APIs/Python3/eric7.bas	Fri Jun 03 19:54:57 2022 +0200
@@ -130,6 +130,8 @@
 CreateDialogCodeDialog QDialog Ui_CreateDialogCodeDialog
 CustomHelpFormatter argparse.HelpFormatter
 CustomNamespace argparse.Namespace
+CycloneDXConfigDialog QDialog Ui_CycloneDXConfigDialog
+CycloneDXEnvironmentParser BaseParser
 DateTimeVisitor ast.NodeVisitor
 DebugClient DebugBase DebugClientBase ThreadExtension
 DebugServer QTcpServer
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/CycloneDXInterface/CycloneDXConfigDialog.py	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to configure the CycloneDX SBOM generation.
+"""
+
+import os
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtWidgets import QDialog
+
+from EricWidgets.EricApplication import ericApp
+from EricWidgets.EricPathPicker import EricPathPickerModes
+
+from .Ui_CycloneDXConfigDialog import Ui_CycloneDXConfigDialog
+
+
+class CycloneDXConfigDialog(QDialog, Ui_CycloneDXConfigDialog):
+    """
+    Class implementing a dialog to configure the CycloneDX SBOM generation.
+    """
+    SupportedSchemas = {
+        "JSON": ["1.4", "1.3", "1.2"],
+        "XML": ["1.4", "1.3", "1.2", "1.1", "1.0"],
+    }
+    Sources = {
+        "pipenv": "Pipfile.lock",
+        "poetry": "poetry.lock",
+        "requirements": "requirements.txt",
+    }
+    DefaultFileFormat = "JSON"
+    DefaultFileNames = {
+        "JSON": "cyclonedx.json",
+        "XML": "cyclonedx.xml",
+    }
+    
+    def __init__(self, environment, parent=None):
+        """
+        Constructor
+        
+        @param environment name of the virtual environment
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        if environment == "<project>":
+            project = ericApp().getObject("Project")
+            self.__defaultDirectory = project.getProjectPath()
+        else:
+            venvManager = ericApp().getObject("VirtualEnvManager")
+            self.__defaultDirectory = venvManager.getVirtualenvDirectory(
+                environment)
+        
+        self.environmentLabel.setText(environment)
+        
+        self.pipenvButton.setEnabled(os.path.isfile(os.path.join(
+            self.__defaultDirectory,
+            CycloneDXConfigDialog.Sources["pipenv"]
+        )))
+        self.poetryButton.setEnabled(os.path.isfile(os.path.join(
+            self.__defaultDirectory,
+            CycloneDXConfigDialog.Sources["poetry"]
+        )))
+        self.requirementsButton.setEnabled(os.path.isfile(os.path.join(
+            self.__defaultDirectory,
+            CycloneDXConfigDialog.Sources["requirements"]
+        )))
+        
+        self.filePicker.setMode(EricPathPickerModes.SAVE_FILE_OVERWRITE_MODE)
+        self.filePicker.setDefaultDirectory(self.__defaultDirectory)
+        
+        self.fileFormatComboBox.setCurrentText(
+            CycloneDXConfigDialog.DefaultFileFormat)
+        self.on_fileFormatComboBox_currentTextChanged(
+            CycloneDXConfigDialog.DefaultFileFormat)
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    @pyqtSlot(str)
+    def on_fileFormatComboBox_currentTextChanged(self, fileFormat):
+        """
+        Private slot to handle the selection of a SBOM file format.
+        
+        @param fileFormat selected format
+        @type str
+        """
+        # re-populate the file schema combo box
+        self.schemaVersionComboBox.clear()
+        self.schemaVersionComboBox.addItems(
+            CycloneDXConfigDialog.SupportedSchemas[fileFormat])
+        
+        # set the file filter
+        if fileFormat == "JSON":
+            self.filePicker.setFilters(
+                self.tr("JSON Files (*.json);;All Files (*)"))
+        elif fileFormat == "XML":
+            self.filePicker.setFilters(
+                self.tr("XML Files (*.xml);;All Files (*)"))
+        else:
+            self.filePicker.setFilters(self.tr("All Files (*)"))
+    
+    def getData(self):
+        """
+        Public method to get the SBOM configuration data.
+        
+        @return tuple containing the input source, the input file name, the
+            file format, the schema version and the path of the SBOM file to
+            be written
+        @rtype tuple of (str, str, str, str, str)
+        """
+        if self.environmentButton.isChecked():
+            inputSource = "environment"
+            inputFile = None
+        elif self.pipenvButton.isChecked():
+            inputSource = "pipenv"
+            inputFile = os.path.join(
+                self.__defaultDirectory,
+                CycloneDXConfigDialog.Sources["pipenv"]
+            )
+        elif self.poetryButton.isChecked():
+            inputSource = "poetry"
+            inputFile = os.path.join(
+                self.__defaultDirectory,
+                CycloneDXConfigDialog.Sources["poetry"]
+            )
+        elif self.requirementsButton.isChecked():
+            inputSource = "requirements"
+            inputFile = os.path.join(
+                self.__defaultDirectory,
+                CycloneDXConfigDialog.Sources["requirements"]
+            )
+        else:
+            # should not happen
+            inputSource = None
+            inputFile = None
+        
+        fileFormat = self.fileFormatComboBox.currentText()
+        schemaVersion = self.schemaVersionComboBox.currentText()
+        sbomFile = self.filePicker.text()
+        if not sbomFile:
+            try:
+                sbomFile = os.path.join(
+                    self.__defaultDirectory,
+                    CycloneDXConfigDialog.DefaultFileNames[fileFormat]
+                )
+            except KeyError:
+                # should not happen
+                sbomFile = None
+        
+        return inputSource, inputFile, fileFormat, schemaVersion, sbomFile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/CycloneDXInterface/CycloneDXConfigDialog.ui	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,228 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CycloneDXConfigDialog</class>
+ <widget class="QDialog" name="CycloneDXConfigDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>650</width>
+    <height>278</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>SBOM Configuration</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Environment:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="environmentLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Input</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QRadioButton" name="environmentButton">
+        <property name="toolTip">
+         <string>Select to build SBOM from environment</string>
+        </property>
+        <property name="text">
+         <string>Environment</string>
+        </property>
+        <property name="checked">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="pipenvButton">
+        <property name="toolTip">
+         <string>Select to build SBOM from 'Pipfile.lock' file</string>
+        </property>
+        <property name="text">
+         <string>PipEnv</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="poetryButton">
+        <property name="toolTip">
+         <string>Select to build SBOM from 'poetry.lock' file</string>
+        </property>
+        <property name="text">
+         <string>Poetry</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QRadioButton" name="requirementsButton">
+        <property name="toolTip">
+         <string>Select to build SBOM from 'requirements.txt' file</string>
+        </property>
+        <property name="text">
+         <string>Requirements</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>SBOM Output</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>File Format:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QComboBox" name="fileFormatComboBox">
+        <property name="toolTip">
+         <string>Select the format of the SBOM file</string>
+        </property>
+        <item>
+         <property name="text">
+          <string>JSON</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>XML</string>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>413</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Schema Version:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QComboBox" name="schemaVersionComboBox">
+        <property name="toolTip">
+         <string>Select the SBOM schema version of the SBOM file</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>File Name:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1" colspan="2">
+       <widget class="EricPathPicker" name="filePicker" native="true">
+        <property name="focusPolicy">
+         <enum>Qt::StrongFocus</enum>
+        </property>
+        <property name="toolTip">
+         <string>Enter the file path for the SBOM file (leave empty for default)</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>EricPathPicker</class>
+   <extends>QWidget</extends>
+   <header>EricWidgets/EricPathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>CycloneDXConfigDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>CycloneDXConfigDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/CycloneDXInterface/CycloneDXUtilities.py	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the interface to CycloneDX.
+"""
+
+import os
+
+from PyQt6.QtCore import QCoreApplication
+from PyQt6.QtWidgets import QDialog
+
+from EricWidgets.EricApplication import ericApp
+from EricWidgets import EricMessageBox
+
+from packageurl import PackageURL
+
+from cyclonedx.model import LicenseChoice
+from cyclonedx.model.bom import Bom
+from cyclonedx.model.component import Component
+from cyclonedx.output import (
+    OutputFormat, SchemaVersion, get_instance as get_output_instance
+)
+from cyclonedx.parser import BaseParser
+
+from cyclonedx_py.parser.pipenv import PipEnvFileParser
+from cyclonedx_py.parser.poetry import PoetryFileParser
+from cyclonedx_py.parser.requirements import RequirementsFileParser
+
+
+class CycloneDXEnvironmentParser(BaseParser):
+    """
+    Class implementing a parser to get package data for a named environment.
+    """
+    def __init__(self, venvName):
+        """
+        Constructor
+        
+        @param venvName name of the virtual environment
+        @type str
+        """
+        super().__init__()
+        
+        pip = ericApp().getObject("Pip")
+        packages = pip.getLicenses(venvName)
+        for package in packages:
+            comp = Component(
+                name=package["Name"],
+                version=package["Version"],
+                author=package["Author"],
+                description=package["Description"],
+                purl=PackageURL(
+                    type='pypi',
+                    name=package["Name"],
+                    version=package["Version"]
+                )
+            )
+            for lic in package["License"].split(";"):
+                comp.licenses.add(
+                    LicenseChoice(license_expression=lic.strip())
+                )
+            
+            self._components.append(comp)
+
+
+def createCycloneDXFile(venvName):
+    """
+    Function to create a CyccloneDX SBOM file.
+    
+    @param venvName name of the virtual environment
+    @type str
+    @exception RuntimeError raised to indicate illegal creation parameters
+    """
+    from .CycloneDXConfigDialog import CycloneDXConfigDialog
+    dlg = CycloneDXConfigDialog(venvName)
+    if dlg.exec() == QDialog.DialogCode.Accepted:
+        inputSource, inputFile, fileFormat, schemaVersion, sbomFile = (
+            dlg.getData()
+        )
+        
+        if inputSource not in ("environment", "pipenv", "poetry",
+                               "requirements"):
+            raise RuntimeError("Unsupported input source given.")
+        if fileFormat not in ("XML", "JSON"):
+            raise RuntimeError("Unsupported SBOM file format given.")
+        
+        if inputSource == "environment":
+            parser = CycloneDXEnvironmentParser(venvName)
+        else:
+            # all other parsers need an input file
+            if not os.path.isfile(inputFile):
+                EricMessageBox.warning(
+                    None,
+                    QCoreApplication.translate(
+                        "CycloneDX", "CycloneDX - SBOM Creation"),
+                    QCoreApplication.translate(
+                        "CycloneDX",
+                        "<p>The configured input file <b>{0}</b> does not"
+                        " exist. Aborting...</p>"
+                    ).format(inputFile)
+                )
+                return
+            
+            if inputSource == "pipenv":
+                parser = PipEnvFileParser(pipenv_lock_filename=inputFile)
+            elif inputSource == "poetry":
+                parser = PoetryFileParser(poetry_lock_filename=inputFile)
+            elif inputSource == "requirements":
+                parser = RequirementsFileParser(requirements_file=inputFile)
+        
+        if fileFormat == "XML":
+            outputFormat = OutputFormat.XML
+        elif fileFormat == "JSON":
+            outputFormat = OutputFormat.JSON
+        
+        if parser.has_warnings():
+            excludedList = ["<li>{0}</li>".format(warning.get_item())
+                            for warning in parser.get_warnings()]
+            EricMessageBox.warning(
+                None,
+                QCoreApplication.translate(
+                    "CycloneDX", "CycloneDX - SBOM Creation"),
+                QCoreApplication.translate(
+                    "CycloneDX",
+                    "<p>Some of the dependencies do not have pinned version"
+                    " numbers.<ul>{0}</ul>The above listed packages will NOT"
+                    " be included in the generated CycloneDX SBOM file as"
+                    " version is a mandatory field.</p>"
+                ).format("".join(excludedList))
+            )
+        
+        bom = Bom.from_parser(parser=parser)
+        output = get_output_instance(
+            bom=bom,
+            output_format=outputFormat,
+            schema_version=SchemaVersion['V{0}'.format(
+                schemaVersion.replace('.', '_')
+            )]
+        )
+        output.output_to_file(filename=sbomFile, allow_overwrite=True)
+        
+        EricMessageBox.information(
+            None,
+            QCoreApplication.translate(
+                "CycloneDX", "CycloneDX - SBOM Creation"),
+            QCoreApplication.translate(
+                "CycloneDX",
+                "<p>The SBOM data was written to file <b>{0}</b>.</p>"
+            ).format(sbomFile)
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/CycloneDXInterface/__init__.py	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2022 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the interface to the CycloneDX tool for the generation
+of Software Bill of Materials files.
+"""
+
+from .CycloneDXUtilities import createCycloneDXFile
+
+__all__ = [createCycloneDXFile]
Binary file eric7/Documentation/Help/source.qch has changed
--- a/eric7/Documentation/Help/source.qhp	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Documentation/Help/source.qhp	Fri Jun 03 19:54:57 2022 +0200
@@ -28,6 +28,10 @@
             <section title="eric7.Cooperation.CooperationClient" ref="eric7.Cooperation.CooperationClient.html" />
             <section title="eric7.Cooperation.CooperationServer" ref="eric7.Cooperation.CooperationServer.html" />
           </section>
+          <section title="eric7.CycloneDXInterface" ref="index-eric7.CycloneDXInterface.html">
+            <section title="eric7.CycloneDXInterface.CycloneDXConfigDialog" ref="eric7.CycloneDXInterface.CycloneDXConfigDialog.html" />
+            <section title="eric7.CycloneDXInterface.CycloneDXUtilities" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html" />
+          </section>
           <section title="eric7.DataViews" ref="index-eric7.DataViews.html">
             <section title="eric7.DataViews.CodeMetrics" ref="eric7.DataViews.CodeMetrics.html" />
             <section title="eric7.DataViews.CodeMetricsDialog" ref="eric7.DataViews.CodeMetricsDialog.html" />
@@ -3287,6 +3291,15 @@
       <keyword name="CustomHelpFormatter._format_action" id="CustomHelpFormatter._format_action" ref="eric7.PipInterface.piplicenses.html#CustomHelpFormatter._format_action" />
       <keyword name="CustomHelpFormatter._split_lines" id="CustomHelpFormatter._split_lines" ref="eric7.PipInterface.piplicenses.html#CustomHelpFormatter._split_lines" />
       <keyword name="CustomNamespace" id="CustomNamespace" ref="eric7.PipInterface.piplicenses.html#CustomNamespace" />
+      <keyword name="CycloneDXConfigDialog" id="CycloneDXConfigDialog" ref="eric7.CycloneDXInterface.CycloneDXConfigDialog.html#CycloneDXConfigDialog" />
+      <keyword name="CycloneDXConfigDialog (Constructor)" id="CycloneDXConfigDialog (Constructor)" ref="eric7.CycloneDXInterface.CycloneDXConfigDialog.html#CycloneDXConfigDialog.__init__" />
+      <keyword name="CycloneDXConfigDialog (Module)" id="CycloneDXConfigDialog (Module)" ref="eric7.CycloneDXInterface.CycloneDXConfigDialog.html" />
+      <keyword name="CycloneDXConfigDialog.getData" id="CycloneDXConfigDialog.getData" ref="eric7.CycloneDXInterface.CycloneDXConfigDialog.html#CycloneDXConfigDialog.getData" />
+      <keyword name="CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged" id="CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged" ref="eric7.CycloneDXInterface.CycloneDXConfigDialog.html#CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged" />
+      <keyword name="CycloneDXEnvironmentParser" id="CycloneDXEnvironmentParser" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html#CycloneDXEnvironmentParser" />
+      <keyword name="CycloneDXEnvironmentParser (Constructor)" id="CycloneDXEnvironmentParser (Constructor)" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html#CycloneDXEnvironmentParser.__init__" />
+      <keyword name="CycloneDXInterface (Package)" id="CycloneDXInterface (Package)" ref="index-eric7.CycloneDXInterface.html" />
+      <keyword name="CycloneDXUtilities (Module)" id="CycloneDXUtilities (Module)" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html" />
       <keyword name="DataViews (Package)" id="DataViews (Package)" ref="index-eric7.DataViews.html" />
       <keyword name="DateTimeVisitor" id="DateTimeVisitor" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.Miscellaneous.MiscellaneousChecker.html#DateTimeVisitor" />
       <keyword name="DateTimeVisitor (Constructor)" id="DateTimeVisitor (Constructor)" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.Miscellaneous.MiscellaneousChecker.html#DateTimeVisitor.__init__" />
@@ -11369,6 +11382,7 @@
       <keyword name="PipPackagesWidget.__allUpdateableItems" id="PipPackagesWidget.__allUpdateableItems" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__allUpdateableItems" />
       <keyword name="PipPackagesWidget.__availablePipVersion" id="PipPackagesWidget.__availablePipVersion" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__availablePipVersion" />
       <keyword name="PipPackagesWidget.__clearVulnerabilityInfo" id="PipPackagesWidget.__clearVulnerabilityInfo" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__clearVulnerabilityInfo" />
+      <keyword name="PipPackagesWidget.__createSBOMFile" id="PipPackagesWidget.__createSBOMFile" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__createSBOMFile" />
       <keyword name="PipPackagesWidget.__editConfiguration" id="PipPackagesWidget.__editConfiguration" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__editConfiguration" />
       <keyword name="PipPackagesWidget.__editUserConfiguration" id="PipPackagesWidget.__editUserConfiguration" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__editUserConfiguration" />
       <keyword name="PipPackagesWidget.__editVirtualenvConfiguration" id="PipPackagesWidget.__editVirtualenvConfiguration" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__editVirtualenvConfiguration" />
@@ -11831,6 +11845,7 @@
       <keyword name="Project.__checkProjectFileGroup" id="Project.__checkProjectFileGroup" ref="eric7.Project.Project.html#Project.__checkProjectFileGroup" />
       <keyword name="Project.__cleanupMake" id="Project.__cleanupMake" ref="eric7.Project.Project.html#Project.__cleanupMake" />
       <keyword name="Project.__closeAllWindows" id="Project.__closeAllWindows" ref="eric7.Project.Project.html#Project.__closeAllWindows" />
+      <keyword name="Project.__createSBOMFile" id="Project.__createSBOMFile" ref="eric7.Project.Project.html#Project.__createSBOMFile" />
       <keyword name="Project.__createSnapshotSource" id="Project.__createSnapshotSource" ref="eric7.Project.Project.html#Project.__createSnapshotSource" />
       <keyword name="Project.__createZipDirEntries" id="Project.__createZipDirEntries" ref="eric7.Project.Project.html#Project.__createZipDirEntries" />
       <keyword name="Project.__deleteDebugProperties" id="Project.__deleteDebugProperties" ref="eric7.Project.Project.html#Project.__deleteDebugProperties" />
@@ -11868,6 +11883,7 @@
       <keyword name="Project.__showContextMenuDebugger" id="Project.__showContextMenuDebugger" ref="eric7.Project.Project.html#Project.__showContextMenuDebugger" />
       <keyword name="Project.__showContextMenuGraphics" id="Project.__showContextMenuGraphics" ref="eric7.Project.Project.html#Project.__showContextMenuGraphics" />
       <keyword name="Project.__showContextMenuMake" id="Project.__showContextMenuMake" ref="eric7.Project.Project.html#Project.__showContextMenuMake" />
+      <keyword name="Project.__showContextMenuOthers" id="Project.__showContextMenuOthers" ref="eric7.Project.Project.html#Project.__showContextMenuOthers" />
       <keyword name="Project.__showContextMenuPackagers" id="Project.__showContextMenuPackagers" ref="eric7.Project.Project.html#Project.__showContextMenuPackagers" />
       <keyword name="Project.__showContextMenuRecent" id="Project.__showContextMenuRecent" ref="eric7.Project.Project.html#Project.__showContextMenuRecent" />
       <keyword name="Project.__showContextMenuSession" id="Project.__showContextMenuSession" ref="eric7.Project.Project.html#Project.__showContextMenuSession" />
@@ -18346,6 +18362,7 @@
       <keyword name="createConfigurationPage" id="createConfigurationPage" ref="eric7.Plugins.PluginVcsPySvn.html#createConfigurationPage" />
       <keyword name="createConfigurationPage" id="createConfigurationPage" ref="eric7.Plugins.PluginVcsSubversion.html#createConfigurationPage" />
       <keyword name="createCreateProcess" id="createCreateProcess" ref="eric7.DebugClients.Python.MultiProcessDebugExtension.html#createCreateProcess" />
+      <keyword name="createCycloneDXFile" id="createCycloneDXFile" ref="eric7.CycloneDXInterface.CycloneDXUtilities.html#createCycloneDXFile" />
       <keyword name="createDebuggerInterfaceNone" id="createDebuggerInterfaceNone" ref="eric7.Debugger.DebuggerInterfaceNone.html#createDebuggerInterfaceNone" />
       <keyword name="createDebuggerInterfacePython3" id="createDebuggerInterfacePython3" ref="eric7.Debugger.DebuggerInterfacePython.html#createDebuggerInterfacePython3" />
       <keyword name="createDefaultConfig" id="createDefaultConfig" ref="eric7.Plugins.VcsPlugins.vcsPySvn.SvnUtilities.html#createDefaultConfig" />
@@ -19271,6 +19288,8 @@
       <file>eric7.Cooperation.Connection.html</file>
       <file>eric7.Cooperation.CooperationClient.html</file>
       <file>eric7.Cooperation.CooperationServer.html</file>
+      <file>eric7.CycloneDXInterface.CycloneDXConfigDialog.html</file>
+      <file>eric7.CycloneDXInterface.CycloneDXUtilities.html</file>
       <file>eric7.DataViews.CodeMetrics.html</file>
       <file>eric7.DataViews.CodeMetricsDialog.html</file>
       <file>eric7.DataViews.PyCoverageDialog.html</file>
@@ -20406,6 +20425,7 @@
       <file>eric7.eric7config.html</file>
       <file>index-eric7.CondaInterface.html</file>
       <file>index-eric7.Cooperation.html</file>
+      <file>index-eric7.CycloneDXInterface.html</file>
       <file>index-eric7.DataViews.html</file>
       <file>index-eric7.DebugClients.Python.html</file>
       <file>index-eric7.DebugClients.html</file>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.CycloneDXInterface.CycloneDXConfigDialog.html	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.CycloneDXInterface.CycloneDXConfigDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.CycloneDXInterface.CycloneDXConfigDialog</h1>
+
+<p>
+Module implementing a dialog to configure the CycloneDX SBOM generation.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#CycloneDXConfigDialog">CycloneDXConfigDialog</a></td>
+<td>Class implementing a dialog to configure the CycloneDX SBOM generation.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="CycloneDXConfigDialog" ID="CycloneDXConfigDialog"></a>
+<h2>CycloneDXConfigDialog</h2>
+
+<p>
+    Class implementing a dialog to configure the CycloneDX SBOM generation.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_CycloneDXConfigDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>DefaultFileFormat</td></tr><tr><td>DefaultFileNames</td></tr><tr><td>Sources</td></tr><tr><td>SupportedSchemas</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#CycloneDXConfigDialog.__init__">CycloneDXConfigDialog</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#CycloneDXConfigDialog.getData">getData</a></td>
+<td>Public method to get the SBOM configuration data.</td>
+</tr>
+<tr>
+<td><a href="#CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged">on_fileFormatComboBox_currentTextChanged</a></td>
+<td>Private slot to handle the selection of a SBOM file format.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="CycloneDXConfigDialog.__init__" ID="CycloneDXConfigDialog.__init__"></a>
+<h4>CycloneDXConfigDialog (Constructor)</h4>
+<b>CycloneDXConfigDialog</b>(<i>environment, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>environment</i> (str)</dt>
+<dd>
+name of the virtual environment
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="CycloneDXConfigDialog.getData" ID="CycloneDXConfigDialog.getData"></a>
+<h4>CycloneDXConfigDialog.getData</h4>
+<b>getData</b>(<i></i>)
+
+<p>
+        Public method to get the SBOM configuration data.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple containing the input source, the input file name, the
+            file format, the schema version and the path of the SBOM file to
+            be written
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (str, str, str, str, str)
+</dd>
+</dl>
+<a NAME="CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged" ID="CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged"></a>
+<h4>CycloneDXConfigDialog.on_fileFormatComboBox_currentTextChanged</h4>
+<b>on_fileFormatComboBox_currentTextChanged</b>(<i>fileFormat</i>)
+
+<p>
+        Private slot to handle the selection of a SBOM file format.
+</p>
+<dl>
+
+<dt><i>fileFormat</i> (str)</dt>
+<dd>
+selected format
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/eric7.CycloneDXInterface.CycloneDXUtilities.html	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.CycloneDXInterface.CycloneDXUtilities</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.CycloneDXInterface.CycloneDXUtilities</h1>
+
+<p>
+Module implementing the interface to CycloneDX.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#CycloneDXEnvironmentParser">CycloneDXEnvironmentParser</a></td>
+<td>Class implementing a parser to get package data for a named environment.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#createCycloneDXFile">createCycloneDXFile</a></td>
+<td>Function to create a CyccloneDX SBOM file.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="CycloneDXEnvironmentParser" ID="CycloneDXEnvironmentParser"></a>
+<h2>CycloneDXEnvironmentParser</h2>
+
+<p>
+    Class implementing a parser to get package data for a named environment.
+</p>
+<h3>Derived from</h3>
+BaseParser
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#CycloneDXEnvironmentParser.__init__">CycloneDXEnvironmentParser</a></td>
+<td>Constructor</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="CycloneDXEnvironmentParser.__init__" ID="CycloneDXEnvironmentParser.__init__"></a>
+<h4>CycloneDXEnvironmentParser (Constructor)</h4>
+<b>CycloneDXEnvironmentParser</b>(<i>venvName</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>venvName</i> (str)</dt>
+<dd>
+name of the virtual environment
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="createCycloneDXFile" ID="createCycloneDXFile"></a>
+<h2>createCycloneDXFile</h2>
+<b>createCycloneDXFile</b>(<i>venvName</i>)
+
+<p>
+    Function to create a CyccloneDX SBOM file.
+</p>
+<dl>
+
+<dt><i>venvName</i> (str)</dt>
+<dd>
+name of the virtual environment
+</dd>
+</dl>
+<dl>
+
+<dt>Raises <b>RuntimeError</b>:</dt>
+<dd>
+raised to indicate illegal creation parameters
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/eric7.PipInterface.PipLicensesDialog.html	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Documentation/Source/eric7.PipInterface.PipLicensesDialog.html	Fri Jun 03 19:54:57 2022 +0200
@@ -111,14 +111,14 @@
 </dl>
 <a NAME="PipLicensesDialog.__filterPackagesByLicense" ID="PipLicensesDialog.__filterPackagesByLicense"></a>
 <h4>PipLicensesDialog.__filterPackagesByLicense</h4>
-<b>__filterPackagesByLicense</b>(<i>license</i>)
+<b>__filterPackagesByLicense</b>(<i>licenseName</i>)
 
 <p>
         Private slot to filter the list of packages by license.
 </p>
 <dl>
 
-<dt><i>license</i> (str)</dt>
+<dt><i>licenseName</i> (str)</dt>
 <dd>
 license name
 </dd>
--- a/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWidget.html	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWidget.html	Fri Jun 03 19:54:57 2022 +0200
@@ -83,6 +83,10 @@
 <td>Private slot to clear the vulnerability info.</td>
 </tr>
 <tr>
+<td><a href="#PipPackagesWidget.__createSBOMFile">__createSBOMFile</a></td>
+<td>Private slot to create a "Software Bill Of Material" file.</td>
+</tr>
+<tr>
 <td><a href="#PipPackagesWidget.__editConfiguration">__editConfiguration</a></td>
 <td>Private method to edit a configuration.</td>
 </tr>
@@ -494,6 +498,13 @@
 <p>
         Private slot to clear the vulnerability info.
 </p>
+<a NAME="PipPackagesWidget.__createSBOMFile" ID="PipPackagesWidget.__createSBOMFile"></a>
+<h4>PipPackagesWidget.__createSBOMFile</h4>
+<b>__createSBOMFile</b>(<i></i>)
+
+<p>
+        Private slot to create a "Software Bill Of Material" file.
+</p>
 <a NAME="PipPackagesWidget.__editConfiguration" ID="PipPackagesWidget.__editConfiguration"></a>
 <h4>PipPackagesWidget.__editConfiguration</h4>
 <b>__editConfiguration</b>(<i>venvName=""</i>)
--- a/eric7/Documentation/Source/eric7.Project.Project.html	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Documentation/Source/eric7.Project.Project.html	Fri Jun 03 19:54:57 2022 +0200
@@ -285,6 +285,10 @@
 <td>Private method to close all project related windows.</td>
 </tr>
 <tr>
+<td><a href="#Project.__createSBOMFile">__createSBOMFile</a></td>
+<td>Private slot to create a SBOM file of the project dependencies.</td>
+</tr>
+<tr>
 <td><a href="#Project.__createSnapshotSource">__createSnapshotSource</a></td>
 <td>Private method to create a snapshot plugin version.</td>
 </tr>
@@ -433,6 +437,10 @@
 <td>Private slot called before the make menu is shown.</td>
 </tr>
 <tr>
+<td><a href="#Project.__showContextMenuOthers">__showContextMenuOthers</a></td>
+<td>Private slot called before the 'Other Tools' menu is shown.</td>
+</tr>
+<tr>
 <td><a href="#Project.__showContextMenuPackagers">__showContextMenuPackagers</a></td>
 <td>Private slot called before the packagers menu is shown.</td>
 </tr>
@@ -1241,6 +1249,13 @@
 <p>
         Private method to close all project related windows.
 </p>
+<a NAME="Project.__createSBOMFile" ID="Project.__createSBOMFile"></a>
+<h4>Project.__createSBOMFile</h4>
+<b>__createSBOMFile</b>(<i></i>)
+
+<p>
+        Private slot to create a SBOM file of the project dependencies.
+</p>
 <a NAME="Project.__createSnapshotSource" ID="Project.__createSnapshotSource"></a>
 <h4>Project.__createSnapshotSource</h4>
 <b>__createSnapshotSource</b>(<i>filename</i>)
@@ -1685,6 +1700,13 @@
 <p>
         Private slot called before the make menu is shown.
 </p>
+<a NAME="Project.__showContextMenuOthers" ID="Project.__showContextMenuOthers"></a>
+<h4>Project.__showContextMenuOthers</h4>
+<b>__showContextMenuOthers</b>(<i></i>)
+
+<p>
+        Private slot called before the 'Other Tools' menu is shown.
+</p>
 <a NAME="Project.__showContextMenuPackagers" ID="Project.__showContextMenuPackagers"></a>
 <h4>Project.__showContextMenuPackagers</h4>
 <b>__showContextMenuPackagers</b>(<i></i>)
--- a/eric7/Documentation/Source/eric7.Utilities.__init__.html	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Documentation/Source/eric7.Utilities.__init__.html	Fri Jun 03 19:54:57 2022 +0200
@@ -1661,7 +1661,8 @@
 </p>
 <p>
     The file names for the test file is built by prepending the string
-    "test" and "test_" to the file name passed into this function.
+    "test" and "test_" to the file name passed into this function and
+    by appending the string "_test".
 </p>
 <dl>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/Documentation/Source/index-eric7.CycloneDXInterface.html	Fri Jun 03 19:54:57 2022 +0200
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.CycloneDXInterface</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<h1>eric7.CycloneDXInterface</h1>
+
+<p>
+Package implementing the interface to the CycloneDX tool for the generation
+of Software Bill of Materials files.
+</p>
+
+
+<h3>Modules</h3>
+<table>
+
+<tr>
+<td><a href="eric7.CycloneDXInterface.CycloneDXConfigDialog.html">CycloneDXConfigDialog</a></td>
+<td>Module implementing a dialog to configure the CycloneDX SBOM generation.</td>
+</tr>
+<tr>
+<td><a href="eric7.CycloneDXInterface.CycloneDXUtilities.html">CycloneDXUtilities</a></td>
+<td>Module implementing the interface to CycloneDX.</td>
+</tr>
+</table>
+</body></html>
\ No newline at end of file
--- a/eric7/Documentation/Source/index-eric7.html	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Documentation/Source/index-eric7.html	Fri Jun 03 19:54:57 2022 +0200
@@ -27,6 +27,10 @@
 <td>Package containing the modules dealing with cooperation.</td>
 </tr>
 <tr>
+<td><a href="index-eric7.CycloneDXInterface.html">CycloneDXInterface</a></td>
+<td>Package implementing the interface to the CycloneDX tool for the generation of Software Bill of Materials files.</td>
+</tr>
+<tr>
 <td><a href="index-eric7.DataViews.html">DataViews</a></td>
 <td>Package containing modules for viewing various data.</td>
 </tr>
--- a/eric7/PipInterface/PipPackagesWidget.py	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/PipInterface/PipPackagesWidget.py	Fri Jun 03 19:54:57 2022 +0200
@@ -1200,6 +1200,10 @@
             self.tr("Update Vulnerability Database"),
             self.__updateVulnerabilityDbCache)
         self.__pipMenu.addSeparator()
+        self.__cyclonedxAct = self.__pipMenu.addAction(
+            self.tr("Create SBOM file"),
+            self.__createSBOMFile)
+        self.__pipMenu.addSeparator()
         self.__cacheInfoAct = self.__pipMenu.addAction(
             self.tr("Show Cache Info..."),
             self.__showCacheInfo)
@@ -1260,6 +1264,8 @@
         self.__checkVulnerabilityAct.setEnabled(
             enable & self.vulnerabilityCheckBox.isEnabled())
         
+        self.__cyclonedxAct.setEnabled(enable)
+        
         self.__showLicensesDialogAct.setEnabled(enable)
     
     @pyqtSlot()
@@ -1850,3 +1856,19 @@
             parent=self
         )
         dlg.exec()
+    
+    ##################################################################
+    ## Interface to create a SBOM file using CycloneDX
+    ##################################################################
+    
+    @pyqtSlot()
+    def __createSBOMFile(self):
+        """
+        Private slot to create a "Software Bill Of Material" file.
+        """
+        import CycloneDXInterface
+        
+        venvName = self.environmentsComboBox.currentText()
+        if venvName == self.__pip.getProjectEnvironmentString():
+            venvName = "<project>"
+        CycloneDXInterface.createCycloneDXFile(venvName)
--- a/eric7/Project/Project.py	Fri Jun 03 12:09:02 2022 +0200
+++ b/eric7/Project/Project.py	Fri Jun 03 19:54:57 2022 +0200
@@ -2341,6 +2341,7 @@
                 self.pdata["MAKEPARAMS"]["MakeEnabled"])
             self.menuMakeAct.setEnabled(
                 self.pdata["MAKEPARAMS"]["MakeEnabled"])
+            self.menuOtherToolsAct.setEnabled(True)
             
             self.projectAboutToBeCreated.emit()
             
@@ -3033,6 +3034,7 @@
                         self.pdata["MAKEPARAMS"]["MakeEnabled"])
                     self.menuMakeAct.setEnabled(
                         self.pdata["MAKEPARAMS"]["MakeEnabled"])
+                    self.menuOtherToolsAct.setEnabled(True)
                     
                     # open a project debugger properties file being quiet
                     # about errors
@@ -3289,6 +3291,7 @@
         self.pluginGrp.setEnabled(False)
         self.makeGrp.setEnabled(False)
         self.menuMakeAct.setEnabled(False)
+        self.menuOtherToolsAct.setEnabled(False)
         
         self.__model.projectClosed()
         self.projectClosedHooks.emit()
@@ -4386,6 +4389,21 @@
         self.makeTestAct.triggered.connect(
             lambda: self.__executeMake(questionOnly=True))
         self.actions.append(self.makeTestAct)
+        
+        self.createSBOMAct = EricAction(
+            self.tr('Create SBOM File'),
+            self.tr('Create &SBOM File'), 0, 0,
+            self, 'project_create_sbom')
+        self.createSBOMAct.setStatusTip(
+            self.tr("Create a SBOM file of the project dependencies."))
+        self.createSBOMAct.setWhatsThis(self.tr(
+            """<b>Create SBOM File</b>"""
+            """<p>This allows the creation of a SBOM file of the project"""
+            """ dependencies. This may be based on various input sources"""
+            """ and will be saved as a CycloneDX SBOM file.</p>"""
+        ))
+        self.createSBOMAct.triggered.connect(self.__createSBOMFile)
+        self.actions.append(self.createSBOMAct)
 
         self.closeAct.setEnabled(False)
         self.saveAct.setEnabled(False)
@@ -4424,6 +4442,7 @@
         self.apidocMenu = QMenu(self.tr('Source &Documentation'), toolsMenu)
         self.apidocMenu.setTearOffEnabled(True)
         self.makeMenu = QMenu(self.tr('Make'), toolsMenu)
+        self.othersMenu = QMenu(self.tr('Other Tools'), toolsMenu)
         
         self.__menus = {
             "Main": menu,
@@ -4437,6 +4456,7 @@
             "Debugger": self.debuggerMenu,
             "Packagers": self.packagersMenu,
             "Make": self.makeMenu,
+            "OtherTools": self.othersMenu,
         }
         
         # connect the aboutToShow signals
@@ -4451,6 +4471,7 @@
         self.sessionMenu.aboutToShow.connect(self.__showContextMenuSession)
         self.debuggerMenu.aboutToShow.connect(self.__showContextMenuDebugger)
         self.makeMenu.aboutToShow.connect(self.__showContextMenuMake)
+        self.othersMenu.aboutToShow.connect(self.__showContextMenuOthers)
         menu.aboutToShow.connect(self.__showMenu)
         
         # build the show menu
@@ -4483,6 +4504,10 @@
         self.makeMenu.addActions(self.makeGrp.actions())
         self.makeMenu.addSeparator()
         
+        # build the 'Other Tools' menu
+        self.othersMenu.setTearOffEnabled(True)
+        self.othersMenu.addAction(self.createSBOMAct)
+        
         # build the project main menu
         menu.setTearOffEnabled(True)
         menu.addActions(self.actGrp1.actions())
@@ -4519,6 +4544,8 @@
         self.menuApidocAct = toolsMenu.addMenu(self.apidocMenu)
         toolsMenu.addSeparator()
         self.menuPackagersAct = toolsMenu.addMenu(self.packagersMenu)
+        toolsMenu.addSeparator()
+        self.menuOtherToolsAct = toolsMenu.addMenu(self.othersMenu)
         
         self.menuCheckAct.setEnabled(False)
         self.menuShowAct.setEnabled(False)
@@ -4528,6 +4555,7 @@
         self.menuApidocAct.setEnabled(False)
         self.menuPackagersAct.setEnabled(False)
         self.menuMakeAct.setEnabled(False)
+        self.menuOtherToolsAct.setEnabled(False)
         
         self.__menu = menu
         self.__toolsMenu = toolsMenu
@@ -5914,6 +5942,25 @@
         @rtype str
         """
         return self.pdata["DOCSTRING"]
+    
+    #########################################################################
+    ## Below are methods implementing the 'SBOM' support
+    #########################################################################
+     
+    def __showContextMenuOthers(self):
+        """
+        Private slot called before the 'Other Tools' menu is shown.
+        """
+        self.showMenu.emit("OtherTools", self.othersMenu)
+   
+    @pyqtSlot()
+    def __createSBOMFile(self):
+        """
+        Private slot to create a SBOM file of the project dependencies.
+        """
+        import CycloneDXInterface
+        
+        CycloneDXInterface.createCycloneDXFile("<project>")
 
 #
 # eflag: noqa = M601
--- a/scripts/install.py	Fri Jun 03 12:09:02 2022 +0200
+++ b/scripts/install.py	Fri Jun 03 19:54:57 2022 +0200
@@ -1621,6 +1621,8 @@
         "jedi": ("jedi", ""),
         "packaging": ("packaging", ""),
         "pipdeptree": ("pipdeptree", ""),
+        "cyclonedx-python-lib": ("cyclonedx", ""),
+        "cyclonedx-bom": ("cyclonedx_py", ""),
     }
     if not ignorePyqt6Tools:
         optionalModulesList["qt6-applications"] = ("qt6_applications", "")
--- a/setup.py	Fri Jun 03 12:09:02 2022 +0200
+++ b/setup.py	Fri Jun 03 19:54:57 2022 +0200
@@ -356,6 +356,8 @@
         "jedi",
         "packaging",
         "pipdeptree",
+        "cyclonedx-python-lib",
+        "cyclonedx-bom",
         "pywin32>=1.0;platform_system=='Windows'",
     ],
     data_files=getDataFiles(),

eric ide

mercurial