Skip to content

Commit

Permalink
Merge pull request #3390 from infotroph/memoize-commit-at-build-time
Browse files Browse the repository at this point in the history
Record commit at package installation
  • Loading branch information
infotroph authored Nov 2, 2024
2 parents 5970ffd + edd24b0 commit 4faeac6
Show file tree
Hide file tree
Showing 50 changed files with 261 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For more information about this file see also [Keep a Changelog](http://keepacha
* Model packages `PEcAn.BASGRA`, `PEcAn.CLM45`, `PEcAn.DALEC`, `PEcAn.dvmdostem`, `PEcAn.FATES`, `PEcAn.GDAY`, `PEcAn.JULES`, `PEcAn.LDNDC`, `PEcAn.LINKAGES`, `PEcAn.LPJGUESS`, `PEcAn.MAAT`, `PEcAn.MAESPA`, `PEcAn.PRELES`, `PEcAn.SIBCASA`, `PEcAn.SIPNET`, `PEcAn.STICS`, and the new model package template.
* Modules `PEcAn.allometry`, `PEcAn.assim.batch`, `PEcAn.data.mining`, `PEcAn.emulator`, `PEcAn.MA`, `PEcAn.photosynthesis`, `PEcAn.priors`, and `PEcAn.RTM`.
- Renamed master branch to main
- `PEcAn.all::pecan_version()` now reports commit hashes as well as version numbers for each installed package.

### Removed

Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,19 @@ drop_parents = $(filter-out $(patsubst %/,%,$(dir $1)), $1)
# Generates a list of regular files at any depth inside its argument
files_in_dir = $(call drop_parents, $(call recurse_dir, $1))

# Git hash + clean status for this directory
git_rev = $(shell \
CLEAN=$$([[ -n $$(git status -s $1) ]] && echo "+mod"); \
echo $$(git rev-parse --short=10 HEAD)"$$CLEAN")

# HACK: NA vs TRUE switch on dependencies argument is an ugly workaround for
# a circular dependency between benchmark and data.land.
# When this is fixed, can go back to simple `dependencies = TRUE`
depends_R_pkg = ./scripts/time.sh "depends ${1}" ./scripts/confirm_deps.R ${1} \
$(if $(findstring modules/benchmark,$(1)),NA,TRUE)
install_R_pkg = ./scripts/time.sh "install ${1}" Rscript \
-e ${SETROPTIONS} \
-e "Sys.setenv(PECAN_GIT_REV='$(call git_rev,$1)')" \
-e "remotes::install_local('$(strip $(1))', force=TRUE, dependencies=FALSE, upgrade=FALSE)"
check_R_pkg = ./scripts/time.sh "check ${1}" Rscript scripts/check_with_errors.R $(strip $(1))
test_R_pkg = ./scripts/time.sh "test ${1}" Rscript \
Expand Down
1 change: 1 addition & 0 deletions base/all/NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Generated by roxygen2: do not edit by hand

S3method(print,pecan_version_report)
export(pecan_version)
4 changes: 4 additions & 0 deletions base/all/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
## License change
* PEcAn.all is now distributed under the BSD three-clause license instead of the NCSA Open Source license.

## Changed
* `pecan_version()` now reports the Git revision (if known) for each package,
and prints its results more compactly for easier reading.

# PEcAn.all 1.8.0

## Added
Expand Down
61 changes: 55 additions & 6 deletions base/all/R/pecan_version.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
#' @param exact Show only tags that exactly match `version`,
#' or all tags that have it as a substring?
#' @return data frame with columns for package name, expected version(s),
#' and installed version.
#' If the `sessioninfo` package is installed, a fourth column reports where
#' each package was installed from: local, github, CRAN, etc.
#' installed version, and Git hash (if known).
#' If the `sessioninfo` package is installed, an additional column reports
#' where each package was installed from: local, github, CRAN, etc.
#'
#' @examples
#' pecan_version()
Expand All @@ -46,7 +46,7 @@ pecan_version <- function(version = max(PEcAn.all::pecan_releases$version),
)
version <- unique(unlist(version))
}
cols_to_return <- c("package", version, "installed")
cols_to_return <- c("package", version, "installed", "build_hash")


if (requireNamespace("sessioninfo", quietly = TRUE)) {
Expand Down Expand Up @@ -94,14 +94,20 @@ pecan_version <- function(version = max(PEcAn.all::pecan_releases$version),
our_pkgs <- our_pkgs[!duplicated(our_pkgs),]
}

want_hash <- !is.na(our_pkgs$installed)
our_pkgs$build_hash[want_hash] <- sapply(
our_pkgs$package[want_hash],
get_buildhash)

res <- merge(
x = our_pkgs,
y = PEcAn.all::pecan_version_history,
all = TRUE)
res <- res[, cols_to_return]
res <- drop_na_version_rows(res[, cols_to_return])
rownames(res) <- res$package
class(res) <- c("pecan_version_report", class(res))

drop_na_version_rows(res)
res
}

# Remove rows where all versions are missing
Expand All @@ -110,3 +116,46 @@ drop_na_version_rows <- function(df) {
stopifnot(colnames(df)[[1]] == "package")
df[rowSums(is.na(df[, -1])) < ncol(df[, -1]), ]
}


# Look up git revision, if recorded, from an installed PEcAn package
get_buildhash <- function(pkg) {
# Set if pkg was installed from r-universe or via install_github()
desc_sha <- utils::packageDescription(pkg, fields = "RemoteSha")
if (!is.na(desc_sha)) {
return(substr(desc_sha, 1, 10))
}
# Set if PECAN_GIT_REV was set during install (includes `make install`)
get0(".build_hash", envir = asNamespace(pkg), ifnotfound = NA_character_)
}


# print method for version
# (Just to help it display more compactly)
#' @export
print.pecan_version_report <- function(x, ...) {

dots <- list(...)
if (is.null(dots$row.names)) { dots$row.names <- FALSE }
if (is.null(dots$right)) { dots$right <- FALSE }

xx <- as.data.frame(x)
# only print hash for dev versions
# (typically x.y.z.9000, but we'll use anything with a 4th version component)
skip_hash <- is.na(xx$installed[,4]) | is.na(xx$build_hash)
xx$build_hash[skip_hash] <- ""
xx$build_hash <- sub(".{4}\\+mod$", "+mod", xx$build_hash)
xx$installed <- paste0(
xx$installed,
sub("(.+)", " (\\1)", xx$build_hash))
xx$build_hash <- NULL
if (!is.null(xx$source)) {
xx$source <- paste0(
strtrim(xx$source, 17),
ifelse(nchar(xx$source, type="width") <= 17, "", "..."))
}
dots$x <- xx
do.call("print", dots)

invisible(x)
}
3 changes: 3 additions & 0 deletions base/all/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
52 changes: 28 additions & 24 deletions base/all/data/pecan_version_history.R
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@

# Read and format a list of pecan versions

pecan_version_history <- utils::read.csv(
"pecan_version_history.csv",
colClasses = "character",
check.names = FALSE)
# The local() wrapper is to avoid adding objects to the package data:
# Any extra vars defined at the top level of this file would be loaded
# into the global environment by `data("pecan_version_history")`

# We'd like to parse strictly to catch invalid versions (probably typos).
# But we _need_ to allow NAs... and in R < 4.4, package_version did not
# accept NAs unless strict=FALSE.
strict <- TRUE
na_version <- try(
package_version(NA_character_, strict = strict),
silent = TRUE)
if (inherits(na_version, "try-error")) {
strict <- FALSE
}
pecan_version_history <- local({
pvh <- utils::read.csv(
"pecan_version_history.csv",
colClasses = "character",
check.names = FALSE)

for (col in colnames(pecan_version_history)) {
if (col != "package") {
pecan_version_history[[col]] <- package_version(
pecan_version_history[[col]],
strict = strict)
# We'd like to parse strictly to catch invalid versions (probably typos).
# But we _need_ to allow NAs... and in R < 4.4, package_version did not
# accept NAs unless strict=FALSE.
strict <- TRUE
na_version <- try(
package_version(NA_character_, strict = strict),
silent = TRUE)
if (inherits(na_version, "try-error")) {
strict <- FALSE
}
}

# Now remove local vars
# Yes, this really is needed: _all_ objects left defined at end of script
# will be added to the package data list!
rm(strict, na_version, col)
for (col in colnames(pvh)) {
if (col != "package") {
pvh[[col]] <- package_version(
pvh[[col]],
strict = strict)
}
}

pvh
})
6 changes: 3 additions & 3 deletions base/all/man/pecan_version.Rd

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

46 changes: 40 additions & 6 deletions base/all/tests/testthat/test-pecan_version.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ test_that("pecan_version", {
# tags substring matched only when exact = FALSE
expect_named(
pecan_version("v1.5"),
c("package", paste0("v1.5.", 0:3), "installed", "source")
c("package", paste0("v1.5.", 0:3), "installed", "build_hash", "source")
)
expect_error(
pecan_version("v1.5", exact = TRUE),
"undefined columns"
)
expect_named(
pecan_version("v1.3", exact = TRUE),
c("package", "v1.3", "installed", "source")
c("package", "v1.3", "installed", "build_hash", "source")
)

# returns current release if no args given
noargs <- pecan_version()
expected_tag <- tail(PEcAn.all::pecan_releases, 1)$tag
expect_length(noargs, 4)
expect_named(noargs, c("package", expected_tag, "installed", "source"))
expect_length(noargs, 5)
expect_named(noargs, c("package", expected_tag, "installed", "build_hash", "source"))

# Why the `any()`s below?
# Because R CMD check runs tests with local test dir added to .libPaths,
Expand Down Expand Up @@ -77,8 +77,8 @@ test_that("pecan_version without sessioninfo", {
mockery::stub(pecan_version, 'requireNamespace', FALSE)
without_sessinfo <- pecan_version()

expect_length(with_sessinfo, 4)
expect_length(without_sessinfo, 3)
expect_length(with_sessinfo, 5)
expect_length(without_sessinfo, 4)
expect_equal(
with_sessinfo[, colnames(with_sessinfo) != "source"],
without_sessinfo)
Expand All @@ -91,3 +91,37 @@ test_that("pecan_version without sessioninfo", {
# The approach that failed just before I wrote this note:
# No, the version of PEcAn.all (1.8.1.9000 today) is not reliably in sync with
# the PEcAn version last tagged as a release (1.7.2 today).


test_that("printing", {
ver <- structure(
data.frame(
package = "PEcAnFake",
v0.0 = package_version("1.2.3"),
installed = package_version("1.2.3.9000"),
build_hash = "01234567ab",
source = "13 characters"),
class = c("pecan_version_report", "data.frame")
)

long_ver <- ver
long_ver$build_hash = "01234567ab+mod"
long_ver$source = "twenty-two characters"

# hash truncated to fit "+mod" if present
expect_output(print(ver), "01234567ab", fixed = TRUE)
expect_output(print(long_ver), "012345+mod", fixed = TRUE)

# source truncated to total of 20 chars
expect_output(print(ver), "13 characters$")
expect_output(print(long_ver), "twenty-two charac...", fixed = TRUE)

# source truncation works on width not glyph count
long_ver$source <- gsub("tw", "\U{1F197}\U{1F192}", long_ver$source)
expect_output(print(long_ver), "\U{1F192}o ch...", fixed = TRUE)

# dots passed on
expect_output(print(ver), "\n PEcAnFake")
expect_output(print(ver, row.names = TRUE), "\n1 PEcAnFake", fixed = TRUE)
expect_output(print(ver, quote = TRUE), "\n \"PEcAnFake\"", fixed = TRUE)
})
3 changes: 3 additions & 0 deletions base/db/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/logger/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/qaqc/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/remote/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/settings/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/utils/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/visualization/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions base/workflow/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/basgra/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/biocro/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/clm45/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/dalec/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/dvmdostem/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/ed/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/fates/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/gday/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/jules/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/ldndc/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/linkages/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/lpjguess/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/maat/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/maespa/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/preles/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/sibcasa/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/sipnet/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/stics/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions models/template/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
3 changes: 3 additions & 0 deletions modules/allometry/R/version.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set at package install time, used by pecan.all::pecan_version()
# to identify development versions of packages
.build_hash <- Sys.getenv("PECAN_GIT_REV", "unknown")
Loading

0 comments on commit 4faeac6

Please sign in to comment.