1+ # ' Create a dockerfile from the current R session
2+ # '
3+ # ' @param base_image Base image to use (default: rocker/r-ver with current R version)
4+ # ' @param include_packages Include loaded packages (default: TRUE)
5+ # ' @param include_sysreqs Include system requirements for packages (default: TRUE)
6+ # ' @param package_manager Package manager to use (default: auto-detected)
7+ # ' @return A dockerfile object
8+ # ' @export
9+ dk_from_session <- function (base_image = NULL , include_packages = TRUE ,
10+ include_sysreqs = TRUE , package_manager = " auto" ) {
11+ # Get current R version
12+ r_version <- paste(R.version $ major , R.version $ minor , sep = " ." )
13+
14+ # Set default base image if not provided
15+ if (is.null(base_image )) {
16+ base_image <- paste0(" rocker/r-ver:" , r_version )
17+ }
18+
19+ # Create base dockerfile
20+ df <- dockerfile() | >
21+ dfi_from(base_image ) | >
22+ dfi_label(maintainer = Sys.getenv(" USER" , " unknown" ))
23+
24+ # Set package manager
25+ if (package_manager == " auto" ) {
26+ package_manager <- determine_package_manager(base_image )
27+ }
28+
29+ # Add loaded packages if requested
30+ if (include_packages ) {
31+ # Get loaded packages from the current session
32+ pkgs <- sort(setdiff(
33+ # Get names of all loaded packages
34+ loadedNamespaces(),
35+ # Exclude base R packages and dockitect
36+ c(" base" , " compiler" , " datasets" , " graphics" ,
37+ " grDevices" , " grid" , " methods" , " parallel" ,
38+ " splines" , " stats" , " stats4" , " tcltk" , " tools" ,
39+ " utils" , " dockitect" )
40+ ))
41+
42+ # Add system requirements if requested
43+ if (include_sysreqs && length(pkgs ) > 0 ) {
44+ df <- dk_add_sysreqs(df , pkgs , package_manager )
45+ }
46+
47+ # Add R package installation command
48+ if (length(pkgs ) > 0 ) {
49+ pkg_list <- paste0(shQuote(pkgs ), collapse = " , " )
50+ df <- dfi_run(df , paste0(" R -e \" install.packages(c(" , pkg_list , " ), repos='https://cloud.r-project.org/')\" " ))
51+ }
52+ }
53+
54+ df
55+ }
56+
57+ # ' Add system requirements for R packages to a dockerfile
58+ # '
59+ # ' @param dockerfile A dockerfile object
60+ # ' @param packages Character vector of package names
61+ # ' @param package_manager Package manager to use (default: auto-detected)
62+ # ' @return Updated dockerfile object
63+ # ' @export
64+ dk_add_sysreqs <- function (dockerfile , packages , package_manager = " auto" ) {
65+ check_dockerfile(dockerfile )
66+
67+ if (package_manager == " auto" ) {
68+ package_manager <- dockerfile $ metadata $ package_manager
69+ if (is.null(package_manager )) {
70+ package_manager <- " apt"
71+ cli :: cli_warn(" Could not determine package manager. Defaulting to apt." )
72+ }
73+ }
74+
75+ # Get system requirements using pak
76+ if (! requireNamespace(" pak" , quietly = TRUE )) {
77+ cli :: cli_warn(" Package 'pak' is required to determine system requirements. Skipping." )
78+ return (dockerfile )
79+ }
80+
81+ # Map package manager to appropriate sysreqs platform
82+ os <- determine_os(dockerfile $ metadata $ base_image )
83+ platform <- map_to_sysreqs_platform(package_manager , os )
84+
85+ # Get system requirements data frame
86+ sysreqs_df <- pak :: pkg_sysreqs(packages , sysreqs_platform = platform )
87+
88+ if (is.null(sysreqs_df $ packages ) || nrow(sysreqs_df $ packages ) == 0 ) {
89+ return (dockerfile )
90+ }
91+
92+ # Extract system packages from the data frame and remove duplicates
93+ system_packages <- unique(unlist(sysreqs_df $ packages $ system_packages ))
94+
95+ if (length(system_packages ) == 0 ) {
96+ return (dockerfile )
97+ }
98+
99+ # Generate system-specific install commands using utility function
100+ install_cmd <- generate_pkg_install_cmd(package_manager , system_packages )
101+
102+ # Add the installation command
103+ dockerfile <- dfi_run(dockerfile , paste(install_cmd , collapse = " && \\\n " ))
104+
105+ dockerfile
106+ }
107+
108+ # ' Create a dockerfile from an renv.lock file
109+ # '
110+ # ' @param lock_file Path to renv.lock file
111+ # ' @param r_version R version to use (default: from lock file)
112+ # ' @param base_image Base image to use (default: determined from R version)
113+ # ' @param include_sysreqs Include system requirements (default: TRUE)
114+ # ' @return A dockerfile object
115+ # ' @export
116+ dk_from_renv <- function (lock_file = " renv.lock" , r_version = NULL ,
117+ base_image = NULL , include_sysreqs = TRUE ) {
118+ if (! file.exists(lock_file )) {
119+ cli :: cli_abort(" Lock file not found: {lock_file}" )
120+ }
121+
122+ # Read lock file
123+ lock_data <- jsonlite :: fromJSON(lock_file )
124+
125+ # Extract R version if not provided
126+ if (is.null(r_version ) && ! is.null(lock_data $ R $ Version )) {
127+ r_version <- lock_data $ R $ Version
128+ }
129+
130+ # Set default base image if not provided
131+ if (is.null(base_image ) && ! is.null(r_version )) {
132+ base_image <- paste0(" rocker/r-ver:" , r_version )
133+ } else if (is.null(base_image )) {
134+ base_image <- " rocker/r-ver:latest"
135+ cli :: cli_warn(" R version not specified. Using {base_image}" )
136+ }
137+
138+ # Create base dockerfile
139+ df <- dockerfile() | >
140+ dfi_from(base_image ) | >
141+ dfi_label(maintainer = Sys.getenv(" USER" , " unknown" ))
142+
143+ # Add packages from lock file
144+ if (! is.null(lock_data $ Packages ) && length(lock_data $ Packages ) > 0 ) {
145+ pkgs <- names(lock_data $ Packages )
146+
147+ # Add system requirements if requested
148+ if (include_sysreqs && length(pkgs ) > 0 ) {
149+ df <- dk_add_sysreqs(df , pkgs )
150+ }
151+
152+ # Add renv initialization and restore
153+ df <- df | >
154+ dfi_workdir(" /app" ) | >
155+ dfi_copy(lock_file , " /app/renv.lock" ) | >
156+ dfi_run(c(
157+ " R -e \" install.packages('renv', repos = 'https://cloud.r-project.org/')\" " ,
158+ " R -e \" renv::init()\" " ,
159+ " R -e \" renv::restore()\" "
160+ ))
161+ }
162+
163+ df
164+ }
165+
166+ # ' Create a dockerfile from a DESCRIPTION file
167+ # '
168+ # ' @param description_file Path to DESCRIPTION file
169+ # ' @param r_version R version to use (default: from DESCRIPTION)
170+ # ' @param base_image Base image to use (default: determined from R version)
171+ # ' @param include_sysreqs Include system requirements (default: TRUE)
172+ # ' @return A dockerfile object
173+ # ' @export
174+ dk_from_description <- function (description_file = " DESCRIPTION" , r_version = NULL ,
175+ base_image = NULL , include_sysreqs = TRUE ) {
176+ if (! file.exists(description_file )) {
177+ cli :: cli_abort(" DESCRIPTION file not found: {description_file}" )
178+ }
179+
180+ # Read DESCRIPTION file
181+ desc_data <- read.dcf(description_file )
182+
183+ # Extract R version if not provided
184+ if (is.null(r_version ) && " Depends" %in% colnames(desc_data )) {
185+ r_depends <- desc_data [1 , " Depends" ]
186+ r_ver_match <- regexpr(" R \\ (>= ([0-9\\ .]+)\\ )" , r_depends )
187+ if (r_ver_match != - 1 ) {
188+ r_version <- sub(" R \\ (>= ([0-9\\ .]+)\\ )" , " \\ 1" , regmatches(r_depends , r_ver_match ))
189+ }
190+ }
191+
192+ # Set default base image if not provided
193+ if (is.null(base_image ) && ! is.null(r_version )) {
194+ base_image <- paste0(" rocker/r-ver:" , r_version )
195+ } else if (is.null(base_image )) {
196+ base_image <- " rocker/r-ver:latest"
197+ cli :: cli_warn(" R version not specified. Using {base_image}" )
198+ }
199+
200+ # Create base dockerfile
201+ df <- dockerfile() | >
202+ dfi_from(base_image ) | >
203+ dfi_label(maintainer = if (" Maintainer" %in% colnames(desc_data )) desc_data [1 , " Maintainer" ] else Sys.getenv(" USER" , " unknown" ))
204+
205+ # Extract packages from Depends, Imports, and Suggests
206+ pkg_fields <- c(" Depends" , " Imports" , " Suggests" )
207+ pkgs <- character (0 )
208+
209+ for (field in pkg_fields ) {
210+ if (field %in% colnames(desc_data )) {
211+ field_content <- desc_data [1 , field ]
212+ if (! is.na(field_content )) {
213+ # Split on commas and extract package names
214+ field_pkgs <- strsplit(field_content , " ," )[[1 ]]
215+ field_pkgs <- trimws(field_pkgs )
216+ # Remove version specifications and R dependency
217+ field_pkgs <- sub(" \\ s*\\ (.*\\ )$" , " " , field_pkgs )
218+ field_pkgs <- field_pkgs [! grepl(" ^R$" , field_pkgs )]
219+ pkgs <- c(pkgs , field_pkgs )
220+ }
221+ }
222+ }
223+
224+ # Add system requirements if requested
225+ if (include_sysreqs && length(pkgs ) > 0 ) {
226+ df <- dk_add_sysreqs(df , pkgs )
227+ }
228+
229+ # Add R package installation command
230+ if (length(pkgs ) > 0 ) {
231+ pkg_list <- paste0(shQuote(pkgs ), collapse = " , " )
232+ df <- dfi_run(df , paste0(" R -e \" install.packages(c(" , pkg_list , " ), repos='https://cloud.r-project.org/')\" " ))
233+ }
234+
235+ # Add package directory
236+ pkg_name <- desc_data [1 , " Package" ]
237+ df <- df | >
238+ dfi_workdir(" /app" ) | >
239+ dfi_copy(" ." , " /app/" ) | >
240+ dfi_run(paste0(" R CMD INSTALL --no-multiarch --with-keep.source /app" ))
241+
242+ df
243+ }
244+
245+ # ' Create a dockerfile from an R script
246+ # '
247+ # ' @param script_file Path to R script
248+ # ' @param base_image Base image to use (default: latest rocker/r-ver)
249+ # ' @param include_sysreqs Include system requirements (default: TRUE)
250+ # ' @return A dockerfile object
251+ # ' @export
252+ dk_from_script <- function (script_file , base_image = " rocker/r-ver:latest" , include_sysreqs = TRUE ) {
253+ if (! file.exists(script_file )) {
254+ cli :: cli_abort(" Script file not found: {script_file}" )
255+ }
256+
257+ # Read script file
258+ script <- readLines(script_file )
259+
260+ # Extract library and require calls to identify packages
261+ # Consider adding a dependency on `renv` to handle this kind of detection.
262+ library_pattern <- " library\\ (([^\\ )]+)\\ )"
263+ require_pattern <- " require\\ (([^\\ )]+)\\ )"
264+
265+ library_matches <- regmatches(script , gregexpr(library_pattern , script ))
266+ require_matches <- regmatches(script , gregexpr(require_pattern , script ))
267+
268+ pkgs <- character (0 )
269+
270+ # Process library calls
271+ for (match_list in library_matches ) {
272+ if (length(match_list ) > 0 ) {
273+ for (match in match_list ) {
274+ pkg_name <- sub(library_pattern , " \\ 1" , match )
275+ # Remove quotes if present
276+ pkg_name <- gsub(" [\" ']" , " " , pkg_name )
277+ pkgs <- c(pkgs , pkg_name )
278+ }
279+ }
280+ }
281+
282+ # Process require calls
283+ for (match_list in require_matches ) {
284+ if (length(match_list ) > 0 ) {
285+ for (match in match_list ) {
286+ pkg_name <- sub(require_pattern , " \\ 1" , match )
287+ # Remove quotes if present
288+ pkg_name <- gsub(" [\" ']" , " " , pkg_name )
289+ pkgs <- c(pkgs , pkg_name )
290+ }
291+ }
292+ }
293+
294+ # Create base dockerfile
295+ df <- dockerfile() | >
296+ dfi_from(base_image ) | >
297+ dfi_label(maintainer = Sys.getenv(" USER" , " unknown" ))
298+
299+ # Add system requirements if requested
300+ if (include_sysreqs && length(pkgs ) > 0 ) {
301+ df <- dk_add_sysreqs(df , pkgs )
302+ }
303+
304+ # Add R package installation command
305+ if (length(pkgs ) > 0 ) {
306+ pkgs <- unique(pkgs )
307+ pkg_list <- paste0(shQuote(pkgs ), collapse = " , " )
308+ df <- dfi_run(df , paste0(" R -e \" install.packages(c(" , pkg_list , " ), repos='https://cloud.r-project.org/')\" " ))
309+ }
310+
311+ # Add script execution
312+ script_name <- basename(script_file )
313+ df <- df | >
314+ dfi_workdir(" /app" ) | >
315+ dfi_copy(script_file , paste0(" /app/" , script_name )) | >
316+ dfi_cmd(paste0(" Rscript /app/" , script_name ))
317+
318+ df
319+ }
0 commit comments