Skip to content

Commit 37d75d4

Browse files
authored
New feature: sorting of .proto files (#20)
The order in which .proto files are given to the compiler is file system dependent, and thus by extension also operating system dependent, which doesn't matter much for code generation, but can be critical for documentation generation (for which protoc plugins exist!). This commit adds an optional parameter "sortProtoFiles" which can be set to true (defaults to false) to have the .proto files be sorted lexicographically according to their path and file names.
1 parent fc29025 commit 37d75d4

File tree

8 files changed

+306
-5
lines changed

8 files changed

+306
-5
lines changed

src/it/TEST-38/invoker.properties

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#
2+
# Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
# An optional description for this build job to be included in the build reports.
18+
invoker.description = \
19+
Verifies that the deterministic proto file sorting correctly orders the proto file paths \
20+
when calling protoc instead of using whichever order is determined by the file system when \
21+
listing the files.
22+
23+
invoker.goals = -X clean compile

src/it/TEST-38/pom.xml

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
~ Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
5+
~
6+
~ Licensed under the Apache License, Version 2.0 (the "License");
7+
~ you may not use this file except in compliance with the License.
8+
~ You may obtain a copy of the License at
9+
~
10+
~ http://www.apache.org/licenses/LICENSE-2.0
11+
~
12+
~ Unless required by applicable law or agreed to in writing, software
13+
~ distributed under the License is distributed on an "AS IS" BASIS,
14+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
~ See the License for the specific language governing permissions and
16+
~ limitations under the License.
17+
-->
18+
19+
<project xmlns="http://maven.apache.org/POM/4.0.0"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
23+
<modelVersion>4.0.0</modelVersion>
24+
25+
<parent>
26+
<groupId>dev.cookiecode.its</groupId>
27+
<artifactId>it-parent</artifactId>
28+
<version>1.0.0</version>
29+
</parent>
30+
31+
<artifactId>test-38</artifactId>
32+
<version>1.0.0</version>
33+
34+
<name>Integration Test 38</name>
35+
36+
<build>
37+
<plugins>
38+
<plugin>
39+
<groupId>dev.cookiecode</groupId>
40+
<artifactId>another-protobuf-maven-plugin</artifactId>
41+
<version>@project.version@</version>
42+
<executions>
43+
<execution>
44+
<goals>
45+
<goal>compile</goal>
46+
<goal>test-compile</goal>
47+
</goals>
48+
</execution>
49+
</executions>
50+
<configuration>
51+
<sortProtoFiles>true</sortProtoFiles>
52+
<protocArtifact>
53+
com.google.protobuf:protoc:${protobufVersion}:exe:${os.detected.classifier}
54+
</protocArtifact>
55+
</configuration>
56+
</plugin>
57+
</plugins>
58+
</build>
59+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
19+
package it.project1.subproject1;
20+
21+
option java_package = "it.project1.subproject1.messages";
22+
option java_outer_classname = "TestProtos11";
23+
option optimize_for = SPEED;
24+
25+
message TestMessage1 {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
19+
package it.project1.subproject2;
20+
21+
option java_package = "it.project1.subproject2.messages";
22+
option java_outer_classname = "TestProtos12";
23+
option optimize_for = SPEED;
24+
25+
message TestMessage1 {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
19+
package it.project1;
20+
21+
option java_package = "it.project1.messages";
22+
option java_outer_classname = "TestProtos1";
23+
option optimize_for = SPEED;
24+
25+
message TestMessage1 {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
19+
package it.project2;
20+
21+
option java_package = "it.project2.messages";
22+
option java_outer_classname = "TestProtos2";
23+
option optimize_for = SPEED;
24+
25+
message TestMessage1 {
26+
}

src/it/TEST-38/verify.groovy

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) 2016 Maven Protocol Buffers Plugin Authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
outputDirectory = new File(basedir, 'target/generated-sources/protobuf/java');
18+
assert outputDirectory.exists();
19+
assert outputDirectory.isDirectory();
20+
21+
generatedJavaFile1 = new File(outputDirectory, 'it/project1/messages/TestProtos1.java');
22+
assert generatedJavaFile1.exists();
23+
assert generatedJavaFile1.isFile();
24+
25+
generatedJavaFile2 = new File(outputDirectory, 'it/project1/subproject1/messages/TestProtos11.java');
26+
assert generatedJavaFile2.exists();
27+
assert generatedJavaFile2.isFile();
28+
29+
generatedJavaFile3 = new File(outputDirectory, 'it/project1/subproject2/messages/TestProtos12.java');
30+
assert generatedJavaFile3.exists();
31+
assert generatedJavaFile3.isFile();
32+
33+
generatedJavaFile4 = new File(outputDirectory, 'it/project2/messages/TestProtos2.java');
34+
assert generatedJavaFile4.exists();
35+
assert generatedJavaFile4.isFile();
36+
37+
buildLogFile = new File(basedir, 'build.log');
38+
assert buildLogFile.exists();
39+
assert buildLogFile.isFile();
40+
41+
/*
42+
* Now test the order in which the .proto files were given to the compiler.
43+
* The listed order below is the expected order with sortProtoFiles enabled.
44+
* First, process all of "project1", starting with the highest-level files,
45+
* then going into subdirectories in alphabetic order.
46+
* Finally process project2.
47+
*
48+
* Without sortProtoFiles, the order is non-deterministic (depends on file
49+
* system) and it was tested to be different (at least on macOS, probably
50+
* also on Linux).
51+
*/
52+
content = buildLogFile.text;
53+
pos1 = content.indexOf("src/main/proto/it/project1/test1.proto");
54+
pos2 = content.indexOf("src/main/proto/it/project1/subproject1/test1_1.proto");
55+
pos3 = content.indexOf("src/main/proto/it/project1/subproject2/test1_2.proto");
56+
pos4 = content.indexOf("src/main/proto/it/project2/test2.proto");
57+
assert pos1 < pos2;
58+
assert pos2 < pos3;
59+
assert pos3 < pos4;
60+
61+
return true;

src/main/java/dev/cookiecode/maven/plugin/protobuf/AbstractProtocMojo.java

+59-5
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,13 @@
4848

4949
import java.io.File;
5050
import java.io.IOException;
51+
import java.nio.file.Path;
5152
import java.security.MessageDigest;
5253
import java.security.NoSuchAlgorithmException;
53-
import java.util.ArrayList;
54-
import java.util.Enumeration;
55-
import java.util.List;
56-
import java.util.Set;
54+
import java.util.*;
5755
import java.util.jar.JarEntry;
5856
import java.util.jar.JarFile;
57+
import java.util.stream.Collectors;
5958

6059
import static java.lang.Math.max;
6160
import static java.lang.String.format;
@@ -392,6 +391,22 @@ abstract class AbstractProtocMojo extends AbstractMojo {
392391
)
393392
private boolean clearOutputDirectory;
394393

394+
/**
395+
* When {@code true}, the .proto files to be compiled will be sorted before providing their
396+
* paths to the protoc compiler. Otherwise, the order depends on the order in which the
397+
* files are returned from the file system, which is nondeterministic and differs between
398+
* file systems (and thus between Windows and macOS/Linux, for example).
399+
* <p>
400+
* This can for example be relevant when using documentation generation plugins for protoc.
401+
*
402+
* @since 0.7.2
403+
*/
404+
@Parameter(
405+
required = false,
406+
defaultValue = "false"
407+
)
408+
private boolean sortProtoFiles;
409+
395410
/**
396411
* Executes the mojo.
397412
*/
@@ -886,7 +901,11 @@ protected List<File> findProtoFilesInDirectory(final File directory) {
886901
} catch (IOException e) {
887902
throw new MojoInitializationException("Unable to retrieve the list of files: " + e.getMessage(), e);
888903
}
889-
return protoFilesInDirectory;
904+
if(sortProtoFiles) {
905+
return sortProtoFiles(protoFilesInDirectory);
906+
} else {
907+
return protoFilesInDirectory;
908+
}
890909
}
891910

892911
protected List<File> findProtoFilesInDirectories(final Iterable<File> directories) {
@@ -900,6 +919,41 @@ protected List<File> findProtoFilesInDirectories(final Iterable<File> directorie
900919
return protoFiles;
901920
}
902921

922+
/**
923+
* Sorts the given list of .proto files lexicographically.
924+
* <p>
925+
* This results in an order like
926+
* <ol>
927+
* <li>/top1/a.proto</li>
928+
* <li>/top1/sub1/a.proto</li>
929+
* <li>/top1/sub1/b.proto</li>
930+
* <li>/top1/sub1/bot1/d.proto</li>
931+
* <li>/top1/sub1/bot2/c.proto</li>
932+
* <li>/top1/sub2/x.proto</li>
933+
* <li>/top2/z.proto</li>
934+
* <li>/top2/sub1/y.proto</li>
935+
* </ol>
936+
*
937+
* @param unsortedFiles the unsorted list of files
938+
* @return the sorted list
939+
*/
940+
protected List<File> sortProtoFiles(final List<File> unsortedFiles) {
941+
return unsortedFiles.stream().sorted((aFirst, aSecond) -> {
942+
final Path first = aFirst.getAbsoluteFile().toPath();
943+
final Path firstParent = first.getParent();
944+
final Path second = aSecond.getAbsoluteFile().toPath();
945+
final Path secondParent = second.getParent();
946+
947+
// In case of equal parent directories, compare the file names
948+
if(firstParent.equals(secondParent)) {
949+
return first.getFileName().compareTo(second.getFileName());
950+
} else {
951+
// For different parent directories, sort parents lexicographically
952+
return firstParent.compareTo(secondParent);
953+
}
954+
}).collect(Collectors.toList());
955+
}
956+
903957
/**
904958
* Truncates the path of jar files so that they are relative to the local repository.
905959
*

0 commit comments

Comments
 (0)