1 /* 2 * Copyright (C) 2019 The Android Open Source Project 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 package com.google.tuningfork.validation; 18 19 import com.google.common.base.Preconditions; 20 import com.google.common.collect.ImmutableList; 21 import com.google.common.io.Files; 22 import com.google.protobuf.DescriptorProtos.FileDescriptorProto; 23 import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 24 import com.google.protobuf.Descriptors.DescriptorValidationException; 25 import com.google.protobuf.Descriptors.FileDescriptor; 26 import com.google.protobuf.InvalidProtocolBufferException; 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.util.Optional; 31 32 /** Compiles .proto file into a {@link FileDescriptor} */ 33 public class ExternalProtoCompiler { 34 35 private final String protoPath; 36 private final FileDescriptor[] emptyDeps = new FileDescriptor[0]; 37 ExternalProtoCompiler(File protoBinary)38 public ExternalProtoCompiler(File protoBinary) { 39 Preconditions.checkNotNull(protoBinary, "executable"); 40 protoPath = protoBinary.getAbsolutePath(); 41 } 42 compile(File file, Optional<File> outFile)43 public FileDescriptor compile(File file, Optional<File> outFile) 44 throws IOException, CompilationException { 45 Preconditions.checkNotNull(file, "file"); 46 FileDescriptor descriptor = buildAndRunCompilerProcess(file, outFile); 47 return descriptor; 48 } 49 runCommand(ProcessBuilder processBuilder)50 public byte[] runCommand(ProcessBuilder processBuilder) throws IOException, CompilationException { 51 Process process = processBuilder.start(); 52 try { 53 process.waitFor(); 54 } catch (InterruptedException e) { 55 throw new CompilationException("Process was interrupted", e); 56 } 57 InputStream stdin = process.getInputStream(); 58 byte[] result = new byte[stdin.available()]; 59 stdin.read(result); 60 return result; 61 } 62 buildAndRunCompilerProcess(File file, Optional<File> outFile)63 private FileDescriptor buildAndRunCompilerProcess(File file, Optional<File> outFile) 64 throws IOException, CompilationException { 65 ImmutableList<String> commandLine = createCommandLine(file); 66 byte[] result = runCommand(new ProcessBuilder(commandLine)); 67 68 try { 69 FileDescriptorSet fileSet = FileDescriptorSet.parseFrom(result); 70 if (outFile.isPresent()) { 71 Files.write(fileSet.toByteArray(), outFile.get()); 72 } 73 for (FileDescriptorProto descProto : fileSet.getFileList()) { 74 if (descProto.getName().equals(file.getName())) { 75 return FileDescriptor.buildFrom(descProto, emptyDeps); 76 } 77 } 78 } catch (DescriptorValidationException | InvalidProtocolBufferException e) { 79 throw new IllegalStateException(e); 80 } 81 throw new CompilationException( 82 String.format("Descriptor for [%s] does not exist.", file.getName())); 83 } 84 85 /* Decode textproto message from text(textprotoFile) into binary(binFile) */ encodeFromTextprotoFile( String message, File protoFile, File textprotoFile, String binaryPath, Optional<File> errorFile)86 public File encodeFromTextprotoFile( 87 String message, 88 File protoFile, 89 File textprotoFile, 90 String binaryPath, 91 Optional<File> errorFile) 92 throws IOException, CompilationException { 93 ImmutableList<String> command = encodeCommandLine(message, protoFile); 94 95 File binFile = new File(binaryPath); 96 97 ProcessBuilder builder = 98 new ProcessBuilder(command).redirectInput(textprotoFile).redirectOutput(binFile); 99 100 if (errorFile.isPresent()) { 101 builder.redirectError(errorFile.get()); 102 } 103 104 runCommand(builder); 105 return binFile; 106 } 107 108 /* Decode textproto message from binary(binFile) into text(textFile) */ decodeToTextprotoFile( String message, File protoFile, String textprotoPath, File binFile, Optional<File> errorFile)109 public File decodeToTextprotoFile( 110 String message, File protoFile, String textprotoPath, File binFile, Optional<File> errorFile) 111 throws IOException, CompilationException { 112 ImmutableList<String> command = decodeCommandLine(message, protoFile); 113 114 File textprotoFile = new File(textprotoPath); 115 116 ProcessBuilder builder = 117 new ProcessBuilder(command).redirectInput(binFile).redirectOutput(textprotoFile); 118 119 if (errorFile.isPresent()) { 120 builder.redirectError(errorFile.get()); 121 } 122 123 runCommand(builder); 124 return textprotoFile; 125 } 126 createCommandLine(File file)127 private ImmutableList<String> createCommandLine(File file) { 128 return ImmutableList.of( 129 protoPath, 130 "-o", 131 "/dev/stdout", 132 "-I", 133 file.getName() + "=" + file.getAbsolutePath(), // That should be one line 134 file.getName()); 135 } 136 encodeCommandLine(String message, File protoFile)137 private ImmutableList<String> encodeCommandLine(String message, File protoFile) { 138 return ImmutableList.of( 139 protoPath, 140 "--encode=" + message, 141 "-I", 142 protoFile.getName() + "=" + protoFile.getAbsolutePath(), // That should be one line 143 protoFile.getName()); 144 } 145 decodeCommandLine(String message, File protoFile)146 private ImmutableList<String> decodeCommandLine(String message, File protoFile) { 147 return ImmutableList.of( 148 protoPath, 149 "--decode=" + message, 150 "-I", 151 protoFile.getName() + "=" + protoFile.getAbsolutePath(), // That should be one line 152 protoFile.getName()); 153 } 154 } 155