(Quick Reference)

3.7.6 快照和其他变化的依赖 - Reference Documentation

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

Version: null

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的组合)的最新版本(修改时间的同名词)。