Skip to content

Packages

A package is Jiang’s project boundary. A directory with package.jiang can be compiled from its package root. Put shared public APIs in the root file and import implementation modules as needed.

Use a fixed package.jiang file:

#package {
    name = "app";
    version = "1.0.0";
    root = "src/main.jiang";
    type = .bin;
}

Fields:

  • name: package name; defaults to the directory name.
  • version: package version; optional. The compiler accepts ASCII letters, digits, ., _, +, and -.
  • root: entry source file; defaults to <name>.jiang.
  • type: package kind; defaults to .lib; use .bin for an executable. .lang marks a custom-syntax provider package.

When the compiler input is a directory, it reads package.jiang and compiles the source file named by root.

Declare local source dependencies in dependencies { ... }:

#package {
    name = "app";
    version = "1.0.0";
    root = "src/main.jiang";
    dependencies {
        util = "../util";
    }
}

Inside the package, import a dependency by alias:

import util;

Int main() {
    return util.answer();
}

import util; uses the dependency alias and gives access to the dependency root’s public API.

Cross-package access only sees the dependency package root module’s public namespace:

  • public declarations in the root file are package API.
  • public import in the root file re-exports a module namespace.
  • public alias in the root file re-exports a public symbol. A function alias preserves the target’s public overload family and does not expose private overloads.
  • public declarations in non-root files are not automatically package API.

This makes the root file the explicit API boundary of a package.

Do not use a quoted path to import dependency source across package boundaries:

import "../util/src/util.jiang"; // error: cross-package file import

Declare the dependency in package.jiang, then write:

import util;

Package dependency cycles are rejected. Module import cycles are currently allowed.

Only the root package root module contributes the executable entry. A dependency package may define a function named main, but it is treated as an ordinary function.

@entry(main) can select a root function with any name; otherwise main remains the default. The entry must be non-generic, synchronous, parameterless and return an integer or Void. Lang and generate use @entry(lang) and @entry(generate) in the selected root file and may be private. Each role has at most one explicit entry per root.

Register provider dependencies in dependencies { ... }. lang <name> { ... } and generate <name> { ... } select aliases, rather than declaring another dependency source. An internal generator uses module instead of package. See generation for configuration examples. The package entry is package.jiang; fields use =, semicolons and named child blocks.

#package provides a public constant named info. Import the package entry from src/main.jiang to use the project’s version directly:

import std;
import "../package.jiang";

Int main() {
    std.fs.File output! = std.io.stdout();
    Void written = try output.write_all(package.info.version) catch { return 1; };
    return 0;
}

You can also pass package.info to a function accepting std.jiang.PackageInfo. Change version and rebuild to update the value used by your program. There is no second version string to maintain. Do not declare another info in the package entry.

Configuration can use ordinary Jiang expressions and independent helpers. It cannot depend on the root, dependencies, language providers or generated output it is preparing. Dependency paths are relative to the package directory; cross-package imports use dependency aliases.

#doc may appear between fields and child blocks. These documents do not attach to individual fields and do not change configuration values. Put #doc before #package to document the package declaration.