Skip to content

vix.app Generated CMake

vix.app is the user-facing project description.

CMake is the compatibility layer underneath.

The current implementation works like this:

txt
vix.app
  -> parse manifest
  -> validate manifest
  -> generate .vix/generated/app/CMakeLists.txt
  -> configure with CMake
  -> build with CMake/Ninja

The generated CMake file is not meant to be edited by the user.

It is an implementation detail that lets Vix support a simpler project format without breaking compatibility with the C++ ecosystem.

Why generate CMake?

The main reason is compatibility.

CMake already understands:

txt
compilers
generators
toolchains
targets
linking
find_package(...)
platform differences
Ninja integration
build directories

Instead of replacing all of that immediately, Vix can start with a smaller layer:

txt
vix.app -> generated CMake

This gives the user a simple manifest while still using the existing build infrastructure underneath.

The user-facing file

The user writes:

ini
name = hello
type = executable
standard = c++20

sources = [
  src/main.cpp,
]

This file is small.

It only describes the target.

It does not need to contain:

txt
cmake_minimum_required(...)
project(...)
add_executable(...)
target_compile_features(...)
target_include_directories(...)

Vix generates those details internally.

The generated file location

For vix.app projects, Vix writes the generated CMake file under:

txt
.vix/generated/app/CMakeLists.txt

Example project:

txt
hello/
  vix.app
  src/
    main.cpp

After running:

bash
vix build

Vix may create:

txt
hello/
  .vix/
    generated/
      app/
        CMakeLists.txt

This generated file is part of Vix’s internal build metadata.

Do not edit generated CMake

Do not edit:

txt
.vix/generated/app/CMakeLists.txt

Edit:

txt
vix.app

The generated file can be replaced whenever Vix regenerates the project.

If you need to change the target, sources, include directories, links, packages, or resources, change the manifest.

A minimal example

Input vix.app:

ini
name = hello
type = executable
standard = c++20

sources = [
  src/main.cpp,
]

The generated CMake is conceptually similar to:

cmake
cmake_minimum_required(VERSION 3.24)

project(hello LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(hello
  /absolute/path/to/project/src/main.cpp
)

The exact generated output may evolve, but the idea is stable:

txt
manifest fields become target-level CMake instructions

Mapping vix.app to CMake

The generator translates manifest fields into CMake concepts.

vix.app fieldGenerated CMake concept
nametarget name and project name
type = executableadd_executable(...)
type = staticadd_library(... STATIC ...)
type = sharedadd_library(... SHARED ...)
standardCMAKE_CXX_STANDARD
sourcestarget source list
include_dirstarget_include_directories(...)
definestarget_compile_definitions(...)
compile_optionstarget_compile_options(...)
link_optionstarget_link_options(...)
compile_featurestarget_compile_features(...)
packagesfind_package(...)
linkstarget_link_libraries(...)
resourcespost-build copy commands
output_dirtarget output directory properties

Target generation

The type field decides what target command is generated.

For an executable:

ini
name = hello
type = executable

Vix generates the equivalent of:

cmake
add_executable(hello
  ...
)

For a static library:

ini
name = mathlib
type = static

Vix generates the equivalent of:

cmake
add_library(mathlib STATIC
  ...
)

For a shared library:

ini
name = plugin
type = shared

Vix generates the equivalent of:

cmake
add_library(plugin SHARED
  ...
)

This keeps the manifest small while still producing a normal CMake target internally.

Source paths

In vix.app, source paths are written relative to the project directory.

Example:

ini
sources = [
  src/main.cpp,
  src/app.cpp,
]

The generator resolves these paths relative to the directory containing vix.app.

Internally, it can emit normalized paths in the generated CMake file.

This avoids ambiguity when CMake is configured from:

txt
.vix/generated/app/

rather than from the user project root.

Why paths need care

The generated CMake project lives in:

txt
.vix/generated/app/

But the user’s source files live in the real project directory:

txt
src/
include/
assets/

That means the generator must be careful.

It cannot assume that relative paths inside the generated CMake file are relative to the original project root.

It must resolve user paths safely.

This is one reason generated CMake is handled by Vix instead of asking the user to edit it.

Include directories

Input:

ini
include_dirs = [
  include,
  third_party/asio/include,
]

Generated CMake is conceptually similar to:

cmake
target_include_directories(myapp PRIVATE
  /absolute/path/to/project/include
  /absolute/path/to/project/third_party/asio/include
)

The manifest stays relative and readable.

The generated layer can use resolved paths.

Compile definitions

Input:

ini
defines = [
  MYAPP_VERSION="1.2.3",
  MYAPP_ENABLE_LOGGING=1,
]

Generated CMake is conceptually similar to:

cmake
target_compile_definitions(myapp PRIVATE
  MYAPP_VERSION="1.2.3"
  MYAPP_ENABLE_LOGGING=1
)

Definitions remain target-local.

They are not global compiler flags.

Compile options

Input:

ini
compile_options = [
  -Wall,
  -Wextra,
  -Wpedantic,
]

Generated CMake:

cmake
target_compile_options(myapp PRIVATE
  -Wall
  -Wextra
  -Wpedantic
)

These options are emitted as raw CMake list items, not quoted strings, because compiler flags and CMake targets often need to remain raw.

Input:

ini
link_options = [
  "-Wl,--as-needed",
]

Generated CMake:

cmake
target_link_options(myapp PRIVATE
  -Wl,--as-needed
)

Values containing commas should be quoted in vix.app so the manifest parser does not split them incorrectly.

Compile features

Input:

ini
compile_features = [
  cxx_std_23,
]

Generated CMake:

cmake
target_compile_features(myapp PRIVATE
  cxx_std_23
)

Most projects can use:

ini
standard = c++23

But compile_features is available when explicit CMake features are needed.

Packages

Input:

ini
packages = [
  Threads:REQUIRED,
  fmt:REQUIRED,
  "Boost:COMPONENTS=system,filesystem:REQUIRED",
]

Generated CMake is conceptually similar to:

cmake
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(fmt REQUIRED)
find_package(Boost REQUIRED COMPONENTS system filesystem)

The package field only emits find_package(...).

It does not link imported targets automatically.

Input:

ini
links = [
  Threads::Threads,
  fmt::fmt,
  Boost::system,
  Boost::filesystem,
]

Generated CMake:

cmake
target_link_libraries(myapp PRIVATE
  Threads::Threads
  fmt::fmt
  Boost::system
  Boost::filesystem
)

This separation is deliberate.

packages discovers packages.

links links targets or libraries.

The generator does not guess which imported targets a package should link.

CMake imported targets such as:

txt
Threads::Threads
fmt::fmt
Boost::system

should be emitted as CMake target names.

They should not be treated like ordinary quoted strings.

This is why the generator needs separate handling for:

txt
quoted path-like values
raw CMake target-like values

For target links, compile features, compile options, and link options, raw output is usually the correct behavior.

Output directories

Input:

ini
output_dir = bin

Generated CMake is conceptually similar to:

cmake
set_target_properties(myapp PROPERTIES
  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
  LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
  ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)

A relative output_dir is resolved under the build directory.

So with:

txt
build-ninja/

the output becomes:

txt
build-ninja/bin/

Resources

Input:

ini
resources = [
  assets,
  "data/config.json=config/config.json",
]

The generator emits post-build copy commands.

Conceptually:

cmake
add_custom_command(TARGET myapp POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_directory
          /path/to/project/assets
          $<TARGET_FILE_DIR:myapp>/assets
)

And for a renamed file:

cmake
add_custom_command(TARGET myapp POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy
          /path/to/project/data/config.json
          $<TARGET_FILE_DIR:myapp>/config/config.json
)

The exact generated command may differ, but the behavior is:

txt
resources are copied next to the built target after build

Why generated CMake is readable

The generated CMake should be readable.

Even though users should not edit it, readability matters for debugging.

A readable generated file helps engineers understand:

txt
what Vix parsed
what Vix generated
what CMake is configuring
why a package is found or not found
why a target links or does not link
where resources are copied

Generated code is still engineering output.

It should not be treated as a black box.

Debugging generated CMake

If a vix.app build fails, inspect:

txt
.vix/generated/app/CMakeLists.txt

Then run:

bash
vix build -v

or:

bash
vix build --cmake-verbose

You can also pass extra CMake arguments after --:

bash
vix build -- -DCMAKE_PREFIX_PATH=/path/to/prefix

This is useful for packages installed in custom locations.

Error boundary

The best errors should happen before CMake runs.

For example, Vix should catch:

txt
missing name
missing sources
invalid target type
invalid standard
malformed array
unknown field
source file not found
invalid package syntax

These errors belong at the manifest layer.

CMake should not be the first tool to discover that src/main.cpp does not exist.

CMake errors still happen

Some errors can only happen at configure or build time.

Examples:

txt
package not found
imported target not found
compiler flag not supported
linker flag not supported
symbol not found
duplicate main()
undefined reference

These are normal build errors.

Vix can improve diagnostics, but they still originate from the compiler, linker, or CMake.

Example: package found but not linked

Input:

ini
packages = [
  fmt:REQUIRED,
]

Generated CMake finds fmt, but no link is emitted.

The build may fail if the source uses fmt.

Correct manifest:

ini
packages = [
  fmt:REQUIRED,
]

links = [
  fmt::fmt,
]

This produces both:

cmake
find_package(fmt REQUIRED)

and:

cmake
target_link_libraries(myapp PRIVATE
  fmt::fmt
)

Example: wrong source path

Input:

ini
sources = [
  main.cpp,
]

But the real file is:

txt
src/main.cpp

Vix should fail during manifest validation:

txt
vix.app source file not found: main.cpp

The fix is:

ini
sources = [
  src/main.cpp,
]

This is better than generating invalid CMake and waiting for CMake to fail later.

Why not expose generated CMake as the API

One possible design would be:

txt
vix app init
generate CMakeLists.txt
user edits generated CMake

But that would turn the generated file into the source of truth.

That is not the goal.

The source of truth should remain:

txt
vix.app

The generated CMake is disposable.

That allows Vix to improve the generator without forcing users to maintain generated build files.

Generated CMake and future native builds

The generated CMake layer is the current compatibility path.

It does not have to be the final build path.

Because vix.app is structured, Vix can later build directly from it:

txt
vix.app
  -> native BuildGraph
  -> ObjectCache
  -> Scheduler
  -> Link

In that future model, generated CMake may still exist as:

txt
debug output
fallback path
compatibility mode
export format

But the hot path for simple projects could become native.

Why this matters

Arbitrary CMake is hard to optimize because CMake is a full language.

Vix has to ask CMake what the build graph is.

But vix.app is declarative and narrow.

Vix can understand it directly.

That means Vix can eventually answer questions like:

txt
Which source files belong to this target?
Which include directories affect compilation?
Which definitions changed?
Which object files can be reused?
Does this target need to link again?
Which resources need copying?

This is the foundation for a faster build path.

Generated CMake as a bridge

The current design is a bridge.

It gives users a small manifest today.

It keeps CMake compatibility today.

It prepares Vix for native build execution later.

That is why generated CMake exists.

It is not the final vision.

It is the safe compatibility layer.

Common debugging checklist

When debugging a generated CMake project:

txt
1. Check vix.app first.
2. Check .vix/generated/app/CMakeLists.txt.
3. Check that sources were resolved correctly.
4. Check include directories.
5. Check packages.
6. Check links.
7. Check output_dir.
8. Check resource copy commands.
9. Run vix build -v.
10. Use --cmake-verbose if configure output is needed.

What should not be generated

The generator should not try to invent complex logic.

It should not guess:

txt
which imported targets to link
which optional packages to use
how to fetch dependencies
how to generate sources
how to install packages
how to run CTest

Those belong in explicit user configuration or full CMake.

The generator should be powerful, but not magical.

Conclusion

Generated CMake is the bridge between vix.app and the existing C++ build ecosystem.

The user writes a small manifest.

Vix validates it.

Vix generates a normal CMake project internally.

CMake and Ninja build it.

The generated file remains internal and disposable.

This gives Vix a practical path:

txt
simple manifest today
CMake compatibility today
native BuildGraph tomorrow

That is the engineering role of generated CMake in vix.app.

Last updated: