Source code for swh.osv.luigi.cherrypicks

# 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 listing cherry-picks
====================================
"""

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

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


[docs] class ListCherrypicks(luigi.Task): """Lists cherry-picks using subgraph node ids (without transitive reduction).""" local_graph_path = luigi.PathParameter() graph_name = luigi.StrParameter(default="graph") vulnerabilities_dir = luigi.PathParameter() include_vcs_matches = luigi.BoolParameter(default=True) @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` and :class:`swh.osv.luigi.reachability.ComputeConnectedComponents`.""" 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, ), }
[docs] def output(self) -> luigi.LocalTarget: """``cherrypicks_subgraphnodeids`` directory containing cherrypick multimaps and their EF indices.""" return luigi.LocalTarget( self.vulnerabilities_dir / "cherrypicks_subgraphnodeids" )
[docs] def run(self) -> None: """Runs ``list-cherrypicks`` and ``build-ef`` on each multimap.""" from .shell import Rust out_dir = Path(self.output().path) out_dir.mkdir(parents=True, exist_ok=True) cherrypicks_base = out_dir / "cherrypicks2originals" originals_base = out_dir / "originals2cherrypicks" # fmt: off Rust( "list-cherrypicks", "--graph", self.local_graph_path / self.graph_name, "--db", self.input()["sqlite"], "--subgraphwccs", self.input()["subgraphwccs"], "--cherrypicks2originals-out", cherrypicks_base, "--originals2cherrypicks-out", originals_base, "--subgraph-nodeids", ).run() Rust( "build-ef", "--graph", cherrypicks_base, ).run() Rust( "build-ef", "--graph", originals_base, ).run() # fmt: on (out_dir / ".stamp").touch()
[docs] class UploadCherrypicksToS3(_BaseUploadToS3): """Uploads the cherry-picks multimaps to S3.""" include_vcs_matches = luigi.BoolParameter(default=True)
[docs] def requires(self) -> Dict[str, luigi.Task]: """Returns an instance of :class:`ListCherrypicks`.""" return { "cherrypicks": ListCherrypicks( local_graph_path=self.local_graph_path, graph_name=self.graph_name, vulnerabilities_dir=self.vulnerabilities_dir, include_vcs_matches=self.include_vcs_matches, ), }
[docs] def output(self) -> luigi.Target: """S3 target for the cherrypick multimap directory stamp.""" import luigi.contrib.s3 return luigi.contrib.s3.S3Target( f"{self._s3_prefix()}/cherrypicks_subgraphnodeids/.stamp" )
def _files_to_upload(self) -> Iterator[Tuple[PathLike, str]]: """Yields all cherrypick multimap files for upload.""" local_dir = Path(self.input()["cherrypicks"].path) for path in sorted(local_dir.rglob("*")): if path.is_dir(): continue yield ( path, f"cherrypicks_subgraphnodeids/{path.relative_to(local_dir)}", )