diff --git a/.Rbuildignore b/.Rbuildignore index c5928ef14..3680560b4 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -18,3 +18,4 @@ ^\.github$ ^CRAN-RELEASE$ ^data-raw$ +^minver$ diff --git a/.travis.yml b/.travis.yml index a6d18acb0..f78fed507 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,10 +48,13 @@ jobs: - r: release env: - BUILD_PKGDOWN=true - - r: 3.5 - - r: 3.4 - - r: 3.3 - r: 3.2 + # old fansi needs R 3.3 + - r: 3.3 + env: + - MIN_VERSIONS=TRUE + - r: 3.4 + - r: 3.5 fast_finish: true #env diff --git a/DESCRIPTION b/DESCRIPTION index 936abf317..bc6c07709 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,18 +14,17 @@ LazyData: true URL: https://github.com/r-lib/pillar BugReports: https://github.com/r-lib/pillar/issues Imports: - cli (>= 1.1.0), + cli, crayon (>= 1.3.4), - fansi (>= 0.4.0), - methods, - rlang (>= 0.3.4), - utf8 (>= 1.1.4), - vctrs (>= 0.1.0) + fansi, + rlang (>= 0.3.0), + utf8 (>= 1.1.0), + vctrs Suggests: - knitr (>= 1.22), - lubridate (>= 1.7.4), - testthat (>= 2.1.1), - withr (>= 2.1.2) + knitr, + lubridate, + testthat, + withr Roxygen: list(markdown = TRUE) RoxygenNote: 6.1.1 Collate: diff --git a/minver/.gitignore b/minver/.gitignore new file mode 100644 index 000000000..ea92e9fc9 --- /dev/null +++ b/minver/.gitignore @@ -0,0 +1,2 @@ +*.rds +lib/ diff --git a/minver/01-gen.R b/minver/01-gen.R new file mode 100644 index 000000000..5a4216c83 --- /dev/null +++ b/minver/01-gen.R @@ -0,0 +1,20 @@ +library(tidyverse) + +# pkgdep <- pkgdep::as_pkgdep(available.packages()) +# gen <- pkgdep::get_all_deps("tibble", pkgdep) + +my_package <- basename(getwd()) + +db <- cranly::clean_CRAN_db() +network <- cranly::build_network(db) +tree <- cranly::compute_dependence_tree(network, my_package) + +deps <- desc::desc_get_deps() + +tree %>% + arrange(generation) %>% + distinct(package, .keep_all = TRUE) %>% + arrange(-generation) %>% + inner_join(deps, by = "package") %>% + filter(version != "*") %>% + saveRDS("minver/gen.rds") diff --git a/minver/02-baseline.R b/minver/02-baseline.R new file mode 100644 index 000000000..c484a6e7c --- /dev/null +++ b/minver/02-baseline.R @@ -0,0 +1,18 @@ +library(tidyverse) + +gen <- + readRDS("minver/gen.rds") %>% + as_tibble() + +gen_crandb <- + gen %>% + select(-type, -version) %>% + mutate(crandb = map(package, ~ crandb::package(., version = "all"))) + +baseline <- + gen_crandb %>% + mutate(version = map(crandb, ~ names(pluck(., "versions")))) %>% + mutate(first_version = map_chr(version, 1)) %>% + select(-crandb) + +saveRDS(baseline, "minver/baseline.rds") diff --git a/minver/03-candidate.R b/minver/03-candidate.R new file mode 100644 index 000000000..8c711c0fe --- /dev/null +++ b/minver/03-candidate.R @@ -0,0 +1,9 @@ +library(tidyverse) + +baseline <- readRDS("minver/baseline.rds") + +baseline %>% + select(package = 1) %>% + pwalk(desc::desc_set_dep) + +saveRDS(baseline, "minver/candidate-0001.rds") diff --git a/minver/04-lib.R b/minver/04-lib.R new file mode 100644 index 000000000..a8774351f --- /dev/null +++ b/minver/04-lib.R @@ -0,0 +1,9 @@ +library(tidyverse) + +lib <- "minver/lib" +unlink(lib, recursive = TRUE, force = TRUE) +dir.create(lib, showWarnings = FALSE) + +invisible(remotes::install_deps) +withr::with_libpaths(lib, remotes::install_deps(dependencies = TRUE)) +withr::with_libpaths(lib, install.packages("memoise")) diff --git a/minver/05-next-candidate.R b/minver/05-next-candidate.R new file mode 100644 index 000000000..a6d14aa06 --- /dev/null +++ b/minver/05-next-candidate.R @@ -0,0 +1,90 @@ +library(tidyverse) + +bisect_first_min_version <- function(version) { + na_versions <- which(map_int(version, length) > 1) + if (length(na_versions) == 0) stop("All versions determined, move to next script.", call. = FALSE) + first_na_version <- na_versions[[1]] + + version_candidates <- version[[first_na_version]] + + version_idx <- quantile(seq_along(version_candidates), 0.5, type = 1) + + min_version <- rep(NA_character_, length(version)) + + pinned <- seq_len(first_na_version - 1) + min_version[pinned] <- unlist(version[pinned]) + min_version[[first_na_version]] <- version_candidates[[version_idx]] + min_version +} + +update_bisect_result <- function(version, next_min_version, failure) { + first_na_version <- which(map_int(version, length) > 1)[[1]] + + version_candidates <- package_version(version[[first_na_version]]) + pivot <- package_version(next_min_version[[first_na_version]]) + + if (failure) { + version_candidates <- version_candidates[ version_candidates > pivot ] + } else { + version_candidates <- version_candidates[ version_candidates <= pivot ] + } + + version[[first_na_version]] <- as.character(version_candidates) + version +} + +candidate_files <- dir("minver", pattern = "candidate-....[.]rds") + +candidate_file <- tail(candidate_files, 1)[[1]] + +candidate <- + readRDS(file.path("minver", candidate_file)) %>% + mutate(next_min_version = bisect_first_min_version(version)) %>% + mutate(dep = if_else(is.na(next_min_version), "*", paste0(">= ", next_min_version))) + +print(candidate) + +pinned <- + candidate %>% + filter(!is.na(next_min_version)) %>% + select(package = 1, version = next_min_version) %>% + arrange(-row_number()) + +print(pinned) + +libpath <- tempfile("lib") +dir.create(libpath) + +existing <- setdiff(dir("minver/lib"), pinned$package) + +stopifnot(all(file.symlink(normalizePath(file.path("minver/lib", existing)), libpath))) + +invisible(remotes::install_version) +invisible(rcmdcheck::rcmdcheck) + +Sys.setenv(NOT_CRAN = "true") + +result <- safely( + ~ withr::with_libpaths( + libpath, + { + pwalk(pinned, remotes::install_version, dependencies = character(), upgrade = "never") + rcmdcheck::rcmdcheck(error_on = "note") + } + ) +)() + +failure <- is.null(result$result) +failure + +n <- length(candidate_files) + 1 + +new_candidate <- + candidate %>% + mutate(version = update_bisect_result(version, next_min_version, failure)) %>% + select(-next_min_version, -dep) + +new_candidate %>% + saveRDS(sprintf("minver/candidate-%.4d.rds", n)) + +new_candidate diff --git a/minver/06-finalize.R b/minver/06-finalize.R new file mode 100644 index 000000000..d083922cd --- /dev/null +++ b/minver/06-finalize.R @@ -0,0 +1,13 @@ +library(tidyverse) + +candidate_files <- dir("minver", pattern = "candidate-....[.]rds") + +candidate_file <- tail(candidate_files, 1)[[1]] + +final <- + readRDS(file.path("minver", candidate_file)) %>% + unnest() %>% + mutate(cond = if_else(version == first_version, "*", paste0(">= ", version))) %>% + select(package, version = cond) + +pwalk(final, desc::desc_set_dep) diff --git a/minver/README.md b/minver/README.md new file mode 100644 index 000000000..f891ce83e --- /dev/null +++ b/minver/README.md @@ -0,0 +1,26 @@ +# minver + +A workflow for finding the minimum required versions of all packages. + +## How does it work? + +1. It creates a baseline library via `remotes::install_deps(dependencies = TRUE)` . + +2. Imported packages are sorted topologically, packages that depend on other packages come first. Versions are erased from the `DESCRIPTION` . + +3. For each imported package, a bisection along the available versions (obtained via `crandb::package(version = "all")`) is made. The baseline library is copied (using symlinks), and the packages for which minimum versions are to be tested are installed using `remotes::install_version()`. The test criterion is a successful `R CMD check` without NOTEs. + +4. The minimum version for each package is written to the `DESCRIPTION` . + +## Run it + +Copy the `minver` directory into the root of your package, call `usethis::use_build_ignore("minver")`. + +Run each script `??-*.R` in sequence, the `05-*.R` script must be run repeatedly until the error `"All versions determined, move to next script."` is raised. + +The following files are created: + +- `gen.rds`: Imported packages sorted by "generation" (topologically) +- `baseline.rds`: All CRAN versions of imported packages +- `lib/`: The baseline library +- `candidate-####.rds`: Intermediate results of the bisection diff --git a/tic.R b/tic.R index 1e575d9dc..04d31bacf 100644 --- a/tic.R +++ b/tic.R @@ -3,6 +3,34 @@ do_package_checks() if (ci_has_env("DEV_VERSIONS")) { get_stage("install") %>% add_step(step_install_github(c("r-lib/rlang", "r-lib/cli", "r-lib/crayon", "brodieG/fansi", "patperry/r-utf8", "r-lib/vctrs"))) +} else if (ci_has_env("MIN_VERSIONS")) { + # Make sure all other packages are installed when we downgrade + get_stage("before_script") %>% + add_code_step( + { + deps <- desc::desc_get_deps() + deps <- deps[deps$type == "Imports", ] + + # revdepcheck needs recent cli, installing in the same library + deps <- deps[deps$package != "cli", ] + + is_first <- (deps$version == "*") + all_first_packages <- lapply(deps$package[is_first], crandb::package, version = "all") + all_first_version_details <- lapply(all_first_packages, `[[`, "versions") + all_first_versions <- lapply(all_first_version_details, names) + first_versions <- vapply(all_first_versions, `[[`, 1, FUN.VALUE = character(1)) + + version <- rlang::rep_along(NA_character_, is_first) + version[is_first] <- first_versions + version[!is_first] <- gsub(">= ", "", deps$version[!is_first]) + + Map(remotes::install_version, deps$package, version) + }, + { + remotes::install_cran("desc") + remotes::install_github("r-hub/crandb") + } + ) } if (ci_has_env("BUILD_PKGDOWN") && ci_get_branch() == "master") {