I am using the Fitnesse Maven classpath plugin that allows Fitnesse to resolve the classpath using the Maven repository.
The plugin works, but it does not seem to be able to resolve dependencies that are stored in my remote repository.
It can resolve a snapshot if it's in my local repository but not remotely.
If I run a mvn install
(from outside Fitnesse) then it has no problems finding the dependencies so that suggests my settings.xml
is set up correctly.
I've debugged the plugin but am having difficulty pinpointing exactly why it cannot resolve the remote snapshots.
Question: How can I change this to allow it to resolve snapshots?
Edit: Adding More Details
This can easily be reproduced by running this unit test: MavenClasspathExtractorTest
This unit test attempts to resolve the commons-lang dependency.
If you remove this dependency from your local repository then the test fails as it does not seem to be able to retrieve from the remote repository.
If you put it back into your local repository it passes once again.
This is the crux of the issue.
Potential Solution
The following is a potential solution using the jcabi-aether
library that uses the local repository first, and if not there will download from the remote repository. Does this look fool-proof? It works for my needs
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;
}
}
I was able to run the
MavenClasspathExtractorTest
successfully, then:commons-lang
version in thepom.xml
file used by the test, changed to2.0
, not present in my Maven cache: test successful, dependency downloaded propery in my Maven cachecommon-lang
version specified by thepom.xml
,2.6
: test successful, dependency downloaded again to my local cacheHence, the behavior you are experiencing is not reproducible: it properly fetches Maven dependencies from remote repositories.
However, indeed it was not able to fetch
SNAPSHOT
dependencies (adding them to the samepom.xml
test file above). The following warning was shown into my IDE console:Note: I removed some information (url, dependency name).
This is basically because some dependencies were missing from the project
pom.xml
, hence at runtime the embedded Maven could not find any valid factory to transfer dependencies from the given repository.Adding the following dependencies to the pom (of the project, not the tested one) fixed the issue:
The
SNAPSHOT
dependency was then fetched properly.Update
The behavior described above was influenced by the IDE (in my case Eclipse) which was downloading automatically dependencies when editing the test pom. Otherwise, dependencies should already be in the local maven cache to have the test executed successfully.
The behavior you see is the expected behavior of the plugin: the goal of this plugin is not to reproduce Maven's ability to download dependencies for you. Its goal is to make the dependencies that you have available to you during fixture development with Maven (i.e. writing Java code for fixtures and then compiling them) also available to the wiki when it uses your fixtures. It assumes you compiled your Java source using Maven (thus downloading any needed dependencies to your local repository) and you now want to use the resulting classes (and their dependencies from the wiki). (This is what @A_Di-Matteo also experienced.)
if you want to make all dependencies available to people NOT compiling the code you should copy the dependencies to the FitNesse installation and distribute them with the wiki. The easiest way I know to get all dependencies and place them in one place is by using the maven-dependency-plugin's copy-dependencies goal.
In my FitNesse baseline I use the Maven classpath plugin to ensure I don't have to update the wiki's classpath by hand during development. I copy the dependencies to a single directory and distribute them with the wiki so people can execute/write tests using the fixtures without compiling or having Maven on their system (to create 'a standalone (no JDK or Maven required) Fitnesse environment'). On my root page I combine both approaches: