Fitnesse Maven Classpath plugin cannot resolve rem

2019-07-23 03:43发布

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;
    }
}

2条回答
放我归山
2楼-- · 2019-07-23 04:34

I was able to run the MavenClasspathExtractorTest successfully, then:

  • Changed the commons-lang version in the pom.xml file used by the test, changed to 2.0, not present in my Maven cache: test successful, dependency downloaded propery in my Maven cache
  • Removed locally the common-lang version specified by the pom.xml, 2.6: test successful, dependency downloaded again to my local cache

Hence, 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 same pom.xml test file above). The following warning was shown into my IDE console:

[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

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:

<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>

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.

查看更多
闹够了就滚
3楼-- · 2019-07-23 04:35

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:

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
查看更多
登录 后发表回答