Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proto generation spreading #174

Merged
merged 7 commits into from
Nov 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 64 additions & 10 deletions src/main/groovy/com/google/protobuf/gradle/GenerateProtoTask.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ package com.google.protobuf.gradle

import com.google.common.base.Preconditions
import com.google.common.collect.ImmutableList
import com.google.common.primitives.Ints

import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
Expand All @@ -46,6 +47,8 @@ import org.gradle.util.ConfigureUtil
*/
// TODO(zhangkun83): add per-plugin output dir reconfiguraiton.
public class GenerateProtoTask extends DefaultTask {
static final int VISTA_CMD_LENGTH_LIMIT = 8191
static final int XP_CMD_LENGTH_LIMIT = 2047

private final List includeDirs = []
private final NamedDomainObjectContainer<PluginOptions> builtins
Expand Down Expand Up @@ -372,13 +375,13 @@ public class GenerateProtoTask extends DefaultTask {
List<String> dirs = includeDirs*.path.collect { "-I${it}" }
logger.debug "ProtobufCompile using directories ${dirs}"
logger.debug "ProtobufCompile using files ${protoFiles}"
List<String> cmd = [ tools.protoc.path ]
cmd.addAll(dirs)
List<String> baseCmd = [ tools.protoc.path ]
baseCmd.addAll(dirs)

// Handle code generation built-ins
builtins.each { builtin ->
String outPrefix = makeOptionsPrefix(builtin.options)
cmd += "--${builtin.name}_out=${outPrefix}${getOutputDir(builtin)}"
baseCmd += "--${builtin.name}_out=${outPrefix}${getOutputDir(builtin)}"
}

// Handle code generation plugins
Expand All @@ -389,8 +392,8 @@ public class GenerateProtoTask extends DefaultTask {
throw new GradleException("Codegen plugin ${name} not defined")
}
String pluginOutPrefix = makeOptionsPrefix(plugin.options)
cmd += "--${name}_out=${pluginOutPrefix}${getOutputDir(plugin)}"
cmd += "--plugin=protoc-gen-${name}=${locator.path}"
baseCmd += "--${name}_out=${pluginOutPrefix}${getOutputDir(plugin)}"
baseCmd += "--plugin=protoc-gen-${name}=${locator.path}"
}

if (generateDescriptorSet) {
Expand All @@ -401,17 +404,24 @@ public class GenerateProtoTask extends DefaultTask {
if (!folder.exists()) {
folder.mkdirs()
}
cmd += "--descriptor_set_out=${path}"
baseCmd += "--descriptor_set_out=${path}"
if (descriptorSetOptions.includeImports) {
cmd += "--include_imports"
baseCmd += "--include_imports"
}
if (descriptorSetOptions.includeSourceInfo) {
cmd += "--include_source_info"
baseCmd += "--include_source_info"
}
}

cmd.addAll protoFiles
logger.log(LogLevel.INFO, cmd.toString())
List<String> cmds = generateCmds(baseCmd.join(" "), protoFiles, getCmdLengthLimit())
for (String cmd : cmds) {
compileFiles(cmd)
}
}

private void compileFiles(String cmd) {
logger.log(LogLevel.INFO, cmd)

StringBuffer stdout = new StringBuffer()
StringBuffer stderr = new StringBuffer()
Process result = cmd.execute()
Expand All @@ -424,4 +434,48 @@ public class GenerateProtoTask extends DefaultTask {
}
}

static List<String> generateCmds(String baseCmd, List<File> protoFiles, int cmdLengthLimit) {
List<String> cmds = []
if (!protoFiles.isEmpty()) {
StringBuilder currentCmd = new StringBuilder(baseCmd)
for (File proto: protoFiles) {
String protoFileName = proto
// Check if appending the next proto string will overflow the cmd length limit
if (currentCmd.length() + protoFileName.length() + 1 > cmdLengthLimit) {
// Add the current cmd before overflow
cmds.add(currentCmd.toString())
currentCmd.setLength(baseCmd.length())
}
// Append the proto file to the command
currentCmd.append(" ").append(protoFileName)
}
// Add the last cmd for execution
cmds.add(currentCmd.toString())
}
return cmds
}

static int getCmdLengthLimit() {
return getCmdLengthLimit(System.getProperty("os.name"), System.getProperty("os.version"))
}

static int getCmdLengthLimit(String os, String version) {
// Check if operating system is Windows
if (os != null && os.toLowerCase(Locale.ROOT).indexOf("win") > -1) {
if (version != null) {
int idx = version.indexOf('.')
if (idx > 0) {
// If the major version is > 5, we are on Windows Vista or higher
int majorVersion = Ints.tryParse(version[0..(idx - 1)]) ?: 0
if (majorVersion > 5) {
return VISTA_CMD_LENGTH_LIMIT
}
}
}
// Failed to read version, assume Windows XP or lower
return XP_CMD_LENGTH_LIMIT
}
return Integer.MAX_VALUE
}

}
87 changes: 87 additions & 0 deletions src/test/groovy/com/google/plugins/ProtobufJavaPluginTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,91 @@ class ProtobufJavaPluginTest extends Specification {
where:
gradleVersion << GRADLE_VERSIONS
}

void "test generateCmds should split commands when limit exceeded"() {
given: "a cmd length limit and two proto files"

String baseCmd = "protoc"
List<File> protoFiles = [ new File("short.proto"), new File("long_proto_name.proto") ]
int cmdLengthLimit = 32

when: "the commands are generated"

List<String> cmds = GenerateProtoTask.generateCmds(baseCmd, protoFiles, cmdLengthLimit)

then: "it splits appropriately"
cmds.size() == 2 && cmds[0] == "protoc short.proto" && cmds[1] == "protoc long_proto_name.proto"
}

void "test generateCmds should not split commands when under limit"() {
given: "a cmd length limit and two proto files"

String baseCmd = "protoc"
List<File> protoFiles = [ new File("short.proto"), new File("long_proto_name.proto") ]
int cmdLengthLimit = 64

when: "the commands are generated"

List<String> cmds = GenerateProtoTask.generateCmds(baseCmd, protoFiles, cmdLengthLimit)

then: "it splits appropriately"
cmds.size() == 1 && cmds[0] == "protoc short.proto long_proto_name.proto"
}

void "test generateCmds should not return commands when no protos are given"() {
given: "a cmd length limit and no proto files"

String baseCmd = "protoc"
List<File> protoFiles = []
int cmdLengthLimit = 32

when: "the commands are generated"

List<String> cmds = GenerateProtoTask.generateCmds(baseCmd, protoFiles, cmdLengthLimit)

then: "it returns no commands"
cmds.isEmpty()
}

void "test getCmdLengthLimit returns correct limit for Windows XP"() {
given: "Windows OS at major version 5"

String os = "windows"
String version = "5.0.0"

when: "the command length limit is queried"

int limit = GenerateProtoTask.getCmdLengthLimit(os, version)

then: "it returns the XP limit"
limit == GenerateProtoTask.XP_CMD_LENGTH_LIMIT
}

void "test getCmdLengthLimit returns correct limit for Windows Vista"() {
given: "Windows OS at major version 6"

String os = "Windows"
String version = "6.0.0"

when: "the command length limit is queried"

int limit = GenerateProtoTask.getCmdLengthLimit(os, version)

then: "it returns the Vista limit"
limit == GenerateProtoTask.VISTA_CMD_LENGTH_LIMIT
}

void "test getCmdLengthLimit returns correct limit for non-Windows OS"() {
given: "MacOS X at major version 10"

String os = "Mac OS X"
String version = "10.0.0"

when: "the command length limit is queried"

int limit = GenerateProtoTask.getCmdLengthLimit(os, version)

then: "it returns maximum integer value"
limit == Integer.MAX_VALUE
}
}