vix.app Internal Modules

vix.app can describe internal application modules. These modules are not external dependencies. They are parts of the application itself. The goal is simple:

application code stays modular
the manifest stays readable
the generated build handles the wiring

The problem

As a C++ application grows, putting everything into one source tree becomes hard to maintain. A backend application may quickly need separated areas such as:

  • users
  • auth
  • billing
  • orders
  • notifications
  • storage

Without a module structure, the project can become a flat collection of headers and source files. Without build automation, every new module also requires manual CMake edits. That creates friction. A developer should be able to create a module, declare it in the app, and build.

Internal modules

Internal modules are created with:

vix modules add User

This creates a module under:

modules/User/

The module has its own public header and implementation file. Conceptually:

modules/User/
  include/User/api.hpp
  src/User.cpp
  CMakeLists.txt

The module is part of the application. It is not downloaded from the registry. It is not a system package. It is local app code.

Declaring modules in vix.app

A vix.app project declares internal modules with:

modules = [
  User,
]

Example:

name = api
type = executable
standard = c++20

sources = [
  src/main.cpp,
]

modules = [
  User,
]

This tells Vix:

the app target depends on the internal module User

The generated build links the module into the application target.

Field separation

vix.app keeps different concepts separate.

modules   internal application modules
deps      external packages from the Vix Registry
packages  system or CMake packages resolved with find_package(...)
links     raw CMake targets or libraries linked manually

This separation is important. A module is code inside the current application. A registry dependency is external package code. A package is a CMake discovery rule. A link is a manual target-level link instruction. The manifest stays explicit.

Example backend app

A backend app can use internal modules for application features:

name = api
type = executable
standard = c++20
output_dir = bin

sources = [
  src/main.cpp,
  src/api/app/AppBootstrap.cpp,
  src/api/support/HttpResponse.cpp,
]

include_dirs = [
  include,
  src,
]

modules = [
  User,
  Auth,
  Billing,
]

This makes the app structure visible directly in the manifest. The reader can see that the app has internal modules for:

  • User
  • Auth
  • Billing

without opening the generated CMake file.

Build flow

The internal module flow is:

vix modules add User
  -> modules/User/
  -> modules/User/CMakeLists.txt

vix.app
  -> modules = [ User ]

vix build
  -> generate .vix/generated/app/CMakeLists.txt
  -> include cmake/vix_modules.cmake
  -> link api with api::User
  -> build with CMake/Ninja

The user works with:

  • vix modules
  • vix.app
  • vix build

Vix handles the generated CMake layer.

Module targets

A module gets a canonical CMake alias based on the app name. For an app named:

name = api

and a module named:

User

the expected module target is:

api::User

So this manifest:

name = api

modules = [
  User,
]

is conceptually linked as:

target_link_libraries(api PRIVATE api::User)

The user does not write this manually in a vix.app project.

Why the app name matters

The app name is used as the project namespace for internal modules. Example:

name = blog

modules = [
  User,
]

Expected module alias:

blog::User

Another app:

name = api

modules = [
  User,
]

Expected module alias:

api::User

This keeps module targets local to the application. It avoids generic target names like:

User

which could conflict with other libraries or modules.

vix modules init

Before adding modules, a project can initialize the module layout:

vix modules init

For a vix.app project, Vix does not patch a root CMakeLists.txt. There is no user-maintained root CMake file.

The output is simple:

vix.app project detected. CMake patch skipped.
modules initialized

Vix creates the module directory and module loader. The generated CMake project later includes that loader automatically.

vix modules add

Adding a module:

vix modules add User

creates:

modules/User/
modules/User/include/User/api.hpp
modules/User/src/User.cpp

For a vix.app project, the next step is:

add "User" to modules in vix.app

Then:

vix build

This is the intended workflow.

The user does not edit generated CMake.

Why modules are declared manually

vix modules add User creates the module.

It does not automatically add it to vix.app. That is intentional. Creating a module and using a module are different actions. A project may create a module that is not linked yet. A module may be experimental. A module may be used later. The manifest should clearly show which modules the app target actually uses. That is why the user declares:

modules = [
  User,
]

Generated module loading

The generated CMake includes the module loader from the real project directory:

include("/absolute/path/to/project/cmake/vix_modules.cmake")

The absolute path matters.

The generated CMake file lives in:

.vix/generated/app/

The module loader lives in:

cmake/vix_modules.cmake

A relative include could point to the wrong location. So Vix resolves the module loader from the original project directory.

Why not patch CMakeLists.txt

For a classic CMake project, vix modules init can patch the root CMakeLists.txt. For a vix.app project, that would be wrong.

The source of truth is:

vix.app

The generated CMake file is disposable. So the correct behavior is:

CMake project   -> patch CMakeLists.txt when requested
vix.app project -> do not patch CMakeLists.txt

This keeps the vix.app workflow clean.

Module structure

A module separates public API from implementation.

Example:

modules/User/
  include/User/api.hpp
  src/User.cpp

The public header is what other app code can include:

#include <User/api.hpp>

The implementation file stays inside the module. This encourages a clear boundary. The application can depend on the module API without knowing the internal implementation layout.

Module safety

Internal modules should follow safe boundaries. A public header should not include private implementation files. A module that includes another module should declare that relationship in its module CMake target. This prevents hidden coupling. It also makes the build graph easier to understand. A module system should not only create folders. It should help preserve architecture.

Modules and generated CMake

When vix.app declares:

modules = [
  User,
]

Vix generates logic equivalent to:

if(TARGET api::User)
  target_link_libraries(api PRIVATE api::User)
else()
  message(FATAL_ERROR "vix.app module not found: User")
endif()

The exact generated output may evolve.

The idea is stable:

declared modules become target links

Module not found

If a module is declared in vix.app but does not exist, the build should fail clearly.

Example:

modules = [
  User,
]

But the module folder is missing:

modules/User/

The error should explain that the module declared in vix.app is not available. This is better than a generic CMake target error. The user should know which manifest entry caused the problem.

Modules and registry deps

Internal modules and registry dependencies can be used together. Example:

name = api
type = executable
standard = c++20

sources = [
  src/main.cpp,
]

modules = [
  User,
]

deps = [
  gk/email,
]

Here:

User      local application module
gk/email  external registry package

The generated build links both into the app target. But they remain different concepts in the manifest.

Why this is app-first

The app should describe its architecture. With modules, the manifest can say:

this application is composed of these internal units

That is more meaningful than only listing source files. Source files are low-level build input. Modules are higher-level application structure. This is why modules belongs in vix.app.

Why this matters for large apps

As applications grow, architecture becomes more important than build commands. A backend project may have:

  • presentation
  • application
  • domain
  • infrastructure
  • support

Inside those areas, internal modules can represent clear feature boundaries.

Example:

modules = [
  User,
  Auth,
  Orders,
  Notifications,
]

This gives the project a visible shape. The manifest becomes more than a file list. It becomes a small architecture map.

Good developer experience

The module workflow should feel direct:

vix modules init
vix modules add User

Then edit:

modules = [
  User,
]

Then:

vix build

The output should avoid internal build jargon when it is not needed. For example, in a vix.app project, vix modules add User should not tell the user to edit target_link_libraries(...). It should tell the user to add the module in vix.app.

What stays explicit

Vix does not silently link every module in modules/. The user decides what the app uses. This avoids accidental coupling. It also allows modules to exist without being part of the current app target. The manifest remains explicit:

modules = [
  User,
  Billing,
]

Only declared modules are linked.

Current design boundary

Internal modules currently use generated CMake underneath.

The flow is:

modules/ -> cmake/vix_modules.cmake -> generated CMake -> CMake/Ninja

This is a compatibility layer. Later, Vix can use the same manifest data to build modules through a native build graph. The important part is that vix.app now has a structured way to describe internal architecture.

Conclusion

vix.app can now work with internal application modules. The developer can create a module with:

vix modules add User

Then declare it in the manifest:

modules = [
  User,
]

Vix loads the module system. Vix links the module into the app target. The user does not maintain generated CMake. This keeps the project simple at the surface and modular underneath.