vix Direct Script Runner
The direct script runner is the simplest execution path in vix run.
It exists for one use case:
I have one C++ file.
Compile it.
Run it.Example:
vix run main.cppThis should not require a CMakeLists.txt.
It should not require a vix.app.
It should not require a project layout.
The file itself is the entry point.
Why direct script mode exists
C++ is often treated as a language that needs a project before it can run.
For large systems, that is reasonable.
But for small experiments, examples, learning, tests, and local tools, requiring a full project is unnecessary friction.
A developer should be able to write:
#include <vix.hpp>
int main()
{
vix::print("hello");
return 0;
}and run:
vix run main.cppThe direct script runner makes that possible.
The basic flow
For a simple file, the flow is:
source file
-> compile
-> link
-> executeConceptually:
main.cpp
-> main.o
-> main
-> run ./mainThe exact output path is an implementation detail.
The user-facing behavior should remain simple:
vix run main.cppScript mode is not project mode
Direct script mode is different from project mode.
Script mode input:
one source fileProject mode input:
project root
CMakeLists.txt or vix.app
target name
build directorySo this:
vix run main.cppshould not behave the same as:
vix runinside a project.
An explicit source file means script mode.
No source file means project mode.
Minimal example
main.cpp:
#include <vix.hpp>
int main()
{
vix::print("hello from Vix script mode");
return 0;
}Run:
vix run main.cppExpected output:
hello from Vix script modeThe developer does not need to create:
CMakeLists.txt
vix.app
build-ninja/manually.
What the runner needs to know
Even for one file, Vix still needs build information.
The direct script runner must decide:
which compiler to use
which C++ standard to use
which include paths to use
which libraries to link
where to place outputs
how to pass runtime arguments
how to report compile errorsThe command is simple, but the execution still needs structure.
Compiler selection
The runner needs a compiler.
Possible sources:
environment variables
Vix defaults
detected system compiler
configured toolchain
target tripleCommon compiler candidates:
c++
g++
clang++
clThe selected compiler becomes part of the script build identity.
If the compiler changes, cached outputs may no longer be valid.
Default standard
Script mode should have a reasonable default C++ standard.
For Vix, a good default is:
c++20The user should be able to override this if needed.
Example idea:
vix run main.cpp --std c++23The exact CLI flag can evolve, but the model is clear:
script mode has defaults
advanced users can override themOutput location
The direct script runner should avoid polluting the current directory.
Instead of producing:
main
main.o
main.dnext to main.cpp, Vix can place outputs in an internal build or cache directory.
Possible layout:
.vix/run/
.vix/cache/run/
build-script/The exact path is less important than the rule:
script outputs should be managed by VixScript identity
A script build needs an identity.
For:
vix run main.cppthe identity can include:
source path
source content
compiler identity
compiler flags
C++ standard
include paths
linked libraries
target triple
runtime modeThis identity lets Vix decide whether it can reuse a previous script build.
Reusing script builds
If nothing changed, Vix should avoid recompiling the script.
A no-op script run can be:
source unchanged
compiler unchanged
flags unchanged
binary exists
run existing binaryThis makes repeated vix run main.cpp calls fast.
But reuse must be conservative.
If Vix is unsure, it should rebuild.
Dependency files
Even a single source file can include headers.
Example:
#include "message.hpp"If message.hpp changes, the script should rebuild.
That means the compile command should ideally emit a dependency file.
Conceptual command:
c++ -std=c++20 -MMD -MP -MF main.d -c main.cpp -o main.oThe dependency file tells Vix which headers affect the script binary.
Local headers
Example layout:
scratch/
main.cpp
message.hppmain.cpp:
#include <vix.hpp>
#include "message.hpp"
int main()
{
vix::print(message());
return 0;
}If message.hpp changes, vix run main.cpp should rebuild.
That is why dependency tracking matters even in script mode.
Include paths
For script mode, Vix may include the source file directory by default.
If main.cpp is in:
scratch/main.cppthen local includes from:
scratch/should work naturally.
Additional include paths may be provided through flags later.
Example design:
vix run main.cpp --include includeor through CMake fallback when the case becomes complex.
Linking Vix
Many Vix examples use:
#include <vix.hpp>Some Vix APIs may be header-only.
Others may require linking compiled Vix modules.
The direct script runner needs to know how to link the required Vix components.
For simple APIs such as printing, direct compilation may be enough.
For compiled modules, the runner may need a fallback build path.
Header-only path
If the script only uses header-only functionality, direct mode is straightforward.
Example:
#include <vix.hpp>
int main()
{
vix::print("hello");
return 0;
}The runner can compile and link directly.
This is the ideal fast path.
Compiled module path
Some modules may require linking.
Examples may include:
kv
http
crypto
threadpool
networking modulesIf the script uses a compiled module, direct mode may not have enough information to link safely.
In that case, Vix can use a fallback.
Why fallback is needed
Direct compilation is fast because it is simple.
But it cannot handle every dependency case.
A script may need:
compiled Vix modules
registry packages
external libraries
package discovery
complex link flags
platform-specific setupTrying to force all of that into direct mode would make direct mode complex and fragile.
The better design is layered:
direct mode for simple scripts
fallback mode for complex scriptsCMake fallback for scripts
When direct mode is not enough, Vix can generate a temporary CMake project for the script.
Flow:
main.cpp
-> generated script CMake project
-> CMake configure
-> CMake build
-> run executableThis keeps the user command the same:
vix run main.cppbut gives Vix more build power internally.
Direct mode vs fallback mode
The decision can look like:
if script can be compiled directly:
use direct runner
else:
use generated CMake fallbackDirect runner:
fast
simple
minimal overheadCMake fallback:
more compatible
handles complex linking
supports compiled modulesWhen direct mode should be used
Direct mode is best when:
single source file
simple local headers
no complex packages
no generated sources
no complex external linking
known Vix headers or simple modulesThis covers many learning and experimentation cases.
When fallback should be used
Fallback is better when the script needs:
compiled Vix modules
registry dependencies
external CMake packages
complex link options
generated build metadata
platform-specific dependency setupThe user should not need to choose manually in most cases.
Vix can try direct mode first, then fallback when necessary.
Runtime arguments
The direct script runner must separate Vix flags from program arguments.
Example:
vix run main.cpp --run --name GaspardThe arguments after the runtime boundary should be passed to the program:
--name GaspardAnother common model is:
vix run main.cpp -- --name GaspardThe important rule is:
Vix arguments configure Vix.
Runtime arguments go to the script executable.Why argument boundaries matter
A C++ program may use flags like:
--port
--config
--verbose
--helpVix also has flags.
Without a clear boundary, Vix may consume flags meant for the program.
The direct script runner must keep this separation predictable.
Process execution
After compilation, Vix runs the executable as a child process.
The runner should preserve:
stdin
stdout
stderr
exit code
signals when possibleA program run through Vix should feel like running the binary directly.
If the program exits with code 1, Vix should report that as the program exit code, not necessarily as a Vix crash.
Non-zero exits
A non-zero exit from the user program is not the same as a build failure.
Example:
int main()
{
return 1;
}The build succeeded.
The program returned 1.
vix run should distinguish:
compile failure
link failure
runtime exit code
runtime crashThis distinction makes diagnostics clearer.
Interactive programs
Some programs read from standard input.
Example:
#include <vix.hpp>
int main()
{
auto name = vix::input("Name: ");
vix::print("Hello", name);
return 0;
}The direct script runner should allow interactive input.
That means process execution should not always fully capture stdin in a way that breaks interaction.
Output handling
For normal script runs, stdout and stderr should pass through naturally.
For errors, Vix can still format diagnostics.
Good behavior:
compiler errors are formatted
program output is preserved
runtime exit is reported clearlyThe user program should not feel trapped behind the tool.
Compile diagnostics
If compilation fails, Vix should show a useful diagnostic.
Example:
int main()
{
unknown_symbol();
}The error should point to:
main.cppnot to an internal generated file.
Direct mode gives Vix a clearer path to source-level diagnostics.
Link diagnostics
If linking fails, Vix should explain the link error.
Common causes:
missing library
missing symbol
compiled module not linked
wrong dependencyIf the error suggests direct mode is insufficient, Vix can fallback when possible or show a hint.
Runtime diagnostics
Runtime failures are different from build failures.
Examples:
program exited with code 1
program crashed
program timed out
permission denied
executable not foundThese should be reported as runtime errors.
The build may still have been successful.
Script cache
The direct runner can use a script cache.
A cache entry can contain:
binary path
object file
dependency file
source hash
command hash
compiler identity
last run metadataOn the next run, Vix can decide whether to reuse the binary.
Object cache in script mode
The direct script runner can also use the normal ObjectCache.
For a single source file:
main.cpp -> main.oIf the object cache has a valid object, Vix can restore it.
Then it only needs to link or reuse the existing binary.
Artifact cache in script mode
For a complete script binary, Vix can use a small artifact cache.
If all inputs match, Vix can reuse:
compiled executableThis makes repeated script runs very fast.
But the same correctness rule applies:
reuse only when the full identity matchesWatch mode
Script mode can support watch mode.
Example:
vix run main.cpp --watchIn watch mode, Vix should rebuild and rerun when inputs change.
Inputs include:
main.cpp
local headers from dependency file
possibly runtime resourcesDependency files make watch mode more accurate.
Watch mode flow
A simple watch loop:
build and run once
watch source and dependency files
on change:
stop previous process if needed
rebuild
rerunThis is useful for small tools and examples.
Working directory
The working directory matters for relative file access.
In script mode, a reasonable default is:
current working directoryIf the user runs:
vix run examples/hello.cppfrom the project root, the program may expect relative files from that root.
This should be documented and consistent.
Environment variables
The script runner should pass environment variables to the child process.
For build behavior, Vix may also read variables such as:
CXX
VIX_LOG_LEVELRuntime environment and build environment should be treated carefully.
Temporary files
Direct mode may create temporary files.
These can include:
object files
dependency files
binaries
logs
metadataThey should live in a Vix-managed directory.
The user should not need to clean them manually in normal use.
Cleanup
Vix can keep script outputs for cache reuse.
It should also provide ways to clean them.
Example concepts:
vix clean
vix cache clean
vix run --cleanThe exact command can evolve.
The important idea is that cache should be useful but manageable.
Relationship with vix build
vix run main.cpp is not the same as vix build.
But it still uses build concepts:
compiler detection
compile task
link task
cache
diagnostics
process executionThe difference is input shape.
vix build builds a project target.
vix run main.cpp builds and runs one source file.
Relationship with vix.app
vix.app is for project mode.
Direct script mode does not need it.
However, both systems can share future build infrastructure:
BuildGraph
ObjectCache
Scheduler
Link
DiagnosticsSo the architecture can converge underneath while keeping user workflows separate.
Future native script graph
The direct script runner can eventually be modeled as a tiny BuildGraph.
For:
vix run main.cppthe graph is:
Node: main.cpp
Node: main.o
Node: main executable
Task: compile main.cpp -> main.o
Task: link main.o -> executable
Task: run executableThis makes script mode part of the same build architecture as native vix.app.
Fallback graph
When fallback is needed, Vix can generate a temporary project and use the CMake compatibility path.
That fallback can still produce graph data:
compile_commands.json
build.ninja
dependency filesSo even fallback mode can participate in cache and diagnostics.
Common errors
Source file not found
Command:
vix run main.cppbut main.cpp does not exist.
Vix should report:
source file not found: main.cppCompile error
The file exists, but the compiler fails.
Vix should show the compiler diagnostic.
Link error
The file compiles, but linking fails.
Vix should report the linker error and possibly suggest missing links or fallback.
Runtime exit
The program exits with non-zero status.
Vix should report the program exit code.
This is different from compile or link failure.
Common design mistakes
Treating script mode as project mode
Wrong:
vix run main.cpp requires vix.appCorrect:
explicit source file uses script modePolluting the source directory
Wrong:
main.o appears next to main.cppCorrect:
Vix stores outputs in a managed directoryIgnoring runtime arguments
Wrong:
Vix consumes all flagsCorrect:
program arguments pass through after a clear boundaryRebuilding every time
Wrong:
compile on every run even when nothing changedCorrect:
reuse binary/object when inputs are unchangedA practical first implementation
A practical direct script runner should support:
single .cpp file
default C++ standard
local includes
Vix include path
managed output directory
compile diagnostics
link diagnostics
runtime argument forwarding
interactive stdin
exit code preservation
simple cache reuse
fallback when direct mode is insufficientThis is enough to create a strong first experience.
Later improvements
Later improvements can include:
object cache integration
artifact cache integration
dependency-file-based watch mode
compiled module auto-linking
registry dependency support
native BuildGraph execution
explainable rebuilds
better runtime working directory controlsThe runner can grow in layers.
Engineering principle
The direct script runner should follow this principle:
make the simple case immediate
fallback for complexityDo not make every script pay the cost of a full project.
Do not make complex scripts fail when a fallback path can handle them.
This keeps script mode fast and practical.
Conclusion
The direct script runner gives C++ a lightweight execution path inside Vix.
It lets a developer run:
vix run main.cppwithout writing a project first.
The runner should compile, link, execute, preserve runtime behavior, and reuse outputs when safe.
For complex cases, it can fall back to generated build infrastructure.
The long-term direction is to make script mode part of the same native build architecture as vix.app:
source file
-> BuildGraph
-> ObjectCache
-> Scheduler
-> Link
-> RunThat keeps the simple path simple while giving Vix room to become faster and more capable underneath.