Luce Base
LuceSourceLuciaOS

Modules and packages

A module is one source file (or one directory of files). A package is a project: a directory with a package.prisma manifest, its modules, and its dependencies. luc builds packages and fetches their dependencies. Reference: Modules, packages, and tests.

Modules#

A module is named by its path under the package's source directory, src/:

FileModule
src/main.lucbmain
src/image/color.lucbimage.color
src/net/http/client.lucbnet.http.client

So a file's name, without .lucb, must be a valid name: color_space.lucb, not color-space.lucb.

  • Declarations are private to the module unless pub. A pub function's signature can only use pub types.
  • A module's top-level declarations can refer to each other in any order.
  • Modules may not import each other in a cycle.
  • A ## block at the top of the file, followed by a blank line, documents the module.

A module in several files#

When one module grows too large for a file, it can be a directory of the same name containing an ORDER file that lists its files in order:

src/canvas/
  ORDER            canvas.lucb
                   drawing.lucb
  canvas.lucb      declares struct Canvas
  drawing.lucb     extend Canvas: ...

The files share one scope, as if they were one file, and errors name the file they occur in. A type declared in one file can take more methods in another with extend Type: (see Functions and methods). Importing the module is unchanged: import canvas.

Imports#

FormMakes available
import image.colorthe module, as color (its last name)
import image.color as palettethe module, as palette
from image import colorthe module image.color, as color
from image import color, geometryseveral modules from one directory
from image.color import Rgbthe declaration Rgb, by its own name
from image.color import Rgb as Coloura declaration under another name
from luce_std import filesthe module files of the package luce-std
from luce_std import files, pathsseveral modules of one package
from luce_crypto import native as cryptoa package's module under another name
from shapes_lib.geometry import circlea module in a directory of another package

The rules, in brief:

  • import keeps names qualified: color.rgb(...). from brings names in directly, as in Python.
  • from X import a imports modules when X is a package or a directory of modules, and declarations when X is a module.
  • A package is not a module. import luce_ui is the error "a package is not a module: import one of its modules".
  • from io import Writer brings only Writer. Using io.stdout() as well needs import io too.
  • There are no wildcard imports and no relative imports (from . import x).
  • An import can appear anywhere at the top level of a module, near the code that uses it.
  • Importing the same thing twice is an error.

Standard modules#

These come with the compiler and are imported by their bare names:

ModuleContents
memoryallocators, heap, allocator, allocate, size_of, align_of, offset_of, copy, move, set, read, write, frame
iostdout(), stderr(), Writer, Location, the user's directories
osthe target platform as constants, environment variables, the process
stringsformat, copy, release, join, split, find, trim, number parsing
threadspawn, Handle, sleep
syncMutex, Condition, Once, Semaphore
atomicfence, Ordering
timenow, unix, since
luceComparable, Display, Iterable, Iterator, location
cC's types, errno()
testinghelpers for tests

Their names are reserved: no module of yours can be called io, memory, c and so on. Everything else, including files, paths, processes, networking and maths, is in packages, starting with luce-std. The Library lists every module and function.

Packages#

A package is a directory with package.prisma and sources under src/:

shapes-lib/
  package.prisma
  src/
    geometry/circle.lucb
    canvas/...
    internal.lucb
  tests/

The manifest is written in Prisma, a small configuration format:

#prisma 4.0
def package "shapes-lib" {
    str owner = "you"
    str version = "0.1.0"
    str kind = "package"
    str language = "luce-base"
    str[] public = ["geometry.circle", "canvas"]

    def dependency "luce-std" {
        str owner = "dymokomi"
    }
}
FieldMeaning
package "name"the package's name; shapes-lib is imported as shapes_lib
owner, versionwho publishes it, and its version, major.minor.patch
kindpackage (a library), tool (a command-line program) or application (a desktop program)
languageluce-base
entryfor a tool or application, its main module, src/main.lucb
str[] publicthe modules other packages may import, by module name
str sourcethe source directory, if not src
description, readmeone line of at most 256 bytes, and the README file, for the registry
licenseoptional: the package's license as an SPDX expression, "MIT OR Apache-2.0"
def dependencya package this one uses (below)
def nativeC sources and libraries to build with (see C)
def taska named command for luc run <task>

Each field is set once in its element: a second str[] public = ... in the same def is an error at the line that repeats it, `package.prisma:5:11: public is set twice in one element, from the compiler and from luc` alike.

A package's own modules import each other by their names, public or not. Other packages may import only the public ones; anything else is "the module is not among the package's public ones".

The package name is also the identity of the package's error codes, so two packages' code 1 are different codes.

Dependencies#

A dependency comes from the registry, or from a directory beside the project:

def dependency "luce-std" {
    str owner = "dymokomi"
}

def dependency "shapes-lib" {
    str path = "../shapes-lib"
}

luc add owner/name adds a registry package with no version: its newest release, which luc.lock records. A project that must hold back gives a caret version, luc add owner/name@^0.3.2 (any 0.3.x from 0.3.2). luc add ../path adds a local one. A dependency's own dependencies are resolved relative to it.

luc.lock records the exact version of every registry package in the build, and the SHA-256 of its source. Commit it: it makes builds reproducible. Registry packages are unpacked into .luc/deps/; do not commit that directory.

CommandDoes
luc add owner/nameadd a registry package and lock it
luc add ../pathadd a package checked out beside this one
luc remove nameremove a dependency
luc lockresolve every dependency and rewrite luc.lock
luc sync [--offline]make .luc/deps/ match luc.lock; --offline uses only the download cache

Builds sync automatically and never contact the network unless something is missing. Reading from the registry needs no account.

Building and running a package#

CommandDoes
luc build [--release]build into build/<name>
luc run [--release] [-- args]build and run, passing args to the program
luc testbuild and run the tests (see Testing)
--diagnostic on build, run, testthe diagnostic profile, into build/<name>-diagnostic (Memory)
luc checktype-check without building
luc fmt [--check]format the sources, or check that they are formatted
luc cleanremove build/
luc run <task>run a task declared in the manifest

A task runs a shell command, after the tasks it depends on:

def task "assets" {
    str cmd = "python3 tools/pack_assets.py"
}
def task "release" {
    str cmd = "luc build --release"
    str[] depends = ["assets"]
}

Installing and publishing#

luc install owner/name downloads, builds and installs a tool (its command goes into ~/.luce/bin) or an application (into the system's applications folder). luc list shows what is installed, luc update name upgrades it, and luc uninstall name removes it.

Publishing to pkg.luciaos.com needs an account. Once logged in (luc login), luc publish -m "what changed" checks that the work is committed, tags the version in package.prisma as v<version>, and pushes it; the registry publishes the source of that tag as the release. Raise version and commit before publishing the next one.