Skip to content

Commit

Permalink
Various fixes to codegen plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
stanley-cheung committed Aug 17, 2018
1 parent 30c30e4 commit b990a53
Show file tree
Hide file tree
Showing 14 changed files with 604 additions and 52 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ bazel-genfiles
bazel-grpc-web
bazel-out
bazel-testlogs
*.o
protoc-gen-*
3 changes: 2 additions & 1 deletion javascript/net/grpc/web/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
# limitations under the License.

CXX = g++
CPPFLAGS += -I/usr/local/include -pthread
CPPFLAGS += -I/usr/local/include \
-I../../../../third_party/grpc/third_party/protobuf/src -pthread
CXXFLAGS += -std=c++11
LDFLAGS += -L/usr/local/lib -lprotoc -lprotobuf -lpthread -ldl

Expand Down
118 changes: 107 additions & 11 deletions javascript/net/grpc/web/grpc_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <google/protobuf/descriptor.h>
#include <google/protobuf/io/printer.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/stubs/strutil.h>

using google::protobuf::Descriptor;
using google::protobuf::FileDescriptor;
Expand All @@ -32,6 +33,10 @@ using google::protobuf::compiler::ParseGeneratorParameter;
using google::protobuf::compiler::PluginMain;
using google::protobuf::io::Printer;
using google::protobuf::io::ZeroCopyOutputStream;
using google::protobuf::HasSuffixString;
using google::protobuf::ReplaceCharacters;
using google::protobuf::StripPrefixString;
using google::protobuf::StripSuffixString;

namespace grpc {
namespace web {
Expand Down Expand Up @@ -90,6 +95,75 @@ string LowercaseFirstLetter(string s) {
return s;
}

// The following function was copied from
// google/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc
string StripProto(const string& filename) {
if (HasSuffixString(filename, ".protodevel")) {
return StripSuffixString(filename, ".protodevel");
} else {
return StripSuffixString(filename, ".proto");
}
}

// The following 3 functions were copied from
// google/protobuf/src/google/protobuf/compiler/js/js_generator.cc

// Returns the name of the message with a leading dot and taking into account
// nesting, for example ".OuterMessage.InnerMessage", or returns empty if
// descriptor is null. This function does not handle namespacing, only message
// nesting.
string GetNestedMessageName(const Descriptor* descriptor) {
if (descriptor == NULL) {
return "";
}
string result = StripPrefixString(descriptor->full_name(),
descriptor->file()->package());
// Add a leading dot if one is not already present.
if (!result.empty() && result[0] != '.') {
result = "." + result;
}
return result;
}

// Given a filename like foo/bar/baz.proto, returns the root directory
// path ../../
string GetRootPath(const string& from_filename, const string& to_filename) {
if (to_filename.find("google/protobuf") == 0) {
// Well-known types (.proto files in the google/protobuf directory) are
// assumed to come from the 'google-protobuf' npm package. We may want to
// generalize this exception later by letting others put generated code in
// their own npm packages.
return "google-protobuf/";
}

size_t slashes = std::count(from_filename.begin(), from_filename.end(), '/');
if (slashes == 0) {
return "./";
}
string result = "";
for (size_t i = 0; i < slashes; i++) {
result += "../";
}
return result;
}

// Returns the alias we assign to the module of the given .proto filename
// when importing.
string ModuleAlias(const string& filename) {
// This scheme could technically cause problems if a file includes any 2 of:
// foo/bar_baz.proto
// foo_bar_baz.proto
// foo_bar/baz.proto
//
// We'll worry about this problem if/when we actually see it. This name isn't
// exposed to users so we can change it later if we need to.
string basename = StripProto(filename);
ReplaceCharacters(&basename, "-", '$');
ReplaceCharacters(&basename, "/", '_');
ReplaceCharacters(&basename, ".", '_');
return basename + "_pb";
}

/* Finds all message types used in all services in the file, and returns them
* as a map of fully qualified message type name to message descriptor */
std::map<string, const Descriptor*> GetAllMessages(const FileDescriptor* file) {
Expand Down Expand Up @@ -126,14 +200,20 @@ void PrintMessagesDeps(Printer* printer, const FileDescriptor* file) {
}

void PrintCommonJsMessagesDeps(Printer* printer, const FileDescriptor* file) {
std::map<string, const Descriptor*> messages = GetAllMessages(file);
std::map<string, string> vars;

for (int i = 0; i < file->dependency_count(); i++) {
const string& name = file->dependency(i)->name();
vars["alias"] = ModuleAlias(name);
vars["dep_filename"] = GetRootPath(file->name(), name) + StripProto(name);
// we need to give each cross-file import an alias
printer->Print(
vars,
"\nvar $alias$ = require('$dep_filename$_pb.js')\n");
}

string package = file->package();
string filename = file->name();
// Remove .proto extension
filename = filename.substr(0, filename.size() - 6);
vars["package_name"] = package;
vars["filename"] = filename;

printer->Print(vars, "const proto = {};\n");
if (!package.empty()) {
Expand All @@ -149,6 +229,14 @@ void PrintCommonJsMessagesDeps(Printer* printer, const FileDescriptor* file) {
}
}

// need to import the messages from our own file
string filename = StripProto(file->name());
size_t last_slash = filename.find_last_of('/');
if (last_slash != string::npos) {
filename = filename.substr(last_slash + 1);
}
vars["filename"] = filename;

printer->Print(
vars,
"proto.$package_name$ = require('./$filename$_pb.js');\n\n");
Expand Down Expand Up @@ -221,7 +309,7 @@ void PrintMethodInfo(Printer* printer, std::map<string, string> vars) {
printer->Indent();
printer->Print(
vars,
"proto.$out$,\n"
"$out_type$,\n"
"/** @param {!proto.$in$} request */\n"
"function(request) {\n");
printer->Print(
Expand All @@ -230,7 +318,7 @@ void PrintMethodInfo(Printer* printer, std::map<string, string> vars) {
printer->Print("},\n");
printer->Print(
vars,
("proto.$out$." + GetDeserializeMethodName(vars["mode"]) +
("$out_type$." + GetDeserializeMethodName(vars["mode"]) +
"\n").c_str());
printer->Outdent();
printer->Print(
Expand Down Expand Up @@ -349,8 +437,7 @@ class GrpcCodeGenerator : public CodeGenerator {
}

if (file_name.empty()) {
*error = "options: out is required";
return false;
file_name = StripProto(file->name()) + "_grpc_pb.js";
}
if (mode.empty()) {
*error = "options: mode is required";
Expand Down Expand Up @@ -398,8 +485,8 @@ class GrpcCodeGenerator : public CodeGenerator {
switch (import_style) {
case ImportStyle::CLOSURE:
printer.Print(
vars,
"goog.provide('proto.$package_dot$$service_name$Client');\n");
vars,
"goog.provide('proto.$package_dot$$service_name$Client');\n");
break;
case ImportStyle::COMMONJS:
break;
Expand Down Expand Up @@ -439,6 +526,15 @@ class GrpcCodeGenerator : public CodeGenerator {
vars["method_name"] = method->name();
vars["in"] = method->input_type()->full_name();
vars["out"] = method->output_type()->full_name();
if (import_style == ImportStyle::COMMONJS &&
method->output_type()->file() != file) {
// Cross-file ref in CommonJS needs to use the module alias instead
// of the global name.
vars["out_type"] = ModuleAlias(method->output_type()->file()->name())
+ GetNestedMessageName(method->output_type());
} else {
vars["out_type"] = "proto."+method->output_type()->full_name();
}

// Client streaming is not supported yet
if (!method->client_streaming()) {
Expand Down
11 changes: 9 additions & 2 deletions packages/grpc-web/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
{
"name": "grpc-web",
"version": "0.3.0",
"description": "gRPC web runtime",
"author": "Google Inc.",
"description": "gRPC-Web JS Client Runtime Library",
"homepage": "https://grpc.io/",
"repository": {
"type": "git",
"url": "https://github.com/grpc/grpc-web.git"
},
"bugs": "https://github.com/grpc/grpc-web/issues",
"main": "index.js",
"scripts": {
"build": "node scripts/build.js",
"prepare": "npm run build && require-self",
"prepublishOnly": "npm run build",
"test": "mocha"
"test": "mocha --timeout 5000 \"./test/**/*_test.js\""
},
"license": "Apache-2.0",
"devDependencies": {
Expand Down
18 changes: 18 additions & 0 deletions packages/grpc-web/scripts/build.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/**
*
* Copyright 2018 Google LLC
*
* 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
*
* https://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.
*
*/

const path = require("path");
const {exec} = require("child_process");

Expand Down
34 changes: 34 additions & 0 deletions packages/grpc-web/test/closure_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
*
* Copyright 2018 Google LLC
*
* 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
*
* https://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.
*
*/

goog.provide('proto.grpc.gateway.testing.EchoAppClient');

goog.require('proto.grpc.gateway.testing.EchoServiceClient');
goog.require('proto.grpc.gateway.testing.EchoRequest');


proto.grpc.gateway.testing.EchoAppClient = function() {
this.client =
new proto.grpc.gateway.testing.EchoServiceClient('MyHostname', null, null);
}

proto.grpc.gateway.testing.EchoAppClient.prototype.echo = function(msg, cb) {
var request = new proto.grpc.gateway.testing.EchoRequest();
request.setMessage(msg);
this.client.echo(request, {}, cb);
}
37 changes: 37 additions & 0 deletions packages/grpc-web/test/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
*
* Copyright 2018 Google LLC
*
* 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
*
* https://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.
*
*/

const fs = require('fs');
const GENERATED_CODE_PATH = './generated';

var removeDirectory = function(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(function(file, index) {
var curPath = path + "/" + file;
if (fs.lstatSync(curPath).isDirectory()) {
removeDirectory(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}

exports.removeDirectory = removeDirectory;
exports.GENERATED_CODE_PATH = GENERATED_CODE_PATH;
Loading

0 comments on commit b990a53

Please sign in to comment.