diff --git a/CHANGELOG b/CHANGELOG index 16e44b6fc..49495f1fc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,15 +1,13 @@ 3.0.1 - 10/30/2012 =============================================================================== -Features +Features and bug fixes * Doc comments are included in the generated code. * Generated exception structs now have getMessage() method * Generate header that emits Scrooge version -* Fixed importing woes. You can now import a directory or a Jar/Zip file through - command line argument, which will be stored in a chain of paths maintained by - Scrooge. Then refer to a file using relative path in the thrift "include" - statement. Scrooge will locate the file in the path chain. - This also fixed the bug that couldn't resolve a symbol imported through a - relative path and threw an UndefinedSymbolException +* You can now import a directory or a Jar/Zip file through command line + argument, which will be stored in a chain of paths maintained by Scrooge. + Then refer to a file using relative path in the thrift "include" statement. + Scrooge will locate the file in the path chain. * Introduce a "strict" mode that defaults to on. Unfavored syntax throws an exception when "strict" mode is on and prints a warning when it's off. The strict mode can be disabled by specifying the "--disable-strict" argument. @@ -30,22 +28,25 @@ Features } The "required" and "optional" modifiers in a union type will throw exceptions in strict mode and print warnings in non-strict mode. -* Fixing namespace aliasing bug. * Have a common trait ThriftException for all the thrift exception structs. * Support cross file service inheritance. Now you can do include "foo.thrift" service MyService extends foo.FooService { ... } - -Implementation -* The project structure refactoring: +* Bug fix: It couldn't resolve a symbol imported through a relative path and + threw an UndefinedSymbolException +* Bug fix: namespace aliasing put the parentheses in the wrong place. +* Bug fix: services using binary fields wouldn't compile +* Bug fix: cross-file const referencing didn't work + +Implementation updates +* Project structure: - frontend: Importer and ThriftParser - mustache: everything related to mustache, including template parser, loader and handlebar - ast: Thrift AST definition - backend: code generation include various generators and dictionaries to hydrate Mustache templates. -* For maintainability and easy trouble shooting in the future, define clear and - separate responsibilities of each components: +* Redefine clear and separate responsibilities of each components: - Move ID manipulation(concatenation, case conversion, keyword rewriting etc) to Generator phase. - Utilizing Scala static type checking to enforce scoping correctness by diff --git a/README.md b/README.md index 6d40ba2d0..891d8cf43 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# scrooge +# Scrooge Scrooge is a [thrift](http://thrift.apache.org/) code generator written in -scala, which currently generates code for scala and java. +Scala, which currently generates code for Scala and Java. It's meant to be a replacement for the apache thrift code generator, and generates conforming, compatible binary codecs by building on top of libthrift. -Since scala is API-compatible with java, you can use the apache thrift code -generator to generate java files and use them from within scala, but the -generated code uses java collections and mutable "bean" classes, causing some +Since Scala is API-compatible with Java, you can use the apache thrift code +generator to generate Java files and use them from within Scala, but the +generated code uses Java collections and mutable "bean" classes, causing some annoying boilerplate conversions to be hand-written. This is an attempt to -bypass the problem by generating scala code directly. It also uses scala +bypass the problem by generating Scala code directly. It also uses Scala syntax so the generated code is much more compact. There is a fairly comprehensive set of unit tests, which actually generate @@ -23,16 +23,9 @@ There are two sub-projects: - scrooge-runtime: some base traits used by the generated code -## Building - -To build scrooge, use sbt: - - $ sbt package-dist - - ## Features -- Generates native scala thrift codecs, in immutable and "builder" variants, +- Generates native Scala thrift codecs, in immutable and "builder" variants, using case classes and functions. - Generated code is templated using a mustache variant, making it easy to @@ -42,10 +35,29 @@ To build scrooge, use sbt: generated at the same time. -## Running Scrooge +## Building Scrooge + +To build scrooge, use maven: + + $ mvn clean package + +## Runtime dependency -A starter script is built into `dist/scrooge/scripts`. You can run that or -write your own. +There are a couple of classes needed by the generated code. These have been +moved out of scrooge into a separate jar to keep dependencies small. +Maven users need to add the following to the pom.xml file: + + + com.twitter + scrooge-runtime + 3.0.1 + + +SBT users need this: + + val scrooge_runtime = "com.twitter.scrooge" % "scrooge-runtime" % "3.0.1" + +## Running Scrooge To get command line help: @@ -64,14 +76,34 @@ extra include paths, rebuilding only those files that have changed: -s [ ...] - -## Runtime dependency - -There are a couple of classes needed by the generated code. These have been -moved out of scrooge into a separate jar to keep dependencies small: - - val scrooge_runtime = "com.twitter" % "scrooge-runtime" % "1.0.3" - +A complete command line help menu: + + Usage: scrooge [options] + + --help + show this help screen + -V | --version + print version and quit + -v | --verbose + log verbose messages about progress + -d | --dest + write generated code to a folder (default: .) + -i | --import-path + path(s) to search for imported thrift files (may be used multiple times) + -n = | --namespace-map = + map old namespace to new (may be used multiple times) + --disable-strict + issue warnings on non-severe parse errors instead of aborting + -s | --skip-unchanged + Don't re-generate if the target is newer than the input + -l | --language + name of language to generate code in ('Java' and 'Scala' are currently supported) + --finagle + generate finagle classes + --ostrich + generate ostrich server interface + + thrift files to compile ## SBT Plugin @@ -89,51 +121,122 @@ To use it, add a line like this to your `plugins.sbt` file: ## Finagle integration -If you pass the `--finagle` option to scrooge, it will generate a -[finagle](https://github.com/twitter/finagle) -client and server wrapper class for each thrift service. - -The service wrapper takes a thrift protocol factory (which specifies which -wire protocol to use) and an implementation of the future-based interface: - - class FinagledService( - iface: FutureIface, - val protocolFactory: TProtocolFactory - ) extends FinagleThriftService - -Here's an example of creating a finagle service using this class, assuming -your thrift service in named `AwesomeService`: - +You can generate [finagle](https://github.com/twitter/finagle) binding code +by passing the `--finagle` option to scrooge. For each thrift service, Scrooge +will generate a wrapper class that builds Finagle services both on the server +and client sides. + +Here's an example, assuming your thrift service is + + service BinaryService { + binary fetchBlob(1: i64 id) + } + +Scrooge generates the following wrapper class: + + import com.twitter.finagle.Service + import com.twitter.finagle.thrift.{ThriftClientRequest, + ThriftServerFramedCodec, ThriftClientFramedCodec} + object BinaryService { + // vanilla interface + trait Iface { + def fetchBlob(id: Long): ByteBuffer + } + + // furture-based Finagle interface + trait FutureIface { + def fetchBlob(id: Long): Future[ByteBuffer] + } + + /* + The server side service wrapper takes a thrift protocol factory (to + specify which wire protocol to use) and an implementation of + FutureIface + */ + class FinagledService( + iface: FutureIface, + val protocolFactory: TProtocolFactory + ) extends Service[Array[Byte], Array[Byte]] + + /* + The client wrapper implements FutureIface. + */ + class FinagledClient( + val service: Service[ThriftClientRequest, Array[Byte]], + val protocolFactory: TProtocolFactory = new TBinaryProtocol.Factory, + override val serviceName: Option[String] = None, + stats: StatsReceiver = NullStatsReceiver + ) extends FutureIface { + /* + The method call encodes method name along with arguments in + ThriftClientRequest and sends to the server, then decodes server + response to reconstruct the return value. + */ + def fetchBlob(id: Long): Future[ByteBuffer] + } + } + +To create a server, you need to provide an implementation of FutureIface, +and use it with FinagledService: + + // provide an implementation of the future-base service interface + class MyImpl extends BinaryService.FutureIface { + ... + } + val protocol = new TBinaryProtocol.Factory() + val serverService = new BinaryService.FinagledService(new MyImpl, protocol) val address = new InetSocketAddress(listenAddress, port) var builder = ServerBuilder() .codec(ThriftServerFramedCodec()) - .name("awesome_service") + .name("binary_service") .bindTo(address) - val protocol = new TBinaryProtocol.Factory() - // calling build() in finagle is equivalent to calling start(). - builder.build(new AwesomeService.FinagledService(myService, protocol)) + .build(serverService) -The client wrapper has a more complex interface, but is easy to use: - - class FinagledClient( - val service: FinagleService[ThriftClientRequest, Array[Byte]], - val protocolFactory: TProtocolFactory = new TBinaryProtocol.Factory, - override val serviceName: Option[String] = None, - stats: StatsReceiver = NullStatsReceiver - ) extends FinagleThriftClient with FutureIface - -It implements the future-based interface of the thrift service: +Creating a client is easy, you just need to build a finagle thrift client +service to pass to FinagledClient. val service = ClientBuilder() .hosts(new InetSocketAddress(host, port)) .codec(ThriftClientFramedCodec()) .build() - val client = new AwesomeService.FinagledClient(service) + val client = new BinaryService.FinagledClient(service) In both the server and client cases, you probably want to pass more configuration parameters to finagle, so check the finagle documentation for tweaks once you get things to compile. +## Ostrich Integration +If you pass the "--ostrich" option, Scrooge will generate a convenience +wrapper ThriftServer. Following the BinaryService example: + + import com.twitter.ostrich.admin.Service + object BinaryService { + trait Iface { ... } + trait FutureIface { ... } + trait ThriftServer extends Service with FutureIface { + val thriftPort: Int + val serverName: String + + //You can override serverBuilder to provide additional configuration. + def serverBuilder = ... + + // Ostrich interface implementation is generated. It operates on the server built by serverBuilder. + def start() { ... } + def shutdown() { ... } + } + } + +To use the generated code Ostrich server: + + //First, you need to provide an implementation, as seen previously in the "--finagle" example + class MyImpl extends BinaryService.FutureIface { ... } + val ostrichServer = new MyImpl with ThriftServer { + // server configuration + val thriftPort = .. + val serverName = .. + } + ostrichServer.start() + ## Implementation Semantics @@ -143,7 +246,7 @@ such as serialization, deserialization, and new instance creation, and different implementations do different things (see http://lionet.livejournal.com/66899.html for a good analysis). -Scrooge attempts to be as rigourous as possible in this regard with +Scrooge attempts to be as rigorous as possible in this regard with consistently applied and hopefully easy to understand rules. 1. If neither "required" nor "optional" is declared for a field, it then has diff --git a/scrooge-generator/TODO.md b/scrooge-generator/TODO.md index 4dfa714fa..2eecb88fc 100644 --- a/scrooge-generator/TODO.md +++ b/scrooge-generator/TODO.md @@ -1,12 +1,7 @@ -- enums are not generated. - - handle optional/required [is there really anything to do there?] x generate a struct for each args/return from a method x handle "not present" fields in encoding (needed particularly for return-value structs) -- put constants in an object, not just loose in the file. - - a field can be: - in a struct: