Skip to content

Commit

Permalink
Merge pull request #97 from posit-dev/feature/ark-version-display
Browse files Browse the repository at this point in the history
Add a way to see the Ark version from Positron
  • Loading branch information
jmcphers authored Sep 21, 2023
2 parents 8fcd87e + 1a0b7ba commit 0751e0c
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/ark/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ark"
version = "0.1.4"
version = "0.1.5"
edition = "2021"
rust-version = "1.70.0"
description = """
Expand Down Expand Up @@ -46,6 +46,9 @@ uuid = "1.3.0"
walkdir = "2"
yaml-rust = "0.4.5"

[build-dependencies]
chrono = "0.4.23"

# The libR-sys package doesn't currently provide bindings for R on aarch64
# Linux platforms, so on those systems we need to add the use-bingen feature,
# which causes the package to build its own bindings from scratch.
Expand Down
26 changes: 26 additions & 0 deletions crates/ark/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// build.rs
//
// Copyright (C) 2023 Posit Software, PBC. All rights reserved.
//
//

use std::process::Command;

fn main() {
// Attempt to use `git rev-parse HEAD` to get the current git hash. If this
// fails, we'll just use the string "<unknown>" to indicate that the git hash
// could not be determined..
let git_hash = match Command::new("git")
.args(&["rev-parse", "--short", "HEAD"])
.output()
{
Ok(output) => String::from_utf8(output.stdout).unwrap(),
Err(_) => String::from("<unknown>"),
};
println!("cargo:rustc-env=BUILD_GIT_HASH={}", git_hash);

// Get the build date as a string
let build_date = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
println!("cargo:rustc-env=BUILD_DATE={}", build_date);
}
4 changes: 4 additions & 0 deletions crates/ark/src/modules/public/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@
}

}

.ps.ark.version <- function() {
.ps.Call("ps_ark_version")
}
18 changes: 18 additions & 0 deletions crates/ark/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
//
//

use std::collections::HashMap;
use std::process::Command;

use anyhow::Context;
use harp::object::RObject;
use itertools::Itertools;
use libR_sys::*;

pub struct RVersion {
// Major version of the R installation
Expand Down Expand Up @@ -63,3 +66,18 @@ pub fn detect_r() -> anyhow::Result<RVersion> {
anyhow::bail!("Failed to extract R version");
}
}

#[harp::register]
pub unsafe extern "C" fn ps_ark_version() -> SEXP {
let mut info = HashMap::<String, String>::new();
// Set the version info in the map
info.insert(
String::from("version"),
String::from(env!("CARGO_PKG_VERSION")),
);
info.insert(String::from("commit"), String::from(env!("BUILD_GIT_HASH")));
info.insert(String::from("date"), String::from(env!("BUILD_DATE")));

let result = RObject::from(info);
result.sexp
}
66 changes: 66 additions & 0 deletions crates/harp/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ fn r_size(x: SEXP) -> usize {
}
}

fn r_length(x: SEXP) -> isize {
unsafe { Rf_xlength(x) }
}

impl RObject {
pub unsafe fn new(data: SEXP) -> Self {
RObject {
Expand Down Expand Up @@ -183,6 +187,10 @@ impl RObject {
pub fn size(&self) -> usize {
r_size(self.sexp)
}

pub fn length(&self) -> isize {
r_length(self.sexp)
}
}

impl Clone for RObject {
Expand Down Expand Up @@ -285,6 +293,41 @@ impl From<String> for RObject {
}
}

// Convert a String -> String HashMap into named character vector.
impl From<HashMap<String, String>> for RObject {
fn from(value: HashMap<String, String>) -> Self {
unsafe {
// Allocate the vector of values
let values = Rf_protect(Rf_allocVector(STRSXP, value.len() as isize));

// Allocate the vector of names; this will be protected by attaching
// it to the values vector as an attribute
let names = Rf_allocVector(STRSXP, value.len() as isize);
Rf_setAttrib(values, R_NamesSymbol, names);

// Convert the hashmap to a sorted vector of tuples; we do this so that the
// order of the values and names is deterministic
let mut sorted: Vec<_> = value.into_iter().collect();
sorted.sort_by(|a, b| a.0.cmp(&b.0));

// Loop over the values and names, setting them in the vectors
for (idx, (key, value)) in sorted.iter().enumerate() {
SET_STRING_ELT(
values,
idx as isize,
Rf_mkChar(value.as_ptr() as *mut c_char),
);
SET_STRING_ELT(names, idx as isize, Rf_mkChar(key.as_ptr() as *mut c_char));
}

// Clean up the protect stack and return the RObject from the values
// vector
Rf_unprotect(1);
RObject::new(values)
}
}
}

/// Convert RObject into other types.
impl From<RObject> for SEXP {
Expand Down Expand Up @@ -681,6 +724,29 @@ mod tests {
}
}

#[test]
#[allow(non_snake_case)]
fn test_tryfrom_RObject_hashmap_string() {
r_test! {
// Create a map of pizza toppings to their acceptability.
let mut map = HashMap::<String, String>::new();
map.insert(String::from("pepperoni"), String::from("OK"));
map.insert(String::from("sausage"), String::from("OK"));
map.insert(String::from("pineapple"), String::from("NOT OK"));
let len = map.len();

// Ensure we created an object of the same size as the map.
let robj = RObject::from(map);
assert_eq!(robj.length(), len as isize);

// Ensure we can convert the object back into a map with the same values.
let out: HashMap<String, String> = robj.try_into().unwrap();
assert_eq!(out.get("pepperoni").unwrap(), "OK");
assert_eq!(out.get("sausage").unwrap(), "OK");
assert_eq!(out.get("pineapple").unwrap(), "NOT OK");
}
}

#[test]
#[allow(non_snake_case)]
fn test_tryfrom_RObject_Vec_Option_String() {
Expand Down

0 comments on commit 0751e0c

Please sign in to comment.