Project files (.xeproj)

A .xeproj file describes a Xenon compilation: which source tree to include, what to build, which profile to use and which Xenon or native libraries to consume.

Why a project file exists

A .xe file contains language declarations. A .xeproj file contains build configuration. It gives the compiler one entry point for a program or library spread across many source files, so you do not have to pass each source path separately.

The project file is UTF-8 text using a TOML-style configuration format with the settings shown on this page. Settings belong to sections such as [project] and [source]; strings use double quotes, and # starts a comment outside a string.

Project inputs and using have different jobs

The project determines which files enter the compilation. A using directive resolves names among declarations already included in that compilation. It does not load a file, search a directory, download a package or link a library.

A project with two source files

Create the following layout. The source directory exists beside the project file:

ExampleApp/
    App.xeproj
    src/
        Main.xe
        Math/
            Arithmetic.xe

App.xeproj:

[project]
name = "ExampleApp"
type = "executable"

[source]
root = "src"

src/Math/Arithmetic.xe:

namespace Example.Math;

public int Add(int a, int b)
{
    return a + b;
}

src/Main.xe:

using Example.Math;

namespace Example;

int Main()
{
    int result = Add(20, 22);
    if (result == 42)
        return 0;
    return 1;
}

With the Xenon CLI on your path, run these commands from ExampleApp:

xenon build App.xeproj
xenon run App.xeproj

The executable returns exit code 0 after confirming that the call produced 42.

How the project makes using possible

  1. The loader resolves source.root relative to the directory containing App.xeproj and recursively collects the .xe files below it.
  2. All discovered files are compiled together, so their declared namespaces and types are available throughout the project.
  3. Each file's using directives apply to that file. In the example, Example.Math is available because Arithmetic.xe belongs to the project.
  4. In Main.xe, Add is found through the imported namespace. The function is public, so code in Example can call it.

Folder names do not declare namespaces. The folder Math/ could be renamed without changing namespace Example.Math;. Likewise, the project name ExampleApp does not automatically create an ExampleApp namespace.

Imports affect only their own file and do not bypass access modifiers. You can also call Example.Math.Add(20, 22) without a using, but Arithmetic.xe must still be included in the compilation. See namespaces and aliases and access modifiers.

Building Main.xe alone is a different compilation

xenon build src/Main.xe selects only that file. It does not search for a nearby .xeproj or automatically include Arithmetic.xe. In this example it reports an unknown namespace. Build App.xeproj or its directory to include the complete source tree.

Explicit and implicit projects

A project file is useful, but not mandatory for every compilation. The input passed to xenon build or xenon run determines how the compiler selects sources:

InputSelection rule
App.xeprojLoad that project's settings and source root.
A directory with one .xeproj directly inside itLoad that project. Only its configured source tree is included.
A directory with no .xeproj directly inside itCreate an implicit executable project from all .xe files recursively below the directory. The directory name becomes the project name.
A directory with several .xeproj files directly inside itReport an ambiguity. Pass the intended project file explicitly.
One .xe fileCreate an implicit executable project containing only that file. The filename without its extension becomes the project name.
No pathResolve the current working directory using the directory rules above.

For example, xenon build . inside ExampleApp selects App.xeproj. An implicit directory project can also use using across its included files; the directive does not require a physical project file.

Source discovery includes every .xe file under the selected source root. Paths are sorted with case-insensitive ordinal comparison on Windows and ordinal comparison on other supported platforms. Keep unrelated examples, test programs with their own Main, and generated .xe files outside that root.

Project and source settings

SettingRequired / defaultMeaning
[project] nameRequired, nonempty quoted stringProject identity used for output filenames. It does not have to match the .xeproj filename.
[project] typeRequired"executable", "static-library", "shared-library" or target-specific "xenon-library".
[project] versionOptional quoted stringStored project metadata. It does not select a language version or resolve dependencies.
[source] root"src"Directory searched recursively for source files. A relative path is based on the project file's directory.
[references] projects[]Array of referenced Xenon library project files. Relative paths are based on the current project file's directory.
[libraries] libraries[]Explicit .xelib paths and native library inputs. Relative XELIB paths are based on the project file's directory.
[libraries] library-paths[]Native linker search directories. These paths are not searched for XELIB files.

The source root must already exist and contain at least one .xe file. Omitting [source] uses src; it does not switch to the implicit project's whole-directory search. For paths in quoted strings, forward slashes work without escaping, including on Windows.

An executable requires exactly one namespace-level int Main(); the compiler generates the native entry wrapper. Static and shared libraries do not require that entry point. xenon run accepts executable projects only.

Native dependencies are a separate mechanism from using. For example, this configuration tells the linker where to find a native library; it does not import a Xenon namespace or read the library's declarations:

[libraries]
library-paths = ["native/lib"]
libraries = ["math"]

Declare the native functions with extern. See native dependencies and building libraries for platform naming, exports and runtime library search. Static archives do not bundle their native dependencies; those must be supplied when the consumer is linked.

Entries ending in .xelib are target-specific Xenon semantic libraries rather than native linker inputs. The consumer requires the exact canonical target triple recorded by the library and reads its public symbols and implementation bodies without needing the producer's source files. Producer defines are already fixed in the artifact and cannot be reselected by consumer defines. See XELIB libraries for build output, explicit-reference and dependency rules.

Project references

Use [references] projects when one Xenon project depends on another Xenon library project:

[references]
projects = [
    "../SomeLibrary/SomeLibrary.xeproj"
]

Each relative entry is resolved from the directory containing the project file that declares it. Referenced projects are loaded recursively and built before the project that uses them, with the same selected profile and target. Their public declarations participate in the dependent compilation, while normal namespace, using and access rules still apply. Their generated library artifacts are supplied when the dependent native artifact is linked. For xenon-library projects, the transitive XELIB dependency closure follows the project graph automatically and duplicate content identities are loaded once.

References must name existing .xeproj library projects. Executable projects cannot be dependencies. Duplicate references, dependency cycles, missing projects and duplicate project names in one graph are reported as project-system errors. Shared-library dependencies must still be available to the platform loader at runtime.

Debug and release profiles

Two build profiles are available. These are their default values; add the sections only when you want to make the defaults explicit or override individual settings:

[profile.debug]
optimization = 0
debug-info = true
checks = true

[profile.release]
optimization = 3
debug-info = false
checks = false

The default is debug. Select release with --release or --profile release; select debug explicitly with --profile debug. Missing settings inherit the defaults of their own profile, not values from the other profile.

optimization accepts an integer from 0 through 3. debug-info and checks accept booleans.

Build commands and output

From the example project's directory:

# Build using the default debug profile
xenon build App.xeproj

# Build and run the executable
xenon run App.xeproj

# Release project build
xenon build App.xeproj --release

# Emit an object for a non-host target
xenon build App.xeproj --target aarch64-unknown-linux-gnu

# Direct source mode: explicitly write LLVM IR beside Main.xe
xenon src/Main.xe --emit-llvm

A build loads the project, checks all selected sources, emits an object and invokes the host linker or archiver for the requested project type. run builds first and returns the executable's exit code. For a non-host target, the build produces an object file to pass to a target-specific SDK and linker.

OutputPath relative to the project directory
Intermediate object.xenon/obj/<profile>/<target-triple>/ExampleApp.obj on Windows; .o on other targets.
Native executable or librarybuild/<profile>/<target-triple>/, with a filename based on project.name, project type and platform.
Target-specific Xenon librarybuild/<canonical-target-triple>/<project-name>.xelib. When user or project defines are active, the path is build/<canonical-target-triple>/defines-<fingerprint>/<project-name>.xelib.
LLVM IR intermediate.xenon/obj/<profile>/<target-triple>/ExampleApp.ll, alongside the intermediate object.

The XELIB define fingerprint is the lowercase SHA-256 digest of the sorted, deduplicated user and project define names. Target and effective defines identify the semantic artifact; the selected Debug/Release profile does not. A XELIB can therefore be reused by Debug and Release consumers when its canonical target triple and fixed define set match.

The project build and run pipelines generate the target-specific .ll intermediate as part of object emission, regardless of whether --emit-llvm is present. In project mode that flag does not create a second IR file or add an IR-path line to the CLI output. The CLI reports the object and native artifact paths, plus a Windows import-library path when one is produced.

Direct source compilation is different: xenon Main.xe --emit-llvm writes Main.ll beside the first input source and reports that path. For a Windows debug project build of the example, the executable is build/debug/<target-triple>/ExampleApp.exe. Native toolchain requirements and library filenames are described in building libraries.

Common errors

String arrays may span multiple lines and have trailing commas. Each setting must have a unique name and use the syntax shown above. Duplicate settings, unquoted string values and invalid string escapes are rejected.

ProblemWhat to check
Unknown namespace in a usingIs its declaring source file inside source.root? Did you build the project rather than one file? Does the namespace spelling match?
A function from another namespace is privateMark the intended API public. Including the file or adding a using does not change visibility.
Multiple project files in a directoryPass the desired .xeproj explicitly.
Source root missing or no sources foundCheck the path relative to the project file, the default src directory and the .xe extensions.
Multiple functions named MainCheck whether recursive discovery included another executable example or test program. Use separate source roots for separate programs.
Unknown setting or profileUse the supported schema and the built-in debug/release profile names.

Conditional defines

[build]
defines = ["USE_VULKAN", "ENABLE_DIAGNOSTICS"]

These boolean symbols apply only to this project's source. For a XELIB producer, these local choices are fixed in the library. Invocation flags such as -DENABLE_VALIDATION apply to source projects throughout the build graph. Prebuilt XELIB choices remain fixed. The compiler reserves XENON_ for target/profile built-ins. See conditional compilation for syntax and XELIB target compatibility rules.