This is a Gradle Plugin to make it easy to handle Ceylon projects with Gradle.
Because Ceylon itself manages Ceylon dependencies, this plugin focuses on managing legacy Java dependencies so that you can get transitive dependencies resolved by Gradle.
This plugin can be a real lifesaver while the Ceylon ecosystem is still developing and you need to rely on Java (and Scala, Groovy, Kotlin)'s libraries!
Check out my blog post about this plugin to understand more about the motivations behind this project.
To use this plugin, simply add a Gradle build file to the root of your project and apply this plugin as shown below:
- Gradle 2.1+ and 3.0+
plugins {
id 'com.athaydes.ceylon' version '1.3.1'
}
- Old style Gradle plugin declaration
build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.athaydes.gradle.ceylon:ceylon-gradle-plugin:1.3.1"
}
}
apply plugin: 'com.athaydes.ceylon'
You can configure the behaviour of the plugin in the ceylon
block.
For example:
ceylon {
module = "com.athaydes.maven"
}
Many settings are read from the Ceylon config file, if available... however, all supported settings can be overridden in the Gradle file if specified explicitly.
The only mandatory field (if no Ceylon config file is present) is module
,
which you should set to the name of the Ceylon module.
Other properties are explained in the next sections.
The default properties of this Gradle Plugin make your Ceylon project use the default Java flat classpath (ie. no Ceylon module isolation) because that makes it much easier to make use of several popular Java frameworks, as well as other JVM languages, which assume free access to everything in the classpath.
The problem with that is that the default Ceylon module isolation is lost.
To use Ceylon defaults rather than Java's, add the following options to the ceylon
block in your build file:
ceylon {
...
flatClasspath = false
importJars = true
}
The Ceylon-Gradle Plugin adds the following tasks to your project:
cleanCeylon
- removes output created by the other tasks of this plugin.resolveCeylonDependencies
- resolves the project's dependencies.createDependenciesPoms
- creates Maven pom files for all transitive dependencies.createMavenRepo
- creates a local Maven repository containing all transitive dependencies.createModuleDescriptors
- creates module descriptors for all transitive dependencies.generateOverridesFile
- generates the overrides.xml file.importJars
- imports transitive dependencies into theoutput
Ceylon repository.compileCeylon
- compiles the Ceylon module.compileCeylonTest
- compiles the Ceylon test module.runCeylon
- runs the Ceylon module (see theentryPoint
config property).testCeylon
- runs the tests in the test module.createJavaRuntime
- creates a directory with all resources required to run the Ceylon application with only the JVM.fatJar
- creates a fat jar that can be used to deploy the Ceylon project more easily as a Java application.
Lifecycle tasks such as
build
andcheck
also work with the Ceylon Plugin.
Examples:
gradle cleanCeylon runCeylon
gradle compileCeylon --info
For even more output, use --debug
.
Another useful task that Gradle itself adds by default is
dependencies
, which prints the whole dependency tree of your project.
get-ceylon-command
: if the project has this property, any Ceylon commands that would have been called by this plugin are simply printed to stdout instead of actually being called.ceylon-args
: allows arguments to be provided directly to the Ceylon tool.app-args
: allows arguments to be provided to the Ceylon application (only used by therunCeylon
task).
Example usage:
gradle -P get-ceylon-command runCeylon
# this can be used to activate Ceylon tool options that are not supported by the Gradle Plugin
gradle compileCeylon -Pceylon-args="--fully-export-maven-dependencies"
Notice that you can also set properties in Gradle build files, so the above could be achieved by adding this line to your build file:
project.ext.'ceylon-args' = '--fully-export-maven-dependencies'
gradle runCeylon -Papp-args="--my-command-options other-option"
Most of the configuration of this plugin is done inside the ceylon
block.
The following properties can be set in the ceylon
block:
module
ormoduleName
: (mandatory if no Ceylon config file exists) name of the Ceylon module.ceylonLocation
: (optional) path to the ceylon executable. Set theCEYLON_HOME
environment variable instead to make the build more portable, or do nothing if you use SDKMAN! as in that case, the Ceylon location will be found automatically.sourceRoots
: (default:['source']
) List of directories where the Ceylon source code is located.resourceRoots
: (default:['resource']
) List of directories where resources are located.testRoots
: (default['source']
) List of directories where Ceylon test code is located.testResourceRoots
: (default['test-resource']
) List of directories where test resources are located.generateTestReport
: (defaulttrue
) whether to ask Ceylon test to generate test reports after running tests.testReportDestination
: (default${project.buildDir}/reports
) directory to save test reports into.output
: (default:modules
) specifies the output module repository.testModule
: (default: same value asmodule
): name of the Ceylon test module.moduleExclusions
: (default:[]
) name of the modules to remove from the compilation and runtime.overrides
: (default:'${project.buildDir}/overrides.xml'
) location to store the automatically-generated overrides.xml file.mavenSettings
: (default:'${project.buildDir}/maven-settings.xml'
) location of Maven settings file. If the file already exists, it is not overwritten, otherwise an appropriate file is generated (recommended).flatClasspath
: (default:true
) use a flat classpath (like in standard Java), bypassing Ceylon's default module isolation.importJars
: (default:false
) import dependencies' jar files into the Ceylon repository.forceImports
: (default:false
) use the--force
option when importing dependencies' jar files into the Ceylon repository.verbose
: (default:false
) use the--verbose
option when invoking Ceylon commands.javaRuntimeDestination
: (default:${project.buildDir}/java-runtime
) directory to save JVM resources created by thecreateJavaRuntime
task.fatJarDestination
: (default${project.buildDir}
) directory to save the fat jar created by thefatJar
task.entryPoint
: (default:${moduleName}::run
) top-level element to run when calling therunCeylon
task or the bash scripts created by thecreateJavaRuntime
task.
An example configuration (using most options above) might look like this:
ceylon {
module = "com.acme.awesome"
testModule = "test.com.acme.awesome"
flatClasspath = false
importJars = true
sourceRoots = ['source', 'src/main/ceylon']
resourceRoots = ['resource', 'src/main/resources']
output = 'dist'
verbose = true
javaRuntimeDestination = file('target/jvm')
entryPoint = "com.acme.awesome::start"
}
See the project samples for working examples.
When you use both this plugin and Eclipse, you need to let Eclipse know about the resources created by this plugin. That's very easy:
- open the
Project Properties
in Eclipse. - go to the
Ceylon Build > Module repositories
section. - in
Module overrides file
, enter the path to theoverrides.xml
file created by this plugin. - if you configured this plugin to use a
flatClasspath
, check the equivalent box just below the overrides file. - click on the
Add Maven Repository...
button. - enter the path to the Maven
settings.xml
file created by this plugin. - done! Just hit
OK
a few times and build the project again.
Notes:
- by default, the overrides file is saved at
build/overrides.xml
. - by default, the Maven settings file is saved at
build/maven-settings.xml
.
All direct dependencies of your project must be declared in Ceylon module.ceylon
file.
However, as Ceylon does not automatically resolve transitive Maven dependencies (note: this has changed in Ceylon 1.2.3 with the --fully-export-maven-dependencies
flag), the Ceylon-Gradle Plugin reads
the module.ceylon
file and creates an
overrides.xml
file that informs Ceylon what those transitive dependencies are.
The dependencies are resolved using Gradle's standard mechanism, so you can use any repository supported by Gradle.
Notice that you can exclude some particular dependency easily using Gradle. For example, to exclude dependencies with group ch.qos.logback
(and all its transtive dependencies as well) add this to your Gradle file:
configurations {
all*.exclude group: "ch.qos.logback"
}
It is important to notice that there are 2 different ways to use the Ceylon Gradle Plugin:
- Flat classpath: Java's default flat classpath, with JVM dependencies used as plain jars. This is the default.
- Ceylon module system: Using Ceylon's standard JBoss module system, which isolates modules' classpaths and verifies the runtime is consistent and not missing anything that's needed. JVM dependencies may be imported into the Ceylon repository.
Which one you should use depends on your application requirements.
- To use Java's flat classpath:
ceylon {
...
flatClasspath = true
importJars = false
}
The above is the default, so you might as well omit these parameters.
- To use the Ceylon module system:
ceylon {
...
flatClasspath = false
importJars = true
}
If Ceylon complains about packages missing, this probably means the jar you are importing refers to optional Maven dependencies' packages which Ceylon cannot guarantee will work at runtime!
To force Ceylon to accept the imports anyway, set forceImports
to true
:
ceylon {
...
flatClasspath = false
importJars = true
forceImports = true
}
Here's some differences between the two so you can decide which to use:
- only one version of each module may be available to all modules at runtime.
- Java libraries may load any classpath resource and instantiate classes from other modules. Popular Java libraries such as Hibernate, Jersey and Guice, for example, require this behaviour.
- all packages of every module are shared at runtime, just like in Java. At compile-time, however, the Ceylon compiler can still enforce the module boundaries between different modules.
- no need to generate extra metadata to import Jars into a local Ceylon repository.
Example of importing a Maven module with coordinates com.athaydes.groupId:module-name:1.0.0
in a Ceylon module file:
import "com.athaydes.groupId:module-name" "1.0.0";
Notice that the module name consists of the
groupId
and theartifactId
appended with a:
in the middle.
- many versions of the same module may be used (not currently supported by this plugin's dependency resolution).
- modules boundaries are strictly enforced by Ceylon, so non-shared imports and internals of a module are completely invisible to other modules.
- Jars are imported into the local Ceylon repository together with a module descriptor (generated automatically by this plugin. Currently all dependencies are shared and non-optional).
Example of importing the same module as in the previous section, but using the Ceylon module syntax:
import com.athaydes.groupId.module_name "1.0.0";
The module is imported into the Ceylon repository, so it must use Ceylon module name syntax: its name does not need to be quoted, the name is separated from the groupId with just a
.
, and the illegal character-
is replaced with_
.
Suppose you want to use a Java library, say SparkJava,
in your Ceylon project, and you are content with using a flat classpath...
you would have a module.ceylon
file similar to this:
native("jvm")
module com.athaydes.sparkweb "1.0.0" {
import java.base "8";
shared import "com.sparkjava:spark-core" "2.3";
}
Even though Spark has a lot of dependencies itself, you don't need to worry about that as Gradle will figure out all the transitive dependencies for you.
You can see them if you want, with the dependencies
task:
gradle dependencies
Which will print this:
ceylonCompile
\--- com.sparkjava:spark-core:2.3
+--- org.slf4j:slf4j-api:1.7.12
+--- org.slf4j:slf4j-simple:1.7.12
| \--- org.slf4j:slf4j-api:1.7.12
+--- org.eclipse.jetty:jetty-server:9.3.2.v20150730
| +--- javax.servlet:javax.servlet-api:3.1.0
| +--- org.eclipse.jetty:jetty-http:9.3.2.v20150730
| | \--- org.eclipse.jetty:jetty-util:9.3.2.v20150730
| \--- org.eclipse.jetty:jetty-io:9.3.2.v20150730
| \--- org.eclipse.jetty:jetty-util:9.3.2.v20150730
+--- org.eclipse.jetty:jetty-webapp:9.3.2.v20150730
| +--- org.eclipse.jetty:jetty-xml:9.3.2.v20150730
| | \--- org.eclipse.jetty:jetty-util:9.3.2.v20150730
| \--- org.eclipse.jetty:jetty-servlet:9.3.2.v20150730
| \--- org.eclipse.jetty:jetty-security:9.3.2.v20150730
| \--- org.eclipse.jetty:jetty-server:9.3.2.v20150730 (*)
+--- org.eclipse.jetty.websocket:websocket-server:9.3.2.v20150730
| +--- org.eclipse.jetty.websocket:websocket-common:9.3.2.v20150730
| | +--- org.eclipse.jetty.websocket:websocket-api:9.3.2.v20150730
| | +--- org.eclipse.jetty:jetty-util:9.3.2.v20150730
| | \--- org.eclipse.jetty:jetty-io:9.3.2.v20150730 (*)
| +--- org.eclipse.jetty.websocket:websocket-client:9.3.2.v20150730
| | +--- org.eclipse.jetty:jetty-util:9.3.2.v20150730
| | +--- org.eclipse.jetty:jetty-io:9.3.2.v20150730 (*)
| | \--- org.eclipse.jetty.websocket:websocket-common:9.3.2.v2015073 (*)
| +--- org.eclipse.jetty.websocket:websocket-servlet:9.3.2.v20150730
| | +--- org.eclipse.jetty.websocket:websocket-api:9.3.2.v20150730
| | \--- javax.servlet:javax.servlet-api:3.1.0
| +--- org.eclipse.jetty:jetty-servlet:9.3.2.v20150730 (*)
| \--- org.eclipse.jetty:jetty-http:9.3.2.v20150730 (*)
\--- org.eclipse.jetty.websocket:websocket-servlet:9.3.2.v20150730 (*)
ceylonRuntime
No dependencies
Luckily, you don't need to figure out all these dependencies yourself!
The auto-generated overrides.xml file for this example will be this one:
<overrides>
<artifact groupId='com.sparkjava' artifactId='spark-core' version='2.3' shared='true'>
<add groupId='org.slf4j' artifactId='slf4j-api' version='1.7.12' shared='true' />
<add groupId='org.slf4j' artifactId='slf4j-simple' version='1.7.12' shared='true' />
<add groupId='org.slf4j' artifactId='slf4j-api' version='1.7.12' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-server' version='9.3.2.v20150730' shared='true' />
<add groupId='javax.servlet' artifactId='javax.servlet-api' version='3.1.0' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-http' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-util' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-io' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-util' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-webapp' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-xml' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-util' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-servlet' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-security' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-server' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-server' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-common' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-api' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-util' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-io' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-client' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-util' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-io' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-common' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-servlet' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-api' version='9.3.2.v20150730' shared='true' />
<add groupId='javax.servlet' artifactId='javax.servlet-api' version='3.1.0' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-servlet' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty' artifactId='jetty-http' version='9.3.2.v20150730' shared='true' />
<add groupId='org.eclipse.jetty.websocket' artifactId='websocket-servlet' version='9.3.2.v20150730' shared='true' />
</artifact>
</overrides>
If you don't want all of transitive dependencies to be added to your project, you can remove them by specifying them as usual with Gradle and adding exclusions.
For example, continuing the example above, supposing that you don't want the websocket dependencies of Jetty because you're not going to use websockets, you can declare the dependency on Spark also in the Gradle file, as follows:
dependencies {
ceylonCompile "com.sparkjava:spark-core:2.3", {
exclude group: 'org.eclipse.jetty.websocket'
}
}
Notice that you still must declare a dependency on
spark-core
in themodule.ceylon
file even if you add that dependency on the Gradle build file.
It's important to understand that direct dependencies should be declared in module.ceylon
, as with any Ceylon project.
The Gradle dependencies
should be used only to add more information to how you want transitive dependencies to be
handled, as in this example. This avoids tightly coupling your project to Gradle (once you have the overrides.xml file
generated by Gradle, your project does not need anything else from Gradle and can be run by ceylon
as usual)
and confusion regarding where dependencies should be declared!
You can add a dependency to another module of your Gradle project, be it a Java (or even Scala, Groovy or Kotlin) module or another Ceylon module, using this syntax in the Gradle build file:
dependencies {
ceylonCompile project( ':multi-modules-project:another-module' )
}
Where the name of the project is multi-modules-project
.
In the Ceylon module file, you simply refer to the name of the module (which depends on whether you're using a flat classpath or not, as explained above).
For example, if the other module has these declarations in its Gradle build file:
group = 'com.athaydes.gradle'
version = '4.2'
notice that, with Gradle, the module name, by default, is the name of the module root directory. To change that, use a Gradle
settings.gradle
file.
If using a flat classpath, you would import this module in the Ceylon module file using this statement:
import "com.athaydes.gradle:another-module" "4.2";
If NOT using a flat classpath:
import com.athaydes.gradle.another_module "4.2";
The createJavaRuntime
task creates a directory containing all jars required to run your Ceylon module without using
ceylon run
, as well as bash/bat scripts to make it trivial to start the java
process with the Ceylon module.
It is extremely easy to do it. From the root directory of your Ceylon project, run the following commands:
./gradlew clean createJavaRuntime
bash build/java-runtime/run.sh
gradlew clean createJavaRuntime
build\java-runtime\run.bat
Because you can get a 10x startup time improvement just by doing this.
Check this out (you can run the same from the module-with-tests-sample directory):
# clean, compile and create the Java runtime
$ ./gradlew clean createJavaRuntime
...
# run the module directly with the ceylon command
# to print the full command, run
# ./gradlew -P get-ceylon-command runCeylon
$ time ceylon run \
--overrides build/overrides.xml \
--rep=aether:build/maven-settings.xml \
--rep=modules \
--run=com.athaydes.simple::addArgs \
com.athaydes.simple 2 5.3 6
The sum of { 2.0, 5.3, 6.0 } is: 13.3
real 0m1.033s
user 0m2.459s
sys 0m0.189s
# invoke java with the run.sh created by the Gradle Plugin
$ time bash target/jvm/run.sh 2 5.3 6
The sum of { 2.0, 5.3, 6.0 } is: 13.3
real 0m0.160s
user 0m0.173s
sys 0m0.031s
For working examples of Gradle projects using the Ceylon Gradle Plugin, refer to the test samples directory.