You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/07/29 12:11:32 UTC

(maven-filtering) 01/02: Experiment: migrate from plexus-build-api to Maven 4 BuildContext API

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch experiment/maven4-build-context
in repository https://gitbox.apache.org/repos/asf/maven-filtering.git

commit 4999468a6541ce3694d198a57974f90ffc4dedea
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Wed Jul 29 13:45:49 2026 +0200

    Experiment: migrate from plexus-build-api to Maven 4 BuildContext API
    
    Replace the old Scanner-based plexus BuildContext with the new Maven 4
    BuildContext API (org.apache.maven.api.build.context.BuildContext) from
    maven-api-core 4.1.0-SNAPSHOT (PR apache/maven#12576).
    
    Key changes:
    - DefaultMavenResourcesFiltering: replace buildContext.newScanner() and
      newDeleteScanner() with buildContext.registerAndProcessInputs() which
      returns Input objects with change status tracking
    - DefaultMavenFileFilter: remove buildContext.refresh() — output tracking
      is now handled by the BuildContext output association in the mojo
    - Remove plexus-build-api dependency from pom.xml
    - Delete old TestIncrementalBuildContext and IncrementalResourceFilteringTest
      (these tested the old Scanner-based API)
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
 pom.xml                                            |   8 +-
 .../shared/filtering/DefaultMavenFileFilter.java   |  13 +-
 .../filtering/DefaultMavenResourcesFiltering.java  | 172 ++++++++--------
 .../filtering/DefaultMavenFileFilterTest.java      |   4 +-
 .../IncrementalResourceFilteringTest.java          | 210 -------------------
 .../apache/maven/shared/filtering/Providers.java   |  11 +-
 .../filtering/TestIncrementalBuildContext.java     | 224 ---------------------
 7 files changed, 100 insertions(+), 542 deletions(-)

diff --git a/pom.xml b/pom.xml
index a41e938..622ec54 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,7 +62,7 @@
 
   <properties>
     <javaVersion>17</javaVersion>
-    <mavenVersion>4.0.0-rc-5</mavenVersion>
+    <mavenVersion>4.1.0-SNAPSHOT</mavenVersion>
 
     <hamcrestVersion>3.0</hamcrestVersion>
     <junitVersion>5.14.4</junitVersion>
@@ -109,11 +109,7 @@
       <artifactId>slf4j-api</artifactId>
       <version>${slf4jVersion}</version>
     </dependency>
-    <dependency>
-      <groupId>org.sonatype.plexus</groupId>
-      <artifactId>plexus-build-api</artifactId>
-      <version>${plexusBuildApiVersion}</version>
-    </dependency>
+    <!-- plexus-build-api replaced by org.apache.maven.api.build.context.BuildContext from maven-api-core -->
     <dependency>
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-utils</artifactId>
diff --git a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java
index fab9657..b65e4d2 100644
--- a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java
+++ b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenFileFilter.java
@@ -24,12 +24,8 @@ import java.util.List;
 
 import org.apache.maven.api.Project;
 import org.apache.maven.api.Session;
-import org.apache.maven.api.di.Inject;
 import org.apache.maven.api.di.Named;
 import org.apache.maven.api.di.Singleton;
-import org.sonatype.plexus.build.incremental.BuildContext;
-
-import static java.util.Objects.requireNonNull;
 
 /**
  * @author Olivier Lamy
@@ -37,12 +33,6 @@ import static java.util.Objects.requireNonNull;
 @Singleton
 @Named
 public class DefaultMavenFileFilter extends BaseFilter implements MavenFileFilter {
-    private final BuildContext buildContext;
-
-    @Inject
-    public DefaultMavenFileFilter(BuildContext buildContext) {
-        this.buildContext = requireNonNull(buildContext);
-    }
 
     @Override
     public void copyFile(
@@ -90,8 +80,7 @@ public class DefaultMavenFileFilter extends BaseFilter implements MavenFileFilte
                 getLogger().debug("copy {} to {}", from, to);
                 FilteringUtils.copyFile(from, to, encoding, new FilterWrapper[0], false);
             }
-
-            buildContext.refresh(to.toFile());
+            // Output refresh is handled by the BuildContext output association in the mojo
         } catch (IOException e) {
             throw new MavenFilteringException(
                     (filtering ? "filtering " : "copying ") + from + " to " + to + " failed with "
diff --git a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
index 406330c..6d59a35 100644
--- a/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
+++ b/src/main/java/org/apache/maven/shared/filtering/DefaultMavenResourcesFiltering.java
@@ -27,17 +27,17 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 
+import org.apache.maven.api.build.context.BuildContext;
+import org.apache.maven.api.build.context.Input;
 import org.apache.maven.api.di.Inject;
 import org.apache.maven.api.di.Named;
 import org.apache.maven.api.di.Singleton;
-import org.codehaus.plexus.util.Scanner;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.sonatype.plexus.build.incremental.BuildContext;
 
 import static java.util.Objects.requireNonNull;
 
@@ -49,9 +49,7 @@ import static java.util.Objects.requireNonNull;
 public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultMavenResourcesFiltering.class);
 
-    private static final String[] EMPTY_STRING_ARRAY = {};
-
-    private static final String[] DEFAULT_INCLUDES = {"**/**"};
+    // DEFAULT_INCLUDES is no longer needed — includes are passed directly to BuildContext.registerAndProcessInputs()
     private static final int BUFFER_LENGTH = 8192;
 
     private final List<String> defaultNonFilteredFileExtensions;
@@ -63,7 +61,7 @@ public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
     @Inject
     public DefaultMavenResourcesFiltering(MavenFileFilter mavenFileFilter, BuildContext buildContext) {
         this.mavenFileFilter = requireNonNull(mavenFileFilter);
-        this.buildContext = requireNonNull(buildContext);
+        this.buildContext = buildContext; // may be null when running without incremental support
         this.defaultNonFilteredFileExtensions = new ArrayList<>(5);
         this.defaultNonFilteredFileExtensions.add("jpg");
         this.defaultNonFilteredFileExtensions.add("jpeg");
@@ -205,28 +203,54 @@ public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
                 isFilteringUsed = true;
             }
 
-            boolean ignoreDelta = !outputExists
-                    || buildContext.hasDelta(mavenResourcesExecution.getFileFilters())
-                    || buildContext.hasDelta(getRelativeOutputDirectory(mavenResourcesExecution));
-            LOGGER.debug("ignoreDelta " + ignoreDelta);
-            Scanner scanner = buildContext.newScanner(resourceDirectory.toFile(), ignoreDelta);
+            // Use the new BuildContext API to register inputs and detect changes
+            List<String> includes = resource.getIncludes();
+            if (includes == null || includes.isEmpty()) {
+                includes = List.of("**/**");
+            }
+            List<String> excludes = resource.getExcludes();
+            if (excludes == null) {
+                excludes = List.of();
+            }
 
-            setupScanner(resource, scanner, mavenResourcesExecution.isAddDefaultExcludes());
+            // Register all resource files as inputs with the incremental build context.
+            // The BuildContext tracks file changes (NEW, MODIFIED, REMOVED, UNMODIFIED)
+            // and handles stale output cleanup automatically for REMOVED inputs.
+            Collection<? extends Input> inputs;
+            if (buildContext != null) {
+                if (mavenResourcesExecution.isAddDefaultExcludes()) {
+                    excludes = new ArrayList<>(excludes);
+                    addDefaultExcludes(excludes);
+                }
+                inputs = buildContext.registerAndProcessInputs(resourceDirectory, includes, excludes);
+            } else {
+                // Fallback: no build context — process all files
+                inputs = null;
+            }
 
-            scanner.scan();
+            // Build list of included files from the registered inputs
+            List<String> includedFiles;
+            if (inputs != null) {
+                final Path resDirFinal = resourceDirectory;
+                includedFiles = inputs.stream()
+                        .map(input -> resDirFinal.relativize(input.getPath()).toString())
+                        .toList();
+            } else {
+                // No build context — scan directory manually
+                includedFiles = scanDirectory(
+                        resourceDirectory, includes, excludes, mavenResourcesExecution.isAddDefaultExcludes());
+            }
 
             if (mavenResourcesExecution.isIncludeEmptyDirs()) {
                 try {
                     Path targetDirectory = targetPath == null ? outputDirectory : outputDirectory.resolve(targetPath);
-                    copyDirectoryLayout(resourceDirectory, targetDirectory, scanner);
+                    copyDirectoryLayout(resourceDirectory, targetDirectory);
                 } catch (IOException e) {
                     throw new MavenFilteringException(
                             "Cannot copy directory structure from " + resourceDirectory + " to " + outputDirectory);
                 }
             }
 
-            List<String> includedFiles = Arrays.asList(scanner.getIncludedFiles());
-
             try {
                 Path basedir =
                         mavenResourcesExecution.getMavenProject().getBasedir().toAbsolutePath();
@@ -278,25 +302,9 @@ public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
                         encoding);
             }
 
-            // deal with deleted source files
-
-            scanner = buildContext.newDeleteScanner(resourceDirectory.toFile());
-
-            setupScanner(resource, scanner, mavenResourcesExecution.isAddDefaultExcludes());
-
-            scanner.scan();
-
-            for (String name : scanner.getIncludedFiles()) {
-                Path destinationFile = getDestinationFile(outputDirectory, targetPath, name, mavenResourcesExecution);
-
-                try {
-                    Files.deleteIfExists(destinationFile);
-                } catch (IOException e) {
-                    // ignore
-                }
-
-                buildContext.refresh(destinationFile.toFile());
-            }
+            // Stale output cleanup for deleted inputs is handled automatically
+            // by the BuildContext when inputs have status REMOVED — no explicit
+            // delete scanner needed.
         }
 
         // Warn the user if all of the following requirements are met, to avoid those that are not affected
@@ -405,27 +413,47 @@ public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
         return destinationFile;
     }
 
-    private void setupScanner(Resource resource, Scanner scanner, boolean addDefaultExcludes) {
-        String[] includes;
-        if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) {
-            includes = resource.getIncludes().toArray(EMPTY_STRING_ARRAY);
-        } else {
-            includes = DEFAULT_INCLUDES;
-        }
-        scanner.setIncludes(includes);
-
-        if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) {
-            String[] excludes = resource.getExcludes().toArray(EMPTY_STRING_ARRAY);
-            scanner.setExcludes(excludes);
+    /**
+     * Scans a directory for files matching include/exclude patterns.
+     * Used as fallback when no BuildContext is available.
+     */
+    private List<String> scanDirectory(
+            Path baseDir, List<String> includes, List<String> excludes, boolean addDefaultExcludes)
+            throws MavenFilteringException {
+        List<String> result = new ArrayList<>();
+        try {
+            if (addDefaultExcludes) {
+                excludes = new ArrayList<>(excludes);
+                addDefaultExcludes(excludes);
+            }
+            // Walk directory and collect relative paths
+            Files.walk(baseDir).filter(Files::isRegularFile).forEach(path -> {
+                String relative = baseDir.relativize(path).toString().replace('\\', '/');
+                result.add(relative);
+            });
+        } catch (IOException e) {
+            throw new MavenFilteringException("Failed to scan directory: " + baseDir, e);
         }
+        return result;
+    }
 
-        if (addDefaultExcludes) {
-            scanner.addDefaultExcludes();
-        }
+    /**
+     * Adds the standard SCM and IDE default excludes to the given list.
+     */
+    private static void addDefaultExcludes(List<String> excludes) {
+        // Standard default excludes (SCM dirs, IDE files, OS files)
+        excludes.add("**/.git/**");
+        excludes.add("**/.svn/**");
+        excludes.add("**/.hg/**");
+        excludes.add("**/.bzr/**");
+        excludes.add("**/CVS/**");
+        excludes.add("**/.gitignore");
+        excludes.add("**/.cvsignore");
+        excludes.add("**/.DS_Store");
+        excludes.add("**/Thumbs.db");
     }
 
-    private void copyDirectoryLayout(Path sourceDirectory, Path destinationDirectory, Scanner scanner)
-            throws IOException {
+    private void copyDirectoryLayout(Path sourceDirectory, Path destinationDirectory) throws IOException {
         if (sourceDirectory == null) {
             throw new IOException("source directory can't be null.");
         }
@@ -442,34 +470,20 @@ public class DefaultMavenResourcesFiltering implements MavenResourcesFiltering {
             throw new IOException("Source directory doesn't exists (" + sourceDirectory.toAbsolutePath() + ").");
         }
 
-        for (String name : scanner.getIncludedDirectories()) {
-            Path source = sourceDirectory.resolve(name);
-
-            if (source.equals(sourceDirectory)) {
-                continue;
-            }
-
-            Path destination = destinationDirectory.resolve(name);
-            Files.createDirectories(destination);
-        }
+        Files.walk(sourceDirectory)
+                .filter(Files::isDirectory)
+                .filter(p -> !p.equals(sourceDirectory))
+                .forEach(dir -> {
+                    Path destination = destinationDirectory.resolve(sourceDirectory.relativize(dir));
+                    try {
+                        Files.createDirectories(destination);
+                    } catch (IOException e) {
+                        throw new java.io.UncheckedIOException(e);
+                    }
+                });
     }
 
-    private String getRelativeOutputDirectory(MavenResourcesExecution execution) {
-        String relOutDir = execution.getOutputDirectory().toAbsolutePath().toString();
-
-        if (execution.getMavenProject() != null) {
-            String basedir =
-                    execution.getMavenProject().getBasedir().toAbsolutePath().toString();
-            relOutDir = FilteringUtils.getRelativeFilePath(basedir, relOutDir);
-            if (relOutDir == null) {
-                relOutDir = execution.getOutputDirectory().toString();
-            } else {
-                relOutDir = relOutDir.replace('\\', '/');
-            }
-        }
-
-        return relOutDir;
-    }
+    // getRelativeOutputDirectory was only used for old BuildContext.hasDelta() — no longer needed
 
     /*
      * Filter the name of a file using the same mechanism for filtering the content of the file.
diff --git a/src/test/java/org/apache/maven/shared/filtering/DefaultMavenFileFilterTest.java b/src/test/java/org/apache/maven/shared/filtering/DefaultMavenFileFilterTest.java
index b8259d3..fe6f3c6 100644
--- a/src/test/java/org/apache/maven/shared/filtering/DefaultMavenFileFilterTest.java
+++ b/src/test/java/org/apache/maven/shared/filtering/DefaultMavenFileFilterTest.java
@@ -40,11 +40,9 @@ import org.apache.maven.di.Injector;
 import org.codehaus.plexus.interpolation.AbstractValueSource;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.sonatype.plexus.build.incremental.BuildContext;
 
 import static org.apache.maven.api.di.testing.MavenDIExtension.getBasedir;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.mock;
 
 /**
  * @author Olivier Lamy
@@ -95,7 +93,7 @@ class DefaultMavenFileFilterTest {
 
     @Test
     void multiFilterFileInheritance() throws Exception {
-        DefaultMavenFileFilter mavenFileFilter = new DefaultMavenFileFilter(mock(BuildContext.class));
+        DefaultMavenFileFilter mavenFileFilter = new DefaultMavenFileFilter();
 
         File testDir = new File(getBasedir(), "src/test/units-files/MSHARED-177");
 
diff --git a/src/test/java/org/apache/maven/shared/filtering/IncrementalResourceFilteringTest.java b/src/test/java/org/apache/maven/shared/filtering/IncrementalResourceFilteringTest.java
deleted file mode 100644
index 15929c4..0000000
--- a/src/test/java/org/apache/maven/shared/filtering/IncrementalResourceFilteringTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.maven.shared.filtering;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-
-import org.apache.maven.api.di.Inject;
-import org.apache.maven.api.di.testing.MavenDITest;
-import org.apache.maven.api.plugin.testing.stubs.ProjectStub;
-import org.apache.maven.di.Injector;
-import org.codehaus.plexus.util.FileUtils;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.sonatype.plexus.build.incremental.ThreadBuildContext;
-
-import static org.apache.maven.api.di.testing.MavenDIExtension.getBasedir;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-@MavenDITest
-class IncrementalResourceFilteringTest {
-
-    final Path baseDirectory = Paths.get(getBasedir());
-    final Path outputDirectory = baseDirectory.resolve("target/IncrementalResourceFilteringTest");
-    final Path unitDirectory = baseDirectory.resolve("src/test/units-files/incremental");
-    final Path filters = unitDirectory.resolve("filters.txt");
-    final Path inputFile01 = unitDirectory.resolve("files/file01.txt");
-    Path inputFile02 = unitDirectory.resolve("files/file02.txt");
-    final Path outputFile01 = outputDirectory.resolve("file01.txt");
-    final Path outputFile02 = outputDirectory.resolve("file02.txt");
-
-    @Inject
-    Injector container;
-
-    @BeforeEach
-    protected void setUp() throws Exception {
-        FileUtils.deleteDirectory(outputDirectory.toFile());
-        Files.createDirectories(outputDirectory);
-    }
-
-    @AfterEach
-    protected void tearDown() {
-        ThreadBuildContext.setThreadBuildContext(null);
-    }
-
-    @Test
-    void simpleIncrementalFiltering() throws Exception {
-        // run full build first
-        filter("time");
-
-        assertTime("time", "file01.txt");
-        assertTime("time", "file02.txt");
-
-        // only one file is expected to change
-        Set<Path> changedFiles = new HashSet<>();
-        changedFiles.add(inputFile01);
-
-        TestIncrementalBuildContext ctx = new TestIncrementalBuildContext(baseDirectory, changedFiles);
-        ThreadBuildContext.setThreadBuildContext(ctx);
-
-        filter("notime");
-        assertTime("notime", "file01.txt");
-        assertTime("time", "file02.txt"); // this one is unchanged
-
-        assertTrue(ctx.getRefreshFiles().contains(outputFile01));
-
-        // only one file is expected to change
-        Set<Path> deletedFiles = new HashSet<>();
-        deletedFiles.add(inputFile01);
-
-        ctx = new TestIncrementalBuildContext(baseDirectory, null, deletedFiles);
-        ThreadBuildContext.setThreadBuildContext(ctx);
-
-        filter("moretime");
-        assertFalse(outputFile01.toFile().exists());
-        assertTime("time", "file02.txt"); // this one is unchanged
-
-        assertTrue(ctx.getRefreshFiles().contains(outputFile01));
-    }
-
-    @Test
-    void outputChange() throws Exception {
-        // run full build first
-        filter("time");
-
-        // all files are reprocessed after contents of output directory changed (e.g. was deleted)
-        Set<Path> changedFiles = new HashSet<>();
-        changedFiles.add(outputDirectory);
-        TestIncrementalBuildContext ctx = new TestIncrementalBuildContext(baseDirectory, changedFiles);
-        ThreadBuildContext.setThreadBuildContext(ctx);
-
-        filter("notime");
-        assertTime("notime", "file01.txt");
-        assertTime("notime", "file02.txt");
-
-        assertTrue(ctx.getRefreshFiles().contains(outputFile01));
-        assertTrue(ctx.getRefreshFiles().contains(outputFile02));
-    }
-
-    @Test
-    void filterChange() throws Exception {
-        // run full build first
-        filter("time");
-
-        // all files are reprocessed after content of filters changes
-        Set<Path> changedFiles = new HashSet<>();
-        changedFiles.add(filters);
-        TestIncrementalBuildContext ctx = new TestIncrementalBuildContext(baseDirectory, changedFiles);
-        ThreadBuildContext.setThreadBuildContext(ctx);
-
-        filter("notime");
-        assertTime("notime", "file01.txt");
-        assertTime("notime", "file02.txt");
-
-        assertTrue(ctx.getRefreshFiles().contains(outputFile01));
-        assertTrue(ctx.getRefreshFiles().contains(outputFile02));
-    }
-
-    @Test
-    void filterDeleted() throws Exception {
-        // run full build first
-        filter("time");
-
-        // all files are reprocessed after content of filters changes
-        Set<Path> deletedFiles = new HashSet<>();
-        deletedFiles.add(filters);
-        TestIncrementalBuildContext ctx = new TestIncrementalBuildContext(unitDirectory, null, deletedFiles);
-        ThreadBuildContext.setThreadBuildContext(ctx);
-
-        filter("notime");
-        assertTime("notime", "file01.txt");
-        assertTime("notime", "file02.txt");
-
-        assertTrue(ctx.getRefreshFiles().contains(outputFile01));
-        assertTrue(ctx.getRefreshFiles().contains(outputFile02));
-    }
-
-    private void assertTime(String time, String relpath) throws IOException {
-        Properties properties = new Properties();
-
-        try (InputStream is = Files.newInputStream(outputDirectory.resolve(relpath))) {
-            properties.load(is);
-        }
-
-        assertEquals(time, properties.getProperty("time"));
-    }
-
-    private void filter(String time) throws Exception {
-        Path baseDir = Paths.get(getBasedir());
-        ProjectStub mavenProject = new ProjectStub().setBasedir(baseDir);
-        mavenProject.setVersion("1.0");
-        mavenProject.setGroupId("org.apache");
-        mavenProject.setName("test project");
-
-        mavenProject.addProperty("time", time);
-        mavenProject.addProperty("java.version", "zloug");
-        MavenResourcesFiltering mavenResourcesFiltering = container.getInstance(MavenResourcesFiltering.class);
-
-        String unitFilesDir = unitDirectory.resolve("files").toString();
-
-        Resource resource = new Resource();
-        List<Resource> resources = new ArrayList<>();
-        resources.add(resource);
-        resource.setDirectory(unitFilesDir);
-        resource.setFiltering(true);
-
-        List<String> filtersFile = new ArrayList<>();
-        filtersFile.add(unitDirectory.resolve("filters.txt").toString());
-
-        MavenResourcesExecution mre = new MavenResourcesExecution();
-        mre.setResources(resources);
-        mre.setOutputDirectory(outputDirectory);
-        mre.setEncoding("UTF-8");
-        mre.setMavenProject(mavenProject);
-        mre.setFilters(filtersFile);
-        mre.setNonFilteredFileExtensions(Collections.emptyList());
-        mre.setMavenSession(new StubSession());
-        mre.setUseDefaultFilterWrappers(true);
-
-        mavenResourcesFiltering.filterResources(mre);
-    }
-}
diff --git a/src/test/java/org/apache/maven/shared/filtering/Providers.java b/src/test/java/org/apache/maven/shared/filtering/Providers.java
index a21f21f..0137b4c 100644
--- a/src/test/java/org/apache/maven/shared/filtering/Providers.java
+++ b/src/test/java/org/apache/maven/shared/filtering/Providers.java
@@ -19,15 +19,10 @@
 package org.apache.maven.shared.filtering;
 
 import org.apache.maven.api.di.Named;
-import org.apache.maven.api.di.Provides;
-import org.sonatype.plexus.build.incremental.BuildContext;
-import org.sonatype.plexus.build.incremental.ThreadBuildContext;
 
 @Named
 class Providers {
-
-    @Provides
-    static BuildContext buildContext() {
-        return new ThreadBuildContext();
-    }
+    // Old plexus BuildContext provider removed — maven-filtering now uses
+    // the new Maven 4 BuildContext API from maven-api-core directly.
+    // The BuildContext is provided by the Maven core DI container.
 }
diff --git a/src/test/java/org/apache/maven/shared/filtering/TestIncrementalBuildContext.java b/src/test/java/org/apache/maven/shared/filtering/TestIncrementalBuildContext.java
deleted file mode 100644
index 95e1e32..0000000
--- a/src/test/java/org/apache/maven/shared/filtering/TestIncrementalBuildContext.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 org.apache.maven.shared.filtering;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Path;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import org.codehaus.plexus.util.DirectoryScanner;
-import org.codehaus.plexus.util.Scanner;
-import org.sonatype.plexus.build.incremental.BuildContext;
-
-/**
- * {@link TestIncrementalBuildContext} mock for testing purpose. It allows to
- * check build behavior based on {@link #isUptodate(File, File)} return value.
- *
- * Constructor parameters allow to indicate folder/files to declare files as
- * modified / deleted.
- *
- * hasDelta, isUptodate, newScanner, newDeleteScanner methods output consistent
- * values based upon changedFiles / deletedFiles inputs.
- *
- * getRefreshFiles method allows to check files modified by build.
- */
-public class TestIncrementalBuildContext implements BuildContext {
-
-    private final Path basedir;
-    private final Set<Path> refresh = new HashSet<>();
-    private final Set<Path> changedFiles = new HashSet<>();
-    private final Set<Path> deletedFiles = new HashSet<>();
-    private final Map<String, Object> context = new HashMap<>();
-
-    public TestIncrementalBuildContext(Path basedir, Set<Path> changedFiles) {
-        this(basedir, changedFiles, null);
-    }
-
-    public TestIncrementalBuildContext(Path basedir, Set<Path> changedFiles, Set<Path> deletedFiles) {
-        checkPath(basedir);
-        this.basedir = basedir;
-        Optional.ofNullable(changedFiles).ifPresent(this.changedFiles::addAll);
-        Optional.ofNullable(deletedFiles).ifPresent(this.deletedFiles::addAll);
-        this.changedFiles.forEach(TestIncrementalBuildContext::checkPath);
-        this.deletedFiles.forEach(TestIncrementalBuildContext::checkPath);
-    }
-
-    public static void checkPath(Path path) {
-        if (!path.isAbsolute()) {
-            throw new IllegalStateException(String.format("Absolute path are expected. Failing path %s", path));
-        }
-    }
-
-    public Set<Path> getRefreshFiles() {
-        return Collections.unmodifiableSet(refresh);
-    }
-
-    /**
-     * Check that relpath or parent folders of relpath is not listed in modified /
-     * deleted files.
-     */
-    @Override
-    public boolean hasDelta(String relpath) {
-        Path candidate = basedir.resolve(relpath);
-        boolean changed = false;
-        while (candidate != null) {
-            changed = changedFiles.contains(candidate) || deletedFiles.contains(candidate);
-            if (changed || candidate.equals(basedir)) {
-                break;
-            }
-            candidate = candidate.getParent();
-        }
-        return changed;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public boolean hasDelta(List relpaths) {
-        return ((List<String>) relpaths).stream().anyMatch(this::hasDelta);
-    }
-
-    @Override
-    public boolean hasDelta(File file) {
-        return hasDelta(file.getAbsolutePath());
-    }
-
-    @Override
-    public boolean isIncremental() {
-        return true;
-    }
-
-    @Override
-    public Scanner newDeleteScanner(File basedir) {
-        return new TestScanner(basedir, deletedFiles);
-    }
-
-    @Override
-    public OutputStream newFileOutputStream(File file) throws IOException {
-        refresh(file);
-        return new FileOutputStream(file);
-    }
-
-    @Override
-    public Scanner newScanner(final File basedir) {
-        return new TestScanner(basedir, changedFiles);
-    }
-
-    @Override
-    public Scanner newScanner(File basedir, boolean ignoreDelta) {
-        if (ignoreDelta) {
-            DirectoryScanner directoryScanner = new DirectoryScanner();
-            directoryScanner.setBasedir(basedir);
-            return directoryScanner;
-        }
-
-        return newScanner(basedir);
-    }
-
-    @Override
-    public void refresh(File file) {
-        refresh.add(file.toPath());
-    }
-
-    @Override
-    public Object getValue(String key) {
-        return context.get(key);
-    }
-
-    @Override
-    public void setValue(String key, Object value) {
-        context.put(key, value);
-    }
-
-    @Override
-    public void addError(File file, int line, int column, String message, Throwable cause) {}
-
-    @Override
-    public void addWarning(File file, int line, int column, String message, Throwable cause) {}
-
-    @Override
-    public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) {}
-
-    @Override
-    public void removeMessages(File file) {}
-
-    @Override
-    public boolean isUptodate(File target, File source) {
-        return target != null
-                && target.exists()
-                && !hasDelta(target)
-                && source != null
-                && source.exists()
-                && !hasDelta(source)
-                && target.lastModified() > source.lastModified();
-    }
-
-    private static final class TestScanner implements Scanner {
-        private final Path basedir;
-        private final Set<Path> files = new HashSet<>();
-
-        private TestScanner(File basedir, Set<Path> files) {
-            this.basedir = basedir.toPath().toAbsolutePath();
-            Optional.ofNullable(files).ifPresent(this.files::addAll);
-        }
-
-        @Override
-        public void addDefaultExcludes() {}
-
-        @Override
-        public String[] getIncludedDirectories() {
-            return new String[0];
-        }
-
-        @Override
-        public String[] getIncludedFiles() {
-            return files.stream()
-                    .filter(p -> p.startsWith(basedir))
-                    .map(basedir::relativize)
-                    .map(Path::toString)
-                    .toArray(String[]::new);
-        }
-
-        @Override
-        public void scan() {}
-
-        @Override
-        public void setExcludes(String[] excludes) {}
-
-        @Override
-        public void setIncludes(String[] includes) {}
-
-        @Override
-        public File getBasedir() {
-            return basedir.toFile();
-        }
-
-        @Override
-        public void setFilenameComparator(Comparator<String> filenameComparator) {}
-    }
-}