{"id":16703825,"url":"https://github.com/leeper/depends","last_synced_at":"2025-07-11T07:34:45.655Z","repository":{"id":70769467,"uuid":"136150079","full_name":"leeper/Depends","owner":"leeper","description":"Quick demo of the risks of using 'Depends' in R packages","archived":false,"fork":false,"pushed_at":"2018-06-07T08:10:33.000Z","size":17,"stargazers_count":14,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T05:07:02.742Z","etag":null,"topics":["r","r-packages"],"latest_commit_sha":null,"homepage":null,"language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/leeper.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-05T09:03:01.000Z","updated_at":"2025-03-22T11:06:10.000Z","dependencies_parsed_at":"2023-03-29T18:17:40.290Z","dependency_job_id":null,"html_url":"https://github.com/leeper/Depends","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/leeper/Depends","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeper%2FDepends","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeper%2FDepends/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeper%2FDepends/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeper%2FDepends/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/leeper","download_url":"https://codeload.github.com/leeper/Depends/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/leeper%2FDepends/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264756337,"owners_count":23659300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["r","r-packages"],"created_at":"2024-10-12T19:09:51.259Z","updated_at":"2025-07-11T07:34:45.512Z","avatar_url":"https://github.com/leeper.png","language":"R","funding_links":[],"categories":[],"sub_categories":[],"readme":"This is a demo of the potential dangers of `Depends`. As [Hadley said on Twitter](https://twitter.com/hadleywickham/status/1003986395344470016), \" it takes a while to fully grasp that DESCRIPTION primary influences the installation of your package, not is behaviour at run time. Depends is the unfortunate field that affects both.\" This demo shows precisely what that means. It contains four packages:\n\n - `dependsdplyr` Depends on dplyr and has no code.\n - `dependsMASS` Depends on MASS and has no code.\n - `dependsdplyr2` Depends on dplyr also, and uses the `select()` function without importing it or the dplyr namespace. Instead, it only `Depends` on dplyr.\n - `importsdplyr` Depends on MASS but imports the `dplyr::select()` function for the sample function as in dependsdplyr2.\n\nThis will highlight an important distinction between Depends - which *loads* a package namespace and *attaches* that namespace to the `search()` path - and Imports - which only loads the namespace.\n\n\nHere's some setup using the installed packages:\n\n```{r, results=\"hide\"}\ndevtools::install(\"dependsdplyr\", quick = TRUE, quiet = TRUE)\ndevtools::install(\"dependsdplyr2\", quick = TRUE, quiet = TRUE)\ndevtools::install(\"dependsMASS\", quick = TRUE, quiet = TRUE)\ndevtools::install(\"importsdplyr\", quick = TRUE, quiet = TRUE)\n```\n\nAnd here's a demo of the usual package load order problem with which we are all familiar:\n\n```{r}\nlibrary(\"dependsdplyr\")\nhead(select(mtcars, \"cyl\")) # works\n\nlibrary(\"dependsMASS\")\nhead(select(mtcars, \"cyl\")) # fails\n\n# remove packages from search() path\ndetach(\"package:dependsdplyr\")\ndetach(\"package:dplyr\")\ndetach(\"package:dependsMASS\")\ndetach(\"package:MASS\")\n```\n\nThat shows that using Depends affects top-level code (i.e., user-created code in the R console). Not super surprising.\n\nBut here's the weird and possibly unexpected problem:\n\n```{r}\nlibrary(\"dependsdplyr2\")\n\"package:dplyr\" %in% search() # TRUE\n\"package:MASS\" %in% search() # FALSE\n\n# Here's a simple function for 'dependsdplyr2':\n# dependsdplyr2::choose_cols() \nchoose_cols\n\n# dependsdplyr2 function works as expected\nhead(choose_cols(mtcars, \"cyl\"))\n\n# now load MASS\nlibrary(\"MASS\")\n\"package:dplyr\" %in% search() # TRUE\n\"package:MASS\" %in% search() # TRUE\n\n# dependsdplyr2 function errors\nhead(choose_cols(mtcars, \"cyl\"))\n```\n\nAnd the same error occurs if I attach MASS *indirectly* by loading a package that depends on it:\n\n```{r}\ndetach(\"package:MASS\")\n\n# dependsdplyr2 function works as expected, again\nhead(choose_cols(mtcars, \"cyl\"))\n\n# now load dependsMASS\nlibrary(\"dependsMASS\")\n\"package:dplyr\" %in% search() # TRUE\n\"package:MASS\" %in% search() # TRUE\n\n# dependsdplyr2 function errors, again, even though I didn't explicitly attach MASS\nhead(choose_cols(mtcars, \"cyl\"))\n```\n\nThis fails even though it's package code. Why? Because dependsdplyr2 is counting on dplyr being not only attached but also attached after any other package that might create namespace conflicts.\n\n```{r}\n# cleanup\ndetach(\"package:dependsMASS\")\ndetach(\"package:MASS\")\ndetach(\"package:dependsdplyr2\")\ndetach(\"package:dplyr\")\n```\n\nTwo further examples might be useful. One is where we Depend on MASS but actually Import from dplyr:\n\n```{r}\nlibrary(\"importsdplyr\")\n\"package:dplyr\" %in% search() # FALSE\n\"package:MASS\" %in% search() # TRUE\n\n# imports dplyr function works as expected\nhead(choose_cols(mtcars, \"cyl\"))\n```\n\nThis highlights that the Depends on MASS is pointless. It's not imported from in NAMESPACE or via `::` so we don't actually need it in importsdplyr and our direct Import from dplyr prevents the attach-order problems of above.\n\nAnother example uses Hadley's [conflicted](https://github.com/r-lib/conflicted) package to issue errors on namespace conflicts:\n\n```{r}\n# cleanup last example\ndetach(\"package:importsdplyr\")\nunloadNamespace(\"importsdplyr\")\nunloadNamespace(\"dplyr\")\nunloadNamespace(\"MASS\")\n\n# load conflicted\nlibrary(\"conflicted\")\n\n# try to use importsdplyr again\nlibrary(\"importsdplyr\")\nhead(choose_cols(mtcars, \"cyl\")) # works\n\n# and again after loading dplyr and MASS\nlibrary(\"dplyr\")\nlibrary(\"MASS\")\nhead(choose_cols(mtcars, \"cyl\")) # works\n\n# but we get the error from conflicted if we use `select()` at the top-level\nselect(mtcars, cyl)\n```\n\nThe lessons learned:\n\n - Always use a NAMESPACE to specify imports so that your package code isn't harmed by other peoples' use of Depends. (If you don't do this, you'll get a `NOTE` on `R CMD check` but you may not be doing that.)\n - Use Imports to specify any package that must be installed and *loaded* for your package to work. Using Depends may not affect your package code if you're following good practice, but it can affect the user's.\n - Always use a fully qualified reference - `pkg::func()` - when there might be some namespace ambiguity, such as at the top-level or when your package imports from two namespaces that conflict (like MASS and dplyr)\n - Don't use Depends in your packages.\n\nThere are exceptions to the final rule (such as needing the methods package), but you never know what your use of Depends might do to someone else's top-level or package code. As always [*Writing R Extensions*](https://cran.r-project.org/doc/manuals/r-devel/R-exts.html#Package-Dependencies) is the best reference for appropriate specification of the DESCRIPTION file. \n\nThe main counter-argument I've heard to this is that a developer may have a package that is fairly useless without its strong dependency (e.g., a graphics package building on ggplot2). In these cases, I also think Depends is a bad idea (for all the above reasons) but also because it makes assumptions about the end-user (either that they don't know the dependency or that it's preferable for them to have the package attached.) Smart people disagree here, but my view is that we should try to make as few assumptions as possible about the end-user. Putting the dependency in Imports ensures that the package is installed and its namespace is available. \n\nI don't think we should further assume that the user wants to be able to use `func()` without having to `library(\"dependency\")` or `dependency::func()`. WRE says graphics extensions are a possible extension but I think it's safer to let users decide whether and when to attach rather than load dependencies. For example, in my own scripts, I generally use `requireNamespace()` and fully qualified references and wouldn't want packages I'm using to attach anything. Lest weird stuff happens:\n\n\n```{r}\n# really cleanup last example\ndetach(\"package:importsdplyr\")\nunloadNamespace(\"importsdplyr\")\ndetach(\"package:dplyr\")\nunloadNamespace(\"dplyr\")\ndetach(\"package:MASS\")\nunloadNamespace(\"MASS\")\nunloadNamespace(\"conflicted\")\n\n# try something from MASS\narea(sin, 0, pi) # fails\n\nlibrary(\"importsdplyr\")\nhead(choose_cols(mtcars, \"cyl\"))\n\n# try something from MASS, again\narea(sin, 0, pi) # works\n```\n\nEven though we didn't explicitly attach MASS, code from it now works. What changed? We could debug by actively checking `search()` but it's not obvious from the code alone. Again, opinions will differ on whether that's desirable or undesirable in any particular application but to me it feels wrong, since it then affects top-level code in non-obvious ways.\n\n\n---\n\nSome cleanup:\n\n```{r}\n# remove packages\nunloadNamespace(\"dependsdplyr\")\nunloadNamespace(\"dependsdplyr2\")\nunloadNamespace(\"dependsMASS\")\nunloadNamespace(\"importsdplyr\")\nremove.packages(c(\"dependsdplyr\", \"dependsdplyr2\", \"dependsMASS\", \"importsdplyr\"))\n\n# session info\nsessionInfo()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleeper%2Fdepends","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fleeper%2Fdepends","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fleeper%2Fdepends/lists"}