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 + *

    + *
  1. /top1/a.proto
  2. + *
  3. /top1/sub1/a.proto
  4. + *
  5. /top1/sub1/b.proto
  6. + *
  7. /top1/sub1/bot1/d.proto
  8. + *
  9. /top1/sub1/bot2/c.proto
  10. + *
  11. /top1/sub2/x.proto
  12. + *
  13. /top2/z.proto
  14. + *
  15. /top2/sub1/y.proto
  16. + *
+ * + * @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. *