Skip to content

Commit

Permalink
jsii-ruby: low-level ruby client for jsii-runtime (#143)
Browse files Browse the repository at this point in the history
Implement the low-level API for the jsii-ruby runtime client.

This layer starts the jsii-runtime child node.js process and allows
interacting it via the stdin/stdout request/response interface.

Implemented as a leaky abstraction, so there is no need to
implement glue in ruby for each kernel API. Sync overrides are
implemented as well.

Unit test verifies a common scenario, and specifically verifies
synchronous and async virtual overrides.

Added minimal "no op" support for ruby pacmak which simply
emits the tarballs. This is needed for the initial set of tests.

"npm run build" will:

- Generate jsii-calc via pacmak (just tarballs for now)
- "bundle install" gems
- Run "robucop" (ruby linter)
- Run "gem build" which builds the ruby gem
- Runs unit tests

Related to #144
  • Loading branch information
Elad Ben-Israel authored Aug 6, 2018
1 parent 4e70209 commit ebdc6e8
Show file tree
Hide file tree
Showing 12 changed files with 411 additions and 0 deletions.
40 changes: 40 additions & 0 deletions packages/jsii-pacmak/lib/targets/ruby.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as spec from 'jsii-spec';
import { Generator } from '../generator';
import { Target, TargetOptions } from '../target';

export default class Ruby extends Target {
protected readonly generator = new PackOnly();

constructor(options: TargetOptions) {
super(options);
}

public build(sourceDir: string, outDir: string) {
// TODO: "gem build"
return this.copyFiles(sourceDir, outDir);
}
}

// ##################
// # CODE GENERATOR #
// ##################

class PackOnly extends Generator {

protected getAssemblyOutputDir(_mod: spec.Assembly) {
return '.';
}

protected onBeginInterface(_ifc: spec.InterfaceType) { return; }
protected onEndInterface(_ifc: spec.InterfaceType) { return; }
protected onInterfaceMethod(_ifc: spec.InterfaceType, _method: spec.Method) { return; }
protected onInterfaceMethodOverload(_ifc: spec.InterfaceType, _overload: spec.Method, _originalMethod: spec.Method) { return; }
protected onInterfaceProperty(_ifc: spec.InterfaceType, _prop: spec.Property) { return; }
protected onProperty(_cls: spec.ClassType, _prop: spec.Property) { return; }
protected onStaticProperty(_cls: spec.ClassType, _prop: spec.Property) { return; }
protected onUnionProperty(_cls: spec.ClassType, _prop: spec.Property, _union: spec.UnionTypeReference) { return; }
protected onMethod(_cls: spec.ClassType, _method: spec.Method) { return; }
protected onMethodOverload(_cls: spec.ClassType, _overload: spec.Method, _originalMethod: spec.Method) { return; }
protected onStaticMethod(_cls: spec.ClassType, _method: spec.Method) { return; }
protected onStaticMethodOverload(_cls: spec.ClassType, _overload: spec.Method, _originalMethod: spec.Method) { return; }
}
3 changes: 3 additions & 0 deletions packages/jsii-ruby-runtime/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
project/version.txt
project/test/jsii-calc
project/resources
14 changes: 14 additions & 0 deletions packages/jsii-ruby-runtime/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail
res="./project/resources"

version=$(node -e "console.log(require('./package.json').version)")
echo "${version}" > project/version.txt

# embed jsii-runtime as a resource
mkdir -p ${res}
rsync -av node_modules/jsii-runtime/dist/ ${res}

# generate jsii-calc for ruby
mkdir -p project/test/jsii-calc
jsii-pacmak -t ruby -o project/test/jsii-calc --recurse node_modules/jsii-calc
32 changes: 32 additions & 0 deletions packages/jsii-ruby-runtime/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "jsii-ruby-runtime",
"version": "0.5.0-beta",
"description": "Ruby client for jsii runtime",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"gen": "/bin/bash generate.sh",
"deps": "cd project && bundle install",
"lint": "cd project && rubocop",
"build": "npm run gen && npm run deps && npm run lint && cd project && gem build *.gemspec",
"test": "cd project && ruby test/suite.rb"
},
"devDependencies": {
"@types/node": "^9.6.18",
"jsii-calc": "^0.5.0-beta",
"jsii-pacmak": "^0.5.0-beta",
"jsii-runtime": "^0.5.0-beta",
"typescript": "^2.9.2"
},
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
"email": "aws-jsii@amazon.com"
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/awslabs/jsii.git"
},
"homepage": "https://github.com/awslabs/jsii"
}
17 changes: 17 additions & 0 deletions packages/jsii-ruby-runtime/project/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Metrics/LineLength:
Max: 200

Metrics/MethodLength:
Max: 1000

Layout/IndentHash:
EnforcedStyle: consistent

Style/GuardClause:
Enabled: false

Style/IfUnlessModifier:
Enabled: false

Metrics/AbcSize:
Enabled: false
3 changes: 3 additions & 0 deletions packages/jsii-ruby-runtime/project/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source 'https://rubygems.org'

gem 'rubocop', require: false
29 changes: 29 additions & 0 deletions packages/jsii-ruby-runtime/project/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.0)
jaro_winkler (1.5.1)
parallel (1.12.1)
parser (2.5.1.2)
ast (~> 2.4.0)
powerpack (0.1.2)
rainbow (3.0.0)
rubocop (0.58.2)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
powerpack (~> 0.1)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0)
unicode-display_width (1.4.0)

PLATFORMS
ruby

DEPENDENCIES
rubocop

BUNDLED WITH
1.16.3
14 changes: 14 additions & 0 deletions packages/jsii-ruby-runtime/project/jsii_runtime.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require 'json'
package_json_path = File.join(File.dirname(__FILE__), '..', 'package.json')
pkg = JSON.parse(File.read(package_json_path))

Gem::Specification.new do |s|
s.name = 'jsii_runtime'
s.version = pkg['version']
s.licenses = pkg['license']
s.summary = pkg['description']
s.authors = pkg['author']
s.files = Dir['lib/**']
puts s.files
s.require_paths = ['lib']
end
126 changes: 126 additions & 0 deletions packages/jsii-ruby-runtime/project/lib/jsii_runtime.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
require 'open3'
require 'logger'
require 'json'
require_relative 'jsii_runtime/errors'

module Aws
module Jsii
# Represents the jsii-runtime
class Runtime
def initialize(debug: false)
@logger = Logger.new(STDERR)

@debug = debug
@logger.level = debug ? Logger::DEBUG : Logger::INFO

runtime_js = File.join(File.dirname(__FILE__), '..', 'resources', 'jsii-runtime.js')
@logger.debug("jsii-runtime at: #{runtime_js}")
env = {}
env['JSII_DEBUG'] = '1' if @debug
@stdin, @stdout, wait_thr = Open3.popen2(env, 'node', runtime_js)

@logger.debug("jsii-runtime started on pid #{wait_thr.pid}")

# ensure version compat.
handshake

at_exit { close }
end

def close
@logger.debug('closing jsii-runtime child-process streams...')
@stdin.close
@stdout.close
end

def self.define_api(api)
define_method(api) do |**opts|
request_response(api: api, **opts)
end
end

define_api(:load)
define_api(:create)
define_api(:del)
define_api(:get)
define_api(:sget)
define_api(:set)
define_api(:sset)
define_api(:invoke)
define_api(:sinvoke)
define_api(:begin)
define_api(:end)
define_api(:callbacks)
define_api(:complete)
define_api(:naming)
define_api(:stats)

def on_callback(&blk)
@callback_handler = blk
end

private

def request_response(req)
req_s = JSON.generate(req.delete_if { |_, v| v.nil? })

@logger.debug("> #{req_s}")
@stdin.puts(req_s)

resp = read_next_response
@logger.debug("< #{JSON.generate(resp)}")

return process_error(resp) if resp['error']
return process_callback(resp) if resp['callback']

# nil "ok" means undefined result (or void).
resp['ok']
end

def read_next_response
line = @stdout.readline
@logger.debug("line: #{line}")
JSON.parse(line)
end

def handshake
hello = read_next_response
version = hello['hello']

expected_version = File.read(File.join(File.dirname(__FILE__), '..', 'version.txt')).strip
expected_version_string = "jsii-runtime@#{expected_version}"

if version != expected_version_string
raise JsiiError, "Invalid jsii-runtime handshake version '#{version}'. Expected: '#{expected_version_string}'"
end
end

def process_error(resp)
message = resp['error']
stack = resp['stack']
message += "\n" + stack unless stack.nil?
raise JsiiError, message
end

def process_callback(resp)
raise JsiiError, 'no callback handler registered with on_callback' if @callback_handler.nil?
callback = resp['callback']

result = nil
err = nil

begin
result = @callback_handler.call(callback)
rescue StandardError => e
err = e
end

request_response(complete: {
cbid: callback['cbid'],
err: err,
result: result
})
end
end
end
end
6 changes: 6 additions & 0 deletions packages/jsii-ruby-runtime/project/lib/jsii_runtime/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Aws
module Jsii
class JsiiError < StandardError
end
end
end
Loading

0 comments on commit ebdc6e8

Please sign in to comment.