Packages

A lightweight Elixir interface to CmdStan, the command line interface to the Stan probabilistic programming language.

Current section

Files

Jump to
cmdstanex lib cmdstan installer.ex
Raw

lib/cmdstan/installer.ex

defmodule CmdStan.Installer do
@moduledoc """
Main installer for CmdStan.
Orchestrates the download, extraction, building, and testing of CmdStan installations.
"""
alias CmdStan.{Config, Platform, Downloader, Builder}
require Logger
@doc """
Install CmdStan with the given configuration.
This is the main entry point that handles the complete installation process:
1. Validate configuration and directory
2. Get the version to install (latest if not specified)
3. Download or clone the CmdStan release
4. Extract the archive
5. Build CmdStan
6. Test the compilation
7. Set the CmdStan path
## Examples
iex> CmdStan.Installer.install_cmdstan(version: "2.32.2")
:ok
iex> CmdStan.Installer.install_cmdstan(dir: "/opt/cmdstan", progress: true, verbose: true)
:ok
"""
@spec install_cmdstan(keyword()) :: :ok | {:error, term()}
def install_cmdstan(opts \\ []) do
config = Config.new(opts)
try do
Logger.info("Starting CmdStan installation")
# Validate installation directory
Platform.validate_dir(config.dir)
Logger.info("Installation directory: #{config.dir}")
# Get version to install
version = config.version || get_version_to_install(config)
Logger.info("Installing CmdStan version: #{version}")
# Check if already installed
cmdstan_dir = Path.join(config.dir, cmdstan_folder_name(version))
if already_installed?(cmdstan_dir, config.overwrite) do
Logger.info("CmdStan version #{version} already installed")
set_cmdstan_path(config.dir, version)
:ok
else
# Download and install
with {:ok, extracted_dir} <- download_and_extract(version, config),
:ok <- build_and_test(extracted_dir, config) do
set_cmdstan_path(config.dir, version)
Logger.info("CmdStan installation completed successfully")
:ok
end
end
catch
error ->
Logger.error("CmdStan installation failed: #{inspect(error)}")
{:error, error}
end
end
# Private functions
defp get_version_to_install(config) do
case Downloader.get_latest_version(config.github_token) do
{:ok, version} ->
Logger.info("Using latest version: #{version}")
version
{:error, reason} ->
raise "Failed to get latest version: #{inspect(reason)}"
end
end
defp cmdstan_folder_name(version) do
if String.starts_with?(version, "git:") do
tag = String.replace_prefix(version, "git:", "")
"cmdstan-#{String.replace(tag, "/", "_")}"
else
"cmdstan-#{version}"
end
end
defp already_installed?(cmdstan_dir, overwrite) do
if overwrite do
false
else
File.exists?(cmdstan_dir) &&
bernoulli_executable_exists?(cmdstan_dir)
end
end
defp bernoulli_executable_exists?(cmdstan_dir) do
exe_path =
Path.join([cmdstan_dir, "examples", "bernoulli", "bernoulli#{Platform.extension()}"])
File.exists?(exe_path)
end
defp download_and_extract(version, config) do
cmdstan_dir = Path.join(config.dir, cmdstan_folder_name(version))
# Remove existing directory if it exists
if File.exists?(cmdstan_dir) do
Logger.info("Removing existing installation: #{cmdstan_dir}")
File.rm_rf!(cmdstan_dir)
end
if String.starts_with?(version, "git:") do
# Clone from git
tag = String.replace_prefix(version, "git:", "")
case Downloader.clone_git(tag, cmdstan_dir) do
:ok -> {:ok, cmdstan_dir}
{:error, reason} -> {:error, reason}
end
else
# Download tar.gz and extract
case Downloader.download_release(version, config) do
{:ok, tar_file} ->
Logger.info("Extracting #{tar_file} to #{config.dir}")
case extract_tar_gz(tar_file, config.dir) do
:ok ->
# Clean up downloaded file
File.rm!(tar_file)
{:ok, cmdstan_dir}
{:error, reason} ->
{:error, {:extraction_failed, reason}}
end
{:error, reason} ->
{:error, {:download_failed, reason}}
end
end
end
defp extract_tar_gz(tar_file, dest_dir) do
# Use erl_tar for extraction
case :erl_tar.extract(String.to_charlist(tar_file), [
:compressed,
{:cwd, String.to_charlist(dest_dir)}
]) do
:ok -> :ok
{:error, reason} -> {:error, reason}
end
end
defp build_and_test(cmdstan_dir, config) do
with :ok <- Builder.build_cmdstan(cmdstan_dir, config),
:ok <- Builder.test_compilation(cmdstan_dir, config) do
:ok
end
end
defp set_cmdstan_path(install_dir, version) do
folder_name = cmdstan_folder_name(version)
cmdstan_path = Path.join(install_dir, folder_name)
# Set environment variable
System.put_env("CMDSTAN", cmdstan_path)
Logger.info("CmdStan path set to: #{cmdstan_path}")
end
end