Built-in support for modularity is a major goal of the Ceylon project, but what am I really talking about when I use this word? Well, I suppose there's multiple layers to this:
- Language-level support for a unit of visibility that is bigger than a package, but smaller than
all packages
. - A module descriptor format that expresses dependencies between specific versions of modules.
- A built-in module archive format and module repository layout that is understood by all tools written for the language, from the compiler, to the IDE, to the runtime.
- A runtime that features a peer-to-peer classloading (one classloader per module) and the ability to manage multiple versions of the same module.
- An ecosystem of remote module repositories where folks can share code with others.
I'm not going to get into a whole lot of fine detail of this, partly because what I have written down in the language spec today will probably change by the time you actually get to use any of this stuff, but let me give you a taste of the overall architecture proposed.
Module-level visibility
A package in Ceylon may be shared or unshared. An unshared package (the default) is visible only to the module which contains the package. We can make the package shared by providing a package descriptor:
Package package {
name = 'org.hibernate.query';
shared = true;
doc = "The typesafe query API.";
}
(Alert readers will notice that this is just a snippet of Ceylon code, using the declarative
object builder syntax.)
A shared package defines part of the public
API of the module. Other modules can directly access shared declarations in a shared package.
Module descriptors
A module must explicitly specify the other modules on which it depends. This is accomplished via a module descriptor:
Module module {
name = 'org.hibernate';
version = '3.0.0.beta';
doc = "The best-ever ORM solution!";
license = 'http://www.gnu.org/licenses/lgpl.html';
Import {
name = 'ceylon.language';
version = '1.0.1';
export = true;
},
Import {
name = 'java.sql';
version = '4.0';
}
}
A module may be runnable. A runnable module must specify a run() method in the module descriptor:
Module module {
name = 'org.hibernate.test';
version = '3.0.0.beta';
doc = "The test suite for Hibernate";
license = 'http://www.gnu.org/licenses/lgpl.html';
void run() {
TestSuite().run();
}
Import {
name = 'org.hibernate'; version = '3.0.0.beta';
}
}
Module archives and module repositories
A module archive packages together compiled .class files, package descriptors, and module descriptors into a Java-style jar archive with the extension car. The Ceylon compiler doesn't usually produce individual .class files in a directory. Instead, it directly produces module archives.
Module archives live in module repositories. A module repository is a well-defined directory structure with a well-defined location for each module. A module repository may be either local (on the filesystem) or remote (on the Internet). Given a list of module repositories, the Ceylon compiler can automatically locate dependencies mentioned in the module descriptor of the module it is compiling. And when it finishes compiling the module, it puts the resulting module archive in the right place in a local module repository.
(The architecture also includes support for source directories, source archives, and module documentation directories, but I'm not going to cover all that today.)
Module runtime
Ceylon's module runtime is based on JBoss Modules, a technology that also exists at the very core of JBoss 7. Given a list of module repositories, the runtime automatically locates a module archive and its versioned dependencies in the repositories, even downloading module archives from remote repositories if necessary.
Normally, the Ceylon runtime is invoked by specifying the name of a runnable module at the command line.
Module repository ecosystem
One of the nice advantages of this architecture is that it's possible to run a module straight off the internet
, just by typing, for example:
ceylon org.jboss.ceylon.demo -rep http://jboss.org/ceylon/modules
And all required dependencies get automatically downloaded as needed.
Red Hat will maintain a central public module repository where the community can contribute reusable modules. Of course, the module repository format will be an open standard, so any organization can maintain its own public module repository.
I have several questions regarding modules and packages:
1. Packages: Do I have to add a separate package file for every shared package?
2. Runnable modules: Is this just for functions or also usable as something like an osgi-like activator? Say, several modules provide several services which form an application. I want to load a module at runtime and I want to initialize/register the services inside the module so that other modules may find them.
P.S.: could somebody please fix the timeout issues which appear while writing comments...
With what's in the spec today, yes, but only for shared packages. But this could easily change. It might be easier to list shared packages in the module descriptor.
I want to provide a sufficiently simple and general mechanism that could be used for either running a simple program from the command line, or for bootstrapping some kind of server. But I don't want the module runtime to actually include any kind of server or service architecture.
From my point of view, a is something that should be itself a module. And then each service that runs the server would be a separate module that somehow (I don't know or care how) registers itself with the module. I don't want to be defining how any of this works in the Ceylon language spec. I want people to be able to adapt the module runtime to how they think a server should work.
Ok, but if I want to run multiple modules, the run section is executed for each module (this would be like osgi activators)? If not (or even if), the module must be somehow informed that there are new modules around.
I have not figured out the details yet, but yeah, there definitely needs to be a way to cause a runnable module to be deployed in an already-running VM instance. I guess there needs to be some kind of API for the module to interact with the module runtime. All these details need to be figured out, probably after the M1 compiler release.
This sounds pretty good. I like the idea that the compiler directly produces car files. Here are a couple of things that are unclear to me.
All in all, I have a vague impression that this will really help and make things rather straightforward. I am assuming that it will be possible to setup remote repositories in an intranet as well, so that enterprise customers can be provided with their own tools, right?
With Java, I once created a self-updating stand-alone application, which would simply check timestamps of remote jars compared to the local ones and download the missing or outdated jars. This solution still works well, but I still had the nagging feeling that this could be implemented in a better way. If I understand correctly, Ceylon's modules will make self-updating applications possible. (Yes, we used WebStart before, but it was too restricting and cumbersome.)
No, think of it more like a maven repo.
We will probably at some stage add the notion of an assembly, which would be a package of module archives, but honestly I have not thought through the details.
Well, the compiler and runtime will pull down stuff from remote to local automaticlly if needed, but also there will be a command line tool to fetch specific modules into the local repo.
Well, again this is a question of server architecture that we were discussing above. If you run a module directly from the commandline, then it runs synchronously.
TestSuite is a class visible to the module. (i.e. in the module or one of its dependencies)
Right.
Yes, that's one of the goals of a module runtime.
I hope it won't be as, err, as a maven repo. Sorry, I cannot help it, but maven let's my nails coil. You're not really inspired by Maven, are you? :)
OK, I guess. That will be very transparent, right? I'd really hate it if a piece of local software got updated automagically without explicit configuration. But I am sure, that's not what you meant.
Thanks for the prompt answers. I really can't wait for M1 ...
Well, um, how could I put this delicately....
I hate and despise maven with a passion. ;-)
So no, it's not a major inspiration to us.
Well, the idea is that archives come with version numbers. And we'll encourage you to be explicit with your version numbers.
Note that M1 won't have much, if any, of what I've described in this post. Not that what I'm describing here is pure vapor. JBoss Modules is a released product and Ales has already done a bunch of work on repurposing it for Ceylon. Rather it's just that integrating things together can take a lot of time and we want to get M1 out as early as possible.
OK. Got it. Thanks again.
PS. I won't tell anyone, but if we'd started a club, I am sure there'd be a looong waiting list to sign up.
;)
As you said, I have some basic stuff in place, with some @Tests as well. :-) To get this into M1, I would need to replace my current SDK mocks with actual current Ceylon code; e.g. real Module info
Repository support has some basic code for local, remote and source. I would suspect that getting proper source repo to fully work would be a bit too much work for M1, but the other two should be fine.