From 90b49671097844a465645848f567f22f736e7ab3 Mon Sep 17 00:00:00 2001 From: Sylwester Lachiewicz Date: Sun, 23 Aug 2026 13:06:14 +0200 Subject: [PATCH] Add followSymlinks to follow links when jarring sources A symlink under a source root was archived as a link, so the sources jar carried an entry pointing outside itself and the linked file was absent (plexus-archiver#160). The new parameter defaults to false, keeping that behaviour for anyone relying on it. Needs plexus-archiver 4.14.0 for FileSet.isFollowingSymLinks(). --- pom.xml | 2 +- src/it/jar-follow-symlinks/invoker.properties | 19 ++++++ src/it/jar-follow-symlinks/pom.xml | 59 +++++++++++++++++++ src/it/jar-follow-symlinks/setup.groovy | 29 +++++++++ .../shared-sources/shared/Shared.java | 22 +++++++ .../src/main/java/MyClass.java | 20 +++++++ src/it/jar-follow-symlinks/verify.groovy | 34 +++++++++++ .../plugins/source/AbstractSourceJarMojo.java | 20 ++++++- 8 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 src/it/jar-follow-symlinks/invoker.properties create mode 100644 src/it/jar-follow-symlinks/pom.xml create mode 100644 src/it/jar-follow-symlinks/setup.groovy create mode 100644 src/it/jar-follow-symlinks/shared-sources/shared/Shared.java create mode 100644 src/it/jar-follow-symlinks/src/main/java/MyClass.java create mode 100644 src/it/jar-follow-symlinks/verify.groovy diff --git a/pom.xml b/pom.xml index f5478c1..51e853f 100644 --- a/pom.xml +++ b/pom.xml @@ -139,7 +139,7 @@ under the License. org.codehaus.plexus plexus-archiver - 4.13.0 + 4.14.0 org.codehaus.plexus diff --git a/src/it/jar-follow-symlinks/invoker.properties b/src/it/jar-follow-symlinks/invoker.properties new file mode 100644 index 0000000..a785aed --- /dev/null +++ b/src/it/jar-follow-symlinks/invoker.properties @@ -0,0 +1,19 @@ +# 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. + +# Creating a symbolic link needs privileges that are not granted by default on Windows. +invoker.os.family = !windows diff --git a/src/it/jar-follow-symlinks/pom.xml b/src/it/jar-follow-symlinks/pom.xml new file mode 100644 index 0000000..faa6208 --- /dev/null +++ b/src/it/jar-follow-symlinks/pom.xml @@ -0,0 +1,59 @@ + + + + + 4.0.0 + + org.apache.maven.its.source + jar-follow-symlinks + 1.0-SNAPSHOT + + Test for followSymlinks + + + UTF-8 + 8 + 8 + + + + + + org.apache.maven.plugins + maven-source-plugin + @project.version@ + + true + + + + attach-sources + + jar + + + + + + + + diff --git a/src/it/jar-follow-symlinks/setup.groovy b/src/it/jar-follow-symlinks/setup.groovy new file mode 100644 index 0000000..58ab158 --- /dev/null +++ b/src/it/jar-follow-symlinks/setup.groovy @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import java.nio.file.Files + +// A source directory that is a symbolic link to a tree kept outside src/main/java. +File link = new File(basedir, 'src/main/java/shared') +if (Files.isSymbolicLink(link.toPath())) { + Files.delete(link.toPath()) +} +Files.createSymbolicLink(link.toPath(), new File(basedir, 'shared-sources/shared').toPath()) + +return true diff --git a/src/it/jar-follow-symlinks/shared-sources/shared/Shared.java b/src/it/jar-follow-symlinks/shared-sources/shared/Shared.java new file mode 100644 index 0000000..8eb707b --- /dev/null +++ b/src/it/jar-follow-symlinks/shared-sources/shared/Shared.java @@ -0,0 +1,22 @@ +/* + * 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 shared; + +public class Shared {} \ No newline at end of file diff --git a/src/it/jar-follow-symlinks/src/main/java/MyClass.java b/src/it/jar-follow-symlinks/src/main/java/MyClass.java new file mode 100644 index 0000000..43a7520 --- /dev/null +++ b/src/it/jar-follow-symlinks/src/main/java/MyClass.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +public class MyClass {} \ No newline at end of file diff --git a/src/it/jar-follow-symlinks/verify.groovy b/src/it/jar-follow-symlinks/verify.groovy new file mode 100644 index 0000000..e1f3ca8 --- /dev/null +++ b/src/it/jar-follow-symlinks/verify.groovy @@ -0,0 +1,34 @@ +/* + * 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. + */ + +import java.util.zip.ZipFile + +File jar = new File(basedir, 'target/jar-follow-symlinks-1.0-SNAPSHOT-sources.jar') +assert jar.isFile() : "Missing sources jar $jar" + +new ZipFile(jar).withCloseable { zip -> + def entry = zip.getEntry('shared/Shared.java') + assert entry != null : "shared/Shared.java is missing; entries were ${zip.entries().collect { it.name }}" + + // The link was resolved, so the entry carries the target's source rather than a link path. + def text = zip.getInputStream(entry).text + assert text.contains('class Shared') : "shared/Shared.java holds $text" +} + +return true diff --git a/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java b/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java index 284d2f1..74696ea 100644 --- a/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java @@ -83,6 +83,16 @@ public abstract class AbstractSourceJarMojo extends AbstractMojo { @Parameter(property = "maven.source.useDefaultExcludes", defaultValue = "true") private boolean useDefaultExcludes; + /** + * Whether to follow symbolic links below the source directories. When false, a symbolic link is archived as a + * link, which points nowhere once the jar is unpacked somewhere else; when true, a link is archived as the file + * or directory it points at, and a linked directory's contents are included. + * + * @since 3.5.0 + */ + @Parameter(property = "maven.source.followSymlinks", defaultValue = "false") + private boolean followSymlinks; + /** * The Maven Project Object */ @@ -444,7 +454,9 @@ protected void addDirectory(Archiver archiver, File sourceDirectory, String[] pI throws MojoExecutionException { try { getLog().debug("add directory " + sourceDirectory + " to archiver"); - archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory).includeExclude(pIncludes, pExcludes)); + archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory) + .includeExclude(pIncludes, pExcludes) + .followingSymLinks(followSymlinks)); } catch (ArchiverException e) { throw new MojoExecutionException("Error adding directory to source archive.", e); } @@ -463,8 +475,10 @@ protected void addDirectory( throws MojoExecutionException { try { getLog().debug("add directory " + sourceDirectory + " to archiver with prefix " + prefix); - archiver.addFileSet( - DefaultFileSet.fileSet(sourceDirectory).prefixed(prefix).includeExclude(pIncludes, pExcludes)); + archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory) + .prefixed(prefix) + .includeExclude(pIncludes, pExcludes) + .followingSymLinks(followSymlinks)); } catch (ArchiverException e) { throw new MojoExecutionException("Error adding directory to source archive.", e); }