Source code for swh.osv.luigi.commit2vuln

# Copyright (C) 2026  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information

"""
Luigi tasks for building the commit-to-vulnerability mapping
============================================================
"""

from os import PathLike
from pathlib import Path
from typing import Dict, Iterator

import luigi

from swh.graph.luigi.compressed_graph import LocalGraph
from swh.osv.luigi import DownloadVulnerabilities
from swh.osv.luigi.reachability import ComputeConnectedComponents
from swh.osv.luigi.utils import _BaseUploadToS3
from swh.osv.luigi.vuln2commit import Vuln2CommitParquet


[docs] class Commit2VulnBv(luigi.Task): """Creates a BVGraph that maps each revision to the vulnerabilities that affect it.""" local_graph_path = luigi.PathParameter() graph_name = luigi.StrParameter(default="graph") vulnerabilities_dir = luigi.PathParameter() include_vcs_matches = luigi.BoolParameter(default=True) use_cherrypicks = luigi.BoolParameter(default=False) @property def resources(self): """Declares the task uses every CPU available""" import socket hostname = socket.getfqdn() return { f"{hostname}_max_cpu": 1, }
[docs] def requires(self) -> Dict[str, luigi.Task]: """Returns instances of :class:`swh.graph.luigi.compressed_graph.LocalGraph`, :class:`swh.osv.luigi.DownloadVulnerabilities`, :class:`swh.osv.luigi.reachability.ComputeConnectedComponents` and :class:`swh.osv.luigi.vuln2commit.Vuln2CommitParquet`.""" return { "graph": LocalGraph(local_graph_path=self.local_graph_path), "sqlite": DownloadVulnerabilities( local_graph_path=self.local_graph_path, graph_name=self.graph_name, vulnerabilities_dir=self.vulnerabilities_dir, ), "subgraphwccs": ComputeConnectedComponents( local_graph_path=self.local_graph_path, graph_name=self.graph_name, vulnerabilities_dir=self.vulnerabilities_dir, include_vcs_matches=self.include_vcs_matches, ), "vuln2commit": Vuln2CommitParquet( local_graph_path=self.local_graph_path, graph_name=self.graph_name, vulnerabilities_dir=self.vulnerabilities_dir, include_vcs_matches=self.include_vcs_matches, use_cherrypicks=self.use_cherrypicks, ), }
[docs] def output(self) -> luigi.LocalTarget: """BVGraph file mapping revision ids to vulnerability ids.""" dir_name = ( "commit2vuln_using_cherrypicks" if self.use_cherrypicks else "commit2vuln_without_cherrypicks" ) return luigi.LocalTarget(self.vulnerabilities_dir / f"{dir_name}.graph")
[docs] def run(self) -> None: """Runs ``build-commit2vuln-bv`` and ``build-ef``.""" from .shell import Rust multimap_base = Path(self.output().path).with_suffix("") # fmt: off Rust( "build-commit2vuln-bv", "--graph", self.local_graph_path / self.graph_name, "--db", self.input()["sqlite"], "--subgraphwccs", self.input()["subgraphwccs"], "--vuln2commit-dir", self.input()["vuln2commit"]["dir"], "--multimap-out", multimap_base, ).run() Rust( "build-ef", "--graph", multimap_base, ).run()
# fmt: on
[docs] class UploadCommit2VulnBvToS3(_BaseUploadToS3): """Uploads the commit2vuln BVGraph to S3.""" include_vcs_matches = luigi.BoolParameter(default=True) use_cherrypicks = luigi.BoolParameter(default=False)
[docs] def requires(self) -> Dict[str, luigi.Task]: """Returns an instance of :class:`Commit2VulnBv`.""" return { "commit2vuln": Commit2VulnBv( local_graph_path=self.local_graph_path, graph_name=self.graph_name, vulnerabilities_dir=self.vulnerabilities_dir, include_vcs_matches=self.include_vcs_matches, use_cherrypicks=self.use_cherrypicks, ), }
[docs] def output(self) -> luigi.Target: """S3 target for the commit2vuln BVGraph file.""" import luigi.contrib.s3 dir_name = ( "commit2vuln_using_cherrypicks" if self.use_cherrypicks else "commit2vuln_without_cherrypicks" ) return luigi.contrib.s3.S3Target(f"{self._s3_prefix()}/{dir_name}.graph")
def _files_to_upload(self) -> Iterator[tuple[PathLike, str]]: base = Path(self.input()["commit2vuln"].path).with_suffix("") for path in sorted(base.parent.glob(f"{base.name}.*")): if path.is_dir(): continue yield (path, str(path.relative_to(self.vulnerabilities_dir)))