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

Record commit at package installation #3390

Merged
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()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually it would be preferable to set this at build time, so that someone could download a prebuilt package binary and still have it provide this information, but the getenv is not evaluated until the package is byte-compiled (which happens during installation).

(Scripts in data/ are evaluated at build rather than install, so I considered treating this information as an exported "dataset" rather than an internal variable. I decided against it because that would mean the hash was exported and hence visible to confuse users, but I'd be open to revisiting the idea if other folks think it'd be useful)

# 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
Loading