blob: 9e9849cfb0538a3e67263c10c460371a7775db01 [file] [log] [blame]
temporal40ee5512008-07-10 02:12:20 +00001// Protocol Buffers - Google's data interchange format
kenton@google.com24bf56f2008-09-24 20:31:01 +00002// Copyright 2008 Google Inc. All rights reserved.
temporal40ee5512008-07-10 02:12:20 +00003// http://code.google.com/p/protobuf/
4//
kenton@google.com24bf56f2008-09-24 20:31:01 +00005// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
temporal40ee5512008-07-10 02:12:20 +00008//
kenton@google.com24bf56f2008-09-24 20:31:01 +00009// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
temporal40ee5512008-07-10 02:12:20 +000018//
kenton@google.com24bf56f2008-09-24 20:31:01 +000019// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
temporal40ee5512008-07-10 02:12:20 +000030
31// Author: kenton@google.com (Kenton Varda)
32// Based on original Protocol Buffers design by
33// Sanjay Ghemawat, Jeff Dean, and others.
34
kenton@google.comfccb1462009-12-18 02:11:36 +000035#include <google/protobuf/compiler/command_line_interface.h>
36
kenton@google.com26bd9ee2008-11-21 00:06:27 +000037#include <stdio.h>
temporal40ee5512008-07-10 02:12:20 +000038#include <sys/types.h>
39#include <sys/stat.h>
40#include <fcntl.h>
41#ifdef _MSC_VER
42#include <io.h>
43#include <direct.h>
44#else
45#include <unistd.h>
46#endif
47#include <errno.h>
48#include <iostream>
temporalcc930432008-07-21 20:28:30 +000049#include <ctype.h>
temporal40ee5512008-07-10 02:12:20 +000050
liujisi@google.com33165fe2010-11-02 13:14:58 +000051#include <google/protobuf/stubs/hash.h>
52
53#include <google/protobuf/stubs/common.h>
temporal40ee5512008-07-10 02:12:20 +000054#include <google/protobuf/compiler/importer.h>
55#include <google/protobuf/compiler/code_generator.h>
kenton@google.comfccb1462009-12-18 02:11:36 +000056#include <google/protobuf/compiler/plugin.pb.h>
57#include <google/protobuf/compiler/subprocess.h>
kenton@google.com50663222009-12-24 02:12:15 +000058#include <google/protobuf/compiler/zip_writer.h>
temporal40ee5512008-07-10 02:12:20 +000059#include <google/protobuf/descriptor.h>
temporal779f61c2008-08-13 03:15:00 +000060#include <google/protobuf/text_format.h>
61#include <google/protobuf/dynamic_message.h>
temporal40ee5512008-07-10 02:12:20 +000062#include <google/protobuf/io/zero_copy_stream_impl.h>
kenton@google.comfccb1462009-12-18 02:11:36 +000063#include <google/protobuf/io/printer.h>
temporal40ee5512008-07-10 02:12:20 +000064#include <google/protobuf/stubs/strutil.h>
kenton@google.comfccb1462009-12-18 02:11:36 +000065#include <google/protobuf/stubs/substitute.h>
66#include <google/protobuf/stubs/map-util.h>
kenton@google.com5f121642009-12-23 07:03:06 +000067#include <google/protobuf/stubs/stl_util-inl.h>
temporal40ee5512008-07-10 02:12:20 +000068
69
70namespace google {
71namespace protobuf {
72namespace compiler {
73
74#if defined(_WIN32)
75#define mkdir(name, mode) mkdir(name)
76#ifndef W_OK
77#define W_OK 02 // not defined by MSVC for whatever reason
78#endif
79#ifndef F_OK
80#define F_OK 00 // not defined by MSVC for whatever reason
81#endif
temporal779f61c2008-08-13 03:15:00 +000082#ifndef STDIN_FILENO
83#define STDIN_FILENO 0
84#endif
85#ifndef STDOUT_FILENO
86#define STDOUT_FILENO 1
87#endif
temporal40ee5512008-07-10 02:12:20 +000088#endif
89
90#ifndef O_BINARY
91#ifdef _O_BINARY
92#define O_BINARY _O_BINARY
93#else
94#define O_BINARY 0 // If this isn't defined, the platform doesn't need it.
95#endif
96#endif
97
98namespace {
99#if defined(_WIN32) && !defined(__CYGWIN__)
100static const char* kPathSeparator = ";";
101#else
102static const char* kPathSeparator = ":";
103#endif
temporalcc930432008-07-21 20:28:30 +0000104
105// Returns true if the text looks like a Windows-style absolute path, starting
kenton@google.com2f669cb2008-12-02 05:59:15 +0000106// with a drive letter. Example: "C:\foo". TODO(kenton): Share this with
107// copy in importer.cc?
temporalcc930432008-07-21 20:28:30 +0000108static bool IsWindowsAbsolutePath(const string& text) {
109#if defined(_WIN32) || defined(__CYGWIN__)
110 return text.size() >= 3 && text[1] == ':' &&
111 isalpha(text[0]) &&
112 (text[2] == '/' || text[2] == '\\') &&
113 text.find_last_of(':') == 1;
114#else
115 return false;
116#endif
117}
118
temporal779f61c2008-08-13 03:15:00 +0000119void SetFdToTextMode(int fd) {
120#ifdef _WIN32
121 if (_setmode(fd, _O_TEXT) == -1) {
122 // This should never happen, I think.
123 GOOGLE_LOG(WARNING) << "_setmode(" << fd << ", _O_TEXT): " << strerror(errno);
124 }
125#endif
126 // (Text and binary are the same on non-Windows platforms.)
127}
128
129void SetFdToBinaryMode(int fd) {
130#ifdef _WIN32
131 if (_setmode(fd, _O_BINARY) == -1) {
132 // This should never happen, I think.
133 GOOGLE_LOG(WARNING) << "_setmode(" << fd << ", _O_BINARY): " << strerror(errno);
134 }
135#endif
136 // (Text and binary are the same on non-Windows platforms.)
137}
138
kenton@google.com5f121642009-12-23 07:03:06 +0000139void AddTrailingSlash(string* path) {
140 if (!path->empty() && path->at(path->size() - 1) != '/') {
141 path->push_back('/');
142 }
143}
144
145bool VerifyDirectoryExists(const string& path) {
146 if (path.empty()) return true;
147
148 if (access(path.c_str(), W_OK) == -1) {
149 cerr << path << ": " << strerror(errno) << endl;
150 return false;
151 } else {
152 return true;
153 }
154}
155
156// Try to create the parent directory of the given file, creating the parent's
157// parent if necessary, and so on. The full file name is actually
158// (prefix + filename), but we assume |prefix| already exists and only create
159// directories listed in |filename|.
kenton@google.com50663222009-12-24 02:12:15 +0000160bool TryCreateParentDirectory(const string& prefix, const string& filename) {
kenton@google.com5f121642009-12-23 07:03:06 +0000161 // Recursively create parent directories to the output file.
162 vector<string> parts;
163 SplitStringUsing(filename, "/", &parts);
164 string path_so_far = prefix;
165 for (int i = 0; i < parts.size() - 1; i++) {
166 path_so_far += parts[i];
167 if (mkdir(path_so_far.c_str(), 0777) != 0) {
168 if (errno != EEXIST) {
169 cerr << filename << ": while trying to create directory "
170 << path_so_far << ": " << strerror(errno) << endl;
kenton@google.com50663222009-12-24 02:12:15 +0000171 return false;
kenton@google.com5f121642009-12-23 07:03:06 +0000172 }
173 }
174 path_so_far += '/';
175 }
kenton@google.com50663222009-12-24 02:12:15 +0000176
177 return true;
kenton@google.com5f121642009-12-23 07:03:06 +0000178}
179
temporal40ee5512008-07-10 02:12:20 +0000180} // namespace
181
182// A MultiFileErrorCollector that prints errors to stderr.
temporal779f61c2008-08-13 03:15:00 +0000183class CommandLineInterface::ErrorPrinter : public MultiFileErrorCollector,
184 public io::ErrorCollector {
temporal40ee5512008-07-10 02:12:20 +0000185 public:
liujisi@google.com33165fe2010-11-02 13:14:58 +0000186 ErrorPrinter(ErrorFormat format, DiskSourceTree *tree = NULL)
kenton@google.com6793c1a2010-04-05 21:45:45 +0000187 : format_(format), tree_(tree) {}
temporal40ee5512008-07-10 02:12:20 +0000188 ~ErrorPrinter() {}
189
190 // implements MultiFileErrorCollector ------------------------------
191 void AddError(const string& filename, int line, int column,
192 const string& message) {
kenton@google.comf663b162009-04-15 19:50:54 +0000193
kenton@google.com6793c1a2010-04-05 21:45:45 +0000194 // Print full path when running under MSVS
liujisi@google.com33165fe2010-11-02 13:14:58 +0000195 string dfile;
196 if (format_ == CommandLineInterface::ERROR_FORMAT_MSVS &&
kenton@google.com6793c1a2010-04-05 21:45:45 +0000197 tree_ != NULL &&
198 tree_->VirtualFileToDiskFile(filename, &dfile)) {
199 cerr << dfile;
200 } else {
201 cerr << filename;
202 }
kenton@google.comf663b162009-04-15 19:50:54 +0000203
temporal40ee5512008-07-10 02:12:20 +0000204 // Users typically expect 1-based line/column numbers, so we add 1
205 // to each here.
temporal40ee5512008-07-10 02:12:20 +0000206 if (line != -1) {
kenton@google.comf663b162009-04-15 19:50:54 +0000207 // Allow for both GCC- and Visual-Studio-compatible output.
208 switch (format_) {
209 case CommandLineInterface::ERROR_FORMAT_GCC:
210 cerr << ":" << (line + 1) << ":" << (column + 1);
211 break;
212 case CommandLineInterface::ERROR_FORMAT_MSVS:
213 cerr << "(" << (line + 1) << ") : error in column=" << (column + 1);
214 break;
215 }
temporal40ee5512008-07-10 02:12:20 +0000216 }
kenton@google.comf663b162009-04-15 19:50:54 +0000217
temporal40ee5512008-07-10 02:12:20 +0000218 cerr << ": " << message << endl;
219 }
temporal779f61c2008-08-13 03:15:00 +0000220
221 // implements io::ErrorCollector -----------------------------------
222 void AddError(int line, int column, const string& message) {
223 AddError("input", line, column, message);
224 }
kenton@google.comf663b162009-04-15 19:50:54 +0000225
226 private:
227 const ErrorFormat format_;
kenton@google.com6793c1a2010-04-05 21:45:45 +0000228 DiskSourceTree *tree_;
temporal40ee5512008-07-10 02:12:20 +0000229};
230
231// -------------------------------------------------------------------
232
liujisi@google.com33165fe2010-11-02 13:14:58 +0000233// A GeneratorContext implementation that buffers files in memory, then dumps
kenton@google.com53530182010-01-07 02:08:03 +0000234// them all to disk on demand.
liujisi@google.com33165fe2010-11-02 13:14:58 +0000235class CommandLineInterface::GeneratorContextImpl : public GeneratorContext {
temporal40ee5512008-07-10 02:12:20 +0000236 public:
liujisi@google.com33165fe2010-11-02 13:14:58 +0000237 GeneratorContextImpl(const vector<const FileDescriptor*>& parsed_files);
238 ~GeneratorContextImpl();
temporal40ee5512008-07-10 02:12:20 +0000239
kenton@google.com53530182010-01-07 02:08:03 +0000240 // Write all files in the directory to disk at the given output location,
241 // which must end in a '/'.
kenton@google.com50663222009-12-24 02:12:15 +0000242 bool WriteAllToDisk(const string& prefix);
kenton@google.com53530182010-01-07 02:08:03 +0000243
244 // Write the contents of this directory to a ZIP-format archive with the
245 // given name.
kenton@google.com50663222009-12-24 02:12:15 +0000246 bool WriteAllToZip(const string& filename);
kenton@google.com53530182010-01-07 02:08:03 +0000247
248 // Add a boilerplate META-INF/MANIFEST.MF file as required by the Java JAR
249 // format, unless one has already been written.
kenton@google.comd2fcbba2010-01-04 19:47:18 +0000250 void AddJarManifest();
temporal40ee5512008-07-10 02:12:20 +0000251
liujisi@google.com33165fe2010-11-02 13:14:58 +0000252 // implements GeneratorContext --------------------------------------
temporal40ee5512008-07-10 02:12:20 +0000253 io::ZeroCopyOutputStream* Open(const string& filename);
kenton@google.comfccb1462009-12-18 02:11:36 +0000254 io::ZeroCopyOutputStream* OpenForInsert(
255 const string& filename, const string& insertion_point);
liujisi@google.com33165fe2010-11-02 13:14:58 +0000256 void ListParsedFiles(vector<const FileDescriptor*>* output) {
257 *output = parsed_files_;
258 }
temporal40ee5512008-07-10 02:12:20 +0000259
260 private:
kenton@google.com5f121642009-12-23 07:03:06 +0000261 friend class MemoryOutputStream;
262
kenton@google.com50663222009-12-24 02:12:15 +0000263 // map instead of hash_map so that files are written in order (good when
264 // writing zips).
265 map<string, string*> files_;
liujisi@google.com33165fe2010-11-02 13:14:58 +0000266 const vector<const FileDescriptor*>& parsed_files_;
temporal40ee5512008-07-10 02:12:20 +0000267 bool had_error_;
268};
269
kenton@google.com5f121642009-12-23 07:03:06 +0000270class CommandLineInterface::MemoryOutputStream
kenton@google.comfccb1462009-12-18 02:11:36 +0000271 : public io::ZeroCopyOutputStream {
272 public:
liujisi@google.com33165fe2010-11-02 13:14:58 +0000273 MemoryOutputStream(GeneratorContextImpl* directory, const string& filename);
274 MemoryOutputStream(GeneratorContextImpl* directory, const string& filename,
kenton@google.com5f121642009-12-23 07:03:06 +0000275 const string& insertion_point);
276 virtual ~MemoryOutputStream();
kenton@google.comfccb1462009-12-18 02:11:36 +0000277
278 // implements ZeroCopyOutputStream ---------------------------------
kenton@google.com5f121642009-12-23 07:03:06 +0000279 virtual bool Next(void** data, int* size) { return inner_->Next(data, size); }
280 virtual void BackUp(int count) { inner_->BackUp(count); }
281 virtual int64 ByteCount() const { return inner_->ByteCount(); }
kenton@google.comfccb1462009-12-18 02:11:36 +0000282
283 private:
kenton@google.com5f121642009-12-23 07:03:06 +0000284 // Where to insert the string when it's done.
liujisi@google.com33165fe2010-11-02 13:14:58 +0000285 GeneratorContextImpl* directory_;
kenton@google.comfccb1462009-12-18 02:11:36 +0000286 string filename_;
kenton@google.com5f121642009-12-23 07:03:06 +0000287 string insertion_point_;
kenton@google.comfccb1462009-12-18 02:11:36 +0000288
kenton@google.com5f121642009-12-23 07:03:06 +0000289 // The string we're building.
290 string data_;
291
292 // StringOutputStream writing to data_.
293 scoped_ptr<io::StringOutputStream> inner_;
kenton@google.comfccb1462009-12-18 02:11:36 +0000294};
295
temporal40ee5512008-07-10 02:12:20 +0000296// -------------------------------------------------------------------
297
liujisi@google.com33165fe2010-11-02 13:14:58 +0000298CommandLineInterface::GeneratorContextImpl::GeneratorContextImpl(
299 const vector<const FileDescriptor*>& parsed_files)
300 : parsed_files_(parsed_files),
301 had_error_(false) {
302}
kenton@google.com5f121642009-12-23 07:03:06 +0000303
liujisi@google.com33165fe2010-11-02 13:14:58 +0000304CommandLineInterface::GeneratorContextImpl::~GeneratorContextImpl() {
kenton@google.com5f121642009-12-23 07:03:06 +0000305 STLDeleteValues(&files_);
306}
307
liujisi@google.com33165fe2010-11-02 13:14:58 +0000308bool CommandLineInterface::GeneratorContextImpl::WriteAllToDisk(
kenton@google.com50663222009-12-24 02:12:15 +0000309 const string& prefix) {
kenton@google.com5f121642009-12-23 07:03:06 +0000310 if (had_error_) {
311 return false;
temporal40ee5512008-07-10 02:12:20 +0000312 }
temporal40ee5512008-07-10 02:12:20 +0000313
kenton@google.com50663222009-12-24 02:12:15 +0000314 if (!VerifyDirectoryExists(prefix)) {
315 return false;
316 }
317
318 for (map<string, string*>::const_iterator iter = files_.begin();
kenton@google.com5f121642009-12-23 07:03:06 +0000319 iter != files_.end(); ++iter) {
kenton@google.com50663222009-12-24 02:12:15 +0000320 const string& relative_filename = iter->first;
kenton@google.com5f121642009-12-23 07:03:06 +0000321 const char* data = iter->second->data();
322 int size = iter->second->size();
temporal40ee5512008-07-10 02:12:20 +0000323
kenton@google.com50663222009-12-24 02:12:15 +0000324 if (!TryCreateParentDirectory(prefix, relative_filename)) {
325 return false;
326 }
327 string filename = prefix + relative_filename;
328
kenton@google.com5f121642009-12-23 07:03:06 +0000329 // Create the output file.
330 int file_descriptor;
331 do {
332 file_descriptor =
333 open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
334 } while (file_descriptor < 0 && errno == EINTR);
335
336 if (file_descriptor < 0) {
337 int error = errno;
338 cerr << filename << ": " << strerror(error);
339 return false;
340 }
341
342 // Write the file.
343 while (size > 0) {
344 int write_result;
345 do {
346 write_result = write(file_descriptor, data, size);
347 } while (write_result < 0 && errno == EINTR);
348
349 if (write_result <= 0) {
350 // Write error.
351
352 // FIXME(kenton): According to the man page, if write() returns zero,
353 // there was no error; write() simply did not write anything. It's
354 // unclear under what circumstances this might happen, but presumably
355 // errno won't be set in this case. I am confused as to how such an
356 // event should be handled. For now I'm treating it as an error,
357 // since retrying seems like it could lead to an infinite loop. I
358 // suspect this never actually happens anyway.
359
360 if (write_result < 0) {
361 int error = errno;
362 cerr << filename << ": write: " << strerror(error);
363 } else {
364 cerr << filename << ": write() returned zero?" << endl;
365 }
366 return false;
367 }
368
369 data += write_result;
370 size -= write_result;
371 }
372
373 if (close(file_descriptor) != 0) {
374 int error = errno;
375 cerr << filename << ": close: " << strerror(error);
temporal40ee5512008-07-10 02:12:20 +0000376 return false;
377 }
378 }
379
380 return true;
381}
382
liujisi@google.com33165fe2010-11-02 13:14:58 +0000383bool CommandLineInterface::GeneratorContextImpl::WriteAllToZip(
kenton@google.com50663222009-12-24 02:12:15 +0000384 const string& filename) {
385 if (had_error_) {
386 return false;
387 }
388
389 // Create the output file.
390 int file_descriptor;
391 do {
392 file_descriptor =
393 open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
394 } while (file_descriptor < 0 && errno == EINTR);
395
396 if (file_descriptor < 0) {
397 int error = errno;
398 cerr << filename << ": " << strerror(error);
399 return false;
400 }
401
402 // Create the ZipWriter
403 io::FileOutputStream stream(file_descriptor);
404 ZipWriter zip_writer(&stream);
405
406 for (map<string, string*>::const_iterator iter = files_.begin();
407 iter != files_.end(); ++iter) {
408 zip_writer.Write(iter->first, *iter->second);
409 }
410
411 zip_writer.WriteDirectory();
412
413 if (stream.GetErrno() != 0) {
414 cerr << filename << ": " << strerror(stream.GetErrno()) << endl;
415 }
416
417 if (!stream.Close()) {
418 cerr << filename << ": " << strerror(stream.GetErrno()) << endl;
419 }
420
421 return true;
422}
423
liujisi@google.com33165fe2010-11-02 13:14:58 +0000424void CommandLineInterface::GeneratorContextImpl::AddJarManifest() {
kenton@google.comd2fcbba2010-01-04 19:47:18 +0000425 string** map_slot = &files_["META-INF/MANIFEST.MF"];
426 if (*map_slot == NULL) {
427 *map_slot = new string(
428 "Manifest-Version: 1.0\n"
429 "Created-By: 1.6.0 (protoc)\n"
430 "\n");
431 }
432}
433
liujisi@google.com33165fe2010-11-02 13:14:58 +0000434io::ZeroCopyOutputStream* CommandLineInterface::GeneratorContextImpl::Open(
temporal40ee5512008-07-10 02:12:20 +0000435 const string& filename) {
kenton@google.com5f121642009-12-23 07:03:06 +0000436 return new MemoryOutputStream(this, filename);
temporal40ee5512008-07-10 02:12:20 +0000437}
438
kenton@google.comfccb1462009-12-18 02:11:36 +0000439io::ZeroCopyOutputStream*
liujisi@google.com33165fe2010-11-02 13:14:58 +0000440CommandLineInterface::GeneratorContextImpl::OpenForInsert(
kenton@google.comfccb1462009-12-18 02:11:36 +0000441 const string& filename, const string& insertion_point) {
kenton@google.com5f121642009-12-23 07:03:06 +0000442 return new MemoryOutputStream(this, filename, insertion_point);
kenton@google.comfccb1462009-12-18 02:11:36 +0000443}
444
kenton@google.com5f121642009-12-23 07:03:06 +0000445// -------------------------------------------------------------------
kenton@google.comfccb1462009-12-18 02:11:36 +0000446
kenton@google.com5f121642009-12-23 07:03:06 +0000447CommandLineInterface::MemoryOutputStream::MemoryOutputStream(
liujisi@google.com33165fe2010-11-02 13:14:58 +0000448 GeneratorContextImpl* directory, const string& filename)
kenton@google.com5f121642009-12-23 07:03:06 +0000449 : directory_(directory),
kenton@google.comfccb1462009-12-18 02:11:36 +0000450 filename_(filename),
kenton@google.com5f121642009-12-23 07:03:06 +0000451 inner_(new io::StringOutputStream(&data_)) {
kenton@google.comfccb1462009-12-18 02:11:36 +0000452}
453
kenton@google.com5f121642009-12-23 07:03:06 +0000454CommandLineInterface::MemoryOutputStream::MemoryOutputStream(
liujisi@google.com33165fe2010-11-02 13:14:58 +0000455 GeneratorContextImpl* directory, const string& filename,
kenton@google.com5f121642009-12-23 07:03:06 +0000456 const string& insertion_point)
457 : directory_(directory),
458 filename_(filename),
459 insertion_point_(insertion_point),
460 inner_(new io::StringOutputStream(&data_)) {
461}
kenton@google.comfccb1462009-12-18 02:11:36 +0000462
kenton@google.com5f121642009-12-23 07:03:06 +0000463CommandLineInterface::MemoryOutputStream::~MemoryOutputStream() {
464 // Make sure all data has been written.
465 inner_.reset();
466
467 // Insert into the directory.
468 string** map_slot = &directory_->files_[filename_];
469
470 if (insertion_point_.empty()) {
471 // This was just a regular Open().
472 if (*map_slot != NULL) {
473 cerr << filename_ << ": Tried to write the same file twice." << endl;
474 directory_->had_error_ = true;
475 return;
476 }
477
478 *map_slot = new string;
479 (*map_slot)->swap(data_);
kenton@google.comfccb1462009-12-18 02:11:36 +0000480 } else {
kenton@google.com5f121642009-12-23 07:03:06 +0000481 // This was an OpenForInsert().
kenton@google.comfccb1462009-12-18 02:11:36 +0000482
kenton@google.com5f121642009-12-23 07:03:06 +0000483 // If the data doens't end with a clean line break, add one.
484 if (!data_.empty() && data_[data_.size() - 1] != '\n') {
485 data_.push_back('\n');
kenton@google.comfccb1462009-12-18 02:11:36 +0000486 }
487
kenton@google.com5f121642009-12-23 07:03:06 +0000488 // Find the file we are going to insert into.
489 if (*map_slot == NULL) {
490 cerr << filename_ << ": Tried to insert into file that doesn't exist."
491 << endl;
492 directory_->had_error_ = true;
493 return;
kenton@google.comfccb1462009-12-18 02:11:36 +0000494 }
kenton@google.com5f121642009-12-23 07:03:06 +0000495 string* target = *map_slot;
kenton@google.comfccb1462009-12-18 02:11:36 +0000496
kenton@google.com5f121642009-12-23 07:03:06 +0000497 // Find the insertion point.
498 string magic_string = strings::Substitute(
499 "@@protoc_insertion_point($0)", insertion_point_);
500 string::size_type pos = target->find(magic_string);
kenton@google.comfccb1462009-12-18 02:11:36 +0000501
kenton@google.com5f121642009-12-23 07:03:06 +0000502 if (pos == string::npos) {
503 cerr << filename_ << ": insertion point \"" << insertion_point_
504 << "\" not found." << endl;
505 directory_->had_error_ = true;
506 return;
kenton@google.com684d45b2009-12-19 04:50:00 +0000507 }
kenton@google.comfccb1462009-12-18 02:11:36 +0000508
kenton@google.com5f121642009-12-23 07:03:06 +0000509 // Seek backwards to the beginning of the line, which is where we will
510 // insert the data. Note that this has the effect of pushing the insertion
511 // point down, so the data is inserted before it. This is intentional
512 // because it means that multiple insertions at the same point will end
513 // up in the expected order in the final output.
514 pos = target->find_last_of('\n', pos);
515 if (pos == string::npos) {
516 // Insertion point is on the first line.
517 pos = 0;
518 } else {
519 // Advance to character after '\n'.
520 ++pos;
521 }
522
523 // Extract indent.
524 string indent_(*target, pos, target->find_first_not_of(" \t", pos) - pos);
525
526 if (indent_.empty()) {
527 // No indent. This makes things easier.
528 target->insert(pos, data_);
529 } else {
530 // Calculate how much space we need.
531 int indent_size = 0;
532 for (int i = 0; i < data_.size(); i++) {
533 if (data_[i] == '\n') indent_size += indent_.size();
534 }
535
536 // Make a hole for it.
537 target->insert(pos, data_.size() + indent_size, '\0');
538
539 // Now copy in the data.
540 string::size_type data_pos = 0;
541 char* target_ptr = string_as_array(target) + pos;
542 while (data_pos < data_.size()) {
543 // Copy indent.
544 memcpy(target_ptr, indent_.data(), indent_.size());
545 target_ptr += indent_.size();
546
547 // Copy line from data_.
548 // We already guaranteed that data_ ends with a newline (above), so this
549 // search can't fail.
550 string::size_type line_length =
551 data_.find_first_of('\n', data_pos) + 1 - data_pos;
552 memcpy(target_ptr, data_.data() + data_pos, line_length);
553 target_ptr += line_length;
554 data_pos += line_length;
555 }
556
557 GOOGLE_CHECK_EQ(target_ptr,
558 string_as_array(target) + pos + data_.size() + indent_size);
559 }
kenton@google.comfccb1462009-12-18 02:11:36 +0000560 }
561}
562
temporal40ee5512008-07-10 02:12:20 +0000563// ===================================================================
564
565CommandLineInterface::CommandLineInterface()
temporal779f61c2008-08-13 03:15:00 +0000566 : mode_(MODE_COMPILE),
kenton@google.comf663b162009-04-15 19:50:54 +0000567 error_format_(ERROR_FORMAT_GCC),
temporal779f61c2008-08-13 03:15:00 +0000568 imports_in_descriptor_set_(false),
569 disallow_services_(false),
temporal40ee5512008-07-10 02:12:20 +0000570 inputs_are_proto_path_relative_(false) {}
571CommandLineInterface::~CommandLineInterface() {}
572
573void CommandLineInterface::RegisterGenerator(const string& flag_name,
574 CodeGenerator* generator,
575 const string& help_text) {
576 GeneratorInfo info;
577 info.generator = generator;
578 info.help_text = help_text;
579 generators_[flag_name] = info;
580}
581
kenton@google.comfccb1462009-12-18 02:11:36 +0000582void CommandLineInterface::AllowPlugins(const string& exe_name_prefix) {
583 plugin_prefix_ = exe_name_prefix;
584}
585
temporal40ee5512008-07-10 02:12:20 +0000586int CommandLineInterface::Run(int argc, const char* const argv[]) {
587 Clear();
temporal779f61c2008-08-13 03:15:00 +0000588 if (!ParseArguments(argc, argv)) return 1;
temporal40ee5512008-07-10 02:12:20 +0000589
590 // Set up the source tree.
591 DiskSourceTree source_tree;
592 for (int i = 0; i < proto_path_.size(); i++) {
593 source_tree.MapPath(proto_path_[i].first, proto_path_[i].second);
594 }
595
596 // Map input files to virtual paths if necessary.
597 if (!inputs_are_proto_path_relative_) {
598 if (!MakeInputsBeProtoPathRelative(&source_tree)) {
temporal779f61c2008-08-13 03:15:00 +0000599 return 1;
temporal40ee5512008-07-10 02:12:20 +0000600 }
601 }
602
603 // Allocate the Importer.
kenton@google.com6793c1a2010-04-05 21:45:45 +0000604 ErrorPrinter error_collector(error_format_, &source_tree);
temporal40ee5512008-07-10 02:12:20 +0000605 Importer importer(&source_tree, &error_collector);
606
temporal779f61c2008-08-13 03:15:00 +0000607 vector<const FileDescriptor*> parsed_files;
608
kenton@google.comfccb1462009-12-18 02:11:36 +0000609 // Parse each file.
temporal40ee5512008-07-10 02:12:20 +0000610 for (int i = 0; i < input_files_.size(); i++) {
611 // Import the file.
612 const FileDescriptor* parsed_file = importer.Import(input_files_[i]);
temporal779f61c2008-08-13 03:15:00 +0000613 if (parsed_file == NULL) return 1;
614 parsed_files.push_back(parsed_file);
temporal40ee5512008-07-10 02:12:20 +0000615
616 // Enforce --disallow_services.
617 if (disallow_services_ && parsed_file->service_count() > 0) {
618 cerr << parsed_file->name() << ": This file contains services, but "
619 "--disallow_services was used." << endl;
temporal779f61c2008-08-13 03:15:00 +0000620 return 1;
temporal40ee5512008-07-10 02:12:20 +0000621 }
kenton@google.comfccb1462009-12-18 02:11:36 +0000622 }
temporal40ee5512008-07-10 02:12:20 +0000623
liujisi@google.com33165fe2010-11-02 13:14:58 +0000624 // We construct a separate GeneratorContext for each output location. Note
kenton@google.com50663222009-12-24 02:12:15 +0000625 // that two code generators may output to the same location, in which case
liujisi@google.com33165fe2010-11-02 13:14:58 +0000626 // they should share a single GeneratorContext so that OpenForInsert() works.
627 typedef hash_map<string, GeneratorContextImpl*> GeneratorContextMap;
628 GeneratorContextMap output_directories;
kenton@google.com50663222009-12-24 02:12:15 +0000629
kenton@google.comfccb1462009-12-18 02:11:36 +0000630 // Generate output.
631 if (mode_ == MODE_COMPILE) {
632 for (int i = 0; i < output_directives_.size(); i++) {
kenton@google.com50663222009-12-24 02:12:15 +0000633 string output_location = output_directives_[i].output_location;
kenton@google.com50663222009-12-24 02:12:15 +0000634 if (!HasSuffixString(output_location, ".zip") &&
kenton@google.comd2fcbba2010-01-04 19:47:18 +0000635 !HasSuffixString(output_location, ".jar")) {
kenton@google.com50663222009-12-24 02:12:15 +0000636 AddTrailingSlash(&output_location);
637 }
liujisi@google.com33165fe2010-11-02 13:14:58 +0000638 GeneratorContextImpl** map_slot = &output_directories[output_location];
kenton@google.com50663222009-12-24 02:12:15 +0000639
640 if (*map_slot == NULL) {
641 // First time we've seen this output location.
liujisi@google.com33165fe2010-11-02 13:14:58 +0000642 *map_slot = new GeneratorContextImpl(parsed_files);
kenton@google.com50663222009-12-24 02:12:15 +0000643 }
644
645 if (!GenerateOutput(parsed_files, output_directives_[i], *map_slot)) {
kenton@google.comf9fa0592010-01-08 05:47:13 +0000646 STLDeleteValues(&output_directories);
kenton@google.comfccb1462009-12-18 02:11:36 +0000647 return 1;
temporal779f61c2008-08-13 03:15:00 +0000648 }
649 }
650 }
651
kenton@google.com50663222009-12-24 02:12:15 +0000652 // Write all output to disk.
liujisi@google.com33165fe2010-11-02 13:14:58 +0000653 for (GeneratorContextMap::iterator iter = output_directories.begin();
kenton@google.comf9fa0592010-01-08 05:47:13 +0000654 iter != output_directories.end(); ++iter) {
kenton@google.com50663222009-12-24 02:12:15 +0000655 const string& location = iter->first;
liujisi@google.com33165fe2010-11-02 13:14:58 +0000656 GeneratorContextImpl* directory = iter->second;
kenton@google.com50663222009-12-24 02:12:15 +0000657 if (HasSuffixString(location, "/")) {
658 if (!directory->WriteAllToDisk(location)) {
kenton@google.comf9fa0592010-01-08 05:47:13 +0000659 STLDeleteValues(&output_directories);
kenton@google.com50663222009-12-24 02:12:15 +0000660 return 1;
661 }
662 } else {
kenton@google.comd2fcbba2010-01-04 19:47:18 +0000663 if (HasSuffixString(location, ".jar")) {
664 directory->AddJarManifest();
665 }
666
kenton@google.com50663222009-12-24 02:12:15 +0000667 if (!directory->WriteAllToZip(location)) {
kenton@google.comf9fa0592010-01-08 05:47:13 +0000668 STLDeleteValues(&output_directories);
kenton@google.com50663222009-12-24 02:12:15 +0000669 return 1;
670 }
671 }
kenton@google.com5f121642009-12-23 07:03:06 +0000672 }
673
kenton@google.comf9fa0592010-01-08 05:47:13 +0000674 STLDeleteValues(&output_directories);
675
temporal779f61c2008-08-13 03:15:00 +0000676 if (!descriptor_set_name_.empty()) {
677 if (!WriteDescriptorSet(parsed_files)) {
678 return 1;
679 }
680 }
681
682 if (mode_ == MODE_ENCODE || mode_ == MODE_DECODE) {
683 if (codec_type_.empty()) {
684 // HACK: Define an EmptyMessage type to use for decoding.
685 DescriptorPool pool;
686 FileDescriptorProto file;
687 file.set_name("empty_message.proto");
688 file.add_message_type()->set_name("EmptyMessage");
689 GOOGLE_CHECK(pool.BuildFile(file) != NULL);
690 codec_type_ = "EmptyMessage";
691 if (!EncodeOrDecode(&pool)) {
692 return 1;
693 }
694 } else {
695 if (!EncodeOrDecode(importer.pool())) {
696 return 1;
temporal40ee5512008-07-10 02:12:20 +0000697 }
698 }
699 }
700
701 return 0;
702}
703
704void CommandLineInterface::Clear() {
temporal779f61c2008-08-13 03:15:00 +0000705 // Clear all members that are set by Run(). Note that we must not clear
706 // members which are set by other methods before Run() is called.
707 executable_name_.clear();
temporal40ee5512008-07-10 02:12:20 +0000708 proto_path_.clear();
709 input_files_.clear();
710 output_directives_.clear();
temporal779f61c2008-08-13 03:15:00 +0000711 codec_type_.clear();
712 descriptor_set_name_.clear();
713
714 mode_ = MODE_COMPILE;
715 imports_in_descriptor_set_ = false;
716 disallow_services_ = false;
temporal40ee5512008-07-10 02:12:20 +0000717}
718
719bool CommandLineInterface::MakeInputsBeProtoPathRelative(
720 DiskSourceTree* source_tree) {
721 for (int i = 0; i < input_files_.size(); i++) {
722 string virtual_file, shadowing_disk_file;
723 switch (source_tree->DiskFileToVirtualFile(
724 input_files_[i], &virtual_file, &shadowing_disk_file)) {
725 case DiskSourceTree::SUCCESS:
726 input_files_[i] = virtual_file;
727 break;
728 case DiskSourceTree::SHADOWED:
729 cerr << input_files_[i] << ": Input is shadowed in the --proto_path "
730 "by \"" << shadowing_disk_file << "\". Either use the latter "
731 "file as your input or reorder the --proto_path so that the "
732 "former file's location comes first." << endl;
733 return false;
734 case DiskSourceTree::CANNOT_OPEN:
735 cerr << input_files_[i] << ": " << strerror(errno) << endl;
736 return false;
737 case DiskSourceTree::NO_MAPPING:
738 // First check if the file exists at all.
739 if (access(input_files_[i].c_str(), F_OK) < 0) {
740 // File does not even exist.
741 cerr << input_files_[i] << ": " << strerror(ENOENT) << endl;
742 } else {
743 cerr << input_files_[i] << ": File does not reside within any path "
744 "specified using --proto_path (or -I). You must specify a "
kenton@google.com477f7992009-10-07 21:38:11 +0000745 "--proto_path which encompasses this file. Note that the "
746 "proto_path must be an exact prefix of the .proto file "
747 "names -- protoc is too dumb to figure out when two paths "
748 "(e.g. absolute and relative) are equivalent (it's harder "
749 "than you think)." << endl;
temporal40ee5512008-07-10 02:12:20 +0000750 }
751 return false;
752 }
753 }
754
755 return true;
756}
757
758bool CommandLineInterface::ParseArguments(int argc, const char* const argv[]) {
759 executable_name_ = argv[0];
760
761 // Iterate through all arguments and parse them.
762 for (int i = 1; i < argc; i++) {
763 string name, value;
764
765 if (ParseArgument(argv[i], &name, &value)) {
temporal779f61c2008-08-13 03:15:00 +0000766 // Returned true => Use the next argument as the flag value.
temporal40ee5512008-07-10 02:12:20 +0000767 if (i + 1 == argc || argv[i+1][0] == '-') {
768 cerr << "Missing value for flag: " << name << endl;
temporal779f61c2008-08-13 03:15:00 +0000769 if (name == "--decode") {
770 cerr << "To decode an unknown message, use --decode_raw." << endl;
771 }
temporal40ee5512008-07-10 02:12:20 +0000772 return false;
773 } else {
774 ++i;
775 value = argv[i];
776 }
777 }
778
779 if (!InterpretArgument(name, value)) return false;
780 }
781
782 // If no --proto_path was given, use the current working directory.
783 if (proto_path_.empty()) {
784 proto_path_.push_back(make_pair("", "."));
785 }
786
787 // Check some errror cases.
temporal779f61c2008-08-13 03:15:00 +0000788 bool decoding_raw = (mode_ == MODE_DECODE) && codec_type_.empty();
789 if (decoding_raw && !input_files_.empty()) {
790 cerr << "When using --decode_raw, no input files should be given." << endl;
791 return false;
792 } else if (!decoding_raw && input_files_.empty()) {
temporal40ee5512008-07-10 02:12:20 +0000793 cerr << "Missing input file." << endl;
794 return false;
795 }
temporal779f61c2008-08-13 03:15:00 +0000796 if (mode_ == MODE_COMPILE && output_directives_.empty() &&
797 descriptor_set_name_.empty()) {
temporal40ee5512008-07-10 02:12:20 +0000798 cerr << "Missing output directives." << endl;
799 return false;
800 }
temporal779f61c2008-08-13 03:15:00 +0000801 if (imports_in_descriptor_set_ && descriptor_set_name_.empty()) {
802 cerr << "--include_imports only makes sense when combined with "
kenton@google.comd0580ea2008-11-07 02:07:18 +0000803 "--descriptor_set_out." << endl;
temporal779f61c2008-08-13 03:15:00 +0000804 }
temporal40ee5512008-07-10 02:12:20 +0000805
806 return true;
807}
808
809bool CommandLineInterface::ParseArgument(const char* arg,
810 string* name, string* value) {
811 bool parsed_value = false;
812
813 if (arg[0] != '-') {
814 // Not a flag.
815 name->clear();
816 parsed_value = true;
817 *value = arg;
818 } else if (arg[1] == '-') {
819 // Two dashes: Multi-character name, with '=' separating name and
820 // value.
821 const char* equals_pos = strchr(arg, '=');
822 if (equals_pos != NULL) {
823 *name = string(arg, equals_pos - arg);
824 *value = equals_pos + 1;
825 parsed_value = true;
826 } else {
827 *name = arg;
828 }
829 } else {
830 // One dash: One-character name, all subsequent characters are the
831 // value.
832 if (arg[1] == '\0') {
833 // arg is just "-". We treat this as an input file, except that at
834 // present this will just lead to a "file not found" error.
835 name->clear();
836 *value = arg;
837 parsed_value = true;
838 } else {
839 *name = string(arg, 2);
840 *value = arg + 2;
841 parsed_value = !value->empty();
842 }
843 }
844
845 // Need to return true iff the next arg should be used as the value for this
846 // one, false otherwise.
847
848 if (parsed_value) {
849 // We already parsed a value for this flag.
850 return false;
851 }
852
853 if (*name == "-h" || *name == "--help" ||
854 *name == "--disallow_services" ||
temporal779f61c2008-08-13 03:15:00 +0000855 *name == "--include_imports" ||
856 *name == "--version" ||
857 *name == "--decode_raw") {
temporal40ee5512008-07-10 02:12:20 +0000858 // HACK: These are the only flags that don't take a value.
859 // They probably should not be hard-coded like this but for now it's
860 // not worth doing better.
861 return false;
862 }
863
864 // Next argument is the flag value.
865 return true;
866}
867
868bool CommandLineInterface::InterpretArgument(const string& name,
869 const string& value) {
870 if (name.empty()) {
871 // Not a flag. Just a filename.
872 if (value.empty()) {
873 cerr << "You seem to have passed an empty string as one of the "
874 "arguments to " << executable_name_ << ". This is actually "
875 "sort of hard to do. Congrats. Unfortunately it is not valid "
876 "input so the program is going to die now." << endl;
877 return false;
878 }
879
880 input_files_.push_back(value);
881
882 } else if (name == "-I" || name == "--proto_path") {
883 // Java's -classpath (and some other languages) delimits path components
884 // with colons. Let's accept that syntax too just to make things more
885 // intuitive.
886 vector<string> parts;
887 SplitStringUsing(value, kPathSeparator, &parts);
888
889 for (int i = 0; i < parts.size(); i++) {
890 string virtual_path;
891 string disk_path;
892
893 int equals_pos = parts[i].find_first_of('=');
894 if (equals_pos == string::npos) {
895 virtual_path = "";
896 disk_path = parts[i];
897 } else {
898 virtual_path = parts[i].substr(0, equals_pos);
899 disk_path = parts[i].substr(equals_pos + 1);
900 }
901
902 if (disk_path.empty()) {
903 cerr << "--proto_path passed empty directory name. (Use \".\" for "
904 "current directory.)" << endl;
905 return false;
906 }
907
908 // Make sure disk path exists, warn otherwise.
909 if (access(disk_path.c_str(), F_OK) < 0) {
910 cerr << disk_path << ": warning: directory does not exist." << endl;
911 }
912
913 proto_path_.push_back(make_pair(virtual_path, disk_path));
914 }
915
temporal779f61c2008-08-13 03:15:00 +0000916 } else if (name == "-o" || name == "--descriptor_set_out") {
917 if (!descriptor_set_name_.empty()) {
918 cerr << name << " may only be passed once." << endl;
919 return false;
920 }
921 if (value.empty()) {
922 cerr << name << " requires a non-empty value." << endl;
923 return false;
924 }
925 if (mode_ != MODE_COMPILE) {
926 cerr << "Cannot use --encode or --decode and generate descriptors at the "
927 "same time." << endl;
928 return false;
929 }
930 descriptor_set_name_ = value;
931
932 } else if (name == "--include_imports") {
933 if (imports_in_descriptor_set_) {
934 cerr << name << " may only be passed once." << endl;
935 return false;
936 }
937 imports_in_descriptor_set_ = true;
938
temporal40ee5512008-07-10 02:12:20 +0000939 } else if (name == "-h" || name == "--help") {
940 PrintHelpText();
941 return false; // Exit without running compiler.
942
943 } else if (name == "--version") {
944 if (!version_info_.empty()) {
945 cout << version_info_ << endl;
946 }
947 cout << "libprotoc "
948 << protobuf::internal::VersionString(GOOGLE_PROTOBUF_VERSION)
949 << endl;
950 return false; // Exit without running compiler.
951
952 } else if (name == "--disallow_services") {
953 disallow_services_ = true;
954
temporal779f61c2008-08-13 03:15:00 +0000955 } else if (name == "--encode" || name == "--decode" ||
956 name == "--decode_raw") {
957 if (mode_ != MODE_COMPILE) {
958 cerr << "Only one of --encode and --decode can be specified." << endl;
959 return false;
960 }
961 if (!output_directives_.empty() || !descriptor_set_name_.empty()) {
962 cerr << "Cannot use " << name
963 << " and generate code or descriptors at the same time." << endl;
964 return false;
965 }
966
967 mode_ = (name == "--encode") ? MODE_ENCODE : MODE_DECODE;
968
969 if (value.empty() && name != "--decode_raw") {
970 cerr << "Type name for " << name << " cannot be blank." << endl;
971 if (name == "--decode") {
972 cerr << "To decode an unknown message, use --decode_raw." << endl;
973 }
974 return false;
975 } else if (!value.empty() && name == "--decode_raw") {
976 cerr << "--decode_raw does not take a parameter." << endl;
977 return false;
978 }
979
980 codec_type_ = value;
981
kenton@google.comf663b162009-04-15 19:50:54 +0000982 } else if (name == "--error_format") {
983 if (value == "gcc") {
984 error_format_ = ERROR_FORMAT_GCC;
985 } else if (value == "msvs") {
986 error_format_ = ERROR_FORMAT_MSVS;
987 } else {
988 cerr << "Unknown error format: " << value << endl;
989 return false;
990 }
991
kenton@google.comfccb1462009-12-18 02:11:36 +0000992 } else if (name == "--plugin") {
993 if (plugin_prefix_.empty()) {
994 cerr << "This compiler does not support plugins." << endl;
995 return false;
996 }
997
998 string name;
999 string path;
1000
1001 string::size_type equals_pos = value.find_first_of('=');
1002 if (equals_pos == string::npos) {
1003 // Use the basename of the file.
1004 string::size_type slash_pos = value.find_last_of('/');
1005 if (slash_pos == string::npos) {
1006 name = value;
1007 } else {
1008 name = value.substr(slash_pos + 1);
1009 }
1010 path = value;
1011 } else {
1012 name = value.substr(0, equals_pos);
1013 path = value.substr(equals_pos + 1);
1014 }
1015
1016 plugins_[name] = path;
1017
temporal40ee5512008-07-10 02:12:20 +00001018 } else {
1019 // Some other flag. Look it up in the generators list.
kenton@google.comfccb1462009-12-18 02:11:36 +00001020 const GeneratorInfo* generator_info = FindOrNull(generators_, name);
1021 if (generator_info == NULL &&
1022 (plugin_prefix_.empty() || !HasSuffixString(name, "_out"))) {
temporal40ee5512008-07-10 02:12:20 +00001023 cerr << "Unknown flag: " << name << endl;
1024 return false;
1025 }
1026
1027 // It's an output flag. Add it to the output directives.
temporal779f61c2008-08-13 03:15:00 +00001028 if (mode_ != MODE_COMPILE) {
1029 cerr << "Cannot use --encode or --decode and generate code at the "
1030 "same time." << endl;
1031 return false;
1032 }
1033
temporal40ee5512008-07-10 02:12:20 +00001034 OutputDirective directive;
1035 directive.name = name;
kenton@google.comfccb1462009-12-18 02:11:36 +00001036 if (generator_info == NULL) {
1037 directive.generator = NULL;
1038 } else {
1039 directive.generator = generator_info->generator;
1040 }
temporal40ee5512008-07-10 02:12:20 +00001041
1042 // Split value at ':' to separate the generator parameter from the
temporalcc930432008-07-21 20:28:30 +00001043 // filename. However, avoid doing this if the colon is part of a valid
1044 // Windows-style absolute path.
1045 string::size_type colon_pos = value.find_first_of(':');
1046 if (colon_pos == string::npos || IsWindowsAbsolutePath(value)) {
1047 directive.output_location = value;
temporal40ee5512008-07-10 02:12:20 +00001048 } else {
temporalcc930432008-07-21 20:28:30 +00001049 directive.parameter = value.substr(0, colon_pos);
1050 directive.output_location = value.substr(colon_pos + 1);
temporal40ee5512008-07-10 02:12:20 +00001051 }
1052
1053 output_directives_.push_back(directive);
1054 }
1055
1056 return true;
1057}
1058
1059void CommandLineInterface::PrintHelpText() {
1060 // Sorry for indentation here; line wrapping would be uglier.
1061 cerr <<
temporal779f61c2008-08-13 03:15:00 +00001062"Usage: " << executable_name_ << " [OPTION] PROTO_FILES\n"
1063"Parse PROTO_FILES and generate output based on the options given:\n"
temporal40ee5512008-07-10 02:12:20 +00001064" -IPATH, --proto_path=PATH Specify the directory in which to search for\n"
1065" imports. May be specified multiple times;\n"
1066" directories will be searched in order. If not\n"
1067" given, the current working directory is used.\n"
1068" --version Show version info and exit.\n"
temporal779f61c2008-08-13 03:15:00 +00001069" -h, --help Show this text and exit.\n"
1070" --encode=MESSAGE_TYPE Read a text-format message of the given type\n"
1071" from standard input and write it in binary\n"
1072" to standard output. The message type must\n"
1073" be defined in PROTO_FILES or their imports.\n"
1074" --decode=MESSAGE_TYPE Read a binary message of the given type from\n"
1075" standard input and write it in text format\n"
1076" to standard output. The message type must\n"
1077" be defined in PROTO_FILES or their imports.\n"
1078" --decode_raw Read an arbitrary protocol message from\n"
1079" standard input and write the raw tag/value\n"
1080" pairs in text format to standard output. No\n"
1081" PROTO_FILES should be given when using this\n"
1082" flag.\n"
1083" -oFILE, Writes a FileDescriptorSet (a protocol buffer,\n"
1084" --descriptor_set_out=FILE defined in descriptor.proto) containing all of\n"
1085" the input files to FILE.\n"
1086" --include_imports When using --descriptor_set_out, also include\n"
1087" all dependencies of the input files in the\n"
kenton@google.comf663b162009-04-15 19:50:54 +00001088" set, so that the set is self-contained.\n"
1089" --error_format=FORMAT Set the format in which to print errors.\n"
1090" FORMAT may be 'gcc' (the default) or 'msvs'\n"
1091" (Microsoft Visual Studio format)." << endl;
kenton@google.comfccb1462009-12-18 02:11:36 +00001092 if (!plugin_prefix_.empty()) {
1093 cerr <<
1094" --plugin=EXECUTABLE Specifies a plugin executable to use.\n"
1095" Normally, protoc searches the PATH for\n"
1096" plugins, but you may specify additional\n"
1097" executables not in the path using this flag.\n"
1098" Additionally, EXECUTABLE may be of the form\n"
1099" NAME=PATH, in which case the given plugin name\n"
1100" is mapped to the given executable even if\n"
1101" the executable's own name differs." << endl;
1102 }
temporal40ee5512008-07-10 02:12:20 +00001103
1104 for (GeneratorMap::iterator iter = generators_.begin();
1105 iter != generators_.end(); ++iter) {
1106 // FIXME(kenton): If the text is long enough it will wrap, which is ugly,
1107 // but fixing this nicely (e.g. splitting on spaces) is probably more
1108 // trouble than it's worth.
1109 cerr << " " << iter->first << "=OUT_DIR "
1110 << string(19 - iter->first.size(), ' ') // Spaces for alignment.
1111 << iter->second.help_text << endl;
1112 }
1113}
1114
1115bool CommandLineInterface::GenerateOutput(
kenton@google.comfccb1462009-12-18 02:11:36 +00001116 const vector<const FileDescriptor*>& parsed_files,
kenton@google.com5f121642009-12-23 07:03:06 +00001117 const OutputDirective& output_directive,
liujisi@google.com33165fe2010-11-02 13:14:58 +00001118 GeneratorContext* generator_context) {
temporal40ee5512008-07-10 02:12:20 +00001119 // Call the generator.
1120 string error;
kenton@google.comfccb1462009-12-18 02:11:36 +00001121 if (output_directive.generator == NULL) {
1122 // This is a plugin.
1123 GOOGLE_CHECK(HasPrefixString(output_directive.name, "--") &&
1124 HasSuffixString(output_directive.name, "_out"))
1125 << "Bad name for plugin generator: " << output_directive.name;
1126
1127 // Strip the "--" and "_out" and add the plugin prefix.
1128 string plugin_name = plugin_prefix_ + "gen-" +
1129 output_directive.name.substr(2, output_directive.name.size() - 6);
1130
1131 if (!GeneratePluginOutput(parsed_files, plugin_name,
1132 output_directive.parameter,
liujisi@google.com33165fe2010-11-02 13:14:58 +00001133 generator_context, &error)) {
kenton@google.comfccb1462009-12-18 02:11:36 +00001134 cerr << output_directive.name << ": " << error << endl;
1135 return false;
1136 }
1137 } else {
1138 // Regular generator.
1139 for (int i = 0; i < parsed_files.size(); i++) {
1140 if (!output_directive.generator->Generate(
1141 parsed_files[i], output_directive.parameter,
liujisi@google.com33165fe2010-11-02 13:14:58 +00001142 generator_context, &error)) {
kenton@google.comfccb1462009-12-18 02:11:36 +00001143 // Generator returned an error.
1144 cerr << output_directive.name << ": " << parsed_files[i]->name() << ": "
1145 << error << endl;
1146 return false;
1147 }
1148 }
temporal40ee5512008-07-10 02:12:20 +00001149 }
1150
temporal40ee5512008-07-10 02:12:20 +00001151 return true;
1152}
1153
kenton@google.comfccb1462009-12-18 02:11:36 +00001154bool CommandLineInterface::GeneratePluginOutput(
1155 const vector<const FileDescriptor*>& parsed_files,
1156 const string& plugin_name,
1157 const string& parameter,
liujisi@google.com33165fe2010-11-02 13:14:58 +00001158 GeneratorContext* generator_context,
kenton@google.comfccb1462009-12-18 02:11:36 +00001159 string* error) {
1160 CodeGeneratorRequest request;
1161 CodeGeneratorResponse response;
1162
1163 // Build the request.
1164 if (!parameter.empty()) {
1165 request.set_parameter(parameter);
1166 }
1167
1168 set<const FileDescriptor*> already_seen;
1169 for (int i = 0; i < parsed_files.size(); i++) {
1170 request.add_file_to_generate(parsed_files[i]->name());
1171 GetTransitiveDependencies(parsed_files[i], &already_seen,
1172 request.mutable_proto_file());
1173 }
1174
1175 // Invoke the plugin.
1176 Subprocess subprocess;
1177
1178 if (plugins_.count(plugin_name) > 0) {
1179 subprocess.Start(plugins_[plugin_name], Subprocess::EXACT_NAME);
1180 } else {
1181 subprocess.Start(plugin_name, Subprocess::SEARCH_PATH);
1182 }
1183
1184 string communicate_error;
1185 if (!subprocess.Communicate(request, &response, &communicate_error)) {
1186 *error = strings::Substitute("$0: $1", plugin_name, communicate_error);
1187 return false;
1188 }
1189
1190 // Write the files. We do this even if there was a generator error in order
1191 // to match the behavior of a compiled-in generator.
1192 scoped_ptr<io::ZeroCopyOutputStream> current_output;
1193 for (int i = 0; i < response.file_size(); i++) {
1194 const CodeGeneratorResponse::File& output_file = response.file(i);
1195
1196 if (!output_file.insertion_point().empty()) {
1197 // Open a file for insert.
1198 // We reset current_output to NULL first so that the old file is closed
1199 // before the new one is opened.
1200 current_output.reset();
liujisi@google.com33165fe2010-11-02 13:14:58 +00001201 current_output.reset(generator_context->OpenForInsert(
kenton@google.comfccb1462009-12-18 02:11:36 +00001202 output_file.name(), output_file.insertion_point()));
1203 } else if (!output_file.name().empty()) {
1204 // Starting a new file. Open it.
1205 // We reset current_output to NULL first so that the old file is closed
1206 // before the new one is opened.
1207 current_output.reset();
liujisi@google.com33165fe2010-11-02 13:14:58 +00001208 current_output.reset(generator_context->Open(output_file.name()));
kenton@google.comfccb1462009-12-18 02:11:36 +00001209 } else if (current_output == NULL) {
1210 *error = strings::Substitute(
1211 "$0: First file chunk returned by plugin did not specify a file name.",
1212 plugin_name);
1213 return false;
1214 }
1215
1216 // Use CodedOutputStream for convenience; otherwise we'd need to provide
1217 // our own buffer-copying loop.
1218 io::CodedOutputStream writer(current_output.get());
1219 writer.WriteString(output_file.content());
1220 }
1221
1222 // Check for errors.
1223 if (!response.error().empty()) {
1224 // Generator returned an error.
1225 *error = response.error();
1226 return false;
1227 }
1228
1229 return true;
1230}
1231
temporal779f61c2008-08-13 03:15:00 +00001232bool CommandLineInterface::EncodeOrDecode(const DescriptorPool* pool) {
1233 // Look up the type.
1234 const Descriptor* type = pool->FindMessageTypeByName(codec_type_);
1235 if (type == NULL) {
1236 cerr << "Type not defined: " << codec_type_ << endl;
1237 return false;
1238 }
1239
1240 DynamicMessageFactory dynamic_factory(pool);
1241 scoped_ptr<Message> message(dynamic_factory.GetPrototype(type)->New());
1242
1243 if (mode_ == MODE_ENCODE) {
1244 SetFdToTextMode(STDIN_FILENO);
1245 SetFdToBinaryMode(STDOUT_FILENO);
1246 } else {
1247 SetFdToBinaryMode(STDIN_FILENO);
1248 SetFdToTextMode(STDOUT_FILENO);
1249 }
1250
1251 io::FileInputStream in(STDIN_FILENO);
1252 io::FileOutputStream out(STDOUT_FILENO);
1253
1254 if (mode_ == MODE_ENCODE) {
1255 // Input is text.
kenton@google.comf663b162009-04-15 19:50:54 +00001256 ErrorPrinter error_collector(error_format_);
temporal779f61c2008-08-13 03:15:00 +00001257 TextFormat::Parser parser;
1258 parser.RecordErrorsTo(&error_collector);
1259 parser.AllowPartialMessage(true);
1260
1261 if (!parser.Parse(&in, message.get())) {
1262 cerr << "Failed to parse input." << endl;
1263 return false;
1264 }
1265 } else {
1266 // Input is binary.
1267 if (!message->ParsePartialFromZeroCopyStream(&in)) {
1268 cerr << "Failed to parse input." << endl;
1269 return false;
1270 }
1271 }
1272
1273 if (!message->IsInitialized()) {
1274 cerr << "warning: Input message is missing required fields: "
1275 << message->InitializationErrorString() << endl;
1276 }
1277
1278 if (mode_ == MODE_ENCODE) {
1279 // Output is binary.
1280 if (!message->SerializePartialToZeroCopyStream(&out)) {
1281 cerr << "output: I/O error." << endl;
1282 return false;
1283 }
1284 } else {
1285 // Output is text.
1286 if (!TextFormat::Print(*message, &out)) {
1287 cerr << "output: I/O error." << endl;
1288 return false;
1289 }
1290 }
1291
1292 return true;
1293}
1294
1295bool CommandLineInterface::WriteDescriptorSet(
1296 const vector<const FileDescriptor*> parsed_files) {
1297 FileDescriptorSet file_set;
temporal779f61c2008-08-13 03:15:00 +00001298
kenton@google.comfccb1462009-12-18 02:11:36 +00001299 if (imports_in_descriptor_set_) {
1300 set<const FileDescriptor*> already_seen;
1301 for (int i = 0; i < parsed_files.size(); i++) {
1302 GetTransitiveDependencies(
1303 parsed_files[i], &already_seen, file_set.mutable_file());
1304 }
1305 } else {
1306 for (int i = 0; i < parsed_files.size(); i++) {
1307 parsed_files[i]->CopyTo(file_set.add_file());
temporal779f61c2008-08-13 03:15:00 +00001308 }
1309 }
1310
1311 int fd;
1312 do {
1313 fd = open(descriptor_set_name_.c_str(),
1314 O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
1315 } while (fd < 0 && errno == EINTR);
1316
1317 if (fd < 0) {
1318 perror(descriptor_set_name_.c_str());
1319 return false;
1320 }
1321
1322 io::FileOutputStream out(fd);
1323 if (!file_set.SerializeToZeroCopyStream(&out)) {
1324 cerr << descriptor_set_name_ << ": " << strerror(out.GetErrno()) << endl;
1325 out.Close();
1326 return false;
1327 }
1328 if (!out.Close()) {
1329 cerr << descriptor_set_name_ << ": " << strerror(out.GetErrno()) << endl;
1330 return false;
1331 }
1332
1333 return true;
1334}
1335
kenton@google.comfccb1462009-12-18 02:11:36 +00001336void CommandLineInterface::GetTransitiveDependencies(
1337 const FileDescriptor* file,
1338 set<const FileDescriptor*>* already_seen,
1339 RepeatedPtrField<FileDescriptorProto>* output) {
1340 if (!already_seen->insert(file).second) {
1341 // Already saw this file. Skip.
1342 return;
1343 }
1344
1345 // Add all dependencies.
1346 for (int i = 0; i < file->dependency_count(); i++) {
1347 GetTransitiveDependencies(file->dependency(i), already_seen, output);
1348 }
1349
1350 // Add this file.
1351 file->CopyTo(output->Add());
1352}
1353
temporal40ee5512008-07-10 02:12:20 +00001354
1355} // namespace compiler
1356} // namespace protobuf
1357} // namespace google