Modules
A directory (including its all children) is a module if it contains a file named module.ens
.
Below is the list of configurations of module.ens
.
Table of Contents
target
The field target
defines the entry points of a module. It should look like the following:
{
// ..
target {
TARGET-1 {
main "path/to/source/file-1.nt"
},
// ..
TARGET-N {
main "path/to/source/file-n.nt"
},
},
// ..
}
Suppose that your module has the following target
in module.ens
:
{
// ..
target {
foo {
main "foo.nt",
},
bar {
main "item/whatever.nt"
},
},
// ..
}
In this case, your module has two targets: foo
and bar
. The entry point of foo
is the main
function in (source-dir)/foo.nt
. The entry point of bar
is the main
function in (source-dir)/item/whatever.nt
.
The names in target
can be specified when running neut build
. For example, given the above definition of target
, you can run neut build foo
.
The names of targets are also used as the names of executables. For example, if you run neut build foo --install ./bin/
, an executable named foo
will be created under the directory ./bin/
.
The field target
is optional. The default value of target
is {}
.
compile-option
You can add compile-option
to a target as follows:
{
target {
foo {
main "foo.nt",
// ↓ here
compile-option [
"-g",
"-O0",
"-fsanitize=address",
"$SOME_ENV_VAR",
"$(some-command arg)",
],
},
},
}
The compiler passes the options specified here to clang
when compiling LLVM IRs into object files.
In compile-option
, you can use environment variables and shell interpolations.
The field compile-option
is optional. The default value of compile-option
is []
.
link-option
You can add link-option
to a target as follows:
{
target {
foo {
main "foo.nt",
// ↓ here
link-option [
"-g",
"-O0",
"$SOME_ENV_VAR",
"$(some-command)",
],
},
},
}
The compiler passes the options specified here to clang
when linking object files.
In link-option
, you can use environment variables and shell interpolations.
The field link-option
is optional. The default value of link-option
is []
.
build-option
You can add build-option
to a target as follows:
{
target {
foo {
main "foo.nt",
// ↓ here
build-option [
"$(pkg-config openssl --libs --cflags)",
"whatever",
],
},
},
}
Adding an element to build-option
is the same as adding the element to both compile-option
and link-option
.
In build-option
, you can use environment variables and shell interpolations.
The field build-option
is optional. The default value of build-option
is []
.
dependency
The field dependency
defines the dependencies of a module. It should look like the following:
{
// ..
dependency {
foo {
digest "(base64url-encoded sha256 checksum)",
mirror [
"URL-1",
// ..
"URL-N",
],
enable-preset <true | false>, // ← optional field
},
// ..
bar { .. },
},
// ..
}
An example of dependency
:
{
// ..
dependency {
core {
digest "ub3MUXVac9F1rebIhl_Crm2_GJ7PzCAekgp8aYH3-mo",
mirror [
"https://github.com/vekatze/neut-core/raw/main/archive/0-38.tar.zst",
],
enable-preset true,
},
some-package {
digest "F_ST8PtL9dLCDWVZ4GpuS7sviUU0_-TUz2s6iw-86KU",
mirror [
"https://example.com/foobarbuz/packages/22-3.tar.zst",
],
},
},
// ..
}
The field digest
specifies the checksum of the tarball of the dependency. The digest is a Base64URL-encoded SHA256 checksum of the tarball. This digest is the "real" name of this dependency, and used as an identifier.
The field mirror
specifies a list of URLs from which the compiler can fetch the tarball. When running neut get
, the compiler will try to get the tarball if necessary, using this list from the beginning to the end.
The optional field enable-preset
specifies whether to import preset
s automatically, as in "prelude" in other languages. This field should only be used (and set to be true) with the core library. For more information, see the explanation of preset
in this section.
The field dependency
is optional. The default value of dependency
is {}
.
archive
The field archive
defines the path of the directory into which the subcommand neut archive
store tarballs. It should look like the following:
{
// ..
archive "my-archive",
// ..
}
The field archive
is optional. The default value of archive
is ./archive/
.
cache
The field cache
defines the path of the directory to store object files, executables, dependencies, etc. It should look like the following:
{
// ..
cache ".cache",
// ..
}
The field cache
is optional. The default value of cache
is ./cache/
.
source
The field source
defines the path of the directory to store source files. It should look like the following:
{
// ..
source ".",
// ..
}
The field source
is optional. The default value of source
is ./source/
.
prefix
The field prefix
defines the aliases of source files. It should look like the following:
{
// ..
prefix {
Foo "this.foo",
// ..
Bar "this.item.bar",
},
// ..
}
Each field in prefix
specifies an alias of the specified source file. For example, given the definition above, the code
import {
this.foo,
this.item.bar {some-func},
}
can be rewritten into the following:
import {
Foo,
Bar {some-func},
}
The prefixes specified in a module.ens
of a module can be used only in the module.
The field prefix
is optional. The default value of prefix
is {}
.
foreign
The field foreign
defines a way to compile external source files. It should look like the following:
{
// ..
foreign {
input [
"source/foo.c",
"source/bar.c",
],
output [
"foo.o",
"bar.o"
],
script [
"{{clang}} -c -flto=thin -O2 source/foo.c -o {{foreign}}/foo.o",
"{{clang}} -c -flto=thin -O2 source/bar.c -o {{foreign}}/bar.o",
]
}
}
input
The field input
specifies the list of external source files. The paths are relative to the root of the module.
When running neut archive
, the compiler adds all the input
files to the resulting tarballs.
output
The field output
specifies the resulting files of foreign source files. The paths are relative to a directory named foreign directory. You can find a foreign directory in the build directory.
When running neut build
, the compiler links all the output
files in the foreign directory (in addition to Neut's "domestic" object files).
script
The field script
specifies how to compile external source files. When running neut build
, the compiler executes the specified commands immediately after resolving all the import
s.
In the field script
, you can use the following placeholders:
{{clang}}
: Theclang
used by the compiler{{foreign}}
: The foreign directory
The compiler skips running the script
if all the files in output
are newer than input
.
When running the script
, the compiler sets the current working directory to the module's root directory.
Notes
The field foreign
is optional. The default value of foreign
is:
{
input [],
output [],
script [],
}
An example of foreign
can be found in the core library.
The compiler links the resulting foreign object files without any name mangling. You're strongly encouraged to prefix names in your foreign sources with your module name and the major version to avoid name collision. You can find an example of prefixed names here.
static
The field static
defines the list of static files that can be embedded into source files at compile time. It should look like the following:
{
// ..
static {
some-file "relative/path/from/the/module/root/to/some-file.txt",
other-file "relative/path/from/the/module/root/to/other-file.txt",
},
// ..
}
You can use the keys defined here in source files using import
and include-text
:
// foo.nt
import {
// ..
static {some-file, other-file}
// ..
}
define use-some-file(): unit {
let t1: &text = include-text(some-file) in
let t2: &text = include-text(other-file) in
print(t1);
print(t2)
}
After specifying a key of a static source file in import
, you can use it in include-text
to embed the file's content to the source file at compile time. Here, include-text
assumes that the encoding of the static file is UTF-8.
The compiler triggers recompilation when necessary by comparing the modification times of static resources and source files. In the code above, for example, the compiler recompiles foo.nt
if you modify the content of some-file.txt
.
The field static
is optional. The default value of static
is {}
.
preset
The field preset
defines the list of names that must be imported implicitly when the module is used as a dependency. It should look like the following:
{
// ..
preset {
foo ["my-func", "other-func"],
item.bar ["hoge", "pohe"],
},
// ..
}
In the example above, the current module is expected to have the following files:
(source-dir)/foo.nt
that contains the definitions ofmy-func
andother-func
(source-dir)/item/bar.nt
that contains the definitions ofhoge
andpohe
The field preset
is used in combination with enable-preset
in dependency
.
Suppose we released a module that contains the definition of preset
as in the above. Also, suppose someone is developing a module MMM
and they added our module to MMM
's dependency:
// module.ens in MMM
{
// ..
dependency {
sample {
digest "BASE64_URL_ENCODED_SHA256_CHECKSUM",
mirror ["SOME_URL"],
enable-preset true,
},
// ..
},
// ..
}
In this case, source files in MMM
imports our preset names automatically since enable-preset
is true
.
As an example, suppose a file in MMM
contains an import
like the below:
import {
sample.foo {my-func, other-func},
sample.item.bar {hoge, pohe},
}
define buz() {
let i = my-func() in
print-int(i)
}
This code is the same as the following since the preset is enabled:
define buz() {
let i = my-func() in
print-int(i)
}
The field preset
is expected to be used as a way to realize "preludes" in other languages.
The field preset
is optional. The default value of preset
is {}
.
inline-limit
The field inline-limit
defines the limit on recursion performed during compilation. It should look like the below:
{
// ..
inline-limit 100000,
// ..
}
During compilation, the compiler performs possibly recursive computation when:
- type-checking, and
- expanding the definitions of inline functions.
The inline-limit
specifies a limit here. If the limit is exceeded, the compiler reports an error like the following:
/path/to/file.nt:123:45
Error: Exceeded max recursion depth of 1000 during (..)
The field inline-limit
is optional. The default value of inline-limit
is 1000000
.
antecedent
The internal field antecedent
defines the list of older compatible versions. This field should look like the following:
{
// ..
antecedent [
"Bp8RulJ-XGTL9Eovre0yQupJpeS3lGNk8Q6QQYua7ag",
// ..
"zptXghmyD5druBl8kx2Qrei6O6fDsKCA7z2KoHp1aqA",
],
// ..
}
This information is used to select the newest compatible version of the module. For more information, see the explanation on neut archive
in Commands.
The field antecedent
is optional. The default value of antecedent
is []
.
This field must be modified only by the compiler. If you modify this field manually, the behavior of the compiler is undefined.
Internally, the compiler treats a module as a library if and only if the module's module.ens
contains the key antecedent
.