Managing Sources

This chapter covers how GPR locates source files: directory scanning, explicit file lists, exclusions, and naming conventions. Understanding these mechanisms lets you organize your source tree freely without forcing a particular directory layout.

How source discovery works

When a project is loaded, GPR tools scan every directory listed in Source_Dirs and collects all files whose extensions match the active languages. For Ada, the default extensions are .ads (specs) and .adb (bodies); for C, .c and .h; and so on.

Files that do not match any known extension for the declared languages are silently ignored.

Source directories

Source_Dirs accepts a list of directory paths, relative to the project file. Glob patterns are expanded at load time:

for Source_Dirs use ("src", "generated/src", "platform/" & Platform);

The ** pattern matches any number of directory levels, enabling a recursive scan:

for Source_Dirs use ("src/**");

This collects sources from src/ and every subdirectory beneath it, however deeply nested.

To collect sources only from the project file’s own directory, use a single dot:

for Source_Dirs use (".");

To declare a project with no sources - one that exists purely to share attributes with other projects - use the abstract qualifier:

abstract project Support is
   --  no sources; attributes only
end Support;

An abstract project may not declare Main, Object_Dir, or Exec_Dir; those attributes are meaningful only for projects that produce build artifacts. See Project Extension for a typical use of abstract projects.

Tip

Avoid overlapping Source_Dirs between two projects in the same tree. If the same source file is visible to two projects, a duplicate-unit error is reported at load time.

Explicit source lists

For finer control, you can enumerate sources explicitly instead of scanning a directory.

Source_Files lists individual file names (not paths) directly in the project file:

for Source_Files use ("utils.ads", "utils.adb", "config.ads");

Only the named files are included, even if other source files exist in Source_Dirs.

For larger lists, Source_List_File points to a text file containing one file name per line:

for Source_List_File use "sources.txt";

This is useful when the list is generated by a build script or version control system.

Excluding sources

Excluded_Source_Files removes specific files from an otherwise directory-scanned project:

for Excluded_Source_Files use ("old_impl.adb", "stub.ads");

The complement of Source_List_File, Excluded_Source_List_File points to a text file listing the files to exclude:

for Excluded_Source_List_File use "excluded.txt";

Exclusions apply after the directory scan, so you can keep sources in the same directory without including all of them in the build.

Ada naming conventions

The default naming convention for Ada follows the GNAT standard:

  • Specs use the .ads extension, bodies use .adb.

  • Child unit names use - as the separator: the body of My_Lib.Utils lives in my_lib-utils.adb.

  • Unit names map to file names in lower case.

Ada source files and unit names are tightly coupled: when a file with the right suffix (.ads or .adb) is found in a source directory, GPR derives the corresponding unit name by reversing the naming convention (stripping the suffix, replacing the dot-replacement character back to ., and so on). If the result is not a valid Ada identifier, the file is silently ignored.

For example, my_source__unix.adb maps to my_source__unix - a name containing a double underscore, which is not valid in Ada. GPR will not recognize this file as an Ada source during directory scanning.

Tip

To include a file whose name would otherwise be rejected, add an explicit per-unit mapping in the Naming package (see Per-unit overrides below).

If your project uses a different convention, the Naming package lets you override it.

Changing the dot replacement character

To use __ instead of - as the child-unit separator:

package Naming is
   for Dot_Replacement use "__";
end Naming;

With this setting, the body of My_Lib.Utils is expected in my_lib__utils.adb.

Changing the default suffixes

package Naming is
   for Spec_Suffix ("Ada") use ".1.ada";
   for Body_Suffix ("Ada") use ".2.ada";
end Naming;

Per-unit overrides

Individual units can be mapped to arbitrary file names:

package Naming is
   for Spec ("My_Lib.Utils") use "ml_utils_s.ada";
   for Body ("My_Lib.Utils") use "ml_utils_b.ada";
end Naming;

Per-unit mappings take precedence over suffix rules. This is the mechanism GPRname uses when it generates a project file for a source tree with non-standard naming (see Working with Tools).

Multiple suffixes per language

Body_Suffix and Spec_Suffix accept a single value per language. A project that contains source files with different suffixes for the same language - for example .cc and .cpp both for C++ - cannot be described by a single suffix declaration.

Two options are available:

  • Rename all files to a common suffix. This requires no project structure changes and is the simplest solution when the source tree is under your control.

  • Split into two projects with limited with. Create a companion project that covers the extra suffix, sharing the same Source_Dirs and Object_Dir as the primary project. The companion imports the primary with a regular with; the primary references the companion back with limited with to break what would otherwise be a circular dependency. The companion also sets Spec_Suffix to a value that matches no file, so it does not claim spec files already owned by the primary. The compiler settings are shared by renaming the package:

    --  prj.gpr  (handles .cpp files)
    limited with "prj_cc_support";
    
    project Prj is
       for Languages   use ("C++");
       for Source_Dirs use ("src");
       for Object_Dir  use "obj";
    
       package Naming is
          for Body_Suffix ("C++") use ".cpp";
          for Spec_Suffix ("C++") use ".h";
       end Naming;
    
       package Compiler is
          for Switches ("C++") use ("-O2");
       end Compiler;
    end Prj;
    
    --  prj_cc_support.gpr  (handles .cc files)
    with "prj";
    
    project Prj_Cc_Support is
       for Languages   use ("C++");
       for Source_Dirs use Prj'Source_Dirs;
       for Object_Dir  use Prj'Object_Dir;
    
       package Naming is
          for Body_Suffix ("C++") use ".cc";
          for Spec_Suffix ("C++") use ".no_match";  --  no .no_match files exist
       end Naming;
    
       package Compiler renames Prj.Compiler;
    end Prj_Cc_Support;
    

    Build with gprbuild -P prj.gpr: GPRbuild loads both projects and compiles .cpp files via Prj and .cc files via Prj_Cc_Support, depositing all objects in the shared obj/ directory.