Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency filters for production #26

Merged
merged 17 commits into from
Jun 11, 2024
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Depends:
R (>= 3.5.0)
Imports:
gh,
igraph,
jsonlite,
nanonext,
pkgsearch,
Expand Down
5 changes: 5 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export(assert_package)
export(assert_release_exists)
export(get_current_versions)
export(issues_checks)
export(issues_dependencies)
export(issues_descriptions)
export(issues_versions)
export(meta_checks)
Expand All @@ -16,6 +17,10 @@ export(review_pull_request)
export(review_pull_requests)
export(try_message)
importFrom(gh,gh)
importFrom(igraph,V)
importFrom(igraph,graph)
importFrom(igraph,neighbors)
importFrom(igraph,subcomponent)
importFrom(jsonlite,parse_json)
importFrom(jsonlite,read_json)
importFrom(jsonlite,stream_in)
Expand Down
72 changes: 72 additions & 0 deletions R/issues_dependencies.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#' @title Report package dependency issues
#' @export
#' @family issues
#' @description Flag packages which have issues in their strong dependencies
#' (`Imports:`, `Depends:`, and `LinkingTo:` in the `DESCRIPTION`.)
#' These include indirect/upstream dependencies, as well, not just
#' the explicit mentions in the `DESCRIPTION` file.
#' @inheritSection record_issues Package issues
#' @return A nested list of problems triggered by dependencies.
#' The names of top-level elements are packages affected downstream.
#' The value of each top-level element is a list whose names are
# packages at the source of a problem upstream.
#' Each element of this inner list is a character
#' vector of relevant dependencies of the downstream package.
#'
#' For example, consider a linear dependency graph where `crew.cluster`
#' depends on `crew`, `crew` depends on `mirai`, and
#' `mirai` depends on `nanonext`. We represent the graph like this:
#' `nanonext -> mirai -> crew -> crew.cluster`.
#' If `nanonext` has an issue, then [issues_dependencies()] returns
#' `list(crew.cluster = list(nanonext = "crew"), ...)`, where `...`
#' stands for additional named list entries. From this list, we deduce
#' that `nanonext` is causing an issue affecting `crew.cluster` through
#' the direct dependency on `crew`.
#'
#' The choice in output format from [issues_dependencies()] allows package
#' maintainers to more easily figure out which direct dependencies
#' are contributing issues and drop those direct dependencies if necessary.
#' @param packages Character vector of names of packages with other issues.
#' @param meta A data frame with R-universe package check results
#' returned by [meta_checks()].
#' @examples
#' meta <- meta_packages(repo = "https://wlandau.r-universe.dev")
#' issues_dependencies(packages = character(0L), meta = meta)
#' issues_dependencies(packages = "crew.aws.batch", meta = meta)
#' issues_dependencies(packages = "nanonext", meta = meta)
#' issues_dependencies(packages = "crew", meta = meta)
#' issues_dependencies(packages = c("crew", "mirai"), meta = meta)
issues_dependencies <- function(packages, meta) {
shikokuchuo marked this conversation as resolved.
Show resolved Hide resolved
graph <- issues_dependencies_graph(meta)
vertices <- names(igraph::V(graph))
issues <- list()
for (package in intersect(packages, vertices)) {
revdeps <- names(igraph::subcomponent(graph, v = package, mode = "out"))
revdeps <- setdiff(revdeps, package)
for (revdep in revdeps) {
neighbors <- names(igraph::neighbors(graph, v = revdep, mode = "in"))
issues[[revdep]][[package]] <- intersect(neighbors, revdeps)
}
}
issues
}

issues_dependencies_graph <- function(meta) {
repo_packages <- meta$package
repo_dependencies <- meta[["_dependencies"]]
edge_list <- list()
for (index in seq_len(nrow(meta))) {
package <- .subset2(repo_packages, index)
all <- .subset2(repo_dependencies, index)
packages <- .subset2(all, "package")
role <- .subset2(all, "role")
strong <- packages[role %in% c("Depends", "Imports", "LinkingTo")]
strong <- intersect(setdiff(unique(strong), "R"), repo_packages)
edges <- rep(strong, each = 2L)
if (length(edges) >= 2L) {
edges[seq(from = 2L, to = length(edges), by = 2L)] <- package
edge_list[[index]] <- edges
}
}
igraph::graph(edges = unlist(edge_list))
}
1 change: 1 addition & 0 deletions R/package.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#' @importFrom gh gh
#' @importFrom igraph graph neighbors subcomponent V
#' @importFrom jsonlite parse_json read_json stream_in write_json
#' @importFrom nanonext ncurl parse_url status_code
#' @importFrom pkgsearch cran_package
Expand Down
28 changes: 20 additions & 8 deletions R/record_issues.R
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,15 @@ record_issues <- function(
issues <- list() |>
add_issues(issues_checks(meta = checks), "checks") |>
add_issues(issues_descriptions(meta = packages), "descriptions") |>
add_issues(issues_versions(versions = versions), "versions") |>
overwrite_issues(output = output, today = today)
add_issues(issues_versions(versions = versions), "versions")
issues <- issues |>
add_issues(issues_dependencies(names(issues), packages), "dependencies")
overwrite_issues(
issues = issues,
output = output,
today = today,
packages = packages
)
invisible()
}

Expand All @@ -78,15 +85,15 @@ add_issues <- function(total, subset, category) {
total
}

overwrite_issues <- function(issues, output, today) {
packages <- list.files(output)
overwrite_issues <- function(issues, output, today, packages) {
with_issues <- list.files(output)
dates <- lapply(
X = packages,
X = with_issues,
FUN = function(path) {
jsonlite::read_json(file.path(output, path), simplifyVector = TRUE)$date
}
)
names(dates) <- packages
names(dates) <- with_issues
unlink(output, recursive = TRUE)
dir.create(output)
lapply(
Expand All @@ -95,7 +102,8 @@ overwrite_issues <- function(issues, output, today) {
issues = issues,
output = output,
today = today,
dates = dates
dates = dates,
packages = packages
)
}

Expand All @@ -104,10 +112,14 @@ overwrite_package_issues <- function(
issues,
output,
today,
dates
dates,
packages
) {
path <- file.path(output, package)
issues[[package]]$date <- dates[[package]] %||% today
index <- packages$package == package
issues[[package]]$version <- packages[["version"]][index]
issues[[package]]$remote_hash <- packages[["remotesha"]][index]
jsonlite::write_json(
x = issues[[package]],
path = file.path(output, package),
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ reference:
- title: Check issues
contents:
- issues_checks
- issues_dependencies
- issues_descriptions
- issues_versions
- title: Record issues
Expand Down
1 change: 1 addition & 0 deletions man/issues_checks.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions man/issues_dependencies.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/issues_descriptions.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/issues_versions.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading