Current section
Files
Jump to
Current section
Files
lib/cmdstan/platform.ex
defmodule CmdStan.Platform do
@moduledoc """
Cross-platform utilities for CmdStan operations.
Provides platform detection, file extensions, and system-specific operations.
"""
@type os_type :: :linux | :macos | :windows | :unknown
@doc """
Detect the current operating system.
## Examples
iex> CmdStan.Platform.os()
:linux
# or :macos, :windows, :unknown
"""
@spec os() :: os_type()
def os do
case :os.type() do
{:unix, :darwin} -> :macos
{:unix, :linux} -> :linux
{:win32, _} -> :windows
_ -> :unknown
end
end
@doc """
Get the executable file extension for the current platform.
## Examples
iex> CmdStan.Platform.extension()
"" # on Unix-like systems
# On Windows:
# ".exe"
"""
@spec extension() :: String.t()
def extension do
case os() do
:windows -> ".exe"
_ -> ""
end
end
@doc """
Get the make command for the current platform.
## Examples
iex> CmdStan.Platform.make_command()
"make" # on Unix-like systems
# On Windows:
# "mingw32-make"
"""
@spec make_command() :: String.t()
def make_command do
case os() do
:windows -> "mingw32-make"
_ -> "make"
end
end
@doc """
Validate that a directory exists and is writable.
Creates the directory if it doesn't exist. Raises an error if it's not writable.
## Examples
iex> CmdStan.Platform.validate_dir("/tmp/cmdstan")
:ok
iex> CmdStan.Platform.validate_dir("/nonexistent/path")
:ok # creates the directory
iex> CmdStan.Platform.validate_dir("/root/protected")
** (RuntimeError) Cannot write to directory /root/protected: eacces
"""
@spec validate_dir(String.t()) :: :ok | no_return()
def validate_dir(dir) do
# Create directory if it doesn't exist
unless File.exists?(dir) do
case File.mkdir_p(dir) do
:ok ->
:ok
{:error, reason} ->
raise "Cannot create directory #{dir}: #{reason}"
end
end
# Check if it's actually a directory
unless File.dir?(dir) do
raise "Path exists but is not a directory: #{dir}"
end
# Test write permissions
test_file = Path.join(dir, ".cmdstan_write_test")
case File.write(test_file, "test") do
:ok ->
File.rm!(test_file)
:ok
{:error, reason} ->
raise "Cannot write to directory #{dir}: #{reason}"
end
end
@doc """
Determine the Linux architecture for downloading the correct CmdStan binary.
Returns the architecture string or nil for universal binaries.
"""
@spec linux_arch() :: String.t() | nil
def linux_arch do
case os() do
:linux ->
machine = :erlang.system_info(:system_architecture) |> to_string()
cond do
String.contains?(machine, "aarch64") ->
"arm64"
String.contains?(machine, "armv7") ->
"armhf"
String.contains?(machine, "ppc64el") or String.contains?(machine, "ppc64le") ->
"ppc64el"
String.contains?(machine, "s390x") ->
"s390x"
true ->
nil
end
_ ->
nil
end
end
@doc """
Get the CPU architecture string for CmdStan downloads.
"""
@spec arch_string() :: String.t() | nil
def arch_string do
case os() do
:linux -> linux_arch()
_ -> nil
end
end
@doc """
Get environment variable for overriding the architecture.
"""
@spec env_arch() :: String.t() | nil
def env_arch do
case System.get_env("CMDSTAN_ARCH") do
nil -> nil
"false" -> nil
arch -> arch
end
end
@doc """
Get the final architecture string to use for downloads.
"""
@spec final_arch() :: String.t() | nil
def final_arch do
env_arch() || arch_string()
end
@doc """
Get the path to the CmdStan installation directory.
"""
@spec cmdstan_path() :: String.t() | nil
def cmdstan_path do
# Check environment variable first
case System.get_env("CMDSTAN") do
nil ->
# Try default location
default_path = Path.expand("~/.cmdstan")
if File.exists?(default_path) do
# Find the latest version
case File.ls(default_path) do
{:ok, entries} ->
cmdstan_dirs = Enum.filter(entries, &String.starts_with?(&1, "cmdstan-"))
if Enum.empty?(cmdstan_dirs) do
nil
else
latest = Enum.max(cmdstan_dirs)
Path.join(default_path, latest)
end
_ ->
nil
end
else
nil
end
path ->
path
end
end
end