RuntimeClasspathResolver.java

/*
 * Copyright © 2026 IKE Network (support@ike.network)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package network.ike.plugin;

import org.apache.maven.api.PathScope;
import org.apache.maven.api.Project;
import org.apache.maven.api.Session;
import org.apache.maven.api.services.DependencyResolver;

import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

/**
 * The one entry point through which this plugin's goals resolve a project's
 * {@code MAIN_RUNTIME} classpath (IKE-Network/ike-issues#901).
 *
 * <p>Maven 4 rc-5's {@code DefaultDependencyResolver} keeps its session-scoped module
 * cache in a plain, unsynchronized {@code HashMap}: two goals resolving concurrently in
 * a {@code -T} reactor race its {@code computeIfAbsent} and fail with
 * {@code ConcurrentModificationException} — observed nondeterministically from
 * {@code knowledge-export} and {@code knowledge-bindings} building the ike-starter-set
 * reactor in parallel. The defect is in Maven core, not in this plugin's own state, so
 * the remedy here is to serialize just this plugin's entry into the racy API with a
 * JVM-wide lock. Resolution is fast and per-project, so the contention cost is
 * negligible; remove the lock when the build adopts a Maven release carrying the
 * announced rc-6 {@code -T} concurrency fixes.
 */
final class RuntimeClasspathResolver {

    private static final ReentrantLock RESOLVER_LOCK = new ReentrantLock();

    private RuntimeClasspathResolver() {
    }

    /**
     * Resolves the project's {@code MAIN_RUNTIME} dependency paths, serialized
     * JVM-wide against every other goal in this plugin doing the same.
     *
     * @param session the Maven session
     * @param project the project whose runtime classpath to resolve
     * @return the resolved dependency paths, in resolver order
     */
    static List<Path> mainRuntimePaths(Session session, Project project) {
        RESOLVER_LOCK.lock();
        try {
            return session.getService(DependencyResolver.class)
                    .resolve(session, project, PathScope.MAIN_RUNTIME)
                    .getPaths();
        } finally {
            RESOLVER_LOCK.unlock();
        }
    }
}