use indexmap::{IndexMap, IndexSet};
use pkgcraft::dep::{Cpn, Cpv};
use pkgcraft::pkg::ebuild::{EbuildPkg, EbuildRawPkg};
use pkgcraft::repo::PkgRepository;
use pkgcraft::restrict::Scope;
use tracing::warn;
use crate::check::{CheckRun, CheckRunner};
use crate::report::ReportScope;
use crate::scan::ScannerRun;
use crate::source::*;
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub(super) enum Target {
Cpv(Cpv),
Cpn(Cpn),
Category(String),
Repo,
}
impl Target {
fn run(&self, runner: &CheckRunner, run: &ScannerRun) {
match self {
Self::Cpn(cpn) => runner.run_cpn(cpn, run),
Self::Cpv(cpv) => runner.run_cpv(cpv, run),
Self::Category(cat) => runner.run_category(cat, run),
Self::Repo => runner.run_repo(run),
}
}
fn finish(&self, runner: &CheckRunner, run: &ScannerRun) {
match self {
Self::Cpn(cpn) => runner.finish_cpn(cpn, run),
Self::Cpv(cpv) => runner.finish_cpv(cpv, run),
Self::Category(cat) => runner.finish_category(cat, run),
_ => (),
}
}
}
impl From<Cpn> for Target {
fn from(value: Cpn) -> Self {
Self::Cpn(value)
}
}
impl From<Cpv> for Target {
fn from(value: Cpv) -> Self {
Self::Cpv(value)
}
}
impl From<&ReportScope> for Target {
fn from(value: &ReportScope) -> Self {
match value {
ReportScope::Version(cpv, _) => cpv.cpn().clone().into(),
ReportScope::Package(cpn) => cpn.clone().into(),
ReportScope::Category(s) => Target::Category(s.to_string()),
ReportScope::Repo(_) => Target::Repo,
}
}
}
#[derive(Default)]
pub(super) struct SyncCheckRunner {
runners: IndexMap<SourceKind, GenericCheckRunner>,
}
impl SyncCheckRunner {
pub(super) fn new(run: &ScannerRun) -> Self {
let mut runner = Self::default();
for r in &run.runners {
runner.add_runner(r, run);
}
runner
}
fn add_runner(&mut self, runner: &CheckRunner, run: &ScannerRun) {
for source in runner
.check
.sources
.iter()
.filter(|source| source.scope() <= run.scope)
.copied()
{
self.runners
.entry(source)
.or_insert_with(|| GenericCheckRunner::new(source))
.add_runner(runner.clone())
}
}
pub(super) fn run_checks(&self, target: &Target, run: &ScannerRun) {
for runner in self.runners.values() {
runner.run_checks(target, run);
}
}
pub(super) fn run(&self, runner: &CheckRunner, target: &Target, run: &ScannerRun) {
for r in runner
.check
.sources
.iter()
.filter_map(|x| self.runners.get(x))
{
r.run(runner, target, run);
}
}
pub(super) fn finish_target(
&self,
runner: &CheckRunner,
target: &Target,
run: &ScannerRun,
) {
target.finish(runner, run);
}
pub(super) fn finish_check(&self, runner: &CheckRunner, run: &ScannerRun) {
runner.finish(run);
}
}
enum GenericCheckRunner {
EbuildPkg(EbuildPkgCheckRunner),
EbuildRawPkg(EbuildRawPkgCheckRunner),
Cpn(CpnCheckRunner),
Cpv(CpvCheckRunner),
Category,
Repo,
}
impl GenericCheckRunner {
fn new(source: SourceKind) -> Self {
match source {
SourceKind::EbuildPkg => Self::EbuildPkg(Default::default()),
SourceKind::EbuildRawPkg => Self::EbuildRawPkg(Default::default()),
SourceKind::Cpn => Self::Cpn(Default::default()),
SourceKind::Cpv => Self::Cpv(Default::default()),
SourceKind::Category => Self::Category,
SourceKind::Repo => Self::Repo,
}
}
fn add_runner(&mut self, runner: CheckRunner) {
match self {
Self::EbuildPkg(r) => r.add_runner(runner),
Self::EbuildRawPkg(r) => r.add_runner(runner),
Self::Cpn(r) => r.add_runner(runner),
Self::Cpv(r) => r.add_runner(runner),
_ => (),
}
}
fn run_checks(&self, target: &Target, run: &ScannerRun) {
match (self, target) {
(Self::EbuildPkg(r), Target::Cpn(cpn)) => r.run_checks(cpn, run),
(Self::EbuildRawPkg(r), Target::Cpn(cpn)) => r.run_checks(cpn, run),
(Self::Cpn(r), Target::Cpn(cpn)) => r.run_checks(cpn, run),
(Self::Cpv(r), Target::Cpn(cpn)) => r.run_checks(cpn, run),
_ => (),
}
}
fn run(&self, runner: &CheckRunner, target: &Target, run: &ScannerRun) {
match (self, target) {
(Self::EbuildPkg(r), Target::Cpv(cpv)) => r.run_pkg(runner, cpv, run),
(Self::EbuildPkg(r), Target::Cpn(cpn)) => r.run_pkg_set(runner, cpn, run),
(Self::EbuildRawPkg(r), Target::Cpv(cpv)) => r.run_pkg(runner, cpv, run),
(Self::EbuildRawPkg(r), Target::Cpn(cpn)) => r.run_pkg_set(runner, cpn, run),
_ => target.run(runner, run),
}
}
}
macro_rules! make_pkg_check_runner {
($pkg_check_runner:ident, $source:ty, $pkg:ty) => {
#[derive(Default)]
struct $pkg_check_runner {
pkg_runners: IndexSet<CheckRunner>,
pkg_set_runners: IndexSet<CheckRunner>,
source: $source,
cache: std::sync::OnceLock<PkgCache<$pkg>>,
}
impl $pkg_check_runner {
fn cache(&self, run: &ScannerRun) -> &PkgCache<$pkg> {
self.cache.get_or_init(|| PkgCache::new(&self.source, run))
}
fn add_runner(&mut self, runner: CheckRunner) {
if runner.check.scope == Scope::Version {
self.pkg_runners.insert(runner);
} else {
self.pkg_set_runners.insert(runner);
}
}
fn run_checks(&self, cpn: &Cpn, run: &ScannerRun) {
let source = &self.source;
let mut pkgs = Ok(vec![]);
for result in source.iter_restrict(run, cpn) {
match result {
Ok(pkg) => {
for runner in &self.pkg_runners {
source.run_pkg(runner, &pkg, run);
}
if !self.pkg_set_runners.is_empty()
&& let Ok(pkgs) = pkgs.as_mut()
{
pkgs.push(pkg);
}
}
Err(e) => pkgs = Err(e),
}
}
match &pkgs {
Ok(pkgs) => {
if !pkgs.is_empty() {
for runner in &self.pkg_set_runners {
source.run_pkg_set(runner, cpn, pkgs, run);
}
}
}
Err(e) => warn!("skipping {source} set checks due to {e}"),
}
}
fn run_pkg(&self, runner: &CheckRunner, cpv: &Cpv, run: &ScannerRun) {
match self.cache(run).get_pkg(cpv) {
Some(Ok(pkg)) => self.source.run_pkg(runner, pkg, run),
Some(Err(e)) => warn!("{runner}: skipping due to {e}"),
None => warn!("{runner}: skipping due to filtered pkg: {cpv}"),
}
}
fn run_pkg_set(&self, runner: &CheckRunner, cpn: &Cpn, run: &ScannerRun) {
match self.cache(run).get_pkgs() {
Ok(pkgs) => {
if !pkgs.is_empty() {
self.source.run_pkg_set(runner, cpn, pkgs, run);
}
}
Err(e) => warn!("{runner}: skipping due to {e}"),
}
}
}
};
}
make_pkg_check_runner!(EbuildPkgCheckRunner, EbuildPkgSource, EbuildPkg);
make_pkg_check_runner!(EbuildRawPkgCheckRunner, EbuildRawPkgSource, EbuildRawPkg);
#[derive(Default)]
struct CpnCheckRunner {
runners: IndexSet<CheckRunner>,
}
impl CpnCheckRunner {
fn add_runner(&mut self, runner: CheckRunner) {
self.runners.insert(runner);
}
fn run_checks(&self, cpn: &Cpn, run: &ScannerRun) {
for runner in &self.runners {
runner.run_cpn(cpn, run);
if runner.check.finish_target() {
runner.finish_cpn(cpn, run);
}
}
}
}
#[derive(Default)]
struct CpvCheckRunner {
runners: IndexSet<CheckRunner>,
}
impl CpvCheckRunner {
fn add_runner(&mut self, runner: CheckRunner) {
self.runners.insert(runner);
}
fn run_checks(&self, cpn: &Cpn, run: &ScannerRun) {
for cpv in run.repo.iter_cpv_restrict(cpn) {
for runner in &self.runners {
runner.run_cpv(&cpv, run);
if runner.check.finish_target() {
runner.finish_cpv(&cpv, run);
}
}
}
}
}