Chapter 9. Module system

The Ceylon module architecture enables a toolset which relieves developers of many mundane tasks. The module system specifies:

Thus, developers are never exposed to individual .class files, and are not required to manually manage module archives using the operating system file manager. Instead, the toolset helps automate the management of modules within module repositories.

Circular dependencies between modules are not supported. The Ceylon compiler detects such dependencies and produces an error.

9.1. The module runtime and module isolation

At any time, there may be multiple versions of a certain module available in the virtual machine. Modules execute under the control of the module runtime. The module runtime:

  • obtains modules from module repositories,

  • reads module metadata and recursively loads dependencies, and

  • isolates modules that belong to different assemblies.

Execution of a module begins with a specified toplevel method or class, or with an entry point specified in the module descriptor, and imported modules are loaded lazily as classes they contain are needed. The name and version id of the imported module containing the needed class are determined from the imported package name specified by the compilation unit and the imported module version specified by the module descriptor.

The mechanism behind this is platform-dependent.

9.1.1. Module isolation for the Java platform

In the JVM environment, each version of each module is loaded using a different class loader. Classes inside a module have access to other classes in the same module and to classes belonging to modules that are explicitly imported in the module descriptor. Classes in other modules are not accessible.

Ceylon supports a simplified class loader architecture:

  • The bootstrap class loader owns classes required to bootstrap the module runtime. It is the direct parent of all module class loaders, and its classes are visible to all module class loaders.

  • A module class loader owns classes belonging to a given version of a certain module. Its classes are visible only to classes belonging to the module class loader of a module which declares an explicit dependency on the given version of the first module.

The Ceylon module runtime for the JVM is implemented using JBoss Modules. It is included in the Ceylon SDK.

9.1.2. Module isolation for the JavaScript platform

In the JavaScript environment, modules are loaded using the require() function defined by CommonJS Modules.

There are various implementations of the CommonJS-style require() function, and Ceylon module scripts should work with any of them.

9.1.3. Assemblies

A future release of the language will add support for assemblies, that is, the ability to:

  • package together several interdependent versioned modules into a single archive for deployment as a single well-defined application or service,

  • specify the name and version of the application or service, and

  • override the versions of imported modules declared in modules.ceylon, as defined in §9.3.12 Module descriptors, with assembly-specific module versions.

An assembly archive will probably just be an archived module repository with an assembly descriptor.

9.2. Source layout

A source directory contains Ceylon source code in files with the extension .ceylon and Java source code in files with the extension .java. The module and package to which a compilation unit belongs is determined by the subdirectory in which the source file is found.

The name of the package to which a compilation unit belongs is formed by replacing every path directory separator character with a period in the relative path from the root source directory to the subdirectory containing the source file. In the case of a Java source file, the subdirectory must agree with the package specified by the Java package declaration.

The name of the module to which a compilation unit belongs is determined by searching all containing directories for a module descriptor. The name of the module is formed by replacing every path directory separator character with a period in the relative path from the source directory to the subdirectory containing the module descriptor. If no module descriptor is found, the code belongs to the default module.

Note: the default module is intended only as a convenience for experimental code.

A package or compilation unit may belong to only one module. No more than one module descriptor may occur in the containing directories of a compilation unit.

Thus, the structure of the source directory containing the module org.hello might be the following:

source/
    org/
        hello/
            module.ceylon      //the module descriptor
            main/
                hello.ceylon
            default/
                DefaultHello.ceylon
            personalized/
                PersonalizedHello.ceylon

The source code for multiple modules may be contained in a single source directory.

9.3. Module architecture

Compiled code is automatically packaged into module archives and module scripts by the Ceylon compiler. A module repository is a repository containing module archives, module scripts, and other miscellaneous artifacts. A module archive or module script is automatically obtained from a module repository when code belonging to the module is needed by the compiler or module runtime.

Modules that form part of the Ceylon SDK are found in the module repository in the modules directory of the Ceylon distribution.

Red Hat maintains a central module repository at http://modules.ceylon-lang.org. Read access to this site is free of registration and free of charge. Ceylon projects may apply for a user account which provides write access to the central module repository.

A module belonging to the central module repository must satisfy the following regulations:

  • the first element of the module name must be a top-level internet domain name, and the second element of the module name must be a second-level domain of the given top-level domain owned by the organization distributing the module, and.

  • the module must be made available under a royalty-free license.

For example, a module developed by Red Hat might be named org.jboss.server.

TODO: should we require that module archives be signed using the Java jarsigner tool?

9.3.1. Module names and version identifiers

A module name is a period-separated list of initial lowercase identifiers, for example:

ceylon.language
org.hibernate

It is recommended that module names follow the Java package naming convention embedding the organization's domain name (in this case, hibernate.org). The namespace ceylon is reserved for Ceylon SDK modules. The namespace java is reserved for modules belonging to the Java SDK. The namespace default is reserved for the default module.

It is highly recommended, but not required, that every user-written module have at least three identifiers in its name. Therefore, org.hibernate.orm is strongly preferred to org.hibernate.

Modules may not be "nested". That is, the list of identifiers forming the name of a module may not be a prefix of the list of identifiers forming the name of another module.

A package belongs to a module if the list of identifiers forming the name of the module is a prefix of the list of identifiers forming the name of the package. For example, the packages:

ceylon.language
ceylon.language.assertion
ceylon.language.meta
ceylon.language.meta.declaration

belong to the module ceylon.language. The packages:

org.hibernate
org.hibernate.impl
org.hibernate.cache

belong to the module org.hibernate.

TODO: This might not work out all that well in practice, unless we introduce some additional convention for "extras" modules, for example, modules containing examples. It could be org.hibernate vs org.hibernate_example or org.hibernate.core vs org.hibernate.example.

The name of the default module is default. The default module has no version and cannot be published to a remote repository nor to the local repository cache under ~/.ceylon/repo.

A module version identifier is a character string containing digits, periods, and lowercase letters, for example:

1.0.1
3.0.0.beta

TODO: at some stage we will probably need to add a format for specifying version ranges.

9.3.2. Module archive names for the Java platform

A module archive name is constructed from the module name and version identifier. A module archive name is of the following standard form:

<module>-<version>.car

where <module> is the full name of the module, and <version> is the module version identifier. For example:

ceylon.language-1.0.1.car
org.hibernate-3.0.0.beta.car

The default module has no version, its module archive name is default.car

9.3.3. Module script names for the JavaScript platform

A module script name is likewise constructed from the module name and version identifier. A module script name is of the following standard form:

<module>-<version>.js

where <module> is the full name of the module, and <version> is the module version identifier. For example:

ceylon.language-1.0.1.js
org.hibernate-3.0.0.beta.js

The default module has no version, its module archive name is default.js

9.3.4. Source archive names

A source archive name is of the following standard form:

<module>-<version>.src

For example:

ceylon.language-1.0.1.src
org.hibernate-3.0.0.beta.src

The default module has no version, its source archive name is default.src

9.3.5. Documentation archive names

A documentation archive name is of the following standard form:

<module>-<version>.doc.zip

For example:

ceylon.language-1.0.1.doc.zip
org.hibernate-3.0.0.beta.doc.zip

The default module has no version, its documentation archive name is default.doc.zip

9.3.6. Module archives

A Ceylon module archive is a Java jar archive which:

  • contains a Ceylon module descriptor in the module directory,

  • contains the compiled .class files for all compilation units belonging to the module, and

  • has a filename which adheres to the standard for module archive names.

The module directory of the module archive is formed by replacing each period in the fully qualified package name with the directory separator character. For example, the module directory for the module ceylon.language is:

/ceylon/language

The module directory for the module org.hibernate is:

/org/hibernate

The package directory for a package belonging to the module archive is formed by replacing each period in the fully qualified package name with the directory separator character. For example, the package directory for the package org.hibernate.impl is:

/org/hibernate/impl

Inside a module archive, a .class file is found in the package directory of the package to which it belongs.

Thus, the structure of the module archive for the module org.hello might be the following:

org.hello-1.0.0.car
    META-INF/
        MANIFEST.MF
    org/
        hello/
            module.class       //the module descriptor
            main/
                package.class  //a package descriptor
                hello.class
            default/
                DefaultHello.class
            personalized/
                PersonalizedHello.class

A module archive may not contain multiple modules.

9.3.7. Module scripts

A Ceylon module script is a JavaScript source file which:

  • complies with the CommonJS Modules specification, and

  • has a filename which adheres to the standard for module script names.

9.3.8. Source archives

A source archive is a zip archive which:

  • contains the source code (.ceylon and .java files) for all compilation units belonging to the module, and

  • has a filename which adheres to the standard for source archive names.

Inside a source archive, a Ceylon or Java source file is located in the package directory of the package to which the compilation unit belongs. The package directory for a package belonging to the source archive is formed by replacing each period in the fully qualified package name with the directory separator character.

Thus, the structure of the source archive for the module org.hello might be the following:

org.hello-1.0.0.src
    org/
        hello/
            module.ceylon       //the module descriptor
            main/
                package.ceylon  //a package descriptor
                hello.ceylon
            default/
                DefaultHello.ceylon
            personalized/
                PersonalizedHello.ceylon

A source archive may not contain the source of multiple modules.

9.3.9. Documentation archives

A documentation archive is a zip archive which:

  • contains the module documentation generated by the documentation compiler (.html and resources files), and

  • has a filename which adheres to the standard for documentation archive names.

Inside a documentation archive, HTML source is located in the module-doc directory.

Thus, the structure of the documentation archive for the module org.hello might be the following:

org.hello-1.0.0.doc.zip
    module-doc/
        .resources/
            ...
        index.html
        search.html
        module.ceylon.html
        main/
            index.html
            package.ceylon.html
            hello.ceylon.html
        default/
            index.html
            DefaultHello.html
            DefaultHello.ceylon.html
        personalized/
            index.html
            PersonalizedHello.html
            PersonalizedHello.ceylon.html

A documentation archive may not contain the documentation of multiple modules.

9.3.10. Module repositories

A module repository is a directory structure on the local filesystem or a remote HTTP server.

  • A local module repository is identified by a filesystem path.

  • A remote module repository is identified by a URL with protocol http: or https:.

A publishable module repository is a local module repository, or a WebDAV-enabled remote module repository.

For example:

modules
/usr/bin/ceylon/modules
http://jboss.org/ceylon/modules
https://gavin:secret@modules.ceylon-lang.org

A module repository contains module archives, module scripts, source archives, and documentation archives. The address of an artifact belonging to the repository adheres to the following standard form:

<repository>/<module-path>/<version>/<artifact>

where <repository> is the filesystem path or URL of the repository, <artifact> is the name of the artifact, <version> is the module version, and <module-path> is formed by replacing every period with a slash in the module name.

The default module having no version, its access path does not contain the version.

<repository>/default/<archive>

For example, the module archive ceylon.language-1.0.1.car, module script, source archive ceylon.language-1.0.1.src, and documentation archive belonging to the repository included in the Ceylon SDK are obtained from the following addresses:

modules/ceylon/language/1.0.1/ceylon.language-1.0.1.car
modules/ceylon/language/1.0.1/ceylon.language-1.0.1.js
modules/ceylon/language/1.0.1/ceylon.language-1.0.1.src
modules/ceylon/language/1.0.1/ceylon.language-1.0.1.doc.zip

The module archive org.hibernate-3.0.0.beta.car, source archive org.hibernate-3.0.0.beta.src, and documentation archive belonging to the repository http://jboss.org/ceylon/modules are obtained from the following addresses:

http://jboss.org/ceylon/modules/org/hibernate/3.0.0.beta/org.hibernate-3.0.0.beta.car
http://jboss.org/ceylon/modules/org/hibernate/3.0.0.beta/org.hibernate-3.0.0.beta.src
http://jboss.org/ceylon/modules/org/hibernate/3.0.0.beta/org.hibernate-3.0.0.beta.doc.zip

The module archive org.h2-1.2.141.car and legacy archive org.h2-1.2.141.jar belonging to the repository /usr/bin/ceylon/modules are obtained from the following addresses:

/usr/bin/ceylon/modules/org/h2/1.2.141/org.h2-1.2.141.car
/usr/bin/ceylon/modules/org/h2/1.2.141/org.h2-1.2.141.jar

For each archive, the module repository may contain a SHA-1 checksum file. The checksum file is a plain text file containing just the SHA-1 checksum of the archive. The address of a checksum file adheres to the following standard form:

<repository>/<module-path>/<version>/<archive>.sha1

The compiler or module runtime verifies the checksum after downloading the archive from the module repository.

A module repository may contain documentation generated by the Ceylon documentation compiler in exploded form. A module's documentation resides in the module documentation directory, a directory with address adhering to the following standard form:

<repository>/<module-path>/<version>/module-doc/

For example, the home page for the documentation of the module org.hibernate is:

http://jboss.org/ceylon/modules/org/hibernate/module-doc/index.html

9.3.11. Package descriptors

A package descriptor is defined in a source file named package.ceylon in the package it describes.

PackageDescriptor: Annotations "package" FullPackageName ";"

A package may be annotated shared. A shared package is visible outside the containing module, that is, in any module which imports the containing module.

The package descriptor is optional for unshared packages.

"The typesafe query API."
license ("http://www.gnu.org/licenses/lgpl.html")
shared package org.hibernate.query;

9.3.12. Module descriptors

A module descriptor is defined in a source file named module.ceylon in the root package of the module it describes (the package with the same name as the module).

ModuleDescriptor: Annotations "module" FullPackageName StringLiteral ModuleBody

The literal string after the module name specifies the version of the module.

A module may import other modules.

ModuleBody: "{" ModuleImport* "}"
ModuleImport: Annotations "import" (FullPackageName|StringLiteral) StringLiteral ";"

The name of the imported module may be specified using the usual syntax for a module name, or as a literal string, to allow interoperation with legacy module repositories existing outside the Ceylon ecosystem.

Note: this enables interoperation with Maven.

Note: in Ceylon 1.0 it is illegal to explicitly import the module ceylon.language. The language module is always implicitly imported.

The string literal after the imported module name specifies the version of the imported module.

An imported module may be annotated optional and/or shared.

  • If module x has a shared import of module y, then any module that imports x implicitly imports y.

  • If module x has an optional import of module y, then x may be executed even if y is not available at runtime.

If a declaration belonging to module x is visible outside the module and involves types imported from a different module y, then the module import of y in the module descriptor for x must be shared.

"The best-ever ORM solution!"
license ("http://www.gnu.org/licenses/lgpl.html")
module org.hibernate "3.0.0.beta" {
    shared import ceylon.language "1.0.1";
    import javax.sql "4.0";
}
"The test suite for Hibernate"
license ("http://www.gnu.org/licenses/lgpl.html")
module org.hibernate.test "3.0.0.beta" {
    import org.hibernate "3.0.0.beta";
    TestSuite().run();
}

TODO: do we allow procedural code in the body of a module?