Skip to content

Commit

Permalink
Update README.md, add a couple of more items from May to July 2012 to…
Browse files Browse the repository at this point in the history
… CHANGELOG

RB_ID=94721
  • Loading branch information
Chunyan Song committed Nov 1, 2012
1 parent 8e9220f commit c65ed5b
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 74 deletions.
27 changes: 14 additions & 13 deletions 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.
Expand All @@ -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
Expand Down
215 changes: 159 additions & 56 deletions 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
Expand All @@ -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
Expand All @@ -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:

<dependency>
<groupId>com.twitter</groupId>
<artifactId>scrooge-runtime</artifactId>
<version>3.0.1</version>
</dependency>

SBT users need this:

val scrooge_runtime = "com.twitter.scrooge" % "scrooge-runtime" % "3.0.1"

## Running Scrooge

To get command line help:

Expand All @@ -64,14 +76,34 @@ extra include paths, rebuilding only those files that have changed:
-s
<thrift-file1> [<thrift-file2> ...]


## 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] <files...>

--help
show this help screen
-V | --version
print version and quit
-v | --verbose
log verbose messages about progress
-d <path> | --dest <path>
write generated code to a folder (default: .)
-i <path> | --import-path <path>
path(s) to search for imported thrift files (may be used multiple times)
-n <oldname>=<newname> | --namespace-map <oldname>=<newname>
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 <value> | --language <value>
name of language to generate code in ('Java' and 'Scala' are currently supported)
--finagle
generate finagle classes
--ostrich
generate ostrich server interface
<files...>
thrift files to compile

## SBT Plugin

Expand All @@ -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

Expand All @@ -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
Expand Down
5 changes: 0 additions & 5 deletions 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:
Expand Down

0 comments on commit c65ed5b

Please sign in to comment.