(Quick Reference)

3.7 依赖解析 - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith

Version: null

3.7 依赖解析

Grails features a dependency resolution DSL that lets you control how plugins and JAR dependencies are resolved.

You specify a grails.project.dependency.resolution property inside the grails-app/conf/BuildConfig.groovy file that configures how dependencies are resolved:

grails.project.dependency.resolution = {
   // config here
}

The default configuration looks like the following:

grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
//grails.project.war.file = "target/${appName}-${appVersion}.war"

grails.project.dependency.resolution = { // inherit Grails' default dependencies inherits("global") { // uncomment to disable ehcache // excludes 'ehcache' } log "warn" repositories { grailsPlugins() grailsHome() grailsCentral()

// uncomment these to enable remote dependency resolution // from public Maven repositories //mavenCentral() //mavenLocal() //mavenRepo "http://snapshots.repository.codehaus.org" //mavenRepo "http://repository.codehaus.org" //mavenRepo "http://download.java.net/maven/2/" //mavenRepo "http://repository.jboss.com/maven2/" } dependencies { // specify dependencies here under either 'build', 'compile', // 'runtime', 'test' or 'provided' scopes eg.

// runtime 'mysql:mysql-connector-java:5.1.16' }

plugins { compile ":hibernate:$grailsVersion" compile ":jquery:1.6.1.1" compile ":resources:1.0"

build ":tomcat:$grailsVersion" } }

The details of the above will be explained in the next few sections.

Grails提供了依赖解析的DSL来处理插件和JAR的依赖处理。

你可以在grails-app/conf/BuildConfig.groovy中设置grails.project.dependency.resolution属性来配置依赖是如何解析的,模板如下:

grails.project.dependency.resolution = {
   // config here
}

其缺省的配置如下所示:

grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
//grails.project.war.file = "target/${appName}-${appVersion}.war"

grails.project.dependency.resolution = { // inherit Grails' default dependencies inherits("global") { // uncomment to disable ehcache // excludes 'ehcache' } log "warn" repositories { grailsPlugins() grailsHome() grailsCentral()

// uncomment these to enable remote dependency resolution // from public Maven repositories //mavenCentral() //mavenLocal() //mavenRepo "http://snapshots.repository.codehaus.org" //mavenRepo "http://repository.codehaus.org" //mavenRepo "http://download.java.net/maven/2/" //mavenRepo "http://repository.jboss.com/maven2/" } dependencies { // specify dependencies here under either 'build', 'compile', // 'runtime', 'test' or 'provided' scopes eg.

// runtime 'mysql:mysql-connector-java:5.1.16' }

plugins { compile ":hibernate:$grailsVersion" compile ":jquery:1.6.1.1" compile ":resources:1.0"

build ":tomcat:$grailsVersion" } }

上述示例的详细描述将会在下来的几个章节中解释。

3.7.1 配置和依赖

Grails features five dependency resolution configurations (or 'scopes'):
  • build: Dependencies for the build system only
  • compile: Dependencies for the compile step
  • runtime: Dependencies needed at runtime but not for compilation (see above)
  • test: Dependencies needed for testing but not at runtime (see above)
  • provided: Dependencies needed at development time, but not during WAR deployment

Within the dependencies block you can specify a dependency that falls into one of these configurations by calling the equivalent method. For example if your application requires the MySQL driver to function at runtime you can specify that like this:

runtime 'com.mysql:mysql-connector-java:5.1.16'

This uses the string syntax: group:name:version. You can also use a Map-based syntax:

runtime group: 'com.mysql',
        name: 'mysql-connector-java',
        version: '5.1.16'

In Maven terminology, group corresponds to an artifact's groupId and name corresponds to its artifactId.

Multiple dependencies can be specified by passing multiple arguments:

runtime 'com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1'

// Or

runtime( [group:'com.mysql', name:'mysql-connector-java', version:'5.1.16'], [group:'net.sf.ehcache', name:'ehcache', version:'1.6.1'] )

Grails提供了如下5种依赖解析配置(或者是‘范围’):

  • build: 只在系统构建时的依赖
  • compile: 编译阶段时的依赖
  • runtime: 运行阶段的依赖,不包括编译阶段(见上解释)
  • test: 测试阶段的依赖,不包括运行阶段
  • provided: 开发阶段的依赖,不包括WAR部署阶段

dependencies代码块中,你可以通过同等的方法调用方式来指定一个依赖。比如你的应用中需要runtime的MySQL驱动,你可以这样处理:

runtime 'com.mysql:mysql-connector-java:5.1.16'

此处使用了字符串语法,其格式是:group:name:version,你也可以使用Map格式的语法:

runtime group: 'com.mysql',
        name: 'mysql-connector-java',
        version: '5.1.16'

对应于Maven术语,group跟工件(artifact)的groupId相对应,nameartifactId相对应。

多个依赖可以通过多参数方式来处理:

runtime 'com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1'

// Or

runtime( [group:'com.mysql', name:'mysql-connector-java', version:'5.1.16'], [group:'net.sf.ehcache', name:'ehcache', version:'1.6.1'] )

Disabling transitive dependency resolution

By default, Grails will not only get the JARs and plugins that you declare, but it will also get their transitive dependencies. This is usually what you want, but there are occasions where you want a dependency without all its baggage. In such cases, you can disable transitive dependency resolution on a case-by-case basis:

runtime('com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1') {
    transitive = false
}

// Or runtime group:'com.mysql', name:'mysql-connector-java', version:'5.1.16', transitive:false

禁用依赖解析的传递性

缺省情况下,Grails不仅仅获取你直接声明的JAR和插件,还包含其间接所依赖的。多数情况下,这正是你所需要的,不过在个别情况下,你并不需要这种传递性的依赖。这时,你可以有针对地禁止传递依赖,比如:

runtime('com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1') {
    transitive = false
}

// Or runtime group:'com.mysql', name:'mysql-connector-java', version:'5.1.16', transitive:false

Excluding specific transitive dependencies

A far more common scenario is where you want the transitive dependencies, but some of them cause issues with your own dependencies or are unnecessary. For example, many Apache projects have 'commons-logging' as a transitive dependency, but it shouldn't be included in a Grails project (we use SLF4J). That's where the excludes option comes in:

runtime('com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1') {
    excludes "xml-apis", "commons-logging"
}

// Or runtime(group:'com.mysql', name:'mysql-connector-java', version:'5.1.16') { excludes([ group: 'xml-apis', name: 'xml-apis'], [ group: 'org.apache.httpcomponents' ], [ name: 'commons-logging' ])

As you can see, you can either exclude dependencies by their artifact ID (also known as a module name) or any combination of group and artifact IDs (if you use the Map notation). You may also come across exclude as well, but that can only accept a single string or Map:

runtime('com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1') {
    exclude "xml-apis"
}

排除特定的依赖

传递依赖对你来说是如此的常用,但也有会跟你自己的依赖冲突或者重复的情况,比如很多的Apache项目都有依赖于'commons-logging',但是它不能被包含于Grails工程(其使用的是SLF4J)。因此就产生了excludes选项,比如:

runtime('com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1') {
    excludes "xml-apis", "commons-logging"
}

// Or runtime(group:'com.mysql', name:'mysql-connector-java', version:'5.1.16') { excludes([ group: 'xml-apis', name: 'xml-apis'], [ group: 'org.apache.httpcomponents' ], [ name: 'commons-logging' ])

如你所见,你可以通过工件ID(又名模块名称)或者组名加工件ID的方式来排除特定的依赖。你也可以通过exclude来排除,不过此处只能接收一个字符串或者Map:

runtime('com.mysql:mysql-connector-java:5.1.16',
        'net.sf.ehcache:ehcache:1.6.1') {
    exclude "xml-apis"
}

Using Ivy module configurations

If you use Ivy module configurations and wish to depend on a specific configuration of a module, you can use the dependencyConfiguration method to specify the configuration to use.

provided("my.org:web-service:1.0") {
    dependencyConfiguration "api"
}

If the dependency configuration is not explicitly set, the configuration named "default" will be used (which is also the correct value for dependencies coming from Maven style repositories).

使用Ivy模块配置

如果你是使用Ivy的模块配置,并且希望依赖于某一特定模块,你可以使用dependencyConfiguration方法来指定:

provided("my.org:web-service:1.0") {
    dependencyConfiguration "api"
}

如果依赖配置没有明确指定,那么将被使用名为"default"缺省配置(其也可以兼容来自Maven风格的存储仓库)。

Where are the JARs?

With all these declarative dependencies, you may wonder where all the JARs end up. They have to go somewhere after all. By default Grails puts them into a directory, called the dependency cache, that resides on your local file system at user.home/.grails/ivy-cache. You can change this either via the settings.groovy file:

grails.dependency.cache.dir = "${userHome}/.my-dependency-cache"

or in the dependency DSL:

grails.project.dependency.resolution = {
    …
    cacheDir "target/ivy-cache"
    …
}

The settings.groovy option applies to all projects, so it's the preferred approach.

JAR在哪里?

对于所有声明的依赖,你可能好奇,这些JAR都到哪里去了?它们总要有个地方来保存的。缺省情况下,Grails将它们放到依赖缓存的目录,其位于你本地文件系统中user.home/.grails/ivy-cache。你也可以通过settings.groovy来修改,比如:

grails.dependency.cache.dir = "${userHome}/.my-dependency-cache"

或者使用依赖DSL:

grails.project.dependency.resolution = {
    …
    cacheDir "target/ivy-cache"
    …
}

settings.groovy的选项将应用于所有的工程,因为它是最优先使用的。

3.7.2 依赖存储库

Remote Repositories

Initially your BuildConfig.groovy does not use any remote public Maven repositories. There is a default grailsHome() repository that will locate the JAR files Grails needs from your Grails installation. To use a public repository, specify it in the repositories block:

repositories {
    mavenCentral()
}

In this case the default public Maven repository is specified. To use the SpringSource Enterprise Bundle Repository you can use the ebr() method:

repositories {
    ebr()
}

You can also specify a specific Maven repository to use by URL:

repositories {
    mavenRepo "http://repository.codehaus.org"
}

and even give it a name:

repositories {
    mavenRepo name: "Codehaus", root: "http://repository.codehaus.org"
}

so that you can easily identify it in logs.

远程存储仓库

刚创建的BuildConfig.groovy文件并没有使用任何远程的公共Maven存储库,在那里只有一个缺省的 grailsHome()用以从Grails安装目录定位所需要的JAR文件。要使用远程的公共存储库,请使用 repositories代码块,比如:

repositories {
    mavenCentral()
}

在上述示例中,指定的是Maven缺省的公共存储库。要使用SpringSource的企业包存储库,你可以使用ebr()方法:

repositories {
    ebr()
}

你还可以使用URL的方式来指定Maven存储库:

repositories {
    mavenRepo "http://repository.codehaus.org"
}

并且可以给其命名:

repositories {
    mavenRepo name: "Codehaus", root: "http://repository.codehaus.org"
}

这样你就可以在日志中轻易地辨别它们。

Controlling Repositories Inherited from Plugins

A plugin you have installed may define a reference to a remote repository just as an application can. By default your application will inherit this repository definition when you install the plugin.

If you do not wish to inherit repository definitions from plugins then you can disable repository inheritance:

repositories {
    inherit false
}

In this case your application will not inherit any repository definitions from plugins and it is down to you to provide appropriate (possibly internal) repository definitions.

控制插件存储库地继承

通常,你安装的插件会定义一个远程的存储库,缺省情况下,而你的应用将继承你安装插件中的存储库的定义。

如果你不希望继承来自插件的存储库定义,你可以禁止这种存储库的继承:

repositories {
    inherit false
}

上述示例中,应用将不继承任何插件的存储库的定义,而是仅仅依赖于你定义的存储库。

Offline Mode

There are times when it is not desirable to connect to any remote repositories (whilst working on the train for example!). In this case you can use the offline flag to execute Grails commands and Grails will not connect to any remote repositories:

grails --offline run-app

Note that this command will fail if you do not have the necessary dependencies in your local Ivy cache

You can also globally configure offline mode by setting grails.offline.mode to true in ~/.grails/settings.groovy or in your project's BuildConfig.groovy file:

grails.offline.mode=true

离线模式

偶尔的时候,你将不能访问任何远程的存储库(比如在火车上工作时)。此种情况下,你可以使用offline标志来执行Grails命令,这样Grails将不会连接任何远程存储库,比如:

grails --offline run-app

注意:如果你本地Ivy缓存中没有所需的依赖,上述命令将会出错。

你还可以通过设置~/.grails/settings.groovy或者工程中BuildConfig.groovy文件中的grails.offline.modetrue的方式,将其配置为全局离线模式:

grails.offline.mode=true

Local Resolvers

If you do not wish to use a public Maven repository you can specify a flat file repository:

repositories {
    flatDir name:'myRepo', dirs:'/path/to/repo'
}

To specify your local Maven cache (~/.m2/repository) as a repository:

repositories {
    mavenLocal()
}

本地解析器

如果你不希望使用远程的Maven存储库,你可以指定一个平面文件(flat file)存储器:

repositories {
    flatDir name:'myRepo', dirs:'/path/to/repo'
}

将你本地的Maven缓存作为(~/.m2/repository)作为存储器,可以用下面的处理:

repositories {
    mavenLocal()
}

Custom Resolvers

If all else fails since Grails builds on Apache Ivy you can specify an Ivy resolver:

/*
 * Configure our resolver.
 */
def libResolver = new org.apache.ivy.plugins.resolver.URLResolver()
['libraries', 'builds'].each {

libResolver.addArtifactPattern( "http://my.repository/${it}/" + "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]")

libResolver.addIvyPattern( "http://my.repository/${it}/" + "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]") }

libResolver.name = "my-repository" libResolver.settings = ivySettings

resolver libResolver

It's also possible to pull dependencies from a repository using SSH. Ivy comes with a dedicated resolver that you can configure and include in your project like so:

import org.apache.ivy.plugins.resolver.SshResolver
…
repositories {
    ...

def sshResolver = new SshResolver( name: "myRepo", user: "username", host: "dev.x.com", keyFile: new File("/home/username/.ssh/id_rsa"), m2compatible: true)

sshResolver.addArtifactPattern( "/home/grails/repo/[organisation]/[artifact]/" + "[revision]/[artifact]-[revision].[ext]")

sshResolver.latestStrategy = new org.apache.ivy.plugins.latest.LatestTimeStrategy()

sshResolver.changingPattern = ".*SNAPSHOT"

sshResolver.setCheckmodified(true)

resolver sshResolver }

Download the JSch JAR and add it to Grails' classpath to use the SSH resolver. You can do this by passing the path in the Grails command line:

grails -classpath /path/to/jsch compile|run-app|etc.

You can also add its path to the CLASSPATH environment variable but be aware this it affects many Java applications. An alternative on Unix is to create an alias for grails -classpath ... so that you don't have to type the extra arguments each time.

自定义解析器

如果上述的都失败了,你还可以自定义一个Ivy解析器,因为Grails是基于Apache Ivy构建的:

/*
 * Configure our resolver.
 */
def libResolver = new org.apache.ivy.plugins.resolver.URLResolver()
['libraries', 'builds'].each {

libResolver.addArtifactPattern( "http://my.repository/${it}/" + "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]")

libResolver.addIvyPattern( "http://my.repository/${it}/" + "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]") }

libResolver.name = "my-repository" libResolver.settings = ivySettings

resolver libResolver

此外还可以通过SSH的方式从存储库中获取依赖。为此Ivy提供了一个专门的用于配置的解析器,在你的工程看起来可能如下:

import org.apache.ivy.plugins.resolver.SshResolver
…
repositories {
    ...

def sshResolver = new SshResolver( name: "myRepo", user: "username", host: "dev.x.com", keyFile: new File("/home/username/.ssh/id_rsa"), m2compatible: true)

sshResolver.addArtifactPattern( "/home/grails/repo/[organisation]/[artifact]/" + "[revision]/[artifact]-[revision].[ext]")

sshResolver.latestStrategy = new org.apache.ivy.plugins.latest.LatestTimeStrategy()

sshResolver.changingPattern = ".*SNAPSHOT"

sshResolver.setCheckmodified(true)

resolver sshResolver }

要使用SSH解析器,先要下载JSch的JAR到你Grails的类路径(classpath)中,你可以通过命令行的方式来设置,比如:

grails -classpath /path/to/jsch compile|run-app|etc.

你也可以将路径添加到CLASSPATH环境变量中,不要要注意,这可能会影响很多的Java应用。在Unix下,你可以为grails -classpath ...创建一个别名,这样就可以不需要每次都敲额外的参数了。

Authentication

If your repository requires authentication you can configure this using a credentials block:

credentials {
    realm = ".."
    host = "localhost"
    username = "myuser"
    password = "mypass"
}

This can be placed in your USER_HOME/.grails/settings.groovy file using the grails.project.ivy.authentication setting:

grails.project.ivy.authentication = {
    credentials {
        realm = ".."
        host = "localhost"
        username = "myuser"
        password = "mypass"
    }
}

验证

如果你的存储仓库需要验证,那么你可以通过credentials代码块来进行配置,比如:

credentials {
    realm = ".."
    host = "localhost"
    username = "myuser"
    password = "mypass"
}

你也可以通过设置USER_HOME/.grails/settings.groovy文件中的grails.project.ivy.authentication来实现:

grails.project.ivy.authentication = {
    credentials {
        realm = ".."
        host = "localhost"
        username = "myuser"
        password = "mypass"
    }
}

3.7.3 调试解析

If you are having trouble getting a dependency to resolve you can enable more verbose debugging from the underlying engine using the log method:

// log level of Ivy resolver, either 'error', 'warn',
// 'info', 'debug' or 'verbose'
log "warn"

A common issue is that the checksums for a dependency don't match the associated JAR file, and so Ivy rejects the dependency. This helps ensure that the dependencies are valid. But for a variety of reasons some dependencies simply don't have valid checksums in the repositories, even if they are valid JARs. To get round this, you can disable Ivy's dependency checks like so:

grails.project.dependency.resolution = {
    …
    log "warn"
    checksums false
    …
}

This is a global setting, so only use it if you have to.

如果你解析依赖遇到了问题的话,你可以使用基本的log方法来显示更多的调试信息:

// log level of Ivy resolver, either 'error', 'warn',
// 'info', 'debug' or 'verbose'
log "warn"

一个常见的问题是依赖的校验码跟其相应的JAR文件不匹配,这将导致Ivy不接收此依赖。此举用以保证依赖的有效性,但是由于不确定的原因,导致有些依赖在存储仓库中即使存在有效的JAR,也还是没有有效的校验码。要解决此问题,你可以禁用Ivy的依赖检测,比如:

grails.project.dependency.resolution = {
    …
    log "warn"
    checksums false
    …
}

这是个全局设置,因此请在不得已的时候才使用。

3.7.4 依赖继承

By default every Grails application inherits several framework dependencies. This is done through the line:

inherits "global"

Inside the BuildConfig.groovy file. To exclude specific inherited dependencies you use the excludes method:

inherits("global") {
    excludes "oscache", "ehcache"
}

缺省情况下,每一个Grails应用都继承多个框架依赖,配置如下:

inherits "global"

BuildConfig.groovy文件中,你可以通过excludes方法来排除掉指定的依赖,比如:

inherits("global") {
    excludes "oscache", "ehcache"
}

3.7.5 缺省的依赖

Most Grails applications have runtime dependencies on several jar files that are provided by the Grails framework. These include libraries like Spring, Sitemesh, Hibernate etc. When a war file is created, all of these dependencies will be included in it. But, an application may choose to exclude these jar files from the war. This is useful when the jar files will be provided by the container, as would normally be the case if multiple Grails applications are deployed to the same container.

The dependency resolution DSL provides a mechanism to express that all of the default dependencies will be provided by the container. This is done by invoking the defaultDependenciesProvided method and passing true as an argument:

grails.project.dependency.resolution = {

defaultDependenciesProvided true // all of the default dependencies will // be "provided" by the container

inherits "global" // inherit Grails' default dependencies

repositories { grailsHome() … } dependencies { … } }

defaultDependenciesProvided must come before inherits, otherwise the Grails dependencies will be included in the war.

大部分的Grails应用依赖于Grails框架自带的一些jar文件,比如Spring、Sitemesh和Hibernate等等。当创建一个war文件的时候。但是应用还是可以选择从war中排除这些jar文件的。这非常适合于本身容器已经包含这些jar文件的情况,尤其多个Grails应用部署于这样同一容器中。

解析依赖的DSL就有让容器提供缺省依赖的选项,这可以通过传递truedefaultDependenciesProvided方法来实现:

grails.project.dependency.resolution = {

defaultDependenciesProvided true // all of the default dependencies will // be "provided" by the container

inherits "global" // inherit Grails' default dependencies

repositories { grailsHome() … } dependencies { … } }

defaultDependenciesProvided必须位于inherits之前,否则Grails的依赖还是会被包含到war中。

3.7.6 快照和其他变化的依赖

Typically, dependencies are constant. That is, for a given combination of group, name and version the jar (or plugin) that it refers to will never change. The Grails dependency management system uses this fact to cache dependencies in order to avoid having to download them from the source repository each time. Sometimes this is not desirable. For example, many developers use the convention of a snapshot (i.e. a dependency with a version number ending in “-SNAPSHOT”) that can change from time to time while still retaining the same version number. We call this a "changing dependency".

Whenever you have a changing dependency, Grails will always check the remote repository for a new version. More specifically, when a changing dependency is encountered during dependency resolution its last modified timestamp in the local cache is compared against the last modified timestamp in the dependency repositories. If the version on the remote server is deemed to be newer than the version in the local cache, the new version will be downloaded and used.

{info} Be sure to read the next section on “Dependency Resolution Caching” in addition to this one as it affects changing dependencies. {info}

All dependencies (jars and plugins) with a version number ending in -SNAPSHOT are implicitly considered to be changing by Grails. You can also explicitly specify that a dependency is changing by setting the changing flag in the dependency DSL:

runtime ('org.my:lib:1.2.3') {
    changing = true
}

There is a caveat to the support for changing dependencies that you should be aware of. Grails will stop looking for newer versions of a dependency once it finds a remote repository that has the dependency.

Consider the following setup:

grails.project.dependency.resolution = {
    repositories {
        mavenLocal()
        mavenRepo "http://my.org/repo"
    }
    dependencies {
        compile "myorg:mylib:1.0-SNAPSHOT"
    }

In this example we are using the local maven repository and a remote network maven repository. Assuming that the local Grails dependency and the local Maven cache do not contain the dependency but the remote repository does, when we perform dependency resolution the following actions will occur:

  • maven local repository is searched, dependency not found
  • maven network repository is searched, dependency is downloaded to the cache and used

Note that the repositories are checked in the order they are defined in the BuildConfig.groovy file.

If we perform dependency resolution again without the dependency changing on the remote server, the following will happen:

  • maven local repository is searched, dependency not found
  • maven network repository is searched, dependency is found to be the same “age” as the version in the cache so will not be updated (i.e. downloaded)

Later on, a new version of mylib 1.0-SNAPSHOT is published changing the version on the server. The next time we perform dependency resolution, the following will happen:

  • maven local repository is searched, dependency not found
  • maven network repository is searched, dependency is found to newer than version in the cache so will be updated (i.e. downloaded to the cache)

So far everything is working well.

Now we want to test some local changes to the mylib library. To do this we build it locally and install it to the local Maven cache (how doesn't particularly matter). The next time we perform a dependency resolution, the following will occur:

  • maven local repository is searched, dependency is found to newer than version in the cache so will be updated (i.e. downloaded to the cache)
  • maven network repository is NOT searched as we've already found the dependency

This is what we wanted to occur.

Later on, a new version of mylib 1.0-SNAPSHOT is published changing the version on the server. The next time we perform dependency resolution, the following will happen:

  • maven local repository is searched, dependency is found to be the same “age” as the version in the cache so will not be updated (i.e. downloaded)
  • maven network repository is NOT searched as we've already found the dependency

This is likely to not be the desired outcome. We are now out of sync with the latest published snapshot and will continue to keep using the version from the local maven repository.

The rule to remember is this: when resolving a dependency, Grails will stop searching as soon as it finds a repository that has the dependency at the specified version number. It will not continue searching all repositories trying to find a more recently modified instance.

To remedy this situation (i.e. build against the newer version of mylib 1.0-SNAPSHOT in the remote repository), you can either:

  • Delete the version from the local maven repository, or
  • Reorder the repositories in the BuildConfig.groovy file

Where possible, prefer deleting the version from the local maven repository. In general, when you have finished building against a locally built SNAPSHOT always try to clear it from the local maven repository.

This changing dependency behaviour is an unmodifiable characteristic of the underlying dependency management system that Grails uses, Apache Ivy. It is currently not possible to have Ivy search all repositories to look for newer versions (in terms of modification date) of the same dependency (i.e. the same combination of group, name and version).

通常情况下,依赖是不变的。或者说,对于给定了groupnameversion组合的jar(或者插件)来说,就意味着将永远不变。Grails的依赖管理系统就是利用这点来缓存依赖以避免每次从远程存储库中下载。但有时候这并不是可取的。比如很多开发者使用的是 快照 (例如依赖一个以“-SNAPSHOT”结尾的版本号)规约,这样就是在同一个版本下,也可以一次一次的更新变化。我们称之为“变化的依赖”。

每当你有一个变化依赖的时候,Grails将总是从远程存储库中检查新版本。更确切地说,当依赖解析处理一个变化依赖时,其本地缓存的最新时间戳将跟依赖存储库中的最新时间戳比较。如果远程服务器的版本比本地缓存的新,那么新的版本将被下载和使用。

{info} 除此之外,请务必阅读下一节的“依赖解析缓存”,因为它将影响变化的依赖。 {info}

所有版本号以-SNAPSHOT结尾的依赖(jar和插件),Grails将 隐含地 将其视为变化的。你也可以通过changing标记来显式的在依赖DSL中指定变化的依赖:

runtime ('org.my:lib:1.2.3') {
    changing = true
}

对变化依赖的支持,有一个点你需要知道,那就是一旦在依赖的远程存储库中存在了,那么Grails将会停止查找依赖的新版本。

以如下的设置为例:

grails.project.dependency.resolution = {
    repositories {
        mavenLocal()
        mavenRepo "http://my.org/repo"
    }
    dependencies {
        compile "myorg:mylib:1.0-SNAPSHOT"
    }

在此示例中,我们将使用maven的本地存储库和一个远程网络存储库。假设一个依赖不在Grails的本地依赖和Maven的本地缓存中,而是在其远程存储库中,那么当我们执行依赖解析的时候,将会发生如下的动作:

  • 在maven的本地存储库中查找,依赖没有找到
  • 在maven的网络存储库中查找,依赖将被下载到本地缓存中并且使用它

要提醒的是,存储库的检查顺序是定义在BuildConfig.groovy文件中的。

如果远程服务器依赖没有变化,我们再执行依赖解析的话,将发生如下情况:

  • 在maven的本地存储库中查找,依赖没有找到
  • 在maven的网络存储库中查找,依赖被发现跟本地缓存是同一个版本,因此也就不会更新(比如下载)

再后来,一个新版本的mylib 1.0-SNAPSHOT被发布到服务器上。接下来我们再执行依赖解析,将会发生如下情况:

  • 在maven的本地存储库中查找,依赖没有找到
  • 在maven的网络存储库中查找,依赖被发现比本地缓存的版本新,因此执行更新操作(比如将其下载到本地缓存中)

到目前为止,一切都很顺利。

现在我们想测试mylib库的一些本地变动。要完成此事,我们需要在本地构建并且将其安装到Maven的本地缓存中(如果没有什么特别问题的话)。接下来,我们执行依赖解析,将发生如下情况:

  • 在maven的本地存储库中查找,依赖被发现比本地缓存的版本新,因此执行更新操作(比如将其下载到本地缓存中)
  • 不再查找maven的网络存储库,因为我们已经找到了依赖

这正是我们期望发生的。

再继续,在服务器上发布一个变化的新版本mylib 1.0-SNAPSHOT。然后,我们执行依赖解析,将发生如下情况:

  • 在maven的本地存储库中查找,依赖被发现跟本地缓存是同一个版本,因此也就不会更新(比如下载)
  • 不再查找maven的网络存储库,因为我们已经找到了依赖

这可能就不是一个理想的结果了。我们现在不再同步已经最新发布的快照了,并且将继续使用maven的本地存储库中的版本。

因此要记住这条规则:在解析一个依赖的时候,如果在一个存储库中找到了一个给定版本的依赖,那么Grails将会停止其搜索。因此也就 不会 继续搜索全部的存储库以查找一个最新修改的依赖实例。

要补救这个问题(比如远程存储库中mylib 1.0-SNAPSHOT 版本构建问题),你可以有下边两个选择:

  • 删除maven的本地存储库中的版本,或者
  • BuildConfig.groovy文件中的repositories重新排序

如果可能,推荐采用删除maven的本地存储库中的版本的方式。总的来说,当你结束基于SNAPSHOT的本地构建时,应该尽量将其从maven的本地存储库中清除。

此变化依赖的行为是Apache Ivy(是Grails的依赖管理系统基础)一个不可变特性。目前,Ivy是不可能搜索全部的存储库来寻找同一个依赖(比如相同groupnameversion的组合)的最新版本(修改时间的同名词)。

3.7.7 依赖报告

As mentioned in the previous section a Grails application consists of dependencies inherited from the framework, the plugins installed and the application dependencies itself.

To obtain a report of an application's dependencies you can run the dependency-report command:

grails dependency-report

By default this will generate reports in the target/dependency-report directory. You can specify which configuration (scope) you want a report for by passing an argument containing the configuration name:

grails dependency-report runtime

正如前面提到的,Grails应用所依赖的主要来自系统框架,已经安装的插件和应用自身的依赖。

要获取应用的依赖情况报告,可以运行dependency-report命令:

grails dependency-report

缺省情况下,生成的报告位于target/dependency-report目录下。你也可以通过传递一个配置(又称范围)名称来生成特定的报告,比如:

grails dependency-report runtime

3.7.8 插件的JAR依赖

Specifying Plugin JAR dependencies

The way in which you specify dependencies for a plugin is identical to how you specify dependencies in an application. When a plugin is installed into an application the application automatically inherits the dependencies of the plugin.

To define a dependency that is resolved for use with the plugin but not exported to the application then you can set the export property of the dependency:

test('org.spockframework:spock-core:0.5-groovy-1.8') {
    export = false
}

In this case the Spock dependency will be available only to the plugin and not resolved as an application dependency. Alternatively, if you're using the Map syntax:

test group: 'org.spockframework', name: 'spock-core',
     version: '0.5-groovy-1.8', export: false

You can use exported = false instead of export = false, but we recommend the latter because it's consistent with the Map argument.

指定插件的JAR依赖

为一个插件指定依赖的方式跟你为一个应用的方式一致。当一个插件被安装以后,此应用将自动继承插件的依赖。

如果希望插件的依赖 不要导出 到应用中,可以通过设置export属性来完成:

test('org.spockframework:spock-core:0.5-groovy-1.8') {
    export = false
}

上述示例,所依赖的Spock只在插件中有效,在应用中将不会被解析。你也可以使用Map的语法来设置:

test group: 'org.spockframework', name: 'spock-core',
     version: '0.5-groovy-1.8', export: false

你也可以使用exported = false来替代export = false,但是我们推荐你使用后者,因为这样可以跟Map参数保持一致。

Overriding Plugin JAR Dependencies in Your Application

If a plugin is using a JAR which conflicts with another plugin, or an application dependency then you can override how a plugin resolves its dependencies inside an application using exclusions. For example:

plugins {
    compile(":hibernate:$grailsVersion") {
        excludes "javassist"
    }
}

dependencies { runtime "javassist:javassist:3.4.GA" }

In this case the application explicitly declares a dependency on the "hibernate" plugin and specifies an exclusion using the excludes method, effectively excluding the javassist library as a dependency.

在应用中覆盖插件依赖

如果一个插件使用到的JAR依赖跟另外一个插件或者应用的依赖相冲突,你可以使用排除加覆盖的方式来解决,比如:

plugins {
    compile(":hibernate:$grailsVersion") {
        excludes "javassist"
    }
}

dependencies { runtime "javassist:javassist:3.4.GA" }

在上述依赖"hibernate"插件的应用中,你可以通过excludes方法排除掉javassist框架,而后另外声明其依赖。

3.7.9 Maven集成

When using the Grails Maven plugin, Grails' dependency resolution mechanics are disabled as it is assumed that you will manage dependencies with Maven's pom.xml file.

However, if you would like to continue using Grails regular commands like run-app, test-app and so on then you can tell Grails' command line to load dependencies from the Maven pom.xml file instead.

To do so simply add the following line to your BuildConfig.groovy:

grails.project.dependency.resolution = {
    pom true
    ..
}

The line pom true tells Grails to parse Maven's pom.xml and load dependencies from there.

当使用Grails的Maven插件时,Grails自带的依赖解析机制将被禁止,因为其假设你已经使用Maven的pom.xml来管理依赖了。

即使这样,你如果想继续使用Grails的常规命令比如run-apptest-app等等,你还是可以通过其命令行从Maven的pom.xml加载依赖的。

你只需要在BuildConfig.groovy文件中简单加入如下即可:

grails.project.dependency.resolution = {
    pom true
    ..
}

pom true就是用来告诉Grails要使用Maven的pom.xml来加载依赖。

3.7.10 部署到Maven存储库

If you use Maven to build your Grails project, you can use the standard Maven targets mvn install and mvn deploy. If not, you can deploy a Grails project or plugin to a Maven repository using the maven-publisher plugin.

The plugin provides the ability to publish Grails projects and plugins to local and remote Maven repositories. There are two key additional targets added by the plugin:

  • maven-install - Installs a Grails project or plugin into your local Maven cache
  • maven-deploy - Deploys a Grails project or plugin to a remote Maven repository

By default this plugin will automatically generate a valid pom.xml for you unless a pom.xml is already present in the root of the project, in which case this pom.xml file will be used.

如果你是使用Maven来构建你的Grails工程,那么你可以使用标准的Maven目标命令(target)mvn installmvn deploy来安装部署。 如果不是,你可以通过maven-publisher插件来将Grails工程或者插件部署到Maven存储库。

此插件能够将Grails工程和插件发布到本地和远程的Maven存储库,下面是此插件额外的目标命令:

  • maven-install - 安装Grails工程或者插件到你本地的Maven缓存中
  • maven-deploy - 部署Grails工程或者插件到远程的Maven存储库中

如果你工程的根目录下边没有pom.xml,此插件将为你自动生成此文件,否则系统将直接使用已经存在的pom.xml文件。

maven-install

The maven-install command will install the Grails project or plugin artifact into your local Maven cache:

grails maven-install

In the case of plugins, the plugin zip file will be installed, whilst for application the application WAR file will be installed.

maven-install

maven-install命令将安装Grails工程或者插件到你本地的Maven缓存:

grails maven-install

当工程是插件时,将安装成zip文件;当是应用时,将安装成WAR文件。

maven-deploy

The maven-deploy command will deploy a Grails project or plugin into a remote Maven repository:

grails maven-deploy

It is assumed that you have specified the necessary <distributionManagement> configuration within a pom.xml or that you specify the id of the remote repository to deploy to:

grails maven-deploy --repository=myRepo

The repository argument specifies the 'id' for the repository. Configure the details of the repository specified by this 'id' within your grails-app/conf/BuildConfig.groovy file or in your $USER_HOME/.grails/settings.groovy file:

grails.project.dependency.distribution = {
    localRepository = "/path/to/my/local"
    remoteRepository(id: "myRepo", url: "http://myserver/path/to/repo")
}

The syntax for configuring remote repositories matches the syntax from the remoteRepository element in the Ant Maven tasks. For example the following XML:

<remoteRepository id="myRepo" url="scp://localhost/www/repository">
    <authentication username="..." privateKey="${user.home}/.ssh/id_dsa"/>
</remoteRepository>

Can be expressed as:

remoteRepository(id: "myRepo", url: "scp://localhost/www/repository") {
    authentication username: "...", privateKey: "${userHome}/.ssh/id_dsa"
}

By default the plugin will try to detect the protocol to use from the URL of the repository (ie "http" from "http://.." etc.), however to specify a different protocol you can do:

grails maven-deploy --repository=myRepo --protocol=webdav

The available protocols are:

  • http
  • scp
  • scpexe
  • ftp
  • webdav

maven-deploy

maven-deploy命令将Grails工程或者插件发布到远程的Maven存储库:

grails maven-deploy

上述示例的前提是你已经配置了pom.xml文件的<distributionManagement>,或者你可以通过指定远程存储库id的方式,比如:

grails maven-deploy --repository=myRepo

repository参数是存储库的'id',此'id'的详细配置位于grails-app/conf/BuildConfig.groovy或者$USER_HOME/.grails/settings.groovy文件中:

grails.project.dependency.distribution = {
    localRepository = "/path/to/my/local"
    remoteRepository(id: "myRepo", url: "http://myserver/path/to/repo")
}

配置远程存储库的语法跟Ant Maven任务的remoteRepository元素相对应,如下XML所示:

<remoteRepository id="myRepo" url="scp://localhost/www/repository">
    <authentication username="..." privateKey="${user.home}/.ssh/id_dsa"/>
</remoteRepository>

可以对应为:

remoteRepository(id: "myRepo", url: "scp://localhost/www/repository") {
    authentication username: "...", privateKey: "${userHome}/.ssh/id_dsa"
}

缺省条件下,此插件通过存储库的URL来自动检测协议的(比如"http://.."是"http"),不过你还是可以指定另外一个不同的协议的:

grails maven-deploy --repository=myRepo --protocol=webdav

支持的有效协议如下:

  • http
  • scp
  • scpexe
  • ftp
  • webdav

Groups, Artifacts and Versions

Maven defines the notion of a 'groupId', 'artifactId' and a 'version'. This plugin pulls this information from the Grails project conventions or plugin descriptor.

Projects

For applications this plugin will use the Grails application name and version provided by Grails when generating the pom.xml file. To change the version you can run the set-version command:

grails set-version 0.2

The Maven groupId will be the same as the project name, unless you specify a different one in Config.groovy:

grails.project.groupId="com.mycompany"

组,工件和版本

在Maven中,定义了'groupId','artifactId'和'version'概念,Maven插件会将Grails工程或者插件的描述转换为这些相应的定义。

工程

对于一个应用来说,当要生成pom.xml文件的时候,此插件会根据Grails应用提供的名称和版本进行生成。要修改版本信息,可以运行set-version命令来完成:

grails set-version 0.2

缺省情况下,Maven的groupId跟其工程名称一样,不过你可以在Config.groovy中指定,比如:

grails.project.groupId="com.mycompany"

Plugins

With a Grails plugin the groupId and version are taken from the following properties in the *GrailsPlugin.groovy descriptor:

String groupId = 'myOrg'
String version = '0.1'

The 'artifactId' is taken from the plugin name. For example if you have a plugin called FeedsGrailsPlugin the artifactId will be "feeds". If your plugin does not specify a groupId then this defaults to "org.grails.plugins".

插件

对于一个Grails插件来说,其groupIdversion来自于*GrailsPlugin.groovy文件的描述属性:

String groupId = 'myOrg'
String version = '0.1'

'artifactId'来自于插件的名称。比如你有一个插件是FeedsGrailsPlugin那么其artifactId是"feeds"。如果你的插件没有指定groupId那么其缺省值为"org.grails.plugins"。

3.7.11 插件依赖

As of Grails 1.3 you can declaratively specify plugins as dependencies via the dependency DSL instead of using the install-plugin command:

grails.project.dependency.resolution = {
    …
    repositories {
        …
    }

plugins { runtime ':hibernate:1.2.1' }

dependencies { … } … }

If you don't specify a group id the default plugin group id of org.grails.plugins is used. You can specify to use the latest version of a particular plugin by using "latest.integration" as the version number:

plugins {
    runtime ':hibernate:latest.integration'
}

从Grails 1.3以来,你就可以通过依赖DSL的方式来声明插件的依赖了,而非只用install-plugin命令:

grails.project.dependency.resolution = {
    …
    repositories {
        …
    }

plugins { runtime ':hibernate:1.2.1' }

dependencies { … } … }

如果你没有指定插件的组ID,其缺省值是org.grails.plugins。你还可以将"latest.integration"作为版本号,这样就会自动获取最新的版本,例如:

plugins {
    runtime ':hibernate:latest.integration'
}

Integration vs. Release

The "latest.integration" version label will also include resolving snapshot versions. To not include snapshot versions then use the "latest.release" label:

plugins {
    runtime ':hibernate:latest.release'
}

The "latest.release" label only works with Maven compatible repositories. If you have a regular SVN-based Grails repository then you should use "latest.integration".

And of course if you use a Maven repository with an alternative group id you can specify a group id:

plugins {
    runtime 'mycompany:hibernate:latest.integration'
}

集成(Integration)和正式(Release)版本

"latest.integration"的版本标签将会解析包含快照的版本,如果不想,可以使用"latest.release"标签:

plugins {
    runtime ':hibernate:latest.release'
}

"latest.release"标签仅仅用于兼容Maven的存储库。如果你使用基于SVN的Grails存储库,你应该使用"latest.integration"。

当然如果你使用非缺省组ID的Maven存储库,你可以为其指定一个:

plugins {
    runtime 'mycompany:hibernate:latest.integration'
}

Plugin Exclusions

You can control how plugins transitively resolves both plugin and JAR dependencies using exclusions. For example:

plugins {
    runtime(':weceem:0.8') {
        excludes "searchable"
    }
}

Here we have defined a dependency on the "weceem" plugin which transitively depends on the "searchable" plugin. By using the excludes method you can tell Grails not to transitively install the searchable plugin. You can combine this technique to specify an alternative version of a plugin:

plugins {
    runtime(':weceem:0.8') {
        excludes "searchable" // excludes most recent version
    }
    runtime ':searchable:0.5.4' // specifies a fixed searchable version
}

You can also completely disable transitive plugin installs, in which case no transitive dependencies will be resolved:

plugins {
    runtime(':weceem:0.8') {
        transitive = false
    }
    runtime ':searchable:0.5.4' // specifies a fixed searchable version
}

插件的排除

通过使用排除,你可以控制插件如何传递地解析JAR和插件的依赖,比如:

plugins {
    runtime(':weceem:0.8') {
        excludes "searchable"
    }
}

此处我们定义了一个名为"weceem"的插件,其又依赖于"searchable"插件。通过使用excludes方法,你可以告诉Grails 不以 传递的方式安装searchable插件。你可以在此种方式的基础上,另外单独安装另一个版本的插件:

plugins {
    runtime(':weceem:0.8') {
        excludes "searchable" // excludes most recent version
    }
    runtime ':searchable:0.5.4' // specifies a fixed searchable version
}

你也可以完全禁用插件的传递安装,此时那些传递的依赖将不再解析,比如:

plugins {
    runtime(':weceem:0.8') {
        transitive = false
    }
    runtime ':searchable:0.5.4' // specifies a fixed searchable version
}