MetaCall Runtime Environment Profile (MREP)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Feature
- Clarity
- Mostly clear
- Activity status
- Quiet
Research direction
Start by reading loader.json.in, global.json, and PR #734 to understand the existing configuration and Python initialization paths. Then inspect install.ps1 and the packaging/profile entry points described in the RFC. Done means the MREP format, discovery and validation behavior, shell integration, and compatibility layers are implemented across the supported platforms.
Written by the indexing model from the issue text.
Description
Status
RFC 0001
Drafted "v0.0.2"
Related PRs:
#734
Abstract
This RFC proposes a standardized mechanism for defining and propagating runtime environment configuration across MetaCall loaders and ports. The mechanism introduces a declarative environment profile format that centralizes runtime home paths, search paths, and initialization requirements, replacing the current pattern of duplicative platform-specific code in each loader and port implementation.
Motivation
The Problem
The current MetaCall architecture requires each loader and port to implement its own platform-specific initialization logic. Consider the changes introduced in PR #734 to fix Windows Python initialization and mainly it's a repeated problem in CIs and Infrastructure:
- py_loader adds
py_loader_impl_initialize_python_home()to setPYTHONHOMEbeforePy_InitializeEx() - rs_port adds
ensure_python_home()as a fallback for backward compatibility
This pattern creates several issues:
- Code duplication: Each loader must reimplement the same initialization patterns for its target runtime
- Platform-specific pollution:
#if defined(WIN32)blocks scattered across the codebase - Maintenance burden: When runtime initialization logic changes, it must be updated in multiple places
- Inconsistent behavior: Different loaders may handle the same initialization scenarios differently
The Opportunity
Other polyglot runtimes and version managers have solved similar problems through declarative profile systems:
- SDKMAN uses
~/.sdkman/etc/configand~/.sdkman/candidates/layout - Conda uses
environment.ymlwith explicit channel and dependency declarations - GraalVM uses
META-INF/native-image/with structured metadata - asdf uses
.tool-versionsfor project-level runtime specification
MetaCall already has a configuration system (loader.json.in, global.json). The opportunity is to extend this with a dedicated runtime environment profile that:
- Declares runtime requirements per language
- Provides platform-aware path resolution
- Enables automatic environment variable propagation
- Supports both system-wide and per-project profiles
Delimma
A developer installs MetaCall on a fresh Linux system. They want to run Python and Node scripts that call each other. With the current system, they must:
- Manually set
LOADER_LIBRARY_PATH - Manually set
SERIAL_LIBRARY_PATH - Manually configure
PYTHONHOMEandNODE_PATH - Ensure these variables are set consistently across all shell sessions
With the proposed system, they run:
source /etc/profile.d/metacall.sh
And the profile script automatically sets all necessary variables based on the installed runtime configuration.
Specification
1. Terminology
| Term | Definition |
|---|---|
| MREP | MetaCall Runtime Environment Profile, the declarative configuration format |
| Runtime Profile | A JSON file containing MREP declarations |
| Runtime Home | The base directory containing a language runtime installation |
| Profile Directory | The directory containing runtime profiles and associated metadata |
| Port | A language binding that loads the MetaCall runtime (Rust, Go, Java, C#, etc.) |
| Loader | A plugin that embeds a language runtime (Python, Node, Ruby, etc.) |
2. Profile Format
The MREP format uses JSON with the following schema:
{
"$schema": "https://metacall.io/schemas/mrep-v1.json",
"meta": {
"version": "1.0.0",
"schema": "https://metacall.io/schemas/mrep-v1.json",
"generated": "2024-01-15T10:30:00Z"
},
"runtimes": {
"<language>": {
"home": "<absolute-path-or-variable-reference>",
"search_paths": ["<path>", "<path>"],
"environment": {
"<VAR_NAME>": "<value>"
},
"options": {
"<option>": "<value>"
}
}
},
"defaults": {
"loaders_path": "<path>",
"scripts_path": "<path>",
"config_path": "<path>"
}
}
2.1 Field Definitions
| Field | Type | Description |
|---|---|---|
meta.version |
string | MREP format version (semver) |
meta.schema |
string | JSON Schema URI for validation |
meta.generated |
string | ISO 8601 timestamp of profile generation |
runtimes.<language>.home |
string | Runtime home directory or variable reference |
runtimes.<language>.search_paths |
array | Platform-specific library search paths |
runtimes.<language>.environment |
object | Environment variables to set before initialization |
runtimes.<language>.options |
object | Loader-specific configuration options |
defaults.loaders_path |
string | Default directory for loader plugins |
defaults.scripts_path |
string | Default directory for scripts |
defaults.config_path |
string | Default path to global configuration |
2.2 Variable References
The home field supports variable references for flexible path resolution:
| Reference | Expansion | Example |
|---|---|---|
$METACALL_PREFIX |
Installation prefix | /usr/local |
$METACALL_HOME |
Profile directory parent | /etc/metacall |
$ORIGIN |
Loader binary directory | Platform-specific |
<os-family> |
Platform-specific path | Windows: %LOCALAPPDATA%\MetaCall |
2.3 Example Profiles
Linux System Installation:
{
"meta": {
"version": "1.0.0",
"schema": "https://metacall.io/schemas/mrep-v1.json",
"generated": "2024-01-15T10:30:00Z"
},
"runtimes": {
"python": {
"home": "$METACALL_PREFIX/lib/metacall/runtimes/python",
"search_paths": [
"$METACALL_PREFIX/lib",
"$METACALL_PREFIX/lib/metacall/runtimes/python/lib"
],
"environment": {
"PYTHONIOENCODING": "utf-8",
"PYTHONDONTWRITEBYTECODE": "1"
}
},
"ruby": {
"home": "$METACALL_PREFIX/lib/metacall/runtimes/ruby",
"search_paths": [
"$METACALL_PREFIX/lib",
"$METACALL_PREFIX/lib/metacall/runtimes/ruby/lib"
]
},
"node": {
"home": "$METACALL_PREFIX/lib/metacall/runtimes/node",
"search_paths": [
"$METACALL_PREFIX/lib"
]
}
},
"defaults": {
"loaders_path": "$METACALL_PREFIX/lib/metacall/loaders",
"scripts_path": "$METACALL_PREFIX/share/metacall/scripts",
"config_path": "$METACALL_PREFIX/etc/metacall/global.json"
}
}
Windows Installation:
{
"meta": {
"version": "1.0.0",
"schema": "https://metacall.io/schemas/mrep-v1.json",
"generated": "2024-01-15T10:30:00Z"
},
"runtimes": {
"python": {
"home": "%LOCALAPPDATA%\\MetaCall\\runtimes\\python",
"search_paths": [
"%LOCALAPPDATA%\\MetaCall\\lib",
"%LOCALAPPDATA%\\MetaCall\\runtimes\\python"
],
"environment": {
"PYTHONHOME": "%LOCALAPPDATA%\\MetaCall\\runtimes\\python",
"PYTHONIOENCODING": "utf-8"
}
}
},
"defaults": {
"loaders_path": "%LOCALAPPDATA%\\MetaCall\\loaders",
"scripts_path": "%LOCALAPPDATA%\\MetaCall\\scripts",
"config_path": "%LOCALAPPDATA%\\MetaCall\\config\\global.json"
}
}
2.4 Python Path Matrix
The table below captures the current Python-related paths used by MetaCall as requested.
| Install method | MetaCall library discovery (Python port) | Python runtime root | Python module path / PYTHONPATH |
MetaCall configuration path | Loader paths / env profile |
|---|---|---|---|---|---|
| Linux distributable tarball (Guix-based) | /gnu/lib/libmetacall(.so) (also searched by Python/Node/Ruby ports via /gnu/lib) |
Guix Python runtime in /gnu/store/..., exposed through /gnu profile |
PYTHONPATH=/gnu/lib/python<major.minor>/site-packages (from /gnu/etc/profile); installer wrapper also appends lib-dynload |
CONFIGURATION_PATH=/gnu/configurations/global.json |
LOADER_LIBRARY_PATH=/gnu/lib, SERIAL_LIBRARY_PATH=/gnu/lib, DETOUR_LIBRARY_PATH=/gnu/lib, PORT_LIBRARY_PATH=/gnu/lib |
macOS Homebrew formula (metacall/homebrew) |
/opt/homebrew/lib/libmetacall(.dylib) (arm64) or /usr/local/lib/libmetacall(.dylib) (intel) |
Homebrew Python formula ([email protected]) via python.opt_* (no fixed PYTHONHOME) |
Wrapper exports PYTHONPATH=${PREFIX}/lib/python:${PYTHONPATH:-}; formula tests also use ${HOMEBREW_PREFIX}/lib/python |
Wrapper exports CONFIGURATION_PATH=${PREFIX}/configurations/global.json |
Wrapper exports SERIAL_LIBRARY_PATH, DETOUR_LIBRARY_PATH, PORT_LIBRARY_PATH to ${PREFIX}/lib (note: current formula uses LOADER_LIBRARY, not LOADER_LIBRARY_PATH) |
Linux DEB/RPM package from core releases (apt/dpkg) |
Default package build currently targets /usr/local/lib/libmetacall(.so) |
System Python from package dependencies (not a bundled runtime folder) | No package-managed PYTHONPATH export by default; relies on system Python layout |
Build/install default resolves to /usr/local/share/metacall/configurations/global.json (or configured prefix equivalent) |
Compile-time defaults point loader/serial/detour/port install paths to <prefix>/lib |
Windows distributable installer script (install.ps1 + zip asset) |
Port default search root is %LOCALAPPDATA%\MetaCall\metacall and recursively finds metacall(.dll) (currently stored under ...\lib) |
%LOCALAPPDATA%\MetaCall\metacall\runtimes\python (PYTHONHOME) |
PIP_TARGET=%LOCALAPPDATA%\MetaCall\metacall\runtimes\python\Lib\site-packages; runtime PATH includes Python/Scripts/site-packages bin |
CONFIGURATION_PATH=%LOCALAPPDATA%\MetaCall\metacall\configurations\global.json |
LOADER_LIBRARY_PATH, SERIAL_LIBRARY_PATH, DETOUR_LIBRARY_PATH, PORT_LIBRARY_PATH all exported to %LOCALAPPDATA%\MetaCall\metacall\lib |
Windows installer note: current core packaging generators are NSIS;ZIP (not MSI). The Windows one-line installer consumes metacall-tarball-win-<arch>.zip assets from metacall/distributable-windows.
Implication for MREP abstraction: Python path resolution needs at least three explicit layers:
- User override layer (
METACALL_INSTALL_PATH,PYTHONHOME, etc.) - Installer/profile layer (distributable wrappers or generated shell/profile scripts)
- Platform fallback layer (compiled install prefix + known platform discovery roots)
This preserves compatibility with existing installs while removing per-port/per-loader hardcoded path duplication.
3. Profile Discovery
3.1 Discovery Order
The system MUST search for profiles in the following order, with earlier entries taking precedence:
$METACALL_ENVIRONMENT- Explicit profile path via environment variable$METACALL_HOME/environment.json- User-level profile$METACALL_PREFIX/etc/metacall/environment.json- System installation profile- Embedded default profile in loader library
3.2 Path Resolution
Variable references in profiles MUST be resolved according to the following rules:
| Variable | Source | Example |
|---|---|---|
$METACALL_PREFIX |
CMake CMAKE_INSTALL_PREFIX or equivalent |
/usr/local |
$METACALL_HOME |
User-configurable, defaults to $HOME/.metacall |
~/.metacall |
$ORIGIN |
Resolved at runtime to loader binary location | Platform-specific |
3.3 Profile Validation
The system SHOULD validate profiles against the MREP JSON Schema before use. Invalid profiles MUST be logged and MUST NOT prevent operation, but the invalid sections MUST be ignored.
4. Shell Profile Integration
4.1 Linux and Unix-like Systems
The installation MUST provide a shell profile script at:
- System-wide:
/etc/profile.d/metacall.sh - User-installable:
$METACALL_HOME/etc/metacall.sh
The profile script MUST:
- Determine the installation prefix
- Source the runtime environment profile
- Export all necessary environment variables
- Provide a
metacall_envhelper function for debugging
Example /etc/profile.d/metacall.sh:
# MetaCall Runtime Environment Profile
# Generated by MetaCall installation
METACALL_PREFIX="/usr/local"
METACALL_HOME="${METACALL_HOME:-${HOME}/.metacall}"
if [ -f "${METACALL_PREFIX}/etc/metacall/environment.json" ]; then
# Source generated environment from profile
. "${METACALL_PREFIX}/etc/metacall/metacall-env.sh"
elif [ -f "${METACALL_HOME}/environment.json" ]; then
. "${METACALL_HOME}/etc/metacall-env.sh"
fi
# Helper function to display current MetaCall environment
metacall_env() {
echo "MetaCall Environment:"
echo " METACALL_PREFIX: ${METACALL_PREFIX}"
echo " METACALL_HOME: ${METACALL_HOME}"
echo " LOADER_LIBRARY_PATH: ${LOADER_LIBRARY_PATH}"
echo " LOADER_SCRIPT_PATH: ${LOADER_SCRIPT_PATH}"
}
4.2 Windows
On Windows, the installation SHOULD:
- Create a PowerShell profile snippet at
$HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 - Or provide a batch file at
%USERPROFILE%\metacall_env.batfor cmd.exe users - Register
METACALL_ENVIRONMENTin the user environment variables
4.3 macOS
On macOS with Homebrew, the formula MUST:
- Provide a bash/zshrc snippet via
etc/profile.d/metacall.sh - Respect
HOMEBREW_PREFIXas the installation prefix - Follow Homebrew conventions for profile.d integration
5. C API Extensions
5.1 New Functions
The following functions SHOULD be added to metacall.h:
/**
* Initialize the runtime environment from the active profile.
* This function reads the MREP, resolves paths, and sets
* necessary environment variables before loader initialization.
*
* Returns 0 on success, non-zero on failure.
*/
int metacall_initialize_environment(void);
/**
* Get the runtime configuration for a specific language.
* The returned pointer MUST NOT be freed by the caller.
*
* Returns NULL if the language is not configured.
*/
const metacall_runtime_config_t metacall_get_runtime_config(const char *language);
/**
* Iterate over all configured runtimes.
* The callback is invoked for each runtime with its name and config.
* Return non-zero from callback to stop iteration.
*/
int metacall_foreach_runtime(
int (*callback)(const char *language, const metacall_runtime_config_t config, void *user),
void *user
);
5.2 Runtime Configuration Structure
typedef struct metacall_runtime_config_type {
const char *language; // Language identifier (e.g., "python", "ruby")
const char *home; // Resolved runtime home path
const char **search_paths; // Array of search paths (NULL-terminated)
const char **environment; // Array of "KEY=VALUE" strings (NULL-terminated)
} * metacall_runtime_config_t;
6. Loader Integration
6.1 Profile-Driven Initialization
Loaders SHOULD use the MREP system for runtime initialization instead of implementing platform-specific logic. The loader initialization flow becomes:
1. metacall_initialize_environment() reads and parses MREP
2. For each runtime in profile:
a. Resolve variable references
b. Set environment variables from profile
c. Store resolved paths for loader use
3. Loader initialization reads resolved paths from profile
4. Platform-specific env var code is replaced by profile lookup
6.2 Fallback Behavior
The system MUST maintain backward compatibility:
- If no profile is found, fall back to existing environment variable behavior
- If a specific runtime is not in the profile, use loader-specific defaults
- Log a warning when falling back to deprecated behavior
7. Port Integration
7.1 MetaCall-sys (Rust)
The Rust port build script MUST generate METACALL_RUNTIME_CONFIG from the installed profile:
// In build.rs
fn main() {
// Read installed profile if present
if let Some(profile) = find_metacall_profile() {
// Emit runtime config to generated code
println!("cargo:rustc-env=METACALL_RUNTIME_CONFIG={}", profile);
}
}
The ensure_python_home() function in rs_port SHOULD be replaced with:
fn ensure_runtime_env() {
// Call metacall_initialize_environment() from C API
// This handles all languages, not just Python
}
7.2 Other Ports
All ports (Go, Java, C#, etc.) SHOULD follow the same pattern:
- Load the C library with
metacall_initialize_environment()call - Or read
METACALL_RUNTIME_CONFIGenvironment variable if set - Do not implement per-language fallback logic
8. Profile Generation
8.1 CMake Integration
The CMake installation process SHOULD generate environment.json during installation:
# In loaders/CMakeLists.txt
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/environment.json.in"
"${CMAKE_CURRENT_BINARY_DIR}/environment.json"
@ONLY
)
The template file MUST use CMake variables for path substitution:
{
"meta": {
"version": "1.0.0",
"generated": "@METACALL_GENERATED_TIMESTAMP@"
},
"runtimes": {
"python": {
"home": "@PYTHON_HOME@",
"search_paths": [@LOADER_SEARCH_PATHS@]
}
},
"defaults": {
"loaders_path": "@CMAKE_INSTALL_PREFIX@/@INSTALL_LIB@/metacall/loaders"
}
}
8.2 Shell Script Generation
During installation, a shell script MUST be generated from the profile:
#!/bin/bash
# Generated from MetaCall environment.json
export METACALL_PREFIX="@CMAKE_INSTALL_PREFIX@"
export LOADER_LIBRARY_PATH="@CMAKE_INSTALL_PREFIX@/@INSTALL_LIB@/metacall/loaders"
export LOADER_SCRIPT_PATH="@CMAKE_INSTALL_PREFIX@/@INSTALL_DATA@/metacall/scripts"
export CONFIGURATION_PATH="@CMAKE_INSTALL_PREFIX@/@INSTALL_DATA@/metacall/config/global.json"
# Python runtime environment
if [ -z "$PYTHONHOME" ]; then
export PYTHONHOME="@PYTHON_HOME@"
fi
9. Backward Compatibility
9.1 Environment Variables
Existing environment variables MUST continue to work:
| Variable | Behavior | Deprecated |
|---|---|---|
LOADER_LIBRARY_PATH |
Still honored, profile takes precedence | No |
LOADER_SCRIPT_PATH |
Still honored, profile takes precedence | No |
PYTHONHOME |
Still honored, profile takes precedence | Yes |
METACALL_CONFIG |
Still honored, profile takes precedence | No |
9.2 Configuration Files
Existing loader.json files MUST continue to be loaded. The MREP system provides additional configuration that supplements but does not replace existing mechanisms.
Requirements
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Implementation Requirements
Implementations of this RFC:
- MUST provide a mechanism for profile discovery as specified in Section 3.1
- MUST resolve variable references according to the rules in Section 3.2
- MUST maintain backward compatibility with existing environment variables
- MUST provide shell profile integration for Linux, Windows, and macOS
- MUST generate shell scripts from profiles during installation
- MUST NOT break existing installations that do not use profiles
Design Recommendations
Implementations:
- SHOULD validate profiles against the MREP JSON Schema
- SHOULD log warnings when falling back to deprecated behavior
- SHOULD provide tooling for profile generation and validation
- SHOULD document the profile format in user-facing documentation
- MAY provide additional variable reference formats beyond those specified
- MAY extend the MREP schema with loader-specific fields
References
Standards
- RFC 2119 - Key words for use in RFCs to Indicate Requirement Levels
- ISO 8601 - Date and time format
- JSON Schema - JSON Schema specification
Related Systems
- SDKMAN - Software Development Kit Manager
- Conda Environment Files - Conda environment specification
- GraalVM Native - GraalVM metadata approach
- asdf Version Manager - Multi-language version manager
MetaCall Resources
- MetaCall GitHub Repository - Main codebase
- PR #734 - Windows Python initialization fix
- MetaCall Configuration - Current configuration system
- PR #738
Appendix A: Migration Guide "which all of them are one person"
For Loader Developers
To migrate an existing loader to use MREP:
- Remove platform-specific environment variable code
- Replace with calls to
metacall_get_runtime_config() - Use resolved paths from the configuration structure
- Remove
#if defined(WIN32)blocks for environment setup
For Port Developers
To update a port to use MREP:
- Remove per-language fallback functions (e.g.,
ensure_python_home()) - Call
metacall_initialize_environment()early in initialization - Or read
METACALL_RUNTIME_CONFIGfrom environment
For Package Maintainers
To integrate MREP into distribution packages:
- Ensure
environment.jsonis generated at build time - Install shell profile script to
/etc/profile.d/ - Set
METACALL_PREFIXto installation prefix - Document the profile requirement in package metadata
Appendix B: Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://metacall.io/schemas/mrep-v1.json",
"title": "MetaCall Runtime Environment Profile",
"description": "Declarative configuration for MetaCall runtime environments",
"type": "object",
"required": ["meta", "runtimes"],
"properties": {
"meta": {
"type": "object",
"required": ["version", "schema"],
"properties": {
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"schema": {
"type": "string",
"format": "uri"
},
"generated": {
"type": "string",
"format": "date-time"
}
}
},
"runtimes": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"home": { "type": "string" },
"search_paths": {
"type": "array",
"items": { "type": "string" }
},
"environment": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"options": {
"type": "object",
"additionalProperties": true
}
}
}
},
"defaults": {
"type": "object",
"properties": {
"loaders_path": { "type": "string" },
"scripts_path": { "type": "string" },
"config_path": { "type": "string" }
}
}
}
}
- Dominant language
- C
- Stars
- 1.8k
- Forks
- 266
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 14
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from metacall/core
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
metacall/core#964 · 1 comment ·
Maintainers usually reply within 1 day
-
pre-push hook empties VERSION when the clone has no tagsPossibly taken @vedant21-oss claimed this 18 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
metacall/core#939 · 3 comments ·
Maintainers usually reply within 1 day
-
TOCTOU race condition vulnerability!Possibly taken @akshatchitransh claimed this 1 day ago. Openbug
Difficulty 4/5 3-5 days Newbie friendliness 35/100
metacall/core#961 · 2 comments ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 Half a day Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Go Port Race Conditions on MacOSPossibly taken A pull request linked to this issue is open or already merged. Openbug
Difficulty 4/5 3-5 days Newbie friendliness 35/100
metacall/core#942 · 3 comments ·
Maintainers usually reply within 1 day
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
-
`table_create()` crashes when an `hccalloc()` for the option table failsPossibly taken @marinelay claimed this today. Opentype: bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
bug severity: low
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
AlessandroSangiuliano/Uros#950 ·
-
vim.text.indent(): expandtab fails on single-tab blank lines and CRLF blank lines reset old_indent to 0Possibly taken A pull request linked to this issue is open or already merged. Openlua
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day