diff --git a/src/it/TEST-38/invoker.properties b/src/it/TEST-38/invoker.properties
new file mode 100644
index 0000000..c95bc34
--- /dev/null
+++ b/src/it/TEST-38/invoker.properties
@@ -0,0 +1,23 @@
+#
+# Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
+#
+# 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.
+#
+
+# An optional description for this build job to be included in the build reports.
+invoker.description = \
+ Verifies that the deterministic proto file sorting correctly orders the proto file paths \
+ when calling protoc instead of using whichever order is determined by the file system when \
+ listing the files.
+
+invoker.goals = -X clean compile
diff --git a/src/it/TEST-38/pom.xml b/src/it/TEST-38/pom.xml
new file mode 100644
index 0000000..2b105f6
--- /dev/null
+++ b/src/it/TEST-38/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+ 4.0.0
+
+
+ dev.cookiecode.its
+ it-parent
+ 1.0.0
+
+
+ test-38
+ 1.0.0
+
+ Integration Test 38
+
+
+
+
+ dev.cookiecode
+ another-protobuf-maven-plugin
+ @project.version@
+
+
+
+ compile
+ test-compile
+
+
+
+
+ true
+
+ com.google.protobuf:protoc:${protobufVersion}:exe:${os.detected.classifier}
+
+
+
+
+
+
diff --git a/src/it/TEST-38/src/main/proto/it/project1/subproject1/test1_1.proto b/src/it/TEST-38/src/main/proto/it/project1/subproject1/test1_1.proto
new file mode 100644
index 0000000..1c266e7
--- /dev/null
+++ b/src/it/TEST-38/src/main/proto/it/project1/subproject1/test1_1.proto
@@ -0,0 +1,26 @@
+//
+// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
+//
+// 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.
+//
+
+syntax = "proto3";
+
+package it.project1.subproject1;
+
+option java_package = "it.project1.subproject1.messages";
+option java_outer_classname = "TestProtos11";
+option optimize_for = SPEED;
+
+message TestMessage1 {
+}
diff --git a/src/it/TEST-38/src/main/proto/it/project1/subproject2/test1_2.proto b/src/it/TEST-38/src/main/proto/it/project1/subproject2/test1_2.proto
new file mode 100644
index 0000000..b43f656
--- /dev/null
+++ b/src/it/TEST-38/src/main/proto/it/project1/subproject2/test1_2.proto
@@ -0,0 +1,26 @@
+//
+// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
+//
+// 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.
+//
+
+syntax = "proto3";
+
+package it.project1.subproject2;
+
+option java_package = "it.project1.subproject2.messages";
+option java_outer_classname = "TestProtos12";
+option optimize_for = SPEED;
+
+message TestMessage1 {
+}
diff --git a/src/it/TEST-38/src/main/proto/it/project1/test1.proto b/src/it/TEST-38/src/main/proto/it/project1/test1.proto
new file mode 100644
index 0000000..111ef28
--- /dev/null
+++ b/src/it/TEST-38/src/main/proto/it/project1/test1.proto
@@ -0,0 +1,26 @@
+//
+// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
+//
+// 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.
+//
+
+syntax = "proto3";
+
+package it.project1;
+
+option java_package = "it.project1.messages";
+option java_outer_classname = "TestProtos1";
+option optimize_for = SPEED;
+
+message TestMessage1 {
+}
diff --git a/src/it/TEST-38/src/main/proto/it/project2/test2.proto b/src/it/TEST-38/src/main/proto/it/project2/test2.proto
new file mode 100644
index 0000000..40f5a8d
--- /dev/null
+++ b/src/it/TEST-38/src/main/proto/it/project2/test2.proto
@@ -0,0 +1,26 @@
+//
+// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
+//
+// 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.
+//
+
+syntax = "proto3";
+
+package it.project2;
+
+option java_package = "it.project2.messages";
+option java_outer_classname = "TestProtos2";
+option optimize_for = SPEED;
+
+message TestMessage1 {
+}
diff --git a/src/it/TEST-38/verify.groovy b/src/it/TEST-38/verify.groovy
new file mode 100644
index 0000000..170d6c3
--- /dev/null
+++ b/src/it/TEST-38/verify.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
+ *
+ * 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.
+ */
+
+outputDirectory = new File(basedir, 'target/generated-sources/protobuf/java');
+assert outputDirectory.exists();
+assert outputDirectory.isDirectory();
+
+generatedJavaFile1 = new File(outputDirectory, 'it/project1/messages/TestProtos1.java');
+assert generatedJavaFile1.exists();
+assert generatedJavaFile1.isFile();
+
+generatedJavaFile2 = new File(outputDirectory, 'it/project1/subproject1/messages/TestProtos11.java');
+assert generatedJavaFile2.exists();
+assert generatedJavaFile2.isFile();
+
+generatedJavaFile3 = new File(outputDirectory, 'it/project1/subproject2/messages/TestProtos12.java');
+assert generatedJavaFile3.exists();
+assert generatedJavaFile3.isFile();
+
+generatedJavaFile4 = new File(outputDirectory, 'it/project2/messages/TestProtos2.java');
+assert generatedJavaFile4.exists();
+assert generatedJavaFile4.isFile();
+
+buildLogFile = new File(basedir, 'build.log');
+assert buildLogFile.exists();
+assert buildLogFile.isFile();
+
+/*
+ * Now test the order in which the .proto files were given to the compiler.
+ * The listed order below is the expected order with sortProtoFiles enabled.
+ * First, process all of "project1", starting with the highest-level files,
+ * then going into subdirectories in alphabetic order.
+ * Finally process project2.
+ *
+ * Without sortProtoFiles, the order is non-deterministic (depends on file
+ * system) and it was tested to be different (at least on macOS, probably
+ * also on Linux).
+ */
+content = buildLogFile.text;
+pos1 = content.indexOf("src/main/proto/it/project1/test1.proto");
+pos2 = content.indexOf("src/main/proto/it/project1/subproject1/test1_1.proto");
+pos3 = content.indexOf("src/main/proto/it/project1/subproject2/test1_2.proto");
+pos4 = content.indexOf("src/main/proto/it/project2/test2.proto");
+assert pos1 < pos2;
+assert pos2 < pos3;
+assert pos3 < pos4;
+
+return true;
diff --git a/src/main/java/dev/cookiecode/maven/plugin/protobuf/AbstractProtocMojo.java b/src/main/java/dev/cookiecode/maven/plugin/protobuf/AbstractProtocMojo.java
index 0fab0ec..3fe3d95 100644
--- a/src/main/java/dev/cookiecode/maven/plugin/protobuf/AbstractProtocMojo.java
+++ b/src/main/java/dev/cookiecode/maven/plugin/protobuf/AbstractProtocMojo.java
@@ -48,14 +48,13 @@
import java.io.File;
import java.io.IOException;
+import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+import java.util.stream.Collectors;
import static java.lang.Math.max;
import static java.lang.String.format;
@@ -392,6 +391,22 @@ abstract class AbstractProtocMojo extends AbstractMojo {
)
private boolean clearOutputDirectory;
+ /**
+ * When {@code true}, the .proto files to be compiled will be sorted before providing their
+ * paths to the protoc compiler. Otherwise, the order depends on the order in which the
+ * files are returned from the file system, which is nondeterministic and differs between
+ * file systems (and thus between Windows and macOS/Linux, for example).
+ *
+ * This can for example be relevant when using documentation generation plugins for protoc.
+ *
+ * @since 0.7.2
+ */
+ @Parameter(
+ required = false,
+ defaultValue = "false"
+ )
+ private boolean sortProtoFiles;
+
/**
* Executes the mojo.
*/
@@ -886,7 +901,11 @@ protected List findProtoFilesInDirectory(final File directory) {
} catch (IOException e) {
throw new MojoInitializationException("Unable to retrieve the list of files: " + e.getMessage(), e);
}
- return protoFilesInDirectory;
+ if(sortProtoFiles) {
+ return sortProtoFiles(protoFilesInDirectory);
+ } else {
+ return protoFilesInDirectory;
+ }
}
protected List findProtoFilesInDirectories(final Iterable directories) {
@@ -900,6 +919,41 @@ protected List findProtoFilesInDirectories(final Iterable directorie
return protoFiles;
}
+ /**
+ * Sorts the given list of .proto files lexicographically.
+ *
+ * This results in an order like
+ *
+ * - /top1/a.proto
+ * - /top1/sub1/a.proto
+ * - /top1/sub1/b.proto
+ * - /top1/sub1/bot1/d.proto
+ * - /top1/sub1/bot2/c.proto
+ * - /top1/sub2/x.proto
+ * - /top2/z.proto
+ * - /top2/sub1/y.proto
+ *
+ *
+ * @param unsortedFiles the unsorted list of files
+ * @return the sorted list
+ */
+ protected List sortProtoFiles(final List unsortedFiles) {
+ return unsortedFiles.stream().sorted((aFirst, aSecond) -> {
+ final Path first = aFirst.getAbsoluteFile().toPath();
+ final Path firstParent = first.getParent();
+ final Path second = aSecond.getAbsoluteFile().toPath();
+ final Path secondParent = second.getParent();
+
+ // In case of equal parent directories, compare the file names
+ if(firstParent.equals(secondParent)) {
+ return first.getFileName().compareTo(second.getFileName());
+ } else {
+ // For different parent directories, sort parents lexicographically
+ return firstParent.compareTo(secondParent);
+ }
+ }).collect(Collectors.toList());
+ }
+
/**
* Truncates the path of jar files so that they are relative to the local repository.
*