#![deny(missing_docs)]
#[cfg(target_os = "windows")]
extern crate cc;
#[cfg(feature = "archive")]
extern crate bzip2;
#[cfg(feature = "archive")]
extern crate tar;
#[cfg(feature = "download")]
extern crate dirs;
#[cfg(feature = "download")]
extern crate ureq;
#[cfg(feature = "memchr")]
extern crate memchr;
use std::ffi::OsStr;
use std::fmt::{self, Display};
use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use std::string::FromUtf8Error;
#[cfg(feature = "archive")]
mod archive;
#[cfg(feature = "archive")]
pub use archive::Archive;
mod link;
mod util;
pub mod src;
pub mod version;
use version::RubyVersionError;
#[doc(inline)]
pub use self::{
link::*,
src::RubySrc,
version::Version,
};
#[derive(Debug)]
pub struct Ruby {
version: Version,
out_dir: PathBuf,
lib_dir: PathBuf,
bin_path: PathBuf,
}
impl Ruby {
#[inline]
fn bin_name() -> &'static str {
let bin = "ruby.exe";
if cfg!(target_os = "windows") {
bin
} else {
unsafe { bin.get_unchecked(..4) }
}
}
#[inline]
pub fn src<P: AsRef<Path> + ?Sized>(path: &P) -> &RubySrc {
RubySrc::new(path)
}
#[inline]
pub fn new(
version: Version,
out_dir: impl Into<PathBuf>,
) -> Ruby {
let out_dir = out_dir.into();
let lib_dir = out_dir.join("lib");
let bin_path = out_dir.join("bin").join(Self::bin_name());
Ruby { version, out_dir, lib_dir, bin_path }
}
#[inline]
pub fn current() -> Result<Ruby, RubyVersionError> {
Self::from_bin(Self::bin_name())
}
#[inline]
pub fn from_bin(ruby: impl AsRef<OsStr>) -> Result<Ruby, RubyVersionError> {
Self::from_cmd(&mut Command::new(ruby))
}
#[inline]
pub fn from_cmd(ruby: &mut Command) -> Result<Ruby, RubyVersionError> {
Ruby::from_path(RubyExecError::process(
ruby.args(&["-e", "print RbConfig::CONFIG['prefix']"])
)?)
}
#[inline]
pub fn from_path(out_dir: impl Into<PathBuf>) -> Result<Ruby, RubyVersionError> {
let mut ruby = Ruby::new(Version::new(0, 0, 0), out_dir);
ruby.version = Version::from_bin(&ruby.bin_path)?;
Ok(ruby)
}
#[inline]
pub fn from_rvm(version: &Version) -> Result<Ruby, RubyVersionError> {
Ruby::from_cmd(Command::new("rvm")
.arg(version.to_string())
.arg("do")
.arg("ruby"))
}
#[inline]
pub fn from_rbenv(version: &Version) -> Result<Ruby, RubyVersionError> {
Ruby::from_cmd(Command::new("rbenv")
.env("RBENV_VERSION", version.to_string())
.arg("exec")
.arg("ruby"))
}
#[inline]
pub fn version(&self) -> &Version {
&self.version
}
pub fn full_version(&self) -> Result<String, RubyExecError> {
self.exec(Some("-v"))
}
#[inline]
pub fn out_dir(&self) -> &Path {
&self.out_dir
}
#[inline]
pub fn lib_dir(&self) -> &Path {
&self.lib_dir
}
#[inline]
pub fn bin_path(&self) -> &Path {
&self.bin_path
}
pub fn exec<I, S>(&self, args: I) -> Result<String, RubyExecError>
where
I: IntoIterator<Item=S>,
S: AsRef<OsStr>,
{
RubyExecError::process(Command::new(&self.bin_path).args(args))
}
pub fn run(&self, script: impl AsRef<OsStr>) -> Result<String, RubyExecError> {
self.exec(&["-e".as_ref(), script.as_ref()])
}
pub fn run_multiple<I, S>(&self, scripts: I) -> Result<String, RubyExecError>
where
I: IntoIterator<Item=S>,
S: AsRef<OsStr>,
{
let mut command = Command::new(&self.bin_path);
for script in scripts {
command.arg("-e");
command.arg(script);
}
RubyExecError::process(&mut command)
}
fn _get_config(&self, key: &dyn Display) -> Result<String, RubyExecError> {
self.run(&format!("print RbConfig::CONFIG['{}']", key))
}
#[inline]
pub fn get_config(&self, key: impl Display) -> Result<String, RubyExecError> {
self._get_config(&key)
}
#[inline]
pub fn include_dir(&self) -> Result<String, RubyExecError> {
self.get_config("includedir")
}
#[inline]
pub fn header_dir(&self) -> Result<String, RubyExecError> {
self.get_config("rubyhdrdir")
}
#[inline]
pub fn arch_header_dir(&self) -> Result<String, RubyExecError> {
self.get_config("rubyarchhdrdir")
}
#[inline]
pub fn lib_name(&self, static_lib: bool) -> Result<String, RubyExecError> {
let mut name = self.get_config("RUBY_SO_NAME")?;
if static_lib {
name.push_str("-static");
}
Ok(name)
}
#[inline]
pub fn lib_args(&self) -> Result<String, RubyExecError> {
self.get_config("LIBRUBYARG")
}
#[inline]
pub fn libs(&self) -> Result<String, RubyExecError> {
self.get_config("LIBS")
}
#[inline]
pub fn main_libs(&self) -> Result<String, RubyExecError> {
self.get_config("MAINLIBS")
}
#[inline]
pub fn so_libs(&self) -> Result<String, RubyExecError> {
self.get_config("SOLIBS")
}
#[inline]
pub fn aux_libs(&self, static_lib: bool) -> Result<String, RubyExecError> {
if static_lib {
self.main_libs()
} else {
self.libs()
}
}
pub fn link(&self, static_lib: bool) -> Result<(), RubyLinkError> {
link::link(self, static_lib)
}
pub fn with_headers<F: FnMut(PathBuf)>(&self, mut f: F) -> io::Result<()> {
util::walk_files(self.include_dir()?.as_ref(), |path| {
if path.extension() == Some("h".as_ref()) {
f(path);
}
Ok(())
})
}
pub fn headers(&self) -> io::Result<Vec<PathBuf>> {
let mut headers = Vec::new();
self.with_headers(|header| headers.push(header))?;
Ok(headers)
}
pub fn wrapper_header(&self) -> io::Result<String> {
let arch_header_dir = self.arch_header_dir()?;
self.wrapper_header_filtered(|path| {
!path.starts_with(&arch_header_dir)
})
}
#[inline]
pub fn wrapper_header_filtered<F>(&self, mut f: F) -> io::Result<String>
where F: FnMut(&Path) -> bool,
{
self._wrapper_header_filtered(&mut f)
}
fn _wrapper_header_filtered(
&self,
f: &mut dyn FnMut(&Path) -> bool,
) -> io::Result<String> {
let header_dir = self.header_dir()?;
let header_dir = Path::new(&header_dir);
let mut buf = String::new();
fn write_header(buf: &mut String, header: impl Display) -> io::Result<()> {
use io::Write;
let buf = unsafe { buf.as_mut_vec() };
writeln!(buf, "#include <{}>", header)
}
util::walk_files(&header_dir, |path| {
if path.extension() != Some("h".as_ref()) || !f(&path) {
return Ok(());
}
match path.strip_prefix(header_dir) {
Ok(header) => {
if cfg!(target_os = "windows") {
let header = header
.to_string_lossy()
.as_ref()
.replace('\\', "/");
write_header(&mut buf, header)?;
} else {
write_header(&mut buf, header.display())?;
}
Ok(())
},
Err(error) => {
Err(io::Error::new(io::ErrorKind::Other, error))
},
}
})?;
Ok(buf)
}
}
#[derive(Debug)]
pub enum RubyExecError {
ExecFail(io::Error),
RunFail(Output),
Utf8Error(FromUtf8Error),
}
impl std::error::Error for RubyExecError {}
impl Display for RubyExecError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RubyExecError::ExecFail(error) => error.fmt(f),
RubyExecError::RunFail(_) => {
write!(f, "Failed to execute `ruby`")
},
RubyExecError::Utf8Error(error) => error.fmt(f),
}
}
}
impl From<io::Error> for RubyExecError {
#[inline]
fn from(error: io::Error) -> Self {
RubyExecError::ExecFail(error)
}
}
impl From<RubyExecError> for io::Error {
#[inline]
fn from(error: RubyExecError) -> Self {
match error {
RubyExecError::ExecFail(error) => error,
error => io::Error::new(io::ErrorKind::Other, error)
}
}
}
impl From<FromUtf8Error> for RubyExecError {
#[inline]
fn from(error: FromUtf8Error) -> Self {
RubyExecError::Utf8Error(error)
}
}
impl RubyExecError {
pub(crate) fn process(command: &mut Command) -> Result<String, Self> {
let output = command.output()?;
if output.status.success() {
Ok(String::from_utf8(output.stdout)?)
} else {
Err(RubyExecError::RunFail(output))
}
}
}