Fitnesse Maven Classpath 插件无法解析远程依赖项
Fitnesse Maven Classpath plugin cannot resolve remote dependencies
我正在使用允许 Fitnesse 使用 Maven 存储库解析类路径的 Fitnesse Maven classpath plugin。
该插件有效,但它似乎无法解析存储在我的远程存储库中的依赖项。
如果它在我的本地存储库中但不在远程,它可以解析快照。
如果我 运行 a mvn install
(来自 Fitnesse 外部),那么它可以毫无问题地找到依赖关系,这表明我的 settings.xml
设置正确。
我已经调试了 plugin 但我很难准确指出它无法解析远程快照的原因。
问题:如何更改它以允许它解析快照?
编辑:添加更多详细信息
这可以通过 运行 单元测试轻松重现:MavenClasspathExtractorTest
此单元测试尝试解决 commons-lang 依赖性。
如果您从本地存储库中删除此依赖项,则测试将失败,因为它似乎无法从远程存储库中检索。
如果您将它放回本地存储库,它会再次通过。
这就是问题的关键。
可能的解决方案
以下是使用 jcabi-aether
库的潜在解决方案,首先使用本地存储库,如果没有则从远程存储库下载。这看起来很简单吗?它适合我的需要
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Repository;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.building.*;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLoggerManager;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import com.jcabi.aether.Aether;
/**
* Utility class to extract classpath elements from Maven projects. Heavily based on code copied from Jenkin's Maven
* support.
*/
public class MavenClasspathExtractor {
public static final String userHome = System.getProperty( "user.home" );
public static final File userMavenConfigurationHome = new File( userHome, ".m2" );
public static final String envM2Home = System.getenv("M2_HOME");
public final static String DEFAULT_SCOPE = "test";
public static final File DEFAULT_USER_SETTINGS_FILE = new File( userMavenConfigurationHome, "settings.xml" );
public static final File DEFAULT_GLOBAL_SETTINGS_FILE =
new File( System.getProperty( "maven.home", envM2Home != null ? envM2Home : "" ), "conf/settings.xml" );
private final Logger logger = new ConsoleLoggerManager().getLoggerForComponent("maven-classpath-plugin");
// Ensure M2_HOME variable is handled in a way similar to the mvn executable (script). To the extend possible.
static {
String m2Home = System.getenv().get("M2_HOME");
if (m2Home != null && System.getProperty("maven.home") == null) {
System.setProperty("maven.home", m2Home);
}
}
public List<String> extractClasspathEntries(File pomFile) throws MavenClasspathExtractionException {
return extractClasspathEntries(pomFile, DEFAULT_SCOPE);
}
public List<String> extractClasspathEntries(File pomFile, String scope) throws MavenClasspathExtractionException {
MavenXpp3Reader mavenReader;
FileReader reader = null;
try {
mavenReader = new MavenXpp3Reader();
reader = new FileReader(pomFile);
Model model = mavenReader.read(reader);
model.setPomFile(pomFile);
Collection<RemoteRepository> remoteRepositories = getRemoteRepositories();
File localRepo = new File( RepositorySystem.defaultUserLocalRepository.getAbsolutePath());
MavenProject project = new MavenProject(model);
Aether aether = new Aether(remoteRepositories, localRepo);
Artifact root = new DefaultArtifact(project.getGroupId(), project.getArtifactId(), project.getPackaging(), project.getVersion());
List<Artifact> artifacts = aether.resolve(root, scope);
List<String> paths = new ArrayList<>();
for(Artifact artifact : artifacts) {
paths.add(artifact.getFile().getAbsolutePath());
}
return paths;
} catch (Exception e) {
throw new MavenClasspathExtractionException(e);
} finally {
if(reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new MavenClasspathExtractionException(e);
}
}
}
}
private Collection<RemoteRepository> getRemoteRepositories() throws SettingsBuildingException {
SettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
settingsBuildingRequest.setSystemProperties(System.getProperties());
settingsBuildingRequest.setUserSettingsFile(DEFAULT_USER_SETTINGS_FILE);
settingsBuildingRequest.setGlobalSettingsFile(DEFAULT_GLOBAL_SETTINGS_FILE);
DefaultSettingsBuilderFactory mvnSettingBuilderFactory = new DefaultSettingsBuilderFactory();
DefaultSettingsBuilder settingsBuilder = mvnSettingBuilderFactory.newInstance();
SettingsBuildingResult settingsBuildingResult = settingsBuilder.build(settingsBuildingRequest);
Settings effectiveSettings = settingsBuildingResult.getEffectiveSettings();
Map<String, Profile> profilesMap = effectiveSettings.getProfilesAsMap();
Collection<RemoteRepository> remotes = new ArrayList<RemoteRepository>(20);
for (String profileName : effectiveSettings.getActiveProfiles())
{
Profile profile = profilesMap.get(profileName);
List<Repository> repositories = profile.getRepositories();
for (Repository repo : repositories)
{
RemoteRepository remoteRepo
= new RemoteRepository(repo.getId(), "default", repo.getUrl());
remotes.add(remoteRepo);
}
}
return remotes;
}
}
我能够 运行 MavenClasspathExtractorTest
成功,然后:
- 更改了测试使用的
pom.xml
文件中的 commons-lang
版本,更改为 2.0
,不存在于我的 Maven 缓存中:测试成功,已下载依赖项 属性 在我的 Maven 缓存中
- 本地删除了
pom.xml
指定的common-lang
版本,2.6
:测试成功,依赖再次下载到我的本地缓存
因此,您遇到的行为不可重现:它正确地从远程存储库获取 Maven 依赖项。
然而,它确实无法获取 SNAPSHOT
依赖项(将它们添加到上面相同的 pom.xml
测试文件中)。以下警告显示在我的 IDE 控制台中:
[WARNING] Could not transfer metadata /maven-metadata.xml from/to nexus (http:///nexus/content/groups/public/): No connector available to access repository nexus (http:///nexus/content/groups/public/) of type default using the available factories
注意:我删除了一些信息(url,依赖名称)。
这主要是因为项目 pom.xml
中缺少一些依赖项,因此在 运行 时,嵌入式 Maven 找不到任何有效的工厂来从给定的存储库传输依赖项。
将以下依赖项添加到 pom(项目的,而不是测试的)中解决了问题:
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-connector-basic</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-wagon</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-http</artifactId>
<version>2.8</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-provider-api</artifactId>
<version>2.8</version>
</dependency>
然后 SNAPSHOT
依赖项被正确获取。
更新
上述行为受到 IDE(在我的例子中是 Eclipse)的影响,它在编辑测试 pom 时自动下载依赖项。否则,依赖项应该已经在本地 Maven 缓存中才能成功执行测试。
您看到的行为是插件的预期行为:此插件的目标不是重现 Maven 为您下载依赖项的能力。
它的目标是使您在使用 Maven 进行夹具开发期间可用的依赖项(即为夹具编写 Java 代码然后编译它们)在使用您的夹具时也可用于 wiki。
它假定您使用 Maven 编译了您的 Java 源代码(因此将任何需要的依赖项下载到您的本地存储库)并且您现在想要使用生成的 类(以及它们来自 wiki 的依赖项)。 (这是@A_Di-Matteo也经历过的。)
如果你想让所有的依赖项对不编译代码的人可用,你应该将依赖项复制到 FitNesse 安装并使用 wiki 分发它们。我知道获取所有依赖项并将它们放在一个地方的最简单方法是使用 maven-dependency-plugin 的复制依赖项目标。
在我的 FitNesse baseline 中,我使用 Maven 类路径插件来确保我不必在开发过程中手动更新 wiki 的类路径。我将依赖项复制到一个目录并使用 wiki 分发它们,这样人们就可以 execute/write 使用固定装置进行测试,而无需编译或在他们的系统上安装 Maven(创建 'a standalone (no JDK or Maven required) Fitnesse environment')。在我的根页面上,我结合了这两种方法:
The location when working standalone:
!path fixtures
!path fixtures/*.jar
When developing and changing the fixtures, we will work based on the pom.xml:
!pomFile ../pom.xml@compile
我正在使用允许 Fitnesse 使用 Maven 存储库解析类路径的 Fitnesse Maven classpath plugin。
该插件有效,但它似乎无法解析存储在我的远程存储库中的依赖项。
如果它在我的本地存储库中但不在远程,它可以解析快照。
如果我 运行 a mvn install
(来自 Fitnesse 外部),那么它可以毫无问题地找到依赖关系,这表明我的 settings.xml
设置正确。
我已经调试了 plugin 但我很难准确指出它无法解析远程快照的原因。
问题:如何更改它以允许它解析快照?
编辑:添加更多详细信息
这可以通过 运行 单元测试轻松重现:MavenClasspathExtractorTest
此单元测试尝试解决 commons-lang 依赖性。
如果您从本地存储库中删除此依赖项,则测试将失败,因为它似乎无法从远程存储库中检索。
如果您将它放回本地存储库,它会再次通过。
这就是问题的关键。
可能的解决方案
以下是使用 jcabi-aether
库的潜在解决方案,首先使用本地存储库,如果没有则从远程存储库下载。这看起来很简单吗?它适合我的需要
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Repository;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.building.*;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.console.ConsoleLoggerManager;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.util.artifact.DefaultArtifact;
import com.jcabi.aether.Aether;
/**
* Utility class to extract classpath elements from Maven projects. Heavily based on code copied from Jenkin's Maven
* support.
*/
public class MavenClasspathExtractor {
public static final String userHome = System.getProperty( "user.home" );
public static final File userMavenConfigurationHome = new File( userHome, ".m2" );
public static final String envM2Home = System.getenv("M2_HOME");
public final static String DEFAULT_SCOPE = "test";
public static final File DEFAULT_USER_SETTINGS_FILE = new File( userMavenConfigurationHome, "settings.xml" );
public static final File DEFAULT_GLOBAL_SETTINGS_FILE =
new File( System.getProperty( "maven.home", envM2Home != null ? envM2Home : "" ), "conf/settings.xml" );
private final Logger logger = new ConsoleLoggerManager().getLoggerForComponent("maven-classpath-plugin");
// Ensure M2_HOME variable is handled in a way similar to the mvn executable (script). To the extend possible.
static {
String m2Home = System.getenv().get("M2_HOME");
if (m2Home != null && System.getProperty("maven.home") == null) {
System.setProperty("maven.home", m2Home);
}
}
public List<String> extractClasspathEntries(File pomFile) throws MavenClasspathExtractionException {
return extractClasspathEntries(pomFile, DEFAULT_SCOPE);
}
public List<String> extractClasspathEntries(File pomFile, String scope) throws MavenClasspathExtractionException {
MavenXpp3Reader mavenReader;
FileReader reader = null;
try {
mavenReader = new MavenXpp3Reader();
reader = new FileReader(pomFile);
Model model = mavenReader.read(reader);
model.setPomFile(pomFile);
Collection<RemoteRepository> remoteRepositories = getRemoteRepositories();
File localRepo = new File( RepositorySystem.defaultUserLocalRepository.getAbsolutePath());
MavenProject project = new MavenProject(model);
Aether aether = new Aether(remoteRepositories, localRepo);
Artifact root = new DefaultArtifact(project.getGroupId(), project.getArtifactId(), project.getPackaging(), project.getVersion());
List<Artifact> artifacts = aether.resolve(root, scope);
List<String> paths = new ArrayList<>();
for(Artifact artifact : artifacts) {
paths.add(artifact.getFile().getAbsolutePath());
}
return paths;
} catch (Exception e) {
throw new MavenClasspathExtractionException(e);
} finally {
if(reader != null) {
try {
reader.close();
} catch (IOException e) {
throw new MavenClasspathExtractionException(e);
}
}
}
}
private Collection<RemoteRepository> getRemoteRepositories() throws SettingsBuildingException {
SettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
settingsBuildingRequest.setSystemProperties(System.getProperties());
settingsBuildingRequest.setUserSettingsFile(DEFAULT_USER_SETTINGS_FILE);
settingsBuildingRequest.setGlobalSettingsFile(DEFAULT_GLOBAL_SETTINGS_FILE);
DefaultSettingsBuilderFactory mvnSettingBuilderFactory = new DefaultSettingsBuilderFactory();
DefaultSettingsBuilder settingsBuilder = mvnSettingBuilderFactory.newInstance();
SettingsBuildingResult settingsBuildingResult = settingsBuilder.build(settingsBuildingRequest);
Settings effectiveSettings = settingsBuildingResult.getEffectiveSettings();
Map<String, Profile> profilesMap = effectiveSettings.getProfilesAsMap();
Collection<RemoteRepository> remotes = new ArrayList<RemoteRepository>(20);
for (String profileName : effectiveSettings.getActiveProfiles())
{
Profile profile = profilesMap.get(profileName);
List<Repository> repositories = profile.getRepositories();
for (Repository repo : repositories)
{
RemoteRepository remoteRepo
= new RemoteRepository(repo.getId(), "default", repo.getUrl());
remotes.add(remoteRepo);
}
}
return remotes;
}
}
我能够 运行 MavenClasspathExtractorTest
成功,然后:
- 更改了测试使用的
pom.xml
文件中的commons-lang
版本,更改为2.0
,不存在于我的 Maven 缓存中:测试成功,已下载依赖项 属性 在我的 Maven 缓存中 - 本地删除了
pom.xml
指定的common-lang
版本,2.6
:测试成功,依赖再次下载到我的本地缓存
因此,您遇到的行为不可重现:它正确地从远程存储库获取 Maven 依赖项。
然而,它确实无法获取 SNAPSHOT
依赖项(将它们添加到上面相同的 pom.xml
测试文件中)。以下警告显示在我的 IDE 控制台中:
[WARNING] Could not transfer metadata /maven-metadata.xml from/to nexus (http:///nexus/content/groups/public/): No connector available to access repository nexus (http:///nexus/content/groups/public/) of type default using the available factories
注意:我删除了一些信息(url,依赖名称)。
这主要是因为项目 pom.xml
中缺少一些依赖项,因此在 运行 时,嵌入式 Maven 找不到任何有效的工厂来从给定的存储库传输依赖项。
将以下依赖项添加到 pom(项目的,而不是测试的)中解决了问题:
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-connector-basic</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-transport-wagon</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-http</artifactId>
<version>2.8</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-provider-api</artifactId>
<version>2.8</version>
</dependency>
然后 SNAPSHOT
依赖项被正确获取。
更新
上述行为受到 IDE(在我的例子中是 Eclipse)的影响,它在编辑测试 pom 时自动下载依赖项。否则,依赖项应该已经在本地 Maven 缓存中才能成功执行测试。
您看到的行为是插件的预期行为:此插件的目标不是重现 Maven 为您下载依赖项的能力。 它的目标是使您在使用 Maven 进行夹具开发期间可用的依赖项(即为夹具编写 Java 代码然后编译它们)在使用您的夹具时也可用于 wiki。 它假定您使用 Maven 编译了您的 Java 源代码(因此将任何需要的依赖项下载到您的本地存储库)并且您现在想要使用生成的 类(以及它们来自 wiki 的依赖项)。 (这是@A_Di-Matteo也经历过的。)
如果你想让所有的依赖项对不编译代码的人可用,你应该将依赖项复制到 FitNesse 安装并使用 wiki 分发它们。我知道获取所有依赖项并将它们放在一个地方的最简单方法是使用 maven-dependency-plugin 的复制依赖项目标。
在我的 FitNesse baseline 中,我使用 Maven 类路径插件来确保我不必在开发过程中手动更新 wiki 的类路径。我将依赖项复制到一个目录并使用 wiki 分发它们,这样人们就可以 execute/write 使用固定装置进行测试,而无需编译或在他们的系统上安装 Maven(创建 'a standalone (no JDK or Maven required) Fitnesse environment')。在我的根页面上,我结合了这两种方法:
The location when working standalone:
!path fixtures
!path fixtures/*.jar
When developing and changing the fixtures, we will work based on the pom.xml:
!pomFile ../pom.xml@compile