diff --git a/.gitignore b/.gitignore index ac6d1c17..76658194 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,7 @@ Gemfile.lock /pkg/ /spec/reports/ /tmp/ -lib/mini_racer_extension.so +lib/*.so +/lib/sq_mini_racer/ +/ext/mini_racer_extension/mini_racer.creator.* *.bundle diff --git a/.travis.yml b/.travis.yml index c9933f4f..6ec90548 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,13 @@ language: ruby rvm: + - 1.9 + - 2.0 + - 2.1 + - 2.2 - 2.3 - 2.4 - 2.5 - ruby-head -matrix: - include: - - rvm: 2.5.1 - os: osx - osx_image: xcode9.4 - - rvm: 2.4.0 - os: osx - osx_image: xcode8.3 - - rvm: 2.4.0 - os: osx - osx_image: xcode7.3 dist: trusty sudo: true before_install: diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..c872fda5 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,50 @@ +def test(version, env='') { + node('agent_ruby_build') { + unstash 'git' + + sh "${env} ./test_me.sh ${version}" + } +} + +try { + + stage('Checkout scm') { + node('agent_ruby_build') { + checkout scm + + sh 'git clean -ffdx' + + stash includes: '**/*', name: 'git' + } + } + + stage('Testing') { + parallel(failFast: false, + "1.9.3-p551" : { test("1.9.3-p551") }, + "2.0.0-p647" : { test("2.0.0-p647") }, + "2.2.3" : { test("2.2.3") }, + "2.3.3" : { test("2.3.3") }, + "2.4.3" : { test("2.4.3") }, + "2.5.0" : { test("2.5.0") }, + ) + } + + stage('Building 2.3.3') { + node('agent_ruby_build') { + sh './build_me.sh 2.3.3' + + if (env.BRANCH_NAME == 'master') { + archiveArtifacts 'pkg/sq_mini_racer-*.gem' + } + } + } +} catch (e) { + currentBuild.result = "FAILED" + notifyFailed() + throw e +} + + +def notifyFailed() { + slackSend(color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})") +} diff --git a/README.md b/README.md index 8b8a62d7..2e4a9249 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ +# Sqreen's fork of mini\_racer + +This is a fork of [`mini_racer`](https://github.com/discourse/mini_racer), changed to allow coexistence with other versions of mainline mini\_racer and with therubyracer, and to improve compatibility with old versions of Ruby. Otherwise, it should track mainstream mini\_racer. It provides extra functionality to the [`sqreen`](https://rubygems.org/gems/sqreen/) gem. + # MiniRacer -[![Build Status](https://travis-ci.org/discourse/mini_racer.svg?branch=master)](https://travis-ci.org/discourse/mini_racer) +[![Build Status](https://travis-ci.org/sqreen/mini_racer.svg?branch=master)](https://travis-ci.org/sqreen/mini_racer) Minimal, modern embedded V8 for Ruby. MiniRacer provides a minimal two way bridge between the V8 JavaScript engine and Ruby. -It was created as an alternative to the excellent [therubyracer](https://github.com/cowboyd/therubyracer). Unlike therubyracer, mini_racer only implements a minimal bridge. This reduces the surface area making upgrading v8 much simpler and exhaustive testing simpler. +It was created as an alternative to the excellent [therubyracer](https://github.com/cowboyd/therubyracer). Unlike therubyracer, mini\_racer only implements a minimal bridge. This reduces the surface area making upgrading v8 much simpler and exhaustive testing simpler. MiniRacer has an adapter for [execjs](https://github.com/rails/execjs) so it can be used directly with Rails projects to minify assets, run babel or compile CoffeeScript. diff --git a/Rakefile b/Rakefile index 1dad33da..7ad3fc6b 100644 --- a/Rakefile +++ b/Rakefile @@ -10,8 +10,11 @@ end task :default => [:compile, :test] -gem = Gem::Specification.load( File.dirname(__FILE__) + '/mini_racer.gemspec' ) -Rake::ExtensionTask.new( 'mini_racer_extension', gem ) +gem = Gem::Specification.load( File.dirname(__FILE__) + '/sq_mini_racer.gemspec' ) +Rake::ExtensionTask.new( 'mini_racer_extension', gem ) do |ext| + ext.name = 'sq_mini_racer_extension' +end +Rake::ExtensionTask.new('prv_ext_loader', gem) # via http://blog.flavorjon.es/2009/06/easily-valgrind-gdb-your-ruby-c.html diff --git a/bench.rb b/bench.rb new file mode 100644 index 00000000..6b767b64 --- /dev/null +++ b/bench.rb @@ -0,0 +1,13 @@ +require 'mini_racer' +MiniRacer::Platform.set_flags! :perf_basic_prof +c = MiniRacer::Context.new +res = nil +script = File.read('small.json'); +c.eval < $PUBLISH_VERSION + else + git push origin gem-$version + fi; +fi; diff --git a/ext/mini_racer_extension/compat.hpp b/ext/mini_racer_extension/compat.hpp new file mode 100644 index 00000000..e903ce66 --- /dev/null +++ b/ext/mini_racer_extension/compat.hpp @@ -0,0 +1,15 @@ +#ifndef COMPAT_HPP +#define COMPAT_HPP + +#include + +#ifdef PRIsVALUE +# define RB_OBJ_CLASSNAME(obj) rb_obj_class(obj) +# define RB_OBJ_STRING(obj) (obj) +#else +# define PRIsVALUE "s" +# define RB_OBJ_CLASSNAME(obj) rb_obj_classname(obj) +# define RB_OBJ_STRING(obj) StringValueCStr(obj) +#endif + +#endif // COMPAT_HPP diff --git a/ext/mini_racer_extension/extconf.rb b/ext/mini_racer_extension/extconf.rb index cd980b38..6d8f5640 100644 --- a/ext/mini_racer_extension/extconf.rb +++ b/ext/mini_racer_extension/extconf.rb @@ -1,5 +1,5 @@ require 'mkmf' -require 'libv8' +require 'fileutils' have_library('pthread') have_library('objc') if RUBY_PLATFORM =~ /darwin/ @@ -9,6 +9,7 @@ $CPPFLAGS += " -fPIC" unless $CPPFLAGS.split.include? "-rdynamic" or RUBY_PLATFORM =~ /darwin/ $CPPFLAGS += " -std=c++0x" $CPPFLAGS += " -fpermissive" +$CPPFLAGS += " -fno-omit-frame-pointer" $CPPFLAGS += " -Wno-reserved-user-defined-literal" if RUBY_PLATFORM =~ /darwin/ $LDFLAGS.insert 0, " -stdlib=libstdc++ " if RUBY_PLATFORM =~ /darwin/ @@ -50,6 +51,37 @@ CONFIG['debugflags'] << ' -ggdb3 -O0' end -Libv8.configure_makefile +LIBV8_VERSION = '6.7.288.46.1' +libv8_rb = Dir.glob('**/libv8.rb').first +FileUtils.mkdir_p('gemdir') +unless libv8_rb + puts "Will try downloading libv8 gem, version #{LIBV8_VERSION}" + `gem install --version '= #{LIBV8_VERSION}' --install-dir gemdir libv8` + unless $?.success? + warn < #include +#include +#if RUBY_API_VERSION_MAJOR > 1 #include +#endif #include #include #include @@ -9,6 +12,7 @@ #include #include #include +#include "compat.hpp" using namespace v8; @@ -154,7 +158,7 @@ static VALUE rb_platform_set_flag_as_str(VALUE _klass, VALUE flag_as_str) { if(TYPE(flag_as_str) != T_STRING) { rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE" (should be a string)", - rb_obj_class(flag_as_str)); + RB_OBJ_CLASSNAME(flag_as_str)); } platform_lock.lock(); @@ -285,7 +289,11 @@ static void prepare_result(MaybeLocal v8res, } } -void* +#if RUBY_API_VERSION_MAJOR > 1 +static void* +#else +static VALUE +#endif nogvl_context_eval(void* arg) { EvalParams* eval_params = (EvalParams*)arg; @@ -344,7 +352,7 @@ nogvl_context_eval(void* arg) { isolate->SetData(IN_GVL, (void*)true); - return NULL; + return 0; } // assumes isolate locking is in place @@ -545,7 +553,7 @@ static VALUE rb_snapshot_load(VALUE self, VALUE str) { if(TYPE(str) != T_STRING) { rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)", - rb_obj_class(str)); + RB_OBJ_CLASSNAME(str)); } init_v8(); @@ -568,7 +576,7 @@ static VALUE rb_snapshot_warmup_unsafe(VALUE self, VALUE str) { if(TYPE(str) != T_STRING) { rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)", - rb_obj_class(str)); + RB_OBJ_CLASSNAME(str)); } init_v8(); @@ -784,11 +792,11 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) { if(TYPE(str) != T_STRING) { rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be a string)", - rb_obj_class(str)); + RB_OBJ_CLASSNAME(str)); } if(filename != Qnil && TYPE(filename) != T_STRING) { rb_raise(rb_eArgError, "wrong type argument %" PRIsVALUE " (should be nil or a string)", - rb_obj_class(filename)); + RB_OBJ_CLASSNAME(filename)); } { @@ -827,7 +835,11 @@ static VALUE rb_context_eval_unsafe(VALUE self, VALUE str, VALUE filename) { eval_result.message = NULL; eval_result.backtrace = NULL; +#if RUBY_API_VERSION_MAJOR > 1 rb_thread_call_without_gvl(nogvl_context_eval, &eval_params, unblock_eval, &eval_params); +#else + rb_thread_blocking_region(nogvl_context_eval, &eval_params, unblock_eval, &eval_params); +#endif } return convert_result_to_ruby(self, eval_result); @@ -942,6 +954,10 @@ gvl_ruby_callback(void* data) { return NULL; } +#if RUBY_API_VERSION_MAJOR < 2 +extern "C" void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1); +#endif + static void ruby_callback(const FunctionCallbackInfo& args) { bool has_gvl = (bool)args.GetIsolate()->GetData(IN_GVL); @@ -1015,11 +1031,11 @@ static VALUE rb_external_function_notify_v8(VALUE self) { // always raise out of V8 context if (parse_error) { - rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, parent_object); + rb_raise(rb_eParseError, "Invalid object %" PRIsVALUE, RB_OBJ_STRING(parent_object)); } if (attach_error) { - rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", parent_object); + rb_raise(rb_eParseError, "Was expecting %" PRIsVALUE" to be an object", RB_OBJ_STRING(parent_object)); } return Qnil; @@ -1208,12 +1224,16 @@ rb_context_dispose(VALUE self) { return Qnil; } +#if RUBY_API_VERSION_MAJOR > 1 static void* +#else +static VALUE +#endif nogvl_context_call(void *args) { FunctionCall *call = (FunctionCall *) args; if (!call) { - return NULL; + return 0; } Isolate* isolate = call->context_info->isolate_info->isolate; @@ -1239,7 +1259,7 @@ nogvl_context_call(void *args) { isolate->SetData(IN_GVL, (void*)true); - return NULL; + return 0; } static void unblock_function(void *args) { @@ -1312,8 +1332,11 @@ rb_context_call_unsafe(int argc, VALUE *argv, VALUE self) { call.argv[i] = convert_ruby_to_v8(isolate, call_argv[i]); } } - +#if RUBY_API_VERSION_MAJOR > 1 rb_thread_call_without_gvl(nogvl_context_call, &call, unblock_function, &call); +#else + rb_thread_blocking_region(nogvl_context_call, &call, unblock_function, &call); +#endif free(call.argv); } @@ -1341,9 +1364,10 @@ static VALUE rb_context_create_isolate_value(VALUE self) { extern "C" { - void Init_mini_racer_extension ( void ) + void Init_sq_mini_racer_extension ( void ) { - VALUE rb_mMiniRacer = rb_define_module("MiniRacer"); + VALUE rb_mSqreen = rb_define_module("Sqreen"); + VALUE rb_mMiniRacer = rb_define_module_under(rb_mSqreen, "MiniRacer"); rb_cContext = rb_define_class_under(rb_mMiniRacer, "Context", rb_cObject); rb_cSnapshot = rb_define_class_under(rb_mMiniRacer, "Snapshot", rb_cObject); rb_cIsolate = rb_define_class_under(rb_mMiniRacer, "Isolate", rb_cObject); diff --git a/ext/prv_ext_loader/extconf.rb b/ext/prv_ext_loader/extconf.rb new file mode 100644 index 00000000..10b8df2b --- /dev/null +++ b/ext/prv_ext_loader/extconf.rb @@ -0,0 +1,5 @@ +require 'mkmf' + +extension_name = 'prv_ext_loader' +dir_config extension_name +create_makefile extension_name diff --git a/ext/prv_ext_loader/prv_ext_loader.c b/ext/prv_ext_loader/prv_ext_loader.c new file mode 100644 index 00000000..e0fe1083 --- /dev/null +++ b/ext/prv_ext_loader/prv_ext_loader.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +void Init_prv_ext_loader(void); + +static void *_dln_load(const char *file); + +static VALUE _load_shared_lib(VALUE self, volatile VALUE fname) +{ + (void) self; + + // check that path is not tainted + SafeStringValue(fname); + + FilePathValue(fname); + VALUE path = rb_str_encode_ospath(fname); + + char *loc = StringValueCStr(path); + void *handle = _dln_load(loc); + + return handle ? Qtrue : Qfalse; +} + +#define INIT_FUNC_PREFIX ((char[]) {'I', 'n', 'i', 't', '_'}) +#define INIT_FUNCNAME(buf, file) do { \ + const char *base = (file); \ + const size_t flen = _init_funcname(&base); \ + const size_t plen = sizeof(INIT_FUNC_PREFIX); \ + char *const tmp = ALLOCA_N(char, plen + flen + 1); \ + memcpy(tmp, INIT_FUNC_PREFIX, plen); \ + memcpy(tmp+plen, base, flen); \ + tmp[plen+flen] = '\0'; \ + *(buf) = tmp; \ +} while(0) + +static size_t _init_funcname(const char **file) +{ + const char *p = *file, + *base, + *dot = NULL; + + for (base = p; *p; p++) { /* Find position of last '/' */ + if (*p == '.' && !dot) { + dot = p; + } + if (*p == '/') { + base = p + 1; + dot = NULL; + } + } + *file = base; + return (uintptr_t) ((dot ? dot : p) - base); +} + +static void *_dln_load(const char *file) +{ + char *buf; + const char *error; +#define DLN_ERROR() (error = dlerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error)) + + void *handle; + void (*init_fct)(void); + + INIT_FUNCNAME(&buf, file); + + /* Load file */ + if ((handle = dlopen(file, RTLD_LAZY|RTLD_LOCAL|RTLD_DEEPBIND)) == NULL) { + DLN_ERROR(); + goto failed; + } +#if defined(RUBY_EXPORT) + { + static const char incompatible[] = "incompatible library version"; + void *ex = dlsym(handle, "ruby_xmalloc"); + if (ex && ex != (void *) &ruby_xmalloc) { + +# if defined __APPLE__ + /* dlclose() segfaults */ + rb_fatal("%s - %s", incompatible, file); +# else + dlclose(handle); + error = incompatible; + goto failed; +#endif + } + } +# endif + + init_fct = (void (*)(void)) dlsym(handle, buf); + if (init_fct == NULL) { + error = DLN_ERROR(); + dlclose(handle); + goto failed; + } + + /* Call the init code */ + (*init_fct)(); + + return handle; + +failed: + rb_raise(rb_eLoadError, "%s", error); +} + +void Init_prv_ext_loader() +{ + VALUE mSqreen = rb_define_module("Sqreen"); + VALUE mPrvExtLoader = rb_define_module_under(mSqreen, "PrvExtLoader"); + rb_define_singleton_method(mPrvExtLoader, "load", _load_shared_lib, 1); +} diff --git a/lib/mini_racer/version.rb b/lib/mini_racer/version.rb deleted file mode 100644 index 7517c10a..00000000 --- a/lib/mini_racer/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module MiniRacer - VERSION = "0.2.0" -end diff --git a/lib/mini_racer.rb b/lib/sqreen/mini_racer.rb similarity index 95% rename from lib/mini_racer.rb rename to lib/sqreen/mini_racer.rb index 74ffefa7..a1d98259 100644 --- a/lib/mini_racer.rb +++ b/lib/sqreen/mini_racer.rb @@ -1,8 +1,18 @@ -require "mini_racer/version" -require "mini_racer_extension" +require "sqreen/mini_racer/version" require "thread" require "json" +require "prv_ext_loader" +require "pathname" +module Sqreen + shlib = $LOAD_PATH + .map { |p| (Pathname.new(p) + 'sq_mini_racer_extension.so') } + .find { |p| p.file? } + + raise LoadError, "could not find sq_mini_racer.so" unless shlib + PrvExtLoader.load shlib.to_s + +# rubocop:disable IndentationConsistency module MiniRacer class Error < ::StandardError; end @@ -66,8 +76,8 @@ def initialize(snapshot = nil) class Platform class << self - def set_flags!(*args, **kwargs) - flags_to_strings([args, kwargs]).each do |flag| + def set_flags!(*args) + flags_to_strings(args).each do |flag| # defined in the C class set_flag_as_str!(flag) end @@ -345,3 +355,4 @@ def warmup!(src) end end end +end diff --git a/lib/sqreen/mini_racer/version.rb b/lib/sqreen/mini_racer/version.rb new file mode 100644 index 00000000..5d53c25e --- /dev/null +++ b/lib/sqreen/mini_racer/version.rb @@ -0,0 +1,5 @@ +module Sqreen +module MiniRacer + VERSION = "0.2.0.beta1" +end +end diff --git a/mini_racer.gemspec b/mini_racer.gemspec index 81c55a69..c316142b 100644 --- a/mini_racer.gemspec +++ b/mini_racer.gemspec @@ -1,11 +1,11 @@ # coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require 'mini_racer/version' +require 'sqreen/mini_racer/version' Gem::Specification.new do |spec| - spec.name = "mini_racer" - spec.version = MiniRacer::VERSION + spec.name = "sq_mini_racer" + spec.version = Sqreen::MiniRacer::VERSION spec.authors = ["Sam Saffron"] spec.email = ["sam.saffron@gmail.com"] @@ -15,7 +15,8 @@ Gem::Specification.new do |spec| spec.license = "MIT" - spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(benchmark|test|spec|features)/}) } + REJECTS = %r{\A((benchmark|test|spec|features)/|bench\.rb|.+\.sh|Jenkinsfile)} + spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(REJECTS) } spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] @@ -25,10 +26,9 @@ Gem::Specification.new do |spec| spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "rake-compiler" - spec.add_dependency 'libv8', '>= 6.3' spec.require_paths = ["lib", "ext"] - spec.extensions = ["ext/mini_racer_extension/extconf.rb"] + spec.extensions = ["ext/mini_racer_extension/extconf.rb", "ext/prv_ext_loader/extconf.rb"] - spec.required_ruby_version = '>= 2.3' + spec.required_ruby_version = '>= 1.9.3' end diff --git a/test/function_test.rb b/test/function_test.rb index 84e1a074..4b7d4b10 100644 --- a/test/function_test.rb +++ b/test/function_test.rb @@ -1,6 +1,7 @@ require 'test_helper' require 'timeout' +module Sqreen class MiniRacerFunctionTest < Minitest::Test def test_fun context = MiniRacer::Context.new @@ -86,3 +87,4 @@ def test_do_not_hang_with_concurrent_calls assert_equal thread_count, joined_thread_count end end +end diff --git a/test/mini_racer_test.rb b/test/mini_racer_test.rb index f5cd38db..7b7a107e 100644 --- a/test/mini_racer_test.rb +++ b/test/mini_racer_test.rb @@ -2,6 +2,7 @@ require 'date' require 'test_helper' +module Sqreen class MiniRacerTest < Minitest::Test # see `test_platform_set_flags_works` below MiniRacer::Platform.set_flags! :use_strict @@ -19,7 +20,7 @@ def test_segfault end def test_that_it_has_a_version_number - refute_nil ::MiniRacer::VERSION + refute_nil MiniRacer::VERSION end def test_types @@ -742,3 +743,4 @@ def test_isolate_is_nil_after_disposal assert_nil context.isolate end end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 98792b08..ab182eaf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,5 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) -require 'mini_racer' +require 'sqreen/mini_racer' require 'minitest/pride' require 'minitest/autorun' diff --git a/test_me.sh b/test_me.sh new file mode 100755 index 00000000..7b6ed9a5 --- /dev/null +++ b/test_me.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +rm -f Gemfile.lock + +set -x + +if [ $# -eq 1 ]; then + eval "$(rbenv init -)" + rbenv shell $1 +fi; + +set -e + +ruby --version +gem --version + +rbenv exec bundle install --no-deployment --path vendor/bundle + +rbenv exec ruby -v +rbenv exec bundle exec rake compile test