From 22e58587a1faab4f40346bd510bf039a25890a03 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 | 17 +++++- 8 files changed, 199 insertions(+), 3 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 228e321..9be6c8a 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ under the License. 4.0.0-beta-2 4.0.0-beta-4 5.23.0 - 4.13.0 + 4.14.0 ${mavenPluginPluginVersion} 2024-06-26T08:31:50Z 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 4a853a3..79916d4 100644 --- a/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/source/AbstractSourceJarMojo.java @@ -87,6 +87,16 @@ public abstract class AbstractSourceJarMojo implements Mojo { @Parameter(property = "maven.source.useDefaultExcludes", defaultValue = "true") protected 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 */ @@ -450,7 +460,9 @@ protected void addDirectory(Archiver archiver, Path sourceDirectory, String[] pI throws MojoException { try { getLog().debug("add directory " + sourceDirectory + " to archiver"); - archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory.toFile()).includeExclude(pIncludes, pExcludes)); + archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory.toFile()) + .includeExclude(pIncludes, pExcludes) + .followingSymLinks(followSymlinks)); } catch (ArchiverException e) { throw new MojoException("Error adding directory to source archive.", e); } @@ -471,7 +483,8 @@ protected void addDirectory( getLog().debug("add directory " + sourceDirectory + " to archiver with prefix " + prefix); archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory.toFile()) .prefixed(prefix) - .includeExclude(pIncludes, pExcludes)); + .includeExclude(pIncludes, pExcludes) + .followingSymLinks(followSymlinks)); } catch (ArchiverException e) { throw new MojoException("Error adding directory to source archive.", e); }