RInno:

Full Stack Data Science Meetup

Jonathan Hill

May 2, 2017

This is not a Rhino

Ce n’est pas un rhinocéros

R + Shiny + Inno Setup = RInno

An installation framework for Shiny apps

Three Questions

Why R?

  • Open source, object oriented, data science language with 11,000+ libraries
  • Great APIs to big data tools like Apache Spark, Microsoft R Server, H20, Big Memory and other high-performance computing platforms
  • Mature C++ (Rcpp), Java (rJava), and JavaScript (V8) engines
  • Support for all major databases including more specialized databases like Neo4j

Why Shiny?

  • R programmers can easily get started without any front-end development skills
  • Modern, mobile-friendly, bootstrap style
  • Shiny exposes the full functionality of R to the entire enterprise through its user interface
  • Simple by default, convention over configuration
    • Decreases the number of decisions a developer is required to make without losing flexibility

Desktop Shiny Apps

  • In order to prototype and build demand for server applications, it can be very useful to install Shiny apps on desktop computers.
  • Some organizations may never want to invest in a server deployment because of security concerns or a lack of resources
  • Therefore, it is important for the local installation to be low maintenance in the short- and long-term

Why Inno Setup?

A script-driven installation system written in Delphi Pascal by Jordan Russell

[Setup]
AppName=My Program
DefaultDirName={pf}\My Program
Compression=lzma2

[Files]
Source: "MyProg.exe"; DestDir: "{app}"

[Icons]
Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
  • Everyone knows how to use software installers and Inno Setup is one of the most mature installers available
  • Why NOT use a third-party UI for Inno Setup?
    • They are like Chinese water torture (not reproducible, scalable, click, click, click, type, tab, type, OMG, Enter)

Getting Started: RInno

How to install Inno Setup

install.packages("RInno")
require(RInno)
RInno::install_inno()

Inno Setup Downloads

Let’s assume you have a shiny app

example_app(app_dir = "app")
## Example: C:/Users/jhill/Documents/Internal Projects/Full Stack Data Science/app

Creating an installer

You can start simple with default settings:

create_app(
  app_name = "RInnoApp", 
  app_dir  = "app")
compile_iss()

Incrementally add one feature at a time:

create_app(
  app_name = "RInnoApp", 
  app_dir  = "app",
  include_R = TRUE)
compile_iss()

Remember those 11,000+ packages?

create_app(
    app_name = "RInnoApp", 
    app_dir  = "app",
    pkgs     = c("shiny", "dplyr", "plotly", "ggplot2", "xkcd"),
    remotes  = c("talgalili/installr", "daattali/shinyjs"))
compile_iss()
  • This will download and install packages from your default repository for pkgs and Github for remotes.

How about version control?

RInnoApp

create_app(
  app_name     = "RInnoApp", 
  app_dir      = "app",
  app_repo_url = "https://github.com/Dripdrop12/RInnoApp")
compile_iss()
  • If you release a new version of this app on GitHub, all of the installed copies of this app will update during their next startup sequence following your release

Extras

How does information persist?

Through a json file called config.cfg

{
  "appname": "RInnoApp",
  "r_bindir": "C:\\Program Files\\R\\R-3.3.3\\bin",
  "pkgs": {
    "pkgs": ["jsonlite", "shiny", "magrittr", "httr"],
    "cran": {
      "repo": "http://cran.rstudio.com"
    }
  },
  "logging": {
    "error_log": "error.log"
  },
  "host": "github",
  "app_repo": "Dripdrop12/RInnoApp",
  "auth_user": {
    "auth_user": "none"
  },
  "auth_pw": {
    "auth_pw": "none"
  },
  "remotes": {
    "remotes": "none"
  },
  "user_browser": "chrome"
}

Reading config.cfg

… with JScript:

var oFSO = WScript.CreateObject("Scripting.FileSystemObject");
var oShell = WScript.CreateObject("WScript.Shell");

var fConfig = oFSO.OpenTextFile('utils\\config.cfg', 1);

… with R:

# Load config.cfg
config <- jsonlite::fromJSON(file.path(appwd, "utils/config.cfg"))

# Package dependencies
pkgs   <- config$pkgs$pkgs; remotes <- config$remotes

# Custom messaging with your appname
title = sprintf("Starting %s ...", config$appname)

Continuous Integration

# If you provided an app_repo, RInno uses a different startup sequence
if (config$app_repo != "none") {

  # Which involves an API call to your app_repo
  api_response <- get_remote_version(
    app_repo  = config$app_repo,
    host      = config$host,
    auth_user = config$auth_user,
    auth_pw   = config$auth_pw)
  
  # A local version check
  local_version <- packageVersion(config$appname)

  # And a conditional statement like this
  if (local_version != api_response) {
    
    if (config$host == "bitbucket") {
      devtools::install_bitbucket(config$app_repo)
      
    } else if (config$host == "github") {
      devtools::install_github(config$app_repo)
      
    } else {
      "Host error message"
    }
  }
}

Registry Check

This Delphi Pascal script is run during installation to determine if each user needs R. It only provides a copy if R is not detected on the machine

[Code]
# Is R installed?
function RDetected(): boolean;
var
    success: boolean;
begin
  success := RegKeyExists(HKLM, 'Software\R-Core\R\{#RVersion}');
  begin
    Result := success;
  end;
end;

# If R is not detected, it is needed
function RNeeded(): Boolean;
begin
  Result := (RDetected = false);
end;