Frogtoss Labs - title image with flowers

Native Project Standards


What follows is a structure for cross-platform C and C++ projects. By adhering to a standard, programmers expend fewer mental cycles navigating projects. Embedded in these project standards is experience gained over thirty years of navigating large game and tool projects. Hopefully they help a few people avoid mistakes.

Most projects do not need most of this. The idea is that you use cherry pick the part of these standards that work for you, and don’t use the remainder.

Some of this functionality makes sense for libraries and some of it makes sense for full applications.

Revision history

[2.0] - September 19, 2026

Small breaking changes after a decade of use.

  • Explicitly ban kebab-case from filenames and directories
  • Remove /build/autobuild which was hard to understand and nobody used
  • Breaking change: standalone tests go in bin like other executables
  • Add Current Working Directory section
  • Add tools current working directory clarification
  • Add /dist directory
  • Formally name many more architectures.
  • Formally name many more operating systems.
  • Breaking change: built exes in /bin instead of a /build subdirectory
  • Add VERSION file
  • Add new distributable archive filenames

[0.4] - September 5th, 2016

  • Add support for tools
  • Add blurb justifying public header files
  • Address third party code scenarios when not in vendors

[0.3] - August 29th, 2016

  • Add standardized platform names for binary dirs

[0.2] - August 26th, 2016

  • Moved obj, bin and lib to project-specific subdirs
  • Address shader source scenario

[0.1] - August 18th, 2016

  • initial; request for comments

Code Project Root

What is checked in to the Code Project Root is everything that is checked in to a single source repo. The assumption here is that your source control package is not that good at binaries, and so, it is used to handle source code only.

If you are into checking in all compiled assets, you can easily do so in the specified directories. Having a separate binary root is really a superset of the binary check-in approach.

See “Binary Project Root” below for more.

File Naming

All filenames should be 7-bit ASCII without spaces and lowercase, except for specified exceptions. Exceptions exist to match existing industrial conventions. Prefer underscores over CamelCase.

Lowercase filenames prevent your build from breaking when porting to Linux and Android where case sensitivity produces conflicts. It also avoids case conflicts when checking out two files with the same name but different cases on Windows that were checked in on Linux. Various source control programs have solutions for this, which means your library becomes potentially incompatible with different source control mechanisms. Just stay away from uppercase.

CamelCase is ultimately a matter of preference, but I have seen a lot of CamelCase users insert underscores anyway once their trees reach a certain degree of complexity. This creates mental effort trying to remember which files conform to two standards and how they conform.

Prefer underscores to hyphens (snake_case, not kebab-case). Files and directories often end up referring to constructs inside the programming language, and hyphens are not available for naming things in native languages. Hyphens are reserved for the Distributable Archive Naming Grammar.

Getting this right up front is important because you break compiles changing it later.

CPU Architectures

We require consistent naming of CPU architectures for tooling, file and directory naming. For situations where there are multiple names for a CPU architecture, we are picking exactly one to use for consistency. What follows is an alias normalization table. Prefer the “used name” instead of the “unused alternatives” unless a codebase warrants the distinction.

Used Name Unused Alternatives
x64 amd64, x86-64, x86_64, intel64, em64t
386 x86, i386, i486, i586, i686, ia32
arm64 aarch64, armv8-a, armv8, arm64-v8a
arm arm32, aarch32, armv7, armv7l, armv6, armel, armhf
ppc64 powerpc64, powerpc64be, ppc64be
ppc64le powerpc64le, powerpc64el, ppc64el
mipsle mipsel, mips32el, mips32le
mips64 mips64be, mips64eb
mips64le mips64el
s390 s390x, systemz, system-z, zarch, z/architecture
riscv64 rv64, riscv64gc
wasm32 wasm, webassembly, webassembly32
wasm64 webassembly64, wasm-memory64, memory64

Sometimes there is a need to specify something that is not an architecture in a field where architectures usually go. In those cases, prefer:

  • web for a bundle of HTML, CSS or JavaScript intended for execution in a web browser
  • universal for a fat binary representing multiple architectures

Operating Systems

As with CPU architectures, operating system names are normalized.

Used Name Unused Alternatives
windows win32, win64
linux gnulinux
macos macosx, osx, darwin
freebsd
netbsd
openbsd
dragonfly dragonflybsd
plan9
solaris sunos
aix
android
chromeos chromiumos
ipados
ios iphoneos
watchos
tvos
visionos

Use browser as an operating system if distributing an application archive that is intended to run in a browser on any operating system.

Environment Variables

xxxROOT is an environment variable containing the root of the current project. xxx is an uppercase prefix of the project. The environment variable can be used to avoid relative path manipulations in build scripts and build files, which improves their readability.

xxxROOT is used instead of a unified name for all projects because projects could become nested, possibly as vendors. Example: NFDROOT

xxxBIN is the path to a corresponding binary repo root. Example: NFDBIN. More on binary repos later on.

Files

In the root:

LICENSE — Needed for all published repos.

README.md — Use caps in name to conform to GitHub standard if publishing to GitHub.

VERSION — One or more numbers separated by dots, no newline. The single source of truth for the project version.

As a best practice, try to remove files from the root that don’t need to be there. Many obnoxious tools force their configs into the project root. Prefer .mise.toml over mise.toml, for example.

Current Working Directory

When distributed, executables are launched outside of application bundles, installer directories, or from search PATHs. During development, expect them to launch relative to the bin directory. E.g. cd bin; program.exe. Not bin/program.exe.

Doing this prevents needing to specify a current working directory, per-computer, per-program for every debugger, profiler or other development tool, as most default to assuming the current working directory is the same directory the program is in.

Simple programs can just find relative paths to resources, e.g., ../content. More sophisticated programs are recommended to use the whereami code library to portably discover where the executable is, so it can be called from a relative path.

/docs

Unbuilt docs go here, along with any build system needed to build the docs.

Built docs go to /build/docs/<output type>.

/src

Root for all project source. If source is conditionally compiled based on a conditional distinction, it can go into subdirectories. For example: src/ps4 or src/posix.

Prefixes to filenames are preferred to directories. For example: video_init.c instead of video/init.c. This enables directories to signify being conditionally included in compiles while still allowing your file explorer to functionally order files by sorting alphabetically.

This directory contains private header files used to build the codebase. Private header files are not needed by users to compile against a library, but are needed for the library to compile itself.

It is not necessary to put private header files in a separate directory; file browsers can simply sort by type.

Prefer src for any compile_commands.json generation so as not to litter the project root.

/src/shaders

For most non-trivial renderers, shaders must be compiled. Therefore, shader source is treated like code and is checked in to the source repo.

Compiled shaders are treated like binary content and should be checked in to the binary project root (below) under the /content folder.

Where shaders vary by renderer backend, the following directories can serve as examples:

  • /src/shaders/glsl_130
  • /src/shaders/es2

For more trivial renderers, store the canonical, up-to-date shaders in the source repo and copy them over to your distribution tree verbatim on change. This ensures your shaders align with the code that is driving them in the current source revision.

src/config/xxxconfig.h

Where xxx is the prefix name for the project. For example: nfdconfig.h. This is put in its own subdirectory because it is sometimes included in other, linking projects. Doing so would mean adding the directory it’s in to the search path. We want to avoid adding /src to the search path, exposing all private header files.

xxxconfig.h is a header file that contains configuration settings for different, named build configurations (such as DEBUG and RELEASE). Where possible, each config option defines a value as 0 or 1, so #if can be used in place of #ifdef in the code. This enables brevity, but also removes a compile-time ambiguity between false and not defined if compiled with -Wundef. Doing so generates a compile warning instead of a runtime bug.

Example:

#pragma once#ifdef DEBUG#  define VERBOSE_LOG 1#endif#ifdef RELEASE#  define VERBOSE_LOG 0#endif

And then, later on in source:

#include "xxxconfig.h"#if VERBOSE_LOGlog("a lot of extra stuff");#endif

Prefer the use of functionality-specific preprocessor tests to #ifdef DEBUG. This allows additional configuration types to be created without changing the entire codebase.

For example, a user may want to add a PROFILE configuration which is like release but with symbols. Or a FINAL configuration, which is like release but omits the frame pointer.

xxxconfig.h is not generated by a build step; it should contain everything needed to build based on a single compiler define. However, if you decide you must build it, do not generate different configs based on the CPU architecture. Doing so creates real difficulties for fat binary architectures which must include your xxxconfig.h.

/src/3rdparty

In some cases, it is advantageous to insert third party code right into your own build and project rather than produce a library. The excellent STB libraries are one such example.

If the third party files do not follow the directory or naming conventions of the Native Project Standards, or if they become distastefully numerous, placing them in /src/3rdparty reduces clutter.

Do not add an additional include search path to your compile settings for this directory. Doing so erases the opportunity to signify you are including third-party code. Do this instead:

#include "3rdparty/stb_image.h"

/include

This directory contains public header files which are used to call library functions and access library types. If the project is not a library, just intermingle the header files with the source.

It is rare to need a public header file include directory for executable projects, but possible. An application may export settings for compatibility with plugins or mod support.

If you are building an executable project, consider removing this directory until it is expressly needed so as not to confuse developers who would put private header files here instead of in /src where they are intended to go.

/build

The nuts and bolts of build logic goes here. This can be a batch file, CMakeLists or something else.

Optional: Premake

The preferred approach, if at all possible, is to use premake to generate project files. The project files should then be checked in, in subdirectories. This means Premake is a package maintainer’s tool, not a necessary dependency for user compilation.

The premake script can be checked in at /build/premake5.lua. Virtually no other files should be in the build root, so new users aren’t confused and tempted to build from here instead of from a subdirectory.

Project subdirectories are in the form: /build/<buildtype>.

/build/<buildtype>/obj

/build/<buildtype>/obj is not checked in and contains build intermediates including .o, .map and .pdb.

/tools

/tools is a top-level directory, sibling to /src and /build. It contains small scripts and binaries that assist your project’s development in some way.

Many projects end up with multiple tools directories, often by accident. By making /tools a top level directory, it is more likely to be known by developers, stopping them from making competing directories. It is disorderly to have multiple tools directories in a project.

If /tools/bin exists, it is intended to be added to the user’s PATH so scripts can be executed globally. Scripts here should have their executable bit set and use shebang lines where possible.

If a tool is useful for more than just your project and reaches a non-trivial scope, consider creating a different project for it and moving it out of tools entirely.

Tools are assumed to have the current working directory be the same directory the tool is in, provided the tool is not being called from the PATH.

/vendors

Vendors contains all third party libraries that cannot be trivially copied into src/3rdparty. Code is expressly checked in — there are no git submodules. Submodules are not automatic and actually get in the way of diffing upgraded vendor libraries and browsing your git log.

/vendors is intentionally not under /src, which means developers can recursively grep /src or create symbol caches (etags, Source Insight, etc.) without accidentally capturing all vendor symbols. Alternatively, they can run their recursive tools from the project root and capture all vendor source if they like.

If unzipping a vendor generates a directory with a version, the version number is removed before check-in. Example: /vendors/Python27 becomes /vendors/python. This removes the need for reworking multiple build scripts when upgrading vendor versions.

For Frogtoss projects, /vendors/ftgcompile.py will exist, which goes through each directory and compiles each vendor for each available target architecture, calling a corresponding ftgcompile.py in each vendor’s subdirectory.

/vendors/lib

/vendors/lib/<arch> contains the compiled vendor libs. This is a repository for all built libs, and is the singular link directory that your application needs to link against all libraries for a given architecture.

In a Universal binary platform, <arch> is omitted.

This directory’s contents are not checked in.

/vendors/include

/vendors/include contains the public include files for all built libs. It is the singular include directory your application needs, hopefully.

Realistically, in order to achieve this, the ftgcompile.py script responsible for compiling each vendor would need to move the public include files to this directory. This fails on fat binary operating systems where include files differ based on the CPU architecture being compiled. Your vendor is being terrible to you. In this case, you will have to handle this within the standards of the vendor’s library.

This directory’s contents are not checked in — add it to .gitignore.

/test

Unit tests should not produce separate binaries! Conditionally compile all unit tests in so they run on application startup. This avoids the need to separately build and run unit tests when porting to a new platform, something that inevitably happens later than core exe porting or never at all when targeting deploy-to systems such as consoles or mobile.

Tests that go here include everything that has to actually run the compiled binary to work properly. Ideally, these should be one file per program. If they are larger projects, consider creating another project root for them rather than cluttering them up.

Built tests (if they need to compile at all) go in the same /bin subdirectory as other executables.

/bin

Executables go in /bin/<os>_<arch>. Arch is optional. Use it if the project needs to differentiate between architectures.

Example: /bin/windows_386/game.exe.

In common development scenarios, it is convenient to have the most recent built executable right at bin/. Upon compilation success, provide a symlink from bin to the specific subdirectory.

Note: If developer mode is turned on, Windows supports symlinks now, so this works on Windows.

bin/game.exe -> bin/windows_386/game.exe.

When providing symlinks, do not symlink any accompanying PDBs. By default, executables are linked with the full path and filename of the PDB.

/content

If content isn’t packaged, it goes in /content. It is largely up to the application how this is set up.

However, if there are base assets and game-specific assets, /content/base should contain the base assets which are essential to the startup of the application. Examples of this would include a debug font and a mouse cursor. This allows for barebones distributables later on.

/dist

/dist is the directory for archives, or installers, produced for distribution. Installer generator scripts output here. /dist is ignored; not checked in.

Continue to prefer bin for unarchived compiled executables.

Distributable Archive Naming Grammar

By defining a grammar for distributable archive filenames, it is possible to know their full applicability at rest. Doing this up front reduces the release management burden.

Use the following grammar:

<project>-<os>-<arch>[-<arch>...][-<attribute>...]-<version>[-pre].<extension>

All fields are lowercase and substitute underscores for spaces. Hyphens separate fields.

  1. project name
  2. exactly one operating system
  3. one or more architectures (or universal)
  4. zero or more attributes separated by hyphens — this is the place to put project-unique identifiers such as regional builds
  5. version, including dots
  6. -pre, indicating the archive is internal only, or missing if it is published
  7. The extension, such as .msi or .zip.

CPU Architecture names.

Operating System names.

Here are some valid examples:

  • dist/grain-macos-arm64-1.0.0.dmg
  • dist/fun_game-windows-x64-2.0-pre.msi

Binary Project Root

The binary, distributable project root sits sibling to the code project root. It does not nest. This allows you to use a separate binary-friendly source control system for built versions of your application.

This is a good strategy for binary-heavy cross-platform games, but often overkill for smaller applications.

The assumption is that you will eventually have build servers which compile executables and check them in here. Therefore, one tree exists which contains all executables and dependencies and compiled asset resources simultaneously. Generating a distributable installer from the head of this source repo is how you distribute the most recent version of your project.

xxxBIN (described above) references this directory. This enables you to easily launch your application relative to the root of the binary folder. It also lets you check out multiple, differing versions of the binaries folder and test them with a single EXE just by changing the xxxBIN environment variable path.