conflicting libraries - Print R package startup message AFTER automatic package conflict messages instead of before - Stack Over

My R package ale implements a get() method. Normally, this would conflict with the base R base::get() f

My R package ale implements a get() method. Normally, this would conflict with the base R base::get() function, but I implement it such that it calls my package's custom get() methods when the user passes one of my package's objects but for everything else, it transparently hands over to base::get() so that there is no problem. So far, so good.

However, when a package is attached with library() R automatically prints a conflict message to alert users to the potential problem:

# To reproduce this issue, you need to install the current 
# development version of the package at this specific commit:
# # install.packages('pak')
# pak::pak('tripartio/ale@e0ff702')
# 
library(ale)
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get

Created on 2025-03-03 with reprex v2.1.1

This is fine and responsible on R's part (my question is NOT about trying to suppress this message--I leave that to the user's choice). However, I have added a clarifying package startup message to reassure users that there is no problem. I have the following .onAttach() function in my aaa.R file:

# My actual .onAttach() is more complicated, but I simplify here
# to focus on the key issues
.onAttach <- function(libname, pkgname) {
  packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}

The result when users load the package now is:

# Note: you must first restart R before each time you run library(ale) to see what any changes produce
library(ale)
#> The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get

Created on 2025-03-03 with reprex v2.1.1

This is what I want, except I want to print my custom message AFTER R's conflict message, not before. I've tried many things but I have not been able to successfully flip the order. For example, here are some things I've tried that have NOT worked:

# Delay the message using setHook()
.onAttach <- function(libname, pkgname) {
  setHook(
    packageEvent(pkgname, "attach"),
    function(...) {
      packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
    }
  )
}

# Use defer() from {withr}
.onAttach <- function(libname, pkgname) {
  withr::defer({
    packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
  }, envir = parent.env(environment()))
}

# Sys.sleep() before my custom message:
# 5 seconds delay but same order
.onAttach <- function(libname, pkgname) {
  Sys.sleep(5)
  packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}

# .onLoad() instead of .onAttach():
# No change--same order
.onLoad <- function(libname, pkgname) {
  setHook(
    packageEvent(pkgname, "attach"),
    function(...) {
      packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
    }
  )
}

Here is the output that I want; I appreciate any help in getting me there:

# Note: you must first restart R before each time you run library(ale) to see what any changes produce
library(ale)
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get
#>
#> The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.

My R package ale implements a get() method. Normally, this would conflict with the base R base::get() function, but I implement it such that it calls my package's custom get() methods when the user passes one of my package's objects but for everything else, it transparently hands over to base::get() so that there is no problem. So far, so good.

However, when a package is attached with library() R automatically prints a conflict message to alert users to the potential problem:

# To reproduce this issue, you need to install the current 
# development version of the package at this specific commit:
# # install.packages('pak')
# pak::pak('tripartio/ale@e0ff702')
# 
library(ale)
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get

Created on 2025-03-03 with reprex v2.1.1

This is fine and responsible on R's part (my question is NOT about trying to suppress this message--I leave that to the user's choice). However, I have added a clarifying package startup message to reassure users that there is no problem. I have the following .onAttach() function in my aaa.R file:

# My actual .onAttach() is more complicated, but I simplify here
# to focus on the key issues
.onAttach <- function(libname, pkgname) {
  packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}

The result when users load the package now is:

# Note: you must first restart R before each time you run library(ale) to see what any changes produce
library(ale)
#> The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get

Created on 2025-03-03 with reprex v2.1.1

This is what I want, except I want to print my custom message AFTER R's conflict message, not before. I've tried many things but I have not been able to successfully flip the order. For example, here are some things I've tried that have NOT worked:

# Delay the message using setHook()
.onAttach <- function(libname, pkgname) {
  setHook(
    packageEvent(pkgname, "attach"),
    function(...) {
      packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
    }
  )
}

# Use defer() from {withr}
.onAttach <- function(libname, pkgname) {
  withr::defer({
    packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
  }, envir = parent.env(environment()))
}

# Sys.sleep() before my custom message:
# 5 seconds delay but same order
.onAttach <- function(libname, pkgname) {
  Sys.sleep(5)
  packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
}

# .onLoad() instead of .onAttach():
# No change--same order
.onLoad <- function(libname, pkgname) {
  setHook(
    packageEvent(pkgname, "attach"),
    function(...) {
      packageStartupMessage("The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")
    }
  )
}

Here is the output that I want; I appreciate any help in getting me there:

# Note: you must first restart R before each time you run library(ale) to see what any changes produce
library(ale)
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get
#>
#> The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.

Share Improve this question edited Mar 3 at 10:19 Tripartio asked Mar 3 at 9:48 TripartioTripartio 2,3012 gold badges27 silver badges33 bronze badges 1
  • 1 Hey who deleted all the comments ? – Carl Witthoft Commented Mar 4 at 15:31
Add a comment  | 

2 Answers 2

Reset to default 2

You could try to take a creative approach by using R's task callback system to print the packageStartupMessage after .onAttach ran. I am writing this answer because @Tripartio asked me to do so in the comments and this turned out to be working.

addTaskCallback registers an R function that is to be called each time a top-level task is completed.

If the (return)value is FALSE, the callback is removed from the task list and will not be called again by this mechanism

.onAttach <- function(libname, pkgname) {
  # Schedule the message to appear after the current task is complete
  invisible(
    addTaskCallback(function(expr, value, ok, visible) {
      packageStartupMessage("\nNote: The 'get()' function for 'ale' works such that the masked base::get() still works fine without modification.")     
      return(FALSE) # to remove this callback after it runs once
    })
  )
}

DISCLAIMER:

I found out, that all startup messages need to be suppressable to fit CRAN guidelines. I do not know, if this solution fits these guidelines, but since this also outputs a type packageStartupMessage you should be fine. Source

Here's an enhancement to @TimG's great solution that correctly lets startup messages be suppressible:

.onAttach <- function(libname, pkgname) {
  suppress_pkg_msg<- sys.calls() |>
    as.character() |>
    stringr::str_detect('suppressPackageStartupMessages') |>
    any()

  if (!suppress_pkg_msg) {
    # Post custom startup messages AFTER default package conflict messages.
    # https://stackoverflow/a/79480949/2449926
    invisible(
      addTaskCallback(function(expr, value, ok, visible) {
        packageStartupMessage(
          "The 'get()' function for 'ale' works such that the masked base::get() still works fine\nwithout modification."
        )
        return(FALSE)
      })
    )
  }
}

And here's the output with that enhancement.

library(ale)
#> 
#> Attaching package: 'ale'
#> The following object is masked from 'package:base':
#> 
#>     get

detach("package:ale", unload = TRUE)

suppressPackageStartupMessages(library(ale))
#>
# nothing: all messages correctly suppressed

Created on 2025-03-03 with reprex v2.1.1

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745100320a4611232.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信