blob: 062191b30984b43d5b93ee7984b3fe8c64ce5c11 [file]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
ifndef::tools-groovy[]
:tools-groovy: tools-groovy.adoc
endif::[]
[[section-grape]]
= Dependency management with Grape
== Quick start
=== Add a Dependency
Grape is a JAR dependency manager embedded into Groovy. Grape lets you quickly add maven repository dependencies to your
classpath, making scripting even easier. The simplest use is as simple as adding an annotation to your script:
[source,groovy]
--------------------------------------------------------------------
@Grab(group='org.springframework', module='spring-orm', version='5.2.8.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
--------------------------------------------------------------------
`@Grab` also supports a shorthand notation:
[source,groovy]
--------------------------------------------------------------------
@Grab('org.springframework:spring-orm:5.2.8.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate
--------------------------------------------------------------------
Note that we are using an annotated import here, which is the recommended way. You can also search for
dependencies on http://mvnrepository.com[mvnrepository.com] and it will
provide you the `@Grab` annotation form of the `pom.xml` entry.
[[Grape-SpecifyAdditionalRepositories]]
=== Specify Additional Repositories
Not all dependencies are in maven central. You can add new ones like
this:
[source,groovy]
-----------------------------------------------------------------
@GrabResolver(name='restlet', root='http://maven.restlet.org/')
@Grab(group='org.restlet', module='org.restlet', version='1.1.6')
-----------------------------------------------------------------
[[Grape-MavenClassifiers]]
=== Maven Classifiers
Some maven dependencies need classifiers in order to be able to resolve.
You can fix that like this:
[source,groovy]
--------------------------------------------------------------------------------------
@Grab(group='net.sf.json-lib', module='json-lib', version='2.2.3', classifier='jdk15')
--------------------------------------------------------------------------------------
[[Grape-ExcludingTransitiveDependencies]]
=== Excluding Transitive Dependencies
Sometimes you will want to exclude transitive dependencies as you might
be already using a slightly different but compatible version of some
artifact. You can do this as follows:
[source,groovy]
----------------------------------------------
@Grab('net.sourceforge.htmlunit:htmlunit:2.8')
@GrabExclude('xml-apis:xml-apis')
----------------------------------------------
[[Grape-JDBCDrivers]]
=== JDBC Drivers
Because of the way JDBC drivers are loaded, you’ll need to configure
Grape to attach JDBC driver dependencies to the system class loader.
I.e:
[source,groovy]
--------------------------------------------------------------------
@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')
--------------------------------------------------------------------
[[Grape-UsingGrapeFromtheGroovyShell]]
=== Using Grape From the Groovy Shell
From groovysh use the method call variant:
[source,groovy]
----------------------------------------------------------------------------------------
groovy.grape.Grape.grab(group:'org.springframework', module:'spring', version:'2.5.6')
----------------------------------------------------------------------------------------
[[Grape-Proxysettings]]
=== Proxy settings
If you are behind a firewall and/or need to use Groovy/Grape through a
proxy server, you can specify those settings on the command like via the
`http.proxyHost` and `http.proxyPort` system properties:
-------------------------------------------------------------------------
groovy -Dhttp.proxyHost=yourproxy -Dhttp.proxyPort=8080 yourscript.groovy
-------------------------------------------------------------------------
Or you can make this system-wide by adding these properties to your
JAVA_OPTS environment variable:
------------------------------------------------------------
JAVA_OPTS = -Dhttp.proxyHost=yourproxy -Dhttp.proxyPort=8080
------------------------------------------------------------
[[Grape-Logging]]
=== Logging
The Grape facade (`groovy.grape.Grape`) uses
https://docs.oracle.com/en/java/javase/17/docs/api/java.logging/java/lang/System.Logger.html[JDK Platform Logging]
and can be configured through `~/.groovy/logging.properties`
(see <<{tools-groovy}#section-groovy-commandline,the groovy command>> for details).
Logging of the underlying engine depends on which Grape implementation is active.
==== GrapeIvy logging
GrapeIvy routes Ivy's internal logging through JDK Platform Logging
under the logger name `groovy.grape.ivy`. To control the verbosity,
set the level in `~/.groovy/logging.properties`:
[source,properties]
----
# Show Ivy info messages (INFO, WARNING, ERROR)
groovy.grape.ivy.level=INFO
# Show verbose Ivy output (adds dependency resolution details)
groovy.grape.ivy.level=FINE
# Show all Ivy debug output
groovy.grape.ivy.level=FINEST
----
The `grape` command's `-q`, `-w`, `-i`, `-V`, and `-d` flags also
adjust the Ivy logging level for that invocation.
If you want to see download progress specifically, set the system property
`groovy.grape.report.downloads` to `true` (e.g. add
`-Dgroovy.grape.report.downloads=true` to invocation or JAVA_OPTS) and Grape will
print the following infos to System.error:
* Starting resolve of a dependency
* Starting download of an artifact
* Retrying download of an artifact
* Download size and time for downloaded artifacts
==== GrapeMaven logging
When using the GrapeMaven engine, set `-Dgroovy.grape.debug=true` to
enable verbose Maven Resolver logging to `System.err`. The
`groovy.grape.report.downloads` property has no effect with this engine.
[[Grape-Detail]]
== Detail
Grape (The _Groovy Adaptable Packaging Engine_ or _Groovy Advanced
Packaging Engine_) is the infrastructure enabling the grab() calls in
Groovy, a repository-driven module system that allows a developer to
write a script with an essentially arbitrary library requirement, and
ship just the script. Grape will, at runtime, download as needed and
link the named libraries and all dependencies forming a transitive
closure when the script is run from existing repositories such as
Maven Central.
Groovy ships with two Grape engine implementations: `GrapeIvy`, based on
http://ant.apache.org/ivy/[Apache Ivy], and `GrapeMaven`, based on
https://maven.apache.org/resolver/[Maven Resolver (Aether)]. They
provide the same core `@Grab` / `@GrabResolver` / `@GrabExclude`
experience but differ in some areas of advanced configuration and
behaviour; see <<Grape-Implementations>> for details.
Grape follows the Ivy conventions for module version identification,
with naming change.
* `group` - Which module group the module comes from. Translates
directly to a Maven groupId or an Ivy Organization. Any group matching
`/groovy[x][\..*]^/` is reserved and may have special meaning to the
groovy endorsed modules.
* `module` - The name of the module to load. Translated directly to a
Maven artifactId or an Ivy artifact.
* `version` - The version of the module to use. Either a literal version
`1.1-RC3' or a range `[2.2.1,)' meaning 2.2.1 or any greater version).
* `classifier` - The optional classifier to use (for example, _jdk15_)
The downloaded modules will be stored according to the active engine's
cache conventions (see <<Grape-Implementations>> for per-engine cache
directories).
[[Grape-Implementations]]
=== Grape Implementations
Groovy ships with two built-in Grape engine implementations. Both support
the same core `@Grab`, `@GrabResolver`, and `@GrabExclude` annotations and
method calls. They differ in their underlying resolution mechanism, cache
layout, and some advanced configuration options.
==== Built-in implementations
===== GrapeIvy (default)
`groovy.grape.ivy.GrapeIvy` uses http://ant.apache.org/ivy/[Apache Ivy]
for dependency resolution. It is the default engine when both implementations
are on the classpath.
* *Backend*: Apache Ivy
* *Local cache*: `~/.groovy/grapes` (Ivy's standard cache format)
* *Configuration file*: `~/.groovy/grapeConfig.xml` (full Ivy XML settings — see <<Grape-CustomizeIvysettings>>)
* *`@GrabConfig(autoDownload=false)`*: Supported. When `false`, resolution
is restricted to the local cache; no network calls are made.
* *`conf:` parameter*: Mapped directly to Ivy configurations; multiple
configurations such as `conf:['default','optional']` are supported.
* *Version wildcards*: `'*'` resolves to Ivy's `latest.default`.
===== GrapeMaven
`groovy.grape.maven.GrapeMaven` uses
https://maven.apache.org/resolver/[Maven Resolver (Aether)]
for dependency resolution.
* *Backend*: Maven Resolver (Aether)
* *Local cache*: `~/.groovy/grapesM2` (Maven local-repository format)
* *Configuration*: Remote repositories configured via `@GrabResolver` or
the `Grape.addResolver()` API; no configuration file equivalent.
* *`@GrabConfig(autoDownload=…)`*: The `autoDownload` flag is not
honoured; GrapeMaven always attempts remote resolution when an artifact
is not already cached.
* *`conf:` parameter*: Treated as a Maven scope; `'default'` is mapped
to `'compile'`. Multiple configurations in a single grab are not supported.
* *Version wildcards*: `'*'` resolves to Maven's `LATEST`.
==== Selecting an implementation
When both `groovy-grape-ivy` and `groovy-grape-maven` are on the
classpath, `GrapeIvy` is preferred by default. To switch to GrapeMaven
for a script, pass the system property:
-------------------------------------------------------------------------
groovy -Dgroovy.grape.impl=groovy.grape.maven.GrapeMaven yourscript.groovy
-------------------------------------------------------------------------
Or set it globally via `JAVA_OPTS`:
-------------------------------------------------------------------------
JAVA_OPTS=-Dgroovy.grape.impl=groovy.grape.maven.GrapeMaven
-------------------------------------------------------------------------
==== Migration considerations
Most existing `@Grab` scripts work unchanged with both engines, but
there are some compatibility notes to consider:
* *`conf:` parameter*: GrapeIvy supports multiple Ivy configurations
(e.g., `conf:['default','optional']`), while GrapeMaven treats `conf:`
as a singular Maven scope (e.g., `conf:'compile'`). Scripts using
multiple configurations must remain on GrapeIvy or be reworked.
* *Custom Ivy configuration*: If you have customized
`~/.groovy/grapeConfig.xml` with non-standard Ivy settings (especially
if you disabled the default `m2compatible=true`), those settings do not
apply to GrapeMaven. Review your configuration before switching, or
reconfigure custom repositories using `@GrabResolver` annotations.
* *`@GrabConfig(autoDownload=false)`*: GrapeMaven does not honour
`autoDownload=false` as a per-grab switch. The closest equivalent is
to point `@GrabResolver` (or `Grape.addResolver`) to a local-only
repository/cache location. If you rely on `autoDownload=false`
semantics broadly, remain on GrapeIvy.
==== Custom implementations
You can provide your own `GrapeEngine` implementation by including a jar
on the classpath that declares it as a Java SPI (Service Provider Interface).
Groovy will discover and load it via `java.util.ServiceLoader`.
To register a custom implementation:
1. Create a class implementing `groovy.grape.GrapeEngine`.
2. Add a service provider configuration file at
`META-INF/services/groovy.grape.GrapeEngine` in your jar with a single
line naming the fully-qualified class name.
3. Place the jar on the classpath.
You can then select it with:
-------------------------------------------------------------------------
groovy -Dgroovy.grape.impl=my.custom.GrapeImpl yourscript.groovy
-------------------------------------------------------------------------
If your implementation is the only one on the classpath, it will be
automatically discovered and used by default. If multiple implementations
are present (including the built-ins), you must specify which one to use
via the system property.
[[Grape-Usage]]
== Usage
[[Grape-Annotation]]
=== Annotation
One or more `groovy.lang.Grab` annotations can be added at any place that
annotations are accepted to tell the compiler that this code relies on
the specific library. This will have the effect of adding the library to
the classloader of the groovy compiler. This annotation is detected and
evaluated before any other resolution of classes in the script, so
imported classes can be properly resolved by a `@Grab` annotation.
Supported annotation targets include imports (recommended), classes,
methods, constructors, fields, parameters, and local variables.
Here is an example placing `@Grab` on a class:
[source,groovy]
-----------------------------------------------------------------------
import com.jidesoft.swing.JideSplitButton
@Grab(group='com.jidesoft', module='jide-oss', version='[2.2.1,2.3.0)')
public class TestClassAnnotation {
public static String testMethod () {
return JideSplitButton.class.name
}
}
-----------------------------------------------------------------------
Here is an example placing `@Grab` on a local variable:
[source,groovy]
-----------------------------------------------------------------------
@Grab('commons-lang:commons-lang:2.4')
var input = 'hello world'
println org.apache.commons.lang.WordUtils.capitalize(input)
-----------------------------------------------------------------------
An appropriate `grab(...)` call will be added to the static initializer
of the class of the containing class (or script class in the case of an
annotated script element).
[[Grape-MultipleGrapeAnnotations]]
=== Multiple Grape Annotations
In early versions of Groovy, if you wanted to use a Grab annotation multiple times
on the same node you had to use the `@Grapes` annotation, e.g.:
[source,groovy]
---------------------------------------------------------------------------------
@Grapes([
@Grab(group='commons-primitives', module='commons-primitives', version='1.0'),
@Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='0.9.7')])
class Example {
// ...
}
---------------------------------------------------------------------------------
Otherwise you’d encounter the following error:
------------------------------------------------------
Cannot specify duplicate annotation on the same member
------------------------------------------------------
But in recent versions, @Grapes is purely optional.
Technical notes:
* Originally, Groovy stored the Grab annotations for access at runtime
and duplicates aren't allowed in the bytecode. In current versions, @Grab has only
SOURCE retention, so the multiple occurrences aren't an issue.
* Future versions of Grape may support using the Grapes annotation to
provide a level of structuring, e.g. allowing a GrabExclude or GrabResolver
annotation to apply to only a subset of the Grab annotations.
[[Grape-Methodcall]]
=== Method call
Typically a call to grab will occur early in the script or in class
initialization. This is to ensure that the libraries are made available
to the ClassLoader before the groovy code relies on the code. A couple
of typical calls may appear as follows:
[source,groovy]
-------------------------------------------------------------------------------------------------------
import groovy.grape.Grape
// random maven library
Grape.grab(group:'com.jidesoft', module:'jide-oss', version:'[2.2.0,)')
Grape.grab([group:'org.apache.ivy', module:'ivy', version:'2.0.0-beta1', conf:['default', 'optional']],
[group:'org.apache.ant', module:'ant', version:'1.7.0'])
-------------------------------------------------------------------------------------------------------
* Multiple calls to grab in the same context with the same parameters
should be idempotent. However, if the same code is called with a
different `ClassLoader` context then resolution may be re-run.
* If the `args` map passed into the `grab` call has an attribute
`noExceptions` that evaluates true no exceptions will be thrown.
* `grab` requires that a `RootLoader` or `GroovyClassLoader` be specified or
be in the `ClassLoader` chain of the calling class. By default failure to
have such a `ClassLoader` available will result in module resolution and
an exception being thrown
** The ClassLoader passed in via the `classLoader:` argument and its
parent classloaders.
** The ClassLoader of the object passed in as the `referenceObject:`
argument, and its parent classloaders.
** The ClassLoader of the class issuing the call to `grab`
[[Grape-grabHashMapParameters]]
==== grab(HashMap) Parameters
* `group:` - <String> - Which module group the module comes from.
Translates directly to a Maven groupId. Any group matching
`/groovy(|\..|x|x\..)/` is reserved and may have special meaning to the
groovy endorsed modules.
* `module:` - <String> - The name of the module to load. Translated
directly to a Maven artifactId.
* `version:` - <String> and possibly <Range> - The version of the module
to use. Either a literal version `1.1-RC3' or an Ivy Range `[2.2.1,)'
meaning 2.2.1 or any greater version).
* `classifier:` - <String> - The Maven classifier to resolve by.
* `conf:` - <String>, default `default' - The configuration or scope of
the module to download. The default conf is `default:` which maps to the
maven `runtime` and `master` scopes.
* `force:`- <boolean>, defaults true - Used to indicate that this
revision must be used in case of conflicts, independently of
* conflicts manager
* `changing:` - <boolean>, default false - Whether the artifact can
change without its version designation changing.
* `transitive:` - <boolean>, default true - Whether to resolve other
dependencies this module has or not.
There are two principal variants of `grab`, one with a single Map and
one with an arguments Map and multiple dependencies map. A call to the
single map grab is the same as calling grab with the same map passed in
twice, so grab arguments and dependencies can be mixed in the same map,
and grab can be called as a single method with named parameters.
There are synonyms for these parameters. Submitting more than one is a
runtime exception.
* `group:`, `groupId:`, `organisation:`, `organization:`, `org:`
* `module:`, `artifactId:`, `artifact:`
* `version:`, `revision:`, `rev:`
* `conf:`, `scope:`, `configuration:`
[[Grape-ArgumentsMaparguments]]
==== Arguments Map arguments
* `classLoader:` - <GroovyClassLoader> or <RootClassLoader> - The
ClassLoader to add resolved Jars to
* `refObject:` - <Object> - The closest parent ClassLoader for the
object’s class will be treated as though it were passed in as
`classLoader:`
* `validate:` - <boolean>, default false - Should poms or ivy files be
validated (true), or should we trust the cache (false).
* `noExceptions:` - <boolean>, default false - If ClassLoader resolution
or repository querying fails, should we throw an exception (false) or
fail silently (true).
[[Grape-CommandLineTool]]
=== Command Line Tool
Grape added a command line executable `grape' that allows for the
inspection and management of the local grape cache.
------------------------------------------------
grape install [-hv] <group> <module> [<version>] [<classifier>]
------------------------------------------------
This installs the specified groovy module or maven artifact. If a
version is specified that specific version will be installed, otherwise
the most recent version will be used (as if `*' we passed in).
----------
grape list
----------
Lists locally installed modules (with their full maven name in the case
of groovy modules) and versions.
-------------------------------------------------
grape resolve [-adhisv] (<groupId> <artifactId> <version>)+
-------------------------------------------------
This returns the file locations of the jars representing the artifacts
for the specified module(s) and the respective transitive dependencies.
You may optionally pass in -ant, -dos, or -shell to get the dependencies
expressed in a format applicable for an ant script, windows batch file,
or unix shell script respectively. -ivy may be passed to see the
dependencies expressed in an ivy like format.
-------------------------------------------------
grape uninstall [-hv] <group> <module> <version>
-------------------------------------------------
This uninstalls a particular grape: it non-transitively removes the
respective jar file from the grape cache.
[[Grape-Advancedconfiguration]]
=== Advanced configuration
[[Grape-RepositoryDirectory]]
==== Repository Directory
The local cache directory used by each engine is:
* *GrapeIvy*: `~/.groovy/grapes`
* *GrapeMaven*: `~/.groovy/grapesM2`
If you need to change the root directory that Grape uses for downloading
libraries you can specify the `grape.root` system property to change the
default (which is `~/.groovy`). Both engines respect this property.
-------------------------------------------------
groovy -Dgrape.root=/repo/grapes yourscript.groovy
-------------------------------------------------
[[Grape-CustomizeIvysettings]]
==== Customize Ivy settings (GrapeIvy only)
When using GrapeIvy, you can customize the Ivy settings that Grape uses by creating a
`~/.groovy/grapeConfig.xml` file. If no such file exists,
https://github.com/apache/groovy/blob/master/src/resources/groovy/grape/defaultGrapeConfig.xml[here]
are the default settings used by Grape.
For more information on how to customize these settings, please refer to
the https://ant.apache.org/ivy/history/latest-milestone/index.html[Ivy
documentation].
This configuration file is not used by the GrapeMaven engine; with
GrapeMaven, additional remote repositories are registered via
`@GrabResolver` annotations or programmatically via the
`Grape.addResolver` API.
[[Grape-MoreExamples]]
=== More Examples
Using Apache Commons Collections:
[source,groovy]
-----------------------------------------------------------------------------
// create and use a primitive array list
@Grab(group='commons-primitives', module='commons-primitives', version='1.0')
import org.apache.commons.collections.primitives.ArrayIntList
def createEmptyInts() { new ArrayIntList() }
def ints = createEmptyInts()
ints.add(0, 42)
assert ints.size() == 1
assert ints.get(0) == 42
-----------------------------------------------------------------------------
Using TagSoup:
[source,groovy]
------------------------------------------------------------------------
// find the PDF links of the Java specifications
@Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='1.2.1')
def getHtml() {
def parser = new XmlParser(new org.ccil.cowan.tagsoup.Parser())
parser.parse("https://docs.oracle.com/javase/specs/")
}
html.body.'**'.a.@href.grep(~/.*\.pdf/).each{ println it }
------------------------------------------------------------------------
Using Google Collections:
[source,groovy]
-------------------------------------------------------------------------------------------------------
import com.google.common.collect.HashBiMap
@Grab(group='com.google.code.google-collections', module='google-collect', version='snapshot-20080530')
def getFruit() { [grape:'purple', lemon:'yellow', orange:'orange'] as HashBiMap }
assert fruit.lemon == 'yellow'
assert fruit.inverse().yellow == 'lemon'
-------------------------------------------------------------------------------------------------------
Launching a Jetty server to serve Groovy templates:
[source,groovy]
--------------------------------------------------------------------------------------------------
@Grab('org.eclipse.jetty.aggregate:jetty-server:8.1.19.v20160209')
@Grab('org.eclipse.jetty.aggregate:jetty-servlet:8.1.19.v20160209')
@Grab('javax.servlet:javax.servlet-api:3.0.1')
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.ServletContextHandler
import groovy.servlet.TemplateServlet
def runServer(duration) {
def server = new Server(8080)
def context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS)
context.resourceBase = "."
context.addServlet(TemplateServlet, "*.gsp")
server.start()
sleep duration
server.stop()
}
runServer(10000)
--------------------------------------------------------------------------------------------------
Grape will download Jetty and its dependencies on first launch of this
script, and cache them. We create a new Jetty Server on port 8080,
then expose Groovy’s TemplateServlet at the root of the context — Groovy
comes with its own powerful template engine mechanism. We start the
server and let it run for a certain duration. Each time someone will hit
+http://localhost:8080/somepage.gsp+, it will display the somepage.gsp
template to the user — those template pages should be situated in the
same directory as this server script.