# Copyright 2011-2014 Meik Michalke <meik.michalke@hhu.de>
#
# This file is part of the R package roxyPackage.
#
# roxyPackage is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# roxyPackage is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with roxyPackage. If not, see <http://www.gnu.org/licenses/>.
#' Automatic doc creation, package building and repository update
#'
#' This function should help to create R packages with full documentation and updates to a local repository.
#' It supports source and binary packaging (Windows and Mac OS X; see Note section on the limitations).
#'
#' For the documentation \code{roxygen2}[1] is used. Next to the actual in-line documentation of the package's contents, you only need to
#' prepare a data.frame to be used to write a package \code{DESCRIPTION} file. See the example section for details on that. This means
#' that you \emph{neither} edit the \code{DESCRIPTION} \emph{nor} the \code{*-package.R} file manually, they will both be created \emph{automatically}
#' by this function with contents according to these settings!
#'
#' @section Sandboxing:
#' If you want to check out the effects of roxy.package() without touching you actual package sources, try \code{\link[roxyPackage:sandbox]{sandbox}}
#' to set up a safe testing environment.
#'
#' @section Repository layout:
#' The repository will have this directory structure, that is, below the defined \code{repo.root}:
#'
#' \describe{
#' \item{\code{./src/contrib}}{Here go the source packages}
#' \item{\code{./bin/windows/contrib/$RVERSION}}{Here go the Windows binaries}
#' \item{\code{./bin/macosx/leopard/contrib/$RVERSION}}{Here go the Mac OS X binaries}
#' \item{\code{./pckg/index.html}}{A global package index with links to packages' index files, if actions included \code{"html"}}
#' \item{\code{./pckg/web.css}}{A CRAN-style CSS file, if actions included \code{"html"}}
#' \item{\code{./pckg/$PACKAGENAME}}{Here go documentation PDF and vignette, as well as a \code{ChangeLog} file, if found.
#' and an \code{index.html} with package information, if actions included \code{"html"}.
#' This is probably a bit off-standard, but practical if you several packages.}
#' }
#'
#' @section Converting ChangeLogs into NEWS:
#' See \code{\link[roxyPackage:cl2news]{cl2news}} for details.
#'
#' @section Build for several R versions:
#' The options \code{R.libs} and \code{R.homes} can actually take more than one string, but a vector of strings. This can be used
#' to build packages for different R versions, provided you installed them on your system. If you're running GNU/Linux, an easy way
#' of doing so is to fetch the R sources from CRAN, calling \code{"./configure"} with something like \code{"--prefix=$HOME/R/<R version>"},
#' so that \code{"make install"} installs to that path. Let's assume you did that with R 2.12.2 and 2.11.1, you could then call \code{roxy.package}
#' with options like \code{R.homes=c("home/user/R/R-2.11.1", "home/user/R/R-2.12.2")} and \code{R.libs=c("home/user/R/R-2.11.1/lib64/R/library",}
#' \code{"home/user/R/R-2.12.2/lib64/R/library")}. \code{roxy.package} will then call itself recursively for each given R installation.
#'
#' One thing you should be aware of is that \code{roxy.package} will not perform all actions each time. That is because some of them, namely
#' \code{"roxy"}, \code{"cite"}, \code{"license"}, \code{"doc"}, \code{"cl2news"} and \code{"news2rss"}, should produce identical
#' results anyway, so they are only considered during the first run. You should always place the R version which should be linked to from the
#' HTML index last in line, because \code{"html"} will overwrite previous results. For a similar reason, the \code{"deb"} action will only actually
#' build a binary package during the last run, but debianizing it will be done during the first.
#'
#' @section Windows: On Windows, the actions \code{"doc"} and \code{"check"} will only work correctly if you have installed and configured LaTeX
#' accordingly, and you will also need Rtools set up for packaging.
#'
#' @section CRAN compliance: The CRAN policies can sometimes be very strict. This package should allow you to produce packages which are suitable
#' for release on CRAN. But some steps have to be taken care of by yourself. For instance, CRAN does currently not allow copies of common licenses
#' in a source package, nor a \code{debian} folder. Therefore, if your package is supposed to be released on CRAN, you should include
#' \code{Rbuildignore=c("debian", "LICENSE.txt")} to the function call.
#'
#' @note The binary packaging is done simply by zipping (Windows) or targzipping (Mac OS X) the built and installed package. This should
#' do the trick as long as your package is written in pure R code. It will most likely not produce usable packages if it contains
#' code in other languages like C++.
#'
#' @param pck.source.dir Character string, path pointing to the root directory of your package sources.
#' @param pck.version Character string, defining the designated version number. Can be omitted if actions don't
#' include \code{"roxy"}, then this information is read from the present DESCRIPTION file.
#' @param pck.description Data frame holding the package description (see Examples section).
#' @param R.libs Character string, valid path to the R library where the package should be installed to.
#' @param repo.root Character string, valid path to a directory where to build/update a local package repository.
#' @param pck.date Character string of the release date in YYYY-MM-DD format. Defaults to \code{Sys.Date()}. If actions don't
#' include \code{"roxy"}, then this information is read from the present DESCRIPTION file.
#' @param actions Character vector, must contain at least one of the following values:
#' \describe{
#' \item{"roxy"}{Roxygenize the docs}
#' \item{"cite"}{Update CITATION file}
#' \item{"license"}{Update LICENSE.txt file; it's not called LICENSE to prevent an automatic installation}
#' \item{"check"}{Do a full package check, calling \code{R CMD check}}
#' \item{"package"}{Build & install the package, update source repository, calling \code{R CMD build} and \code{R CMD INSTALL}}
#' \item{"cl2news"}{Try to convert a ChangeLog file into an NEWS.Rd file}
#' \item{"news2rss"}{Try to convert \code{inst/NEWS.Rd} into an RSS feed. You must also set
#' \code{URL} accordingly.}
#' \item{"doc"}{Update PDF documentation and vignette (if present), \code{R CMD Rd2pdf} (or \code{R CMD Rd2dvi} for R < 2.15)}
#' \item{"html"}{Update HTML index files}
#' \item{"win"}{Update the Windows binary package}
#' \item{"macosx"}{Update the Mac OS X binary package}
#' \item{"log"}{Generate initial ChangeLog or update a present ChangeLog file}
#' \item{"deb"}{Update the Debian binary package with \code{\link[roxyPackage:debianize]{debianize}} (works only on Debian systems;
#' see \code{deb.options}, too)}
#' \item{"cleanRd"}{Insert line breaks in Rd files with lines longer than 90 chars}
#' }
#' Note that \code{"cl2news"} will write the \code{NEWS.Rd} file to the \code{inst} directory of your sources, which will overwrite
#' an existing file with the same name! Also note that if both a \code{NEWS/NEWS.Rd} and \code{ChangeLog} file are found, only
#' news files will be linked by the \code{"html"} action.
#' @param local.roxy.dir Character string, path to a directory to roxygenize docs in, if you don't want to do it in place.
#' If \code{NULL} (the default) or identical to \code{pck.source.dir}, docs will be created in place.
#' @param cleanup Logical, if \code{TRUE} will remove backup files (matching \code{.*~$} or \code{.*backup$}) from the source directory.
#' @param roxy.unlink.target Logical, setting the \code{unlink.target} option of \code{\link[roxygen2:roxygenize]{roxygenize}}
#' @param rm.vignette Logical, if \code{TRUE} and a vignette PDF was build during the \code{"doc"} action, it will not be kept
#' in the source package but just be moved to the \code{./pckg/$PACKAGENAME} directory of the repository.
#' @param R.homes Path to the R installation to use. Can be set manually to build packages for other R versions than the default one,
#' if you have installed them in parallel. Should probably be used together with \code{R.libs}.
#' @param html.index A character string for the headline of the global index HTML file.
#' @param html.title A character string for the title tag prefix of the package index HTML file.
#' @param Rcmd.options A named character vector with options to be passed on to the internal calls of \code{R CMD build},
#' \code{R CMD INSTALL}, \code{R CMD check} and \code{R CMD Rd2pdf} (or \code{R CMD Rd2dvi} for R < 2.15). Change these only if you know what you're doing!
#' Will be passed on as given here. To deactivate, options must explicitly be se to \code{""}, missing options will be used with the default values.
#' @param URL A character string defining the URL to the root of the repository (i.e., which holds the directories \code{src}
#' etc.). This is not the path to the local file system, but should be the URL to the repository as it is available
#' via internet. This option is neccessary for (and only interpreted by) the action \code{"news2rss"}.
#' @param deb.options A named list with parameters to pass through to \code{\link[roxyPackage:debianize]{debianize}}. By default, \code{pck.source.dir}
#' and \code{repo.root} are set to the values given to the parameters above. As for the other options, if not set, the defaults of \code{debianize}
#' will be used.
#' @param ChangeLog A named list of character vectors with log entry items. The element names will be used as section names in the ChangeLog entry,
#' and each character string in a vector will be pasted as a log item. The news you provide here will be appended to probably present news, while
#' trying to prevent duplicate entries to appear. If you need more control, don't use the \code{"log"} action, but have a look at
#' \code{\link[roxyPackage:updateChangeLog]{updateChangeLog}}. Also note that the date of altered entries will be updated automatically, unless
#' you don't call the \code{"roxy"} action, too.
#' @param Rbuildignore A character vector to be used as lines of an \code{.Rbuildignore} file. If set, this will replace an existing \code{.Rbuildignore}
#' file. Setting it to an empty string (\code{""}) will remove the file, the default value \code{NULL} will simply keep the file, if one is present.
#' @param Rinstignore A character vector to be used as lines of an \code{.Rinstignore} file. If set, this will replace an existing \code{.Rinstignore}
#' file. Setting it to an empty string (\code{""}) will remove the file, the default value \code{NULL} will simply keep the file, if one is present.
#' @param ... Additional options passed through to \code{roxygenize}.
#' @references
#' [1] \url{http://cran.r-project.org/web/packages/roxygen2/}
#' @seealso \code{\link[roxyPackage:sandbox]{sandbox}} to run roxy.package() in a sandbox.
#' @include write_PACKAGES.R
#' @export
#' @examples
#' \dontrun{
#' ## package description as data.frame:
#' pckg.dscrptn <- data.frame(
#' Package="SquareTheCircle",
#' Type="Package",
#' Title="Squaring the circle using Heisenberg compensation",
#' Author="E.A. Dölle <doelle@@eternalwondermaths.example.org>",
#' AuthorR="c(person(given=\"Ernst\", family=\"Dölle\",
#' email=\"doelle@@eternalwondermaths.example.org\", role=c(\"aut\", \"cre\")))",
#' Maintainer="E.A. Dölle <doelle@@eternalwondermaths.example.org>",
#' Depends="R (>= 2.10.0),heisenberg (>= 0.23),tools",
#' Enhances="rkward",
#' Description="This package squares the circle using Heisenberg compensation.
#' The code came from a meeting with Yrla Nor that i had in a dream. Please
#' don't forget to chain your computer to the ground, because these
#' algorithms might make it fly.",
#' License="GPL (>= 3)",
#' Encoding="UTF-8",
#' LazyLoad="yes",
#' URL="http://eternalwondermaths.example.org",
#' stringsAsFactors=FALSE)
#' # hint no. 1: you *don't* specify version number and release date here,
#' # but all other valid fields for DESCRIPTION files must/can be defined
#' # hint no. 2: most of this rarely changes, so you can add this to the
#' # internals of your package and refer to it as
#' # roxy.package(pck.description=SquareTheCircle:::pckg.dscrptn, ...)
#' # hint no. 3: use "AuthorR" for the "Author@@R" field, or "AuthorsR" for
#' # R >= 2.14, to work around naming problems
#'
#' roxy.package(pck.source.dir="~/my_R_stuff/SquareTheCircle",
#' pck.version="0.01-2",
#' pck.description=pckg.dscrptn,
#' R.libs="~/R",
#' repo.root="/var/www/repo",
#' actions=c("roxy", "package", "doc"))
#' }
roxy.package <- function(
pck.source.dir,
pck.version,
pck.description,
R.libs,
repo.root,
pck.date=Sys.Date(),
actions=c("roxy", "package"),
local.roxy.dir=NULL,
cleanup=FALSE,
roxy.unlink.target=TRUE,
rm.vignette=FALSE,
R.homes=R.home(),
html.index="Available R Packages",
html.title="R package",
Rcmd.options=c(
install="",
build="--no-manual --no-build-vignettes",
check="--as-cran",
Rd2pdf="--pdf --no-preview"),
URL=NULL,
deb.options=NULL,
ChangeLog=list(changed=c("initial release"), fixed=c("missing ChangeLog")),
Rbuildignore=NULL,
Rinstignore=NULL,
...){
# avoid some NOTEs from R CMD check
AuthorR <- AuthorsR <- Author.R <- Authors.R <- NULL
# check the OS first
unix.OS <- isUNIX()
# let's check if packages are to be build for several R versions
R.versions <- length(R.homes)
R.libraries <- length(R.libs)
if(R.versions > 1){
if(R.libraries != R.versions){
stop(simpleError("If you specify more than one R.home, you must also define as many R.libs!"))
} else {}
# if so, iterate recursively through it and then end
for (this.R in 1:R.versions){
this.home <- R.homes[this.R]
this.libs <- R.libs[this.R]
this.actions <- actions
this.deb.options <- deb.options
if("deb" %in% actions){
if(this.R == 1){
# for the time being, debianizing the package once is enough
# the actual binary package build will only be done at the last run
this.deb.options[["actions"]] <- deb.options[["actions"]][deb.options[["actions"]] %in% "deb"]
} else if(this.R == R.versions){
# we'll only do it the *last* run
this.deb.options[["actions"]] <- deb.options[["actions"]][deb.options[["actions"]] %in% c("bin", "src")]
} else {
# all other cases: no debianizing
this.deb.options[["actions"]] <- ""
}
} else {}
if(this.R > 1){
# well, the same is true for some other actions
# we'll only perform them during the *first* run
this.actions <- actions[!actions %in% c("roxy", "cite", "license", "doc", "cl2news", "news2rss", "cleanRd")]
# we also don't need to repeat handling of .Rbuildignore and .Rinstignore
Rbuildignore <- Rinstignore <- NULL
} else {}
roxy.package(
pck.source.dir=pck.source.dir,
pck.version=pck.version,
pck.description=pck.description,
R.libs=this.libs,
repo.root=repo.root,
pck.date=pck.date,
actions=this.actions,
local.roxy.dir=local.roxy.dir,
cleanup=cleanup,
roxy.unlink.target=roxy.unlink.target,
rm.vignette=rm.vignette,
R.homes=this.home,
html.index=html.index,
html.title=html.title,
Rcmd.options=Rcmd.options,
URL=URL,
deb.options=this.deb.options,
ChangeLog=ChangeLog,
Rbuildignore=Rbuildignore,
Rinstignore=Rinstignore,
...)
}
return(invisible(NULL))
} else {}
old.dir <- getwd()
on.exit(setwd(old.dir))
# fist see if we need to rename an "AuthorR" field. data.frame definitions tend to
# replace the "@" with a dot
if("AuthorR" %in% names(pck.description)){
pck.description$`Author@R` <- pck.description[["AuthorR"]]
pck.description <- subset(pck.description, select=-AuthorR)
} else if("AuthorsR" %in% names(pck.description)){
pck.description$`Authors@R` <- pck.description[["AuthorsR"]]
pck.description <- subset(pck.description, select=-AuthorsR)
} else if("Author.R" %in% names(pck.description)){
pck.description$`Author@R` <- pck.description[["Author.R"]]
pck.description <- subset(pck.description, select=-Author.R)
} else if("Authors.R" %in% names(pck.description)){
pck.description$`Authors@R` <- pck.description[["Authors.R"]]
pck.description <- subset(pck.description, select=-Authors.R)
} else {}
# normalize all root paths
R.homes <- normalizePathByOS(path=R.homes, is.unix=unix.OS, mustWork=TRUE)
repo.root <- normalizePathByOS(path=repo.root, is.unix=unix.OS, mustWork=FALSE)
pck.source.dir <- normalizePathByOS(path=pck.source.dir, is.unix=unix.OS, mustWork=TRUE)
# get info on the R version used
R.Version.full <- getRvers(R.homes=R.homes)
R.Version.win <- getRvers(R.homes=R.homes, win=TRUE)
if(nchar(R.Version.full) < 3 | nchar(R.Version.win) < 3){
stop(simpleError("R version number cannot be detected, it seems."))
} else {}
# special cases: if actions do not include "roxy",
# take infos from the DESCRIPTION file
if(!"roxy" %in% actions | is.null(pck.description)){
pck.dscrptn <- as.data.frame(read.dcf(file=file.path(pck.source.dir, "DESCRIPTION")), stringsAsFactors=FALSE)
# clean from newlines
pck.dscrptn <- as.data.frame(t(sapply(pck.dscrptn, function(x) gsub("\n", " ", x))), stringsAsFactors=FALSE)
pck.version <- getDescField(pck.dscrptn, field="Version")
# if "Date" is missing, try some fallbacks
pck.date <- as.character(as.Date(getDescField(pck.dscrptn, field=c("Date","Packaged","Date/Publication"))))
pck.package <- getDescField(pck.dscrptn, field="Package")
pck.title <- getDescField(pck.dscrptn, field="Title")
rss.description <- getDescField(pck.dscrptn, field="Description")
pckg.license <- getDescField(pck.dscrptn, field="License")
pckg.dscrptn <- pck.description <- pck.dscrptn
} else {
rss.description <- pck.description[["Description"]]
pck.package <- roxy.description("package", description=pck.description)
pck.title <- roxy.description("title", description=pck.description)
pckg.dscrptn <- roxy.description("description", description=pck.description, version=pck.version, date=as.character(pck.date), R.vers=R.Version.full)
pckg.license <- pck.description[["License"]]
}
pckg.package <- roxy.description("pckg.description", description=pck.description, version=pck.version, date=as.character(pck.date))
## check for sandboxing
if(isTRUE(check.sandbox())){
message("preparing sandbox...")
# prepare folder structure; this will also insure sane values and abort
# if locations are invalid. the function returns a list of paths to use.
if("check" %in% actions){
# for check runs we need also all suggested packages
initSuggests <- TRUE
} else {
initSuggests <- FALSE
}
adjust.paths <- prepare.sandbox(
package=pck.package,
description=pckg.dscrptn,
pck.source.dir=pck.source.dir,
R.libs=R.libs,
R.version=R.Version.full,
repo.root=repo.root,
depLevel=c("Depends", "Imports"),
initSuggests=initSuggests)
# replace paths with sandbox
pck.source.dir <- adjust.paths[["pck.source.dir"]]
R.libs <- adjust.paths[["R.libs"]]
repo.root <- adjust.paths[["repo.root"]]
sandbox.status()
} else {}
# check environment
R.bin <- file.path(R.homes, "bin", "R")
message(paste("R environment\n R.home:", R.homes, "\n R.libs:", R.libs))
repo.src.contrib <- file.path(repo.root, "src", "contrib")
repo.win <- file.path(repo.root, "bin", "windows", "contrib", R.Version.win)
# repo trees have changed with R 3.0.0
repo.macosx <- ifelse(isTRUE(R_system_version(R.Version.full) < "3.0"),
file.path(repo.root, "bin", "macosx","leopard", "contrib", R.Version.win),
file.path(repo.root, "bin", "macosx","contrib", R.Version.win))
repo.pckg.info.main <- file.path(repo.root, "pckg")
repo.pckg.info <- file.path(repo.pckg.info.main, pck.package)
pckg.basename <- paste0(pck.package, "_", pck.version)
pckg.name.src <- paste0(pckg.basename, ".tar.gz")
pckg.name.win <- paste0(pckg.basename, ".zip")
pckg.name.mac <- paste0(pckg.basename, ".tgz")
pckg.inst.dir <- file.path(pck.source.dir, "inst")
pckg.cite.file <- file.path(pckg.inst.dir, "CITATION")
pckg.cite.file.html <- file.path(repo.pckg.info, "citation.html")
src.changelog <- file.path(pck.source.dir, "ChangeLog")
pckg.changelog <- file.path(repo.pckg.info, "ChangeLog")
repo.src.gz <- file.path(repo.src.contrib, pckg.name.src)
win.package <- file.path(repo.win, pckg.name.win)
macosx.package <- file.path(repo.macosx, pckg.name.mac)
pckg.NEWS.Rd <- file.path(pckg.inst.dir, "NEWS.Rd")
pckg.NEWS.inst <- file.path(pckg.inst.dir, "NEWS")
pckg.NEWS <- file.path(pck.source.dir, "NEWS")
pckg.NEWS.html <- file.path(repo.pckg.info, "NEWS.html")
RSS.file.name <- "RSS.xml"
pckg.NEWS.rss <- file.path(repo.pckg.info, RSS.file.name)
pckg.license.file <- file.path(pck.source.dir, "LICENSE.txt")
pckg.license.file.old <- file.path(pck.source.dir, "LICENSE")
pckg.pdf.doc <- paste0(pck.package, ".pdf")
# take care of .Rbuildignore and .Rinstignore
pckg.Rbuildignore <- configFile(root=pck.source.dir, type="build", content=Rbuildignore)
pckg.Rinstignore <- configFile(root=pck.source.dir, type="inst", content=Rinstignore)
# debian package specific stuff, probably needed for "html" action
deb.defaults <- formals(debianize)
deb.defaults[["pck.source.dir"]] <- pck.source.dir
deb.defaults[["repo.root"]] <- repo.root
deb.args.def <- names(deb.defaults)
deb.args.set <- names(deb.options)
deb.defaults[deb.args.set[deb.args.set %in% deb.args.def]] <- deb.options[deb.args.set[deb.args.set %in% deb.args.def]]
# try to set pckg.name.deb and deb.repo.path
# this will only work if repo.root is unchanged, the rest is too messy now...
deb.repo.path.part <- paste0("deb/dists/", deb.defaults[["distribution"]], "/", deb.defaults[["component"]], "/", deb.defaults[["arch"]])
deb.repo.path <- paste0("../../", deb.repo.path.part)
pckg.name.deb.part <- gsub("\\.", "-", tolower(paste("r", deb.defaults[["origin"]], pck.package, sep="-")))
pckg.name.deb <- paste0(pckg.name.deb.part, "_", pck.version, "-", deb.defaults[["revision"]], "_", deb.defaults[["arch"]], ".deb")
deb.package <- file.path(repo.root, deb.repo.path.part, pckg.name.deb)
# check for additional CMD options
if("build" %in% names(Rcmd.options)){
Rcmd.opt.build <- paste0(Rcmd.options[["build"]], " ")
} else {
# --no-vignettes is deprecated since R 3.0.0, need version check here
Rcmd.opt.novignettes <- ifelse(isTRUE(R_system_version(R.Version.full) < "3.0"), "--no-vignettes ", "--no-build-vignettes ")
# --no-manual was introduced with R 2.12, need version check here
Rcmd.opt.build <- ifelse(isTRUE(R_system_version(R.Version.full) < "2.12"),
Rcmd.opt.novignettes,
paste0("--no-manual ", Rcmd.opt.novignettes))
}
Rcmd.opt.install <- ifelse("install" %in% names(Rcmd.options), paste0(Rcmd.options[["install"]], " "), "")
# --as-cran was introduced with R 2.15, strip if this is an older version
if(isTRUE(R_system_version(R.Version.full) < "2.15")){
Rcmd.options[["check"]] <- gsub("--as-cran", "", Rcmd.options[["check"]])
} else {}
Rcmd.opt.check <- ifelse("check" %in% names(Rcmd.options), paste0(Rcmd.options[["check"]], " "), "")
# R 2.15 switched from Rd2dvi to Rd2pdf
Rcmd.cmd.Rd2pdf <- ifelse(isTRUE(R_system_version(R.Version.full) < "2.15"), "Rd2dvi", "Rd2pdf")
if("Rd2pdf" %in% names(Rcmd.options)){
Rcmd.opt.Rd2pdf <- paste0(Rcmd.options[["Rd2pdf"]], " ")
} else if("Rd2dvi" %in% names(Rcmd.options)){
warning("Rcmd.options: Rd2dvi is now called Rd2pdf, please update your scripts! used the settings anyway, though.", call.=FALSE)
Rcmd.opt.Rd2pdf <- paste0(Rcmd.options[["Rd2dvi"]], " ")
} else {
Rcmd.opt.Rd2pdf <- "--pdf --no-preview "
}
# check for/create info directory
createMissingDir(dirPath=repo.pckg.info, action="repo")
# clean up
if(isTRUE(cleanup)){
unlink(list.files(pck.source.dir, pattern=".*~$", full.names=TRUE, recursive=TRUE))
unlink(list.files(pck.source.dir, pattern=".*backup$", full.names=TRUE, recursive=TRUE))
} else {}
if("license" %in% actions){
if(checkLicence(pckg.license)){
copyLicence(pckg.license, pckg.license.file, overwrite=TRUE)
if(file.exists(pckg.license.file.old)){
warning("license: you have both LICENSE and LICENSE.txt in your project! if LICENSE is one of the standard licenses, please rename it to prevent its intallation.", call.=FALSE)
} else {}
} else {
stop(simpleError(paste0("license: unrecognized license (", pckg.license, "), please provide your own LICENSE file! ")))
}
} else {}
if("roxy" %in% actions){
if(is.null(local.roxy.dir)){
local.roxy.dir <- pck.source.dir
} else {}
# re-write DESCRIPTION files
write.dcf(pckg.dscrptn, file=file.path(pck.source.dir, "DESCRIPTION"))
# copy DESCRIPTION to pckg directory for easy HTML indexing
stopifnot(file.copy(file.path(pck.source.dir, "DESCRIPTION"), file.path(repo.pckg.info, "DESCRIPTION"), overwrite=TRUE))
pckg.package.file.R <- file.path(pck.source.dir, "R", paste0(pck.package, "-package.R"))
cat(paste(pckg.package), file=pckg.package.file.R)
message(paste0("roxy: updated ", pckg.package.file.R, "."))
# # if(isTRUE(roxy2)){
# roxygenize(pck.source.dir, roxygen.dir=local.roxy.dir, unlink.target=roxy.unlink.target, ...)
# # } else {
# # require(roxygen)
# # roxygen::roxygenize(pck.source.dir, roxygen.dir=local.roxy.dir, use.Rd2=TRUE, unlink.target=TRUE, ...)
# # }
roxygenVersion <- get.roxyEnv("roxygenVersion")
if(roxygenVersion == 3){
# unforunately, there is no roxygen3 package, and there probably never will be. so, in case
# you want to test it, you have to build, install and load it yourself, since we cannot
# officially import it's functions. that would invalidate a package check for roxyPackage
roxygen3IsLoaded <- any(grepl("package:roxygen3", search()))
if(roxygen3IsLoaded){
roxygenise(pck.source.dir, ...)
} else {
stop(simpleError("roxy: you want to use roxygen3, but the package isn't loaded. please call:\n require(roxygen3)"))
}
} else {
# another -- hopefully temporary -- workaround for roxygen2 >= 3.0.0
# there's still issues with S4 classes, only said to be working with devtools,
# but we don't want to add new dependencies just to get this working.
# so here's a call which seems to do the trick, alas we don't yet know why... ;-)
if(packageVersion("roxygen2") > "2.2.2"){
message("roxy: applying workaround for roxygen2 >= 3.0.0...")
local(dummyResult <- roxygen2:::source_package(pck.source.dir))
} else {}
#
roxygen2::roxygenize(pck.source.dir, roxygen.dir=local.roxy.dir, unlink.target=roxy.unlink.target, ...)
}
} else {}
if("cleanRd" %in% actions){
if("roxy" %in% actions && !identical(local.roxy.dir, pck.source.dir)){
# work in roxy dir for now
pckg.man.dir <- file.path(local.roxy.dir, "man")
} else {
pckg.man.dir <- file.path(pck.source.dir, "man")
}
Rd.files <- list.files(pckg.man.dir, pattern="*.Rd", ignore.case=TRUE)
if(length(Rd.files) > 0){
for (this.file in Rd.files){
sanitizeRdFiles(this.file, root.dir=pckg.man.dir, maxlength=90)
}
} else {}
} else {}
if("cite" %in% actions){
createMissingDir(dirPath=pckg.inst.dir, action="cite")
# calling internal function to generate citation object
cite.obj <- citationText(pck.dscr=pck.description, pck.version=pck.version, pck.date=pck.date)
cat(cite.obj, file=pckg.cite.file)
message(paste0("cite: updated ", pckg.cite.file, "."))
if("html" %in% actions){
cite.obj.html <- roxy.html.cite(cite.obj=eval(parse(text=cite.obj)), page.css="../web.css", package=pck.package)
cat(cite.obj.html, file=pckg.cite.file.html)
message(paste0("cite: updated ", pckg.cite.file.html, "."))
} else {}
} else {}
if("check" %in% actions){
# check for examples check file before
chk.ex.file <- file.path(pck.source.dir, paste0(pck.package, "-Ex.R"))
chk.ex.file.present <- ifelse(file_test("-f", chk.ex.file), TRUE, FALSE)
tryCatch(chk.out.dir <- tempdir(), error=function(e) stop(e))
message(paste0("check: calling R CMD check, this might take a while..."))
if(isTRUE(unix.OS)){
r.cmd.check.call <- paste0("R_LIBS_USER=", R.libs, " ; ",
R.bin, " CMD check --output=", chk.out.dir, " ", Rcmd.opt.check, pck.source.dir)
print(system(r.cmd.check.call, intern=TRUE))
} else {
r.cmd.check.call <- paste0("set R_LIBS_USER=", shQuote(R.libs, type="cmd"), " && ",
R.bin, " CMD check --output=", chk.out.dir, " ", Rcmd.opt.check, shQuote(pck.source.dir, type="cmd"))
print(shell(r.cmd.check.call, translate=TRUE, intern=TRUE))
}
on.exit(message(paste0("check: saved results to ", chk.out.dir, "/", pck.package, ".Rcheck")), add=TRUE)
# need to clean up?
if(!isTRUE(chk.ex.file.present) & file_test("-f", chk.ex.file)){
# there's an example file which wasn't here before
unlink(chk.ex.file)
} else {}
} else {}
if("log" %in% actions){
if(!file.exists(src.changelog)){
newLog <- initChangeLog(entry=ChangeLog, package=pck.package, version=pck.version, date=pck.date)
writeChangeLog(log=newLog, file=src.changelog)
message("log: generated initial ChangeLog")
} else {
oldLog <- readChangeLog(src.changelog)
newLog <- updateChangeLog(oldLog, entry=ChangeLog, version=pck.version, date=pck.date, append=TRUE)
newLogEntry <- getChangeLogEntry(newLog, pck.version)
if(!identical(oldLog, newLog)){
writeChangeLog(log=newLog, file=src.changelog)
message("log: updated ChangeLog")
print(newLogEntry)
} else {
message("log: ChangeLog is up-to-date, skipped")
}
}
} else {}
if("package" %in% actions){
if("roxy" %in% actions){
if(!identical(local.roxy.dir, pck.source.dir)){
stopifnot(file.copy(Sys.glob(file.path(local.roxy.dir, "man", "*")), file.path(pck.source.dir, "man"), overwrite=TRUE))
message("build: copied Rd files from roxygen to man.")
stopifnot(file.copy(file.path(local.roxy.dir, "NAMESPACE"), file.path(pck.source.dir, "NAMESPACE"), overwrite=TRUE))
message("build: copied NAMESPACE from roxygen to build dir.")
} else {}
} else {}
## fill source repo
createMissingDir(dirPath=repo.src.contrib, action="repo")
## TODO: find a solution without sedwd()
jmp.back <- getwd()
setwd(file.path(pck.source.dir, ".."))
## Ready to stop when build or install fails
buildInstallError <-
tryCatch({
if(isTRUE(unix.OS)){
r.cmd.build.call <- paste0(R.bin, " CMD build ", Rcmd.opt.build, pck.source.dir)
system(r.cmd.build.call, intern=TRUE)
} else {
r.cmd.build.call <- paste0(R.bin, " CMD build ", Rcmd.opt.build, shQuote(pck.source.dir, type="cmd"))
shell(r.cmd.build.call, translate=TRUE, ignore.stderr=TRUE, intern=TRUE)
}
file.mv(from=file.path(pck.source.dir,"..",pckg.name.src), to=repo.src.gz, overwrite=TRUE)
message(paste0("repo: copied ", pckg.name.src, " to src/contrib."))
FALSE
}, warning = function(x){
warning(x)
TRUE
})
setwd(jmp.back)
if(buildInstallError)
stop(simpleError("Build was unsuccessful! Aborting..."))
# install.packages() doesn't work if we want to build for/with other R installations than
# the actual running one, so we'll use R CMD INSTALL instead
buildInstallError <-
tryCatch({
if(isTRUE(unix.OS)){
r.cmd.install.call <- paste0(R.bin, " CMD INSTALL -l ", R.libs, " ",
Rcmd.opt.install, pck.source.dir)
system(r.cmd.install.call, intern=TRUE)
} else {
r.cmd.install.call <- paste0(R.bin, " CMD INSTALL -l ", shQuote(R.libs, type="cmd"), " ",
Rcmd.opt.install, shQuote(pck.source.dir, type="cmd"))
shell(r.cmd.install.call, translate=TRUE, ignore.stderr=TRUE, intern=TRUE)
}
message("build: built and installed package")
FALSE
}, warning = function(x){
warning(x)
TRUE
})
if(buildInstallError)
stop(simpleError("Install was unsuccessful! Aborting..."))
write_PACKAGES(dir=repo.src.contrib, type="source", verbose=TRUE, latestOnly=FALSE)
message("repo: updated src/contrib/PACKAGES (source)")
## update ChangeLog
if(file.exists(src.changelog)){
stopifnot(file.copy(src.changelog, pckg.changelog, overwrite=TRUE))
message("pckg: updated ChangeLog")
} else {}
} else {}
## create NEWS.Rd from ChangeLog
if("cl2news" %in% actions){
cl2news(log=src.changelog, news=pckg.NEWS.Rd, codify=TRUE, overwrite=TRUE)
} else {}
## create RSS feed from NEWS.Rd
add.RSS <- FALSE
if("news2rss" %in% actions){
if(is.null(URL)){
warning("news: no URL specified, RSS feed creation was skipped!", call.=FALSE)
} else {
package.URL <- paste(gsub("/*$", "", URL), "pckg", pck.package, sep="/")
RSS.atom.URL <- paste(package.URL, RSS.file.name, sep="/")
news2rss(
news=pckg.NEWS.Rd, rss=pckg.NEWS.rss, html=FALSE, encoding="UTF-8",
channel=c(
title=pck.package,
link=package.URL,
description=rss.description,
atom=RSS.atom.URL))
add.RSS <- TRUE
}
} else {}
# had to move this down here, because tools::pkgVignettes() and tools::buildVignettes()
# don't work without an existing DESCRIPTION file since R 3.0.1... hooooray...
pckg.vignette.dir <- tools::pkgVignettes(dir=pck.source.dir)$dir
## update PDF docs
if("doc" %in% actions){
if(!is.null(pckg.vignette.dir)){
# create and move vignette
tools::buildVignettes(dir=pck.source.dir)
# check for possible vignette documents
# becomes character(0) if none found
pdf.vignette.files <- list.files(pckg.vignette.dir, pattern="*.pdf|*.html", ignore.case=TRUE)
createMissingDir(file.path(R.libs, pck.package, "doc"), action="doc")
for(thisVignette in pdf.vignette.files){
pdf.vignette.src <- file.path(pckg.vignette.dir, thisVignette)
pdf.vignette.dst <- file.path(R.libs, pck.package, "doc", thisVignette)
stopifnot(file.copy(pdf.vignette.src, pdf.vignette.dst, overwrite=TRUE))
if(isTRUE(rm.vignette)){
stopifnot(file.remove(pdf.vignette.src))
} else {}
message(paste0("build: created PDF vignette (", thisVignette, ")"))
## copy vignettes
pdf.vignette.repo.dir <- file.path(repo.pckg.info, "vignettes")
if(!file.exists(pdf.vignette.repo.dir))
stopifnot(dir.create(pdf.vignette.repo.dir))
pdf.vignette.repo <- file.path(pdf.vignette.repo.dir, thisVignette)
if(file.exists(pdf.vignette.dst)){
stopifnot(file.copy(pdf.vignette.dst, pdf.vignette.repo, overwrite=TRUE))
message(paste0("repo: updated vignette (", thisVignette, ")"))
} else {}
}
} else {}
pdf.docs <- file.path(repo.pckg.info, pckg.pdf.doc)
removeIfExists(filePath=pdf.docs)
if(isTRUE(unix.OS)){
r.cmd.doc.call <- paste0(R.bin, " CMD ", Rcmd.cmd.Rd2pdf, " ", Rcmd.opt.Rd2pdf, "--output=", pdf.docs, " ", pck.source.dir)
system(r.cmd.doc.call, intern=TRUE)
} else {
r.cmd.doc.call <- paste0(R.bin, " CMD ", Rcmd.cmd.Rd2pdf, " ", Rcmd.opt.Rd2pdf, "--output=", shQuote(pdf.docs, type="cmd"), " ", shQuote(pck.source.dir, type="cmd"))
shell(r.cmd.doc.call, translate=TRUE, ignore.stderr=TRUE, intern=TRUE)
}
message("build: created PDF docs")
} else {}
## fill windows repo
if("win" %in% actions){
createMissingDir(dirPath=repo.win, action="repo")
removeIfExists(filePath=win.package)
## TODO: find a solution without sedwd()
jmp.back <- getwd()
setwd(R.libs)
# make a list of backup files to exclude
win.exclude.files <- unique(list.files(pck.package, pattern=".*~$", recursive=TRUE))
if(length(win.exclude.files) > 0){
win.exclude.files <- paste0("-x \"", paste(file.path(pck.package, win.exclude.files), collapse="\" \""), "\"")
} else {}
suppressWarnings(zip(win.package, pck.package, extras=win.exclude.files))
message(paste0("repo: created ", pckg.name.win, " (windows)"))
setwd(jmp.back)
write_PACKAGES(dir=repo.win, type="win.binary", verbose=TRUE, latestOnly=FALSE)
message("repo: updated bin/PACKAGES (windows)")
} else {}
## fill macosx repo
if("macosx" %in% actions){
createMissingDir(dirPath=repo.macosx, action="repo")
removeIfExists(filePath=macosx.package)
# since not all tar implementations (especially the BSD default on Mac OS X) support --exclude-vcs,
# we'll exclude these manually
VCS.directories <- c(".svn", "CVS", ".git", "_darcs", ".hg")
VCS.allDirs <- list.dirs(file.path(R.libs,pck.package))
VCS.excludeDirs <- VCS.allDirs[grepl(paste(paste0(".*", VCS.directories, "$"), collapse="|"), VCS.allDirs)]
if(length(VCS.excludeDirs) > 0){
tar.extraFlags <- paste(paste0(" --exclude='", VCS.excludeDirs, "'"), collapse="")
message(paste("repo: excluded these directories from the mac binary:\n ", VCS.excludeDirs, collapse="\n "))
} else {
tar.extraFlags <- ""
}
# failsafe exclusion of backup files
# --exclude=*\\~ caused trouble for path names with tilde
tilde.allFiles <- list.files(file.path(R.libs,pck.package))
tilde.excludeFiles <- tilde.allFiles[grepl(paste(paste0(".*~$"), collapse="|"), tilde.allFiles)]
if(length(tilde.excludeFiles) > 0){
tar.extraFlags <- paste(paste0(" --exclude='", tilde.excludeFiles, "'"), tar.extraFlags, collapse="")
message(paste("repo: excluded these files from the mac binary:\n ", tilde.excludeFiles, collapse="\n "))
} else {}
## TODO: find a solution without sedwd()
jmp.back <- getwd()
setwd(R.libs)
tar(macosx.package, files=pck.package,
tar=Sys.which("tar"),
compression="gzip", extra_flags=paste("-h ", tar.extraFlags))
message(paste0("repo: created ", pckg.name.mac, " (mac OS X)"))
setwd(jmp.back)
write_PACKAGES(dir=repo.macosx, type="mac.binary", verbose=TRUE, latestOnly=FALSE)
message("repo: updated bin/PACKAGES (mac OS X)")
} else {}
## fill debian repo
if("deb" %in% actions){
formals(debianize) <- deb.defaults
debianize()
} else {}
## update HTML index
if("html" %in% actions){
if(!"roxy" %in% actions){
# copy DESCRIPTION to pckg directory
stopifnot(file.copy(file.path(pck.source.dir, "DESCRIPTION"), file.path(repo.pckg.info, "DESCRIPTION"), overwrite=TRUE))
} else {}
# write CSS file, if none is present
css.file <- file.path(repo.pckg.info.main, "web.css")
if(!file_test("-f", css.file)){
cat(rx.css(), file=css.file)
message(paste0("html: created CSS file ", css.file))
} else {}
# copy RSS image, if not present
RSS.image <- file.path(repo.pckg.info.main, "feed-icon-14x14.png")
if(!file_test("-f", RSS.image)){
RSS.local.image <- file.path(roxyPackage.lib.dir(), "images", "feed-icon-14x14.png")
stopifnot(file.copy(RSS.local.image, RSS.image))
message(paste0("html: copied RSS image to ", RSS.image))
} else {}
# check for binaries to link
url.src <- url.win <- url.mac <- url.deb <- url.doc <- url.vgn <- deb.repo <- NULL
if(file_test("-f", repo.src.gz)){
url.src <- pckg.name.src
} else {}
if(file_test("-f", win.package)){
url.win <- pckg.name.win
} else {}
if(file_test("-f", macosx.package)){
url.mac <- pckg.name.mac
} else {}
url.debRepo.info <- NULL
if(file_test("-f", deb.package)){
if(!is.null(URL)){
# generate repository info
url.debRepo.info <- file.path(repo.pckg.info, "deb_repo.html")
cat(debRepoInfo(
URL=URL,
dist=deb.defaults[["distribution"]],
comp=deb.defaults[["component"]],
package=pckg.name.deb.part,
repo=deb.defaults[["origin"]],
gpg.key=deb.defaults[["gpg.key"]],
page.css="../web.css",
package.full=pckg.name.deb,
repo.path=deb.repo.path),
file=url.debRepo.info)
message(paste0("html: updated ", url.debRepo.info))
} else {
message("html: you need to specify 'URL' to generate debian repository information!")
}
} else {}
# check if there is actually any built debian package in the repo
# if not, the link to the debian installation notes will be omitted
deb.built.packages <- dir(file.path(repo.root, deb.repo.path.part), pattern=paste0(pckg.name.deb.part, ".*", ".deb"))
url.deb.repo <- NULL
if(length(deb.built.packages) > 0){
url.deb.repo <- "deb_repo.html"
} else {}
# check for docs to link
pdf.docs <- file.path(repo.pckg.info, pckg.pdf.doc)
pdf.vignette.repo <- file.path("vignettes", list.files(file.path(repo.pckg.info, "vignettes"), pattern="*.pdf|.html", ignore.case=TRUE))
if(file_test("-f", pdf.docs)){
url.doc <- pckg.pdf.doc
} else {}
if(length(pdf.vignette.repo) > 0){
url.vgn <- pdf.vignette.repo
} else {}
# check for NEWS.Rd or NEWS file
if(file_test("-f", pckg.NEWS.Rd)){
roxy.NEWS2HTML(newsRd=pckg.NEWS.Rd, newsHTML=file.path(repo.pckg.info, "NEWS.html"), pckg=pck.package, css="../web.css", R.version=R.Version.full)
url.NEWS <- pckg.NEWS.html
} else if(file_test("-f", pckg.NEWS.inst)){
stopifnot(file.copy(pckg.NEWS.inst, file.path(repo.pckg.info, "NEWS"), overwrite=TRUE))
url.NEWS <- file.path(repo.pckg.info, "NEWS")
} else if(file_test("-f", pckg.NEWS)){
stopifnot(file.copy(pckg.NEWS, file.path(repo.pckg.info, "NEWS"), overwrite=TRUE))
url.NEWS <- file.path(repo.pckg.info, "NEWS")
} else {
url.NEWS <- ""
}
# generate package index file
if(!file_test("-f", pckg.NEWS.rss)){
RSS.file.name <- NULL
} else {}
package.html <- roxy.html(pckg.dscrptn, index=FALSE, css="web.css", R.version=R.Version.win,
url.src=url.src, url.win=url.win, url.mac=url.mac, url.doc=url.doc, url.vgn=url.vgn,
url.deb.repo=url.deb.repo,
title=html.title, cite=pckg.cite.file.html, news=url.NEWS,
changelog=pckg.changelog, rss.file=RSS.file.name)
target.file.pckg <- file.path(repo.pckg.info, "index.html")
cat(package.html, file=target.file.pckg)
message(paste0("html: updated ", target.file.pckg))
# now generate the global index file by scanning for DESCRIPTIONs in all pckg folders
all.descs <- list()
for (this.elmt in file.path(repo.pckg.info.main, dir(repo.pckg.info.main))){
desc.path <- file.path(this.elmt, "DESCRIPTION")
if(file_test("-f", desc.path)){
# ok, there a DESCRIPTION file
all.descs[[length(all.descs) + 1]] <- read.dcf(desc.path)
} else {}
}
target.file.pckg <- file.path(repo.pckg.info.main, "index.html")
pckg.index.html <- roxy.html(all.descs, index=TRUE, css="web.css", title=html.index)
cat(pckg.index.html, file=target.file.pckg)
message(paste0("html: updated pckg index ", target.file.pckg))
target.file.glob <- file.path(repo.root, "index.html")
global.html <- roxy.html(all.descs, index=TRUE, css="web.css", title=html.index, redirect="pckg/")
cat(global.html, file=target.file.glob)
message(paste0("html: updated global index ", target.file.glob))
} else {}
return(invisible(NULL))
} ## end function roxy.package()
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.